libextractor

GNU libextractor
Log | Files | Refs | Submodules | README | LICENSE

extractor.c (20249B)


      1 /*
      2      This file is part of libextractor.
      3      Copyright (C) 2002, 2003, 2004, 2005, 2006, 2009, 2012 Vidyut Samanta and Christian Grothoff
      4 
      5      libextractor is free software; you can redistribute it and/or modify
      6      it under the terms of the GNU General Public License as published
      7      by the Free Software Foundation; either version 3, or (at your
      8      option) any later version.
      9 
     10      libextractor is distributed in the hope that it will be useful, but
     11      WITHOUT ANY WARRANTY; without even the implied warranty of
     12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13      General Public License for more details.
     14 
     15      You should have received a copy of the GNU General Public License
     16      along with libextractor; see the file COPYING.  If not, write to the
     17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     18      Boston, MA 02110-1301, USA.
     19  */
     20 
     21 #include "platform.h"
     22 #include "extractor.h"
     23 #include <dirent.h>
     24 #include <sys/types.h>
     25 #include <signal.h>
     26 #include <ltdl.h>
     27 #include "extractor_datasource.h"
     28 #include "extractor_ipc.h"
     29 #include "extractor_logging.h"
     30 #include "extractor_plugpath.h"
     31 #include "extractor_plugins.h"
     32 
     33 
     34 /**
     35  * Size used for the shared memory segment.
     36  */
     37 #define DEFAULT_SHM_SIZE (16 * 1024)
     38 
     39 
     40 /**
     41  * Closure for #process_plugin_reply()
     42  */
     43 struct PluginReplyProcessor
     44 {
     45   /**
     46    * Function to call if we receive meta data from the plugin.
     47    */
     48   EXTRACTOR_MetaDataProcessor proc;
     49 
     50   /**
     51    * Closure for @e proc.
     52    */
     53   void *proc_cls;
     54 
     55   /**
     56    * Are we done with processing this file? 0 to continue, 1 to terminate.
     57    */
     58   int file_finished;
     59 
     60 };
     61 
     62 
     63 /**
     64  * Send an 'update' message to the plugin.
     65  *
     66  * @param plugin plugin to notify
     67  * @param shm_off new offset for the SHM
     68  * @param data_available number of bytes available in shm
     69  * @param ds datastore backend we are using
     70  */
     71 static void
     72 send_update_message (struct EXTRACTOR_PluginList *plugin,
     73                      int64_t shm_off,
     74                      size_t data_available,
     75                      struct EXTRACTOR_Datasource *ds)
     76 {
     77   struct UpdateMessage um;
     78 
     79   um.opcode = MESSAGE_UPDATED_SHM;
     80   um.reserved = 0;
     81   um.reserved2 = 0;
     82   um.shm_ready_bytes = (uint32_t) data_available;
     83   um.shm_off = (uint64_t) shm_off;
     84   um.file_size = EXTRACTOR_datasource_get_size_ (ds, 0);
     85   if (sizeof (um) !=
     86       EXTRACTOR_IPC_channel_send_ (plugin->channel,
     87                                    &um,
     88                                    sizeof (um)) )
     89   {
     90     LOG ("Failed to send UPDATED_SHM message to plugin\n");
     91     EXTRACTOR_IPC_channel_destroy_ (plugin->channel);
     92     plugin->channel = NULL;
     93     plugin->round_finished = 1;
     94   }
     95 }
     96 
     97 
     98 /**
     99  * Send a 'discard state' message to the plugin and mark it as finished
    100  * for this round.
    101  *
    102  * @param plugin plugin to notify
    103  */
    104 static void
    105 send_discard_message (struct EXTRACTOR_PluginList *plugin)
    106 {
    107   static unsigned char disc_msg = MESSAGE_DISCARD_STATE;
    108 
    109   if (sizeof (disc_msg) !=
    110       EXTRACTOR_IPC_channel_send_ (plugin->channel,
    111                                    &disc_msg,
    112                                    sizeof (disc_msg)) )
    113   {
    114     LOG ("Failed to send DISCARD_STATE message to plugin\n");
    115     EXTRACTOR_IPC_channel_destroy_ (plugin->channel);
    116     plugin->channel = NULL;
    117     plugin->round_finished = 1;
    118   }
    119 }
    120 
    121 
    122 /**
    123  * We had some serious trouble.  Abort all channels.
    124  *
    125  * @param plugins list of plugins with channels to abort
    126  */
    127 static void
    128 abort_all_channels (struct EXTRACTOR_PluginList *plugins)
    129 {
    130   struct EXTRACTOR_PluginList *pos;
    131 
    132   for (pos = plugins; NULL != pos; pos = pos->next)
    133   {
    134     if (NULL == pos->channel)
    135       continue;
    136     EXTRACTOR_IPC_channel_destroy_ (pos->channel);
    137     pos->channel = NULL;
    138   }
    139 }
    140 
    141 
    142 /**
    143  * Handler for a message from one of the plugins.
    144  *
    145  * @param cls closure with our 'struct PluginReplyProcessor'
    146  * @param plugin plugin of the channel sending the message
    147  * @param meta_type type of the meta data
    148  * @param meta_format format of the meta data
    149  * @param mime mime string send from the plugin
    150  * @param value 'data' send from the plugin
    151  * @param value_len number of bytes in 'value'
    152  */
    153 static void
    154 process_plugin_reply (void *cls,
    155                       struct EXTRACTOR_PluginList *plugin,
    156                       enum EXTRACTOR_MetaType meta_type,
    157                       enum EXTRACTOR_MetaFormat meta_format,
    158                       const char *mime,
    159                       const void *value,
    160                       size_t value_len)
    161 {
    162   static unsigned char cont_msg = MESSAGE_CONTINUE_EXTRACTING;
    163   struct PluginReplyProcessor *prp = cls;
    164 
    165   if (0 != prp->file_finished)
    166   {
    167     /* client already aborted, ignore message, tell plugin about abort */
    168     return;
    169   }
    170   if (0 != prp->proc (prp->proc_cls,
    171                       plugin->short_libname,
    172                       meta_type,
    173                       meta_format,
    174                       mime,
    175                       value,
    176                       value_len))
    177   {
    178     prp->file_finished = 1;
    179 #if DEBUG
    180     fprintf (stderr, "Sending ABRT\n");
    181 #endif
    182     send_discard_message (plugin);
    183     return;
    184   }
    185   if (sizeof (cont_msg) !=
    186       EXTRACTOR_IPC_channel_send_ (plugin->channel,
    187                                    &cont_msg,
    188                                    sizeof (cont_msg)) )
    189   {
    190     LOG ("Failed to send CONTINUE_EXTRACTING message to plugin\n");
    191     EXTRACTOR_IPC_channel_destroy_ (plugin->channel);
    192     plugin->channel = NULL;
    193     plugin->round_finished = 1;
    194   }
    195 }
    196 
    197 
    198 /**
    199  * Closure for the in-process callbacks.
    200  */
    201 struct InProcessContext
    202 {
    203   /**
    204    * Current plugin.
    205    */
    206   struct EXTRACTOR_PluginList *plugin;
    207 
    208   /**
    209    * Data source to use.
    210    */
    211   struct EXTRACTOR_Datasource *ds;
    212 
    213   /**
    214    * Function to call with meta data.
    215    */
    216   EXTRACTOR_MetaDataProcessor proc;
    217 
    218   /**
    219    * Closure for @e proc.
    220    */
    221   void *proc_cls;
    222 
    223   /**
    224    * IO buffer.
    225    */
    226   char buf[DEFAULT_SHM_SIZE];
    227 
    228   /**
    229    * 0 to continue extracting, 1 if we are finished
    230    */
    231   int finished;
    232 };
    233 
    234 
    235 /**
    236  * Obtain a pointer to up to @a size bytes of data from the file to process.
    237  * Callback used for in-process plugins.
    238  *
    239  * @param cls a `struct InProcessContext`
    240  * @param data pointer to set to the file data, set to NULL on error
    241  * @param size maximum number of bytes requested
    242  * @return number of bytes now available in data (can be smaller than @a size),
    243  *         -1 on error
    244  */
    245 static ssize_t
    246 in_process_read (void *cls,
    247                  void **data,
    248                  size_t size)
    249 {
    250   struct InProcessContext *ctx = cls;
    251   ssize_t ret;
    252   size_t bsize;
    253 
    254   bsize = sizeof (ctx->buf);
    255   if (size < bsize)
    256     bsize = size;
    257   ret = EXTRACTOR_datasource_read_ (ctx->ds,
    258                                     ctx->buf,
    259                                     bsize);
    260   if (-1 == ret)
    261     *data = NULL;
    262   else
    263     *data = ctx->buf;
    264   return ret;
    265 }
    266 
    267 
    268 /**
    269  * Seek in the file.  Use 'SEEK_CUR' for @a whence and @a pos of 0 to
    270  * obtain the current position in the file.
    271  * Callback used for in-process plugins.
    272  *
    273  * @param cls a 'struct InProcessContext'
    274  * @param pos position to seek (see 'man lseek')
    275  * @param whence how to see (absolute to start, relative, absolute to end)
    276  * @return new absolute position, -1 on error (i.e. desired position
    277  *         does not exist)
    278  */
    279 static int64_t
    280 in_process_seek (void *cls,
    281                  int64_t pos,
    282                  int whence)
    283 {
    284   struct InProcessContext *ctx = cls;
    285 
    286   return EXTRACTOR_datasource_seek_ (ctx->ds,
    287                                      pos,
    288                                      whence);
    289 }
    290 
    291 
    292 /**
    293  * Determine the overall size of the file.
    294  * Callback used for in-process plugins.
    295  *
    296  * @param cls a `struct InProcessContext`
    297  * @return overall file size, UINT64_MAX on error (i.e. IPC failure)
    298  */
    299 static uint64_t
    300 in_process_get_size (void *cls)
    301 {
    302   struct InProcessContext *ctx = cls;
    303 
    304   return (uint64_t) EXTRACTOR_datasource_get_size_ (ctx->ds, 0);
    305 }
    306 
    307 
    308 /**
    309  * Type of a function that libextractor calls for each
    310  * meta data item found.
    311  * Callback used for in-process plugins.
    312  *
    313  * @param cls a 'struct InProcessContext'
    314  * @param plugin_name name of the plugin that produced this value;
    315  *        special values can be used (i.e. '&lt;zlib&gt;' for zlib being
    316  *        used in the main libextractor library and yielding
    317  *        meta data).
    318  * @param type libextractor-type describing the meta data
    319  * @param format basic format information about data
    320  * @param data_mime_type mime-type of data (not of the original file);
    321  *        can be NULL (if mime-type is not known)
    322  * @param data actual meta-data found
    323  * @param data_len number of bytes in data
    324  * @return 0 to continue extracting, 1 to abort
    325  */
    326 static int
    327 in_process_proc (void *cls,
    328                  const char *plugin_name,
    329                  enum EXTRACTOR_MetaType type,
    330                  enum EXTRACTOR_MetaFormat format,
    331                  const char *data_mime_type,
    332                  const char *data,
    333                  size_t data_len)
    334 {
    335   struct InProcessContext *ctx = cls;
    336   int ret;
    337 
    338   if (0 != ctx->finished)
    339     return 1;
    340   ret = ctx->proc (ctx->proc_cls,
    341                    plugin_name,
    342                    type,
    343                    format,
    344                    data_mime_type,
    345                    data,
    346                    data_len);
    347   if (0 != ret)
    348     ctx->finished = 1;
    349   return ret;
    350 }
    351 
    352 
    353 /**
    354  * Extract keywords using the given set of plugins.
    355  *
    356  * @param plugins the list of plugins to use
    357  * @param shm shared memory object used by the plugins (NULL if
    358  *        all plugins are in-process)
    359  * @param ds data to process
    360  * @param proc function to call for each meta data item found
    361  * @param proc_cls cls argument to @a proc
    362  */
    363 static void
    364 do_extract (struct EXTRACTOR_PluginList *plugins,
    365             struct EXTRACTOR_SharedMemory *shm,
    366             struct EXTRACTOR_Datasource *ds,
    367             EXTRACTOR_MetaDataProcessor proc, void *proc_cls)
    368 {
    369   unsigned int plugin_count;
    370   unsigned int plugin_off;
    371   struct EXTRACTOR_PluginList *pos;
    372   struct StartMessage start;
    373   struct EXTRACTOR_Channel *channel;
    374   struct PluginReplyProcessor prp;
    375   struct InProcessContext ctx;
    376   struct EXTRACTOR_ExtractContext ec;
    377   int64_t min_seek;
    378   int64_t end;
    379   ssize_t data_available;
    380   ssize_t ready;
    381   int done;
    382   int have_in_memory;
    383 
    384   plugin_count = 0;
    385   for (pos = plugins; NULL != pos; pos = pos->next)
    386     plugin_count++;
    387   if (NULL != shm)
    388     ready = EXTRACTOR_IPC_shared_memory_set_ (shm,
    389                                               ds,
    390                                               0,
    391                                               DEFAULT_SHM_SIZE);
    392   else
    393     ready = 0;
    394   if (-1 == ready)
    395     return; /* failed to ready _any_ data!? */
    396   have_in_memory = 0;
    397   prp.file_finished = 0;
    398   prp.proc = proc;
    399   prp.proc_cls = proc_cls;
    400 
    401   /* send 'start' message */
    402   start.opcode = MESSAGE_EXTRACT_START;
    403   start.reserved = 0;
    404   start.reserved2 = 0;
    405   start.shm_ready_bytes = (uint32_t) ready;
    406   start.file_size = EXTRACTOR_datasource_get_size_ (ds, 0);
    407   for (pos = plugins; NULL != pos; pos = pos->next)
    408   {
    409     if (EXTRACTOR_OPTION_IN_PROCESS == pos->flags)
    410       have_in_memory = 1;
    411     if ( (NULL != pos->channel) &&
    412          (-1 == EXTRACTOR_IPC_channel_send_ (pos->channel,
    413                                              &start,
    414                                              sizeof (start)) ) )
    415     {
    416       /* Plugins that asked for "force-kill" exit after every file, so a
    417          channel that is gone here is not an error at all -- but unless we
    418          restart the plugin now, this file would silently be skipped by
    419          it, and only every other file would ever be processed. */
    420       EXTRACTOR_IPC_channel_destroy_ (pos->channel);
    421       pos->channel = NULL;
    422       if (NULL != shm)
    423         pos->channel = EXTRACTOR_IPC_channel_create_ (pos,
    424                                                       shm);
    425       if ( (NULL != pos->channel) &&
    426            (-1 == EXTRACTOR_IPC_channel_send_ (pos->channel,
    427                                                &start,
    428                                                sizeof (start)) ) )
    429       {
    430         LOG ("Failed to send EXTRACT_START message to plugin\n");
    431         EXTRACTOR_IPC_channel_destroy_ (pos->channel);
    432         pos->channel = NULL;
    433       }
    434     }
    435   }
    436   done = 0;
    437   while (! done)
    438   {
    439     struct EXTRACTOR_Channel *channels[plugin_count];
    440 
    441     /* calculate current 'channels' array */
    442     plugin_off = 0;
    443     for (pos = plugins; NULL != pos; pos = pos->next)
    444     {
    445       if (-1 == pos->seek_request)
    446       {
    447         /* channel is not seeking, must be running or done */
    448         channels[plugin_off] = pos->channel;
    449       }
    450       else
    451       {
    452         /* not running this round, seeking! */
    453         channels[plugin_off] = NULL;
    454       }
    455       plugin_off++;
    456     }
    457     /* give plugins chance to send us meta data, seek or finished messages */
    458     if (-1 ==
    459         EXTRACTOR_IPC_channel_recv_ (channels,
    460                                      plugin_count,
    461                                      &process_plugin_reply,
    462                                      &prp))
    463     {
    464       /* serious problem in IPC; reset *all* channels */
    465       LOG ("Failed to receive message from channels; full reset\n");
    466       abort_all_channels (plugins);
    467       break;
    468     }
    469 
    470     /* calculate minimum seek request (or set done=0 to continue here) */
    471     done = 1;
    472     min_seek = -1;
    473     plugin_off = 0;
    474     for (pos = plugins; NULL != pos; pos = pos->next)
    475     {
    476       plugin_off++;
    477       if ( (1 == pos->round_finished) ||
    478            (NULL == pos->channel) )
    479       {
    480         continue;     /* inactive plugin */
    481       }
    482       if (-1 == pos->seek_request)
    483       {
    484         /* possibly more meta data at current position, at least
    485      this plugin is still working on it... */
    486         done = 0;
    487         break;
    488       }
    489       if (-1 != pos->seek_request)
    490       {
    491         if (SEEK_END == pos->seek_whence)
    492         {
    493           /* convert distance from end to absolute position */
    494           pos->seek_whence = 0;
    495           end = EXTRACTOR_datasource_get_size_ (ds, 1);
    496           if (pos->seek_request > end)
    497           {
    498             LOG ("Cannot seek to before the beginning of the file!\n");
    499             pos->seek_request = 0;
    500           }
    501           else
    502           {
    503             pos->seek_request = end - pos->seek_request;
    504           }
    505         }
    506         if ( (-1 == min_seek) ||
    507              (min_seek > pos->seek_request) )
    508         {
    509           min_seek = pos->seek_request;
    510         }
    511       }
    512     }
    513     data_available = -1;
    514     if ( (1 == done) &&
    515          (-1 != min_seek) &&
    516          (NULL != shm) )
    517     {
    518       /* current position done, but seek requested */
    519       done = 0;
    520       if (-1 ==
    521           (data_available = EXTRACTOR_IPC_shared_memory_set_ (shm,
    522                                                               ds,
    523                                                               min_seek,
    524                                                               DEFAULT_SHM_SIZE))
    525           )
    526       {
    527         LOG ("Failed to seek; full reset\n");
    528         abort_all_channels (plugins);
    529         break;
    530       }
    531     }
    532     /* if 'prp.file_finished', send 'abort' to plugins;
    533        if not, send 'seek' notification to plugins in range */
    534     for (pos = plugins; NULL != pos; pos = pos->next)
    535     {
    536       if (NULL == (channel = pos->channel))
    537       {
    538         /* Skipping plugin: channel down */
    539         continue;
    540       }
    541       if ( (-1 != pos->seek_request) &&
    542            (1 == prp.file_finished) )
    543       {
    544         send_discard_message (pos);
    545         pos->round_finished = 1;
    546         pos->seek_request = -1;
    547       }
    548       if ( (-1 != data_available) &&
    549            (-1 != pos->seek_request) &&
    550            (min_seek <= pos->seek_request) &&
    551            ( (min_seek + data_available > pos->seek_request) ||
    552              (min_seek == EXTRACTOR_datasource_get_size_ (ds, 0))) )
    553       {
    554         /* Notify plugin about seek to 'min_seek' */
    555         send_update_message (pos,
    556                              min_seek,
    557                              data_available,
    558                              ds);
    559         pos->seek_request = -1;
    560       }
    561       if (0 == pos->round_finished)
    562         done = 0; /* can't be done, plugin still active */
    563     }
    564   }
    565 
    566   if (0 == have_in_memory)
    567     return;
    568   /* run in-process plugins */
    569   ctx.finished = 0;
    570   ctx.ds = ds;
    571   ctx.proc = proc;
    572   ctx.proc_cls = proc_cls;
    573   ec.cls = &ctx;
    574   ec.read = &in_process_read;
    575   ec.seek = &in_process_seek;
    576   ec.get_size = &in_process_get_size;
    577   ec.proc = &in_process_proc;
    578   for (pos = plugins; NULL != pos; pos = pos->next)
    579   {
    580     if (EXTRACTOR_OPTION_IN_PROCESS != pos->flags)
    581       continue;
    582     if (-1 == EXTRACTOR_plugin_load_ (pos))
    583       continue;
    584     ctx.plugin = pos;
    585     ec.config = pos->plugin_options;
    586     if (-1 == EXTRACTOR_datasource_seek_ (ds, 0, SEEK_SET))
    587     {
    588       LOG ("Failed to seek to 0 for in-memory plugins\n");
    589       return;
    590     }
    591     pos->extract_method (&ec);
    592     if (1 == ctx.finished)
    593       break;
    594   }
    595 }
    596 
    597 
    598 /**
    599  * Extract keywords from a file using the given set of plugins.
    600  * If needed, opens the file and loads its data (via mmap).  Then
    601  * decompresses it if the data is compressed.  Finally runs the
    602  * plugins on the (now possibly decompressed) data.
    603  *
    604  * @param plugins the list of plugins to use
    605  * @param filename the name of the file, can be NULL if data is not NULL
    606  * @param data data of the file in memory, can be NULL (in which
    607  *        case libextractor will open file) if filename is not NULL
    608  * @param size number of bytes in data, ignored if data is NULL
    609  * @param proc function to call for each meta data item found
    610  * @param proc_cls cls argument to @a proc
    611  */
    612 void
    613 EXTRACTOR_extract (struct EXTRACTOR_PluginList *plugins,
    614                    const char *filename,
    615                    const void *data,
    616                    size_t size,
    617                    EXTRACTOR_MetaDataProcessor proc,
    618                    void *proc_cls)
    619 {
    620   struct EXTRACTOR_Datasource *datasource;
    621   struct EXTRACTOR_SharedMemory *shm;
    622   struct EXTRACTOR_PluginList *pos;
    623   int have_oop;
    624 
    625   if (NULL == plugins)
    626     return;
    627   if (NULL == filename)
    628     datasource = EXTRACTOR_datasource_create_from_buffer_ (data, size,
    629                                                            proc, proc_cls);
    630   else
    631     datasource = EXTRACTOR_datasource_create_from_file_ (filename,
    632                                                          proc, proc_cls);
    633   if (NULL == datasource)
    634     return;
    635   shm = NULL;
    636   have_oop = 0;
    637   for (pos = plugins; NULL != pos; pos = pos->next)
    638   {
    639     if (NULL == shm)
    640       shm = pos->shm;
    641     if (EXTRACTOR_OPTION_IN_PROCESS != pos->flags)
    642       have_oop = 1;
    643     pos->round_finished = 0;
    644   }
    645   if ( (NULL == shm) &&
    646        (1 == have_oop) )
    647   {
    648     /* need to create shared memory segment */
    649     shm = EXTRACTOR_IPC_shared_memory_create_ (DEFAULT_SHM_SIZE);
    650     if (NULL == shm)
    651     {
    652       LOG ("Failed to setup IPC\n");
    653       EXTRACTOR_datasource_destroy_ (datasource);
    654       return;
    655     }
    656   }
    657   for (pos = plugins; NULL != pos; pos = pos->next)
    658     if ( (NULL == pos->channel) &&
    659          (NULL != shm) &&
    660          (EXTRACTOR_OPTION_IN_PROCESS != pos->flags) )
    661     {
    662       if (NULL == pos->shm)
    663       {
    664         pos->shm = shm;
    665         (void) EXTRACTOR_IPC_shared_memory_change_rc_ (shm, 1);
    666       }
    667       pos->channel = EXTRACTOR_IPC_channel_create_ (pos,
    668                                                     shm);
    669     }
    670   do_extract (plugins,
    671               shm,
    672               datasource,
    673               proc,
    674               proc_cls);
    675   EXTRACTOR_datasource_destroy_ (datasource);
    676 }
    677 
    678 
    679 /**
    680  * Initialize gettext and libltdl (and W32 if needed).
    681  */
    682 void __attribute__ ((constructor))
    683 EXTRACTOR_ltdl_init ()
    684 {
    685   int err;
    686 
    687 #if ENABLE_NLS
    688   bindtextdomain (PACKAGE, LOCALEDIR);
    689 #endif
    690   err = lt_dlinit ();
    691   if (err > 0)
    692   {
    693 #if DEBUG
    694     fprintf (stderr,
    695              _ ("Initialization of plugin mechanism failed: %s!\n"),
    696              lt_dlerror ());
    697 #endif
    698     return;
    699   }
    700 #if WINDOWS
    701   plibc_init_utf8 ("GNU", PACKAGE, 1);
    702   plibc_set_stat_size_size (sizeof (((struct stat *) 0)->st_size));
    703   plibc_set_stat_time_size (sizeof (((struct stat *) 0)->st_mtime));
    704 #endif
    705 }
    706 
    707 
    708 /**
    709  * Deinit.
    710  */
    711 void __attribute__ ((destructor))
    712 EXTRACTOR_ltdl_fini ()
    713 {
    714 #if WINDOWS
    715   plibc_shutdown ();
    716 #endif
    717   lt_dlexit ();
    718 }
    719 
    720 
    721 /* end of extractor.c */