process_tail 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -0
- data/Gemfile +2 -0
- data/README.md +2 -1
- data/bin/process_tail +11 -0
- data/ext/process_tail/extconf.rb +3 -2
- data/ext/process_tail/process_tail.c +190 -83
- data/lib/process_tail/version.rb +1 -1
- data/lib/process_tail.rb +46 -15
- data/spec/process_tail_spec.rb +62 -10
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85d86bdf54735a348134ab9abceec3610882fe23
|
4
|
+
data.tar.gz: 9f1e65346bd355e4fb1b9f9c83fdf8c45a16a224
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e3da11ae5cece8697449bcea8307bc34e1af5b6cb3a39834e96d56f2b32d6306e4ac9070909c79bab0f2486f319a15de408d13df6982d9d3fa367163920c8387
|
7
|
+
data.tar.gz: 30b6556d39cf4b06169e518a69fe7a8c1b0b03a05e0f0db97bcd6abafee42625622048885fbc7452dfbf48cec4915983c2feb10aa6aae5ea132a7a7b8c770b56
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# ProcessTail
|
1
|
+
# ProcessTail [![Build Status](https://travis-ci.org/hibariya/process_tail.svg?branch=master)](https://travis-ci.org/hibariya/process_tail)
|
2
2
|
|
3
3
|
https://github.com/hibariya/process_tail
|
4
4
|
|
@@ -11,6 +11,7 @@ So you can get other process outputs.
|
|
11
11
|
|
12
12
|
* Mac OSX and Windows are not supported at the moment
|
13
13
|
* SEGV occures occasionally
|
14
|
+
* Multithreaded process support
|
14
15
|
|
15
16
|
## Installation
|
16
17
|
|
data/bin/process_tail
ADDED
data/ext/process_tail/extconf.rb
CHANGED
@@ -4,10 +4,11 @@ require 'mkmf'
|
|
4
4
|
abort "missing #{h}" unless have_header(h)
|
5
5
|
end
|
6
6
|
|
7
|
-
abort 'missing ptrace' unless have_func('ptrace', 'sys/ptrace.h')
|
8
|
-
|
9
7
|
%w(orig_rax rdi rdx rsi rdx).each do |m|
|
10
8
|
abort "missing #{m}" unless have_struct_member('struct user_regs_struct', m, 'sys/user.h')
|
11
9
|
end
|
12
10
|
|
11
|
+
abort 'missing __WALL' unless have_const('__WALL', 'sys/wait.h')
|
12
|
+
abort 'missing PTRACE_O_TRACESYSGOOD' unless have_const('PTRACE_O_TRACESYSGOOD', 'sys/ptrace.h')
|
13
|
+
|
13
14
|
create_makefile 'process_tail/process_tail'
|
@@ -1,5 +1,3 @@
|
|
1
|
-
#include <errno.h>
|
2
|
-
#include <stdio.h>
|
3
1
|
#include <stdlib.h>
|
4
2
|
#include <string.h>
|
5
3
|
#include <sys/ptrace.h>
|
@@ -10,148 +8,257 @@
|
|
10
8
|
#include <ruby.h>
|
11
9
|
#include <ruby/thread.h>
|
12
10
|
|
13
|
-
struct
|
14
|
-
process_tail_args {
|
11
|
+
typedef struct pt_tracee {
|
15
12
|
pid_t pid;
|
13
|
+
int activated;
|
14
|
+
int dead;
|
15
|
+
struct pt_tracee *next;
|
16
|
+
} pt_tracee_t;
|
17
|
+
|
18
|
+
typedef struct pt_wait_args {
|
16
19
|
int *status;
|
17
|
-
|
18
|
-
|
19
|
-
};
|
20
|
+
pid_t pid;
|
21
|
+
} pt_wait_args_t;
|
20
22
|
|
21
|
-
static
|
22
|
-
|
23
|
+
static pt_tracee_t *
|
24
|
+
pt_tracee_add(pt_tracee_t **headpp, pid_t pid)
|
23
25
|
{
|
24
|
-
|
26
|
+
pt_tracee_t *tracee = malloc(sizeof(pt_tracee_t));
|
25
27
|
|
26
|
-
|
28
|
+
if (tracee == NULL) {
|
29
|
+
exit(EXIT_FAILURE);
|
30
|
+
}
|
27
31
|
|
28
|
-
|
32
|
+
tracee->pid = pid;
|
33
|
+
tracee->activated = 0;
|
34
|
+
tracee->dead = 0;
|
35
|
+
tracee->next = (struct pt_tracee *)*headpp;
|
36
|
+
|
37
|
+
(*headpp) = tracee;
|
38
|
+
|
39
|
+
return tracee;
|
40
|
+
}
|
41
|
+
|
42
|
+
static pt_tracee_t *
|
43
|
+
pt_tracee_find_or_add(pt_tracee_t **headpp, pid_t pid)
|
44
|
+
{
|
45
|
+
pt_tracee_t *tracee = *headpp;
|
46
|
+
|
47
|
+
while (tracee != NULL) {
|
48
|
+
if (tracee->pid == pid) {
|
49
|
+
return tracee;
|
50
|
+
}
|
51
|
+
|
52
|
+
tracee = tracee->next;
|
53
|
+
}
|
54
|
+
|
55
|
+
return pt_tracee_add(headpp, pid);
|
29
56
|
}
|
30
57
|
|
31
58
|
static int
|
32
|
-
|
59
|
+
pt_tracee_wipedoutq(pt_tracee_t *headp)
|
33
60
|
{
|
34
|
-
|
61
|
+
pt_tracee_t *tracee = headp;
|
35
62
|
|
36
|
-
|
37
|
-
|
38
|
-
|
63
|
+
while (tracee != NULL) {
|
64
|
+
if (tracee->dead == 0) {
|
65
|
+
return 0;
|
66
|
+
}
|
67
|
+
|
68
|
+
tracee = tracee->next;
|
39
69
|
}
|
40
70
|
|
41
|
-
|
71
|
+
return 1;
|
72
|
+
}
|
73
|
+
|
74
|
+
static void
|
75
|
+
pt_tracee_free(pt_tracee_t **headpp)
|
76
|
+
{
|
77
|
+
pt_tracee_t *tracee = *headpp;
|
42
78
|
|
43
|
-
|
79
|
+
if (tracee->next) {
|
80
|
+
pt_tracee_free(&tracee->next);
|
81
|
+
}
|
82
|
+
|
83
|
+
free(tracee);
|
84
|
+
(*headpp) = NULL;
|
44
85
|
}
|
45
86
|
|
46
|
-
static
|
47
|
-
|
87
|
+
static int
|
88
|
+
pt_io_closedq(VALUE io)
|
48
89
|
{
|
49
|
-
|
90
|
+
return rb_funcall(io, rb_intern("closed?"), 0) == Qtrue;
|
91
|
+
}
|
50
92
|
|
51
|
-
|
93
|
+
static void *
|
94
|
+
pt_wait(void *argp)
|
95
|
+
{
|
96
|
+
pt_wait_args_t *args = (pt_wait_args_t *)argp;
|
52
97
|
|
53
|
-
|
98
|
+
args->pid = waitpid(-1, args->status, __WALL);
|
99
|
+
|
100
|
+
return NULL;
|
54
101
|
}
|
55
102
|
|
56
103
|
static void
|
57
|
-
|
104
|
+
pt_get_data(pid_t pid, long addr, char *string, int length)
|
58
105
|
{
|
59
|
-
long data;
|
60
106
|
int i;
|
107
|
+
long data;
|
61
108
|
|
62
|
-
for (i = 0; i <
|
109
|
+
for (i = 0; i < length; i += sizeof(long)) {
|
63
110
|
data = ptrace(PTRACE_PEEKDATA, pid, addr + i);
|
64
111
|
|
65
112
|
memcpy(string + i, &data, sizeof(long));
|
66
113
|
}
|
67
114
|
|
68
|
-
string[
|
115
|
+
string[length] = '\0';
|
69
116
|
}
|
70
117
|
|
71
|
-
static
|
72
|
-
|
118
|
+
static void
|
119
|
+
pt_ptrace_syscall(pid_t pid, long signal)
|
73
120
|
{
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
121
|
+
ptrace(PTRACE_SYSCALL, pid, 0, signal);
|
122
|
+
|
123
|
+
rb_thread_schedule();
|
124
|
+
}
|
125
|
+
|
126
|
+
static void
|
127
|
+
pt_loop(unsigned int fd, VALUE write_io, VALUE read_io, VALUE wait_queue, pt_tracee_t *tracee_headp)
|
128
|
+
{
|
129
|
+
char *string = NULL;
|
130
|
+
int syscall = 0;
|
78
131
|
int status;
|
79
|
-
|
132
|
+
long signal;
|
133
|
+
pid_t pid;
|
134
|
+
pt_tracee_t *tracee;
|
135
|
+
pt_wait_args_t pt_wait_args = {&status};
|
80
136
|
|
81
|
-
|
137
|
+
// NOTE: system call: eax, arguments: rdi, rsi, rdx, r10, r8, r9
|
138
|
+
struct user_regs_struct regs;
|
82
139
|
|
83
|
-
while (
|
84
|
-
rb_thread_call_without_gvl(
|
140
|
+
while (1) {
|
141
|
+
rb_thread_call_without_gvl(pt_wait, &pt_wait_args, RUBY_UBF_PROCESS, NULL);
|
85
142
|
|
86
143
|
signal = 0;
|
144
|
+
pid = pt_wait_args.pid;
|
145
|
+
tracee = pt_tracee_find_or_add(&tracee_headp, pid);
|
146
|
+
|
147
|
+
if (pt_io_closedq(read_io)) {
|
148
|
+
pt_tracee_free(&tracee_headp);
|
87
149
|
|
88
|
-
|
89
|
-
return Qnil;
|
150
|
+
return;
|
90
151
|
}
|
91
152
|
|
92
|
-
if (
|
153
|
+
if (tracee->activated == 0) {
|
154
|
+
ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACESYSGOOD);
|
155
|
+
tracee->activated = 1;
|
156
|
+
|
157
|
+
rb_funcall(wait_queue, rb_intern("enq"), 1, INT2FIX((int)pid));
|
158
|
+
pt_ptrace_syscall(pid, signal);
|
159
|
+
|
93
160
|
continue;
|
94
161
|
}
|
95
162
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
string = malloc(regs.rdx + sizeof(long));
|
107
|
-
process_tail_get_data(args->pid, regs.rsi, string, regs.rdx);
|
108
|
-
|
109
|
-
if (rb_funcall(args->io, rb_intern("closed?"), 0) == Qtrue) {
|
110
|
-
return Qnil;
|
111
|
-
}
|
112
|
-
|
113
|
-
rb_io_write(args->io, rb_str_new_cstr(string));
|
114
|
-
|
115
|
-
free(string);
|
116
|
-
}
|
117
|
-
case SIGVTALRM:
|
118
|
-
break;
|
119
|
-
case SIGSTOP:
|
120
|
-
break;
|
121
|
-
default:
|
122
|
-
signal = WSTOPSIG(status);
|
163
|
+
if (WIFEXITED(status) || WIFSIGNALED(status)) {
|
164
|
+
tracee->dead = 1;
|
165
|
+
|
166
|
+
if (pt_tracee_wipedoutq(tracee_headp)) {
|
167
|
+
pt_tracee_free(&tracee_headp);
|
168
|
+
|
169
|
+
return;
|
170
|
+
}
|
171
|
+
|
172
|
+
continue;
|
123
173
|
}
|
124
174
|
|
125
|
-
|
126
|
-
|
175
|
+
if (!(WIFSTOPPED(status) && WSTOPSIG(status) & 0x80)) {
|
176
|
+
signal = WSTOPSIG(status);
|
177
|
+
pt_ptrace_syscall(pid, signal);
|
127
178
|
|
128
|
-
|
179
|
+
continue;
|
180
|
+
}
|
181
|
+
|
182
|
+
ptrace(PTRACE_GETREGS, pid, 0, ®s);
|
183
|
+
|
184
|
+
if (regs.orig_rax != SYS_write) {
|
185
|
+
pt_ptrace_syscall(pid, signal);
|
186
|
+
|
187
|
+
continue;
|
188
|
+
}
|
189
|
+
|
190
|
+
syscall = 1 - syscall;
|
191
|
+
if (syscall && (fd == 0 || regs.rdi == fd)) {
|
192
|
+
string = malloc(regs.rdx + sizeof(long));
|
193
|
+
|
194
|
+
if (string == NULL) {
|
195
|
+
exit(EXIT_FAILURE);
|
196
|
+
}
|
197
|
+
|
198
|
+
pt_get_data(pid, regs.rsi, string, regs.rdx);
|
199
|
+
|
200
|
+
if (pt_io_closedq(read_io)) {
|
201
|
+
free(string);
|
202
|
+
pt_tracee_free(&tracee_headp);
|
203
|
+
|
204
|
+
return;
|
205
|
+
}
|
206
|
+
|
207
|
+
rb_io_write(write_io, rb_str_new_cstr(string));
|
208
|
+
|
209
|
+
free(string);
|
210
|
+
}
|
211
|
+
|
212
|
+
pt_ptrace_syscall(pid, signal);
|
213
|
+
}
|
129
214
|
}
|
130
215
|
|
131
216
|
static VALUE
|
132
|
-
|
217
|
+
pt_attach(VALUE klass, VALUE pidv)
|
133
218
|
{
|
134
|
-
|
135
|
-
unsigned int fd = (unsigned int)FIX2INT(fdp);
|
219
|
+
int pid = (pid_t)FIX2INT(pidv);
|
136
220
|
|
137
|
-
|
221
|
+
Check_Type(pidv, T_FIXNUM);
|
138
222
|
|
139
|
-
|
140
|
-
|
141
|
-
Check_Type(io, T_FILE);
|
223
|
+
if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
|
224
|
+
rb_sys_fail("PTRACE_ATTACH");
|
142
225
|
|
143
|
-
|
144
|
-
rb_ensure(process_tail_loop, (VALUE)&args, process_tail_detach, (VALUE)&args);
|
226
|
+
return Qnil;
|
145
227
|
}
|
146
228
|
|
229
|
+
return Qtrue;
|
230
|
+
}
|
231
|
+
|
232
|
+
static VALUE
|
233
|
+
pt_detach(VALUE klass, VALUE pidv)
|
234
|
+
{
|
235
|
+
Check_Type(pidv, T_FIXNUM);
|
236
|
+
|
237
|
+
ptrace(PTRACE_DETACH, (pid_t)FIX2INT(pidv), NULL, NULL);
|
238
|
+
|
239
|
+
return Qnil;
|
240
|
+
}
|
241
|
+
|
242
|
+
static VALUE
|
243
|
+
pt_trace(VALUE klass, VALUE fdp, VALUE write_io, VALUE read_io, VALUE wait_queue)
|
244
|
+
{
|
245
|
+
pt_tracee_t *tracee_headp = NULL;
|
246
|
+
|
247
|
+
Check_Type(fdp, T_FIXNUM);
|
248
|
+
Check_Type(write_io, T_FILE);
|
249
|
+
Check_Type(read_io, T_FILE);
|
250
|
+
|
251
|
+
pt_loop((unsigned int)FIX2INT(fdp), write_io, read_io, wait_queue, tracee_headp);
|
252
|
+
|
147
253
|
return Qnil;
|
148
254
|
}
|
149
255
|
|
150
256
|
void
|
151
257
|
Init_process_tail()
|
152
258
|
{
|
153
|
-
VALUE ProcessTail;
|
259
|
+
VALUE ProcessTail = rb_define_module("ProcessTail");
|
154
260
|
|
155
|
-
ProcessTail
|
156
|
-
rb_define_singleton_method(ProcessTail, "do_trace",
|
261
|
+
rb_define_singleton_method(ProcessTail, "attach", pt_attach, 1);
|
262
|
+
rb_define_singleton_method(ProcessTail, "do_trace", pt_trace, 4);
|
263
|
+
rb_define_singleton_method(ProcessTail, "detach", pt_detach, 1);
|
157
264
|
}
|
data/lib/process_tail/version.rb
CHANGED
data/lib/process_tail.rb
CHANGED
@@ -1,37 +1,68 @@
|
|
1
|
+
require 'thread'
|
1
2
|
require 'process_tail/process_tail'
|
2
3
|
require 'process_tail/version'
|
3
4
|
|
4
5
|
module ProcessTail
|
6
|
+
TRACE_LOCK = Mutex.new # NOTE For now, ProcessTail can't trace multiple processes at the same time
|
7
|
+
|
5
8
|
class << self
|
6
9
|
def open(pid, fd = :stdout)
|
7
|
-
|
10
|
+
io = trace(pid, fd)
|
8
11
|
|
9
|
-
block_given? ? yield(
|
12
|
+
block_given? ? yield(io) : io
|
10
13
|
ensure
|
11
|
-
|
12
|
-
read_io.close unless read_io.closed?
|
13
|
-
thread.kill
|
14
|
-
}
|
15
|
-
|
16
|
-
block_given? ? finalize.call : at_exit(&finalize)
|
14
|
+
io.close if block_given? && !io.closed?
|
17
15
|
end
|
18
16
|
|
19
17
|
def trace(pid, fd = :stdout)
|
18
|
+
TRACE_LOCK.synchronize {
|
19
|
+
trace_without_lock(pid, fd)
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def trace_without_lock(pid, fd)
|
20
26
|
read_io, write_io = IO.pipe
|
27
|
+
task_ids = extract_task_ids(pid)
|
28
|
+
wait_queue = Queue.new
|
21
29
|
|
22
30
|
thread = Thread.fork {
|
23
31
|
begin
|
24
|
-
|
32
|
+
extract_task_ids(pid).each do |tid|
|
33
|
+
attach tid
|
34
|
+
end
|
35
|
+
|
36
|
+
do_trace extract_fd(fd), write_io, read_io, wait_queue
|
25
37
|
ensure
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
38
|
+
write_io.close unless write_io.closed?
|
39
|
+
|
40
|
+
task_ids.each do |tid|
|
41
|
+
detach tid
|
42
|
+
end
|
31
43
|
end
|
32
44
|
}
|
33
45
|
|
34
|
-
|
46
|
+
at_exit do
|
47
|
+
thread.kill
|
48
|
+
thread.join
|
49
|
+
end
|
50
|
+
|
51
|
+
wait_all_attach task_ids, wait_queue
|
52
|
+
|
53
|
+
read_io
|
54
|
+
end
|
55
|
+
|
56
|
+
def wait_all_attach(task_ids, waitq)
|
57
|
+
task_ids.size.times do
|
58
|
+
waitq.deq
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def extract_task_ids(pid)
|
63
|
+
Dir.open("/proc/#{pid}/task") {|dir|
|
64
|
+
dir.entries.grep(/^\d+$/).map(&:to_i)
|
65
|
+
}
|
35
66
|
end
|
36
67
|
|
37
68
|
def extract_fd(name)
|
data/spec/process_tail_spec.rb
CHANGED
@@ -1,24 +1,27 @@
|
|
1
1
|
require 'process_tail'
|
2
|
+
require 'timeout'
|
2
3
|
|
3
4
|
describe ProcessTail do
|
4
5
|
def process_maker(&fn)
|
5
6
|
-> {
|
6
|
-
Process.fork {
|
7
|
+
pid = Process.fork {
|
7
8
|
trap :USR1 do
|
8
|
-
sleep 0.1 # XXX :(
|
9
|
-
|
10
9
|
fn.call if fn
|
11
10
|
end
|
12
11
|
|
13
12
|
sleep
|
14
13
|
}
|
14
|
+
|
15
|
+
$recorded_children << pid
|
16
|
+
|
17
|
+
pid
|
15
18
|
}
|
16
19
|
end
|
17
20
|
|
18
21
|
def expect_same_output_and_no_error(fd, method, string)
|
19
|
-
greeting
|
20
|
-
pid
|
21
|
-
io
|
22
|
+
greeting = process_maker { send(method, string) }
|
23
|
+
pid = greeting.call
|
24
|
+
io = ProcessTail.trace(pid, fd)
|
22
25
|
|
23
26
|
Process.kill :USR1, pid # resume child
|
24
27
|
|
@@ -26,9 +29,23 @@ describe ProcessTail do
|
|
26
29
|
expect(io.gets).to eq expected
|
27
30
|
end
|
28
31
|
|
29
|
-
|
32
|
+
io.close
|
33
|
+
end
|
34
|
+
|
35
|
+
around do |example|
|
36
|
+
begin
|
37
|
+
$recorded_children = []
|
38
|
+
|
39
|
+
# at least every examples should be finished within 5 seconds
|
40
|
+
timeout 5 do
|
41
|
+
example.run
|
42
|
+
end
|
30
43
|
|
31
|
-
|
44
|
+
ensure
|
45
|
+
$recorded_children.each do |child|
|
46
|
+
Process.kill :TERM, child
|
47
|
+
end
|
48
|
+
end
|
32
49
|
end
|
33
50
|
|
34
51
|
describe '.open' do
|
@@ -46,7 +63,7 @@ describe ProcessTail do
|
|
46
63
|
pid = process_maker.call
|
47
64
|
read_io = nil
|
48
65
|
|
49
|
-
ProcessTail.open pid, :stdout
|
66
|
+
ProcessTail.open pid, :stdout do |io|
|
50
67
|
read_io = io
|
51
68
|
|
52
69
|
expect(read_io).to_not be_closed
|
@@ -57,9 +74,44 @@ describe ProcessTail do
|
|
57
74
|
end
|
58
75
|
|
59
76
|
describe '.trace' do
|
60
|
-
|
77
|
+
specify 'simple stdout' do
|
61
78
|
expect_same_output_and_no_error :stdout, :puts, 'HELLO'
|
79
|
+
end
|
80
|
+
|
81
|
+
specify 'simple stderr' do
|
62
82
|
expect_same_output_and_no_error :stderr, :warn, 'HELLO'
|
83
|
+
end
|
84
|
+
|
85
|
+
specify 'multithreaded' do
|
86
|
+
pid = Process.fork {
|
87
|
+
trap :USR1 do
|
88
|
+
puts 'HELLO'
|
89
|
+
end
|
90
|
+
|
91
|
+
Thread.fork do
|
92
|
+
trap :USR2 do
|
93
|
+
puts 'HELLO'
|
94
|
+
end
|
95
|
+
|
96
|
+
sleep
|
97
|
+
end
|
98
|
+
|
99
|
+
sleep
|
100
|
+
}
|
101
|
+
|
102
|
+
$recorded_children << pid
|
103
|
+
io = ProcessTail.trace(pid, :stdout)
|
104
|
+
|
105
|
+
Process.kill :USR1, pid
|
106
|
+
Process.kill :USR2, pid
|
107
|
+
|
108
|
+
expect(io.gets).to eq "HELLO\n"
|
109
|
+
expect(io.gets).to eq "HELLO\n"
|
110
|
+
|
111
|
+
io.close
|
112
|
+
end
|
113
|
+
|
114
|
+
specify 'multiline' do
|
63
115
|
expect_same_output_and_no_error :stdout, :puts, "HELLO\nLOVE AND KISSES"
|
64
116
|
end
|
65
117
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: process_tail
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hika Hibariya
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
11
|
+
date: 2015-02-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -69,16 +69,19 @@ dependencies:
|
|
69
69
|
description:
|
70
70
|
email:
|
71
71
|
- hibariya@gmail.com
|
72
|
-
executables:
|
72
|
+
executables:
|
73
|
+
- process_tail
|
73
74
|
extensions:
|
74
75
|
- ext/process_tail/extconf.rb
|
75
76
|
extra_rdoc_files: []
|
76
77
|
files:
|
77
78
|
- ".gitignore"
|
79
|
+
- ".travis.yml"
|
78
80
|
- Gemfile
|
79
81
|
- LICENSE.txt
|
80
82
|
- README.md
|
81
83
|
- Rakefile
|
84
|
+
- bin/process_tail
|
82
85
|
- ext/process_tail/extconf.rb
|
83
86
|
- ext/process_tail/process_tail.c
|
84
87
|
- lib/process_tail.rb
|