error handling
[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      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
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    * Helper process.
42    */
43   struct GNUNET_HELPER_Handle *helper;
44
45   /**
46    * Expanded filename (as given by the scan initiator).
47    * The scanner thread stores a copy here, and frees it when it finishes.
48    */
49   char *filename_expanded;
50
51   /**
52    * Second argument to helper process.
53    */
54   char *ex_arg;
55
56   /**
57    * The function that will be called every time there's a progress
58    * message.
59    */
60   GNUNET_FS_DirScannerProgressCallback progress_callback;
61
62   /**
63    * A closure for progress_callback.
64    */
65   void *progress_callback_cls;
66
67   /**
68    * After the scan is finished, it will contain a pointer to the
69    * top-level directory entry in the directory tree built by the
70    * scanner.
71    */
72   struct GNUNET_FS_ShareTreeItem *toplevel;
73
74   /**
75    * Current position during processing.
76    */
77   struct GNUNET_FS_ShareTreeItem *pos;
78
79   /**
80    * Task scheduled when we are done.
81    */
82   struct GNUNET_SCHEDULER_Task *stop_task;
83
84   /**
85    * Arguments for helper.
86    */
87   char *args[4];
88 };
89
90
91 /**
92  * Abort the scan.  Must not be called from within the progress_callback
93  * function.
94  *
95  * @param ds directory scanner structure
96  */
97 void
98 GNUNET_FS_directory_scan_abort (struct GNUNET_FS_DirScanner *ds)
99 {
100   /* terminate helper */
101   if (NULL != ds->helper)
102     GNUNET_HELPER_stop (ds->helper, GNUNET_NO);
103
104   /* free resources */
105   if (NULL != ds->toplevel)
106     GNUNET_FS_share_tree_free (ds->toplevel);
107   if (NULL != ds->stop_task)
108     GNUNET_SCHEDULER_cancel (ds->stop_task);
109   GNUNET_free_non_null (ds->ex_arg);
110   GNUNET_free (ds->filename_expanded);
111   GNUNET_free (ds);
112 }
113
114
115 /**
116  * Obtain the result of the scan after the scan has signalled
117  * completion.  Must not be called prior to completion.  The 'ds' is
118  * freed as part of this call.
119  *
120  * @param ds directory scanner structure
121  * @return the results of the scan (a directory tree)
122  */
123 struct GNUNET_FS_ShareTreeItem *
124 GNUNET_FS_directory_scan_get_result (struct GNUNET_FS_DirScanner *ds)
125 {
126   struct GNUNET_FS_ShareTreeItem *result;
127
128   /* check that we're actually done */
129   GNUNET_assert (NULL == ds->helper);
130   /* preserve result */
131   result = ds->toplevel;
132   ds->toplevel = NULL;
133   GNUNET_FS_directory_scan_abort (ds);
134   return result;
135 }
136
137
138 /**
139  * Move in the directory from the given position to the next file
140  * in DFS traversal.
141  *
142  * @param pos current position
143  * @return next file, NULL for none
144  */
145 static struct GNUNET_FS_ShareTreeItem *
146 advance (struct GNUNET_FS_ShareTreeItem *pos)
147 {
148   int moved;
149
150   GNUNET_assert (NULL != pos);
151   moved = 0; /* must not terminate, even on file, otherwise "normal" */
152   while ((pos->is_directory == GNUNET_YES) || (0 == moved))
153   {
154     if ((moved != -1) && (NULL != pos->children_head))
155     {
156       pos = pos->children_head;
157       moved = 1;     /* can terminate if file */
158       continue;
159     }
160     if (NULL != pos->next)
161     {
162       pos = pos->next;
163       moved = 1;     /* can terminate if file */
164       continue;
165     }
166     if (NULL != pos->parent)
167     {
168       pos = pos->parent;
169       moved = -1;     /* force move to 'next' or 'parent' */
170       continue;
171     }
172     /* no more options, end of traversal */
173     return NULL;
174   }
175   return pos;
176 }
177
178
179 /**
180  * Add another child node to the tree.
181  *
182  * @param parent parent of the child, NULL for top level
183  * @param filename name of the file or directory
184  * @param is_directory GNUNET_YES for directories
185  * @return new entry that was just created
186  */
187 static struct GNUNET_FS_ShareTreeItem *
188 expand_tree (struct GNUNET_FS_ShareTreeItem *parent,
189              const char *filename,
190              int is_directory)
191 {
192   struct GNUNET_FS_ShareTreeItem *chld;
193   size_t slen;
194
195   chld = GNUNET_new (struct GNUNET_FS_ShareTreeItem);
196   chld->parent = parent;
197   chld->filename = GNUNET_strdup (filename);
198   GNUNET_asprintf (&chld->short_filename,
199                    "%s%s",
200                    GNUNET_STRINGS_get_short_name (filename),
201                    is_directory == GNUNET_YES ? "/" : "");
202   /* make sure we do not end with '//' */
203   slen = strlen (chld->short_filename);
204   if ((slen >= 2) && (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,
234                          GNUNET_SYSERR,
235                          GNUNET_FS_DIRSCANNER_FINISHED);
236 }
237
238
239 /**
240  * Called every time there is data to read from the scanner.
241  * Calls the scanner progress handler.
242  *
243  * @param cls the closure (directory scanner object)
244  * @param msg message from the helper process
245  * @return #GNUNET_OK on success,
246  *    #GNUNET_NO to stop further processing (no error)
247  *    #GNUNET_SYSERR to stop further processing with error
248  */
249 static int
250 process_helper_msgs (void *cls, 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,
274                            GNUNET_NO,
275                            GNUNET_FS_DIRSCANNER_FILE_START);
276     if (NULL == ds->toplevel)
277     {
278       ds->toplevel = expand_tree (ds->pos, filename, GNUNET_NO);
279     }
280     else
281     {
282       GNUNET_assert (NULL != ds->pos);
283       (void) expand_tree (ds->pos, filename, GNUNET_NO);
284     }
285     return GNUNET_OK;
286
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,
305                            GNUNET_YES,
306                            GNUNET_FS_DIRSCANNER_FILE_START);
307     ds->pos = expand_tree (ds->pos, filename, GNUNET_YES);
308     if (NULL == ds->toplevel)
309       ds->toplevel = ds->pos;
310     return GNUNET_OK;
311
312   case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_ERROR:
313     break;
314
315   case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_SKIP_FILE:
316     if ('\0' != filename[left - 1])
317       break;
318     ds->progress_callback (ds->progress_callback_cls,
319                            filename,
320                            GNUNET_SYSERR,
321                            GNUNET_FS_DIRSCANNER_FILE_IGNORED);
322     return GNUNET_OK;
323
324   case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_COUNTING_DONE:
325     if (0 != left)
326     {
327       GNUNET_break (0);
328       break;
329     }
330     if (NULL == ds->toplevel)
331       break;
332     ds->progress_callback (ds->progress_callback_cls,
333                            NULL,
334                            GNUNET_SYSERR,
335                            GNUNET_FS_DIRSCANNER_ALL_COUNTED);
336     ds->pos = ds->toplevel;
337     if (GNUNET_YES == ds->pos->is_directory)
338       ds->pos = advance (ds->pos);
339     return GNUNET_OK;
340
341   case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_META_DATA: {
342       size_t nlen;
343       const char *end;
344
345       if (NULL == ds->pos)
346       {
347         GNUNET_break (0);
348         break;
349       }
350       end = memchr (filename, 0, left);
351       if (NULL == end)
352       {
353         GNUNET_break (0);
354         break;
355       }
356       end++;
357       nlen = end - filename;
358       left -= nlen;
359       if (0 != strcmp (filename, ds->pos->filename))
360       {
361         GNUNET_break (0);
362         break;
363       }
364       ds->progress_callback (ds->progress_callback_cls,
365                              filename,
366                              GNUNET_YES,
367                              GNUNET_FS_DIRSCANNER_EXTRACT_FINISHED);
368       if (0 < left)
369       {
370         ds->pos->meta = GNUNET_CONTAINER_meta_data_deserialize (end, left);
371         if (NULL == ds->pos->meta)
372         {
373           GNUNET_break (0);
374           break;
375         }
376         /* having full filenames is too dangerous; always make sure we clean them up */
377         GNUNET_CONTAINER_meta_data_delete (ds->pos->meta,
378                                            EXTRACTOR_METATYPE_FILENAME,
379                                            NULL,
380                                            0);
381         /* instead, put in our 'safer' original filename */
382         GNUNET_CONTAINER_meta_data_insert (ds->pos->meta,
383                                            "<libgnunetfs>",
384                                            EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME,
385                                            EXTRACTOR_METAFORMAT_UTF8,
386                                            "text/plain",
387                                            ds->pos->short_filename,
388                                            strlen (ds->pos->short_filename)
389                                            + 1);
390       }
391       ds->pos->ksk_uri = GNUNET_FS_uri_ksk_create_from_meta_data (
392         ds->pos->meta);
393       ds->pos = advance (ds->pos);
394       return GNUNET_OK;
395     }
396
397   case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_FINISHED:
398     if (NULL != ds->pos)
399     {
400       GNUNET_break (0);
401       break;
402     }
403     if (0 != left)
404     {
405       GNUNET_break (0);
406       break;
407     }
408     if (NULL == ds->toplevel)
409       break;
410     ds->stop_task = GNUNET_SCHEDULER_add_now (&finish_scan, ds);
411     return GNUNET_OK;
412
413   default:
414     GNUNET_break (0);
415     break;
416   }
417   ds->progress_callback (ds->progress_callback_cls,
418                          NULL,
419                          GNUNET_SYSERR,
420                          GNUNET_FS_DIRSCANNER_INTERNAL_ERROR);
421   return GNUNET_OK;
422 }
423
424
425 /**
426  * Function called if our helper process died.
427  *
428  * @param cls the 'struct GNUNET_FS_DirScanner' callback.
429  */
430 static void
431 helper_died_cb (void *cls)
432 {
433   struct GNUNET_FS_DirScanner *ds = cls;
434
435   ds->helper = NULL;
436   if (NULL != ds->stop_task)
437     return; /* normal death, was finished */
438   ds->progress_callback (ds->progress_callback_cls,
439                          NULL,
440                          GNUNET_SYSERR,
441                          GNUNET_FS_DIRSCANNER_INTERNAL_ERROR);
442 }
443
444
445 /**
446  * Start a directory scanner thread.
447  *
448  * @param filename name of the directory to scan
449  * @param disable_extractor #GNUNET_YES to not run libextractor on files (only
450  *        build a tree)
451  * @param ex if not NULL, must be a list of extra plugins for extractor
452  * @param cb the callback to call when there are scanning progress messages
453  * @param cb_cls closure for 'cb'
454  * @return directory scanner object to be used for controlling the scanner
455  */
456 struct GNUNET_FS_DirScanner *
457 GNUNET_FS_directory_scan_start (const char *filename,
458                                 int disable_extractor,
459                                 const char *ex,
460                                 GNUNET_FS_DirScannerProgressCallback cb,
461                                 void *cb_cls)
462 {
463   struct stat sbuf;
464   char *filename_expanded;
465   struct GNUNET_FS_DirScanner *ds;
466
467   if (0 != stat (filename, &sbuf))
468     return NULL;
469   filename_expanded = GNUNET_STRINGS_filename_expand (filename);
470   if (NULL == filename_expanded)
471     return NULL;
472   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
473               "Starting to scan directory `%s'\n",
474               filename_expanded);
475   ds = GNUNET_new (struct GNUNET_FS_DirScanner);
476   ds->progress_callback = cb;
477   ds->progress_callback_cls = cb_cls;
478   ds->filename_expanded = filename_expanded;
479   if (disable_extractor)
480     ds->ex_arg = GNUNET_strdup ("-");
481   else
482     ds->ex_arg = (NULL != ex) ? GNUNET_strdup (ex) : NULL;
483   ds->args[0] = "gnunet-helper-fs-publish";
484   ds->args[1] = ds->filename_expanded;
485   ds->args[2] = ds->ex_arg;
486   ds->args[3] = NULL;
487   ds->helper = GNUNET_HELPER_start (GNUNET_NO,
488                                     "gnunet-helper-fs-publish",
489                                     ds->args,
490                                     &process_helper_msgs,
491                                     &helper_died_cb,
492                                     ds);
493   if (NULL == ds->helper)
494   {
495     GNUNET_free (filename_expanded);
496     GNUNET_free (ds);
497     return NULL;
498   }
499   return ds;
500 }
501
502
503 /* end of fs_dirmetascan.c */