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