glitch in the license text detected by hyazinthe, thank you!
[oweals/gnunet.git] / src / fs / fs_dirmetascan.c
1 /*
2      This file is part of GNUnet
3      Copyright (C) 2005-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
16 /**
17  * @file fs/fs_dirmetascan.c
18  * @brief code to asynchronously build a 'struct GNUNET_FS_ShareTreeItem'
19  *        from an on-disk directory for publishing; use the 'gnunet-helper-fs-publish'.
20  * @author LRN
21  * @author Christian Grothoff
22  */
23 #include "platform.h"
24 #include "gnunet_fs_service.h"
25 #include "gnunet_scheduler_lib.h"
26 #include <pthread.h>
27
28
29 /**
30  * An opaque structure a pointer to which is returned to the
31  * caller to be used to control the scanner.
32  */
33 struct GNUNET_FS_DirScanner
34 {
35
36   /**
37    * Helper process.
38    */
39   struct GNUNET_HELPER_Handle *helper;
40
41   /**
42    * Expanded filename (as given by the scan initiator).
43    * The scanner thread stores a copy here, and frees it when it finishes.
44    */
45   char *filename_expanded;
46
47   /**
48    * Second argument to helper process.
49    */
50   char *ex_arg;
51
52   /**
53    * The function that will be called every time there's a progress
54    * message.
55    */
56   GNUNET_FS_DirScannerProgressCallback progress_callback;
57
58   /**
59    * A closure for progress_callback.
60    */
61   void *progress_callback_cls;
62
63   /**
64    * After the scan is finished, it will contain a pointer to the
65    * top-level directory entry in the directory tree built by the
66    * scanner.
67    */
68   struct GNUNET_FS_ShareTreeItem *toplevel;
69
70   /**
71    * Current position during processing.
72    */
73   struct GNUNET_FS_ShareTreeItem *pos;
74
75   /**
76    * Task scheduled when we are done.
77    */
78   struct GNUNET_SCHEDULER_Task * stop_task;
79
80   /**
81    * Arguments for helper.
82    */
83   char *args[4];
84
85 };
86
87
88 /**
89  * Abort the scan.  Must not be called from within the progress_callback
90  * function.
91  *
92  * @param ds directory scanner structure
93  */
94 void
95 GNUNET_FS_directory_scan_abort (struct GNUNET_FS_DirScanner *ds)
96 {
97   /* terminate helper */
98   if (NULL != ds->helper)
99     GNUNET_HELPER_stop (ds->helper, GNUNET_NO);
100
101   /* free resources */
102   if (NULL != ds->toplevel)
103     GNUNET_FS_share_tree_free (ds->toplevel);
104   if (NULL != ds->stop_task)
105     GNUNET_SCHEDULER_cancel (ds->stop_task);
106   GNUNET_free_non_null (ds->ex_arg);
107   GNUNET_free (ds->filename_expanded);
108   GNUNET_free (ds);
109 }
110
111
112 /**
113  * Obtain the result of the scan after the scan has signalled
114  * completion.  Must not be called prior to completion.  The 'ds' is
115  * freed as part of this call.
116  *
117  * @param ds directory scanner structure
118  * @return the results of the scan (a directory tree)
119  */
120 struct GNUNET_FS_ShareTreeItem *
121 GNUNET_FS_directory_scan_get_result (struct GNUNET_FS_DirScanner *ds)
122 {
123   struct GNUNET_FS_ShareTreeItem *result;
124
125   /* check that we're actually done */
126   GNUNET_assert (NULL == ds->helper);
127   /* preserve result */
128   result = ds->toplevel;
129   ds->toplevel = NULL;
130   GNUNET_FS_directory_scan_abort (ds);
131   return result;
132 }
133
134
135 /**
136  * Move in the directory from the given position to the next file
137  * in DFS traversal.
138  *
139  * @param pos current position
140  * @return next file, NULL for none
141  */
142 static struct GNUNET_FS_ShareTreeItem *
143 advance (struct GNUNET_FS_ShareTreeItem *pos)
144 {
145   int moved;
146
147   GNUNET_assert (NULL != pos);
148   moved = 0; /* must not terminate, even on file, otherwise "normal" */
149   while ( (pos->is_directory == GNUNET_YES) ||
150           (0 == moved) )
151   {
152     if ( (moved != -1) &&
153          (NULL != pos->children_head) )
154     {
155       pos = pos->children_head;
156       moved = 1; /* can terminate if file */
157       continue;
158     }
159     if (NULL != pos->next)
160     {
161       pos = pos->next;
162       moved = 1; /* can terminate if file */
163       continue;
164     }
165     if (NULL != pos->parent)
166     {
167       pos = pos->parent;
168       moved = -1; /* force move to 'next' or 'parent' */
169       continue;
170     }
171     /* no more options, end of traversal */
172     return NULL;
173   }
174   return pos;
175 }
176
177
178 /**
179  * Add another child node to the tree.
180  *
181  * @param parent parent of the child, NULL for top level
182  * @param filename name of the file or directory
183  * @param is_directory GNUNET_YES for directories
184  * @return new entry that was just created
185  */
186 static struct GNUNET_FS_ShareTreeItem *
187 expand_tree (struct GNUNET_FS_ShareTreeItem *parent,
188              const char *filename,
189              int is_directory)
190 {
191   struct GNUNET_FS_ShareTreeItem *chld;
192   size_t slen;
193
194   chld = GNUNET_new (struct GNUNET_FS_ShareTreeItem);
195   chld->parent = parent;
196   chld->filename = GNUNET_strdup (filename);
197   GNUNET_asprintf (&chld->short_filename,
198                    "%s%s",
199                    GNUNET_STRINGS_get_short_name (filename),
200                    is_directory == GNUNET_YES ? "/" : "");
201   /* make sure we do not end with '//' */
202   slen = strlen (chld->short_filename);
203   if ( (slen >= 2) &&
204        (chld->short_filename[slen-1] == '/') &&
205        (chld->short_filename[slen-2] == '/') )
206     chld->short_filename[slen-1] = '\0';
207   chld->is_directory = is_directory;
208   if (NULL != parent)
209     GNUNET_CONTAINER_DLL_insert (parent->children_head,
210                                  parent->children_tail,
211                                  chld);
212   return chld;
213 }
214
215
216 /**
217  * Task run last to shut everything down.
218  *
219  * @param cls the 'struct GNUNET_FS_DirScanner'
220  */
221 static void
222 finish_scan (void *cls)
223 {
224   struct GNUNET_FS_DirScanner *ds = cls;
225
226   ds->stop_task = NULL;
227   if (NULL != ds->helper)
228   {
229     GNUNET_HELPER_stop (ds->helper, GNUNET_NO);
230     ds->helper = NULL;
231   }
232   ds->progress_callback (ds->progress_callback_cls,
233                          NULL, GNUNET_SYSERR,
234                          GNUNET_FS_DIRSCANNER_FINISHED);
235 }
236
237
238 /**
239  * Called every time there is data to read from the scanner.
240  * Calls the scanner progress handler.
241  *
242  * @param cls the closure (directory scanner object)
243  * @param msg message from the helper process
244  * @return #GNUNET_OK on success,
245  *    #GNUNET_NO to stop further processing (no error)
246  *    #GNUNET_SYSERR to stop further processing with error
247  */
248 static int
249 process_helper_msgs (void *cls,
250                      const struct GNUNET_MessageHeader *msg)
251 {
252   struct GNUNET_FS_DirScanner *ds = cls;
253   const char *filename;
254   size_t left;
255
256 #if 0
257   fprintf (stderr,
258            "DMS parses %u-byte message of type %u\n",
259            (unsigned int) ntohs (msg->size),
260            (unsigned int) ntohs (msg->type));
261 #endif
262   left = ntohs (msg->size) - sizeof (struct GNUNET_MessageHeader);
263   filename = (const char*) &msg[1];
264   switch (ntohs (msg->type))
265   {
266   case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_PROGRESS_FILE:
267     if (filename[left-1] != '\0')
268     {
269       GNUNET_break (0);
270       break;
271     }
272     ds->progress_callback (ds->progress_callback_cls,
273                            filename, GNUNET_NO,
274                            GNUNET_FS_DIRSCANNER_FILE_START);
275     if (NULL == ds->toplevel)
276     {
277       ds->toplevel = expand_tree (ds->pos,
278                                   filename,
279                                   GNUNET_NO);
280     }
281     else
282     {
283       GNUNET_assert (NULL != ds->pos);
284       (void) expand_tree (ds->pos,
285                           filename,
286                           GNUNET_NO);
287     }
288     return GNUNET_OK;
289   case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_PROGRESS_DIRECTORY:
290     if (filename[left-1] != '\0')
291     {
292       GNUNET_break (0);
293       break;
294     }
295     if (0 == strcmp ("..", filename))
296     {
297       if (NULL == ds->pos)
298       {
299         GNUNET_break (0);
300         break;
301       }
302       ds->pos = ds->pos->parent;
303       return GNUNET_OK;
304     }
305     ds->progress_callback (ds->progress_callback_cls,
306                            filename, GNUNET_YES,
307                            GNUNET_FS_DIRSCANNER_FILE_START);
308     ds->pos = expand_tree (ds->pos,
309                            filename,
310                            GNUNET_YES);
311     if (NULL == ds->toplevel)
312       ds->toplevel = ds->pos;
313     return GNUNET_OK;
314   case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_ERROR:
315     break;
316   case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_SKIP_FILE:
317     if ('\0' != filename[left-1])
318       break;
319     ds->progress_callback (ds->progress_callback_cls,
320                            filename, GNUNET_SYSERR,
321                            GNUNET_FS_DIRSCANNER_FILE_IGNORED);
322     return GNUNET_OK;
323   case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_COUNTING_DONE:
324     if (0 != left)
325     {
326       GNUNET_break (0);
327       break;
328     }
329     if (NULL == ds->toplevel)
330     {
331       GNUNET_break (0);
332       break;
333     }
334     ds->progress_callback (ds->progress_callback_cls,
335                            NULL, GNUNET_SYSERR,
336                            GNUNET_FS_DIRSCANNER_ALL_COUNTED);
337     ds->pos = ds->toplevel;
338     if (GNUNET_YES == ds->pos->is_directory)
339       ds->pos = advance (ds->pos);
340     return GNUNET_OK;
341   case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_META_DATA:
342     {
343       size_t nlen;
344       const char *end;
345
346       if (NULL == ds->pos)
347       {
348         GNUNET_break (0);
349         break;
350       }
351       end = memchr (filename, 0, left);
352       if (NULL == end)
353       {
354         GNUNET_break (0);
355         break;
356       }
357       end++;
358       nlen = end - filename;
359       left -= nlen;
360       if (0 != strcmp (filename,
361                        ds->pos->filename))
362       {
363         GNUNET_break (0);
364         break;
365       }
366       ds->progress_callback (ds->progress_callback_cls,
367                              filename,
368                              GNUNET_YES,
369                              GNUNET_FS_DIRSCANNER_EXTRACT_FINISHED);
370       if (0 < left)
371       {
372         ds->pos->meta = GNUNET_CONTAINER_meta_data_deserialize (end,
373                                                                 left);
374         if (NULL == ds->pos->meta)
375         {
376           GNUNET_break (0);
377           break;
378         }
379         /* having full filenames is too dangerous; always make sure we clean them up */
380         GNUNET_CONTAINER_meta_data_delete (ds->pos->meta,
381                                            EXTRACTOR_METATYPE_FILENAME,
382                                            NULL, 0);
383         /* instead, put in our 'safer' original filename */
384         GNUNET_CONTAINER_meta_data_insert (ds->pos->meta, "<libgnunetfs>",
385                                            EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME,
386                                            EXTRACTOR_METAFORMAT_UTF8, "text/plain",
387                                            ds->pos->short_filename,
388                                            strlen (ds->pos->short_filename) + 1);
389       }
390       ds->pos->ksk_uri = GNUNET_FS_uri_ksk_create_from_meta_data (ds->pos->meta);
391       ds->pos = advance (ds->pos);
392       return GNUNET_OK;
393     }
394   case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_FINISHED:
395     if (NULL != ds->pos)
396     {
397       GNUNET_break (0);
398       break;
399     }
400     if (0 != left)
401     {
402       GNUNET_break (0);
403       break;
404     }
405     if (NULL == ds->toplevel)
406     {
407       GNUNET_break (0);
408       break;
409     }
410     ds->stop_task = GNUNET_SCHEDULER_add_now (&finish_scan,
411                                               ds);
412     return GNUNET_OK;
413   default:
414     GNUNET_break (0);
415     break;
416   }
417   ds->progress_callback (ds->progress_callback_cls,
418                          NULL, GNUNET_SYSERR,
419                          GNUNET_FS_DIRSCANNER_INTERNAL_ERROR);
420   return GNUNET_OK;
421 }
422
423
424 /**
425  * Function called if our helper process died.
426  *
427  * @param cls the 'struct GNUNET_FS_DirScanner' callback.
428  */
429 static void
430 helper_died_cb (void *cls)
431 {
432   struct GNUNET_FS_DirScanner *ds = cls;
433
434   ds->helper = NULL;
435   if (NULL != ds->stop_task)
436     return; /* normal death, was finished */
437   ds->progress_callback (ds->progress_callback_cls,
438                          NULL, GNUNET_SYSERR,
439                          GNUNET_FS_DIRSCANNER_INTERNAL_ERROR);
440 }
441
442
443 /**
444  * Start a directory scanner thread.
445  *
446  * @param filename name of the directory to scan
447  * @param disable_extractor #GNUNET_YES to not run libextractor on files (only
448  *        build a tree)
449  * @param ex if not NULL, must be a list of extra plugins for extractor
450  * @param cb the callback to call when there are scanning progress messages
451  * @param cb_cls closure for 'cb'
452  * @return directory scanner object to be used for controlling the scanner
453  */
454 struct GNUNET_FS_DirScanner *
455 GNUNET_FS_directory_scan_start (const char *filename,
456                                 int disable_extractor, const char *ex,
457                                 GNUNET_FS_DirScannerProgressCallback cb,
458                                 void *cb_cls)
459 {
460   struct stat sbuf;
461   char *filename_expanded;
462   struct GNUNET_FS_DirScanner *ds;
463
464   if (0 != STAT (filename, &sbuf))
465     return NULL;
466   filename_expanded = GNUNET_STRINGS_filename_expand (filename);
467   if (NULL == filename_expanded)
468     return NULL;
469   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
470               "Starting to scan directory `%s'\n",
471               filename_expanded);
472   ds = GNUNET_new (struct GNUNET_FS_DirScanner);
473   ds->progress_callback = cb;
474   ds->progress_callback_cls = cb_cls;
475   ds->filename_expanded = filename_expanded;
476   if (disable_extractor)
477     ds->ex_arg = GNUNET_strdup ("-");
478   else
479     ds->ex_arg = (NULL != ex) ? GNUNET_strdup (ex) : NULL;
480   ds->args[0] = "gnunet-helper-fs-publish";
481   ds->args[1] = ds->filename_expanded;
482   ds->args[2] = ds->ex_arg;
483   ds->args[3] = NULL;
484   ds->helper = GNUNET_HELPER_start (GNUNET_NO,
485                                     "gnunet-helper-fs-publish",
486                                     ds->args,
487                                     &process_helper_msgs,
488                                     &helper_died_cb, ds);
489   if (NULL == ds->helper)
490     {
491     GNUNET_free (filename_expanded);
492     GNUNET_free (ds);
493     return NULL;
494   }
495   return ds;
496 }
497
498
499 /* end of fs_dirmetascan.c */