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