hxing
[oweals/gnunet.git] / src / fs / fs_publish.c
1 /*
2      This file is part of GNUnet.
3      (C) 2001, 2002, 2003, 2004, 2005, 2006, 2009 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 /**
22  * @file fs/fs_publish.c
23  * @brief publish a file or directory in GNUnet
24  * @see http://gnunet.org/encoding.php3
25  * @author Krista Bennett
26  * @author Christian Grothoff
27  */
28
29 #include "platform.h"
30 #include "gnunet_util_lib.h"
31 #include "gnunet_fs_service.h"
32 #include "fs.h"
33
34 #define DEBUG_PUBLISH GNUNET_YES
35
36
37 /**
38  * Main function that performs the upload.
39  * @param cls "struct GNUNET_FS_PublishContext" identifies the upload
40  * @param tc task context
41  */
42 static void
43 do_upload (void *cls,
44            const struct GNUNET_SCHEDULER_TaskContext *tc)
45 {
46   struct GNUNET_FS_PublishContext *sc = cls;
47
48   sc->upload_task = GNUNET_SCHEDULER_NO_TASK;  
49
50   // FIXME: find next block, process, schedule
51   // transmission to FS service
52 }
53
54
55 /**
56  * Publish a file or directory.
57  *
58  * @param h handle to the file sharing subsystem
59  * @param ctx initial value to use for the '*ctx'
60  *        in the callback (for the GNUNET_FS_STATUS_PUBLISH_START event).
61  * @param fi information about the file or directory structure to publish
62  * @param namespace namespace to publish the file in, NULL for no namespace
63  * @param nid identifier to use for the publishd content in the namespace
64  *        (can be NULL, must be NULL if namespace is NULL)
65  * @param nuid update-identifier that will be used for future updates 
66  *        (can be NULL, must be NULL if namespace or nid is NULL)
67  * @return context that can be used to control the publish operation
68  */
69 struct GNUNET_FS_PublishContext *
70 GNUNET_FS_publish_start (struct GNUNET_FS_Handle *h,
71                          void *ctx,
72                          struct GNUNET_FS_FileInformation *fi,
73                          struct GNUNET_FS_Namespace *namespace,
74                          const char *nid,
75                          const char *nuid)
76 {
77   struct GNUNET_FS_PublishContext *ret;
78
79   ret = GNUNET_malloc (sizeof (struct GNUNET_FS_PublishContext));
80   ret->h = h;
81   ret->client_ctx = ctx;
82   ret->fi = fi;
83   ret->namespace = namespace;
84   if (namespace != NULL)
85     {
86       namespace->rc++;
87       GNUNET_assert (NULL != nid);
88       ret->nid = GNUNET_strdup (nid);
89       if (NULL != nuid)
90         ret->nuid = GNUNET_strdup (nuid);
91     }
92   // FIXME: make upload persistent!
93   ret->upload_task 
94     = GNUNET_SCHEDULER_add_delayed (h->sched,
95                                     GNUNET_NO,
96                                     GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
97                                     GNUNET_SCHEDULER_NO_TASK,
98                                     GNUNET_TIME_UNIT_ZERO,
99                                     &do_upload,
100                                     ret);
101   return ret;
102 }
103
104
105 /**
106  * Stop an upload.  Will abort incomplete uploads (but 
107  * not remove blocks that have already been publishd) or
108  * simply clean up the state for completed uploads.
109  *
110  * @param sc context for the upload to stop
111  */
112 void 
113 GNUNET_FS_publish_stop (struct GNUNET_FS_PublishContext *sc)
114 {
115   if (GNUNET_SCHEDULER_NO_TASK != sc->upload_task)
116     GNUNET_SCHEDULER_cancel (sc->h->sched, sc->upload_task);
117   // FIXME: remove from persistence DB (?) --- think more about
118   //        shutdown / persistent-resume APIs!!!
119   GNUNET_FS_file_information_destroy (sc->fi, NULL, NULL);
120   GNUNET_FS_namespace_delete (sc->namespace, GNUNET_NO);
121   GNUNET_free_non_null (sc->nid);  
122   GNUNET_free_non_null (sc->nuid);
123   GNUNET_free (sc);
124 }
125
126
127 #if 0
128
129 /**
130  * Append the given key and query to the iblock[level].  If
131  * iblock[level] is already full, compute its chk and push it to
132  * level+1 and clear the level.  iblocks is guaranteed to be big
133  * enough.
134  */
135 static int
136 pushBlock (struct GNUNET_ClientServerConnection *sock,
137            const GNUNET_EC_ContentHashKey * chk,
138            unsigned int level,
139            GNUNET_DatastoreValue ** iblocks,
140            unsigned int prio, GNUNET_CronTime expirationTime)
141 {
142   unsigned int size;
143   unsigned int present;
144   GNUNET_DatastoreValue *value;
145   GNUNET_EC_DBlock *db;
146   GNUNET_EC_ContentHashKey ichk;
147
148   size = ntohl (iblocks[level]->size);
149   GNUNET_GE_ASSERT (NULL, size > sizeof (GNUNET_DatastoreValue));
150   size -= sizeof (GNUNET_DatastoreValue);
151   GNUNET_GE_ASSERT (NULL,
152                     size - sizeof (GNUNET_EC_DBlock) <=
153                     GNUNET_ECRS_IBLOCK_SIZE);
154   present =
155     (size - sizeof (GNUNET_EC_DBlock)) / sizeof (GNUNET_EC_ContentHashKey);
156   db = (GNUNET_EC_DBlock *) & iblocks[level][1];
157   if (present == GNUNET_ECRS_CHK_PER_INODE)
158     {
159       GNUNET_EC_file_block_get_key (db, size, &ichk.key);
160       GNUNET_EC_file_block_get_query (db, size, &ichk.query);
161       if (GNUNET_OK != pushBlock (sock,
162                                   &ichk, level + 1, iblocks, prio,
163                                   expirationTime))
164         return GNUNET_SYSERR;
165       GNUNET_EC_file_block_encode (db, size, &ichk.query, &value);
166       if (value == NULL)
167         {
168           GNUNET_GE_BREAK (NULL, 0);
169           return GNUNET_SYSERR;
170         }
171       value->priority = htonl (prio);
172       value->expiration_time = GNUNET_htonll (expirationTime);
173       if (GNUNET_OK != GNUNET_FS_insert (sock, value))
174         {
175           GNUNET_free (value);
176           return GNUNET_SYSERR;
177         }
178       GNUNET_free (value);
179       size = sizeof (GNUNET_EC_DBlock); /* type */
180     }
181   /* append GNUNET_EC_ContentHashKey */
182   memcpy (&((char *) db)[size], chk, sizeof (GNUNET_EC_ContentHashKey));
183   size += sizeof (GNUNET_EC_ContentHashKey) + sizeof (GNUNET_DatastoreValue);
184   GNUNET_GE_ASSERT (NULL, size < GNUNET_MAX_BUFFER_SIZE);
185   iblocks[level]->size = htonl (size);
186
187   return GNUNET_OK;
188 }
189
190 /**
191  * Index or insert a file.
192  *
193  * @param priority what is the priority for OUR node to
194  *   keep this file available?  Use 0 for maximum anonymity and
195  *   minimum reliability...
196  * @param doIndex GNUNET_YES for index, GNUNET_NO for insertion,
197  *         GNUNET_SYSERR for simulation
198  * @param uri set to the URI of the uploaded file
199  * @return GNUNET_SYSERR if the upload failed (i.e. not enough space
200  *  or gnunetd not running)
201  */
202 int
203 GNUNET_ECRS_file_upload (struct GNUNET_GE_Context *ectx,
204                          struct GNUNET_GC_Configuration *cfg,
205                          const char *filename,
206                          int doIndex,
207                          unsigned int anonymityLevel,
208                          unsigned int priority,
209                          GNUNET_CronTime expirationTime,
210                          GNUNET_ECRS_UploadProgressCallback upcb,
211                          void *upcbClosure,
212                          GNUNET_ECRS_TestTerminate tt,
213                          void *ttClosure, struct GNUNET_ECRS_URI **uri)
214 {
215   unsigned long long filesize;
216   unsigned long long pos;
217   unsigned int treedepth;
218   int fd;
219   int i;
220   int ret;
221   unsigned int size;
222   GNUNET_DatastoreValue **iblocks;
223   GNUNET_DatastoreValue *dblock;
224   GNUNET_EC_DBlock *db;
225   GNUNET_DatastoreValue *value;
226   struct GNUNET_ClientServerConnection *sock;
227   GNUNET_HashCode fileId;
228   GNUNET_EC_ContentHashKey mchk;
229   GNUNET_CronTime eta;
230   GNUNET_CronTime start;
231   GNUNET_CronTime now;
232   GNUNET_EC_FileIdentifier fid;
233 #if DEBUG_UPLOAD
234   GNUNET_EncName enc;
235 #endif
236
237   GNUNET_GE_ASSERT (ectx, cfg != NULL);
238   start = GNUNET_get_time ();
239   memset (&mchk, 0, sizeof (GNUNET_EC_ContentHashKey));
240   if (GNUNET_YES != GNUNET_disk_file_test (ectx, filename))
241     {
242       GNUNET_GE_LOG (ectx,
243                      GNUNET_GE_ERROR | GNUNET_GE_BULK | GNUNET_GE_USER,
244                      _("`%s' is not a file.\n"), filename);
245       return GNUNET_SYSERR;
246     }
247   if (GNUNET_OK !=
248       GNUNET_disk_file_size (ectx, filename, &filesize, GNUNET_YES))
249     {
250       GNUNET_GE_LOG (ectx,
251                      GNUNET_GE_ERROR | GNUNET_GE_BULK | GNUNET_GE_USER,
252                      _("Cannot get size of file `%s'"), filename);
253
254       return GNUNET_SYSERR;
255     }
256   sock = GNUNET_client_connection_create (ectx, cfg);
257   if (sock == NULL)
258     {
259       GNUNET_GE_LOG (ectx,
260                      GNUNET_GE_ERROR | GNUNET_GE_BULK | GNUNET_GE_USER,
261                      _("Failed to connect to gnunetd."));
262       return GNUNET_SYSERR;
263     }
264   eta = 0;
265   if (upcb != NULL)
266     upcb (filesize, 0, eta, upcbClosure);
267   if (doIndex == GNUNET_YES)
268     {
269       if (GNUNET_SYSERR == GNUNET_hash_file (ectx, filename, &fileId))
270         {
271           GNUNET_GE_LOG (ectx,
272                          GNUNET_GE_ERROR | GNUNET_GE_BULK | GNUNET_GE_USER,
273                          _("Cannot hash `%s'.\n"), filename);
274
275           GNUNET_client_connection_destroy (sock);
276           return GNUNET_SYSERR;
277         }
278       if (GNUNET_YES == GNUNET_FS_test_indexed (sock, &fileId))
279         {
280           /* file already indexed; simulate only to get the URI! */
281           doIndex = GNUNET_SYSERR;
282         }
283     }
284   if (doIndex == GNUNET_YES)
285     {
286       now = GNUNET_get_time ();
287       eta = now + 2 * (now - start);
288       /* very rough estimate: GNUNET_hash reads once through the file,
289          we'll do that once more and write it.  But of course
290          the second read may be cached, and we have the encryption,
291          so a factor of two is really, really just a rough estimate */
292       start = now;
293       /* reset the counter since the formula later does not
294          take the time for GNUNET_hash_file into account */
295
296       switch (GNUNET_FS_prepare_to_index (sock, &fileId, filename))
297         {
298         case GNUNET_SYSERR:
299           GNUNET_GE_LOG (ectx,
300                          GNUNET_GE_ERROR | GNUNET_GE_BULK | GNUNET_GE_USER,
301                          _("Initialization for indexing file `%s' failed.\n"),
302                          filename);
303           GNUNET_client_connection_destroy (sock);
304           return GNUNET_SYSERR;
305         case GNUNET_NO:
306           GNUNET_GE_LOG (ectx,
307                          GNUNET_GE_ERROR | GNUNET_GE_BULK | GNUNET_GE_USER,
308                          _
309                          ("Indexing file `%s' failed. Suggestion: try to insert the file.\n"),
310                          filename);
311           GNUNET_client_connection_destroy (sock);
312           return GNUNET_SYSERR;
313         default:
314           break;
315         }
316     }
317   treedepth = GNUNET_ECRS_compute_depth (filesize);
318   fd = GNUNET_disk_file_open (ectx, filename, O_RDONLY | O_LARGEFILE);
319   if (fd == -1)
320     {
321       GNUNET_GE_LOG (ectx,
322                      GNUNET_GE_ERROR | GNUNET_GE_BULK | GNUNET_GE_USER,
323                      _("Cannot open file `%s': `%s'"), filename,
324                      STRERROR (errno));
325
326       GNUNET_client_connection_destroy (sock);
327       return GNUNET_SYSERR;
328     }
329
330   dblock =
331     GNUNET_malloc (sizeof (GNUNET_DatastoreValue) + GNUNET_ECRS_DBLOCK_SIZE +
332                    sizeof (GNUNET_EC_DBlock));
333   dblock->size =
334     htonl (sizeof (GNUNET_DatastoreValue) + GNUNET_ECRS_DBLOCK_SIZE +
335            sizeof (GNUNET_EC_DBlock));
336   dblock->anonymity_level = htonl (anonymityLevel);
337   dblock->priority = htonl (priority);
338   dblock->type = htonl (GNUNET_ECRS_BLOCKTYPE_DATA);
339   dblock->expiration_time = GNUNET_htonll (expirationTime);
340   db = (GNUNET_EC_DBlock *) & dblock[1];
341   db->type = htonl (GNUNET_ECRS_BLOCKTYPE_DATA);
342   iblocks =
343     GNUNET_malloc (sizeof (GNUNET_DatastoreValue *) * (treedepth + 1));
344   for (i = 0; i <= treedepth; i++)
345     {
346       iblocks[i] =
347         GNUNET_malloc (sizeof (GNUNET_DatastoreValue) +
348                        GNUNET_ECRS_IBLOCK_SIZE + sizeof (GNUNET_EC_DBlock));
349       iblocks[i]->size =
350         htonl (sizeof (GNUNET_DatastoreValue) + sizeof (GNUNET_EC_DBlock));
351       iblocks[i]->anonymity_level = htonl (anonymityLevel);
352       iblocks[i]->priority = htonl (priority);
353       iblocks[i]->type = htonl (GNUNET_ECRS_BLOCKTYPE_DATA);
354       iblocks[i]->expiration_time = GNUNET_htonll (expirationTime);
355       ((GNUNET_EC_DBlock *) & iblocks[i][1])->type =
356         htonl (GNUNET_ECRS_BLOCKTYPE_DATA);
357     }
358
359   pos = 0;
360   while (pos < filesize)
361     {
362       if (upcb != NULL)
363         upcb (filesize, pos, eta, upcbClosure);
364       if (tt != NULL)
365         if (GNUNET_OK != tt (ttClosure))
366           goto FAILURE;
367       size = GNUNET_ECRS_DBLOCK_SIZE;
368       if (size > filesize - pos)
369         {
370           size = filesize - pos;
371           memset (&db[1], 0, GNUNET_ECRS_DBLOCK_SIZE);
372         }
373       GNUNET_GE_ASSERT (ectx,
374                         sizeof (GNUNET_DatastoreValue) + size +
375                         sizeof (GNUNET_EC_DBlock) < GNUNET_MAX_BUFFER_SIZE);
376       dblock->size =
377         htonl (sizeof (GNUNET_DatastoreValue) + size +
378                sizeof (GNUNET_EC_DBlock));
379       if (size != READ (fd, &db[1], size))
380         {
381           GNUNET_GE_LOG_STRERROR_FILE (ectx,
382                                        GNUNET_GE_ERROR | GNUNET_GE_BULK |
383                                        GNUNET_GE_ADMIN | GNUNET_GE_USER,
384                                        "READ", filename);
385           goto FAILURE;
386         }
387       if (tt != NULL)
388         if (GNUNET_OK != tt (ttClosure))
389           goto FAILURE;
390       GNUNET_EC_file_block_get_key (db, size + sizeof (GNUNET_EC_DBlock),
391                                     &mchk.key);
392       GNUNET_EC_file_block_get_query (db, size + sizeof (GNUNET_EC_DBlock),
393                                       &mchk.query);
394 #if DEBUG_UPLOAD
395       GNUNET_hash_to_enc (&mchk.query, &enc);
396       fprintf (stderr,
397                "Query for current block of size %u is `%s'\n", size,
398                (const char *) &enc);
399 #endif
400       if (doIndex == GNUNET_YES)
401         {
402           if (GNUNET_SYSERR == GNUNET_FS_index (sock, &fileId, dblock, pos))
403             {
404               GNUNET_GE_LOG (ectx,
405                              GNUNET_GE_ERROR | GNUNET_GE_BULK |
406                              GNUNET_GE_USER,
407                              _
408                              ("Indexing data of file `%s' failed at position %llu.\n"),
409                              filename, pos);
410               goto FAILURE;
411             }
412         }
413       else
414         {
415           value = NULL;
416           if (GNUNET_OK !=
417               GNUNET_EC_file_block_encode (db,
418                                            size + sizeof (GNUNET_EC_DBlock),
419                                            &mchk.query, &value))
420             {
421               GNUNET_GE_BREAK (ectx, 0);
422               goto FAILURE;
423             }
424           GNUNET_GE_ASSERT (ectx, value != NULL);
425           *value = *dblock;     /* copy options! */
426           if ((doIndex == GNUNET_NO) &&
427               (GNUNET_OK != (ret = GNUNET_FS_insert (sock, value))))
428             {
429               GNUNET_GE_BREAK (ectx, ret == GNUNET_NO);
430               GNUNET_free (value);
431               goto FAILURE;
432             }
433           GNUNET_free (value);
434         }
435       pos += size;
436       now = GNUNET_get_time ();
437       if (pos > 0)
438         {
439           eta = (GNUNET_CronTime) (start +
440                                    (((double) (now - start) / (double) pos))
441                                    * (double) filesize);
442         }
443       if (GNUNET_OK != pushBlock (sock, &mchk, 0,       /* dblocks are on level 0 */
444                                   iblocks, priority, expirationTime))
445         goto FAILURE;
446     }
447   if (tt != NULL)
448     if (GNUNET_OK != tt (ttClosure))
449       goto FAILURE;
450 #if DEBUG_UPLOAD
451   GNUNET_GE_LOG (ectx,
452                  GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
453                  "Tree depth is %u, walking up tree.\n", treedepth);
454 #endif
455   for (i = 0; i < treedepth; i++)
456     {
457       size = ntohl (iblocks[i]->size) - sizeof (GNUNET_DatastoreValue);
458       GNUNET_GE_ASSERT (ectx, size < GNUNET_MAX_BUFFER_SIZE);
459       if (size == sizeof (GNUNET_EC_DBlock))
460         {
461 #if DEBUG_UPLOAD
462           GNUNET_GE_LOG (ectx,
463                          GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
464                          "Level %u is empty\n", i);
465 #endif
466           continue;
467         }
468       db = (GNUNET_EC_DBlock *) & iblocks[i][1];
469       GNUNET_EC_file_block_get_key (db, size, &mchk.key);
470 #if DEBUG_UPLOAD
471       GNUNET_GE_LOG (ectx,
472                      GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
473                      "Computing query for %u bytes content.\n", size);
474 #endif
475       GNUNET_EC_file_block_get_query (db, size, &mchk.query);
476 #if DEBUG_UPLOAD
477       IF_GELOG (ectx,
478                 GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
479                 GNUNET_hash_to_enc (&mchk.query, &enc));
480       GNUNET_GE_LOG (ectx,
481                      GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
482                      "Query for current block at level %u is `%s'.\n", i,
483                      &enc);
484 #endif
485       if (GNUNET_OK != pushBlock (sock,
486                                   &mchk, i + 1, iblocks, priority,
487                                   expirationTime))
488         {
489           GNUNET_GE_BREAK (ectx, 0);
490           goto FAILURE;
491         }
492       GNUNET_EC_file_block_encode (db, size, &mchk.query, &value);
493       if (value == NULL)
494         {
495           GNUNET_GE_BREAK (ectx, 0);
496           goto FAILURE;
497         }
498       value->expiration_time = GNUNET_htonll (expirationTime);
499       value->priority = htonl (priority);
500       if ((doIndex != GNUNET_SYSERR) &&
501           (GNUNET_SYSERR == GNUNET_FS_insert (sock, value)))
502         {
503           GNUNET_GE_BREAK (ectx, 0);
504           GNUNET_free (value);
505           goto FAILURE;
506         }
507       GNUNET_free (value);
508       GNUNET_free (iblocks[i]);
509       iblocks[i] = NULL;
510     }
511 #if DEBUG_UPLOAD
512   IF_GELOG (ectx,
513             GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
514             GNUNET_hash_to_enc (&mchk.query, &enc));
515   GNUNET_GE_LOG (ectx, GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
516                  "Query for top block is %s\n", &enc);
517 #endif
518   /* build URI */
519   fid.file_length = GNUNET_htonll (filesize);
520   db = (GNUNET_EC_DBlock *) & iblocks[treedepth][1];
521
522   fid.chk = *(GNUNET_EC_ContentHashKey *) & (db[1]);
523   *uri = GNUNET_malloc (sizeof (URI));
524   (*uri)->type = chk;
525   (*uri)->data.fi = fid;
526
527   /* free resources */
528   GNUNET_free_non_null (iblocks[treedepth]);
529   GNUNET_free (iblocks);
530   GNUNET_free (dblock);
531   if (upcb != NULL)
532     upcb (filesize, filesize, eta, upcbClosure);
533   CLOSE (fd);
534   GNUNET_client_connection_destroy (sock);
535   return GNUNET_OK;
536 FAILURE:
537   for (i = 0; i <= treedepth; i++)
538     GNUNET_free_non_null (iblocks[i]);
539   GNUNET_free (iblocks);
540   GNUNET_free (dblock);
541   CLOSE (fd);
542   GNUNET_client_connection_destroy (sock);
543   return GNUNET_SYSERR;
544 }
545
546 #endif 
547
548 /* end of fs_publish.c */