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