syn
[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  * - directory creation
30  * - KBlocks
31  * - SBlocks
32  * - indexing support
33  * - calling of progress function
34  * - handling of IO errors (emsg)
35  * - code-sharing with unindex
36  * - datastore reservation support
37  * - persistence support
38  */
39
40 #include "platform.h"
41 #include "gnunet_constants.h"
42 #include "gnunet_util_lib.h"
43 #include "gnunet_fs_service.h"
44 #include "fs.h"
45
46 #define DEBUG_PUBLISH GNUNET_YES
47
48 /**
49  * Main function that performs the upload.
50  * @param cls "struct GNUNET_FS_PublishContext" identifies the upload
51  * @param tc task context
52  */
53 static void
54 do_upload (void *cls,
55            const struct GNUNET_SCHEDULER_TaskContext *tc);
56
57
58 /**
59  * Context for "ds_put_cont".
60  */
61 struct PutContCtx
62 {
63   /**
64    * Publishing context for which the datastore
65    * PUT request was executed.
66    */
67   struct GNUNET_FS_PublishContext *sc;
68
69   /**
70    * Specific file with the block.
71    */
72   struct GNUNET_FS_FileInformation *p;
73
74   /**
75    * Function to run next, if any (can be NULL).
76    */
77   GNUNET_SCHEDULER_Task cont;
78 };
79
80 /**
81  * Function called by the datastore API with
82  * the result from the PUT request.
83  *
84  * @param cls our closure
85  * @param success GNUNET_OK on success
86  * @param msg error message (or NULL)
87  */
88 static void
89 ds_put_cont (void *cls,
90              int success,
91              const char *msg)
92 {
93   struct PutContCtx *pcc = cls;
94
95   if (GNUNET_OK != success)
96     {
97       // FIXME: call progress CB with error
98       // FIXME: update pcc->p to indicate abort
99       GNUNET_FS_file_information_sync (pcc->p);
100       return;
101     }
102   GNUNET_FS_file_information_sync (pcc->p);
103   if (NULL != pcc->cont)
104     pcc->sc->upload_task 
105       = GNUNET_SCHEDULER_add_delayed (pcc->sc->h->sched,
106                                       GNUNET_NO,
107                                       GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
108                                       GNUNET_SCHEDULER_NO_TASK,
109                                       GNUNET_TIME_UNIT_ZERO,
110                                       pcc->cont,
111                                       pcc->sc);
112   GNUNET_free (pcc);
113 }
114
115
116 /**
117  * We need to publish a specific block.  Do it.  Then continue with
118  * the main task.
119  *
120  * @param sc overall upload data
121  * @param p file that the block belongs to (needed for options!)
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 void* blk,
131                uint16_t blk_size,
132                uint32_t blk_type,
133                GNUNET_SCHEDULER_Task cont)
134 {
135   struct GNUNET_HashCode key;
136
137   // FIXME: GNUNET_FS_get_key (blk_type, blk, blk_size, &key);
138   // (or add "key" as argument to reduce hashing?)
139   dpc_cls = GNUNET_malloc(sizeof(struct PutContCtx));
140   dpc_cls->cont = cont;
141   dpc_cls->sc = sc;
142   dpc_cls->p = p;
143   // FIXME: need to do something to "sc" to mark
144   // that "sc" can not be freed right now due to this
145   // pending, scheduled operation for which we don't have
146   // a task ID!  
147   GNUNET_DATASTORE_put (sc->dsh,
148                         sc->rid,
149                         &key,
150                         blk_size,
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
285   bds = GNUNET_FS_DBLOCK_SIZE; /* number of bytes each CHK at level "i"
286                                   corresponds to */
287   for (i=0;i<height;i++)
288     bds *= GNUNET_FS_CHK_PER_INODE;
289   GNUNET_assert (0 == (offset % bds));
290   ret = offset / bds;
291   return ret % GNUNET_FS_CHK_PER_INODE; 
292 }
293
294
295 /**
296  * We are uploading a file or directory; load (if necessary) the next
297  * block into memory, encrypt it and send it to the FS service.  Then
298  * continue with the main task.
299  *
300  * @param sc overall upload data
301  * @param p specific file or directory for which kblocks
302  *          should be created
303  */
304 static void
305 publish_content (struct GNUNET_FS_PublishContext *sc,
306                  struct GNUNET_FS_FileInformation *p)
307 {
308   struct ContentHashKey *chk;
309   const void *pt_block;
310   uint16_t pt_size;
311   char *emsg;
312   char iob[GNUNET_FS_DBLOCK_SIZE];
313   char enc[GNUNET_FS_DBLOCK_SIZE];
314   struct GNUNET_CRYPTO_AesSessionKey sk;
315   struct GNUNET_CRYPTO_AesInitializationVector iv;
316   uint64_t size;
317   unsigned int off;
318
319   // FIXME: figure out how to share this code
320   // with unindex!
321   size = (p->is_directory) ? p->data.dir.dir_size : p->data.file.file_size;
322   if (NULL == p->chk_tree)
323     {
324       if (p->is_directory)
325         {
326           /* FIXME: create function to create directory
327              and use that API here! */
328           GNUNET_FS_directory_create (&p->data.dir.dir_size,
329                                       &p->data.dir.dir_data,
330                                       p->meta,
331                                       &directory_entry_lister,
332                                       p->data.dir.entries);
333           size = p->data.dir.data_size;
334         }
335       p->chk_tree_depth = compute_depth (size);
336       p->chk_tree = GNUNET_malloc (p->chk_tree_depth * 
337                                    sizeof (struct ContentHashKey) *
338                                    GNUNET_FS_CHK_PER_INODE);
339       p->current_depth = p->chk_tree_depth;
340     }
341   if (p->current_depth == p->chk_tree_depth)
342     {
343       if (p->is_directory)
344         {
345           pt_size = GNUNET_MIN(GNUNET_FS_DBLOCK_SIZE,
346                                p->data.dir.dir_size - p->publish_offset);
347           pt_block = &p->data.dir.dir_data[p->publish_offset];
348         }
349       else
350         {
351           pt_size = GNUNET_MIN(GNUNET_FS_DBLOCK_SIZE,
352                                p->data.file.file_size - p->publish_offset);
353           p->data.file.reader (p->data.file.reader_cls,
354                                p->publish_offset,
355                                pt_size,
356                                iob,
357                                &emsg);
358           pt_block = iob;
359         }
360     }
361   else
362     {
363       pt_size = compute_iblock_size (p->chk_tree_depth - p->current_depth,
364                                      p->publish_offset); 
365       pt_block = &p->chk_tree[p->current_depth *
366                               GNUNET_FS_CHK_PER_INODE];
367     }
368   off = compute_chk_offset (p->chk_tree_depth - p->current_depth,
369                             p->publish_offset);
370   chk = &p->chk_tree[(p->current_depth-1)*GNUNET_FS_CHK_PER_INODE+off];
371   GNUNET_CRYPTO_hash (pt_block, pt_size, &chk->key);
372   GNUNET_CRYPTO_hash_to_aes_key (&chk->key, &sk, &iv);
373   GNUNET_CRYPTO_aes_encrypt (pt_block,
374                              pt_size,
375                              &sk,
376                              &iv,
377                              enc);
378   // NOTE: this call (and progress below) is all that really differs
379   // between publish/unindex!  Parameterize & move this code!
380   // FIXME: something around here would need to change
381   // for indexing!
382   publish_block (sc, p, enc, pt_size, 
383                  (p->current_depth == p->chk_tree_depth) 
384                  ? GNUNET_DATASTORE_BLOCKTYPE_DBLOCK 
385                  : GNUNET_DATASTORE_BLOCKTYPE_IBLOCK,
386                  &do_upload);
387   // FIXME: should call progress function somewhere here!
388   GNUNET_CRYPTO_hash (enc, pt_size, &chk->query);
389   if (p->current_depth == p->chk_tree_depth) 
390     { 
391       p->publish_offset += pt_size;
392       if ( (p->publish_offset == size) ||
393            (0 == p->publish_offset % (GNUNET_FS_CHK_PER_INODE * GNUNET_FS_DBLOCK_SIZE) ) )
394         p->current_depth--;
395     }
396   else
397     {
398       if ( (off == GNUNET_FS_CHK_PER_INODE) ||
399            (p->publish_offset == size) )
400         p->current_depth--;
401       else
402         p->current_depth = p->chk_tree_depth;
403     }
404   if (0 == p->current_depth)
405     {
406       p->chk_uri = GNUNET_malloc (sizeof(struct GNUNET_FS_Uri));
407       p->chk_uri.type = chk;
408       p->chk_uri.data.chk.chk = p->chk_tree[0];
409       p->chk_uri.data.chk.file_length = size;
410       GNUNET_free (p->chk_tree);
411       p->chk_tree = NULL;
412     }
413 }
414
415
416 /**
417  * Main function that performs the upload.
418  * @param cls "struct GNUNET_FS_PublishContext" identifies the upload
419  * @param tc task context
420  */
421 static void
422 do_upload (void *cls,
423            const struct GNUNET_SCHEDULER_TaskContext *tc)
424 {
425   struct GNUNET_FS_PublishContext *sc = cls;
426   struct GNUNET_FS_FileInformation *p;
427
428   sc->upload_task = GNUNET_SCHEDULER_NO_TASK;  
429   p = sc->fi_pos;
430   if (NULL == p)
431     {
432       /* upload of entire hierarchy complete,
433          publish namespace entries */
434       publish_sblock (sc);
435       return;
436     }
437   if (NULL != p->chk_uri)
438     {
439       /* move on to next file */
440       if (NULL != p->next)
441         sc->fi_pos = p->next;
442       else
443         sc->fi_pos = p->dir;
444       /* upload of "p" complete, publish KBlocks! */
445       publish_kblocks (sc, p);
446       return;
447     }
448   if (p->do_index)
449     {
450       // FIXME: need to pre-compute hash over
451       // the entire file and ask FS to prepare
452       // for indexing!
453       return;
454     }
455   publish_content (sc, p);
456 }
457
458
459 /**
460  * Publish a file or directory.
461  *
462  * @param h handle to the file sharing subsystem
463  * @param ctx initial value to use for the '*ctx'
464  *        in the callback (for the GNUNET_FS_STATUS_PUBLISH_START event).
465  * @param fi information about the file or directory structure to publish
466  * @param namespace namespace to publish the file in, NULL for no namespace
467  * @param nid identifier to use for the publishd content in the namespace
468  *        (can be NULL, must be NULL if namespace is NULL)
469  * @param nuid update-identifier that will be used for future updates 
470  *        (can be NULL, must be NULL if namespace or nid is NULL)
471  * @return context that can be used to control the publish operation
472  */
473 struct GNUNET_FS_PublishContext *
474 GNUNET_FS_publish_start (struct GNUNET_FS_Handle *h,
475                          void *ctx,
476                          struct GNUNET_FS_FileInformation *fi,
477                          struct GNUNET_FS_Namespace *namespace,
478                          const char *nid,
479                          const char *nuid)
480 {
481   struct GNUNET_FS_PublishContext *ret;
482   struct GNUNET_FS_FileInformation *p;
483   struct GNUNET_DATASTORE_Handle *dsh;
484
485   dsh = GNUNET_DATASTORE_connect (h->cfg,
486                                   h->sched);
487   if (NULL == dsh)
488     return NULL;
489   ret = GNUNET_malloc (sizeof (struct GNUNET_FS_PublishContext));
490   ret->dsh = dsh;
491   ret->h = h;
492   ret->client_ctx = ctx;
493   ret->fi = fi;
494   ret->namespace = namespace;
495   if (namespace != NULL)
496     {
497       namespace->rc++;
498       GNUNET_assert (NULL != nid);
499       ret->nid = GNUNET_strdup (nid);
500       if (NULL != nuid)
501         ret->nuid = GNUNET_strdup (nuid);
502     }
503   // FIXME: make upload persistent!
504
505   /* find first leaf, DFS */
506   p = ret->fi;
507   while ( (p->is_directory) &&
508           (NULL != p->data.dir.entries) )
509     p = p->data.dir.entries;          
510   ret->fi_pos = p;
511
512   // FIXME: calculate space needed for "fi"
513   // and reserve as first task (then trigger
514   // "do_upload" from that continuation)!
515   ret->upload_task 
516     = GNUNET_SCHEDULER_add_delayed (h->sched,
517                                     GNUNET_NO,
518                                     GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
519                                     GNUNET_SCHEDULER_NO_TASK,
520                                     GNUNET_TIME_UNIT_ZERO,
521                                     &do_upload,
522                                     ret);
523   return ret;
524 }
525
526
527 /**
528  * Stop an upload.  Will abort incomplete uploads (but 
529  * not remove blocks that have already been publishd) or
530  * simply clean up the state for completed uploads.
531  *
532  * @param sc context for the upload to stop
533  */
534 void 
535 GNUNET_FS_publish_stop (struct GNUNET_FS_PublishContext *sc)
536 {
537   if (GNUNET_SCHEDULER_NO_TASK != sc->upload_task)
538     GNUNET_SCHEDULER_cancel (sc->h->sched, sc->upload_task);
539   // FIXME: remove from persistence DB (?) --- think more about
540   //        shutdown / persistent-resume APIs!!!
541   GNUNET_FS_file_information_destroy (sc->fi, NULL, NULL);
542   GNUNET_FS_namespace_delete (sc->namespace, GNUNET_NO);
543   GNUNET_free_non_null (sc->nid);  
544   GNUNET_free_non_null (sc->nuid);
545   GNUNET_DATASTORE_disconnect (sc->dsh);
546   GNUNET_free (sc);
547 }
548
549 /* end of fs_publish.c */