libextractor

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

extractor_ipc_gnu.c (15397B)


      1 /*
      2      This file is part of libextractor.
      3      Copyright (C) 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  * @file main/extractor_ipc_gnu.c
     22  * @brief IPC with plugin for GNU/POSIX systems
     23  * @author Christian Grothoff
     24  */
     25 #include "platform.h"
     26 #include "extractor.h"
     27 #include "extractor_datasource.h"
     28 #include "extractor_logging.h"
     29 #include "extractor_plugin_main.h"
     30 #include "extractor_plugins.h"
     31 #include "extractor_ipc.h"
     32 #include <dirent.h>
     33 #include <sys/types.h>
     34 #include <sys/wait.h>
     35 #include <sys/shm.h>
     36 #include <signal.h>
     37 #if HAVE_SYS_APPARMOR_H
     38 #include <sys/apparmor.h>
     39 #endif
     40 
     41 /**
     42  * A shared memory resource (often shared with several
     43  * other processes).
     44  */
     45 struct EXTRACTOR_SharedMemory
     46 {
     47   /**
     48    * Pointer to the mapped region of the shm (covers the whole shm)
     49    */
     50   void *shm_ptr;
     51 
     52   /**
     53    * Allocated size of the shm
     54    */
     55   size_t shm_size;
     56 
     57   /**
     58    * POSIX id of the shm into which data is uncompressed
     59    */
     60   int shm_id;
     61 
     62   /**
     63    * Name of the shm
     64    */
     65   char shm_name[MAX_SHM_NAME + 1];
     66 
     67   /**
     68    * Reference counter describing how many references share this SHM.
     69    */
     70   unsigned int rc;
     71 
     72 };
     73 
     74 
     75 /**
     76  * Definition of an IPC communication channel with
     77  * some plugin.
     78  */
     79 struct EXTRACTOR_Channel
     80 {
     81 
     82   /**
     83    * Buffer for reading data from the plugin.
     84    */
     85   char *mdata;
     86 
     87   /**
     88    * Size of the @e mdata buffer.
     89    */
     90   size_t mdata_size;
     91 
     92   /**
     93    * Memory segment shared with this process.
     94    */
     95   struct EXTRACTOR_SharedMemory *shm;
     96 
     97   /**
     98    * The plugin this channel is to communicate with.
     99    */
    100   struct EXTRACTOR_PluginList *plugin;
    101 
    102   /**
    103    * Pipe used to communicate information to the plugin child process.
    104    * NULL if not initialized.
    105    */
    106   int cpipe_in;
    107 
    108   /**
    109    * Number of valid bytes in the channel's buffer.
    110    */
    111   size_t size;
    112 
    113   /**
    114    * Pipe used to read information about extracted meta data from
    115    * the plugin child process.  -1 if not initialized.
    116    */
    117   int cpipe_out;
    118 
    119   /**
    120    * Process ID of the child process for this plugin. 0 for none.
    121    */
    122   pid_t cpid;
    123 
    124 };
    125 
    126 
    127 /**
    128  * Create a shared memory area.
    129  *
    130  * @param size size of the shared area
    131  * @return NULL on error
    132  */
    133 struct EXTRACTOR_SharedMemory *
    134 EXTRACTOR_IPC_shared_memory_create_ (size_t size)
    135 {
    136   struct EXTRACTOR_SharedMemory *shm;
    137   const char *tpath;
    138 
    139   if (NULL == (shm = malloc (sizeof (struct EXTRACTOR_SharedMemory))))
    140   {
    141     LOG_STRERROR ("malloc");
    142     return NULL;
    143   }
    144 #if SOMEBSD
    145   /* this works on FreeBSD, not sure about others... */
    146   tpath = getenv ("TMPDIR");
    147   if (NULL == tpath)
    148     tpath = "/tmp/";
    149 #else
    150   tpath = "/"; /* Linux */
    151 #endif
    152   snprintf (shm->shm_name,
    153             MAX_SHM_NAME,
    154             "%sLE-%u-%u",
    155             tpath,
    156             getpid (),
    157             (unsigned int) random ());
    158   if (-1 == (shm->shm_id = shm_open (shm->shm_name,
    159                                      O_RDWR | O_CREAT,
    160                                      S_IRUSR | S_IWUSR)))
    161   {
    162     LOG_STRERROR_FILE ("shm_open",
    163                        shm->shm_name);
    164     free (shm);
    165     return NULL;
    166   }
    167   if ( (0 != ftruncate (shm->shm_id, size)) ||
    168        (NULL == (shm->shm_ptr = mmap (NULL,
    169                                       size,
    170                                       PROT_WRITE,
    171                                       MAP_SHARED,
    172                                       shm->shm_id,
    173                                       0))) ||
    174        (((void*) -1) == shm->shm_ptr) )
    175   {
    176     LOG_STRERROR ("ftruncate/mmap");
    177     (void) close (shm->shm_id);
    178     (void) shm_unlink (shm->shm_name);
    179     free (shm);
    180     return NULL;
    181   }
    182   shm->shm_size = size;
    183   shm->rc = 0;
    184   return shm;
    185 }
    186 
    187 
    188 /**
    189  * Change the reference counter for this shm instance.
    190  *
    191  * @param shm instance to update
    192  * @param delta value to change RC by
    193  * @return new RC
    194  */
    195 unsigned int
    196 EXTRACTOR_IPC_shared_memory_change_rc_ (struct EXTRACTOR_SharedMemory *shm,
    197                                         int delta)
    198 {
    199   shm->rc += delta;
    200   return shm->rc;
    201 }
    202 
    203 
    204 /**
    205  * Destroy shared memory area.
    206  *
    207  * @param shm memory area to destroy
    208  * @return NULL on error
    209  */
    210 void
    211 EXTRACTOR_IPC_shared_memory_destroy_ (struct EXTRACTOR_SharedMemory *shm)
    212 {
    213   munmap (shm->shm_ptr,
    214           shm->shm_size);
    215   (void) close (shm->shm_id);
    216   (void) shm_unlink (shm->shm_name);
    217   free (shm);
    218 }
    219 
    220 
    221 /**
    222  * Initialize shared memory area from data source.
    223  *
    224  * @param shm memory area to initialize
    225  * @param ds data source to use for initialization
    226  * @param off offset to use in data source
    227  * @param size number of bytes to copy
    228  * @return -1 on error, otherwise number of bytes copied
    229  */
    230 ssize_t
    231 EXTRACTOR_IPC_shared_memory_set_ (struct EXTRACTOR_SharedMemory *shm,
    232                                   struct EXTRACTOR_Datasource *ds,
    233                                   uint64_t off,
    234                                   size_t size)
    235 {
    236   if (-1 ==
    237       EXTRACTOR_datasource_seek_ (ds,
    238                                   off,
    239                                   SEEK_SET))
    240   {
    241     LOG ("Failed to set IPC memory due to seek error\n");
    242     return -1;
    243   }
    244   if (size > shm->shm_size)
    245     size = shm->shm_size;
    246   return EXTRACTOR_datasource_read_ (ds,
    247                                      shm->shm_ptr,
    248                                      size);
    249 }
    250 
    251 
    252 /**
    253  * Query datasource for current position
    254  *
    255  * @param ds data source to query
    256  * @return current position in the datasource or UINT64_MAX on error
    257  */
    258 uint64_t
    259 EXTRACTOR_datasource_get_pos_ (struct EXTRACTOR_Datasource *ds)
    260 {
    261   int64_t pos = EXTRACTOR_datasource_seek_ (ds,
    262                                             0,
    263                                             SEEK_CUR);
    264 
    265   if (-1 == pos)
    266     return UINT64_MAX;
    267   return pos;
    268 }
    269 
    270 
    271 /**
    272  * Create a channel to communicate with a process wrapping
    273  * the plugin of the given name.  Starts the process as well.
    274  *
    275  * @param plugin the plugin
    276  * @param shm memory to share with the process
    277  * @return NULL on error, otherwise IPC channel
    278  */
    279 struct EXTRACTOR_Channel *
    280 EXTRACTOR_IPC_channel_create_ (struct EXTRACTOR_PluginList *plugin,
    281                                struct EXTRACTOR_SharedMemory *shm)
    282 {
    283   struct EXTRACTOR_Channel *channel;
    284   int p1[2];
    285   int p2[2];
    286   pid_t pid;
    287   struct InitMessage *init;
    288   size_t slen;
    289   static int sigpipe_ignored;
    290 
    291   if (! sigpipe_ignored)
    292   {
    293     /* The cpipe_in end may be written to after the plugin child
    294        has died; without ignoring SIGPIPE this would terminate the
    295        host application.  As the channel uses pipes (not sockets),
    296        MSG_NOSIGNAL is not applicable, so we ignore SIGPIPE here. */
    297     signal (SIGPIPE, SIG_IGN);
    298     sigpipe_ignored = 1;
    299   }
    300   if (NULL == (channel = malloc (sizeof (struct EXTRACTOR_Channel))))
    301   {
    302     LOG_STRERROR ("malloc");
    303     return NULL;
    304   }
    305   channel->mdata_size = 1024;
    306   if (NULL == (channel->mdata = malloc (channel->mdata_size)))
    307   {
    308     LOG_STRERROR ("malloc");
    309     free (channel);
    310     return NULL;
    311   }
    312   channel->shm = shm;
    313   channel->plugin = plugin;
    314   channel->size = 0;
    315   if (0 != pipe (p1))
    316   {
    317     LOG_STRERROR ("pipe");
    318     free (channel->mdata);
    319     free (channel);
    320     return NULL;
    321   }
    322   if (0 != pipe (p2))
    323   {
    324     LOG_STRERROR ("pipe");
    325     (void) close (p1[0]);
    326     (void) close (p1[1]);
    327     free (channel->mdata);
    328     free (channel);
    329     return NULL;
    330   }
    331   pid = fork ();
    332   if (pid == -1)
    333   {
    334     LOG_STRERROR ("fork");
    335     (void) close (p1[0]);
    336     (void) close (p1[1]);
    337     (void) close (p2[0]);
    338     (void) close (p2[1]);
    339     free (channel->mdata);
    340     free (channel);
    341     return NULL;
    342   }
    343   if (0 == pid)
    344   {
    345     (void) close (p1[1]);
    346     (void) close (p2[0]);
    347     free (channel->mdata);
    348     free (channel);
    349 #if HAVE_SYS_APPARMOR_H
    350 #if HAVE_APPARMOR
    351     if (0 > aa_change_profile ("libextractor"))
    352     {
    353       int eno = errno;
    354 
    355       if ( (EINVAL != eno) &&
    356            (ENOENT != eno) )
    357       {
    358         fprintf (stderr,
    359                  "Failure changing AppArmor profile: %s\n",
    360                  strerror (errno));
    361         _exit (1);
    362       }
    363     }
    364 #endif
    365 #endif
    366     EXTRACTOR_plugin_main_ (plugin, p1[0], p2[1]);
    367     _exit (0);
    368   }
    369   (void) close (p1[0]);
    370   (void) close (p2[1]);
    371   channel->cpipe_in = p1[1];
    372   channel->cpipe_out = p2[0];
    373   channel->cpid = pid;
    374   slen = strlen (shm->shm_name) + 1;
    375   if (NULL == (init = malloc (sizeof (struct InitMessage) + slen)))
    376   {
    377     LOG_STRERROR ("malloc");
    378     EXTRACTOR_IPC_channel_destroy_ (channel);
    379     return NULL;
    380   }
    381   init->opcode = MESSAGE_INIT_STATE;
    382   init->reserved = 0;
    383   init->reserved2 = 0;
    384   init->shm_name_length = slen;
    385   init->shm_map_size = shm->shm_size;
    386   memcpy (&init[1], shm->shm_name, slen);
    387   if (sizeof (struct InitMessage) + slen !=
    388       EXTRACTOR_IPC_channel_send_ (channel,
    389                                    init,
    390                                    sizeof (struct InitMessage) + slen) )
    391   {
    392     LOG ("Failed to send INIT_STATE message to plugin\n");
    393     EXTRACTOR_IPC_channel_destroy_ (channel);
    394     free (init);
    395     return NULL;
    396   }
    397   free (init);
    398   return channel;
    399 }
    400 
    401 
    402 /**
    403  * Destroy communication channel with a plugin/process.  Also
    404  * destroys the process.
    405  *
    406  * @param channel channel to communicate with the plugin
    407  */
    408 void
    409 EXTRACTOR_IPC_channel_destroy_ (struct EXTRACTOR_Channel *channel)
    410 {
    411   int status;
    412 
    413   if (0 != kill (channel->cpid, SIGKILL))
    414     LOG_STRERROR ("kill");
    415   if (-1 == waitpid (channel->cpid, &status, 0))
    416     LOG_STRERROR ("waitpid");
    417   if (0 != close (channel->cpipe_out))
    418     LOG_STRERROR ("close");
    419   if (0 != close (channel->cpipe_in))
    420     LOG_STRERROR ("close");
    421   if (NULL != channel->plugin)
    422     channel->plugin->channel = NULL;
    423   free (channel->mdata);
    424   free (channel);
    425 }
    426 
    427 
    428 /**
    429  * Send data via the given IPC channel (blocking).
    430  *
    431  * @param channel channel to communicate with the plugin
    432  * @param buf data to send
    433  * @param size number of bytes in buf to send
    434  * @return -1 on error, number of bytes sent on success
    435  *           (never does partial writes)
    436  */
    437 ssize_t
    438 EXTRACTOR_IPC_channel_send_ (struct EXTRACTOR_Channel *channel,
    439                              const void *data,
    440                              size_t size)
    441 {
    442   const char *cdata = data;
    443   size_t off = 0;
    444   ssize_t ret;
    445 
    446   while (off < size)
    447   {
    448     ret = write (channel->cpipe_in, &cdata[off], size - off);
    449     if (ret <= 0)
    450     {
    451       if ((-1 == ret) && (EINTR == errno))
    452         continue;
    453       if (-1 == ret)
    454         LOG_STRERROR ("write");
    455       return -1;
    456     }
    457     off += ret;
    458   }
    459   return size;
    460 }
    461 
    462 
    463 /**
    464  * Receive data from any of the given IPC channels (blocking).
    465  * Wait for one of the plugins to reply.
    466  * Selects on plugin output pipes, runs 'receive_reply'
    467  * on each activated pipe until it gets a seek request
    468  * or a done message. Called repeatedly by the user until all pipes are dry or
    469  * broken.
    470  *
    471  * @param channels array of channels, channels that break may be set to NULL
    472  * @param num_channels length of the @a channels array
    473  * @param proc function to call to process messages (may be called
    474  *             more than once)
    475  * @param proc_cls closure for @a proc
    476  * @return -1 on error, 1 on success
    477  */
    478 int
    479 EXTRACTOR_IPC_channel_recv_ (struct EXTRACTOR_Channel **channels,
    480                              unsigned int num_channels,
    481                              EXTRACTOR_ChannelMessageProcessor proc,
    482                              void *proc_cls)
    483 {
    484   struct timeval tv;
    485   fd_set to_check;
    486   int max;
    487   unsigned int i;
    488   struct EXTRACTOR_Channel *channel;
    489   ssize_t ret;
    490   ssize_t iret;
    491   char *ndata;
    492   int closed_channel;
    493 
    494   FD_ZERO (&to_check);
    495   max = -1;
    496   for (i = 0; i<num_channels; i++)
    497   {
    498     channel = channels[i];
    499     if (NULL == channel)
    500       continue;
    501     FD_SET (channel->cpipe_out, &to_check);
    502     if (max < channel->cpipe_out)
    503       max = channel->cpipe_out;
    504   }
    505   if (-1 == max)
    506   {
    507     return 1;   /* nothing left to do! */
    508   }
    509   tv.tv_sec = 0;
    510   tv.tv_usec = 500000; /* 500 ms */
    511   if (0 >= select (max + 1, &to_check, NULL, NULL, &tv))
    512   {
    513     /* an error or timeout -> something's wrong or all plugins hung up */
    514     closed_channel = 0;
    515     for (i = 0; i<num_channels; i++)
    516     {
    517       channel = channels[i];
    518       if (NULL == channel)
    519         continue;
    520       if (-1 == channel->plugin->seek_request)
    521       {
    522         /* plugin blocked for too long, kill channel */
    523         LOG ("Channel blocked, closing channel to %s\n",
    524              channel->plugin->libname);
    525         channel->plugin->channel = NULL;
    526         channel->plugin->round_finished = 1;
    527         EXTRACTOR_IPC_channel_destroy_ (channel);
    528         channels[i] = NULL;
    529         closed_channel = 1;
    530       }
    531     }
    532     if (1 == closed_channel)
    533       return 1;
    534     /* strange, no channel is to blame, let's die just to be safe */
    535     if ((EINTR != errno) && (0 != errno))
    536       LOG_STRERROR ("select");
    537     return -1;
    538   }
    539   for (i = 0; i<num_channels; i++)
    540   {
    541     channel = channels[i];
    542     if (NULL == channel)
    543       continue;
    544     if (! FD_ISSET (channel->cpipe_out, &to_check))
    545       continue;
    546     if (channel->mdata_size == channel->size)
    547     {
    548       /* not enough space, need to grow allocation (if allowed) */
    549       if (MAX_META_DATA == channel->mdata_size)
    550       {
    551         LOG ("Inbound message from channel too large, aborting\n");
    552         EXTRACTOR_IPC_channel_destroy_ (channel);
    553         channels[i] = NULL;
    554         continue;
    555       }
    556       channel->mdata_size *= 2;
    557       if (channel->mdata_size > MAX_META_DATA)
    558         channel->mdata_size = MAX_META_DATA;
    559       if (NULL == (ndata = realloc (channel->mdata,
    560                                     channel->mdata_size)))
    561       {
    562         LOG_STRERROR ("realloc");
    563         EXTRACTOR_IPC_channel_destroy_ (channel);
    564         channels[i] = NULL;
    565         continue;
    566       }
    567       channel->mdata = ndata;
    568     }
    569     do
    570     {
    571       iret = read (channel->cpipe_out,
    572                    &channel->mdata[channel->size],
    573                    channel->mdata_size - channel->size);
    574     }
    575     while ((-1 == iret) && (EINTR == errno));
    576     if ( (-1 == iret) ||
    577          (0 == iret) ||
    578          (-1 == (ret = EXTRACTOR_IPC_process_reply_ (channel->plugin,
    579                                                      channel->mdata,
    580                                                      channel->size + iret,
    581                                                      proc, proc_cls)) ) )
    582     {
    583       if (-1 == iret)
    584         LOG_STRERROR ("read");
    585       LOG ("Read error from channel, closing channel %s\n",
    586            channel->plugin->libname);
    587       EXTRACTOR_IPC_channel_destroy_ (channel);
    588       channels[i] = NULL;
    589       continue;
    590     }
    591     else
    592     {
    593       channel->size = channel->size + iret - ret;
    594       memmove (channel->mdata,
    595                &channel->mdata[ret],
    596                channel->size);
    597     }
    598   }
    599   return 1;
    600 }
    601 
    602 
    603 /* end of extractor_ipc_gnu.c */