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