ext/magic/ruby-magic.c (916 lines of code) (raw):

#if defined(__cplusplus) extern "C" { #endif #include "ruby-magic.h" static int rb_mgc_do_not_auto_load; static int rb_mgc_do_not_stop_on_error; static int rb_mgc_warning; static ID id_at_flags; static ID id_at_paths; static VALUE rb_cMagic = Qundef; static VALUE rb_mgc_eError = Qundef; static VALUE rb_mgc_eMagicError = Qundef; static VALUE rb_mgc_eLibraryError = Qundef; static VALUE rb_mgc_eParameterError = Qundef; static VALUE rb_mgc_eFlagsError = Qundef; static VALUE rb_mgc_eNotImplementedError = Qundef; void Init_magic(void); static VALUE magic_get_parameter_internal(void *data); static VALUE magic_set_parameter_internal(void *data); static VALUE magic_get_flags_internal(void *data); static VALUE magic_set_flags_internal(void *data); static VALUE magic_load_internal(void *data); static VALUE magic_load_buffers_internal(void *data); static VALUE magic_compile_internal(void *data); static VALUE magic_check_internal(void *data); static VALUE magic_file_internal(void *data); static VALUE magic_buffer_internal(void *data); static VALUE magic_descriptor_internal(void *data); static VALUE magic_close_internal(void *data); static void* nogvl_magic_load(void *data); static void* nogvl_magic_compile(void *data); static void* nogvl_magic_check(void *data); static void* nogvl_magic_file(void *data); static void* nogvl_magic_descriptor(void *data); static VALUE magic_allocate(VALUE klass); static void magic_mark(void *data); static void magic_free(void *data); static VALUE magic_exception_wrapper(VALUE value); static VALUE magic_exception(void *data); static void magic_library_close(void *data); static VALUE magic_library_error(VALUE klass, void *data); static VALUE magic_generic_error(VALUE klass, int magic_errno, const char *magic_error); static VALUE magic_lock(VALUE object, VALUE (*function)(ANYARGS), void *data); static VALUE magic_unlock(VALUE object); static VALUE magic_return(void *data); static int magic_flags(VALUE object); static int magic_set_flags(VALUE object, VALUE value); static VALUE magic_set_paths(VALUE object, VALUE value); /* * call-seq: * Magic.do_not_auto_load -> boolean * * Returns +true+ if the global +do_not_auto_load+ flag is set, or +false+ * otherwise. * * Example: * * Magic.do_not_auto_load #=> false * Magic.do_not_auto_load = true #=> true * Magic.do_not_auto_load #=> true * * See also: Magic::new, Magic#loaded?, Magic#load and Magic#load_buffers */ VALUE rb_mgc_get_do_not_auto_load_global(RB_UNUSED_VAR(VALUE object)) { return CBOOL2RVAL(rb_mgc_do_not_auto_load); } /* * call-seq: * Magic.do_not_auto_load= ( boolean ) -> boolean * * Sets the global +do_not_auto_load+ flag for the Magic object and each of the * Magic object instances. This flag can be used to disable automatic loading of * the Magic database files. * * Returns +true+ if the global +do_not_auto_load+ flag is set, or +false+ * otherwise. * * Example: * * Magic.do_not_auto_load #=> false * Magic.do_not_auto_load = true #=> true * Magic.do_not_auto_load #=> true * * Example: * * Magic.do_not_auto_load = true #=> true * magic = Magic.new * magic.loaded? #=> false * magic.load_buffers(File.read(magic.paths[0] + ".mgc")) #=> nil * magic.loaded? #=> true * * See also: Magic::new, Magic#loaded?, Magic#load and Magic#load_buffers */ VALUE rb_mgc_set_do_not_auto_load_global(RB_UNUSED_VAR(VALUE object), VALUE value) { rb_mgc_do_not_auto_load = RVAL2CBOOL(value); return value; } /* * call-seq: * Magic.do_not_stop_on_error -> boolean * * Returns +true+ if the global +do_not_stop_on_error+ flag is set, or +false+ * otherwise. * * Example: * * Magic.do_not_stop_on_error #=> false * Magic.do_not_stop_on_error = true #=> true * Magic.do_not_stop_on_error #=> true * * See also: Magic::new, Magic::open and Magic#do_not_stop_on_error */ VALUE rb_mgc_get_do_not_stop_on_error_global(RB_UNUSED_VAR(VALUE object)) { return CBOOL2RVAL(rb_mgc_do_not_stop_on_error); } /* * call-seq: * Magic.do_not_stop_on_error= (boolean) -> boolean * * Example: * * Magic.do_not_stop_on_error #=> false * Magic.do_not_stop_on_error = true #=> true * Magic.do_not_stop_on_error #=> true * * See also: Magic::new, Magic::open and Magic#do_not_stop_on_error */ VALUE rb_mgc_set_do_not_stop_on_error_global(RB_UNUSED_VAR(VALUE object), VALUE value) { rb_mgc_do_not_stop_on_error = RVAL2CBOOL(value); return value; } /* * call-seq: * Magic.new -> self * Magic.new( string, ... ) -> self * Magic.new( array ) -> self * * Opens the underlying _Magic_ database and returns a new _Magic_. * * Example: * * magic = Magic.new * magic.class #=> Magic * * See also: Magic::open, Magic::mime, Magic::type, Magic::encoding, Magic::compile and Magic::check */ VALUE rb_mgc_initialize(VALUE object, VALUE arguments) { magic_object_t *mo; const char *klass = "Magic"; if (!NIL_P(object)) klass = rb_obj_classname(object); if (rb_block_given_p()) MAGIC_WARNING(0, "%s::new() does not take block; use %s::open() instead", klass, klass); if(RTEST(rb_eval_string("ENV['MAGIC_DO_NOT_STOP_ON_ERROR']"))) rb_mgc_do_not_stop_on_error = 1; if(RTEST(rb_eval_string("ENV['MAGIC_DO_NOT_AUTOLOAD']"))) rb_mgc_do_not_auto_load = 1; MAGIC_OBJECT(mo); mo->stop_on_errors = 1; if (rb_mgc_do_not_stop_on_error) mo->stop_on_errors = 0; mo->mutex = rb_class_new_instance(0, 0, rb_const_get(rb_cObject, rb_intern("Mutex"))); magic_set_flags(object, INT2NUM(MAGIC_NONE)); magic_set_paths(object, RARRAY_EMPTY); if (rb_mgc_do_not_auto_load) { if (!RARRAY_EMPTY_P(arguments)) MAGIC_WARNING(1, "%s::do_not_auto_load is set; using %s#new() to load " "Magic database from a file will have no effect", klass, klass); return object; } rb_mgc_load(object, arguments); return object; } /* * call-seq: * magic.do_not_stop_on_error -> boolean * * See also: Magic::new, Magic::open and Magic::do_not_stop_on_error */ VALUE rb_mgc_get_do_not_stop_on_error(VALUE object) { magic_object_t *mo; MAGIC_CHECK_OPEN(object); MAGIC_OBJECT(mo); return CBOOL2RVAL(!mo->stop_on_errors); } /* * call-seq: * magic.do_not_stop_on_error= ( boolean ) -> boolean * * See also: Magic::new, Magic::open and Magic::do_not_stop_on_error */ VALUE rb_mgc_set_do_not_stop_on_error(VALUE object, VALUE value) { magic_object_t *mo; MAGIC_CHECK_OPEN(object); MAGIC_OBJECT(mo); mo->stop_on_errors = !RVAL2CBOOL(value); return value; } /* * call-seq: * magic.open? -> true or false * * Returns +true+ if the underlying _Magic_ database is open, * or +false+ otherwise. * * Example: * * magic = Magic.new * magic.open? #=> true * magic.close #=> nil * magic.open? #=> false * * See also: Magic#close?, Magic#close and Magic#new */ VALUE rb_mgc_open_p(VALUE object) { return MAGIC_CLOSED_P(object) ? Qfalse : Qtrue; } /* * call-seq: * magic.close -> nil * * Closes the underlying _Magic_ database. * * Example: * * magic = Magic.new * magic.close #=> nil * * See also: Magic#closed?, Magic#open? and Magic#new */ VALUE rb_mgc_close(VALUE object) { magic_object_t *mo; if (MAGIC_CLOSED_P(object)) return Qnil; MAGIC_OBJECT(mo); if (mo) { MAGIC_SYNCHRONIZED(magic_close_internal, mo); if (DATA_P(object)) DATA_PTR(object) = NULL; } return Qnil; } /* * call-seq: * magic.closed? -> true or false * * Returns +true+ if the underlying _Magic_ database is closed, * or +false+ otherwise. * * Example: * * magic = Magic.new * magic.closed? #=> false * magic.close #=> nil * magic.closed? #=> true * * See also: Magic#close, Magic#open? and #Magic#new */ VALUE rb_mgc_close_p(VALUE object) { magic_object_t *mo; magic_t cookie = NULL; MAGIC_OBJECT(mo); if (mo) cookie = mo->cookie; if (DATA_P(object) && cookie) return Qfalse; return Qtrue; } /* * call-seq: * magic.paths -> array * * Example: * * magic = Magic.new * magic.paths #=> ["/etc/magic", "/usr/share/misc/magic"] * */ VALUE rb_mgc_get_paths(VALUE object) { const char *cstring = NULL; VALUE value = Qundef; MAGIC_CHECK_OPEN(object); value = rb_ivar_get(object, id_at_paths); if (!NIL_P(value) && !RARRAY_EMPTY_P(value) && !getenv("MAGIC")) return value; cstring = magic_getpath_wrapper(); value = magic_split(CSTR2RVAL(cstring), CSTR2RVAL(":")); RB_GC_GUARD(value); return value; } /* * call-seq: * magic.get_parameter( integer ) -> integer */ VALUE rb_mgc_get_parameter(VALUE object, VALUE tag) { int local_errno; magic_object_t *mo; magic_arguments_t ma; MAGIC_CHECK_INTEGER_TYPE(tag); MAGIC_CHECK_OPEN(object); MAGIC_COOKIE(mo, ma.cookie); ma.type.parameter.tag = NUM2INT(tag); MAGIC_SYNCHRONIZED(magic_get_parameter_internal, &ma); local_errno = errno; if (ma.status < 0) { if (local_errno == EINVAL) MAGIC_GENERIC_ERROR(rb_mgc_eParameterError, local_errno, E_PARAM_INVALID_TYPE); MAGIC_LIBRARY_ERROR(ma.cookie); } return SIZET2NUM(ma.type.parameter.value); } /* * call-seq: * magic.set_parameter( integer, integer ) -> nil */ VALUE rb_mgc_set_parameter(VALUE object, VALUE tag, VALUE value) { int local_errno; magic_object_t *mo; magic_arguments_t ma; MAGIC_CHECK_INTEGER_TYPE(tag); MAGIC_CHECK_INTEGER_TYPE(value); MAGIC_CHECK_OPEN(object); MAGIC_COOKIE(mo, ma.cookie); ma.type.parameter.tag = NUM2INT(tag); ma.type.parameter.value = NUM2SIZET(value); MAGIC_SYNCHRONIZED(magic_set_parameter_internal, &ma); local_errno = errno; if (ma.status < 0) { switch (local_errno) { case EINVAL: MAGIC_GENERIC_ERROR(rb_mgc_eParameterError, local_errno, E_PARAM_INVALID_TYPE); case EOVERFLOW: MAGIC_GENERIC_ERROR(rb_mgc_eParameterError, local_errno, E_PARAM_INVALID_VALUE); } MAGIC_LIBRARY_ERROR(ma.cookie); } return Qnil; } /* * call-seq: * magic.flags -> integer * * Example: * * magic = Magic.new * magic.flags #=> 0 * magic.flags = Magic::MIME #=> 1040 * magic.flags #=> 1040 * * See also: Magic#flags_to_a */ VALUE rb_mgc_get_flags(VALUE object) { int local_errno; magic_object_t *mo; magic_arguments_t ma; MAGIC_CHECK_OPEN(object); MAGIC_COOKIE(mo, ma.cookie); MAGIC_SYNCHRONIZED(magic_get_flags_internal, &ma); local_errno = errno; if (ma.flags < 0 && local_errno == ENOSYS) return rb_ivar_get(object, id_at_flags); return INT2NUM(ma.flags); } /* * call-seq: * magic.flags= ( integer ) -> integer * * Example: * * magic = Magic.new * magic.flags = Magic::MIME #=> 1040 * magic.flags = Magic::MIME_TYPE #=> 16 */ VALUE rb_mgc_set_flags(VALUE object, VALUE value) { int local_errno; magic_object_t *mo; magic_arguments_t ma; MAGIC_CHECK_INTEGER_TYPE(value); MAGIC_CHECK_OPEN(object); MAGIC_COOKIE(mo, ma.cookie); ma.flags = NUM2INT(value); MAGIC_SYNCHRONIZED(magic_set_flags_internal, &ma); local_errno = errno; if (ma.status < 0) { switch (local_errno) { case EINVAL: MAGIC_GENERIC_ERROR(rb_mgc_eFlagsError, local_errno, E_FLAG_INVALID_TYPE); case ENOSYS: MAGIC_GENERIC_ERROR(rb_mgc_eNotImplementedError, local_errno, E_FLAG_NOT_IMPLEMENTED); } MAGIC_LIBRARY_ERROR(ma.cookie); } return rb_ivar_set(object, id_at_flags, INT2NUM(ma.flags)); } /* * call-seq: * magic.load -> nil * magic.load( string, ... ) -> nil * magic.load( array ) -> nil * * Example: * * See also: Magic#check, Magic#compile, Magic::check and Magic::compile */ VALUE rb_mgc_load(VALUE object, VALUE arguments) { magic_object_t *mo; magic_arguments_t ma; const char *klass = "Magic"; VALUE value = Qundef; if (ARRAY_P(RARRAY_FIRST(arguments))) arguments = magic_flatten(arguments); MAGIC_CHECK_ARRAY_OF_STRINGS(arguments); MAGIC_CHECK_OPEN(object); MAGIC_COOKIE(mo, ma.cookie); if (rb_mgc_do_not_auto_load) { if (!NIL_P(object)) klass = rb_obj_classname(object); MAGIC_WARNING(2, "%s::do_not_auto_load is set; using %s#load " "will load Magic database from a file", klass, klass); } ma.flags = magic_flags(object); if (!RARRAY_EMPTY_P(arguments)) { value = magic_join(arguments, CSTR2RVAL(":")); ma.type.file.path = RVAL2CSTR(value); } else ma.type.file.path = magic_getpath_wrapper(); magic_set_paths(object, RARRAY_EMPTY); MAGIC_SYNCHRONIZED(magic_load_internal, &ma); if (ma.status < 0) { mo->database_loaded = 0; MAGIC_LIBRARY_ERROR(ma.cookie); } mo->database_loaded = 1; value = magic_split(CSTR2RVAL(ma.type.file.path), CSTR2RVAL(":")); magic_set_paths(object, value); RB_GC_GUARD(value); return Qnil; } /* * call-seq: * magic.load_buffers( string, ... ) -> nil * magic.load_buffers( array ) -> nil * * See also: Magic#load and Magic::do_not_auto_load */ VALUE rb_mgc_load_buffers(VALUE object, VALUE arguments) { size_t count; int local_errno; magic_object_t *mo; magic_arguments_t ma; void **pointers = NULL; size_t *sizes = NULL; VALUE value = Qundef; count = (size_t)RARRAY_LEN(arguments); MAGIC_CHECK_ARGUMENT_MISSING(count, 1); if (ARRAY_P(RARRAY_FIRST(arguments))) { arguments = magic_flatten(arguments); count = (size_t)RARRAY_LEN(arguments); } MAGIC_CHECK_ARRAY_EMPTY(arguments); MAGIC_CHECK_ARRAY_OF_STRINGS(arguments); MAGIC_CHECK_OPEN(object); MAGIC_COOKIE(mo, ma.cookie); pointers = ALLOC_N(void *, count); if (!pointers) { local_errno = ENOMEM; goto error; } sizes = ALLOC_N(size_t, count); if (!sizes) { ruby_xfree(pointers); local_errno = ENOMEM; goto error; } for (size_t i = 0; i < count; i++) { value = RARRAY_AREF(arguments, (long)i); pointers[i] = (void *)RSTRING_PTR(value); sizes[i] = (size_t)RSTRING_LEN(value); } ma.flags = magic_flags(object); ma.type.buffers.count = count; ma.type.buffers.pointers = pointers; ma.type.buffers.sizes = sizes; magic_set_paths(object, RARRAY_EMPTY); MAGIC_SYNCHRONIZED(magic_load_buffers_internal, &ma); if (ma.status < 0) { local_errno = errno; ruby_xfree(pointers); ruby_xfree(sizes); goto error; } mo->database_loaded = 1; ruby_xfree(pointers); ruby_xfree(sizes); return Qnil; error: mo->database_loaded = 0; if (local_errno == ENOMEM) MAGIC_GENERIC_ERROR(rb_mgc_eLibraryError, local_errno, E_NOT_ENOUGH_MEMORY); MAGIC_LIBRARY_ERROR(ma.cookie); } /* * call-seq: * magic.loaded? -> true or false * * Returns +true+ if at least a single Magic database file had been loaded, or * +false+ otherwise. Magic database files can be loaded from a file or from an * in-memory buffer. * * Example: * * magic = Magic.new * magic.loaded? #=> true * * Example: * * Magic.do_not_auto_load = true #=> true * magic = Magic.new * magic.loaded? #=> false * * See also: Magic#load and Magic#load_buffers */ VALUE rb_mgc_load_p(VALUE object) { magic_object_t *mo; MAGIC_CHECK_OPEN(object); MAGIC_OBJECT(mo); return CBOOL2RVAL(mo->database_loaded); } /* * call-seq: * magic.compile( string ) -> nil * magic.compile( array ) -> nil * * See also: Magic#check, Magic::check and Magic::compile */ VALUE rb_mgc_compile(VALUE object, VALUE value) { magic_object_t *mo; magic_arguments_t ma; MAGIC_CHECK_STRING_TYPE(value); MAGIC_CHECK_OPEN(object); MAGIC_COOKIE(mo, ma.cookie); ma.flags = magic_flags(object); ma.type.file.path = RVAL2CSTR(value); MAGIC_SYNCHRONIZED(magic_compile_internal, &ma); if (ma.status < 0) MAGIC_LIBRARY_ERROR(ma.cookie); return Qnil; } /* * call-seq: * magic.check( string ) -> true or false * magic.check( array ) -> true or false * * See also: Magic#compile, Magic::compile and Magic::check */ VALUE rb_mgc_check(VALUE object, VALUE value) { magic_object_t *mo; magic_arguments_t ma; MAGIC_CHECK_STRING_TYPE(value); MAGIC_CHECK_OPEN(object); MAGIC_COOKIE(mo, ma.cookie); ma.flags = magic_flags(object); ma.type.file.path = RVAL2CSTR(value); MAGIC_SYNCHRONIZED(magic_check_internal, &ma); return ma.status < 0 ? Qfalse : Qtrue; } /* * call-seq: * magic.file( object ) -> string or array * magic.file( string ) -> string or array * * See also: Magic#buffer and Magic#descriptor */ VALUE rb_mgc_file(VALUE object, VALUE value) { magic_object_t *mo; magic_arguments_t ma; const char *empty = "(null)"; UNUSED(empty); if (NIL_P(value)) goto error; MAGIC_CHECK_OPEN(object); MAGIC_CHECK_LOADED(object); MAGIC_COOKIE(mo, ma.cookie); if (rb_respond_to(value, rb_intern("to_io"))) return rb_mgc_descriptor(object, value); value = magic_path(value); if (NIL_P(value)) goto error; ma.stop_on_errors = mo->stop_on_errors; ma.flags = magic_flags(object); ma.type.file.path = RVAL2CSTR(value); MAGIC_SYNCHRONIZED(magic_file_internal, &ma); if (ma.status < 0 && !ma.result) { /* * Handle the case when the "ERROR" flag is set regardless of the * current version of the underlying Magic library. * * Prior to version 5.15 the correct behavior that concerns the * following IEEE 1003.1 standards was broken: * * http://pubs.opengroup.org/onlinepubs/007904975/utilities/file.html * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/file.html * * This is an attempt to mitigate the problem and correct it to achieve * the desired behavior as per the standards. */ if (mo->stop_on_errors || (ma.flags & MAGIC_ERROR)) MAGIC_LIBRARY_ERROR(ma.cookie); ma.result = magic_error_wrapper(ma.cookie); } if (!ma.result) MAGIC_GENERIC_ERROR(rb_mgc_eMagicError, EINVAL, E_UNKNOWN); assert(ma.result != NULL && \ "Must be a valid pointer to `const char' type"); /* * Depending on the version of the underlying Magic library the magic_file() * function can fail and either yield no results or return the "(null)" * string instead. Often this would indicate that an older version of the * Magic library is in use. */ assert(strncmp(ma.result, empty, strlen(empty)) != 0 && \ "Empty or invalid result"); return magic_return(&ma); error: MAGIC_ARGUMENT_TYPE_ERROR(value, "String or IO-like object"); } /* * call-seq: * magic.buffer( string ) -> string or array * * See also: Magic#file and Magic#descriptor */ VALUE rb_mgc_buffer(VALUE object, VALUE value) { magic_object_t *mo; magic_arguments_t ma; MAGIC_CHECK_STRING_TYPE(value); MAGIC_CHECK_OPEN(object); MAGIC_CHECK_LOADED(object); MAGIC_COOKIE(mo, ma.cookie); StringValue(value); ma.flags = magic_flags(object); ma.type.buffers.pointers = (void **)RSTRING_PTR(value); ma.type.buffers.sizes = (size_t *)RSTRING_LEN(value); MAGIC_SYNCHRONIZED(magic_buffer_internal, &ma); if (ma.status < 0) MAGIC_LIBRARY_ERROR(ma.cookie); assert(ma.result != NULL && \ "Must be a valid pointer to `const char' type"); return magic_return(&ma); } /* * call-seq: * magic.descriptor( object ) -> string or array * magic.descriptor( integer ) -> string or array * * See also: Magic#file and Magic#buffer */ VALUE rb_mgc_descriptor(VALUE object, VALUE value) { int local_errno; magic_object_t *mo; magic_arguments_t ma; if (rb_respond_to(value, rb_intern("to_io"))) value = INT2NUM(magic_fileno(value)); MAGIC_CHECK_INTEGER_TYPE(value); MAGIC_CHECK_OPEN(object); MAGIC_CHECK_LOADED(object); MAGIC_COOKIE(mo, ma.cookie); ma.flags = magic_flags(object); ma.type.file.fd = NUM2INT(value); MAGIC_SYNCHRONIZED(magic_descriptor_internal, &ma); local_errno = errno; if (ma.status < 0) { if (local_errno == EBADF) rb_raise(rb_eIOError, "Bad file descriptor"); MAGIC_LIBRARY_ERROR(ma.cookie); } assert(ma.result != NULL && \ "Must be a valid pointer to `const char' type"); return magic_return(&ma); } /* * call-seq: * Magic.version -> integer * * Example: * * Magic.version #=> 517 * * See also: Magic::version_to_a and Magic::version_to_s */ VALUE rb_mgc_version(RB_UNUSED_VAR(VALUE object)) { return INT2NUM(magic_version_wrapper()); } static inline void* nogvl_magic_load(void *data) { magic_arguments_t *ma = data; ma->status = magic_load_wrapper(ma->cookie, ma->type.file.path, ma->flags); return NULL; } static inline void* nogvl_magic_compile(void *data) { magic_arguments_t *ma = data; ma->status = magic_compile_wrapper(ma->cookie, ma->type.file.path, ma->flags); return NULL; } static inline void* nogvl_magic_check(void *data) { magic_arguments_t *ma = data; ma->status = magic_check_wrapper(ma->cookie, ma->type.file.path, ma->flags); return NULL; } static inline void* nogvl_magic_file(void *data) { magic_arguments_t *ma = data; ma->result = magic_file_wrapper(ma->cookie, ma->type.file.path, ma->flags); ma->status = !ma->result ? -1 : 0; return NULL; } static inline void* nogvl_magic_descriptor(void *data) { magic_arguments_t *ma = data; ma->result = magic_descriptor_wrapper(ma->cookie, ma->type.file.fd, ma->flags); ma->status = !ma->result ? -1 : 0; return NULL; } static inline VALUE magic_get_parameter_internal(void *data) { size_t value; magic_arguments_t *ma = data; ma->status = magic_getparam_wrapper(ma->cookie, ma->type.parameter.tag, &value); ma->type.parameter.value = value; return (VALUE)NULL; } static inline VALUE magic_set_parameter_internal(void *data) { size_t value; magic_arguments_t *ma = data; value = ma->type.parameter.value; ma->status = magic_setparam_wrapper(ma->cookie, ma->type.parameter.tag, &value); return (VALUE)NULL; } static inline VALUE magic_get_flags_internal(void *data) { magic_arguments_t *ma = data; ma->flags = magic_getflags_wrapper(ma->cookie); return (VALUE)NULL; } static inline VALUE magic_set_flags_internal(void *data) { magic_arguments_t *ma = data; ma->status = magic_setflags_wrapper(ma->cookie, ma->flags); return (VALUE)NULL; } static inline VALUE magic_close_internal(void *data) { magic_library_close(data); return Qnil; } static inline VALUE magic_load_internal(void *data) { magic_arguments_t *ma = data; int old_flags = ma->flags; NOGVL(nogvl_magic_load, ma); if (MAGIC_STATUS_CHECK(ma->status < 0)) magic_setflags_wrapper(ma->cookie, old_flags); return (VALUE)NULL; } static inline VALUE magic_load_buffers_internal(void *data) { magic_arguments_t *ma = data; ma->status = magic_load_buffers_wrapper(ma->cookie, ma->type.buffers.pointers, ma->type.buffers.sizes, ma->type.buffers.count, ma->flags); return (VALUE)NULL; } static inline VALUE magic_compile_internal(void *data) { magic_arguments_t *ma = data; int old_flags = ma->flags; NOGVL(nogvl_magic_compile, ma); if (MAGIC_STATUS_CHECK(ma->status < 0)) magic_setflags_wrapper(ma->cookie, old_flags); return (VALUE)NULL; } static inline VALUE magic_check_internal(void *data) { magic_arguments_t *ma = data; int old_flags = ma->flags; NOGVL(nogvl_magic_check, ma); if (MAGIC_STATUS_CHECK(ma->status < 0)) magic_setflags_wrapper(ma->cookie, old_flags); return (VALUE)NULL; } static VALUE magic_file_internal(void *data) { magic_arguments_t *ma = data; int old_flags = ma->flags; int restore_flags; int local_errno; if (ma->stop_on_errors) ma->flags |= MAGIC_ERROR; if (ma->flags & MAGIC_CONTINUE) ma->flags |= MAGIC_RAW; restore_flags = old_flags != ma->flags; if (restore_flags) magic_setflags_wrapper(ma->cookie, ma->flags); NOGVL(nogvl_magic_file, ma); local_errno = errno; /* * The Magic library often does not correctly report errors, * especially when certain flags (such as e.g., MAGIC_EXTENSION, * etc.) are set. Attempt to obtain an error code first from the * Magic library itself, and if that does not work, then from * the saved errno value. */ if (magic_errno_wrapper(ma->cookie) || local_errno) ma->status = -1; if (restore_flags) magic_setflags_wrapper(ma->cookie, old_flags); return (VALUE)NULL; } static VALUE magic_buffer_internal(void *data) { magic_arguments_t *ma = data; int old_flags = ma->flags; int restore_flags; if (ma->flags & MAGIC_CONTINUE) ma->flags |= MAGIC_RAW; restore_flags = old_flags != ma->flags; if (restore_flags) magic_setflags_wrapper(ma->cookie, ma->flags); ma->result = magic_buffer_wrapper(ma->cookie, (const void *)ma->type.buffers.pointers, (size_t)ma->type.buffers.sizes, ma->flags); ma->status = !ma->result ? -1 : 0; if (restore_flags) magic_setflags_wrapper(ma->cookie, old_flags); return (VALUE)NULL; } static VALUE magic_descriptor_internal(void *data) { magic_arguments_t *ma = data; int old_flags = ma->flags; int restore_flags; if (ma->flags & MAGIC_CONTINUE) ma->flags |= MAGIC_RAW; restore_flags = old_flags != ma->flags; if (restore_flags) magic_setflags_wrapper(ma->cookie, ma->flags); NOGVL(nogvl_magic_descriptor, ma); if (restore_flags) magic_setflags_wrapper(ma->cookie, old_flags); return (VALUE)NULL; } static VALUE magic_allocate(VALUE klass) { int local_errno; magic_object_t *mo; mo = (magic_object_t *)ruby_xmalloc(sizeof(magic_object_t)); local_errno = ENOMEM; if (!mo) { errno = local_errno; MAGIC_GENERIC_ERROR(rb_mgc_eLibraryError, local_errno, E_NOT_ENOUGH_MEMORY); } mo->cookie = NULL; mo->mutex = Qundef; mo->database_loaded = 0; mo->stop_on_errors = 0; mo->cookie = magic_open_wrapper(MAGIC_NONE); local_errno = ENOMEM; if (!mo->cookie) { ruby_xfree(mo); mo = NULL; errno = local_errno; MAGIC_GENERIC_ERROR(rb_mgc_eLibraryError, local_errno, E_MAGIC_LIBRARY_INITIALIZE); } return Data_Wrap_Struct(klass, magic_mark, magic_free, mo); } static inline void magic_library_close(void *data) { magic_object_t *mo = data; assert(mo != NULL && \ "Must be a valid pointer to `magic_object_t' type"); if (mo->cookie) magic_close_wrapper(mo->cookie); mo->cookie = NULL; } static inline void magic_mark(void *data) { magic_object_t *mo = data; assert(mo != NULL && \ "Must be a valid pointer to `magic_object_t' type"); rb_gc_mark(mo->mutex); } static inline void magic_free(void *data) { magic_object_t *mo = data; assert(mo != NULL && \ "Must be a valid pointer to `magic_object_t' type"); if (mo->cookie) magic_library_close(data); mo->cookie = NULL; mo->mutex = Qundef; ruby_xfree(mo); } static inline VALUE magic_exception_wrapper(VALUE value) { magic_exception_t *e = (struct magic_exception *)value; return rb_exc_new2(e->klass, e->magic_error); } static VALUE magic_exception(void *data) { magic_exception_t *e = data; int exception = 0; VALUE object = Qundef; assert(e != NULL && \ "Must be a valid pointer to `magic_exception_t' type"); object = rb_protect(magic_exception_wrapper, (VALUE)e, &exception); if (exception) rb_jump_tag(exception); rb_iv_set(object, "@errno", INT2NUM(e->magic_errno)); RB_GC_GUARD(object); return object; } static inline VALUE magic_generic_error(VALUE klass, int magic_errno, const char *magic_error) { magic_exception_t e; e.magic_errno = magic_errno; e.magic_error = magic_error; e.klass = klass; return magic_exception(&e); } static VALUE magic_library_error(VALUE klass, void *data) { magic_exception_t e; const char *message = NULL; const char *empty = "(null)"; magic_t cookie = data; UNUSED(empty); assert(cookie != NULL && \ "Must be a valid pointer to `magic_t' type"); e.magic_errno = -1; e.magic_error = error(E_UNKNOWN); e.klass = klass; message = magic_error_wrapper(cookie); if (message) { e.magic_errno = magic_errno_wrapper(cookie); e.magic_error = message; } assert(strncmp(e.magic_error, empty, strlen(empty)) != 0 && \ "Empty or invalid error message"); return magic_exception(&e); } VALUE magic_lock(VALUE object, VALUE(*function)(ANYARGS), void *data) { magic_object_t *mo; MAGIC_OBJECT(mo); rb_funcall(mo->mutex, rb_intern("lock"), 0); return rb_ensure(function, (VALUE)data, magic_unlock, object); } VALUE magic_unlock(VALUE object) { magic_object_t *mo; MAGIC_OBJECT(mo); rb_funcall(mo->mutex, rb_intern("unlock"), 0); return Qnil; } static VALUE magic_return(void *data) { magic_arguments_t *ma = data; const char *unknown = "???"; VALUE separator = Qundef; VALUE array = Qundef; VALUE string = Qundef; string = CSTR2RVAL(ma->result); RB_GC_GUARD(string); /* * The value below is a field separator that can be used to split results * when the CONTINUE flag is set causing all valid matches found by the * Magic library to be returned. */ if (ma->flags & MAGIC_CONTINUE) separator = CSTR2RVAL(MAGIC_CONTINUE_SEPARATOR); if (ma->flags & MAGIC_EXTENSION) { /* * A possible I/O-related error has occurred, and there is very * little sense processing the results, so return string as-is. */ if (ma->status < 0) return string; /* * A number of Magic flags that support primarily files e.g., * MAGIC_EXTENSION, etc., would not return a meaningful value for * directories and special files, and such. Thus, it's better to * return an empty string, to indicate lack of results, rather * than a confusing string consisting of three questions marks. */ if (strncmp(ma->result, unknown, strlen(unknown)) == 0) return CSTR2RVAL(""); separator = CSTR2RVAL(MAGIC_EXTENSION_SEPARATOR); } if (ma->flags & (MAGIC_CONTINUE | MAGIC_EXTENSION)) { array = magic_split(string, separator); RB_GC_GUARD(array); return (RARRAY_LEN(array) > 1) ? array : magic_shift(array); } return string; } static inline int magic_flags(VALUE object) { return NUM2INT(rb_ivar_get(object, id_at_flags)); } static inline int magic_set_flags(VALUE object, VALUE value) { return NUM2INT(rb_ivar_set(object, id_at_flags, value)); } static inline VALUE magic_set_paths(VALUE object, VALUE value) { return rb_ivar_set(object, id_at_paths, value); } void Init_magic(void) { id_at_paths = rb_intern("@paths"); id_at_flags = rb_intern("@flags"); rb_cMagic = rb_define_class("Magic", rb_cObject); rb_define_alloc_func(rb_cMagic, magic_allocate); /* * Raised when _Magic_ encounters an error. */ rb_mgc_eError = rb_define_class_under(rb_cMagic, "Error", rb_eStandardError); /* * Stores current value of +errno+ */ rb_define_attr(rb_mgc_eError, "errno", 1, 0); /* * Raised when */ rb_mgc_eMagicError = rb_define_class_under(rb_cMagic, "MagicError", rb_mgc_eError); /* * Raised when */ rb_mgc_eLibraryError = rb_define_class_under(rb_cMagic, "LibraryError", rb_mgc_eError); /* * Raised when */ rb_mgc_eParameterError = rb_define_class_under(rb_cMagic, "ParameterError", rb_mgc_eError); /* * Raised when */ rb_mgc_eFlagsError = rb_define_class_under(rb_cMagic, "FlagsError", rb_mgc_eError); /* * Raised when */ rb_mgc_eNotImplementedError = rb_define_class_under(rb_cMagic, "NotImplementedError", rb_mgc_eError); rb_define_singleton_method(rb_cMagic, "do_not_auto_load", RUBY_METHOD_FUNC(rb_mgc_get_do_not_auto_load_global), 0); rb_define_singleton_method(rb_cMagic, "do_not_auto_load=", RUBY_METHOD_FUNC(rb_mgc_set_do_not_auto_load_global), 1); rb_define_singleton_method(rb_cMagic, "do_not_stop_on_error", RUBY_METHOD_FUNC(rb_mgc_get_do_not_stop_on_error_global), 0); rb_define_singleton_method(rb_cMagic, "do_not_stop_on_error=", RUBY_METHOD_FUNC(rb_mgc_set_do_not_stop_on_error_global), 1); rb_define_singleton_method(rb_cMagic, "version", RUBY_METHOD_FUNC(rb_mgc_version), 0); rb_define_method(rb_cMagic, "initialize", RUBY_METHOD_FUNC(rb_mgc_initialize), -2); rb_define_method(rb_cMagic, "do_not_stop_on_error", RUBY_METHOD_FUNC(rb_mgc_get_do_not_stop_on_error), 0); rb_define_method(rb_cMagic, "do_not_stop_on_error=", RUBY_METHOD_FUNC(rb_mgc_set_do_not_stop_on_error), 1); rb_define_method(rb_cMagic, "open?", RUBY_METHOD_FUNC(rb_mgc_open_p), 0); rb_define_method(rb_cMagic, "close", RUBY_METHOD_FUNC(rb_mgc_close), 0); rb_define_method(rb_cMagic, "closed?", RUBY_METHOD_FUNC(rb_mgc_close_p), 0); rb_define_method(rb_cMagic, "paths", RUBY_METHOD_FUNC(rb_mgc_get_paths), 0); rb_define_method(rb_cMagic, "get_parameter", RUBY_METHOD_FUNC(rb_mgc_get_parameter), 1); rb_define_method(rb_cMagic, "set_parameter", RUBY_METHOD_FUNC(rb_mgc_set_parameter), 2); rb_define_method(rb_cMagic, "flags", RUBY_METHOD_FUNC(rb_mgc_get_flags), 0); rb_define_method(rb_cMagic, "flags=", RUBY_METHOD_FUNC(rb_mgc_set_flags), 1); rb_define_method(rb_cMagic, "file", RUBY_METHOD_FUNC(rb_mgc_file), 1); rb_define_method(rb_cMagic, "buffer", RUBY_METHOD_FUNC(rb_mgc_buffer), 1); rb_define_method(rb_cMagic, "descriptor", RUBY_METHOD_FUNC(rb_mgc_descriptor), 1); rb_alias(rb_cMagic, rb_intern("fd"), rb_intern("descriptor")); rb_define_method(rb_cMagic, "load", RUBY_METHOD_FUNC(rb_mgc_load), -2); rb_define_method(rb_cMagic, "load_buffers", RUBY_METHOD_FUNC(rb_mgc_load_buffers), -2); rb_define_method(rb_cMagic, "loaded?", RUBY_METHOD_FUNC(rb_mgc_load_p), 0); rb_alias(rb_cMagic, rb_intern("load_files"), rb_intern("load")); rb_define_method(rb_cMagic, "compile", RUBY_METHOD_FUNC(rb_mgc_compile), 1); rb_define_method(rb_cMagic, "check", RUBY_METHOD_FUNC(rb_mgc_check), 1); rb_alias(rb_cMagic, rb_intern("valid?"), rb_intern("check")); /* * Controls how many levels of recursion will be followed for * indirect magic entries. */ MAGIC_DEFINE_PARAMETER(INDIR_MAX); /* * Controls the maximum number of calls for name or use magic. */ MAGIC_DEFINE_PARAMETER(NAME_MAX); /* * Controls how many ELF program sections will be processed. */ MAGIC_DEFINE_PARAMETER(ELF_PHNUM_MAX); /* * Controls how many ELF sections will be processed. */ MAGIC_DEFINE_PARAMETER(ELF_SHNUM_MAX); /* * Controls how many ELF notes will be processed. */ MAGIC_DEFINE_PARAMETER(ELF_NOTES_MAX); /* * Controls the length limit for regular expression searches. */ MAGIC_DEFINE_PARAMETER(REGEX_MAX); /* * Controls the maximum number of bytes to read from a file. */ MAGIC_DEFINE_PARAMETER(BYTES_MAX); /* * No special handling and/or flags specified. Default behavior. */ MAGIC_DEFINE_FLAG(NONE); /* * Print debugging messages to standard error output. */ MAGIC_DEFINE_FLAG(DEBUG); /* * If the file queried is a symbolic link, follow it. */ MAGIC_DEFINE_FLAG(SYMLINK); /* * If the file is compressed, unpack it and look at the contents. */ MAGIC_DEFINE_FLAG(COMPRESS); /* * If the file is a block or character special device, then open * the device and try to look at the contents. */ MAGIC_DEFINE_FLAG(DEVICES); /* * Return a MIME type string, instead of a textual description. */ MAGIC_DEFINE_FLAG(MIME_TYPE); /* * Return all matches, not just the first. */ MAGIC_DEFINE_FLAG(CONTINUE); /* * Check the Magic database for consistency and print warnings to * standard error output. */ MAGIC_DEFINE_FLAG(CHECK); /* * Attempt to preserve access time (atime, utime or utimes) of the * file queried on systems that support such system calls. */ MAGIC_DEFINE_FLAG(PRESERVE_ATIME); /* * Do not convert unprintable characters to an octal representation. */ MAGIC_DEFINE_FLAG(RAW); /* * Treat operating system errors while trying to open files and follow * symbolic links as first class errors, instead of storing them in the * Magic library error buffer for retrieval later. */ MAGIC_DEFINE_FLAG(ERROR); /* * Return a MIME encoding, instead of a textual description. */ MAGIC_DEFINE_FLAG(MIME_ENCODING); /* * A shorthand for using MIME_TYPE and MIME_ENCODING flags together. */ MAGIC_DEFINE_FLAG(MIME); /* * Return the Apple creator and type. */ MAGIC_DEFINE_FLAG(APPLE); /* * Do not look for, or inside compressed files. */ MAGIC_DEFINE_FLAG(NO_CHECK_COMPRESS); /* * Do not look for, or inside tar archive files. */ MAGIC_DEFINE_FLAG(NO_CHECK_TAR); /* * Do not consult Magic files. */ MAGIC_DEFINE_FLAG(NO_CHECK_SOFT); /* * Check for EMX application type (only supported on EMX). */ MAGIC_DEFINE_FLAG(NO_CHECK_APPTYPE); /* * Do not check for ELF files (do not examine ELF file details). */ MAGIC_DEFINE_FLAG(NO_CHECK_ELF); /* * Do not check for various types of text files. */ MAGIC_DEFINE_FLAG(NO_CHECK_TEXT); /* * Do not check for CDF files. */ MAGIC_DEFINE_FLAG(NO_CHECK_CDF); /* * Do not check for CSV files. */ MAGIC_DEFINE_FLAG(NO_CHECK_CSV); /* * Do not look for known tokens inside ASCII files. */ MAGIC_DEFINE_FLAG(NO_CHECK_TOKENS); /* * Return a MIME encoding, instead of a textual description. */ MAGIC_DEFINE_FLAG(NO_CHECK_ENCODING); /* * Do not check for JSON files. */ MAGIC_DEFINE_FLAG(NO_CHECK_JSON); /* * Do not use built-in tests; only consult the Magic file. */ MAGIC_DEFINE_FLAG(NO_CHECK_BUILTIN); /* * Do not check for various types of text files, same as NO_CHECK_TEXT. */ MAGIC_DEFINE_FLAG(NO_CHECK_ASCII); /* * Do not look for Fortran sequences inside ASCII files. */ MAGIC_DEFINE_FLAG(NO_CHECK_FORTRAN); /* * Do not look for troff sequences inside ASCII files. */ MAGIC_DEFINE_FLAG(NO_CHECK_TROFF); /* * Return a slash-separated list of extensions for this file type. */ MAGIC_DEFINE_FLAG(EXTENSION); /* * Do not report on compression, only report about the uncompressed data. */ MAGIC_DEFINE_FLAG(COMPRESS_TRANSP); } #if defined(__cplusplus) } #endif