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