fix
[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  * Function called on all entries before the publication.  This is
220  * where we perform modifications to the default based on command-line
221  * options.
222  *
223  * @param cls closure
224  * @param fi the entry in the publish-structure
225  * @param length length of the file or directory
226  * @param m metadata for the file or directory (can be modified)
227  * @param uri pointer to the keywords that will be used for this entry (can be modified)
228  * @param anonymity pointer to selected anonymity level (can be modified)
229  * @param priority pointer to selected priority (can be modified)
230  * @param expirationTime pointer to selected expiration time (can be modified)
231  * @param client_info pointer to client context set upon creation (can be modified)
232  * @return GNUNET_OK to continue, GNUNET_NO to remove
233  *         this entry from the directory, GNUNET_SYSERR
234  *         to abort the iteration
235  */
236 static int
237 publish_inspector (void *cls,
238                    struct GNUNET_FS_FileInformation *fi,
239                    uint64_t length,
240                    struct GNUNET_CONTAINER_MetaData *m,
241                    struct GNUNET_FS_Uri **uri,
242                    unsigned int *anonymity,
243                    unsigned int *priority,
244                    struct GNUNET_TIME_Absolute *expirationTime,
245                    void **client_info)
246 {
247   char *fn;
248   char *fs;
249   struct GNUNET_FS_Uri *new_uri;
250
251   if (cls == fi)
252     return GNUNET_OK;
253   if (NULL != topKeywords)
254     {
255       if (*uri != NULL)
256         {
257           new_uri = GNUNET_FS_uri_ksk_merge (topKeywords,
258                                              *uri);
259           GNUNET_FS_uri_destroy (*uri); 
260           *uri = new_uri;
261           GNUNET_FS_uri_destroy (topKeywords);
262         }
263       else
264         {
265           *uri = topKeywords;
266         }
267       topKeywords = NULL;
268     }
269   if (NULL != meta) 
270     {
271       GNUNET_CONTAINER_meta_data_merge (m, meta);
272       GNUNET_CONTAINER_meta_data_destroy (meta);
273       meta = NULL;
274     }
275   if (! do_disable_creation_time)
276     GNUNET_CONTAINER_meta_data_add_publication_date (m);
277   if (extract_only)
278     {
279       fn = GNUNET_CONTAINER_meta_data_get_by_type (m,
280                                                    EXTRACTOR_METATYPE_FILENAME);
281       fs = GNUNET_STRINGS_byte_size_fancy (length);
282       fprintf (stdout,
283                _("Keywords for file `%s' (%s)\n"),
284                fn,
285                fs);
286       GNUNET_free (fn);
287       GNUNET_free (fs);
288       GNUNET_CONTAINER_meta_data_iterate (m,
289                                           &meta_printer,
290                                           NULL);
291       fprintf (stdout, "\n");
292     }
293   if (GNUNET_YES == GNUNET_FS_meta_data_test_for_directory (m))
294     GNUNET_FS_file_information_inspect (fi,
295                                         &publish_inspector,
296                                         fi);
297   return GNUNET_OK;
298 }
299
300
301 static void 
302 uri_sks_continuation (void *cls,
303                       const struct GNUNET_FS_Uri *ksk_uri,
304                       const char *emsg)
305 {
306   if (emsg != NULL)
307     {
308       fprintf (stderr,
309                "%s\n",
310                emsg);
311       ret = 1;
312     }
313   GNUNET_FS_uri_destroy (uri);
314   uri = NULL;
315   GNUNET_FS_stop (ctx);
316   ctx = NULL;
317 }
318
319
320 static void 
321 uri_ksk_continuation (void *cls,
322                       const struct GNUNET_FS_Uri *ksk_uri,
323                       const char *emsg)
324 {
325   struct GNUNET_FS_Namespace *ns;
326
327   if (emsg != NULL)
328     {
329       fprintf (stderr,
330                "%s\n",
331                emsg);
332       ret = 1;
333     }
334   if (pseudonym != NULL)
335     {
336       ns = GNUNET_FS_namespace_create (ctx,
337                                        pseudonym);
338       if (ns == NULL)
339         {
340           fprintf (stderr,
341                    _("Failed to create namespace `%s'\n"),
342                    pseudonym);
343           ret = 1;
344         }
345       else
346         {
347           GNUNET_FS_publish_sks (ctx,
348                                  ns,
349                                  this_id,
350                                  next_id,
351                                  meta,
352                                  uri,
353                                  GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION),
354                                  anonymity,
355                                  priority,
356                                  GNUNET_FS_PUBLISH_OPTION_NONE,
357                                  uri_sks_continuation,
358                                  NULL);
359           GNUNET_assert (GNUNET_OK ==
360                          GNUNET_FS_namespace_delete (ns, GNUNET_NO));
361           return;
362         }
363     }
364   GNUNET_FS_uri_destroy (uri);
365   uri = NULL;
366   GNUNET_FS_stop (ctx);
367   ctx = NULL;
368 }
369
370
371 /**
372  * Main function that will be run by the scheduler.
373  *
374  * @param cls closure
375  * @param s the scheduler to use
376  * @param args remaining command-line arguments
377  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
378  * @param c configuration
379  */
380 static void
381 run (void *cls,
382      struct GNUNET_SCHEDULER_Handle *s,
383      char *const *args,
384      const char *cfgfile,
385      const struct GNUNET_CONFIGURATION_Handle *c)
386 {
387   struct GNUNET_FS_FileInformation *fi;
388   struct GNUNET_FS_Namespace *namespace;
389   struct EXTRACTOR_PluginList *l;
390   struct stat sbuf;
391   char *ex;
392   char *emsg;
393   
394   sched = s;
395   /* check arguments */
396   if ( ( (uri_string == NULL) || (extract_only) ) 
397        && ( (args[0] == NULL) || (args[1] != NULL) ) )
398     {
399       printf (_
400               ("You must specify one and only one filename for insertion.\n"));
401       ret = -1;
402       return;
403     }
404   if ((uri_string != NULL) && (args[0] != NULL))
405     {
406       printf (_("You must NOT specify an URI and a filename.\n"));
407       ret = -1;
408       return;
409     }
410   if ((uri_string != NULL) && (extract_only))
411     {
412       printf (_("Cannot extract metadata from a URI!\n"));
413       ret = -1;
414       return;
415     }
416   if (pseudonym != NULL)
417     {
418       if (NULL == this_id)
419         {
420           fprintf (stderr,
421                    _("Option `%s' is required when using option `%s'.\n"),
422                    "-t", "-P");
423           ret = -1;
424           return;
425         }
426     }
427   else
428     {                           /* ordinary insertion checks */
429       if (NULL != next_id)
430         {
431           fprintf (stderr,
432                    _("Option `%s' makes no sense without option `%s'.\n"),
433                    "-N", "-P");
434           ret = -1;
435           return;
436         }
437       if (NULL != this_id)
438         {
439           fprintf (stderr,
440                    _("Option `%s' makes no sense without option `%s'.\n"),
441                    "-t", "-P");
442           ret = -1;
443           return;
444         }
445     }
446   if ( (args[0] == NULL) &&
447        (uri_string == NULL) )
448     {
449       fprintf (stderr,
450                _("Need the name of a file to publish!\n"));
451       ret = 1;
452       return;
453     }
454   cfg = c;
455   ctx = GNUNET_FS_start (sched,
456                          cfg,
457                          "gnunet-publish",
458                          &progress_cb,
459                          NULL,
460                          GNUNET_FS_FLAGS_NONE,
461                          GNUNET_FS_OPTIONS_END);
462   if (NULL == ctx)
463     {
464       fprintf (stderr,
465                _("Could not initialize `%s' subsystem.\n"),
466                "FS");
467       ret = 1;
468       return;
469     }
470   namespace = NULL;
471   if (NULL != pseudonym)
472     {
473       namespace = GNUNET_FS_namespace_create (ctx,
474                                               pseudonym);
475       if (NULL == namespace)
476         {
477           fprintf (stderr,
478                    _("Could not create namespace `%s'\n"),
479                    pseudonym);
480           GNUNET_FS_stop (ctx);
481           ret = 1;
482           return;
483         }
484     }
485   if (NULL != uri_string)
486     {      
487       emsg = NULL;
488       uri = GNUNET_FS_uri_parse (uri_string,
489                                  &emsg);
490       if (uri == NULL)
491         {
492           fprintf (stderr, 
493                    _("Failed to parse URI: %s\n"),
494                    emsg);
495           GNUNET_free (emsg);
496           GNUNET_FS_stop (ctx);
497           ret = 1;
498           return;         
499         }
500       GNUNET_FS_publish_ksk (ctx,
501                              topKeywords,
502                              meta,
503                              uri,
504                              GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION),
505                              anonymity,
506                              priority,
507                              GNUNET_FS_PUBLISH_OPTION_NONE,
508                              &uri_ksk_continuation,
509                              NULL);
510       return;
511     }
512   l = NULL;
513   if (! disable_extractor)
514     {
515       l = EXTRACTOR_plugin_add_defaults (EXTRACTOR_OPTION_DEFAULT_POLICY);
516       if (GNUNET_OK ==
517           GNUNET_CONFIGURATION_get_value_string (cfg, "FS", "EXTRACTORS",
518                                                  &ex))
519         {
520           if (strlen (ex) > 0)
521             l = EXTRACTOR_plugin_add_config (l, ex, EXTRACTOR_OPTION_DEFAULT_POLICY);
522           GNUNET_free (ex);
523         }
524     }
525   emsg = NULL;
526   if (0 != STAT (args[0], &sbuf))
527     {
528       GNUNET_asprintf (&emsg,
529                        _("Could not access file: %s\n"),
530                        STRERROR (errno));
531       fi = NULL;
532     }
533   else if (S_ISDIR (sbuf.st_mode))
534     {
535       fi = GNUNET_FS_file_information_create_from_directory (ctx,
536                                                              NULL,
537                                                              args[0],
538                                                              &GNUNET_FS_directory_scanner_default,
539                                                              l,
540                                                              !do_insert,
541                                                              anonymity,
542                                                              priority,
543                                                              GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION),
544                                                              &emsg);
545     }
546   else
547     {
548       fi = GNUNET_FS_file_information_create_from_file (ctx,
549                                                         NULL,
550                                                         args[0],
551                                                         NULL,
552                                                         NULL,
553                                                         !do_insert,
554                                                         anonymity,
555                                                         priority,
556                                                         GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION));
557       GNUNET_break (fi != NULL);
558     }
559   EXTRACTOR_plugin_remove_all (l);  
560   if (fi == NULL)
561     {
562       fprintf (stderr,
563                _("Could not publish `%s': %s\n"),
564                args[0],
565                emsg);
566       GNUNET_free (emsg);
567       if (namespace != NULL)
568         GNUNET_FS_namespace_delete (namespace, GNUNET_NO);
569       GNUNET_FS_stop (ctx);
570       ret = 1;
571       return;
572     }
573   GNUNET_FS_file_information_inspect (fi,
574                                       &publish_inspector,
575                                       NULL);
576   if (extract_only)
577     {
578       if (namespace != NULL)
579         GNUNET_FS_namespace_delete (namespace, GNUNET_NO);
580       GNUNET_FS_file_information_destroy (fi, NULL, NULL);
581       GNUNET_FS_stop (ctx);
582       return;
583     }
584   pc = GNUNET_FS_publish_start (ctx,
585                                 fi,
586                                 namespace,
587                                 this_id,
588                                 next_id,
589                                 (do_simulate) 
590                                 ? GNUNET_FS_PUBLISH_OPTION_SIMULATE_ONLY
591                                 : GNUNET_FS_PUBLISH_OPTION_NONE);
592   if (NULL == pc)
593     {
594       fprintf (stderr,
595                _("Could not start publishing.\n"));
596       GNUNET_FS_stop (ctx);
597       ret = 1;
598       return;
599     }
600   kill_task = GNUNET_SCHEDULER_add_delayed (sched,
601                                             GNUNET_TIME_UNIT_FOREVER_REL,
602                                             &do_stop_task,
603                                             NULL);
604 }
605
606
607 /**
608  * gnunet-publish command line options
609  */
610 static struct GNUNET_GETOPT_CommandLineOption options[] = {
611   {'a', "anonymity", "LEVEL",
612    gettext_noop ("set the desired LEVEL of sender-anonymity"),
613    1, &GNUNET_GETOPT_set_uint, &anonymity},
614   {'d', "disable-creation-time", NULL,
615    gettext_noop
616    ("disable adding the creation time to the metadata of the uploaded file"),
617    0, &GNUNET_GETOPT_set_one, &do_disable_creation_time},
618   {'D', "disable-extractor", NULL,
619    gettext_noop
620    ("do not use libextractor to add keywords or metadata"),
621    0, &GNUNET_GETOPT_set_one, &disable_extractor},
622   {'e', "extract", NULL,
623    gettext_noop
624    ("print list of extracted keywords that would be used, but do not perform upload"),
625    0, &GNUNET_GETOPT_set_one, &extract_only},
626   {'k', "key", "KEYWORD",
627    gettext_noop
628    ("add an additional keyword for the top-level file or directory"
629     " (this option can be specified multiple times)"),
630    1, &GNUNET_FS_getopt_set_keywords, &topKeywords},
631   {'m', "meta", "TYPE:VALUE",
632    gettext_noop ("set the meta-data for the given TYPE to the given VALUE"),
633    1, &GNUNET_FS_getopt_set_metadata, &meta},
634   {'n', "noindex", NULL,
635    gettext_noop ("do not index, perform full insertion (stores entire "
636                  "file in encrypted form in GNUnet database)"),
637    0, &GNUNET_GETOPT_set_one, &do_insert},
638   {'N', "next", "ID",
639    gettext_noop
640    ("specify ID of an updated version to be published in the future"
641     " (for namespace insertions only)"),
642    1, &GNUNET_GETOPT_set_string, &next_id},
643   {'p', "priority", "PRIORITY",
644    gettext_noop ("specify the priority of the content"),
645    1, &GNUNET_GETOPT_set_uint, &priority},
646   {'P', "pseudonym", "NAME",
647    gettext_noop
648    ("publish the files under the pseudonym NAME (place file into namespace)"),
649    1, &GNUNET_GETOPT_set_string, &pseudonym},
650   {'s', "simulate-only", NULL,
651    gettext_noop ("only simulate the process but do not do any "
652                  "actual publishing (useful to compute URIs)"),
653    0, &GNUNET_GETOPT_set_one, &do_simulate},
654   {'t', "this", "ID",
655    gettext_noop ("set the ID of this version of the publication"
656                  " (for namespace insertions only)"),
657    1, &GNUNET_GETOPT_set_string, &this_id},
658   {'u', "uri", "URI",
659    gettext_noop ("URI to be published (can be used instead of passing a "
660                  "file to add keywords to the file with the respective URI)"),
661    1, &GNUNET_GETOPT_set_string, &uri_string}, 
662   {'V', "verbose", NULL,
663    gettext_noop ("be verbose (print progress information)"),
664    0, &GNUNET_GETOPT_set_one, &verbose},
665   GNUNET_GETOPT_OPTION_END
666 };
667
668
669 /**
670  * The main function to publish content to GNUnet.
671  *
672  * @param argc number of arguments from the command line
673  * @param argv command line arguments
674  * @return 0 ok, 1 on error
675  */
676 int
677 main (int argc, char *const *argv)
678 {
679   return (GNUNET_OK ==
680           GNUNET_PROGRAM_run (argc,
681                               argv,
682                               "gnunet-publish",
683                               gettext_noop
684                               ("Publish files on GNUnet."),
685                               options, &run, NULL)) ? ret : 1;
686 }
687
688 /* end of gnunet-publish.c */