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