Class: IO

Inherits:
Object
  • Object
show all
Defined in:
lib/io-extra.rb

Overview

Reopen the core IO class to define pread and pwrite singleton methods for backwards compatibility.

Constant Summary collapse

EXTRA_VERSION =
'1.5.0'.freeze
DIRECTIO_OFF =

Applications get the default system behavior when accessing file data.

0
DIRECTIO_ON =

File data is not cached in the system’s memory pages.

1
IOV_MAX =
LONG2NUM(IOV_MAX)

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.closefrom(lowfd) ⇒ Object

Close all open file descriptors (associated with the current process) that are greater than or equal to lowfd.

This method uses your system’s builtin closefrom() function, if supported. Otherwise, it uses a manual, and (probably) less efficient approach.



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'ext/io/extra.c', line 88

static VALUE io_closefrom(VALUE klass, VALUE v_low_fd){
  int i, lowfd;
  int maxfd;

  lowfd = NUM2INT(v_low_fd);

  if(lowfd < 0)
    rb_raise(rb_eArgError, "lowfd must be non-negative");

  maxfd = open_max();

  if(maxfd < 0)
    rb_raise(rb_eRuntimeError, "failed to determine maximum file descriptor");

  for(i = lowfd; i < maxfd; i++) {
    if(!RB_RESERVED_FD_P(i))
      close(i);
  }

  return klass;
}

.fdwalk(lowfd) {|fh| ... } ⇒ Object

Iterates over each open file descriptor and yields back a File object.

Not supported on all platforms.

Yields:

  • (fh)


210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'ext/io/extra.c', line 210

static VALUE io_fdwalk(int argc, VALUE* argv, VALUE klass){
  VALUE v_low_fd, v_block;
  struct fdwalk_data data;

  rb_scan_args(argc, argv, "1&", &v_low_fd, &v_block);
  data.lowfd = NUM2INT(v_low_fd);

  if(data.lowfd < 0)
    rb_raise(rb_eArgError, "lowfd must be non-negative");

#ifdef PROC_SELF_FD_DIR
  data.dir = opendir(PROC_SELF_FD_DIR);
#endif

  rb_ensure(fdwalk_body, (VALUE)&data, fdwalk_ensure, (VALUE)&data);

  return klass;
}

.pread(fd, maxlen, offset) ⇒ Object

Singleton version of the IO#pread method.



16
17
18
# File 'lib/io-extra.rb', line 16

def self.pread(fd, maxlen, offset)
  fd.pread(maxlen, offset)
end

.pwrite(fd, string, offset) ⇒ Object

Singleton version of the IO#pwrite method.



10
11
12
# File 'lib/io-extra.rb', line 10

def self.pwrite(fd, string, offset)
  fd.pwrite(string, offset)
end

.writev(fd, ary) ⇒ Object

IO.writev(fd, %w(hello world))

This method writes the contents of an array of strings to the given fd. It can be useful to avoid generating a temporary string via Array#join when writing out large arrays of strings.

The given array should have fewer elements than the IO::IOV_MAX constant.

Returns the number of bytes written.



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
# File 'ext/io/extra.c', line 394

static VALUE s_io_writev(VALUE klass, VALUE fd, VALUE ary) {
  ssize_t result = 0;
  ssize_t left;
  struct writev_args args;
  struct iovec_buffer iov_buf;

  // Allow a fileno or filehandle
  if(rb_respond_to(fd, rb_intern("fileno")))
    fd = rb_funcall(fd, rb_intern("fileno"), 0, 0);

  args.fd = NUM2INT(fd);
  ary2iovec(&iov_buf, ary);
  args.iov = iov_buf.iov;
  args.iovcnt = iov_buf.iovcnt;
  left = iov_buf.expected;

  for(;;) {
    ssize_t w = (ssize_t)rb_thread_call_without_gvl(
      (void*)nogvl_writev, &args, RUBY_UBF_IO, 0
    );

    if(w == -1){
      if(rb_io_wait_writable(args.fd)){
        continue;
      }
      else{
        if(result > 0){
          /* unlikely to hit this case, return the already written bytes,
           * we'll let the next write (or close) fail instead */
          break;
        }
        free_iovec_buffer(&iov_buf);
        rb_sys_fail("writev");
      }
    }

    result += w;

    if(w == left){
      break;
    }
    else{
      // Partial write, this can get tricky
      int i;
      struct iovec *new_iov = args.iov;

      left -= w;

      // Skip over iovecs we've already written completely
      for(i = 0; i < args.iovcnt; i++){
        if (w == 0)
          break;

        // Bounds check before pointer arithmetic
        if(new_iov == NULL){
          free_iovec_buffer(&iov_buf);
          rb_raise(rb_eRuntimeError, "writev: iovec bounds check failed");
        }

        // Partially written iov, modify and retry with current iovec in front
        if(new_iov->iov_len > (size_t)w){
          char* base = (char*)new_iov->iov_base;

          // Validate base pointer before arithmetic
          if(base == NULL){
            free_iovec_buffer(&iov_buf);
            rb_raise(rb_eRuntimeError, "writev: null iov_base");
          }

          new_iov->iov_len -= w;
          new_iov->iov_base = (void *)(base + w);
          break;
        }

        w -= new_iov->iov_len;
        new_iov++;
      }

      // Validate we haven't exceeded bounds before modifying args
      if(i > args.iovcnt){
        free_iovec_buffer(&iov_buf);
        rb_raise(rb_eRuntimeError, "writev: exceeded iovec array bounds");
      }

      // Retry without the already-written iovecs
      args.iovcnt -= i;
      args.iov = new_iov;
    }
  }

  free_iovec_buffer(&iov_buf);
  return LONG2NUM(result);
}

