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