Fix for #4553
[oweals/gnunet.git] / src / fs / gnunet-auto-share.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2001--2012 GNUnet e.V.
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., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, USA.
19 */
20 /**
21  * @file fs/gnunet-auto-share.c
22  * @brief automatically publish files on GNUnet
23  * @author Christian Grothoff
24  *
25  * TODO:
26  * - support loading meta data / keywords from resource file
27  * - add stability timer (a la buildbot)
28  */
29 #include "platform.h"
30 #include "gnunet_util_lib.h"
31
32 #define MIN_FREQUENCY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS, 4)
33
34 #define MAX_FREQUENCY GNUNET_TIME_UNIT_MINUTES
35
36
37 /**
38  * Item in our work queue (or in the set of files/directories
39  * we have successfully published).
40  */
41 struct WorkItem
42 {
43
44   /**
45    * PENDING Work is kept in a linked list.
46    */
47   struct WorkItem *prev;
48
49   /**
50    * PENDING Work is kept in a linked list.
51    */
52   struct WorkItem *next;
53
54   /**
55    * Filename of the work item.
56    */
57   char *filename;
58
59   /**
60    * Unique identity for this work item (used to detect
61    * if we need to do the work again).
62    */
63   struct GNUNET_HashCode id;
64 };
65
66
67 /**
68  * Global return value from 'main'.
69  */
70 static int ret;
71
72 /**
73  * Are we running 'verbosely'?
74  */
75 static int verbose;
76
77 /**
78  * Configuration to use.
79  */
80 static const struct GNUNET_CONFIGURATION_Handle *cfg;
81
82 /**
83  * Name of the configuration file.
84  */
85 static char *cfg_filename;
86
87 /**
88  * Disable extractor option to use for publishing.
89  */
90 static int disable_extractor;
91
92 /**
93  * Disable creation time option to use for publishing.
94  */
95 static int do_disable_creation_time;
96
97 /**
98  * Handle for the main task that does scanning and working.
99  */
100 static struct GNUNET_SCHEDULER_Task *run_task;
101
102 /**
103  * Anonymity level option to use for publishing.
104  */
105 static unsigned int anonymity_level = 1;
106
107 /**
108  * Content priority option to use for publishing.
109  */
110 static unsigned int content_priority = 365;
111
112 /**
113  * Replication level option to use for publishing.
114  */
115 static unsigned int replication_level = 1;
116
117 /**
118  * Top-level directory we monitor to auto-publish.
119  */
120 static const char *dir_name;
121
122 /**
123  * Head of linked list of files still to publish.
124  */
125 static struct WorkItem *work_head;
126
127 /**
128  * Tail of linked list of files still to publish.
129  */
130 static struct WorkItem *work_tail;
131
132 /**
133  * Map from the hash of the filename (!) to a `struct WorkItem`
134  * that was finished.
135  */
136 static struct GNUNET_CONTAINER_MultiHashMap *work_finished;
137
138 /**
139  * Set to #GNUNET_YES if we are shutting down.
140  */
141 static int do_shutdown;
142
143 /**
144  * Start time of the current round; used to determine how long
145  * one iteration takes (which influences how fast we schedule
146  * the next one).
147  */
148 static struct GNUNET_TIME_Absolute start_time;
149
150 /**
151  * Pipe used to communicate 'gnunet-publish' completion (SIGCHLD) via signal.
152  */
153 static struct GNUNET_DISK_PipeHandle *sigpipe;
154
155 /**
156  * Handle to the 'gnunet-publish' process that we executed.
157  */
158 static struct GNUNET_OS_Process *publish_proc;
159
160
161 /**
162  * Compute the name of the state database file we will use.
163  */
164 static char *
165 get_state_file ()
166 {
167   char *ret;
168
169   GNUNET_asprintf (&ret,
170                    "%s%s.auto-share",
171                    dir_name,
172                    (DIR_SEPARATOR == dir_name[strlen(dir_name)-1]) ? "" : DIR_SEPARATOR_STR);
173   return ret;
174 }
175
176
177 /**
178  * Load the set of #work_finished items from disk.
179  */
180 static void
181 load_state ()
182 {
183   char *fn;
184   struct GNUNET_BIO_ReadHandle *rh;
185   uint32_t n;
186   struct GNUNET_HashCode id;
187   struct WorkItem *wi;
188   char *emsg;
189
190   emsg = NULL;
191   fn = get_state_file ();
192   rh = GNUNET_BIO_read_open (fn);
193   GNUNET_free (fn);
194   if (NULL == rh)
195     return;
196   fn = NULL;
197   if (GNUNET_OK != GNUNET_BIO_read_int32 (rh, &n))
198     goto error;
199   while (n-- > 0)
200   {
201     if ( (GNUNET_OK !=
202           GNUNET_BIO_read_string (rh, "filename", &fn, 1024)) ||
203          (GNUNET_OK !=
204           GNUNET_BIO_read (rh, "id", &id, sizeof (struct GNUNET_HashCode))) )
205       goto error;
206     wi = GNUNET_new (struct WorkItem);
207     wi->id = id;
208     wi->filename = fn;
209     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
210                 "Loaded serialization ID for `%s' is `%s'\n",
211                 wi->filename,
212                 GNUNET_h2s (&id));
213     fn = NULL;
214     GNUNET_CRYPTO_hash (wi->filename,
215                         strlen (wi->filename),
216                         &id);
217     GNUNET_break (GNUNET_OK ==
218                   GNUNET_CONTAINER_multihashmap_put (work_finished,
219                                                      &id,
220                                                      wi,
221                                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
222   }
223   if (GNUNET_OK ==
224       GNUNET_BIO_read_close (rh, &emsg))
225     return;
226   rh = NULL;
227  error:
228   GNUNET_free_non_null (fn);
229   if (NULL != rh)
230     (void) GNUNET_BIO_read_close (rh, &emsg);
231   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
232               _("Failed to load state: %s\n"),
233               emsg);
234   GNUNET_free_non_null (emsg);
235 }
236
237
238 /**
239  * Write work item from the #work_finished map to the given write handle.
240  *
241  * @param cls the `struct GNUNET_BIO_WriteHandle *`
242  * @param key key of the item in the map (unused)
243  * @param value the `struct WorkItem` to write
244  * @return #GNUNET_OK to continue to iterate (if write worked)
245  */
246 static int
247 write_item (void *cls,
248             const struct GNUNET_HashCode *key,
249             void *value)
250 {
251   struct GNUNET_BIO_WriteHandle *wh = cls;
252   struct WorkItem *wi = value;
253
254   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
255               "Saving serialization ID of file `%s' with value `%s'\n",
256               wi->filename,
257               GNUNET_h2s (&wi->id));
258   if ( (GNUNET_OK !=
259         GNUNET_BIO_write_string (wh, wi->filename)) ||
260        (GNUNET_OK !=
261         GNUNET_BIO_write (wh,
262                           &wi->id,
263                           sizeof (struct GNUNET_HashCode))) )
264     return GNUNET_SYSERR; /* write error, abort iteration */
265   return GNUNET_OK;
266 }
267
268
269 /**
270  * Save the set of #work_finished items on disk.
271  */
272 static void
273 save_state ()
274 {
275   uint32_t n;
276   struct GNUNET_BIO_WriteHandle *wh;
277   char *fn;
278
279   n = GNUNET_CONTAINER_multihashmap_size (work_finished);
280   fn = get_state_file ();
281   wh = GNUNET_BIO_write_open (fn);
282   if (NULL == wh)
283   {
284     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
285                 _("Failed to save state to file %s\n"),
286                 fn);
287     GNUNET_free (fn);
288     return;
289   }
290   if (GNUNET_OK !=
291       GNUNET_BIO_write_int32 (wh, n))
292   {
293     (void) GNUNET_BIO_write_close (wh);
294     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
295                 _("Failed to save state to file %s\n"),
296                 fn);
297     GNUNET_free (fn);
298     return;
299   }
300   (void) GNUNET_CONTAINER_multihashmap_iterate (work_finished,
301                                                 &write_item,
302                                                 wh);
303   if (GNUNET_OK != GNUNET_BIO_write_close (wh))
304     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
305                 _("Failed to save state to file %s\n"),
306                 fn);
307   GNUNET_free (fn);
308 }
309
310
311 /**
312  * Task run on shutdown.  Serializes our current state to disk.
313  *
314  * @param cls closure, unused
315  */
316 static void
317 do_stop_task (void *cls)
318 {
319   do_shutdown = GNUNET_YES;
320   if (NULL != publish_proc)
321   {
322     GNUNET_OS_process_kill (publish_proc,
323                             SIGKILL);
324     return;
325   }
326   if (NULL != run_task)
327   {
328     GNUNET_SCHEDULER_cancel (run_task);
329     run_task = NULL;
330   }
331 }
332
333
334 /**
335  * Decide what the next task is (working or scanning) and schedule it.
336  */
337 static void
338 schedule_next_task (void);
339
340
341 /**
342  * Task triggered whenever we receive a SIGCHLD (child
343  * process died).
344  *
345  * @param cls the `struct WorkItem` we were working on
346  */
347 static void
348 maint_child_death (void *cls)
349 {
350   struct WorkItem *wi = cls;
351   struct GNUNET_HashCode key;
352   enum GNUNET_OS_ProcessStatusType type;
353   unsigned long code;
354   int ret;
355   char c;
356   const struct GNUNET_DISK_FileHandle *pr;
357   const struct GNUNET_SCHEDULER_TaskContext *tc;
358
359   run_task = NULL;
360   pr = GNUNET_DISK_pipe_handle (sigpipe,
361                                 GNUNET_DISK_PIPE_END_READ);
362   tc = GNUNET_SCHEDULER_get_task_context ();
363   if (0 == (tc->reason & GNUNET_SCHEDULER_REASON_READ_READY))
364   {
365     /* shutdown scheduled us, someone else will kill child,
366        we should just try again */
367     run_task =
368       GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
369                                       pr,
370                                       &maint_child_death, wi);
371     return;
372   }
373   /* consume the signal */
374   GNUNET_break (0 < GNUNET_DISK_file_read (pr, &c, sizeof (c)));
375
376   ret = GNUNET_OS_process_status (publish_proc,
377                                   &type,
378                                   &code);
379   GNUNET_assert (GNUNET_SYSERR != ret);
380   if (GNUNET_NO == ret)
381   {
382     /* process still running? Then where did the SIGCHLD come from?
383        Well, let's declare it spurious (kernel bug?) and keep rolling.
384     */
385     GNUNET_break (0);
386     run_task =
387       GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
388                                       pr,
389                                       &maint_child_death, wi);
390     return;
391   }
392   GNUNET_assert (GNUNET_OK == ret);
393
394   GNUNET_OS_process_destroy (publish_proc);
395   publish_proc = NULL;
396
397   if (GNUNET_YES == do_shutdown)
398   {
399     GNUNET_free (wi->filename);
400     GNUNET_free (wi);
401     return;
402   }
403   if ( (GNUNET_OS_PROCESS_EXITED == type) &&
404        (0 == code) )
405   {
406     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
407                 _("Publication of `%s' done\n"),
408                 wi->filename);
409     GNUNET_CRYPTO_hash (wi->filename,
410                         strlen (wi->filename),
411                         &key);
412     GNUNET_break (GNUNET_OK ==
413                   GNUNET_CONTAINER_multihashmap_put (work_finished,
414                                                      &key,
415                                                      wi,
416                                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
417   }
418   else
419   {
420     GNUNET_CONTAINER_DLL_insert_tail (work_head,
421                                       work_tail,
422                                       wi);
423   }
424   save_state ();
425   schedule_next_task ();
426 }
427
428
429 /**
430  * Signal handler called for SIGCHLD.  Triggers the
431  * respective handler by writing to the trigger pipe.
432  */
433 static void
434 sighandler_child_death ()
435 {
436   static char c;
437   int old_errno = errno;        /* back-up errno */
438
439   GNUNET_break (1 ==
440                 GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle
441                                         (sigpipe,
442                                          GNUNET_DISK_PIPE_END_WRITE),
443                                         &c, sizeof (c)));
444   errno = old_errno;            /* restore errno */
445 }
446
447
448 /**
449  * Function called to process work items.
450  *
451  * @param cls closure, NULL
452  */
453 static void
454 work (void *cls)
455 {
456   static char *argv[14];
457   static char anon_level[20];
458   static char content_prio[20];
459   static char repl_level[20];
460   struct WorkItem *wi;
461   const struct GNUNET_DISK_FileHandle *pr;
462   int argc;
463
464   run_task = NULL;
465   wi = work_head;
466   GNUNET_CONTAINER_DLL_remove (work_head,
467                                work_tail,
468                                wi);
469   argc = 0;
470   argv[argc++] = "gnunet-publish";
471   if (verbose)
472     argv[argc++] = "-V";
473   if (disable_extractor)
474     argv[argc++] = "-D";
475   if (do_disable_creation_time)
476     argv[argc++] = "-d";
477   argv[argc++] = "-c";
478   argv[argc++] = cfg_filename;
479   GNUNET_snprintf (anon_level, sizeof (anon_level),
480                    "%u", anonymity_level);
481   argv[argc++] = "-a";
482   argv[argc++] = anon_level;
483   GNUNET_snprintf (content_prio, sizeof (content_prio),
484                    "%u", content_priority);
485   argv[argc++] = "-p";
486   argv[argc++] = content_prio;
487   GNUNET_snprintf (repl_level, sizeof (repl_level),
488                    "%u", replication_level);
489   argv[argc++] = "-r";
490   argv[argc++] = repl_level;
491   argv[argc++] = wi->filename;
492   argv[argc] = NULL;
493   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
494               _("Publishing `%s'\n"),
495               wi->filename);
496   GNUNET_assert (NULL == publish_proc);
497   publish_proc = GNUNET_OS_start_process_vap (GNUNET_YES,
498                                               0, NULL, NULL, NULL,
499                                               "gnunet-publish",
500                                               argv);
501   if (NULL == publish_proc)
502   {
503     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
504                 _("Failed to run `%s'\n"),
505                 "gnunet-publish");
506     GNUNET_CONTAINER_DLL_insert (work_head,
507                                  work_tail,
508                                  wi);
509     run_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MINUTES,
510                                              &work,
511                                              NULL);
512     return;
513   }
514   pr = GNUNET_DISK_pipe_handle (sigpipe,
515                                 GNUNET_DISK_PIPE_END_READ);
516   run_task =
517     GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
518                                     pr,
519                                     &maint_child_death, wi);
520 }
521
522
523 /**
524  * Recursively scan the given file/directory structure to determine
525  * a unique ID that represents the current state of the hierarchy.
526  *
527  * @param cls where to store the unique ID we are computing
528  * @param filename file to scan
529  * @return #GNUNET_OK (always)
530  */
531 static int
532 determine_id (void *cls,
533               const char *filename)
534 {
535   struct GNUNET_HashCode *id = cls;
536   struct stat sbuf;
537   struct GNUNET_HashCode fx[2];
538   struct GNUNET_HashCode ft;
539
540   if (0 != STAT (filename, &sbuf))
541   {
542     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
543                               "stat",
544                               filename);
545     return GNUNET_OK;
546   }
547   GNUNET_CRYPTO_hash (filename,
548                       strlen (filename),
549                       &fx[0]);
550   if (!S_ISDIR (sbuf.st_mode))
551   {
552     uint64_t fattr[2];
553
554     fattr[0] = GNUNET_htonll (sbuf.st_size);
555     fattr[0] = GNUNET_htonll (sbuf.st_mtime);
556
557     GNUNET_CRYPTO_hash (fattr,
558                         sizeof (fattr),
559                         &fx[1]);
560   }
561   else
562   {
563     memset (&fx[1],
564             1,
565             sizeof (struct GNUNET_HashCode));
566     GNUNET_DISK_directory_scan (filename,
567                                 &determine_id,
568                                 &fx[1]);
569   }
570   /* use hash here to make hierarchical structure distinct from
571      all files on the same level */
572   GNUNET_CRYPTO_hash (fx,
573                       sizeof (fx),
574                       &ft);
575   /* use XOR here so that order of the files in the directory
576      does not matter! */
577   GNUNET_CRYPTO_hash_xor (&ft,
578                           id,
579                           id);
580   return GNUNET_OK;
581 }
582
583
584 /**
585  * Function called with a filename (or directory name) to publish
586  * (if it has changed since the last time we published it).  This function
587  * is called for the top-level files only.
588  *
589  * @param cls closure, NULL
590  * @param filename complete filename (absolute path)
591  * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR during shutdown
592  */
593 static int
594 add_file (void *cls,
595           const char *filename)
596 {
597   struct WorkItem *wi;
598   struct GNUNET_HashCode key;
599   struct GNUNET_HashCode id;
600
601   if (GNUNET_YES == do_shutdown)
602     return GNUNET_SYSERR;
603   if ( (NULL != strstr (filename,
604                       "/.auto-share")) ||
605        (NULL != strstr (filename,
606                         "\\.auto-share")) )
607     return GNUNET_OK; /* skip internal file */
608   GNUNET_CRYPTO_hash (filename,
609                       strlen (filename),
610                       &key);
611   wi = GNUNET_CONTAINER_multihashmap_get (work_finished,
612                                           &key);
613   memset (&id, 0, sizeof (struct GNUNET_HashCode));
614   determine_id (&id, filename);
615   if (NULL != wi)
616   {
617     if (0 == memcmp (&id,
618                      &wi->id,
619                      sizeof (struct GNUNET_HashCode)))
620       return GNUNET_OK; /* skip: we did this one already */
621     /* contents changed, need to re-do the directory... */
622     GNUNET_assert (GNUNET_YES ==
623                    GNUNET_CONTAINER_multihashmap_remove (work_finished,
624                                                          &key,
625                                                          wi));
626   }
627   else
628   {
629     wi = GNUNET_new (struct WorkItem);
630     wi->filename = GNUNET_strdup (filename);
631   }
632   wi->id = id;
633   GNUNET_CONTAINER_DLL_insert (work_head,
634                                work_tail,
635                                wi);
636   if (GNUNET_YES == do_shutdown)
637     return GNUNET_SYSERR;
638   return GNUNET_OK;
639 }
640
641
642 /**
643  * Periodically run task to update our view of the directory to share.
644  *
645  * @param cls NULL
646  */
647 static void
648 scan (void *cls)
649 {
650   run_task = NULL;
651   start_time = GNUNET_TIME_absolute_get ();
652   (void) GNUNET_DISK_directory_scan (dir_name,
653                                      &add_file,
654                                      NULL);
655   schedule_next_task ();
656 }
657
658
659 /**
660  * Decide what the next task is (working or scanning) and schedule it.
661  */
662 static void
663 schedule_next_task ()
664 {
665   struct GNUNET_TIME_Relative delay;
666
667   if (GNUNET_YES == do_shutdown)
668     return;
669   GNUNET_assert (NULL == run_task);
670   if (NULL == work_head)
671   {
672     /* delay by at most 4h, at least 1s, and otherwise in between depending
673        on how long it took to scan */
674     delay = GNUNET_TIME_absolute_get_duration (start_time);
675     delay = GNUNET_TIME_relative_min (MIN_FREQUENCY,
676                                       GNUNET_TIME_relative_multiply (delay,
677                                                                      100));
678     delay = GNUNET_TIME_relative_max (delay,
679                                       MAX_FREQUENCY);
680     run_task = GNUNET_SCHEDULER_add_delayed (delay,
681                                              &scan,
682                                              NULL);
683   }
684   else
685   {
686     run_task = GNUNET_SCHEDULER_add_now (&work,
687                                          NULL);
688   }
689 }
690
691
692 /**
693  * Main function that will be run by the scheduler.
694  *
695  * @param cls closure
696  * @param args remaining command-line arguments
697  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
698  * @param c configuration
699  */
700 static void
701 run (void *cls,
702      char *const *args,
703      const char *cfgfile,
704      const struct GNUNET_CONFIGURATION_Handle *c)
705 {
706   /* check arguments */
707   if ( (NULL == args[0]) ||
708        (NULL != args[1]) ||
709        (GNUNET_YES !=
710         GNUNET_DISK_directory_test (args[0],
711                                     GNUNET_YES)) )
712   {
713     printf (_("You must specify one and only one directory name for automatic publication.\n"));
714     ret = -1;
715     return;
716   }
717   cfg_filename = GNUNET_strdup (cfgfile);
718   cfg = c;
719   dir_name = args[0];
720   work_finished = GNUNET_CONTAINER_multihashmap_create (1024,
721                                                         GNUNET_NO);
722   load_state ();
723   run_task = GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
724                                                  &scan,
725                                                  NULL);
726   GNUNET_SCHEDULER_add_shutdown (&do_stop_task,
727                                  NULL);
728 }
729
730
731 /**
732  * Free memory associated with the work item from the work_finished map.
733  *
734  * @param cls NULL (unused)
735  * @param key key of the item in the map (unused)
736  * @param value the `struct WorkItem` to free
737  * @return #GNUNET_OK to continue to iterate
738  */
739 static int
740 free_item (void *cls,
741            const struct GNUNET_HashCode *key,
742            void *value)
743 {
744   struct WorkItem *wi = value;
745
746   GNUNET_free (wi->filename);
747   GNUNET_free (wi);
748   return GNUNET_OK;
749 }
750
751
752 /**
753  * The main function to automatically publish content to GNUnet.
754  *
755  * @param argc number of arguments from the command line
756  * @param argv command line arguments
757  * @return 0 ok, 1 on error
758  */
759 int
760 main (int argc, char *const *argv)
761 {
762   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
763     {'a', "anonymity", "LEVEL",
764      gettext_noop ("set the desired LEVEL of sender-anonymity"),
765      1, &GNUNET_GETOPT_set_uint, &anonymity_level},
766     {'d', "disable-creation-time", NULL,
767      gettext_noop
768      ("disable adding the creation time to the metadata of the uploaded file"),
769      0, &GNUNET_GETOPT_set_one, &do_disable_creation_time},
770     {'D', "disable-extractor", NULL,
771      gettext_noop ("do not use libextractor to add keywords or metadata"),
772      0, &GNUNET_GETOPT_set_one, &disable_extractor},
773     {'p', "priority", "PRIORITY",
774      gettext_noop ("specify the priority of the content"),
775      1, &GNUNET_GETOPT_set_uint, &content_priority},
776     {'r', "replication", "LEVEL",
777      gettext_noop ("set the desired replication LEVEL"),
778      1, &GNUNET_GETOPT_set_uint, &replication_level},
779     {'V', "verbose", NULL,
780      gettext_noop ("be verbose (print progress information)"),
781      0, &GNUNET_GETOPT_set_one, &verbose},
782     GNUNET_GETOPT_OPTION_END
783   };
784   struct WorkItem *wi;
785   int ok;
786   struct GNUNET_SIGNAL_Context *shc_chld;
787
788   if (GNUNET_OK !=
789       GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
790     return 2;
791   sigpipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO,
792                               GNUNET_NO, GNUNET_NO);
793   GNUNET_assert (NULL != sigpipe);
794   shc_chld =
795     GNUNET_SIGNAL_handler_install (GNUNET_SIGCHLD,
796                                    &sighandler_child_death);
797   ok = (GNUNET_OK ==
798         GNUNET_PROGRAM_run (argc, argv,
799                             "gnunet-auto-share [OPTIONS] FILENAME",
800                             gettext_noop
801                             ("Automatically publish files from a directory on GNUnet"),
802                             options, &run, NULL)) ? ret : 1;
803   if (NULL != work_finished)
804   {
805     (void) GNUNET_CONTAINER_multihashmap_iterate (work_finished,
806                                                   &free_item,
807                                                   NULL);
808     GNUNET_CONTAINER_multihashmap_destroy (work_finished);
809   }
810   while (NULL != (wi = work_head))
811   {
812     GNUNET_CONTAINER_DLL_remove (work_head,
813                                  work_tail,
814                                  wi);
815     GNUNET_free (wi->filename);
816     GNUNET_free (wi);
817   }
818   GNUNET_SIGNAL_handler_uninstall (shc_chld);
819   shc_chld = NULL;
820   GNUNET_DISK_pipe_close (sigpipe);
821   sigpipe = NULL;
822   GNUNET_free_non_null (cfg_filename);
823   cfg_filename = NULL;
824   GNUNET_free ((void*) argv);
825   return ok;
826 }
827
828 /* end of gnunet-auto-share.c */