-implementing freeing of state on exit
[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 #include "platform.h"
26 #include "gnunet_util_lib.h"
27
28
29 /**
30  * Item in our work queue (or in the set of files/directories
31  * we have successfully published).
32  */
33 struct WorkItem
34 {
35
36   /**
37    * PENDING Work is kept in a linked list.
38    */
39   struct WorkItem *prev;
40
41   /**
42    * PENDING Work is kept in a linked list.
43    */
44   struct WorkItem *next;
45
46   /**
47    * Filename of the work item.
48    */
49   char *filename;
50
51   /**
52    * Unique identity for this work item (used to detect
53    * if we need to do the work again).
54    */
55   struct GNUNET_HashCode id;
56 };
57
58
59 /**
60  * Global return value from 'main'.
61  */
62 static int ret;
63
64 /**
65  * Are we running 'verbosely'?
66  */
67 static int verbose;
68
69 /**
70  * Configuration to use.
71  */
72 static const struct GNUNET_CONFIGURATION_Handle *cfg;
73
74 /**
75  * Disable extractor option to use for publishing.
76  */
77 static int disable_extractor;
78
79 /**
80  * Disable creation time option to use for publishing.
81  */
82 static int do_disable_creation_time;
83
84 /**
85  * Handle for the 'shutdown' task.
86  */
87 static GNUNET_SCHEDULER_TaskIdentifier kill_task;
88
89 /**
90  * Handle for the main task that does scanning and working.
91  */
92 static GNUNET_SCHEDULER_TaskIdentifier run_task;
93
94 /**
95  * Anonymity level option to use for publishing.
96  */
97 static unsigned int anonymity_level = 1;
98
99 /**
100  * Content priority option to use for publishing.
101  */
102 static unsigned int content_priority = 365;
103
104 /**
105  * Replication level option to use for publishing.
106  */
107 static unsigned int replication_level = 1;
108
109 /**
110  * Top-level directory we monitor to auto-publish.
111  */
112 static const char *dir_name;
113
114 /**
115  * Head of linked list of files still to publish.
116  */
117 static struct WorkItem *work_head;
118
119 /**
120  * Tail of linked list of files still to publish.
121  */
122 static struct WorkItem *work_tail;
123
124 /**
125  * Map from the hash of the filename (!) to a 'struct WorkItem'
126  * that was finished.
127  */
128 static struct GNUNET_CONTAINER_MultiHashMap *work_finished;
129
130 /**
131  * Set to GNUNET_YES if we are shutting down.
132  */
133 static int do_shutdown;
134
135 /**
136  * Start time of the current round; used to determine how long
137  * one iteration takes (which influences how fast we schedule
138  * the next one).
139  */
140 static struct GNUNET_TIME_Absolute start_time;
141
142
143 /**
144  * Compute the name of the state database file we will use.
145  */
146 static char *
147 get_state_file ()
148 {
149   char *ret;
150
151   GNUNET_asprintf (&ret,
152                    "%s%s.auto-share",
153                    dir_name,
154                    (DIR_SEPARATOR == dir_name[strlen(dir_name)-1]) ? "" : DIR_SEPARATOR_STR);
155   return ret;
156 }
157
158
159 /**
160  * Load the set of 'work_finished' items from disk.
161  */
162 static void
163 load_state ()
164 {
165   char *fn;
166   struct GNUNET_BIO_ReadHandle *rh;
167   uint32_t n;
168   struct GNUNET_HashCode id;
169   struct WorkItem *wi;
170   char *emsg;
171
172   emsg = NULL;
173   fn = get_state_file ();
174   rh = GNUNET_BIO_read_open (fn);
175   GNUNET_free (fn);
176   if (NULL == rh)
177     return;
178   fn = NULL;
179   if (GNUNET_OK != GNUNET_BIO_read_int32 (rh, &n))
180     goto error;
181   while (n-- > 0)
182   {
183     if ( (GNUNET_OK !=
184           GNUNET_BIO_read_string (rh, "filename", &fn, 1024)) || 
185          (GNUNET_OK !=
186           GNUNET_BIO_read (rh, "id", &id, sizeof (struct GNUNET_HashCode))) )
187       goto error;
188     wi = GNUNET_malloc (sizeof (struct WorkItem));
189     wi->id = id;
190     wi->filename = fn;
191     fn = NULL;
192     GNUNET_CRYPTO_hash (wi->filename,
193                         strlen (wi->filename),
194                         &id);
195     GNUNET_CONTAINER_multihashmap_put (work_finished,
196                                        &id,
197                                        wi,
198                                        GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
199   }
200   if (GNUNET_OK == 
201       GNUNET_BIO_read_close (rh, &emsg))
202     return;
203   rh = NULL;
204  error:
205   GNUNET_free_non_null (fn);
206   if (NULL != rh)
207     GNUNET_BIO_read_close (rh, &emsg);
208   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
209               _("Failed to load state: %s\n"),
210               emsg);
211   GNUNET_free_non_null (emsg);
212 }
213
214
215 /**
216  * Write work item from the work_finished map to the given write handle.
217  *
218  * @param cls the 'struct GNUNET_BIO_WriteHandle*'
219  * @param key key of the item in the map (unused)
220  * @param value the 'struct WorkItem' to write
221  * @return GNUNET_OK to continue to iterate (if write worked)
222  */
223 static int
224 write_item (void *cls,
225             const struct GNUNET_HashCode *key,
226             void *value)
227 {
228   struct GNUNET_BIO_WriteHandle *wh = cls;
229   struct WorkItem *wi = value;
230
231   if ( (GNUNET_OK != 
232         GNUNET_BIO_write_string (wh, wi->filename)) ||
233        (GNUNET_OK !=
234         GNUNET_BIO_write (wh,
235                           &wi->id,
236                           sizeof (struct GNUNET_HashCode))) )
237     return GNUNET_SYSERR; /* write error, abort iteration */
238   return GNUNET_OK;
239 }
240
241
242 /**
243  * Save the set of 'work_finished' items on disk.
244  */
245 static void
246 save_state ()
247 {
248   uint32_t n;
249   struct GNUNET_BIO_WriteHandle *wh;
250   char *fn;
251
252   n = GNUNET_CONTAINER_multihashmap_size (work_finished);
253   fn = get_state_file ();
254   wh = GNUNET_BIO_write_open (fn);
255   if (GNUNET_OK !=
256       GNUNET_BIO_write_int32 (wh, n))
257   {
258     (void) GNUNET_BIO_write_close (wh);
259     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
260                 _("Failed to save state to file %s\n"),
261                 fn);
262     GNUNET_free (fn);
263     return;
264   }
265   (void) GNUNET_CONTAINER_multihashmap_iterate (work_finished,
266                                                 &write_item,
267                                                 wh);
268   if (GNUNET_OK != GNUNET_BIO_write_close (wh))
269     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
270                 _("Failed to save state to file %s\n"),
271                 fn);
272   GNUNET_free (fn);
273 }
274
275
276 /**
277  * Task run on shutdown.  Serializes our current state to disk.
278  *
279  * @param cls closure, unused
280  * @param tc scheduler context, unused
281  */
282 static void
283 do_stop_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
284 {
285   kill_task = GNUNET_SCHEDULER_NO_TASK;
286   do_shutdown = GNUNET_YES;
287   if (GNUNET_SCHEDULER_NO_TASK != run_task)
288   {
289     GNUNET_SCHEDULER_cancel (run_task);
290     run_task = GNUNET_SCHEDULER_NO_TASK;
291   }
292 }
293
294
295 /**
296  * Decide what the next task is (working or scanning) and schedule it.
297  */
298 static void
299 schedule_next_task (void);
300
301
302 /**
303  * Function called to process work items.
304  *
305  * @param cls closure, NULL
306  * @param tc scheduler context (unused)
307  */
308 static void
309 work (void *cls,
310       const struct GNUNET_SCHEDULER_TaskContext *tc)
311 {
312   struct WorkItem *wi;
313   struct GNUNET_HashCode key;
314
315   run_task = GNUNET_SCHEDULER_NO_TASK;
316   wi = work_head;
317   GNUNET_CONTAINER_DLL_remove (work_head,
318                                work_tail,
319                                wi);
320   // FIXME: actually run 'publish' here!
321
322   GNUNET_CRYPTO_hash (wi->filename,
323                       strlen (wi->filename),
324                       &key);
325   GNUNET_CONTAINER_multihashmap_put (work_finished,
326                                      &key,
327                                      wi,
328                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
329   save_state ();
330   schedule_next_task ();    
331 }
332
333
334 /**
335  * Recursively scan the given file/directory structure to determine
336  * a unique ID that represents the current state of the hierarchy.
337  *
338  * @param cls where to store the unique ID we are computing
339  * @param filename file to scan
340  * @return GNUNET_OK (always)
341  */
342 static int
343 determine_id (void *cls,
344               const char *filename)
345 {
346   struct GNUNET_HashCode *id = cls;
347   struct stat sbuf;
348   struct GNUNET_HashCode fx[2];
349   struct GNUNET_HashCode ft;
350
351   if (NULL != strstr (filename,
352                       DIR_SEPARATOR_STR ".auto-share"))
353     return GNUNET_OK; /* skip internal file */
354   if (0 != STAT (filename, &sbuf))
355   {
356     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat", filename);
357     return GNUNET_OK;
358   }
359   GNUNET_CRYPTO_hash (filename, strlen (filename), &fx[0]);
360   if (!S_ISDIR (sbuf.st_mode))
361   {
362     uint64_t fsize = GNUNET_htonll (sbuf.st_size);
363
364     GNUNET_CRYPTO_hash (&fsize, sizeof (uint64_t), &fx[1]);
365   }
366   else
367   {
368     memset (&fx[1], 1, sizeof (struct GNUNET_HashCode));
369     GNUNET_DISK_directory_scan (filename,
370                                 &determine_id,
371                                 &fx[1]);
372   }
373   /* use hash here to make hierarchical structure distinct from
374      all files on the same level */
375   GNUNET_CRYPTO_hash (fx, sizeof (fx), &ft);
376   /* use XOR here so that order of the files in the directory 
377      does not matter! */
378   GNUNET_CRYPTO_hash_xor (&ft, id, id);
379   return GNUNET_OK;
380 }
381
382
383 /**
384  * Function called with a filename (or directory name) to publish
385  * (if it has changed since the last time we published it).  This function
386  * is called for the top-level files only.
387  *
388  * @param cls closure, NULL
389  * @param filename complete filename (absolute path)
390  * @return GNUNET_OK to continue to iterate, GNUNET_SYSERR during shutdown
391  */
392 static int
393 add_file (void *cls,
394           const char *filename)
395 {
396   struct WorkItem *wi;
397   struct GNUNET_HashCode key;
398   struct GNUNET_HashCode id;
399
400   if (GNUNET_YES == do_shutdown)
401     return GNUNET_SYSERR;
402   GNUNET_CRYPTO_hash (filename,
403                       strlen (filename),
404                       &key);
405   wi = GNUNET_CONTAINER_multihashmap_get (work_finished,
406                                           &key);
407   memset (&id, 0, sizeof (struct GNUNET_HashCode));
408   determine_id (&id, filename);
409   if (NULL != wi)
410   {
411     if (0 == memcmp (&id,
412                      &wi->id,
413                      sizeof (struct GNUNET_HashCode)))
414       return GNUNET_OK; /* skip: we did this one already */
415     /* contents changed, need to re-do the directory... */
416     GNUNET_CONTAINER_multihashmap_remove (work_finished,
417                                           &key,
418                                           wi);
419     wi->id = id; 
420   }
421   else
422   {
423     wi = GNUNET_malloc (sizeof (struct WorkItem));
424     wi->filename = GNUNET_strdup (filename);
425   }
426   GNUNET_CONTAINER_DLL_insert (work_head,
427                                work_tail,
428                                wi);
429   if (GNUNET_YES == do_shutdown)
430     return GNUNET_SYSERR; 
431   return GNUNET_OK;
432 }
433
434
435 /**
436  * Periodically run task to update our view of the directory to share.
437  *
438  * @param cls NULL
439  * @param tc scheduler context, unused
440  */
441 static void
442 scan (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
443 {
444   run_task = GNUNET_SCHEDULER_NO_TASK;
445   start_time = GNUNET_TIME_absolute_get ();
446   (void) GNUNET_DISK_directory_scan (dir_name,
447                                      &add_file,
448                                      NULL);
449   schedule_next_task ();
450 }
451
452
453 /**
454  * Decide what the next task is (working or scanning) and schedule it.
455  */
456 static void
457 schedule_next_task ()
458 {
459   struct GNUNET_TIME_Relative delay;
460
461   if (GNUNET_YES == do_shutdown)
462     return;  
463   if (NULL == work_head)
464   {
465     /* delay by at most 4h, at least 1s, and otherwise in between depending
466        on how long it took to scan */
467     delay = GNUNET_TIME_absolute_get_duration (start_time);
468     delay = GNUNET_TIME_relative_min (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS,
469                                                                      4),
470                                       GNUNET_TIME_relative_multiply (delay,
471                                                                      100));
472     delay = GNUNET_TIME_relative_max (delay,
473                                       GNUNET_TIME_UNIT_MINUTES);
474     run_task = GNUNET_SCHEDULER_add_delayed (delay,
475                                              &scan,
476                                              NULL);
477   }
478   else
479   {
480     run_task = GNUNET_SCHEDULER_add_now (&work, NULL);
481   }
482 }
483
484
485 /**
486  * Main function that will be run by the scheduler.
487  *
488  * @param cls closure
489  * @param args remaining command-line arguments
490  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
491  * @param c configuration
492  */
493 static void
494 run (void *cls, char *const *args, const char *cfgfile,
495      const struct GNUNET_CONFIGURATION_Handle *c)
496 {
497   /* check arguments */
498   if ((args[0] == NULL) || (args[1] != NULL) ||
499       (GNUNET_YES != GNUNET_DISK_directory_test (args[0])))
500   {
501     printf (_("You must specify one and only one directory name for automatic publication.\n"));
502     ret = -1;
503     return;
504   }
505   cfg = c;
506   dir_name = args[0];
507   work_finished = GNUNET_CONTAINER_multihashmap_create (1024);
508   load_state ();
509   run_task = GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
510                                                  &scan, NULL);
511   
512   kill_task =
513       GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &do_stop_task,
514                                     NULL);
515 }
516
517
518 /**
519  * Free memory associated with the work item from the work_finished map.
520  *
521  * @param cls NULL (unused)
522  * @param key key of the item in the map (unused)
523  * @param value the 'struct WorkItem' to free
524  * @return GNUNET_OK to continue to iterate 
525  */
526 static int
527 free_item (void *cls,
528            const struct GNUNET_HashCode *key,
529            void *value)
530 {
531   struct WorkItem *wi = value;
532
533   GNUNET_free (wi->filename);
534   GNUNET_free (wi);
535   return GNUNET_OK;
536 }
537
538 /**
539  * The main function to automatically publish content to GNUnet.
540  *
541  * @param argc number of arguments from the command line
542  * @param argv command line arguments
543  * @return 0 ok, 1 on error
544  */
545 int
546 main (int argc, char *const *argv)
547 {  
548   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
549     {'a', "anonymity", "LEVEL",
550      gettext_noop ("set the desired LEVEL of sender-anonymity"),
551      1, &GNUNET_GETOPT_set_uint, &anonymity_level},
552     {'d', "disable-creation-time", NULL,
553      gettext_noop
554      ("disable adding the creation time to the metadata of the uploaded file"),
555      0, &GNUNET_GETOPT_set_one, &do_disable_creation_time},
556     {'D', "disable-extractor", NULL,
557      gettext_noop ("do not use libextractor to add keywords or metadata"),
558      0, &GNUNET_GETOPT_set_one, &disable_extractor},
559     {'p', "priority", "PRIORITY",
560      gettext_noop ("specify the priority of the content"),
561      1, &GNUNET_GETOPT_set_uint, &content_priority},
562     {'r', "replication", "LEVEL",
563      gettext_noop ("set the desired replication LEVEL"),
564      1, &GNUNET_GETOPT_set_uint, &replication_level},
565     {'V', "verbose", NULL,
566      gettext_noop ("be verbose (print progress information)"),
567      0, &GNUNET_GETOPT_set_one, &verbose},
568     GNUNET_GETOPT_OPTION_END
569   };
570   struct WorkItem *wi;
571   int ok;
572
573   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
574     return 2;
575   ok = (GNUNET_OK ==
576         GNUNET_PROGRAM_run (argc, argv, "gnunet-auto-share [OPTIONS] FILENAME",
577                             gettext_noop
578                             ("Automatically publish files from a directory on GNUnet"),
579                             options, &run, NULL)) ? ret : 1;
580   (void) GNUNET_CONTAINER_multihashmap_iterate (work_finished,
581                                                 &free_item,
582                                                 NULL);
583   GNUNET_CONTAINER_multihashmap_destroy (work_finished);
584   while (NULL != (wi = work_head))
585   {
586     GNUNET_CONTAINER_DLL_remove (work_head, work_tail, wi);
587     GNUNET_free (wi->filename);
588     GNUNET_free (wi);
589   }
590   return ok;
591 }
592
593 /* end of gnunet-auto-share.c */