stuff
[oweals/gnunet.git] / src / fs / fs_publish.c
1 /*
2      This file is part of GNUnet.
3      (C) 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  * TODO:
29  * - KBlocks
30  * - SBlocks
31  * - indexing support
32  * - calling of progress function
33  * - handling of IO errors (emsg)
34  * - code-sharing with unindex
35  * - datastore reservation support
36  * - persistence support
37  */
38
39 #include "platform.h"
40 #include "gnunet_constants.h"
41 #include "gnunet_util_lib.h"
42 #include "gnunet_fs_service.h"
43 #include "fs.h"
44
45 #define DEBUG_PUBLISH GNUNET_YES
46
47 /**
48  * Main function that performs the upload.
49  * @param cls "struct GNUNET_FS_PublishContext" identifies the upload
50  * @param tc task context
51  */
52 static void
53 do_upload (void *cls,
54            const struct GNUNET_SCHEDULER_TaskContext *tc);
55
56
57 /**
58  * Context for "ds_put_cont".
59  */
60 struct PutContCtx
61 {
62   /**
63    * Publishing context for which the datastore
64    * PUT request was executed.
65    */
66   struct GNUNET_FS_PublishContext *sc;
67
68   /**
69    * Specific file with the block.
70    */
71   struct GNUNET_FS_FileInformation *p;
72
73   /**
74    * Function to run next, if any (can be NULL).
75    */
76   GNUNET_SCHEDULER_Task cont;
77 };
78
79 /**
80  * Function called by the datastore API with
81  * the result from the PUT request.
82  *
83  * @param cls our closure
84  * @param success GNUNET_OK on success
85  * @param msg error message (or NULL)
86  */
87 static void
88 ds_put_cont (void *cls,
89              int success,
90              const char *msg)
91 {
92   struct PutContCtx *pcc = cls;
93
94   if (GNUNET_OK != success)
95     {
96       // FIXME: call progress CB with error
97       // FIXME: update pcc->p to indicate abort
98       GNUNET_FS_file_information_sync (pcc->p);
99       return;
100     }
101   GNUNET_FS_file_information_sync (pcc->p);
102   if (NULL != pcc->cont)
103     pcc->sc->upload_task 
104       = GNUNET_SCHEDULER_add_delayed (pcc->sc->h->sched,
105                                       GNUNET_NO,
106                                       GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
107                                       GNUNET_SCHEDULER_NO_TASK,
108                                       GNUNET_TIME_UNIT_ZERO,
109                                       pcc->cont,
110                                       pcc->sc);
111   GNUNET_free (pcc);
112 }
113
114
115 /**
116  * We need to publish a specific block.  Do it.  Then continue with
117  * the main task.
118  *
119  * @param sc overall upload data
120  * @param p file that the block belongs to (needed for options!)
121  * @param query what the block should be indexed under
122  * @param blk encoded block to publish
123  * @param blk_size size of the block
124  * @param blk_type type of the block
125  * @param cont function to run when done
126  */
127 static void
128 publish_block (struct GNUNET_FS_PublishContext *sc,
129                struct GNUNET_FS_FileInformation *p,
130                const GNUNET_HashCode *query,
131                const void* blk,
132                uint16_t blk_size,
133                uint32_t blk_type,
134                GNUNET_SCHEDULER_Task cont)
135 {
136   struct PutContCtx * dpc_cls;
137
138   dpc_cls = GNUNET_malloc(sizeof(struct PutContCtx));
139   dpc_cls->cont = cont;
140   dpc_cls->sc = sc;
141   dpc_cls->p = p;
142   // FIXME: need to do something to "sc" to mark
143   // that "sc" can not be freed right now due to this
144   // pending, scheduled operation for which we don't have
145   // a task ID!  
146   GNUNET_DATASTORE_put (sc->dsh,
147                         sc->rid,
148                         query,
149                         blk_size,
150                         blk,
151                         blk_type,
152                         p->priority,
153                         p->anonymity,
154                         p->expirationTime,
155                         GNUNET_CONSTANTS_SERVICE_TIMEOUT,
156                         &ds_put_cont,
157                         dpc_cls);
158 }
159
160
161 /**
162  * We are almost done publishing the structure,
163  * add SBlocks (if needed).
164  *
165  * @param sc overall upload data
166  */
167 static void
168 publish_sblock (struct GNUNET_FS_PublishContext *sc)
169 {
170   struct GNUNET_FS_FileInformation *p;
171   p = sc->fi;
172
173   // FIXME: build sblock & call publish_block!
174   
175   // FIXME: continuation should
176   // be releasing the datastore reserve
177   // (once implemented)
178 }
179
180
181 /**
182  * We have uploaded a file or directory; now publish
183  * the KBlocks in the global keyword space so that
184  * it can be found.  Then continue with the
185  * main task.
186  *
187  * @param sc overall upload data
188  * @param p specific file or directory for which kblocks
189  *          should be created
190  */
191 static void
192 publish_kblocks (struct GNUNET_FS_PublishContext *sc,
193                  struct GNUNET_FS_FileInformation *p)
194 {
195   // FIXME: build all kblocks
196   // call publish_kblock on each
197   // last continuation should then call the main continuation again
198 }
199
200
201 /**
202  * Compute the depth of the CHK tree.
203  *
204  * @param flen file length for which to compute the depth
205  * @return depth of the tree
206  */
207 static unsigned int
208 compute_depth (uint64_t flen)
209 {
210   unsigned int treeDepth;
211   uint64_t fl;
212
213   treeDepth = 1;
214   fl = GNUNET_FS_DBLOCK_SIZE;
215   while (fl < flen)
216     {
217       treeDepth++;
218       if (fl * GNUNET_FS_CHK_PER_INODE < fl)
219         {
220           /* integer overflow, this is a HUGE file... */
221           return treeDepth;
222         }
223       fl = fl * GNUNET_FS_CHK_PER_INODE;
224     }
225   return treeDepth;
226 }
227
228
229 /**
230  * Compute the size of the current IBlock.
231  *
232  * @param height height of the IBlock in the tree (aka overall
233  *               number of tree levels minus depth); 0 == DBlock
234  * @param offset current offset in the overall file
235  * @return size of the corresponding IBlock
236  */
237 static uint16_t 
238 compute_iblock_size (unsigned int height,
239                      uint64_t offset)
240 {
241   unsigned int ret;
242   unsigned int i;
243   uint64_t mod;
244   uint64_t bds;
245
246   GNUNET_assert (height > 0);
247   bds = GNUNET_FS_DBLOCK_SIZE; /* number of bytes each CHK at level "i"
248                                   corresponds to */
249   for (i=0;i<height;i++)
250     bds *= GNUNET_FS_CHK_PER_INODE;
251   mod = offset % bds;
252   if (0 == mod)
253     {
254       /* we were triggered at the end of a full block */
255       ret = GNUNET_FS_CHK_PER_INODE;
256     }
257   else
258     {
259       /* we were triggered at the end of the file */
260       bds /= GNUNET_FS_CHK_PER_INODE;
261       ret = mod / bds;
262       if (0 != mod % bds)
263         ret++; 
264     }
265   return (uint16_t) (ret * sizeof(struct ContentHashKey));
266 }
267
268
269 /**
270  * Compute the offset of the CHK for the
271  * current block in the IBlock above.
272  *
273  * @param height height of the IBlock in the tree (aka overall
274  *               number of tree levels minus depth); 0 == DBlock
275  * @param offset current offset in the overall file
276  * @return (array of CHKs') offset in the above IBlock
277  */
278 static unsigned int
279 compute_chk_offset (unsigned int height,
280                     uint64_t offset)
281 {
282   uint64_t bds;
283   unsigned int ret;
284   unsigned int i;
285
286   bds = GNUNET_FS_DBLOCK_SIZE; /* number of bytes each CHK at level "i"
287                                   corresponds to */
288   for (i=0;i<height;i++)
289     bds *= GNUNET_FS_CHK_PER_INODE;
290   GNUNET_assert (0 == (offset % bds));
291   ret = offset / bds;
292   return ret % GNUNET_FS_CHK_PER_INODE; 
293 }
294
295
296 /**
297  * We are uploading a file or directory; load (if necessary) the next
298  * block into memory, encrypt it and send it to the FS service.  Then
299  * continue with the main task.
300  *
301  * @param sc overall upload data
302  * @param p specific file or directory for which kblocks
303  *          should be created
304  */
305 static void
306 publish_content (struct GNUNET_FS_PublishContext *sc,
307                  struct GNUNET_FS_FileInformation *p)
308 {
309   struct ContentHashKey *mychk;
310   const void *pt_block;
311   uint16_t pt_size;
312   char *emsg;
313   char iob[GNUNET_FS_DBLOCK_SIZE];
314   char enc[GNUNET_FS_DBLOCK_SIZE];
315   struct GNUNET_CRYPTO_AesSessionKey sk;
316   struct GNUNET_CRYPTO_AesInitializationVector iv;
317   uint64_t size;
318   unsigned int off;
319   struct GNUNET_FS_DirectoryBuilder *db;
320   struct GNUNET_FS_FileInformation *dirpos;
321   void *raw_data;
322   char *dd;
323
324   // FIXME: figure out how to share this code
325   // with unindex!
326   size = (p->is_directory) ? p->data.dir.dir_size : p->data.file.file_size;
327   if (NULL == p->chk_tree)
328     {
329       if (p->is_directory)
330         {
331           db = GNUNET_FS_directory_builder_create (p->meta);
332           dirpos = p->data.dir.entries;
333           while (NULL != dirpos)
334             {
335               if (dirpos->is_directory)
336                 {
337                   raw_data = dirpos->data.dir.dir_data;
338                   dirpos->data.dir.dir_data = NULL;
339                 }
340               else
341                 {
342                   raw_data = NULL;
343                   if ( (dirpos->data.file.file_size < GNUNET_FS_MAX_INLINE_SIZE) &&
344                        (dirpos->data.file.file_size > 0) )
345                     {
346                       raw_data = GNUNET_malloc (dirpos->data.file.file_size);
347                       emsg = NULL;
348                       if (dirpos->data.file.file_size !=
349                           dirpos->data.file.reader (dirpos->data.file.reader_cls,
350                                                     0,
351                                                     dirpos->data.file.file_size,
352                                                     raw_data,
353                                                     &emsg))
354                         {
355                           GNUNET_free_non_null (emsg);
356                           GNUNET_free (raw_data);
357                           raw_data = NULL;
358                         } 
359                     }
360                 }
361               GNUNET_FS_directory_builder_add (db,
362                                                dirpos->chk_uri,
363                                                dirpos->meta,
364                                                raw_data);
365               GNUNET_free_non_null (raw_data);
366               dirpos = dirpos->next;
367             }
368           GNUNET_FS_directory_builder_finish (db,
369                                               &p->data.dir.dir_size,
370                                               &p->data.dir.dir_data);
371           size = p->data.dir.dir_size;
372         }
373       p->chk_tree_depth = compute_depth (size);
374       p->chk_tree = GNUNET_malloc (p->chk_tree_depth * 
375                                    sizeof (struct ContentHashKey) *
376                                    GNUNET_FS_CHK_PER_INODE);
377       p->current_depth = p->chk_tree_depth;
378     }
379   if (p->current_depth == p->chk_tree_depth)
380     {
381       if (p->is_directory)
382         {
383           pt_size = GNUNET_MIN(GNUNET_FS_DBLOCK_SIZE,
384                                p->data.dir.dir_size - p->publish_offset);
385           dd = p->data.dir.dir_data;
386           pt_block = &dd[p->publish_offset];
387         }
388       else
389         {
390           pt_size = GNUNET_MIN(GNUNET_FS_DBLOCK_SIZE,
391                                p->data.file.file_size - p->publish_offset);
392           emsg = NULL;
393           if (pt_size !=
394               p->data.file.reader (p->data.file.reader_cls,
395                                    p->publish_offset,
396                                    pt_size,
397                                    iob,
398                                    &emsg))
399             {
400               // FIXME: abort with error "emsg"
401               GNUNET_free (emsg);
402             }
403           pt_block = iob;
404         }
405     }
406   else
407     {
408       pt_size = compute_iblock_size (p->chk_tree_depth - p->current_depth,
409                                      p->publish_offset); 
410       pt_block = &p->chk_tree[p->current_depth *
411                               GNUNET_FS_CHK_PER_INODE];
412     }
413   off = compute_chk_offset (p->chk_tree_depth - p->current_depth,
414                             p->publish_offset);
415   mychk = &p->chk_tree[(p->current_depth-1)*GNUNET_FS_CHK_PER_INODE+off];
416   GNUNET_CRYPTO_hash (pt_block, pt_size, &mychk->key);
417   GNUNET_CRYPTO_hash_to_aes_key (&mychk->key, &sk, &iv);
418   GNUNET_CRYPTO_aes_encrypt (pt_block,
419                              pt_size,
420                              &sk,
421                              &iv,
422                              enc);
423   // NOTE: this call (and progress below) is all that really differs
424   // between publish/unindex!  Parameterize & move this code!
425   // FIXME: something around here would need to change
426   // for indexing!
427   publish_block (sc, p, 
428                  &mychk->query,
429                  enc, 
430                  pt_size, 
431                  (p->current_depth == p->chk_tree_depth) 
432                  ? GNUNET_DATASTORE_BLOCKTYPE_DBLOCK 
433                  : GNUNET_DATASTORE_BLOCKTYPE_IBLOCK,
434                  &do_upload);
435   // FIXME: should call progress function somewhere here!
436   GNUNET_CRYPTO_hash (enc, pt_size, &mychk->query);
437   if (p->current_depth == p->chk_tree_depth) 
438     { 
439       p->publish_offset += pt_size;
440       if ( (p->publish_offset == size) ||
441            (0 == p->publish_offset % (GNUNET_FS_CHK_PER_INODE * GNUNET_FS_DBLOCK_SIZE) ) )
442         p->current_depth--;
443     }
444   else
445     {
446       if ( (off == GNUNET_FS_CHK_PER_INODE) ||
447            (p->publish_offset == size) )
448         p->current_depth--;
449       else
450         p->current_depth = p->chk_tree_depth;
451     }
452   if (0 == p->current_depth)
453     {
454       p->chk_uri = GNUNET_malloc (sizeof(struct GNUNET_FS_Uri));
455       p->chk_uri->type = chk;
456       p->chk_uri->data.chk.chk = p->chk_tree[0];
457       p->chk_uri->data.chk.file_length = size;
458       GNUNET_free (p->chk_tree);
459       p->chk_tree = NULL;
460     }
461 }
462
463
464 /**
465  * Main function that performs the upload.
466  * @param cls "struct GNUNET_FS_PublishContext" identifies the upload
467  * @param tc task context
468  */
469 static void
470 do_upload (void *cls,
471            const struct GNUNET_SCHEDULER_TaskContext *tc)
472 {
473   struct GNUNET_FS_PublishContext *sc = cls;
474   struct GNUNET_FS_FileInformation *p;
475
476   sc->upload_task = GNUNET_SCHEDULER_NO_TASK;  
477   p = sc->fi_pos;
478   if (NULL == p)
479     {
480       /* upload of entire hierarchy complete,
481          publish namespace entries */
482       publish_sblock (sc);
483       return;
484     }
485   if (NULL != p->chk_uri)
486     {
487       /* move on to next file */
488       if (NULL != p->next)
489         sc->fi_pos = p->next;
490       else
491         sc->fi_pos = p->dir;
492       /* upload of "p" complete, publish KBlocks! */
493       publish_kblocks (sc, p);
494       return;
495     }
496   if ( (!p->is_directory) &&
497        (p->data.file.do_index) )
498     {
499       // FIXME: need to pre-compute hash over
500       // the entire file and ask FS to prepare
501       // for indexing!
502       return;
503     }
504   publish_content (sc, p);
505 }
506
507
508 /**
509  * Publish a file or directory.
510  *
511  * @param h handle to the file sharing subsystem
512  * @param ctx initial value to use for the '*ctx'
513  *        in the callback (for the GNUNET_FS_STATUS_PUBLISH_START event).
514  * @param fi information about the file or directory structure to publish
515  * @param namespace namespace to publish the file in, NULL for no namespace
516  * @param nid identifier to use for the publishd content in the namespace
517  *        (can be NULL, must be NULL if namespace is NULL)
518  * @param nuid update-identifier that will be used for future updates 
519  *        (can be NULL, must be NULL if namespace or nid is NULL)
520  * @return context that can be used to control the publish operation
521  */
522 struct GNUNET_FS_PublishContext *
523 GNUNET_FS_publish_start (struct GNUNET_FS_Handle *h,
524                          void *ctx,
525                          struct GNUNET_FS_FileInformation *fi,
526                          struct GNUNET_FS_Namespace *namespace,
527                          const char *nid,
528                          const char *nuid)
529 {
530   struct GNUNET_FS_PublishContext *ret;
531   struct GNUNET_FS_FileInformation *p;
532   struct GNUNET_DATASTORE_Handle *dsh;
533
534   dsh = GNUNET_DATASTORE_connect (h->cfg,
535                                   h->sched);
536   if (NULL == dsh)
537     return NULL;
538   ret = GNUNET_malloc (sizeof (struct GNUNET_FS_PublishContext));
539   ret->dsh = dsh;
540   ret->h = h;
541   ret->client_ctx = ctx;
542   ret->fi = fi;
543   ret->namespace = namespace;
544   if (namespace != NULL)
545     {
546       namespace->rc++;
547       GNUNET_assert (NULL != nid);
548       ret->nid = GNUNET_strdup (nid);
549       if (NULL != nuid)
550         ret->nuid = GNUNET_strdup (nuid);
551     }
552   // FIXME: make upload persistent!
553
554   /* find first leaf, DFS */
555   p = ret->fi;
556   while ( (p->is_directory) &&
557           (NULL != p->data.dir.entries) )
558     p = p->data.dir.entries;          
559   ret->fi_pos = p;
560
561   // FIXME: calculate space needed for "fi"
562   // and reserve as first task (then trigger
563   // "do_upload" from that continuation)!
564   ret->upload_task 
565     = GNUNET_SCHEDULER_add_delayed (h->sched,
566                                     GNUNET_NO,
567                                     GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
568                                     GNUNET_SCHEDULER_NO_TASK,
569                                     GNUNET_TIME_UNIT_ZERO,
570                                     &do_upload,
571                                     ret);
572   return ret;
573 }
574
575
576 /**
577  * Stop an upload.  Will abort incomplete uploads (but 
578  * not remove blocks that have already been publishd) or
579  * simply clean up the state for completed uploads.
580  *
581  * @param sc context for the upload to stop
582  */
583 void 
584 GNUNET_FS_publish_stop (struct GNUNET_FS_PublishContext *sc)
585 {
586   if (GNUNET_SCHEDULER_NO_TASK != sc->upload_task)
587     GNUNET_SCHEDULER_cancel (sc->h->sched, sc->upload_task);
588   // FIXME: remove from persistence DB (?) --- think more about
589   //        shutdown / persistent-resume APIs!!!
590   GNUNET_FS_file_information_destroy (sc->fi, NULL, NULL);
591   GNUNET_FS_namespace_delete (sc->namespace, GNUNET_NO);
592   GNUNET_free_non_null (sc->nid);  
593   GNUNET_free_non_null (sc->nuid);
594   GNUNET_DATASTORE_disconnect (sc->dsh, GNUNET_NO);
595   GNUNET_free (sc);
596 }
597
598 /* end of fs_publish.c */