LRN: Use binary mode on W32 (lol -CG)
[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          (GNUNET_OK !=
313           write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_PROGRESS_DIRECTORY,
314                          "..", 3)) )
315     {
316       free_tree (item);
317       return GNUNET_SYSERR;
318     }
319   }
320   *dst = item;
321   return GNUNET_OK;
322 }
323
324
325 /**
326  * Extract metadata from files.
327  *
328  * @param item entry we are processing
329  * @return GNUNET_OK on success, GNUNET_SYSERR on fatal errors
330  */
331 static int
332 extract_files (struct ScanTreeNode *item)
333 {  
334   struct GNUNET_CONTAINER_MetaData *meta;
335   ssize_t size;
336   size_t slen;
337
338   if (item->is_directory)
339   {
340     /* for directories, we simply only descent, no extraction, no
341        progress reporting */
342     struct ScanTreeNode *pos;
343
344     for (pos = item->children_head; NULL != pos; pos = pos->next)
345       if (GNUNET_OK !=
346           extract_files (pos))
347         return GNUNET_SYSERR;
348     return GNUNET_OK;
349   }
350   
351   /* this is the expensive operation, *afterwards* we'll check for aborts */
352   meta = GNUNET_CONTAINER_meta_data_create ();
353   if (NULL != plugins)
354     EXTRACTOR_extract (plugins, item->filename, NULL, 0, &add_to_md, meta);
355   slen = strlen (item->filename) + 1;
356   size = GNUNET_CONTAINER_meta_data_get_serialized_size (meta);
357   if ( (-1 == size) ||
358        (size >= GNUNET_SERVER_MAX_MESSAGE_SIZE - slen) )
359   {
360     /* no meta data */
361     GNUNET_CONTAINER_meta_data_destroy (meta);
362     if (GNUNET_OK !=
363         write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_META_DATA,
364                        item->filename, slen))
365       return GNUNET_SYSERR;    
366     return GNUNET_OK;
367   }
368   {
369     char buf[size + slen];
370     char *dst = &buf[slen];
371     
372     memcpy (buf, item->filename, slen);
373     size = GNUNET_CONTAINER_meta_data_serialize (meta,
374                                                  &dst, size,
375                                                  GNUNET_CONTAINER_META_DATA_SERIALIZE_FULL);
376     GNUNET_CONTAINER_meta_data_destroy (meta);
377     if (GNUNET_OK !=
378         write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_META_DATA,
379                        buf, 
380                        slen + size))
381       return GNUNET_SYSERR;
382   }
383   return GNUNET_OK;
384 }
385
386
387 /**
388  * Main function of the helper process to extract meta data.
389  *
390  * @param argc should be 3
391  * @param argv [0] our binary name
392  *             [1] name of the file or directory to process
393  *             [2] "-" to disable extraction, NULL for defaults,
394  *                 otherwise custom plugins to load from LE
395  * @return 0 on success
396  */
397 int main(int argc,
398          char **argv)
399 {
400   const char *filename_expanded;
401   const char *ex;
402   struct ScanTreeNode *root;
403
404 #if WINDOWS
405   /* We're using stdout to communicate binary data back to the parent; use
406    * binary mode.
407    */
408   _setmode (1, _O_BINARY);
409 #endif
410
411   /* parse command line */
412   if ( (argc != 3) && (argc != 2) )
413   {
414     FPRINTF (stderr, 
415              "%s",
416              "gnunet-helper-fs-publish needs exactly one or two arguments\n");
417     return 1;
418   }
419   filename_expanded = argv[1];
420   ex = argv[2];
421   if ( (ex == NULL) ||
422        (0 != strcmp (ex, "-")) )
423   {
424     plugins = EXTRACTOR_plugin_add_defaults (EXTRACTOR_OPTION_DEFAULT_POLICY);
425     if (NULL != ex)
426       plugins = EXTRACTOR_plugin_add_config (plugins, ex,
427                                              EXTRACTOR_OPTION_DEFAULT_POLICY);
428   }
429
430   /* scan tree to find out how much work there is to be done */
431   if (GNUNET_OK != preprocess_file (filename_expanded, 
432                                     &root))
433   {
434     (void) write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_ERROR, NULL, 0);
435     return 2;
436   }
437   /* signal that we're done counting files, so that a percentage of 
438      progress can now be calculated */
439   if (GNUNET_OK !=
440       write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_COUNTING_DONE, NULL, 0))
441     return 3;  
442   if (GNUNET_OK !=
443       extract_files (root))
444   {
445     (void) write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_ERROR, NULL, 0);
446     free_tree (root);
447     return 4;
448   }
449   free_tree (root);
450   /* enable "clean" shutdown by telling parent that we are done */
451   (void) write_message (GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_FINISHED, NULL, 0);
452   if (NULL != plugins)
453     EXTRACTOR_plugin_remove_all (plugins);
454
455   return 0;
456 }
457
458 /* end of gnunet-helper-fs-publish.c */
459