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