2 This file is part of GNUnet.
3 (C) 2009 Christian Grothoff (and other contributing authors)
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.
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.
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.
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
32 * - calling of progress function
33 * - handling of IO errors (emsg)
34 * - code-sharing with unindex
35 * - datastore reservation support
36 * - persistence support
40 #include "gnunet_constants.h"
41 #include "gnunet_util_lib.h"
42 #include "gnunet_fs_service.h"
45 #define DEBUG_PUBLISH GNUNET_YES
48 * Main function that performs the upload.
49 * @param cls "struct GNUNET_FS_PublishContext" identifies the upload
50 * @param tc task context
54 const struct GNUNET_SCHEDULER_TaskContext *tc);
58 * Context for "ds_put_cont".
63 * Publishing context for which the datastore
64 * PUT request was executed.
66 struct GNUNET_FS_PublishContext *sc;
69 * Specific file with the block.
71 struct GNUNET_FS_FileInformation *p;
74 * Function to run next, if any (can be NULL).
76 GNUNET_SCHEDULER_Task cont;
81 * Fill in all of the generic fields for
84 * @param pc structure to fill in
85 * @param sc overall publishing context
86 * @param p file information for the file being published
89 make_publish_status (struct GNUNET_FS_ProgressInfo *pi,
90 struct GNUNET_FS_PublishContext *sc,
91 const struct GNUNET_FS_FileInformation *p)
93 pi->value.publish.sc = sc;
94 pi->value.publish.fi = p;
95 pi->value.publish.cctx
97 pi->value.publish.pctx
98 = (NULL == p->dir) ? NULL : p->dir->client_info;
99 pi->value.publish.size
100 = (p->is_directory) ? p->data.dir.dir_size : p->data.file.file_size;
101 pi->value.publish.eta
102 = GNUNET_TIME_calculate_eta (p->start_time,
104 pi->value.publish.size);
105 pi->value.publish.duration = GNUNET_TIME_absolute_get_duration (p->start_time);
106 pi->value.publish.completed = p->publish_offset;
107 pi->value.publish.anonymity = p->anonymity;
112 * Function called by the datastore API with
113 * the result from the PUT request.
115 * @param cls our closure
116 * @param success GNUNET_OK on success
117 * @param msg error message (or NULL)
120 ds_put_cont (void *cls,
124 struct PutContCtx *pcc = cls;
125 struct GNUNET_FS_ProgressInfo pi;
127 if (GNUNET_OK != success)
129 GNUNET_asprintf (&pcc->p->emsg,
130 _("Upload failed: %s"),
132 GNUNET_FS_file_information_sync (pcc->p);
133 pi.status = GNUNET_FS_STATUS_PUBLISH_ERROR;
134 make_publish_status (&pi, pcc->sc, pcc->p);
135 pi.value.publish.eta = GNUNET_TIME_UNIT_FOREVER_REL;
136 pi.value.publish.specifics.error.message = pcc->p->emsg;
137 pcc->sc->h->upcb (pcc->sc->h->upcb_cls,
141 GNUNET_FS_file_information_sync (pcc->p);
142 if (NULL != pcc->cont)
144 = GNUNET_SCHEDULER_add_delayed (pcc->sc->h->sched,
146 GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
147 GNUNET_SCHEDULER_NO_TASK,
148 GNUNET_TIME_UNIT_ZERO,
156 * We need to publish a specific block. Do it. Then continue with
159 * @param sc overall upload data
160 * @param p file that the block belongs to (needed for options!)
161 * @param query what the block should be indexed under
162 * @param blk encoded block to publish
163 * @param blk_size size of the block
164 * @param blk_type type of the block
165 * @param cont function to run when done
168 publish_block (struct GNUNET_FS_PublishContext *sc,
169 struct GNUNET_FS_FileInformation *p,
170 const GNUNET_HashCode *query,
174 GNUNET_SCHEDULER_Task cont)
176 struct PutContCtx * dpc_cls;
178 dpc_cls = GNUNET_malloc(sizeof(struct PutContCtx));
179 dpc_cls->cont = cont;
182 // FIXME: need to do something to "sc" to mark
183 // that "sc" can not be freed right now due to this
184 // pending, scheduled operation for which we don't have
186 GNUNET_DATASTORE_put (sc->dsh,
195 GNUNET_CONSTANTS_SERVICE_TIMEOUT,
202 * Generate the callback that signals clients
203 * that a file (or directory) has been completely
206 * @param p the completed upload
207 * @param sc context of the publication
210 signal_publish_completion (struct GNUNET_FS_FileInformation *p,
211 struct GNUNET_FS_PublishContext *sc)
213 struct GNUNET_FS_ProgressInfo pi;
215 pi.status = GNUNET_FS_STATUS_PUBLISH_COMPLETED;
216 make_publish_status (&pi, sc, p);
217 pi.value.publish.eta = GNUNET_TIME_UNIT_ZERO;
218 pi.value.publish.specifics.completed.chk_uri = p->chk_uri;
219 sc->h->upcb (sc->h->upcb_cls,
225 * We are almost done publishing the structure,
226 * add SBlocks (if needed).
228 * @param sc overall upload data
231 publish_sblock (struct GNUNET_FS_PublishContext *sc)
233 struct GNUNET_FS_FileInformation *p;
236 // FIXME: build sblock & call publish_block!
238 // FIXME: continuation should
239 // be releasing the datastore reserve
240 // (once implemented)
241 // FIXME: finally, signal overall completion
242 signal_publish_completion (p, sc);
247 * We have uploaded a file or directory; now publish
248 * the KBlocks in the global keyword space so that
249 * it can be found. Then continue with the
252 * @param sc overall upload data
253 * @param p specific file or directory for which kblocks
257 publish_kblocks (struct GNUNET_FS_PublishContext *sc,
258 struct GNUNET_FS_FileInformation *p)
260 // FIXME: build all kblocks
261 // call publish_kblock on each
264 GNUNET_FS_file_information_sync (p);
266 signal_publish_completion (p, sc);
268 // last continuation should then call the main continuation again
273 * Compute the depth of the CHK tree.
275 * @param flen file length for which to compute the depth
276 * @return depth of the tree
279 compute_depth (uint64_t flen)
281 unsigned int treeDepth;
285 fl = GNUNET_FS_DBLOCK_SIZE;
289 if (fl * GNUNET_FS_CHK_PER_INODE < fl)
291 /* integer overflow, this is a HUGE file... */
294 fl = fl * GNUNET_FS_CHK_PER_INODE;
301 * Compute the size of the current IBlock.
303 * @param height height of the IBlock in the tree (aka overall
304 * number of tree levels minus depth); 0 == DBlock
305 * @param offset current offset in the overall file
306 * @return size of the corresponding IBlock
309 compute_iblock_size (unsigned int height,
317 GNUNET_assert (height > 0);
318 bds = GNUNET_FS_DBLOCK_SIZE; /* number of bytes each CHK at level "i"
320 for (i=0;i<height;i++)
321 bds *= GNUNET_FS_CHK_PER_INODE;
325 /* we were triggered at the end of a full block */
326 ret = GNUNET_FS_CHK_PER_INODE;
330 /* we were triggered at the end of the file */
331 bds /= GNUNET_FS_CHK_PER_INODE;
336 return (uint16_t) (ret * sizeof(struct ContentHashKey));
341 * Compute the offset of the CHK for the
342 * current block in the IBlock above.
344 * @param height height of the IBlock in the tree (aka overall
345 * number of tree levels minus depth); 0 == DBlock
346 * @param offset current offset in the overall file
347 * @return (array of CHKs') offset in the above IBlock
350 compute_chk_offset (unsigned int height,
357 bds = GNUNET_FS_DBLOCK_SIZE; /* number of bytes each CHK at level "i"
359 for (i=0;i<height;i++)
360 bds *= GNUNET_FS_CHK_PER_INODE;
361 GNUNET_assert (0 == (offset % bds));
363 return ret % GNUNET_FS_CHK_PER_INODE;
368 * We are uploading a file or directory; load (if necessary) the next
369 * block into memory, encrypt it and send it to the FS service. Then
370 * continue with the main task.
372 * @param sc overall upload data
373 * @param p specific file or directory for which kblocks
377 publish_content (struct GNUNET_FS_PublishContext *sc,
378 struct GNUNET_FS_FileInformation *p)
380 struct GNUNET_FS_ProgressInfo pi;
381 struct ContentHashKey *mychk;
382 const void *pt_block;
385 char iob[GNUNET_FS_DBLOCK_SIZE];
386 char enc[GNUNET_FS_DBLOCK_SIZE];
387 struct GNUNET_CRYPTO_AesSessionKey sk;
388 struct GNUNET_CRYPTO_AesInitializationVector iv;
391 struct GNUNET_FS_DirectoryBuilder *db;
392 struct GNUNET_FS_FileInformation *dirpos;
396 // FIXME: figure out how to share this code
398 size = (p->is_directory) ? p->data.dir.dir_size : p->data.file.file_size;
399 if (NULL == p->chk_tree)
403 db = GNUNET_FS_directory_builder_create (p->meta);
404 dirpos = p->data.dir.entries;
405 while (NULL != dirpos)
407 if (dirpos->is_directory)
409 raw_data = dirpos->data.dir.dir_data;
410 dirpos->data.dir.dir_data = NULL;
415 if ( (dirpos->data.file.file_size < GNUNET_FS_MAX_INLINE_SIZE) &&
416 (dirpos->data.file.file_size > 0) )
418 raw_data = GNUNET_malloc (dirpos->data.file.file_size);
420 if (dirpos->data.file.file_size !=
421 dirpos->data.file.reader (dirpos->data.file.reader_cls,
423 dirpos->data.file.file_size,
427 GNUNET_free_non_null (emsg);
428 GNUNET_free (raw_data);
433 GNUNET_FS_directory_builder_add (db,
437 GNUNET_free_non_null (raw_data);
438 dirpos = dirpos->next;
440 GNUNET_FS_directory_builder_finish (db,
441 &p->data.dir.dir_size,
442 &p->data.dir.dir_data);
443 size = p->data.dir.dir_size;
445 p->chk_tree_depth = compute_depth (size);
446 p->chk_tree = GNUNET_malloc (p->chk_tree_depth *
447 sizeof (struct ContentHashKey) *
448 GNUNET_FS_CHK_PER_INODE);
449 p->current_depth = p->chk_tree_depth;
451 if (p->current_depth == p->chk_tree_depth)
455 pt_size = GNUNET_MIN(GNUNET_FS_DBLOCK_SIZE,
456 p->data.dir.dir_size - p->publish_offset);
457 dd = p->data.dir.dir_data;
458 pt_block = &dd[p->publish_offset];
462 pt_size = GNUNET_MIN(GNUNET_FS_DBLOCK_SIZE,
463 p->data.file.file_size - p->publish_offset);
466 p->data.file.reader (p->data.file.reader_cls,
472 GNUNET_asprintf (&p->emsg,
473 _("Upload failed: %s"),
476 GNUNET_FS_file_information_sync (p);
477 pi.status = GNUNET_FS_STATUS_PUBLISH_ERROR;
478 make_publish_status (&pi, sc, p);
479 pi.value.publish.eta = GNUNET_TIME_UNIT_FOREVER_REL;
480 pi.value.publish.specifics.error.message = p->emsg;
481 sc->h->upcb (sc->h->upcb_cls,
483 /* continue with main (to propagate error up) */
485 = GNUNET_SCHEDULER_add_delayed (sc->h->sched,
487 GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
488 GNUNET_SCHEDULER_NO_TASK,
489 GNUNET_TIME_UNIT_ZERO,
499 pt_size = compute_iblock_size (p->chk_tree_depth - p->current_depth,
501 pt_block = &p->chk_tree[p->current_depth *
502 GNUNET_FS_CHK_PER_INODE];
504 off = compute_chk_offset (p->chk_tree_depth - p->current_depth,
506 mychk = &p->chk_tree[(p->current_depth-1)*GNUNET_FS_CHK_PER_INODE+off];
507 GNUNET_CRYPTO_hash (pt_block, pt_size, &mychk->key);
508 GNUNET_CRYPTO_hash_to_aes_key (&mychk->key, &sk, &iv);
509 GNUNET_CRYPTO_aes_encrypt (pt_block,
514 // NOTE: this call (and progress below) is all that really differs
515 // between publish/unindex! Parameterize & move this code!
516 // FIXME: something around here would need to change
518 publish_block (sc, p,
522 (p->current_depth == p->chk_tree_depth)
523 ? GNUNET_DATASTORE_BLOCKTYPE_DBLOCK
524 : GNUNET_DATASTORE_BLOCKTYPE_IBLOCK,
526 if (p->current_depth == p->chk_tree_depth)
528 pi.status = GNUNET_FS_STATUS_PUBLISH_PROGRESS;
529 make_publish_status (&pi, sc, p);
530 pi.value.publish.specifics.progress.data = pt_block;
531 pi.value.publish.specifics.progress.offset = p->publish_offset;
532 pi.value.publish.specifics.progress.data_len = pt_size;
533 sc->h->upcb (sc->h->upcb_cls,
536 GNUNET_CRYPTO_hash (enc, pt_size, &mychk->query);
537 if (p->current_depth == p->chk_tree_depth)
539 p->publish_offset += pt_size;
540 if ( (p->publish_offset == size) ||
541 (0 == p->publish_offset % (GNUNET_FS_CHK_PER_INODE * GNUNET_FS_DBLOCK_SIZE) ) )
546 if ( (off == GNUNET_FS_CHK_PER_INODE) ||
547 (p->publish_offset == size) )
550 p->current_depth = p->chk_tree_depth;
552 if (0 == p->current_depth)
554 p->chk_uri = GNUNET_malloc (sizeof(struct GNUNET_FS_Uri));
555 p->chk_uri->type = chk;
556 p->chk_uri->data.chk.chk = p->chk_tree[0];
557 p->chk_uri->data.chk.file_length = size;
558 GNUNET_free (p->chk_tree);
565 * Main function that performs the upload.
566 * @param cls "struct GNUNET_FS_PublishContext" identifies the upload
567 * @param tc task context
570 do_upload (void *cls,
571 const struct GNUNET_SCHEDULER_TaskContext *tc)
573 struct GNUNET_FS_PublishContext *sc = cls;
574 struct GNUNET_FS_ProgressInfo pi;
575 struct GNUNET_FS_FileInformation *p;
578 sc->upload_task = GNUNET_SCHEDULER_NO_TASK;
582 /* upload of entire hierarchy complete,
583 publish namespace entries */
589 /* error with current file, abort all
590 related files as well! */
591 while (NULL != p->dir)
593 fn = GNUNET_CONTAINER_meta_data_get_by_type (p->meta,
596 GNUNET_asprintf (&p->emsg,
597 _("Recursive upload failed at `%s'"),
600 GNUNET_FS_file_information_sync (p);
601 pi.status = GNUNET_FS_STATUS_PUBLISH_ERROR;
602 make_publish_status (&pi, sc, p);
603 pi.value.publish.eta = GNUNET_TIME_UNIT_FOREVER_REL;
604 pi.value.publish.specifics.error.message = p->emsg;
605 sc->h->upcb (sc->h->upcb_cls,
610 if (NULL != p->chk_uri)
612 /* move on to next file */
614 sc->fi_pos = p->next;
617 /* upload of "p" complete, publish KBlocks! */
618 publish_kblocks (sc, p);
621 if ( (!p->is_directory) &&
622 (p->data.file.do_index) )
624 // FIXME: need to pre-compute hash over
625 // the entire file and ask FS to prepare
629 publish_content (sc, p);
634 * Publish a file or directory.
636 * @param h handle to the file sharing subsystem
637 * @param ctx initial value to use for the '*ctx'
638 * in the callback (for the GNUNET_FS_STATUS_PUBLISH_START event).
639 * @param fi information about the file or directory structure to publish
640 * @param namespace namespace to publish the file in, NULL for no namespace
641 * @param nid identifier to use for the publishd content in the namespace
642 * (can be NULL, must be NULL if namespace is NULL)
643 * @param nuid update-identifier that will be used for future updates
644 * (can be NULL, must be NULL if namespace or nid is NULL)
645 * @return context that can be used to control the publish operation
647 struct GNUNET_FS_PublishContext *
648 GNUNET_FS_publish_start (struct GNUNET_FS_Handle *h,
650 struct GNUNET_FS_FileInformation *fi,
651 struct GNUNET_FS_Namespace *namespace,
655 struct GNUNET_FS_PublishContext *ret;
656 struct GNUNET_FS_FileInformation *p;
657 struct GNUNET_DATASTORE_Handle *dsh;
659 dsh = GNUNET_DATASTORE_connect (h->cfg,
663 ret = GNUNET_malloc (sizeof (struct GNUNET_FS_PublishContext));
666 ret->client_ctx = ctx;
668 ret->namespace = namespace;
669 if (namespace != NULL)
672 GNUNET_assert (NULL != nid);
673 ret->nid = GNUNET_strdup (nid);
675 ret->nuid = GNUNET_strdup (nuid);
677 // FIXME: make upload persistent!
682 /* find first leaf, DFS */
684 while ( (p->is_directory) &&
685 (NULL != p->data.dir.entries) )
686 p = p->data.dir.entries;
689 // FIXME: calculate space needed for "fi"
690 // and reserve as first task (then trigger
691 // "do_upload" from that continuation)!
693 = GNUNET_SCHEDULER_add_delayed (h->sched,
695 GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
696 GNUNET_SCHEDULER_NO_TASK,
697 GNUNET_TIME_UNIT_ZERO,
705 * Stop an upload. Will abort incomplete uploads (but
706 * not remove blocks that have already been publishd) or
707 * simply clean up the state for completed uploads.
709 * @param sc context for the upload to stop
712 GNUNET_FS_publish_stop (struct GNUNET_FS_PublishContext *sc)
714 if (GNUNET_SCHEDULER_NO_TASK != sc->upload_task)
715 GNUNET_SCHEDULER_cancel (sc->h->sched, sc->upload_task);
716 // FIXME: remove from persistence DB (?) --- think more about
717 // shutdown / persistent-resume APIs!!!
718 GNUNET_FS_file_information_destroy (sc->fi, NULL, NULL);
719 GNUNET_FS_namespace_delete (sc->namespace, GNUNET_NO);
720 GNUNET_free_non_null (sc->nid);
721 GNUNET_free_non_null (sc->nuid);
722 GNUNET_DATASTORE_disconnect (sc->dsh, GNUNET_NO);
726 /* end of fs_publish.c */