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