librelist archives

« back to archive

WIP magic_descriptor support

WIP magic_descriptor support

From:
Eric Wong
Date:
2013-04-05 @ 20:29
diff --git a/extconf.rb b/extconf.rb
index 0709016..adf9849 100644
--- a/extconf.rb
+++ b/extconf.rb
@@ -2,6 +2,7 @@
 
 dir_config('magic')
 have_library('magic', 'magic_open')
+have_func('magic_descriptor')
 have_func('rb_thread_call_without_gvl')
 have_func('rb_thread_blocking_region')
 create_makefile('mahoro')
diff --git a/mahoro.c b/mahoro.c
index 1c0b992..40a9d87 100644
--- a/mahoro.c
+++ b/mahoro.c
@@ -6,8 +6,17 @@
  * he ever chooses to return to this project.
  */
 
+#define _GNU_SOURCE
+#include <unistd.h>
+#include <fcntl.h>
 #include <ruby.h>
 #include <magic.h>
+#ifdef HAVE_RUBY_IO_H /* Ruby 1.9+ */
+#  include <ruby/io.h>
+#else /* Ruby 1.8: */
+#  include <stdio.h>
+#  include <rubyio.h>
+#endif
 
 #ifndef RSTRING_LEN
 #  define RSTRING_LEN(s)->len
@@ -16,9 +25,24 @@
 #  define RSTRING_PTR(s)->ptr
 #endif
 
+#if ! HAVE_RB_IO_T
+#  define rb_io_t OpenFile
+#endif
+
+#ifdef GetReadFile
+#  define FPTR_TO_FD(fptr) (fileno(GetReadFile(fptr)))
+#else
+#  if !HAVE_RB_IO_T || (RUBY_VERSION_MAJOR == 1 && RUBY_VERSION_MINOR == 8)
+#    define FPTR_TO_FD(fptr) fileno(fptr->f)
+#  else
+#    define FPTR_TO_FD(fptr) fptr->fd
+#  endif
+#endif
+
 static VALUE cMahoro;
 static VALUE eMahoroError;
 static ID id_to_path;
+static ID id_to_io;
 
 struct nogvl_args {
 	magic_t cookie;
@@ -77,6 +101,22 @@ fake_nogvl(fn, data1, ubf, data2)
 #  define NOGVL(fn,data1,ubf,data2) fake_nogvl((fn),(data1),(ubf),(data2))
 #endif
 
+static int my_fileno(io)
+	VALUE io;
+{
+	rb_io_t *fptr;
+	int fd;
+
+	if(TYPE(io) != T_FILE)
+		io = rb_convert_type(io, T_FILE, "IO", "to_io");
+	GetOpenFile(io, fptr);
+	fd = FPTR_TO_FD(fptr);
+
+	if(fd < 0)
+		rb_raise(rb_eIOError, "closed stream");
+	return fd;
+}
+
 /* :nodoc: called automatically by GC */
 static void
 mahoro_free(ptr)
@@ -154,6 +194,28 @@ mahoro_initialize(argc, argv, self)
 }
 
 static void *
+nogvl_descriptor(ptr)
+	void *ptr;
+{
+#ifdef HAVE_MAGIC_DESCRIPTOR
+	struct nogvl_args *args = ptr;
+	int fd = fcntl(args->as.fd, F_DUPFD_CLOEXEC);
+	if (fd < 0)
+		return 0x1;
+
+	/* magic_descriptor() closes the FD automatically */
+	return (void *)magic_descriptor(args->cookie, fd);
+#endif
+	return NULL;
+}
+
+#ifdef HAVE_MAGIC_DESCRIPTOR
+static int fd_ok = 1;
+#else
+static int fd_ok = 0;
+#endif
+
+static void *
 nogvl_file(ptr)
 	void *ptr;
 {
@@ -164,7 +226,7 @@ nogvl_file(ptr)
 
 /*
  * call-seq:
- *	mahoro_obj.file(filename)	->	String
+ *	mahoro_obj.file(filename_or_io)	->	String
  *
  * Returns a textual description of the contents of the +filename+ given.
  * Use Mahoro#buffer instead of this method if the contents of your
@@ -180,16 +242,24 @@ mahoro_file(self, path)
 
 	args.cookie = (magic_t)DATA_PTR(self);
 
-	/* Pathname objects may be transformed via #to_path */
-	if (rb_respond_to(path, id_to_path))
-		path = rb_funcall(path, id_to_path, 0);
+	if (rb_respond_to(path, id_to_io)) {
+		if (!fd_ok)
+			rb_raise(rb_eNotImpError,
+			        "libmagic too old, missing magic_descriptor()");
+		args.as.fd = my_fileno(path);
+		msg = NOGVL(nogvl_descriptor, &args, RUBY_UBF_IO, NULL);
+	} else {
+		/* Pathname objects may be transformed via #to_path */
+		if (rb_respond_to(path, id_to_path))
+			path = rb_funcall(path, id_to_path, 0);
 
-	args.as.path = StringValueCStr(path);
+		args.as.path = StringValueCStr(path);
+		msg = NOGVL(nogvl_file, &args, RUBY_UBF_IO, NULL);
+	}
 
-	if(!(msg = NOGVL(nogvl_file, &args, RUBY_UBF_IO, NULL))) {
+	if(!msg)
 		rb_raise(eMahoroError, "failed lookup: %s",
 			magic_error(args.cookie));
-	}
 
 	return rb_str_new2(msg);
 }
@@ -605,6 +675,7 @@ void Init_mahoro(void)
 	rb_define_method(cMahoro, "compile", mahoro_compile, 1);
 	rb_define_method(cMahoro, "load", mahoro_load, 1);
 	id_to_path = rb_intern("to_path");
+	id_to_io = rb_intern("to_io");
 }
 
 /* arch-tag: mahoro */
diff --git a/test.rb b/test.rb
index 75e46d6..e71d0d3 100755
--- a/test.rb
+++ b/test.rb
@@ -26,6 +26,18 @@ def test_pathname
 		assert_c_text(@m.file(pn))
 	end
 
+	def test_io
+		@m.flags = Mahoro::NONE
+		r, w = IO.pipe
+		w.write 'int main(void) { return 0; }'
+		w.close
+		assert_c_text(@m.file(r))
+	rescue NotImplementedError => e
+		assert_match /magic_descriptor/, e.message
+	ensure
+		r.close
+	end
+
 	def test_file
 		@m.flags = Mahoro::NONE
 		assert_c_text(@m.file('mahoro.c'))

Re: WIP magic_descriptor support

From:
Eric Wong
Date:
2013-04-05 @ 20:30
I'm holding off on implementing this since magic_descriptor() silently
closes the descriptor behind our backs...