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