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