4c995a72a61210ae69b95b36d909231e86c6c8b2
[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
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    * A thread object for the scanner thread.
43    */
44 #if WINDOWS
45   HANDLE thread;
46 #else
47   pthread_t thread;
48 #endif
49
50   /**
51    * Expanded filename (as given by the scan initiator).
52    * The scanner thread stores a copy here, and frees it when it finishes.
53    */
54   char *filename_expanded;
55
56   /**
57    * List of libextractor plugins to use for extracting.
58    * Initialized when the scan starts, removed when it finishes.
59    */
60   struct EXTRACTOR_PluginList *plugins;
61   
62   /**
63    * A pipe transfer signals to the scanner.
64    */
65   struct GNUNET_DISK_PipeHandle *stop_pipe;
66
67   /**
68    * A pipe end to read signals from.
69    */
70   const struct GNUNET_DISK_FileHandle *stop_read;
71
72   /**
73    * A pipe end to read signals from.
74    */
75   const struct GNUNET_DISK_FileHandle *stop_write;
76   
77   /**
78    * The pipe that is used to read progress messages.  Only closed
79    * after the scanner thread is finished.
80    */
81   struct GNUNET_DISK_PipeHandle *progress_pipe;
82
83   /**
84    * The end of the pipe that is used to read progress messages.
85    */
86   const struct GNUNET_DISK_FileHandle *progress_read;
87
88   /**
89    * Handle of the pipe end into which the progress messages are written
90    * The initiator MUST keep it alive until the scanner thread is finished.
91    */
92   const struct GNUNET_DISK_FileHandle *progress_write;
93   
94   /**
95    * The function that will be called every time there's a progress
96    * message.
97    */
98   GNUNET_FS_DirScannerProgressCallback progress_callback;
99   
100   /**
101    * A closure for progress_callback.
102    */
103   void *progress_callback_cls;
104   
105   /**
106    * A task for reading progress messages from the scanner.
107    */
108   GNUNET_SCHEDULER_TaskIdentifier progress_read_task;
109
110   /**
111    * After the scan is finished, it will contain a pointer to the
112    * top-level directory entry in the directory tree built by the
113    * scanner.  Must only be manipulated by the thread for the
114    * duration of the thread's runtime.
115    */
116   struct GNUNET_FS_ShareTreeItem *toplevel;
117
118   /**
119    * 1 if the scanner should stop, 0 otherwise. Set in response
120    * to communication errors or when the initiator wants the scanning
121    * process to stop.
122    */
123   int do_stop;
124
125 };
126
127
128
129 /**
130  * Abort the scan.
131  *
132  * @param ds directory scanner structure
133  */
134 void
135 GNUNET_FS_directory_scan_abort (struct GNUNET_FS_DirScanner *ds)
136 {
137   static char c = 1;
138
139   /* signal shutdown to other thread */
140   (void) GNUNET_DISK_file_write (ds->stop_write, &c, 1);
141   GNUNET_DISK_pipe_close_end (ds->stop_pipe, GNUNET_DISK_PIPE_END_WRITE);
142
143   /* stop reading from progress */
144   if (ds->progress_read_task != GNUNET_SCHEDULER_NO_TASK)
145   {
146     GNUNET_SCHEDULER_cancel (ds->progress_read_task);
147     ds->progress_read_task = GNUNET_SCHEDULER_NO_TASK;
148   }
149   GNUNET_DISK_pipe_close_end (ds->progress_pipe, GNUNET_DISK_PIPE_END_READ);
150
151   /* wait for other thread to terminate */
152 #if WINDOWS
153   WaitForSingleObject (ds->thread, INFINITE);
154   CloseHandle (ds->thread);
155 #else
156   pthread_join (ds->thread, NULL);
157   pthread_detach (ds->thread);
158 #endif
159
160   /* free resources */
161   GNUNET_DISK_pipe_close (ds->stop_pipe);
162   GNUNET_DISK_pipe_close (ds->progress_pipe);
163   if (NULL != ds->toplevel)
164     GNUNET_FS_share_tree_free (ds->toplevel);
165   if (NULL != ds->plugins)
166     EXTRACTOR_plugin_remove_all (ds->plugins);
167   GNUNET_free (ds);
168 }
169
170
171 /**
172  * Obtain the result of the scan after the scan has signalled
173  * completion.  Must not be called prior to completion.  The 'ds' is
174  * freed as part of this call.
175  *
176  * @param ds directory scanner structure
177  * @return the results of the scan (a directory tree)
178  */
179 struct GNUNET_FS_ShareTreeItem *
180 GNUNET_FS_directory_scan_get_result (struct GNUNET_FS_DirScanner *ds)
181 {
182   struct GNUNET_FS_ShareTreeItem *result;
183
184   /* check that we're actually done */
185   GNUNET_assert (GNUNET_SCHEDULER_NO_TASK == ds->progress_read_task);
186   /* preserve result */
187   result = ds->toplevel;
188   ds->toplevel = NULL; 
189   GNUNET_FS_directory_scan_abort (ds);
190   return result;
191 }
192
193
194 /**
195  * Write 'size' bytes from 'buf' into 'out'.
196  *
197  * @param in pipe to write to
198  * @param buf buffer with data to write
199  * @param size number of bytes to write
200  * @return GNUNET_OK on success, GNUNET_SYSERR on error
201  */
202 static int
203 write_all (const struct GNUNET_DISK_FileHandle *out,
204            const void *buf,
205            size_t size)
206 {
207   const char *cbuf = buf;
208   size_t total;
209   ssize_t wr;
210
211   total = 0;
212   do
213   {
214     wr = GNUNET_DISK_file_write (out,
215                                  &cbuf[total],
216                                  size - total);
217     if (wr > 0)
218       total += wr;
219   } while ( (wr > 0) && (total < size) );
220   return (total == size) ? GNUNET_OK : GNUNET_SYSERR;
221 }
222
223
224 /**
225  * Write progress message.
226  *
227  * @param ds
228  * @param filename name of the file to transmit, never NULL
229  * @param is_directory GNUNET_YES for directory, GNUNET_NO for file, GNUNET_SYSERR for neither
230  * @param reason reason for the progress call
231  * @return GNUNET_SYSERR to stop scanning (the pipe was broken somehow)
232  */
233 static int
234 write_progress (struct GNUNET_FS_DirScanner *ds,
235                 const char *filename,
236                 int is_directory, 
237                 enum GNUNET_FS_DirScannerProgressUpdateReason reason)
238 {
239   size_t slen;
240
241   slen = strlen (filename) + 1;
242   if ( (GNUNET_OK !=
243         write_all (ds->progress_write,
244                    &reason,
245                    sizeof (reason))) ||
246        (GNUNET_OK !=
247         write_all (ds->progress_write,
248                    &slen,
249                    sizeof (slen))) ||
250        (GNUNET_OK !=
251         write_all (ds->progress_write,
252                    filename,
253                    slen)) ||
254        (GNUNET_OK !=
255         write_all (ds->progress_write,
256                    &is_directory,
257                    sizeof (is_directory))) )
258     return GNUNET_SYSERR;
259   return GNUNET_OK;
260 }
261
262
263 /**
264  * Called every now and then by the scanner thread to check
265  * if we're being aborted.
266  * 
267  * @param ds scanner context
268  * @return GNUNET_OK to continue, GNUNET_SYSERR to stop
269  */
270 static int
271 test_thread_stop (struct GNUNET_FS_DirScanner *ds)
272 {
273   char c;
274
275   if ( (GNUNET_DISK_file_read_non_blocking (ds->stop_read, &c, 1) == 1) ||
276        (EAGAIN != errno) )
277     return GNUNET_SYSERR;
278   return GNUNET_OK;
279 }
280
281
282 /**
283  * Function called to (recursively) add all of the files in the
284  * directory to the tree.  Called by the directory scanner to initiate
285  * the scan.  Does NOT yet add any metadata.
286  *
287  * @param ds directory scanner context to use
288  * @param filename file or directory to scan
289  * @param dst where to store the resulting share tree item
290  * @return GNUNET_OK on success, GNUNET_SYSERR on error
291  */
292 static int
293 preprocess_file (struct GNUNET_FS_DirScanner *ds,
294                  const char *filename,
295                  struct GNUNET_FS_ShareTreeItem **dst);
296
297
298 /**
299  * Closure for the 'scan_callback'
300  */
301 struct RecursionContext
302 {
303   /**
304    * Global scanner context.
305    */
306   struct GNUNET_FS_DirScanner *ds;
307
308   /**
309    * Parent to add the files to.
310    */
311   struct GNUNET_FS_ShareTreeItem *parent;
312
313   /**
314    * Flag to set to GNUNET_YES on serious errors.
315    */
316   int stop;
317 };
318
319
320 /**
321  * Function called by the directory iterator to (recursively) add all
322  * of the files in the directory to the tree.  Called by the directory
323  * scanner to initiate the scan.  Does NOT yet add any metadata.
324  *
325  * @param cls the 'struct RecursionContext'
326  * @param filename file or directory to scan
327  * @return GNUNET_OK on success, GNUNET_SYSERR on error
328  */
329 static int
330 scan_callback (void *cls,
331                const char *filename)
332 {
333   struct RecursionContext *rc = cls;
334   struct GNUNET_FS_ShareTreeItem *chld;
335
336   if (GNUNET_OK !=
337       preprocess_file (rc->ds,
338                        filename,
339                        &chld))
340   {
341     rc->stop = GNUNET_YES;
342     return GNUNET_SYSERR;
343   }
344   chld->parent = rc->parent;
345   GNUNET_CONTAINER_DLL_insert (rc->parent->children_head,
346                                rc->parent->children_tail,
347                                chld);
348   return GNUNET_OK;
349 }
350
351
352 /**
353  * Function called to (recursively) add all of the files in the
354  * directory to the tree.  Called by the directory scanner to initiate
355  * the scan.  Does NOT yet add any metadata.
356  *
357  * @param ds directory scanner context to use
358  * @param filename file or directory to scan
359  * @param dst where to store the resulting share tree item
360  * @return GNUNET_OK on success, GNUNET_SYSERR on error
361  */
362 static int
363 preprocess_file (struct GNUNET_FS_DirScanner *ds,
364                  const char *filename,
365                  struct GNUNET_FS_ShareTreeItem **dst)
366 {
367   struct GNUNET_FS_ShareTreeItem *item;
368   struct stat sbuf;
369
370   if (0 != STAT (filename, &sbuf))
371   {
372     /* If the file doesn't exist (or is not stat-able for any other reason)
373        skip it (but report it), but do continue. */
374     if (GNUNET_OK !=
375         write_progress (ds, filename, GNUNET_SYSERR,
376                         GNUNET_FS_DIRSCANNER_DOES_NOT_EXIST))
377       return GNUNET_SYSERR;
378     return GNUNET_OK;
379   }
380
381   /* Report the progress */
382   if (GNUNET_OK !=
383       write_progress (ds, 
384                       filename, 
385                       S_ISDIR (sbuf.st_mode) ? GNUNET_YES : GNUNET_NO,
386                       GNUNET_FS_DIRSCANNER_FILE_START))
387     return GNUNET_SYSERR;
388   item = GNUNET_malloc (sizeof (struct GNUNET_FS_ShareTreeItem));
389   item->meta = GNUNET_CONTAINER_meta_data_create ();
390   item->filename = GNUNET_strdup (filename);
391   item->short_filename = GNUNET_strdup (GNUNET_STRINGS_get_short_name (filename));
392   item->is_directory = (S_ISDIR (sbuf.st_mode)) ? GNUNET_YES : GNUNET_NO;
393   item->file_size = (uint64_t) sbuf.st_size;
394   if (item->is_directory)
395   {
396     struct RecursionContext rc;
397
398     rc.parent = item;
399     rc.ds = ds;
400     rc.stop = GNUNET_NO;
401     GNUNET_DISK_directory_scan (filename, 
402                                 &scan_callback, 
403                                 &rc);    
404     if ( (rc.stop == GNUNET_YES) ||
405          (GNUNET_OK != 
406           test_thread_stop (ds)) )
407     {
408       GNUNET_FS_share_tree_free (item);
409       return GNUNET_SYSERR;
410     }
411   }
412   /* Report the progress */
413   if (GNUNET_OK !=
414       write_progress (ds, 
415                       filename, 
416                       S_ISDIR (sbuf.st_mode) ? GNUNET_YES : GNUNET_NO,
417                       GNUNET_FS_DIRSCANNER_SUBTREE_COUNTED))
418   {
419     GNUNET_FS_share_tree_free (item);
420     return GNUNET_SYSERR;
421   }
422   *dst = item;
423   return GNUNET_OK;
424 }
425
426
427 /**
428  * Extract metadata from files.
429  *
430  * @param ds directory scanner context
431  * @param item entry we are processing
432  * @return GNUNET_OK on success, GNUNET_SYSERR on fatal errors
433  */
434 static int
435 extract_files (struct GNUNET_FS_DirScanner *ds,
436                struct GNUNET_FS_ShareTreeItem *item)
437 {  
438   if (item->is_directory)
439   {
440     /* for directories, we simply only descent, no extraction, no
441        progress reporting */
442     struct GNUNET_FS_ShareTreeItem *pos;
443
444     for (pos = item->children_head; NULL != pos; pos = pos->next)
445       if (GNUNET_OK !=
446           extract_files (ds, pos))
447         return GNUNET_SYSERR;
448     return GNUNET_OK;
449   }
450   
451   /* this is the expensive operation, *afterwards* we'll check for aborts */
452   GNUNET_FS_meta_data_extract_from_file (item->meta, 
453                                          item->filename,
454                                          ds->plugins);
455
456   /* having full filenames is too dangerous; always make sure we clean them up */
457   GNUNET_CONTAINER_meta_data_delete (item->meta, 
458                                      EXTRACTOR_METATYPE_FILENAME,
459                                      NULL, 0);
460   GNUNET_CONTAINER_meta_data_insert (item->meta, "<libgnunetfs>",
461                                      EXTRACTOR_METATYPE_FILENAME,
462                                      EXTRACTOR_METAFORMAT_UTF8, "text/plain",
463                                      item->short_filename, 
464                                      strlen (item->short_filename) + 1);
465   /* check for abort */
466   if (GNUNET_OK != 
467       test_thread_stop (ds))
468     return GNUNET_SYSERR;
469
470   /* Report the progress */
471   if (GNUNET_OK !=
472       write_progress (ds, 
473                       item->filename, 
474                       GNUNET_NO,
475                       GNUNET_FS_DIRSCANNER_EXTRACT_FINISHED))
476     return GNUNET_SYSERR;
477   return GNUNET_OK;
478 }
479
480
481 /**
482  * The function from which the scanner thread starts
483  *
484  * @param cls the 'struct GNUNET_FS_DirScanner'
485  * @return 0/NULL
486  */
487 #if WINDOWS
488 DWORD
489 #else
490 static void *
491 #endif
492 run_directory_scan_thread (void *cls)
493 {
494   struct GNUNET_FS_DirScanner *ds = cls;
495
496   if (GNUNET_OK != preprocess_file (ds, 
497                                     ds->filename_expanded, 
498                                     &ds->toplevel))
499   {
500     (void) write_progress (ds, "", GNUNET_NO, GNUNET_FS_DIRSCANNER_INTERNAL_ERROR);
501     return 0;
502   }
503   if (GNUNET_OK !=
504       write_progress (ds, "", GNUNET_NO, GNUNET_FS_DIRSCANNER_ALL_COUNTED))
505     return 0;
506   if (GNUNET_OK !=
507       extract_files (ds, ds->toplevel))
508   {
509     (void) write_progress (ds, "", GNUNET_NO, GNUNET_FS_DIRSCANNER_INTERNAL_ERROR);
510     return 0;
511   }
512   (void) write_progress (ds, "", GNUNET_NO, GNUNET_FS_DIRSCANNER_FINISHED);
513   return 0;
514 }
515
516
517 /**
518  * Read 'size' bytes from 'in' into 'buf'.
519  *
520  * @param in pipe to read from
521  * @param buf buffer to read to
522  * @param size number of bytes to read
523  * @return GNUNET_OK on success, GNUNET_SYSERR on error
524  */
525 static int
526 read_all (const struct GNUNET_DISK_FileHandle *in,
527           char *buf,
528           size_t size)
529 {
530   size_t total;
531   ssize_t rd;
532
533   total = 0;
534   do
535   {
536     rd = GNUNET_DISK_file_read (in,
537                                 &buf[total],
538                                 size - total);
539     if (rd > 0)
540       total += rd;
541   } while ( (rd > 0) && (total < size) );
542   return (total == size) ? GNUNET_OK : GNUNET_SYSERR;
543 }
544
545
546 /**
547  * Called every time there is data to read from the scanner.
548  * Calls the scanner progress handler.
549  *
550  * @param cls the closure (directory scanner object)
551  * @param tc task context in which the task is running
552  */
553 static void
554 read_progress_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
555 {
556   struct GNUNET_FS_DirScanner *ds = cls;
557   enum GNUNET_FS_DirScannerProgressUpdateReason reason;
558   size_t filename_len;
559   int is_directory;
560   char *filename;
561
562   ds->progress_read_task = GNUNET_SCHEDULER_NO_TASK;
563   if (! (tc->reason & GNUNET_SCHEDULER_REASON_READ_READY))
564   {
565     ds->progress_read_task
566       = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
567                                         ds->progress_read, &read_progress_task,
568                                         ds);
569     return;
570   }
571
572   /* Read one message. If message is malformed or can't be read, end the scanner */
573   filename = NULL;
574   if ( (GNUNET_OK !=
575         read_all (ds->progress_read,
576                   (char*) &reason,
577                   sizeof (reason))) ||
578        (reason < GNUNET_FS_DIRSCANNER_FILE_START) ||
579        (reason > GNUNET_FS_DIRSCANNER_INTERNAL_ERROR) ||
580        (GNUNET_OK !=
581         read_all (ds->progress_read,
582                   (char*) &filename_len,
583                   sizeof (size_t))) ||
584        (filename_len == 0) ||
585        (filename_len > PATH_MAX) ||
586        (GNUNET_OK !=
587         read_all (ds->progress_read,
588                   filename = GNUNET_malloc (filename_len),
589                   filename_len)) ||
590        (filename[filename_len-1] != '\0') ||
591        (GNUNET_OK !=
592         read_all (ds->progress_read,
593                   (char*) &is_directory,
594                   sizeof (is_directory))) )
595   {
596     /* IPC error, complain, signal client and stop reading
597        from the pipe */
598     GNUNET_break (0);
599     ds->progress_callback (ds->progress_callback_cls, ds,
600                            NULL, GNUNET_SYSERR, 
601                            GNUNET_FS_DIRSCANNER_INTERNAL_ERROR);
602     GNUNET_free_non_null (filename);
603     return;
604   }
605   /* schedule task to keep reading (done here in case client calls
606      abort or something similar) */
607   ds->progress_read_task 
608     = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, 
609                                       ds->progress_read, 
610                                       &read_progress_task, ds);
611
612   /* read successfully, notify client about progress */
613   ds->progress_callback (ds->progress_callback_cls, 
614                          ds, 
615                          filename,
616                          is_directory, 
617                          reason);
618   GNUNET_free (filename);
619 }
620
621
622 /**
623  * Start a directory scanner thread.
624  *
625  * @param filename name of the directory to scan
626  * @param GNUNET_YES to not to run libextractor on files (only build a tree)
627  * @param ex if not NULL, must be a list of extra plugins for extractor
628  * @param cb the callback to call when there are scanning progress messages
629  * @param cb_cls closure for 'cb'
630  * @return directory scanner object to be used for controlling the scanner
631  */
632 struct GNUNET_FS_DirScanner *
633 GNUNET_FS_directory_scan_start (const char *filename,
634                                 int disable_extractor, const char *ex,
635                                 GNUNET_FS_DirScannerProgressCallback cb, 
636                                 void *cb_cls)
637 {
638   struct stat sbuf;
639   char *filename_expanded;
640   struct GNUNET_FS_DirScanner *ds;
641   struct GNUNET_DISK_PipeHandle *progress_pipe;
642   struct GNUNET_DISK_PipeHandle *stop_pipe;
643   int ok;
644
645   if (0 != STAT (filename, &sbuf))
646     return NULL;
647   filename_expanded = GNUNET_STRINGS_filename_expand (filename);
648   if (NULL == filename_expanded)
649     return NULL;
650   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
651               "Starting to scan directory `%s'\n",
652               filename_expanded);
653   progress_pipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO, GNUNET_NO, GNUNET_NO);
654   if (NULL == progress_pipe)
655   {
656     GNUNET_free (filename_expanded);
657     return NULL;
658   }
659   stop_pipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO, GNUNET_NO, GNUNET_NO);
660   if (NULL == stop_pipe)
661   {
662     GNUNET_DISK_pipe_close (progress_pipe);
663     GNUNET_free (filename_expanded);
664     return NULL;
665   }
666   
667   ds = GNUNET_malloc (sizeof (struct GNUNET_FS_DirScanner));
668   ds->progress_callback = cb;
669   ds->progress_callback_cls = cb_cls;
670   ds->stop_pipe = stop_pipe;
671   ds->stop_write = GNUNET_DISK_pipe_handle (ds->stop_pipe,
672                                             GNUNET_DISK_PIPE_END_WRITE);
673   ds->stop_read = GNUNET_DISK_pipe_handle (ds->stop_pipe,
674                                            GNUNET_DISK_PIPE_END_READ);
675   ds->progress_pipe = progress_pipe;
676   ds->progress_write = GNUNET_DISK_pipe_handle (progress_pipe,
677                                                 GNUNET_DISK_PIPE_END_WRITE);
678   ds->progress_read = GNUNET_DISK_pipe_handle (progress_pipe,
679                                                GNUNET_DISK_PIPE_END_READ);
680   ds->filename_expanded = filename_expanded;
681   if (! disable_extractor)
682   {
683     ds->plugins = EXTRACTOR_plugin_add_defaults (EXTRACTOR_OPTION_DEFAULT_POLICY);
684     if ( (NULL != ex) && strlen (ex) > 0)
685       ds->plugins = EXTRACTOR_plugin_add_config (ds->plugins, ex,
686                                                  EXTRACTOR_OPTION_DEFAULT_POLICY);
687   }
688 #if WINDOWS
689   ds->thread = CreateThread (NULL, 0,
690                              (LPTHREAD_START_ROUTINE) &run_directory_scan_thread, 
691                              (LPVOID) ds, 0, NULL);
692   ok = (ds->thread != NULL);
693 #else
694   ok = (0 == pthread_create (&ds->thread, NULL, 
695                              &run_directory_scan_thread, ds));
696 #endif
697   if (!ok)
698   {
699     EXTRACTOR_plugin_remove_all (ds->plugins);
700     GNUNET_free (filename_expanded);
701     GNUNET_DISK_pipe_close (stop_pipe);
702     GNUNET_DISK_pipe_close (progress_pipe);
703     GNUNET_free (ds);
704     return NULL;
705   }
706   ds->progress_read_task 
707     = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, 
708                                       ds->progress_read, 
709                                       &read_progress_task, ds);
710   return ds;
711 }
712
713
714 /* end of fs_dirmetascan.c */