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.
@@ -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
@@ -10,8 +10,6 @@ have_header('sys/uio.h')
10
10
  have_func('closefrom')
11
11
  have_func('fdwalk')
12
12
  have_func('directio')
13
- have_func('pread')
14
- have_func('pwrite')
15
13
  have_func('writev')
16
14
  have_func('ttyname')
17
15
 
@@ -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>
@@ -0,0 +1,5 @@
1
+ ---
2
+ triple: 'arm64-apple-darwin'
3
+ binary-path: extra.bundle
4
+ relocations: []
5
+ ...
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 = open_max();
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
- static int fdwalk(int (*func)(void *data, int fd), void *data){
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
- int lowfd;
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
- fdwalk(close_func, &lowfd);
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
- #ifdef HAVE_PREAD
288
- struct pread_args {
289
- int fd;
290
- void *buf;
291
- size_t nbyte;
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
- static VALUE nogvl_pread(void *ptr)
296
- {
297
- struct pread_args *args = ptr;
298
- return (VALUE)pread(args->fd, args->buf, args->nbyte, args->offset);
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
- return str;
328
- }
337
+ if (TYPE(ary) != T_ARRAY)
338
+ rb_raise(rb_eArgError, "must be an array of strings");
329
339
 
330
- /*
331
- * IO.pread_ptr(fd, length, offset)
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
- return ULL2NUM(vector[0]);
352
- }
353
- #endif
343
+ if (n > IOV_MAX)
344
+ rb_raise(rb_eArgError, "array is larger than IOV_MAX");
354
345
 
355
- #ifdef HAVE_PWRITE
356
- struct pwrite_args {
357
- int fd;
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
- static VALUE nogvl_pwrite(void *ptr)
364
- {
365
- struct pwrite_args *args = ptr;
366
- return (VALUE)pwrite(args->fd, args->buf, args->nbyte, args->offset);
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
- * IO.pwrite(fd, buf, offset)
371
- *
372
- * This method writes the +buf+, starting at +offset+, to the given +fd+,
373
- * which must be opened with write permissions.
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
- ARY2IOVEC(args.iov, args.iovcnt, left, ary);
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++, new_iov++){
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
- VALUE base = (VALUE)new_iov->iov_base;
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(isatty(fd))
529
- v_return = rb_str_new2(ttyname(fd));
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