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