fc00fd26c623be7f51f2eb0bf7129cec5e1f6265
[oweals/gnunet.git] / src / fs / gnunet-publish.c
1 /*
2      This file is part of GNUnet.
3      (C) 2001, 2002, 2004, 2005, 2006, 2007, 2009, 2010 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 2, 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-publish.c
22  * @brief publishing files on GNUnet
23  * @author Christian Grothoff
24  * @author Krista Bennett
25  * @author James Blackwell
26  * @author Igor Wronsky
27  */
28 #include "platform.h"
29 #include "gnunet_fs_service.h"
30
31 #define DEFAULT_EXPIRATION GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS, 2)
32
33 static int ret;
34
35 static int verbose;
36
37 static const struct GNUNET_CONFIGURATION_Handle *cfg;
38
39 static struct GNUNET_FS_Handle *ctx;
40
41 static struct GNUNET_SCHEDULER_Handle *sched;
42
43 static struct GNUNET_FS_PublishContext *pc;
44
45 static struct GNUNET_CONTAINER_MetaData *meta;
46
47 static struct GNUNET_FS_Uri *topKeywords;
48
49 static struct GNUNET_FS_Uri *uri;
50
51 static unsigned int anonymity = 1;
52
53 static unsigned int priority = 365;
54
55 static char *uri_string;
56
57 static char *next_id;
58
59 static char *this_id;
60
61 static char *pseudonym;
62
63 static int do_insert;
64
65 static int disable_extractor;
66
67 static int do_simulate;
68
69 static int extract_only;
70
71 static int do_disable_creation_time;
72
73 static GNUNET_SCHEDULER_TaskIdentifier kill_task;
74
75
76 static void 
77 do_stop_task (void *cls,
78               const struct GNUNET_SCHEDULER_TaskContext *tc)
79 {
80   struct GNUNET_FS_PublishContext *p;
81
82   if (pc != NULL)
83     {
84       p = pc;
85       pc = NULL;
86       GNUNET_FS_publish_stop (p);
87       if (NULL != meta) 
88         {
89           GNUNET_CONTAINER_meta_data_destroy (meta);
90           meta = NULL;
91         }
92     }
93 }
94
95
96 /**
97  * Called by FS client to give information about the progress of an 
98  * operation.
99  *
100  * @param cls closure
101  * @param info details about the event, specifying the event type
102  *        and various bits about the event
103  * @return client-context (for the next progress call
104  *         for this operation; should be set to NULL for
105  *         SUSPEND and STOPPED events).  The value returned
106  *         will be passed to future callbacks in the respective
107  *         field in the GNUNET_FS_ProgressInfo struct.
108  */
109 static void *
110 progress_cb (void *cls,
111              const struct GNUNET_FS_ProgressInfo *info)
112 {
113   char *s;
114
115   switch (info->status)
116     {
117     case GNUNET_FS_STATUS_PUBLISH_START:
118       break;
119     case GNUNET_FS_STATUS_PUBLISH_PROGRESS:
120       if (verbose)
121         {
122           s = GNUNET_STRINGS_relative_time_to_string(info->value.publish.eta);
123           fprintf (stdout,
124                    _("Publishing `%s' at %llu/%llu (%s remaining)\n"),
125                    info->value.publish.filename,
126                    (unsigned long long) info->value.publish.completed,
127                    (unsigned long long) info->value.publish.size,
128                    s);
129           GNUNET_free (s);
130         }
131       break;
132     case GNUNET_FS_STATUS_PUBLISH_ERROR:
133       fprintf (stderr,
134                _("Error publishing: %s.\n"),
135                info->value.publish.specifics.error.message);
136       if (kill_task != GNUNET_SCHEDULER_NO_TASK)
137         {
138           GNUNET_SCHEDULER_cancel (sched,
139                                    kill_task);
140           kill_task = GNUNET_SCHEDULER_NO_TASK;
141         }
142       GNUNET_SCHEDULER_add_continuation (sched,
143                                          &do_stop_task,
144                                          NULL,
145                                          GNUNET_SCHEDULER_REASON_PREREQ_DONE);
146       break;
147     case GNUNET_FS_STATUS_PUBLISH_COMPLETED:
148       fprintf (stdout,
149                _("Publishing `%s' done.\n"),
150                info->value.publish.filename);
151       s = GNUNET_FS_uri_to_string (info->value.publish.specifics.completed.chk_uri);
152       fprintf (stdout,
153                _("URI is `%s'.\n"),
154                s);
155       GNUNET_free (s);
156       if (info->value.publish.pctx == NULL)
157         {
158           if (kill_task != GNUNET_SCHEDULER_NO_TASK)
159             {
160               GNUNET_SCHEDULER_cancel (sched,
161                                        kill_task);
162               kill_task = GNUNET_SCHEDULER_NO_TASK;
163             }
164           GNUNET_SCHEDULER_add_continuation (sched,
165                                              &do_stop_task,
166                                              NULL,
167                                              GNUNET_SCHEDULER_REASON_PREREQ_DONE);
168         }
169       break;
170     case GNUNET_FS_STATUS_PUBLISH_STOPPED: 
171       GNUNET_break (NULL == pc);
172       return NULL;      
173     default:
174       fprintf (stderr,
175                _("Unexpected status: %d\n"),
176                info->status);
177       return NULL;
178     }
179   return ""; /* non-null */
180 }
181
182
183 /**
184  * Print metadata entries (except binary
185  * metadata and the filename).
186  *
187  * @param cls closure
188  * @param plugin_name name of the plugin that generated the meta data
189  * @param type type of the meta data
190  * @param format format of data
191  * @param data_mime_type mime type of data
192  * @param data value of the meta data
193  * @param data_size number of bytes in data
194  * @return always 0
195  */
196 static int
197 meta_printer (void *cls,
198               const char *plugin_name,
199               enum EXTRACTOR_MetaType type, 
200               enum EXTRACTOR_MetaFormat format,
201               const char *data_mime_type,
202               const char *data,
203               size_t data_size)
204 {
205   if ( (format != EXTRACTOR_METAFORMAT_UTF8) &&
206        (format != EXTRACTOR_METAFORMAT_C_STRING) )
207     return 0;
208   if (type == EXTRACTOR_METATYPE_FILENAME) 
209     return 0;
210   fprintf (stdout, 
211            "%s - %s",
212            EXTRACTOR_metatype_to_string (type),
213            data);
214   return 0;
215 }
216
217
218 /**
219  * Merge metadata entries.
220  *
221  * @param cls closure, target metadata structure
222  * @param plugin_name name of the plugin that generated the meta data
223  * @param type type of the meta data
224  * @param format format of data
225  * @param data_mime_type mime type of data
226  * @param data value of the meta data
227  * @param data_size number of bytes in data
228  * @return always 0
229  */
230 static int
231 meta_merger (void *cls,
232               const char *plugin_name,
233               enum EXTRACTOR_MetaType type, 
234               enum EXTRACTOR_MetaFormat format,
235               const char *data_mime_type,
236               const char *data,
237               size_t data_size)
238 {
239   struct GNUNET_CONTAINER_MetaData *m = cls;
240
241   GNUNET_CONTAINER_meta_data_insert (m,
242                                      plugin_name,
243                                      type, 
244                                      format,
245                                      data_mime_type,
246                                      data,
247                                      data_size);
248   return 0;
249 }
250
251
252 /**
253  * Function called on all entries before the publication.  This is
254  * where we perform modifications to the default based on command-line
255  * options.
256  *
257  * @param cls closure
258  * @param fi the entry in the publish-structure
259  * @param length length of the file or directory
260  * @param m metadata for the file or directory (can be modified)
261  * @param uri pointer to the keywords that will be used for this entry (can be modified)
262  * @param anonymity pointer to selected anonymity level (can be modified)
263  * @param priority pointer to selected priority (can be modified)
264  * @param expirationTime pointer to selected expiration time (can be modified)
265  * @param client_info pointer to client context set upon creation (can be modified)
266  * @return GNUNET_OK to continue, GNUNET_NO to remove
267  *         this entry from the directory, GNUNET_SYSERR
268  *         to abort the iteration
269  */
270 static int
271 publish_inspector (void *cls,
272                    struct GNUNET_FS_FileInformation *fi,
273                    uint64_t length,
274                    struct GNUNET_CONTAINER_MetaData *m,
275                    struct GNUNET_FS_Uri **uri,
276                    unsigned int *anonymity,
277                    unsigned int *priority,
278                    struct GNUNET_TIME_Absolute *expirationTime,
279                    void **client_info)
280 {
281   char *fn;
282   char *fs;
283   struct GNUNET_FS_Uri *new_uri;
284
285   if (cls == fi)
286     return GNUNET_OK;
287   if (NULL != topKeywords)
288     {
289       if (*uri != NULL)
290         {
291           new_uri = GNUNET_FS_uri_ksk_merge (topKeywords,
292                                              *uri);
293           GNUNET_FS_uri_destroy (*uri); 
294           *uri = new_uri;
295           GNUNET_FS_uri_destroy (topKeywords);
296         }
297       else
298         {
299           *uri = topKeywords;
300         }
301       topKeywords = NULL;
302     }
303   if (NULL != meta) 
304     GNUNET_CONTAINER_meta_data_iterate (meta,
305                                         &meta_merger,
306                                         m);
307   if (! do_disable_creation_time)
308     GNUNET_CONTAINER_meta_data_add_publication_date (m);
309   if (extract_only)
310     {
311       fn = GNUNET_CONTAINER_meta_data_get_by_type (m,
312                                                    EXTRACTOR_METATYPE_FILENAME);
313       fs = GNUNET_STRINGS_byte_size_fancy (length);
314       fprintf (stdout,
315                _("Keywords for file `%s' (%s)\n"),
316                fn,
317                fs);
318       GNUNET_free (fn);
319       GNUNET_free (fs);
320       GNUNET_CONTAINER_meta_data_iterate (m,
321                                           &meta_printer,
322                                           NULL);
323       fprintf (stdout, "\n");
324     }
325   if (GNUNET_YES == GNUNET_FS_meta_data_test_for_directory (m))
326     GNUNET_FS_file_information_inspect (fi,
327                                         &publish_inspector,
328                                         fi);
329   return GNUNET_OK;
330 }
331
332
333 static void 
334 uri_sks_continuation (void *cls,
335                       const struct GNUNET_FS_Uri *ksk_uri,
336                       const char *emsg)
337 {
338   if (emsg != NULL)
339     {
340       fprintf (stderr,
341                "%s\n",
342                emsg);
343       ret = 1;
344     }
345   GNUNET_FS_uri_destroy (uri);
346   uri = NULL;
347   GNUNET_FS_stop (ctx);
348   ctx = NULL;
349 }
350
351
352 static void 
353 uri_ksk_continuation (void *cls,
354                       const struct GNUNET_FS_Uri *ksk_uri,
355                       const char *emsg)
356 {
357   struct GNUNET_FS_Namespace *ns;
358
359   if (emsg != NULL)
360     {
361       fprintf (stderr,
362                "%s\n",
363                emsg);
364       ret = 1;
365     }
366   if (pseudonym != NULL)
367     {
368       ns = GNUNET_FS_namespace_create (ctx,
369                                        pseudonym);
370       if (ns == NULL)
371         {
372           fprintf (stderr,
373                    _("Failed to create namespace `%s'\n"),
374                    pseudonym);
375           ret = 1;
376         }
377       else
378         {
379           GNUNET_FS_publish_sks (ctx,
380                                  ns,
381                                  this_id,
382                                  next_id,
383                                  meta,
384                                  uri,
385                                  GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION),
386                                  anonymity,
387                                  priority,
388                                  GNUNET_FS_PUBLISH_OPTION_NONE,
389                                  uri_sks_continuation,
390                                  NULL);
391           GNUNET_assert (GNUNET_OK ==
392                          GNUNET_FS_namespace_delete (ns, GNUNET_NO));
393           return;
394         }
395     }
396   GNUNET_FS_uri_destroy (uri);
397   uri = NULL;
398   GNUNET_FS_stop (ctx);
399   ctx = NULL;
400 }
401
402
403 /**
404  * Main function that will be run by the scheduler.
405  *
406  * @param cls closure
407  * @param s the scheduler to use
408  * @param args remaining command-line arguments
409  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
410  * @param c configuration
411  */
412 static void
413 run (void *cls,
414      struct GNUNET_SCHEDULER_Handle *s,
415      char *const *args,
416      const char *cfgfile,
417      const struct GNUNET_CONFIGURATION_Handle *c)
418 {
419   struct GNUNET_FS_FileInformation *fi;
420   struct GNUNET_FS_Namespace *namespace;
421   struct EXTRACTOR_PluginList *l;
422   struct stat sbuf;
423   char *ex;
424   char *emsg;
425   
426   sched = s;
427   /* check arguments */
428   if ( ( (uri_string == NULL) || (extract_only) ) 
429        && ( (args[0] == NULL) || (args[1] != NULL) ) )
430     {
431       printf (_
432               ("You must specify one and only one filename for insertion.\n"));
433       ret = -1;
434       return;
435     }
436   if ((uri_string != NULL) && (args[0] != NULL))
437     {
438       printf (_("You must NOT specify an URI and a filename.\n"));
439       ret = -1;
440       return;
441     }
442   if ((uri_string != NULL) && (extract_only))
443     {
444       printf (_("Cannot extract metadata from a URI!\n"));
445       ret = -1;
446       return;
447     }
448   if (pseudonym != NULL)
449     {
450       if (NULL == this_id)
451         {
452           fprintf (stderr,
453                    _("Option `%s' is required when using option `%s'.\n"),
454                    "-t", "-P");
455           ret = -1;
456           return;
457         }
458     }
459   else
460     {                           /* ordinary insertion checks */
461       if (NULL != next_id)
462         {
463           fprintf (stderr,
464                    _("Option `%s' makes no sense without option `%s'.\n"),
465                    "-N", "-P");
466           ret = -1;
467           return;
468         }
469       if (NULL != this_id)
470         {
471           fprintf (stderr,
472                    _("Option `%s' makes no sense without option `%s'.\n"),
473                    "-t", "-P");
474           ret = -1;
475           return;
476         }
477     }
478   if ( (args[0] == NULL) &&
479        (uri_string == NULL) )
480     {
481       fprintf (stderr,
482                _("Need the name of a file to publish!\n"));
483       ret = 1;
484       return;
485     }
486   cfg = c;
487   ctx = GNUNET_FS_start (sched,
488                          cfg,
489                          "gnunet-publish",
490                          &progress_cb,
491                          NULL,
492                          GNUNET_FS_FLAGS_NONE,
493                          GNUNET_FS_OPTIONS_END);
494   if (NULL == ctx)
495     {
496       fprintf (stderr,
497                _("Could not initialize `%s' subsystem.\n"),
498                "FS");
499       ret = 1;
500       return;
501     }
502   namespace = NULL;
503   if (NULL != pseudonym)
504     {
505       namespace = GNUNET_FS_namespace_create (ctx,
506                                               pseudonym);
507       if (NULL == namespace)
508         {
509           fprintf (stderr,
510                    _("Could not create namespace `%s'\n"),
511                    pseudonym);
512           GNUNET_FS_stop (ctx);
513           ret = 1;
514           return;
515         }
516     }
517   if (NULL != uri_string)
518     {      
519       emsg = NULL;
520       uri = GNUNET_FS_uri_parse (uri_string,
521                                  &emsg);
522       if (uri == NULL)
523         {
524           fprintf (stderr, 
525                    _("Failed to parse URI: %s\n"),
526                    emsg);
527           GNUNET_free (emsg);
528           GNUNET_FS_stop (ctx);
529           ret = 1;
530           return;         
531         }
532       GNUNET_FS_publish_ksk (ctx,
533                              topKeywords,
534                              meta,
535                              uri,
536                              GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION),
537                              anonymity,
538                              priority,
539                              GNUNET_FS_PUBLISH_OPTION_NONE,
540                              &uri_ksk_continuation,
541                              NULL);
542       return;
543     }
544   l = NULL;
545   if (! disable_extractor)
546     {
547       l = EXTRACTOR_plugin_add_defaults (EXTRACTOR_OPTION_DEFAULT_POLICY);
548       if (GNUNET_OK ==
549           GNUNET_CONFIGURATION_get_value_string (cfg, "FS", "EXTRACTORS",
550                                                  &ex))
551         {
552           if (strlen (ex) > 0)
553             l = EXTRACTOR_plugin_add_config (l, ex, EXTRACTOR_OPTION_DEFAULT_POLICY);
554           GNUNET_free (ex);
555         }
556     }
557   emsg = NULL;
558   if (0 != STAT (args[0], &sbuf))
559     {
560       GNUNET_asprintf (&emsg,
561                        _("Could not access file: %s\n"),
562                        STRERROR (errno));
563       fi = NULL;
564     }
565   else if (S_ISDIR (sbuf.st_mode))
566     {
567       fi = GNUNET_FS_file_information_create_from_directory (NULL,
568                                                              args[0],
569                                                              &GNUNET_FS_directory_scanner_default,
570                                                              l,
571                                                              !do_insert,
572                                                              anonymity,
573                                                              priority,
574                                                              GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION),
575                                                              &emsg);
576     }
577   else
578     {
579       fi = GNUNET_FS_file_information_create_from_file (NULL,
580                                                         args[0],
581                                                         NULL,
582                                                         NULL,
583                                                         !do_insert,
584                                                         anonymity,
585                                                         priority,
586                                                         GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION));
587       GNUNET_break (fi != NULL);
588     }
589   EXTRACTOR_plugin_remove_all (l);  
590   if (fi == NULL)
591     {
592       fprintf (stderr,
593                _("Could not publish `%s': %s\n"),
594                args[0],
595                emsg);
596       GNUNET_free (emsg);
597       if (namespace != NULL)
598         GNUNET_FS_namespace_delete (namespace, GNUNET_NO);
599       GNUNET_FS_stop (ctx);
600       ret = 1;
601       return;
602     }
603   GNUNET_FS_file_information_inspect (fi,
604                                       &publish_inspector,
605                                       NULL);
606   if (extract_only)
607     {
608       if (namespace != NULL)
609         GNUNET_FS_namespace_delete (namespace, GNUNET_NO);
610       GNUNET_FS_file_information_destroy (fi, NULL, NULL);
611       GNUNET_FS_stop (ctx);
612       return;
613     }
614   pc = GNUNET_FS_publish_start (ctx,
615                                 fi,
616                                 namespace,
617                                 this_id,
618                                 next_id,
619                                 (do_simulate) 
620                                 ? GNUNET_FS_PUBLISH_OPTION_SIMULATE_ONLY
621                                 : GNUNET_FS_PUBLISH_OPTION_NONE);
622   if (NULL == pc)
623     {
624       fprintf (stderr,
625                _("Could not start publishing.\n"));
626       GNUNET_FS_stop (ctx);
627       ret = 1;
628       return;
629     }
630   kill_task = GNUNET_SCHEDULER_add_delayed (sched,
631                                             GNUNET_TIME_UNIT_FOREVER_REL,
632                                             &do_stop_task,
633                                             NULL);
634 }
635
636
637 /**
638  * gnunet-publish command line options
639  */
640 static struct GNUNET_GETOPT_CommandLineOption options[] = {
641   {'a', "anonymity", "LEVEL",
642    gettext_noop ("set the desired LEVEL of sender-anonymity"),
643    1, &GNUNET_GETOPT_set_uint, &anonymity},
644   {'d', "disable-creation-time", NULL,
645    gettext_noop
646    ("disable adding the creation time to the metadata of the uploaded file"),
647    0, &GNUNET_GETOPT_set_one, &do_disable_creation_time},
648   {'D', "disable-extractor", NULL,
649    gettext_noop
650    ("do not use libextractor to add keywords or metadata"),
651    0, &GNUNET_GETOPT_set_one, &disable_extractor},
652   {'e', "extract", NULL,
653    gettext_noop
654    ("print list of extracted keywords that would be used, but do not perform upload"),
655    0, &GNUNET_GETOPT_set_one, &extract_only},
656   {'k', "key", "KEYWORD",
657    gettext_noop
658    ("add an additional keyword for the top-level file or directory"
659     " (this option can be specified multiple times)"),
660    1, &GNUNET_FS_getopt_set_keywords, &topKeywords},
661   {'m', "meta", "TYPE:VALUE",
662    gettext_noop ("set the meta-data for the given TYPE to the given VALUE"),
663    1, &GNUNET_FS_getopt_set_metadata, &meta},
664   {'n', "noindex", NULL,
665    gettext_noop ("do not index, perform full insertion (stores entire "
666                  "file in encrypted form in GNUnet database)"),
667    0, &GNUNET_GETOPT_set_one, &do_insert},
668   {'N', "next", "ID",
669    gettext_noop
670    ("specify ID of an updated version to be published in the future"
671     " (for namespace insertions only)"),
672    1, &GNUNET_GETOPT_set_string, &next_id},
673   {'p', "priority", "PRIORITY",
674    gettext_noop ("specify the priority of the content"),
675    1, &GNUNET_GETOPT_set_uint, &priority},
676   {'P', "pseudonym", "NAME",
677    gettext_noop
678    ("publish the files under the pseudonym NAME (place file into namespace)"),
679    1, &GNUNET_GETOPT_set_string, &pseudonym},
680   {'s', "simulate-only", NULL,
681    gettext_noop ("only simulate the process but do not do any "
682                  "actual publishing (useful to compute URIs)"),
683    0, &GNUNET_GETOPT_set_one, &do_simulate},
684   {'t', "this", "ID",
685    gettext_noop ("set the ID of this version of the publication"
686                  " (for namespace insertions only)"),
687    1, &GNUNET_GETOPT_set_string, &this_id},
688   {'u', "uri", "URI",
689    gettext_noop ("URI to be published (can be used instead of passing a "
690                  "file to add keywords to the file with the respective URI)"),
691    1, &GNUNET_GETOPT_set_string, &uri_string}, 
692   {'V', "verbose", NULL,
693    gettext_noop ("be verbose (print progress information)"),
694    0, &GNUNET_GETOPT_set_one, &verbose},
695   GNUNET_GETOPT_OPTION_END
696 };
697
698
699 /**
700  * The main function to publish content to GNUnet.
701  *
702  * @param argc number of arguments from the command line
703  * @param argv command line arguments
704  * @return 0 ok, 1 on error
705  */
706 int
707 main (int argc, char *const *argv)
708 {
709   return (GNUNET_OK ==
710           GNUNET_PROGRAM_run (argc,
711                               argv,
712                               "gnunet-publish",
713                               gettext_noop
714                               ("Publish files on GNUnet."),
715                               options, &run, NULL)) ? ret : 1;
716 }
717
718 /* end of gnunet-publish.c */