Instance Method Details

#directio=(advice) ⇒ Object

Sets the advice for the current file descriptor using directio(). Valid values are IO::DIRECTIO_ON and IO::DIRECTIO_OFF. See the directio(3c) man page for more information.

All file descriptors start at DIRECTIO_OFF, unless your filesystem has been mounted using ‘forcedirectio’ (and supports that option).



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'ext/io/extra.c', line 268

static VALUE io_set_directio(VALUE self, VALUE v_advice){
   int fd;
   int advice = NUM2INT(v_advice);

   /* Only two possible valid values */
   if( (advice != DIRECTIO_OFF) && (advice != DIRECTIO_ON) )
      rb_raise(rb_eStandardError, "Invalid value passed to IO#directio=");

   /* Retrieve the current file descriptor in order to pass it to directio() */
   fd = NUM2INT(rb_funcall(self, rb_intern("fileno"), 0, 0));

#if defined(HAVE_DIRECTIO)
   if(directio(fd, advice) < 0)
      rb_raise(rb_eStandardError, "The directio() call failed");

   if(advice == DIRECTIO_ON)
      rb_iv_set(self, "@directio", Qtrue);
   else
      rb_iv_set(self, "@directio", Qfalse);
#else
   {
#if defined(O_DIRECT)
      int flags = fcntl(fd, F_GETFL);

      if(flags < 0)
         rb_sys_fail("fcntl");

      if(advice == DIRECTIO_OFF){
         if(flags & O_DIRECT){
            if(fcntl(fd, F_SETFL, flags & ~O_DIRECT) < 0)
               rb_sys_fail("fcntl");
         }
      } else { /* DIRECTIO_ON */
         if(!(flags & O_DIRECT)){
            if(fcntl(fd, F_SETFL, flags | O_DIRECT) < 0)
               rb_sys_fail("fcntl");
         }
      }
#elif defined(F_NOCACHE)
      if(advice == DIRECTIO_OFF){
         if(fcntl(fd, F_NOCACHE, 0) < 0)
            rb_sys_fail("fcntl");
         rb_iv_set(self, "@directio", Qfalse);
      } else { /* DIRECTIO_ON*/
         if(fcntl(fd, F_NOCACHE, 1) < 0)
            rb_sys_fail("fcntl");
         rb_iv_set(self, "@directio", Qtrue);
      }
#endif
   }
#endif

   return self;
}

#directio?Boolean

Returns true or false, based on whether directio has been set for the current handle. The default is false.

Returns:

  • (Boolean)


238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'ext/io/extra.c', line 238

static VALUE io_get_directio(VALUE self){
#if defined(HAVE_DIRECTIO) || defined(F_NOCACHE)
  VALUE v_advice = rb_iv_get(self, "@directio");

  if(NIL_P(v_advice))
    v_advice = Qfalse;

  return v_advice;
#elif defined(O_DIRECT)
  int fd = NUM2INT(rb_funcall(self, rb_intern("fileno"), 0, 0));
  int flags = fcntl(fd, F_GETFL);

  if(flags < 0)
    rb_sys_fail("fcntl");

  return (flags & O_DIRECT) ? Qtrue : Qfalse;
#endif /* O_DIRECT */
}

#ttynameObject

io.ttyname

Returns the ttyname associated with the IO object, or nil if the IO object isn’t associated with a tty.

Example:

STDOUT.ttyname # => '/dev/ttyp1'


500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
# File 'ext/io/extra.c', line 500

static VALUE io_get_ttyname(VALUE self){
  VALUE v_return = Qnil;

  int fd = NUM2INT(rb_funcall(self, rb_intern("fileno"), 0, 0));

  if(fd < 0)
    rb_raise(rb_eArgError, "invalid file descriptor");

  errno = 0;
  if(isatty(fd)){
    char *name = ttyname(fd);
    if(name != NULL)
      v_return = rb_str_new2(name);
  }

  return v_return;
}