io-extra 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/{CHANGES → CHANGES.md} +34 -16
- data/Gemfile +2 -0
- data/LICENSE +177 -0
- data/{MANIFEST → MANIFEST.md} +5 -4
- data/README.md +114 -0
- data/Rakefile +102 -0
- data/certs/djberg96_pub.pem +26 -0
- data/doc/io_extra.txt +85 -0
- data/examples/example_io_extra.rb +53 -0
- data/examples/example_pread.rb +24 -0
- data/examples/writev_benchmark.rb +19 -0
- data/ext/extconf.rb +0 -2
- data/ext/extra.bundle.dSYM/Contents/Info.plist +20 -0
- data/ext/extra.bundle.dSYM/Contents/Resources/Relocations/aarch64/extra.bundle.yml +5 -0
- data/ext/io/extra.c +166 -199
- data/io-extra.gemspec +51 -0
- data/lib/io-extra.rb +18 -0
- data/spec/io_extra_spec.rb +179 -0
- data.tar.gz.sig +0 -0
- metadata +77 -19
- metadata.gz.sig +0 -0
- data/README +0 -97
- data/test/test_io_extra.rb +0 -163
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
##############################################################################
|
|
2
|
+
# example_io_extra.rb
|
|
3
|
+
#
|
|
4
|
+
# This is a small example program for the io-extra library. Modify as you see
|
|
5
|
+
# fit. You can run this via the 'rake example' task.
|
|
6
|
+
##############################################################################
|
|
7
|
+
require_relative "../lib/io-extra"
|
|
8
|
+
puts "VERSION: #{IO::EXTRA_VERSION}"
|
|
9
|
+
|
|
10
|
+
begin
|
|
11
|
+
fh = File.open("foo.txt","w+")
|
|
12
|
+
|
|
13
|
+
puts "DIRECTIO"
|
|
14
|
+
sleep 2
|
|
15
|
+
|
|
16
|
+
p fh.directio?
|
|
17
|
+
fh.directio = IO::DIRECTIO_ON
|
|
18
|
+
p fh.directio?
|
|
19
|
+
|
|
20
|
+
puts "FDWALK"
|
|
21
|
+
sleep 2
|
|
22
|
+
|
|
23
|
+
IO.fdwalk(0){ |handle|
|
|
24
|
+
p handle
|
|
25
|
+
p handle.fileno
|
|
26
|
+
puts
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
=begin
|
|
30
|
+
STDIN.close
|
|
31
|
+
|
|
32
|
+
# Should print "Hello" 2 times
|
|
33
|
+
IO.fdwalk(0){ |fd|
|
|
34
|
+
puts "Hello #{fd}"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
IO.closefrom(0)
|
|
39
|
+
|
|
40
|
+
puts "Done" # Shouldn't see this
|
|
41
|
+
=end
|
|
42
|
+
|
|
43
|
+
puts "IO.writev"
|
|
44
|
+
sleep 2
|
|
45
|
+
|
|
46
|
+
a = (1..1000).map(&:to_s)
|
|
47
|
+
IO.writev(fh, a)
|
|
48
|
+
fh.rewind
|
|
49
|
+
p fh.read
|
|
50
|
+
ensure
|
|
51
|
+
fh.close
|
|
52
|
+
File.delete("foo.txt") if File.exist?("foo.txt")
|
|
53
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
########################################################################
|
|
2
|
+
# example_pread.rb
|
|
3
|
+
#
|
|
4
|
+
# Example program demonstrating the use of IO.pread.
|
|
5
|
+
########################################################################
|
|
6
|
+
require_relative '../lib/io-extra'
|
|
7
|
+
require 'tmpdir'
|
|
8
|
+
|
|
9
|
+
# Create a temporary file with a little data in it.
|
|
10
|
+
file = File.join(Dir.tmpdir, 'pread_test.txt')
|
|
11
|
+
File.open(file, 'w'){ |fh| 100.times{ |n| fh.puts "Hello: #{n}" } }
|
|
12
|
+
|
|
13
|
+
# Read from the file using pread.
|
|
14
|
+
begin
|
|
15
|
+
fh = File.open(file)
|
|
16
|
+
|
|
17
|
+
puts "Handle position before read: #{fh.pos}"
|
|
18
|
+
puts IO.pread(fh, 18, 0)
|
|
19
|
+
|
|
20
|
+
puts "Handle position after read: #{fh.pos}"
|
|
21
|
+
puts IO.pread(fh, 18, 0)
|
|
22
|
+
ensure
|
|
23
|
+
fh.close
|
|
24
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'benchmark'
|
|
2
|
+
require 'io-extra'
|
|
3
|
+
|
|
4
|
+
a = (0..1023).to_a.map(&:to_s)
|
|
5
|
+
|
|
6
|
+
file1 = 'write_test.txt'
|
|
7
|
+
file2 = 'writev_test.txt'
|
|
8
|
+
|
|
9
|
+
fh = File.open(file2, 'w')
|
|
10
|
+
|
|
11
|
+
Benchmark.bm(25) do |x|
|
|
12
|
+
x.report('write'){ 100000.times{ File.write(file1, a.join) } }
|
|
13
|
+
x.report('writev'){ 100000.times{ IO.writev(fh.fileno, a) } }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
fh.close
|
|
17
|
+
|
|
18
|
+
File.delete(file1)
|
|
19
|
+
File.delete(file2)
|
data/ext/extconf.rb
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>CFBundleDevelopmentRegion</key>
|
|
6
|
+
<string>English</string>
|
|
7
|
+
<key>CFBundleIdentifier</key>
|
|
8
|
+
<string>com.apple.xcode.dsym.extra.bundle</string>
|
|
9
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
|
10
|
+
<string>6.0</string>
|
|
11
|
+
<key>CFBundlePackageType</key>
|
|
12
|
+
<string>dSYM</string>
|
|
13
|
+
<key>CFBundleSignature</key>
|
|
14
|
+
<string>????</string>
|
|
15
|
+
<key>CFBundleShortVersionString</key>
|
|
16
|
+
<string>1.0</string>
|
|
17
|
+
<key>CFBundleVersion</key>
|
|
18
|
+
<string>1</string>
|
|
19
|
+
</dict>
|
|
20
|
+
</plist>
|
data/ext/io/extra.c
CHANGED
|
@@ -87,9 +87,18 @@ static int open_max(void){
|
|
|
87
87
|
*/
|
|
88
88
|
static VALUE io_closefrom(VALUE klass, VALUE v_low_fd){
|
|
89
89
|
int i, lowfd;
|
|
90
|
-
int maxfd
|
|
90
|
+
int maxfd;
|
|
91
|
+
|
|
91
92
|
lowfd = NUM2INT(v_low_fd);
|
|
92
93
|
|
|
94
|
+
if(lowfd < 0)
|
|
95
|
+
rb_raise(rb_eArgError, "lowfd must be non-negative");
|
|
96
|
+
|
|
97
|
+
maxfd = open_max();
|
|
98
|
+
|
|
99
|
+
if(maxfd < 0)
|
|
100
|
+
rb_raise(rb_eRuntimeError, "failed to determine maximum file descriptor");
|
|
101
|
+
|
|
93
102
|
for(i = lowfd; i < maxfd; i++) {
|
|
94
103
|
if(!RB_RESERVED_FD_P(i))
|
|
95
104
|
close(i);
|
|
@@ -99,54 +108,7 @@ static VALUE io_closefrom(VALUE klass, VALUE v_low_fd){
|
|
|
99
108
|
}
|
|
100
109
|
|
|
101
110
|
#ifndef HAVE_FDWALK
|
|
102
|
-
|
|
103
|
-
int rv = 0;
|
|
104
|
-
int fd;
|
|
105
|
-
|
|
106
|
-
#ifdef PROC_SELF_FD_DIR
|
|
107
|
-
DIR *dir = opendir(PROC_SELF_FD_DIR);
|
|
108
|
-
|
|
109
|
-
if(dir){ /* procfs may not be mounted... */
|
|
110
|
-
struct dirent *ent;
|
|
111
|
-
int saved_errno;
|
|
112
|
-
int dir_fd = dirfd(dir);
|
|
113
|
-
|
|
114
|
-
while((ent = readdir(dir))){
|
|
115
|
-
char *err = NULL;
|
|
116
|
-
|
|
117
|
-
if(ent->d_name[0] == '.')
|
|
118
|
-
continue;
|
|
119
|
-
|
|
120
|
-
errno = 0;
|
|
121
|
-
fd = (int)strtol(ent->d_name, &err, 10);
|
|
122
|
-
|
|
123
|
-
if (errno || ! err || *err || fd == dir_fd)
|
|
124
|
-
continue;
|
|
125
|
-
|
|
126
|
-
if ((rv = func(data, fd)))
|
|
127
|
-
break;
|
|
128
|
-
}
|
|
129
|
-
saved_errno = errno;
|
|
130
|
-
closedir(dir);
|
|
131
|
-
errno = saved_errno;
|
|
132
|
-
} else
|
|
133
|
-
#endif /* PROC_SELF_FD_DIR */
|
|
134
|
-
{
|
|
135
|
-
int maxfd = open_max();
|
|
136
|
-
|
|
137
|
-
for(fd = 0; fd < maxfd; fd++){
|
|
138
|
-
/* use fcntl to detect whether fd is a valid file descriptor */
|
|
139
|
-
errno = 0;
|
|
140
|
-
if(fcntl(fd, F_GETFD) < 0)
|
|
141
|
-
continue;
|
|
142
|
-
|
|
143
|
-
errno = 0;
|
|
144
|
-
if ((rv = func(data, fd)))
|
|
145
|
-
break;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return rv;
|
|
149
|
-
}
|
|
111
|
+
/* Define HAVE_FDWALK so the io_fdwalk implementation below is compiled */
|
|
150
112
|
#define HAVE_FDWALK
|
|
151
113
|
#endif
|
|
152
114
|
|
|
@@ -169,6 +131,74 @@ static int close_func(void* lowfd, int fd){
|
|
|
169
131
|
return 0;
|
|
170
132
|
}
|
|
171
133
|
|
|
134
|
+
/* Structure to pass data to fdwalk_body and fdwalk_ensure */
|
|
135
|
+
struct fdwalk_data {
|
|
136
|
+
int lowfd;
|
|
137
|
+
#ifdef PROC_SELF_FD_DIR
|
|
138
|
+
DIR *dir;
|
|
139
|
+
#endif
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/* Cleanup function for rb_ensure */
|
|
143
|
+
static VALUE fdwalk_ensure(VALUE arg){
|
|
144
|
+
struct fdwalk_data *data = (struct fdwalk_data *)arg;
|
|
145
|
+
#ifdef PROC_SELF_FD_DIR
|
|
146
|
+
if(data->dir){
|
|
147
|
+
closedir(data->dir);
|
|
148
|
+
data->dir = NULL;
|
|
149
|
+
}
|
|
150
|
+
#endif
|
|
151
|
+
(void)data; /* Suppress unused warning when PROC_SELF_FD_DIR not defined */
|
|
152
|
+
return Qnil;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* Main fdwalk iteration body */
|
|
156
|
+
static VALUE fdwalk_body(VALUE arg){
|
|
157
|
+
struct fdwalk_data *data = (struct fdwalk_data *)arg;
|
|
158
|
+
int fd;
|
|
159
|
+
|
|
160
|
+
#ifdef PROC_SELF_FD_DIR
|
|
161
|
+
if(data->dir){
|
|
162
|
+
struct dirent *ent;
|
|
163
|
+
int dir_fd = dirfd(data->dir);
|
|
164
|
+
|
|
165
|
+
while((ent = readdir(data->dir))){
|
|
166
|
+
char *err = NULL;
|
|
167
|
+
|
|
168
|
+
if(ent->d_name[0] == '.')
|
|
169
|
+
continue;
|
|
170
|
+
|
|
171
|
+
errno = 0;
|
|
172
|
+
fd = (int)strtol(ent->d_name, &err, 10);
|
|
173
|
+
|
|
174
|
+
if (errno || !err || *err || fd == dir_fd)
|
|
175
|
+
continue;
|
|
176
|
+
|
|
177
|
+
/* Validate fd is still open before calling callback (reduces race window) */
|
|
178
|
+
if(fcntl(fd, F_GETFD) < 0)
|
|
179
|
+
continue;
|
|
180
|
+
|
|
181
|
+
close_func(&data->lowfd, fd);
|
|
182
|
+
}
|
|
183
|
+
} else
|
|
184
|
+
#endif /* PROC_SELF_FD_DIR */
|
|
185
|
+
{
|
|
186
|
+
int maxfd = open_max();
|
|
187
|
+
|
|
188
|
+
for(fd = 0; fd < maxfd; fd++){
|
|
189
|
+
/* use fcntl to detect whether fd is a valid file descriptor */
|
|
190
|
+
errno = 0;
|
|
191
|
+
if(fcntl(fd, F_GETFD) < 0)
|
|
192
|
+
continue;
|
|
193
|
+
|
|
194
|
+
errno = 0;
|
|
195
|
+
close_func(&data->lowfd, fd);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return Qnil;
|
|
200
|
+
}
|
|
201
|
+
|
|
172
202
|
/*
|
|
173
203
|
* call-seq:
|
|
174
204
|
* IO.fdwalk(lowfd){ |fh| ... }
|
|
@@ -179,12 +209,19 @@ static int close_func(void* lowfd, int fd){
|
|
|
179
209
|
*/
|
|
180
210
|
static VALUE io_fdwalk(int argc, VALUE* argv, VALUE klass){
|
|
181
211
|
VALUE v_low_fd, v_block;
|
|
182
|
-
|
|
212
|
+
struct fdwalk_data data;
|
|
183
213
|
|
|
184
214
|
rb_scan_args(argc, argv, "1&", &v_low_fd, &v_block);
|
|
185
|
-
lowfd = NUM2INT(v_low_fd);
|
|
215
|
+
data.lowfd = NUM2INT(v_low_fd);
|
|
186
216
|
|
|
187
|
-
|
|
217
|
+
if(data.lowfd < 0)
|
|
218
|
+
rb_raise(rb_eArgError, "lowfd must be non-negative");
|
|
219
|
+
|
|
220
|
+
#ifdef PROC_SELF_FD_DIR
|
|
221
|
+
data.dir = opendir(PROC_SELF_FD_DIR);
|
|
222
|
+
#endif
|
|
223
|
+
|
|
224
|
+
rb_ensure(fdwalk_body, (VALUE)&data, fdwalk_ensure, (VALUE)&data);
|
|
188
225
|
|
|
189
226
|
return klass;
|
|
190
227
|
}
|
|
@@ -284,140 +321,51 @@ static VALUE io_set_directio(VALUE self, VALUE v_advice){
|
|
|
284
321
|
}
|
|
285
322
|
#endif
|
|
286
323
|
|
|
287
|
-
|
|
288
|
-
struct
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
off_t offset;
|
|
324
|
+
/* Structure to track iovec allocation */
|
|
325
|
+
struct iovec_buffer {
|
|
326
|
+
struct iovec *iov;
|
|
327
|
+
int iovcnt;
|
|
328
|
+
ssize_t expected;
|
|
293
329
|
};
|
|
294
330
|
|
|
295
|
-
|
|
296
|
-
{
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
/*
|
|
302
|
-
* IO.pread(fd, length, offset)
|
|
303
|
-
*
|
|
304
|
-
* This is similar to the IO.read method, except that it reads from a given
|
|
305
|
-
* position in the file without changing the file pointer. And unlike IO.read,
|
|
306
|
-
* the +fd+, +length+ and +offset+ arguments are all mandatory.
|
|
307
|
-
*/
|
|
308
|
-
static VALUE s_io_pread(VALUE klass, VALUE fd, VALUE nbyte, VALUE offset){
|
|
309
|
-
struct pread_args args;
|
|
310
|
-
VALUE str;
|
|
311
|
-
ssize_t nread;
|
|
312
|
-
|
|
313
|
-
args.fd = NUM2INT(fd);
|
|
314
|
-
args.nbyte = NUM2ULONG(nbyte);
|
|
315
|
-
args.offset = NUM2OFFT(offset);
|
|
316
|
-
str = rb_str_new(NULL, args.nbyte);
|
|
317
|
-
args.buf = RSTRING_PTR(str);
|
|
318
|
-
|
|
319
|
-
nread = (ssize_t)rb_thread_call_without_gvl((void*)nogvl_pread, &args, RUBY_UBF_IO, 0);
|
|
320
|
-
|
|
321
|
-
if(nread == -1)
|
|
322
|
-
rb_sys_fail("pread");
|
|
323
|
-
|
|
324
|
-
if((size_t)nread != args.nbyte)
|
|
325
|
-
rb_str_set_len(str, nread);
|
|
331
|
+
/* Convert Ruby array to iovec using heap allocation */
|
|
332
|
+
static void ary2iovec(struct iovec_buffer *buf, VALUE ary) {
|
|
333
|
+
VALUE *cur;
|
|
334
|
+
struct iovec *tmp;
|
|
335
|
+
long n;
|
|
326
336
|
|
|
327
|
-
|
|
328
|
-
|
|
337
|
+
if (TYPE(ary) != T_ARRAY)
|
|
338
|
+
rb_raise(rb_eArgError, "must be an array of strings");
|
|
329
339
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
*
|
|
333
|
-
* This is identical to IO.pread, except that it returns the pointer address
|
|
334
|
-
* of the string, instead of the actual buffer.
|
|
335
|
-
*--
|
|
336
|
-
* This was added because, in some cases, the IO.pread buffer might return
|
|
337
|
-
* an empty string. In such situations we are unable to get the actual pointer
|
|
338
|
-
* address with pure Ruby.
|
|
339
|
-
*/
|
|
340
|
-
static VALUE s_io_pread_ptr(VALUE klass, VALUE v_fd, VALUE v_nbyte, VALUE v_offset){
|
|
341
|
-
int fd = NUM2INT(v_fd);
|
|
342
|
-
size_t nbyte = NUM2ULONG(v_nbyte);
|
|
343
|
-
off_t offset = NUM2OFFT(v_offset);
|
|
344
|
-
uintptr_t* vector = malloc(nbyte + 1);
|
|
345
|
-
|
|
346
|
-
if(pread(fd, vector, nbyte, offset) == -1){
|
|
347
|
-
free(vector);
|
|
348
|
-
rb_sys_fail("pread");
|
|
349
|
-
}
|
|
340
|
+
cur = RARRAY_PTR(ary);
|
|
341
|
+
n = RARRAY_LEN(ary);
|
|
350
342
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
#endif
|
|
343
|
+
if (n > IOV_MAX)
|
|
344
|
+
rb_raise(rb_eArgError, "array is larger than IOV_MAX");
|
|
354
345
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const void *buf;
|
|
359
|
-
size_t nbyte;
|
|
360
|
-
off_t offset;
|
|
361
|
-
};
|
|
346
|
+
buf->iov = tmp = ALLOC_N(struct iovec, n);
|
|
347
|
+
buf->expected = 0;
|
|
348
|
+
buf->iovcnt = (int)n;
|
|
362
349
|
|
|
363
|
-
|
|
364
|
-
{
|
|
365
|
-
|
|
366
|
-
|
|
350
|
+
for (; --n >= 0; tmp++, cur++) {
|
|
351
|
+
if (TYPE(*cur) != T_STRING) {
|
|
352
|
+
xfree(buf->iov);
|
|
353
|
+
buf->iov = NULL;
|
|
354
|
+
rb_raise(rb_eArgError, "must be an array of strings");
|
|
355
|
+
}
|
|
356
|
+
tmp->iov_base = RSTRING_PTR(*cur);
|
|
357
|
+
tmp->iov_len = RSTRING_LEN(*cur);
|
|
358
|
+
buf->expected += tmp->iov_len;
|
|
359
|
+
}
|
|
367
360
|
}
|
|
368
361
|
|
|
369
|
-
/*
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
* This is similar to a seek & write in standard Ruby but the difference,
|
|
376
|
-
* beyond being a singleton method, is that the file pointer is never moved.
|
|
377
|
-
*
|
|
378
|
-
* Returns the number of bytes written.
|
|
379
|
-
*/
|
|
380
|
-
static VALUE s_io_pwrite(VALUE klass, VALUE fd, VALUE buf, VALUE offset){
|
|
381
|
-
ssize_t result;
|
|
382
|
-
struct pwrite_args args;
|
|
383
|
-
|
|
384
|
-
args.fd = NUM2INT(fd);
|
|
385
|
-
args.buf = RSTRING_PTR(buf);
|
|
386
|
-
args.nbyte = RSTRING_LEN(buf);
|
|
387
|
-
args.offset = NUM2OFFT(offset);
|
|
388
|
-
|
|
389
|
-
result = (ssize_t)rb_thread_call_without_gvl((void*)nogvl_pwrite, &args, RUBY_UBF_IO, 0);
|
|
390
|
-
|
|
391
|
-
if(result == -1)
|
|
392
|
-
rb_sys_fail("pwrite");
|
|
393
|
-
|
|
394
|
-
return ULL2NUM(result);
|
|
362
|
+
/* Free iovec buffer */
|
|
363
|
+
static void free_iovec_buffer(struct iovec_buffer *buf) {
|
|
364
|
+
if (buf->iov) {
|
|
365
|
+
xfree(buf->iov);
|
|
366
|
+
buf->iov = NULL;
|
|
367
|
+
}
|
|
395
368
|
}
|
|
396
|
-
#endif
|
|
397
|
-
|
|
398
|
-
/* this can't be a function since we use alloca() */
|
|
399
|
-
#define ARY2IOVEC(iov,iovcnt,expect,ary) \
|
|
400
|
-
do { \
|
|
401
|
-
VALUE *cur; \
|
|
402
|
-
struct iovec *tmp; \
|
|
403
|
-
long n; \
|
|
404
|
-
if (TYPE(ary) != T_ARRAY) \
|
|
405
|
-
rb_raise(rb_eArgError, "must be an array of strings"); \
|
|
406
|
-
cur = RARRAY_PTR(ary); \
|
|
407
|
-
n = RARRAY_LEN(ary); \
|
|
408
|
-
if (n > IOV_MAX) \
|
|
409
|
-
rb_raise(rb_eArgError, "array is larger than IOV_MAX"); \
|
|
410
|
-
iov = tmp = alloca(sizeof(struct iovec) * n); \
|
|
411
|
-
expect = 0; \
|
|
412
|
-
iovcnt = (int)n; \
|
|
413
|
-
for (; --n >= 0; tmp++, cur++) { \
|
|
414
|
-
if (TYPE(*cur) != T_STRING) \
|
|
415
|
-
rb_raise(rb_eArgError, "must be an array of strings"); \
|
|
416
|
-
tmp->iov_base = RSTRING_PTR(*cur); \
|
|
417
|
-
tmp->iov_len = RSTRING_LEN(*cur); \
|
|
418
|
-
expect += tmp->iov_len; \
|
|
419
|
-
} \
|
|
420
|
-
} while (0)
|
|
421
369
|
|
|
422
370
|
#if defined(HAVE_WRITEV)
|
|
423
371
|
struct writev_args {
|
|
@@ -447,9 +395,17 @@ static VALUE s_io_writev(VALUE klass, VALUE fd, VALUE ary) {
|
|
|
447
395
|
ssize_t result = 0;
|
|
448
396
|
ssize_t left;
|
|
449
397
|
struct writev_args args;
|
|
398
|
+
struct iovec_buffer iov_buf;
|
|
399
|
+
|
|
400
|
+
// Allow a fileno or filehandle
|
|
401
|
+
if(rb_respond_to(fd, rb_intern("fileno")))
|
|
402
|
+
fd = rb_funcall(fd, rb_intern("fileno"), 0, 0);
|
|
450
403
|
|
|
451
404
|
args.fd = NUM2INT(fd);
|
|
452
|
-
|
|
405
|
+
ary2iovec(&iov_buf, ary);
|
|
406
|
+
args.iov = iov_buf.iov;
|
|
407
|
+
args.iovcnt = iov_buf.iovcnt;
|
|
408
|
+
left = iov_buf.expected;
|
|
453
409
|
|
|
454
410
|
for(;;) {
|
|
455
411
|
ssize_t w = (ssize_t)rb_thread_call_without_gvl(
|
|
@@ -466,6 +422,7 @@ static VALUE s_io_writev(VALUE klass, VALUE fd, VALUE ary) {
|
|
|
466
422
|
* we'll let the next write (or close) fail instead */
|
|
467
423
|
break;
|
|
468
424
|
}
|
|
425
|
+
free_iovec_buffer(&iov_buf);
|
|
469
426
|
rb_sys_fail("writev");
|
|
470
427
|
}
|
|
471
428
|
}
|
|
@@ -483,13 +440,25 @@ static VALUE s_io_writev(VALUE klass, VALUE fd, VALUE ary) {
|
|
|
483
440
|
left -= w;
|
|
484
441
|
|
|
485
442
|
// Skip over iovecs we've already written completely
|
|
486
|
-
for(i = 0; i < args.iovcnt; i
|
|
443
|
+
for(i = 0; i < args.iovcnt; i++){
|
|
487
444
|
if (w == 0)
|
|
488
445
|
break;
|
|
489
446
|
|
|
447
|
+
// Bounds check before pointer arithmetic
|
|
448
|
+
if(new_iov == NULL){
|
|
449
|
+
free_iovec_buffer(&iov_buf);
|
|
450
|
+
rb_raise(rb_eRuntimeError, "writev: iovec bounds check failed");
|
|
451
|
+
}
|
|
452
|
+
|
|
490
453
|
// Partially written iov, modify and retry with current iovec in front
|
|
491
454
|
if(new_iov->iov_len > (size_t)w){
|
|
492
|
-
|
|
455
|
+
char* base = (char*)new_iov->iov_base;
|
|
456
|
+
|
|
457
|
+
// Validate base pointer before arithmetic
|
|
458
|
+
if(base == NULL){
|
|
459
|
+
free_iovec_buffer(&iov_buf);
|
|
460
|
+
rb_raise(rb_eRuntimeError, "writev: null iov_base");
|
|
461
|
+
}
|
|
493
462
|
|
|
494
463
|
new_iov->iov_len -= w;
|
|
495
464
|
new_iov->iov_base = (void *)(base + w);
|
|
@@ -497,6 +466,13 @@ static VALUE s_io_writev(VALUE klass, VALUE fd, VALUE ary) {
|
|
|
497
466
|
}
|
|
498
467
|
|
|
499
468
|
w -= new_iov->iov_len;
|
|
469
|
+
new_iov++;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Validate we haven't exceeded bounds before modifying args
|
|
473
|
+
if(i > args.iovcnt){
|
|
474
|
+
free_iovec_buffer(&iov_buf);
|
|
475
|
+
rb_raise(rb_eRuntimeError, "writev: exceeded iovec array bounds");
|
|
500
476
|
}
|
|
501
477
|
|
|
502
478
|
// Retry without the already-written iovecs
|
|
@@ -505,6 +481,7 @@ static VALUE s_io_writev(VALUE klass, VALUE fd, VALUE ary) {
|
|
|
505
481
|
}
|
|
506
482
|
}
|
|
507
483
|
|
|
484
|
+
free_iovec_buffer(&iov_buf);
|
|
508
485
|
return LONG2NUM(result);
|
|
509
486
|
}
|
|
510
487
|
#endif
|
|
@@ -525,8 +502,15 @@ static VALUE io_get_ttyname(VALUE self){
|
|
|
525
502
|
|
|
526
503
|
int fd = NUM2INT(rb_funcall(self, rb_intern("fileno"), 0, 0));
|
|
527
504
|
|
|
528
|
-
if(
|
|
529
|
-
|
|
505
|
+
if(fd < 0)
|
|
506
|
+
rb_raise(rb_eArgError, "invalid file descriptor");
|
|
507
|
+
|
|
508
|
+
errno = 0;
|
|
509
|
+
if(isatty(fd)){
|
|
510
|
+
char *name = ttyname(fd);
|
|
511
|
+
if(name != NULL)
|
|
512
|
+
v_return = rb_str_new2(name);
|
|
513
|
+
}
|
|
530
514
|
|
|
531
515
|
return v_return;
|
|
532
516
|
}
|
|
@@ -535,7 +519,7 @@ static VALUE io_get_ttyname(VALUE self){
|
|
|
535
519
|
/* Adds the IO.closefrom, IO.fdwalk class methods, as well as the IO#directio
|
|
536
520
|
* and IO#directio? instance methods (if supported on your platform).
|
|
537
521
|
*/
|
|
538
|
-
void Init_extra(){
|
|
522
|
+
void Init_extra(void){
|
|
539
523
|
rb_define_singleton_method(rb_cIO, "closefrom", io_closefrom, 1);
|
|
540
524
|
|
|
541
525
|
#ifdef HAVE_FDWALK
|
|
@@ -553,22 +537,8 @@ void Init_extra(){
|
|
|
553
537
|
rb_define_const(rb_cIO, "DIRECTIO_ON", UINT2NUM(DIRECTIO_ON));
|
|
554
538
|
#endif
|
|
555
539
|
|
|
556
|
-
#ifdef O_DIRECT
|
|
557
|
-
/* 040000: direct disk access (in Linux) */
|
|
558
|
-
rb_define_const(rb_cIO, "DIRECT", UINT2NUM(O_DIRECT));
|
|
559
|
-
#endif
|
|
560
|
-
|
|
561
540
|
rb_define_const(rb_cIO, "IOV_MAX", LONG2NUM(IOV_MAX));
|
|
562
541
|
|
|
563
|
-
#ifdef HAVE_PREAD
|
|
564
|
-
rb_define_singleton_method(rb_cIO, "pread", s_io_pread, 3);
|
|
565
|
-
rb_define_singleton_method(rb_cIO, "pread_ptr", s_io_pread_ptr, 3);
|
|
566
|
-
#endif
|
|
567
|
-
|
|
568
|
-
#ifdef HAVE_PWRITE
|
|
569
|
-
rb_define_singleton_method(rb_cIO, "pwrite", s_io_pwrite, 3);
|
|
570
|
-
#endif
|
|
571
|
-
|
|
572
542
|
#ifdef HAVE_WRITEV
|
|
573
543
|
rb_define_singleton_method(rb_cIO, "writev", s_io_writev, 2);
|
|
574
544
|
#endif
|
|
@@ -576,7 +546,4 @@ void Init_extra(){
|
|
|
576
546
|
#ifdef HAVE_TTYNAME
|
|
577
547
|
rb_define_method(rb_cIO, "ttyname", io_get_ttyname, 0);
|
|
578
548
|
#endif
|
|
579
|
-
|
|
580
|
-
/* 1.3.0: The version of this library. */
|
|
581
|
-
rb_define_const(rb_cIO, "EXTRA_VERSION", rb_str_freeze(rb_str_new2("1.3.0")));
|
|
582
549
|
}
|
data/io-extra.gemspec
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'rbconfig'
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |spec|
|
|
5
|
+
if File::ALT_SEPARATOR
|
|
6
|
+
STDERR.puts 'Not supported on this platform. Exiting.'
|
|
7
|
+
exit(-1)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
spec.name = 'io-extra'
|
|
11
|
+
spec.version = '1.5.0'
|
|
12
|
+
spec.author = 'Daniel J. Berger'
|
|
13
|
+
spec.license = 'Apache-2.0'
|
|
14
|
+
spec.email = 'djberg96@gmail.com'
|
|
15
|
+
spec.homepage = 'https://github.com/djberg96/io-extra'
|
|
16
|
+
spec.summary = 'Adds extra methods to the IO class'
|
|
17
|
+
spec.test_files = Dir['spec/*_spec.rb']
|
|
18
|
+
spec.extensions = ['ext/extconf.rb']
|
|
19
|
+
spec.cert_chain = ['certs/djberg96_pub.pem']
|
|
20
|
+
spec.files = Dir['**/*'].reject{ |f| f.include?('git') }
|
|
21
|
+
|
|
22
|
+
spec.extra_rdoc_files = [
|
|
23
|
+
'CHANGES.md',
|
|
24
|
+
'README.md',
|
|
25
|
+
'MANIFEST.md',
|
|
26
|
+
'ext/io/extra.c'
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
spec.add_development_dependency('rake')
|
|
30
|
+
spec.add_development_dependency('rspec', '~> 3.9')
|
|
31
|
+
spec.add_development_dependency('rubocop')
|
|
32
|
+
spec.add_development_dependency('rubocop-rspec')
|
|
33
|
+
|
|
34
|
+
spec.metadata = {
|
|
35
|
+
'homepage_uri' => 'https://github.com/djberg96/io-extra',
|
|
36
|
+
'bug_tracker_uri' => 'https://github.com/djberg96/io-extra/issues',
|
|
37
|
+
'changelog_uri' => 'https://github.com/djberg96/io-extra/blob/main/CHANGES.md',
|
|
38
|
+
'documentation_uri' => 'https://github.com/djberg96/io-extra/wiki',
|
|
39
|
+
'source_code_uri' => 'https://github.com/djberg96/io-extra',
|
|
40
|
+
'wiki_uri' => 'https://github.com/djberg96/io-extra/wiki',
|
|
41
|
+
'rubygems_mfa_required' => 'true',
|
|
42
|
+
'github_repo' => 'https://github.com/djberg96/io-extra',
|
|
43
|
+
'funding_uri' => 'https://github.com/sponsors/djberg96',
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
spec.description = <<-EOF
|
|
47
|
+
Adds the IO.closefrom, IO.fdwalk, IO.pread, IO.pwrite, and IO.writev
|
|
48
|
+
singleton methods as well as the IO#directio, IO#directio? and IO#ttyname
|
|
49
|
+
instance methods (for those platforms that support them).
|
|
50
|
+
EOF
|
|
51
|
+
end
|
data/lib/io-extra.rb
CHANGED
|
@@ -1 +1,19 @@
|
|
|
1
1
|
require 'io/extra'
|
|
2
|
+
|
|
3
|
+
# Reopen the core IO class to define pread and pwrite singleton methods
|
|
4
|
+
# for backwards compatibility.
|
|
5
|
+
class IO
|
|
6
|
+
EXTRA_VERSION = '1.5.0'.freeze
|
|
7
|
+
|
|
8
|
+
# Singleton version of the IO#pwrite method.
|
|
9
|
+
#
|
|
10
|
+
def self.pwrite(fd, string, offset)
|
|
11
|
+
fd.pwrite(string, offset)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Singleton version of the IO#pread method.
|
|
15
|
+
#
|
|
16
|
+
def self.pread(fd, maxlen, offset)
|
|
17
|
+
fd.pread(maxlen, offset)
|
|
18
|
+
end
|
|
19
|
+
end
|