-LRN: calculate file size for single files when needed and use GNUNET_DISK_file_size...
[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   hdr.type = htons (message_type);
190   hdr.size = htons (sizeof (struct GNUNET_MessageHeader) + data_length);
191   if ( (GNUNET_OK !=
192         write_all (&hdr,
193                    sizeof (hdr))) ||
194        (GNUNET_OK !=
195         write_all (data,
196                    data_length)) )
197     return GNUNET_SYSERR;
198   return GNUNET_OK;
199 }
200
201
202 /**
203  * Function called to (recursively) add all of the files in the
204  * directory to the tree.  Called by the directory scanner to initiate
205  * the scan.  Does NOT yet add any metadata.
206  *
207  * @param filename file or directory to scan
208  * @param dst where to store the resulting share tree item
209  * @return GNUNET_OK on success, GNUNET_SYSERR on error
210  */
211 static int
212 preprocess_file (const char *filename,
213                  struct ScanTreeNode **dst);
214
215
216 /**
217  * Closure for the 'scan_callback'
218  */
219 struct RecursionContext
220 {
221   /**
222    * Parent to add the files to.
223    */
224   struct ScanTreeNode *parent;
225
226   /**
227    * Flag to set to GNUNET_YES on serious errors.
228    */
229   int stop;
230 };
231
232
233 /**
234  * Function called by the directory iterator to (recursively) add all
235  * of the files in the directory to the tree.  Called by the directory
236  * scanner to initiate the scan.  Does NOT yet add any metadata.
237  *
238  * @param cls the 'struct RecursionContext'
239  * @param filename file or directory to scan
240  * @return GNUNET_OK on success, GNUNET_SYSERR on error
241  */
242 static int
243 scan_callback (void *cls,
244                const char *filename)
245 {
246   struct RecursionContext *rc = cls;
247   struct ScanTreeNode *chld;
248
249   if (GNUNET_OK !=
250       preprocess_file (filename,
251                        &chld))
252   {
253     rc->stop = GNUNET_YES;
254     return GNUNET_SYSERR;
255   }
256   chld->parent = 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   uint64_t fsize = 0;
280
281   if ((0 != STAT (filename, &sbuf)) ||
282       ((!S_ISDIR (sbuf.st_mode)) && (GNUNET_OK != GNUNET_DISK_file_size (
283       filename, &fsize, GNUNET_NO, GNUNET_YES))))
284   {
285     /* If the file doesn't exist (or is not stat-able for any other reason)
286        skip it (but report it), but do continue. */
287     if (GNUNET_OK !=
288         write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_SKIP_FILE,
289                        filename, strlen (filename) + 1))
290       return GNUNET_SYSERR;
291     return GNUNET_OK;
292   }
293
294   /* Report the progress */
295   if (GNUNET_OK !=
296       write_message (S_ISDIR (sbuf.st_mode) 
297                      ? GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_PROGRESS_DIRECTORY
298                      : GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_PROGRESS_FILE,
299                      filename, strlen (filename) + 1))
300     return GNUNET_SYSERR;
301   item = GNUNET_malloc (sizeof (struct ScanTreeNode));
302   item->filename = GNUNET_strdup (filename);
303   item->is_directory = (S_ISDIR (sbuf.st_mode)) ? GNUNET_YES : GNUNET_NO;
304   item->file_size = fsize;
305   if (item->is_directory == GNUNET_YES)
306   {
307     struct RecursionContext rc;
308
309     rc.parent = item;
310     rc.stop = GNUNET_NO;
311     GNUNET_DISK_directory_scan (filename, 
312                                 &scan_callback, 
313                                 &rc);    
314     if ( (rc.stop == GNUNET_YES) ||
315          (GNUNET_OK !=
316           write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_PROGRESS_DIRECTORY,
317                          "..", 3)) )
318     {
319       free_tree (item);
320       return GNUNET_SYSERR;
321     }
322   }
323   *dst = item;
324   return GNUNET_OK;
325 }
326
327
328 /**
329  * Extract metadata from files.
330  *
331  * @param item entry we are processing
332  * @return GNUNET_OK on success, GNUNET_SYSERR on fatal errors
333  */
334 static int
335 extract_files (struct ScanTreeNode *item)
336 {  
337   struct GNUNET_CONTAINER_MetaData *meta;
338   ssize_t size;
339   size_t slen;
340
341   if (item->is_directory == GNUNET_YES)
342   {
343     /* for directories, we simply only descent, no extraction, no
344        progress reporting */
345     struct ScanTreeNode *pos;
346
347     for (pos = item->children_head; NULL != pos; pos = pos->next)
348       if (GNUNET_OK !=
349           extract_files (pos))
350         return GNUNET_SYSERR;
351     return GNUNET_OK;
352   }
353   
354   /* this is the expensive operation, *afterwards* we'll check for aborts */
355   meta = GNUNET_CONTAINER_meta_data_create ();
356   if (NULL != plugins)
357     EXTRACTOR_extract (plugins, item->filename, NULL, 0, &add_to_md, meta);
358   slen = strlen (item->filename) + 1;
359   size = GNUNET_CONTAINER_meta_data_get_serialized_size (meta);
360   if (-1 == size)
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 - slen,
377                                                  GNUNET_CONTAINER_META_DATA_SERIALIZE_PART);
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 #if WINDOWS
407   /* We're using stdout to communicate binary data back to the parent; use
408    * binary mode.
409    */
410   _setmode (1, _O_BINARY);
411 #endif
412
413   /* parse command line */
414   if ( (argc != 3) && (argc != 2) )
415   {
416     FPRINTF (stderr, 
417              "%s",
418              "gnunet-helper-fs-publish needs exactly one or two arguments\n");
419     return 1;
420   }
421   filename_expanded = argv[1];
422   ex = argv[2];
423   if ( (ex == NULL) ||
424        (0 != strcmp (ex, "-")) )
425   {
426     plugins = EXTRACTOR_plugin_add_defaults (EXTRACTOR_OPTION_DEFAULT_POLICY);
427     if (NULL != ex)
428       plugins = EXTRACTOR_plugin_add_config (plugins, ex,
429                                              EXTRACTOR_OPTION_DEFAULT_POLICY);
430   }
431
432   /* scan tree to find out how much work there is to be done */
433   if (GNUNET_OK != preprocess_file (filename_expanded, 
434                                     &root))
435   {
436     (void) write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_ERROR, NULL, 0);
437     return 2;
438   }
439   /* signal that we're done counting files, so that a percentage of 
440      progress can now be calculated */
441   if (GNUNET_OK !=
442       write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_COUNTING_DONE, NULL, 0))
443     return 3;  
444   if (GNUNET_OK !=
445       extract_files (root))
446   {
447     (void) write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_ERROR, NULL, 0);
448     free_tree (root);
449     return 4;
450   }
451   free_tree (root);
452   /* enable "clean" shutdown by telling parent that we are done */
453   (void) write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_FINISHED, NULL, 0);
454   if (NULL != plugins)
455     EXTRACTOR_plugin_remove_all (plugins);
456
457   return 0;
458 }
459
460 /* end of gnunet-helper-fs-publish.c */
461