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