/* * ruby mmap support * by Yoann Guillot - 07/11/2005 * * to compile: ruby extconf.rb ; make * * usage: * * require 'mmap' * x = File.open('bla') * x.mmap * puts x * x.munmap * * y = File.open('bla') * y.mmap(offset=4096, length=150, perm=PROT_READ, flags=MAP_SHARED) * y.munmap * * z = File.mmap('bla', offset=8192) * z.munmap * * returns a Mapping, which is a frozen String * * TODO * Windows port * check freeze issues * allow writing to the string, but disallow changing ptr or length * anonymous mapping * rewrite File#mmap */ #include #include #include #include #include VALUE rb_cMapping; static ID id_open, id_close; static VALUE rb_map_new(int fd, off_t start, off_t len, int prot, int flags) { if ((long)len <= 0) rb_raise(rb_eArgError, "invalid size"); void *ptr; ptr = mmap(0, len, prot, flags, fd, start); if (ptr == MAP_FAILED) rb_sys_fail("mmap"); NEWOBJ(ret, struct RString); OBJSETUP(ret, rb_cMapping, T_STRING); ret->ptr = ptr; ret->len = len; // avoid free() by the gc FL_SET(ret, ELTS_SHARED); ret->aux.shared = Qnil; OBJ_FREEZE(ret); return (VALUE)ret; } static VALUE rb_map_unmap(VALUE self) { int err = -1; if (RSTRING(self)->len) err = munmap(RSTRING(self)->ptr, RSTRING(self)->len); RSTRING(self)->ptr = ""; RSTRING(self)->len = 0; if (err == -1) rb_sys_fail("munmap"); return Qnil; } static VALUE rb_file_mmap(int argc, VALUE *argv, VALUE self) { int fd = fileno(RFILE(self)->fptr->f); off_t offset, length, flength; int prot, flags; { struct stat statb; fstat(fd, &statb); flength = statb.st_size; } offset = 0; length = 0; prot = PROT_READ; flags = MAP_SHARED; switch (argc) { default: rb_raise(rb_eArgError, "invalid number of arguments"); case 4: if (argv[3] != Qnil) { long tmp = NUM2LONG(argv[3]); if (tmp & ~(MAP_SHARED | MAP_PRIVATE)) rb_raise(rb_eArgError, "invalid flag argument"); flags = tmp; } case 3: if (argv[2] != Qnil) { long tmp = NUM2LONG(argv[2]); if (tmp & ~(PROT_READ | PROT_WRITE | PROT_EXEC)) rb_raise(rb_eArgError, "invalid mode argument"); prot = tmp; } case 2: if (argv[1] != Qnil) length = NUM2LONG(argv[1]); case 1: if (argv[0] != Qnil) { offset = NUM2ULONG(argv[0]); if (offset >= flength) rb_raise(rb_eArgError, "invalid offset (past end of file)"); if (offset & 4095) rb_raise(rb_eArgError, "invalid offset (not aligned on a page boundary)"); } case 0: ; } if (!length) length = flength - offset; return rb_map_new(fd, offset, length, prot, flags); } static VALUE rb_fileclass_mmap(int argc, VALUE *argv, VALUE self) { if (argc < 1) rb_raise(rb_eArgError, "need a filename"); /* * this seems a bit ugly... * * want to do: * File.mmap(f, *args) = *File.open(f) { |ff| ff.mmap(*args) } * (taking care not to overwrite some user-defined variable like ff) */ VALUE file; VALUE ret; file = rb_funcall2(CLASS_OF(rb_cIO), id_open, 1, argv); ret = rb_file_mmap(argc-1, argv+1, file); rb_funcall(file, id_close, 0); return ret; } void Init_mmap(void) { rb_cMapping = rb_define_class("Mapping", rb_cString); rb_define_private_method(rb_cMapping, "initialize", rb_map_new, 0); rb_define_method(rb_cMapping, "munmap", rb_map_unmap, 0); rb_define_const(rb_cMapping, "PROT_READ", INT2FIX(PROT_READ)); rb_define_const(rb_cMapping, "PROT_WRITE", INT2FIX(PROT_WRITE)); rb_define_const(rb_cMapping, "PROT_EXEC", INT2FIX(PROT_EXEC)); rb_define_const(rb_cMapping, "MAP_SHARED", INT2FIX(MAP_SHARED)); rb_define_const(rb_cMapping, "MAP_PRIVATE", INT2FIX(MAP_PRIVATE)); rb_define_singleton_method(rb_cFile, "mmap", rb_fileclass_mmap, -1); rb_define_method(rb_cFile, "mmap", rb_file_mmap, -1); id_open = rb_intern("open"); id_close = rb_intern("close"); }