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