-fixing #3034
[oweals/gnunet.git] / src / fs / gnunet-helper-fs-publish.c
1 /*
2      This file is part of GNUnet.
3      (C) 2012 Christian Grothoff (and other contributing authors)
4
5      GNUnet 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      GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file src/fs/gnunet-helper-fs-publish.c
23  * @brief Tool to help extract meta data asynchronously
24  * @author Christian Grothoff
25  *
26  * This program will scan a directory for files with meta data
27  * and report the results to stdout.
28  */
29 #include "platform.h"
30 #include "gnunet_fs_service.h"
31
32
33 /**
34  * A node of a directory tree.
35  */
36 struct ScanTreeNode
37 {
38
39   /**
40    * This is a doubly-linked list
41    */
42   struct ScanTreeNode *next;
43
44   /**
45    * This is a doubly-linked list
46    */
47   struct ScanTreeNode *prev;
48
49   /**
50    * Parent of this node, NULL for top-level entries.
51    */
52   struct ScanTreeNode *parent;
53
54   /**
55    * This is a doubly-linked tree
56    * NULL for files and empty directories
57    */
58   struct ScanTreeNode *children_head;
59
60   /**
61    * This is a doubly-linked tree
62    * NULL for files and empty directories
63    */
64   struct ScanTreeNode *children_tail;
65
66   /**
67    * Name of the file/directory
68    */
69   char *filename;
70
71   /**
72    * Size of the file (if it is a file), in bytes.
73    * At the moment it is set to 0 for directories.
74    */
75   uint64_t file_size;
76
77   /**
78    * GNUNET_YES if this is a directory
79    */
80   int is_directory;
81
82 };
83
84
85 /**
86  * List of libextractor plugins to use for extracting.
87  */
88 static struct EXTRACTOR_PluginList *plugins;
89
90 /**
91  * File descriptor we use for IPC with the parent.
92  */
93 static int output_stream;
94
95
96 /**
97  * Add meta data that libextractor finds to our meta data
98  * container.
99  *
100  * @param cls closure, our meta data container
101  * @param plugin_name name of the plugin that produced this value;
102  *        special values can be used (i.e. '<zlib>' for zlib being
103  *        used in the main libextractor library and yielding
104  *        meta data).
105  * @param type libextractor-type describing the meta data
106  * @param format basic format information about data
107  * @param data_mime_type mime-type of data (not of the original file);
108  *        can be NULL (if mime-type is not known)
109  * @param data actual meta-data found
110  * @param data_len number of bytes in data
111  * @return always 0 to continue extracting
112  */
113 static int
114 add_to_md (void *cls, const char *plugin_name, enum EXTRACTOR_MetaType type,
115            enum EXTRACTOR_MetaFormat format, const char *data_mime_type,
116            const char *data, size_t data_len)
117 {
118   struct GNUNET_CONTAINER_MetaData *md = cls;
119
120   (void) GNUNET_CONTAINER_meta_data_insert (md, plugin_name, type, format,
121                                             data_mime_type, data, data_len);
122   return 0;
123 }
124
125
126 /**
127  * Free memory of the 'tree' structure
128  *
129  * @param tree tree to free
130  */
131 static void 
132 free_tree (struct ScanTreeNode *tree)
133 {
134   struct ScanTreeNode *pos;
135
136   while (NULL != (pos = tree->children_head))
137     free_tree (pos);
138   if (NULL != tree->parent)
139     GNUNET_CONTAINER_DLL_remove (tree->parent->children_head,
140                                  tree->parent->children_tail,
141                                  tree);                          
142   GNUNET_free (tree->filename);
143   GNUNET_free (tree);
144 }
145
146
147 /**
148  * Write @a size bytes from @a buf into the #output_stream.
149  *
150  * @param buf buffer with data to write
151  * @param size number of bytes to write
152  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
153  */
154 static int
155 write_all (const void *buf,
156            size_t size)
157 {
158   const char *cbuf = buf;
159   size_t total;
160   ssize_t wr;
161
162   total = 0;
163   do
164   {
165     wr = write (output_stream,
166                 &cbuf[total],
167                 size - total);
168     if (wr > 0)
169       total += wr;
170   } while ( (wr > 0) && (total < size) );
171   if (wr <= 0)
172     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
173                 "Failed to write to stdout: %s\n",
174                 strerror (errno));
175   return (total == size) ? GNUNET_OK : GNUNET_SYSERR;
176 }
177
178
179 /**
180  * Write message to the master process.
181  *
182  * @param message_type message type to use
183  * @param data data to append, NULL for none
184  * @param data_length number of bytes in @a data
185  * @return #GNUNET_SYSERR to stop scanning (the pipe was broken somehow)
186  */
187 static int
188 write_message (uint16_t message_type,
189                const char *data,
190                size_t data_length)
191 {
192   struct GNUNET_MessageHeader hdr;
193
194 #if 0
195   fprintf (stderr, 
196            "Helper sends %u-byte message of type %u\n",
197            (unsigned int) (sizeof (struct GNUNET_MessageHeader) + data_length),
198            (unsigned int) message_type);
199 #endif
200   hdr.type = htons (message_type);
201   hdr.size = htons (sizeof (struct GNUNET_MessageHeader) + data_length);
202   if ( (GNUNET_OK !=
203         write_all (&hdr,
204                    sizeof (hdr))) ||
205        (GNUNET_OK !=
206         write_all (data,
207                    data_length)) )
208     return GNUNET_SYSERR;
209   return GNUNET_OK;
210 }
211
212
213 /**
214  * Function called to (recursively) add all of the files in the
215  * directory to the tree.  Called by the directory scanner to initiate
216  * the scan.  Does NOT yet add any metadata.
217  *
218  * @param filename file or directory to scan
219  * @param dst where to store the resulting share tree item;
220  *         NULL is stored in @a dst upon recoverable errors (#GNUNET_OK is returned)
221  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
222  */
223 static int
224 preprocess_file (const char *filename,
225                  struct ScanTreeNode **dst);
226
227
228 /**
229  * Closure for the 'scan_callback'
230  */
231 struct RecursionContext
232 {
233   /**
234    * Parent to add the files to.
235    */
236   struct ScanTreeNode *parent;
237
238   /**
239    * Flag to set to GNUNET_YES on serious errors.
240    */
241   int stop;
242 };
243
244
245 /**
246  * Function called by the directory iterator to (recursively) add all
247  * of the files in the directory to the tree.  Called by the directory
248  * scanner to initiate the scan.  Does NOT yet add any metadata.
249  *
250  * @param cls the `struct RecursionContext`
251  * @param filename file or directory to scan
252  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
253  */
254 static int
255 scan_callback (void *cls,
256                const char *filename)
257 {
258   struct RecursionContext *rc = cls;
259   struct ScanTreeNode *chld;
260
261   if (GNUNET_OK !=
262       preprocess_file (filename,
263                        &chld))
264   {
265     rc->stop = GNUNET_YES;
266     return GNUNET_SYSERR;
267   }
268   if (NULL == chld)
269     return GNUNET_OK;
270   chld->parent = rc->parent;
271   GNUNET_CONTAINER_DLL_insert (rc->parent->children_head,
272                                rc->parent->children_tail,
273                                chld);
274   return GNUNET_OK;
275 }
276
277
278 /**
279  * Function called to (recursively) add all of the files in the
280  * directory to the tree.  Called by the directory scanner to initiate
281  * the scan.  Does NOT yet add any metadata.
282  *
283  * @param filename file or directory to scan
284  * @param dst where to store the resulting share tree item;
285  *         NULL is stored in @a dst upon recoverable errors (#GNUNET_OK is returned) 
286  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
287  */
288 static int
289 preprocess_file (const char *filename,
290                  struct ScanTreeNode **dst)
291 {
292   struct ScanTreeNode *item;
293   struct stat sbuf;
294   uint64_t fsize = 0;
295
296   if ((0 != STAT (filename, &sbuf)) ||
297       ((!S_ISDIR (sbuf.st_mode)) && (GNUNET_OK != GNUNET_DISK_file_size (
298       filename, &fsize, GNUNET_NO, GNUNET_YES))))
299   {
300     /* If the file doesn't exist (or is not stat-able for any other reason)
301        skip it (but report it), but do continue. */
302     if (GNUNET_OK !=
303         write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_SKIP_FILE,
304                        filename, strlen (filename) + 1))
305       return GNUNET_SYSERR;
306     /* recoverable error, store 'NULL' in *dst */
307     *dst = NULL;
308     return GNUNET_OK;
309   }
310
311   /* Report the progress */
312   if (GNUNET_OK !=
313       write_message (S_ISDIR (sbuf.st_mode) 
314                      ? GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_PROGRESS_DIRECTORY
315                      : GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_PROGRESS_FILE,
316                      filename, strlen (filename) + 1))
317     return GNUNET_SYSERR;
318   item = GNUNET_malloc (sizeof (struct ScanTreeNode));
319   item->filename = GNUNET_strdup (filename);
320   item->is_directory = (S_ISDIR (sbuf.st_mode)) ? GNUNET_YES : GNUNET_NO;
321   item->file_size = fsize;
322   if (GNUNET_YES == item->is_directory)
323   {
324     struct RecursionContext rc;
325
326     rc.parent = item;
327     rc.stop = GNUNET_NO;
328     GNUNET_DISK_directory_scan (filename, 
329                                 &scan_callback, 
330                                 &rc);    
331     if ( (GNUNET_YES == rc.stop) ||
332          (GNUNET_OK !=
333           write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_PROGRESS_DIRECTORY,
334                          "..", 3)) )
335     {
336       free_tree (item);
337       return GNUNET_SYSERR;
338     }
339   }
340   *dst = item;
341   return GNUNET_OK;
342 }
343
344
345 /**
346  * Extract metadata from files.
347  *
348  * @param item entry we are processing
349  * @return #GNUNET_OK on success, #GNUNET_SYSERR on fatal errors
350  */
351 static int
352 extract_files (struct ScanTreeNode *item)
353 {  
354   struct GNUNET_CONTAINER_MetaData *meta;
355   ssize_t size;
356   size_t slen;
357
358   if (GNUNET_YES == item->is_directory)
359   {
360     /* for directories, we simply only descent, no extraction, no
361        progress reporting */
362     struct ScanTreeNode *pos;
363
364     for (pos = item->children_head; NULL != pos; pos = pos->next)
365       if (GNUNET_OK !=
366           extract_files (pos))
367         return GNUNET_SYSERR;
368     return GNUNET_OK;
369   }
370   
371   /* this is the expensive operation, *afterwards* we'll check for aborts */
372   meta = GNUNET_CONTAINER_meta_data_create ();
373   EXTRACTOR_extract (plugins, item->filename, NULL, 0, &add_to_md, meta);
374   slen = strlen (item->filename) + 1;
375   size = GNUNET_CONTAINER_meta_data_get_serialized_size (meta);
376   if (-1 == size)
377   {
378     /* no meta data */
379     GNUNET_CONTAINER_meta_data_destroy (meta);
380     if (GNUNET_OK !=
381         write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_META_DATA,
382                        item->filename, slen))
383       return GNUNET_SYSERR;    
384     return GNUNET_OK;
385   }
386   else if (size > (UINT16_MAX - sizeof (struct GNUNET_MessageHeader) - slen))
387   {
388     /* We can't transfer more than 64k bytes in one message. */
389     size = UINT16_MAX - sizeof (struct GNUNET_MessageHeader) - slen;
390   }
391   {
392     char buf[size + slen];
393     char *dst = &buf[slen];
394     
395     memcpy (buf, item->filename, slen);
396     size = GNUNET_CONTAINER_meta_data_serialize (meta,
397                                                  &dst, size,
398                                                  GNUNET_CONTAINER_META_DATA_SERIALIZE_PART);
399     if (size < 0)
400     {
401       GNUNET_break (0);
402       size = 0;
403     }
404     GNUNET_CONTAINER_meta_data_destroy (meta);
405     if (GNUNET_OK !=
406         write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_META_DATA,
407                        buf, 
408                        slen + size))
409       return GNUNET_SYSERR;
410   }
411   return GNUNET_OK;
412 }
413
414
415 #ifndef WINDOWS
416 /**
417  * Install a signal handler to ignore SIGPIPE.
418  */
419 static void
420 ignore_sigpipe ()
421 {
422   struct sigaction oldsig;
423   struct sigaction sig;
424
425   memset (&sig, 0, sizeof (struct sigaction));
426   sig.sa_handler = SIG_IGN;
427   sigemptyset (&sig.sa_mask);
428 #ifdef SA_INTERRUPT
429   sig.sa_flags = SA_INTERRUPT;  /* SunOS */
430 #else
431   sig.sa_flags = SA_RESTART;
432 #endif
433   if (0 != sigaction (SIGPIPE, &sig, &oldsig))
434     fprintf (stderr,
435              "Failed to install SIGPIPE handler: %s\n", strerror (errno));
436 }
437
438
439 /**
440  * Turn the given file descriptor in to '/dev/null'.
441  *
442  * @param fd fd to bind to /dev/null
443  * @param flags flags to use (O_RDONLY or O_WRONLY)
444  */
445 static void
446 make_dev_zero (int fd, 
447                int flags)
448 {
449   int z;
450
451   GNUNET_assert (0 == close (fd));
452   z = open ("/dev/null", flags);
453   GNUNET_assert (-1 != z);
454   if (z == fd)
455     return;
456   dup2 (z, fd);
457   GNUNET_assert (0 == close (z));
458 }
459
460 #endif
461
462
463 /**
464  * Main function of the helper process to extract meta data.
465  *
466  * @param argc should be 3
467  * @param argv [0] our binary name
468  *             [1] name of the file or directory to process
469  *             [2] "-" to disable extraction, NULL for defaults,
470  *                 otherwise custom plugins to load from LE
471  * @return 0 on success
472  */
473 int 
474 main (int argc,
475       char *const *argv)
476 {
477   const char *filename_expanded;
478   const char *ex;
479   struct ScanTreeNode *root;
480
481 #if WINDOWS
482   /* We're using stdout to communicate binary data back to the parent; use
483    * binary mode.
484    */
485   _setmode (1, _O_BINARY);
486   /* Get utf-8-encoded arguments */
487   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
488     return 5;
489   output_stream = 1; /* stdout */
490 #else
491   ignore_sigpipe ();
492   /* move stdout to some other FD for IPC, bind 
493      stdout/stderr to /dev/null */
494   output_stream = dup (1);
495   make_dev_zero (1, O_WRONLY);
496   make_dev_zero (2, O_WRONLY);
497 #endif
498
499   /* parse command line */
500   if ( (3 != argc) && (2 != argc) )
501   {
502     FPRINTF (stderr, 
503              "%s",
504              "gnunet-helper-fs-publish needs exactly one or two arguments\n");
505 #if WINDOWS
506     GNUNET_free ((void*) argv);
507 #endif
508     return 1;
509   }
510   filename_expanded = argv[1];
511   ex = argv[2];
512   if ( (NULL == ex) ||
513        (0 != strcmp (ex, "-")) )
514   {
515     plugins = EXTRACTOR_plugin_add_defaults (EXTRACTOR_OPTION_DEFAULT_POLICY);
516     if (NULL != ex)
517       plugins = EXTRACTOR_plugin_add_config (plugins, ex,
518                                              EXTRACTOR_OPTION_DEFAULT_POLICY);
519   }
520
521   /* scan tree to find out how much work there is to be done */
522   if (GNUNET_OK != preprocess_file (filename_expanded, 
523                                     &root))
524   {
525     (void) write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_ERROR, NULL, 0);
526     EXTRACTOR_plugin_remove_all (plugins);
527 #if WINDOWS
528     GNUNET_free ((void*) argv);
529 #endif
530     return 2;
531   }
532   /* signal that we're done counting files, so that a percentage of 
533      progress can now be calculated */
534   if (GNUNET_OK !=
535       write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_COUNTING_DONE, NULL, 0))
536   {
537     EXTRACTOR_plugin_remove_all (plugins);
538 #if WINDOWS
539     GNUNET_free ((void*) argv);
540 #endif
541     return 3;  
542   }
543   if (NULL != root)
544   {
545     if (GNUNET_OK !=
546         extract_files (root))
547     {
548       (void) write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_ERROR, NULL, 0);
549       free_tree (root);
550       EXTRACTOR_plugin_remove_all (plugins);
551 #if WINDOWS
552       GNUNET_free ((void*) argv);
553 #endif
554       return 4;
555     }
556     free_tree (root);
557   }
558   /* enable "clean" shutdown by telling parent that we are done */
559   (void) write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_FINISHED, NULL, 0);
560   EXTRACTOR_plugin_remove_all (plugins);
561 #if WINDOWS
562   GNUNET_free ((void*) argv);  
563 #endif
564   return 0;
565 }
566
567 /* end of gnunet-helper-fs-publish.c */
568