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 * - code-sharing with unindex (can wait)
33 * - persistence support (can wait)
34 * - datastore reservation support (optimization)
38 #include "gnunet_constants.h"
39 #include "gnunet_util_lib.h"
40 #include "gnunet_fs_service.h"
43 #define DEBUG_PUBLISH GNUNET_YES
46 * Main function that performs the upload.
47 * @param cls "struct GNUNET_FS_PublishContext" identifies the upload
48 * @param tc task context
52 const struct GNUNET_SCHEDULER_TaskContext *tc);
56 * Context for "ds_put_cont".
61 * Publishing context for which the datastore
62 * PUT request was executed.
64 struct GNUNET_FS_PublishContext *sc;
67 * Specific file with the block.
69 struct GNUNET_FS_FileInformation *p;
72 * Function to run next, if any (can be NULL).
74 GNUNET_SCHEDULER_Task cont;
79 * Fill in all of the generic fields for
82 * @param pc structure to fill in
83 * @param sc overall publishing context
84 * @param p file information for the file being published
87 make_publish_status (struct GNUNET_FS_ProgressInfo *pi,
88 struct GNUNET_FS_PublishContext *sc,
89 const struct GNUNET_FS_FileInformation *p)
91 pi->value.publish.sc = sc;
92 pi->value.publish.fi = p;
93 pi->value.publish.cctx
95 pi->value.publish.pctx
96 = (NULL == p->dir) ? NULL : p->dir->client_info;
97 pi->value.publish.size
98 = (p->is_directory) ? p->data.dir.dir_size : p->data.file.file_size;
100 = GNUNET_TIME_calculate_eta (p->start_time,
102 pi->value.publish.size);
103 pi->value.publish.duration = GNUNET_TIME_absolute_get_duration (p->start_time);
104 pi->value.publish.completed = p->publish_offset;
105 pi->value.publish.anonymity = p->anonymity;
110 * Cleanup the publish context, we're done
113 * @param pc struct to clean up after
116 publish_cleanup (struct GNUNET_FS_PublishContext *sc)
118 GNUNET_FS_file_information_destroy (sc->fi, NULL, NULL);
119 GNUNET_FS_namespace_delete (sc->namespace, GNUNET_NO);
120 GNUNET_free_non_null (sc->nid);
121 GNUNET_free_non_null (sc->nuid);
122 GNUNET_DATASTORE_disconnect (sc->dsh, GNUNET_NO);
128 * Function called by the datastore API with
129 * the result from the PUT request.
131 * @param cls our closure
132 * @param success GNUNET_OK on success
133 * @param msg error message (or NULL)
136 ds_put_cont (void *cls,
140 struct PutContCtx *pcc = cls;
141 struct GNUNET_FS_ProgressInfo pi;
143 if (GNUNET_SYSERR == pcc->sc->in_network_wait)
145 /* we were aborted in the meantime,
147 publish_cleanup (pcc->sc);
150 GNUNET_assert (GNUNET_YES == pcc->sc->in_network_wait);
151 pcc->sc->in_network_wait = GNUNET_NO;
152 if (GNUNET_OK != success)
154 GNUNET_asprintf (&pcc->p->emsg,
155 _("Upload failed: %s"),
157 GNUNET_FS_file_information_sync (pcc->p);
158 pi.status = GNUNET_FS_STATUS_PUBLISH_ERROR;
159 make_publish_status (&pi, pcc->sc, pcc->p);
160 pi.value.publish.eta = GNUNET_TIME_UNIT_FOREVER_REL;
161 pi.value.publish.specifics.error.message = pcc->p->emsg;
163 = pcc->sc->h->upcb (pcc->sc->h->upcb_cls,
167 GNUNET_FS_file_information_sync (pcc->p);
168 if (NULL != pcc->cont)
170 = GNUNET_SCHEDULER_add_delayed (pcc->sc->h->sched,
172 GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
173 GNUNET_SCHEDULER_NO_TASK,
174 GNUNET_TIME_UNIT_ZERO,
182 * We need to publish a specific block. Do it. Then continue with
185 * @param sc overall upload data
186 * @param p file that the block belongs to (needed for options!)
187 * @param query what the block should be indexed under
188 * @param blk encoded block to publish
189 * @param blk_size size of the block
190 * @param blk_type type of the block
191 * @param cont function to run when done
194 publish_block (struct GNUNET_FS_PublishContext *sc,
195 struct GNUNET_FS_FileInformation *p,
196 const GNUNET_HashCode *query,
200 GNUNET_SCHEDULER_Task cont)
202 struct PutContCtx * dpc_cls;
204 dpc_cls = GNUNET_malloc(sizeof(struct PutContCtx));
205 dpc_cls->cont = cont;
208 GNUNET_assert (GNUNET_NO == sc->in_network_wait);
209 sc->in_network_wait = GNUNET_YES;
210 GNUNET_DATASTORE_put (sc->dsh,
219 GNUNET_CONSTANTS_SERVICE_TIMEOUT,
226 * Generate the callback that signals clients
227 * that a file (or directory) has been completely
230 * @param p the completed upload
231 * @param sc context of the publication
234 signal_publish_completion (struct GNUNET_FS_FileInformation *p,
235 struct GNUNET_FS_PublishContext *sc)
237 struct GNUNET_FS_ProgressInfo pi;
239 pi.status = GNUNET_FS_STATUS_PUBLISH_COMPLETED;
240 make_publish_status (&pi, sc, p);
241 pi.value.publish.eta = GNUNET_TIME_UNIT_ZERO;
242 pi.value.publish.specifics.completed.chk_uri = p->chk_uri;
244 = sc->h->upcb (sc->h->upcb_cls,
250 * We are almost done publishing the structure,
251 * add SBlocks (if needed).
253 * @param sc overall upload data
256 publish_sblock (struct GNUNET_FS_PublishContext *sc)
258 struct GNUNET_FS_FileInformation *p;
261 // FIXME: build sblock & call publish_block!
263 // FIXME: continuation should
264 // be releasing the datastore reserve
265 // (once implemented)
266 // FIXME: finally, signal overall completion
267 signal_publish_completion (p, sc);
272 * We have uploaded a file or directory; now publish
273 * the KBlocks in the global keyword space so that
274 * it can be found. Then continue with the
277 * @param sc overall upload data
278 * @param p specific file or directory for which kblocks
282 publish_kblocks (struct GNUNET_FS_PublishContext *sc,
283 struct GNUNET_FS_FileInformation *p)
285 // FIXME: build all kblocks
286 // call publish_block on each
289 GNUNET_FS_file_information_sync (p);
291 signal_publish_completion (p, sc);
293 // last continuation should then call the main continuation again
298 * Compute the depth of the CHK tree.
300 * @param flen file length for which to compute the depth
301 * @return depth of the tree
304 compute_depth (uint64_t flen)
306 unsigned int treeDepth;
310 fl = GNUNET_FS_DBLOCK_SIZE;
314 if (fl * GNUNET_FS_CHK_PER_INODE < fl)
316 /* integer overflow, this is a HUGE file... */
319 fl = fl * GNUNET_FS_CHK_PER_INODE;
326 * Compute the size of the current IBlock.
328 * @param height height of the IBlock in the tree (aka overall
329 * number of tree levels minus depth); 0 == DBlock
330 * @param offset current offset in the overall file
331 * @return size of the corresponding IBlock
334 compute_iblock_size (unsigned int height,
342 GNUNET_assert (height > 0);
343 bds = GNUNET_FS_DBLOCK_SIZE; /* number of bytes each CHK at level "i"
345 for (i=0;i<height;i++)
346 bds *= GNUNET_FS_CHK_PER_INODE;
350 /* we were triggered at the end of a full block */
351 ret = GNUNET_FS_CHK_PER_INODE;
355 /* we were triggered at the end of the file */
356 bds /= GNUNET_FS_CHK_PER_INODE;
361 return (uint16_t) (ret * sizeof(struct ContentHashKey));
366 * Compute the offset of the CHK for the
367 * current block in the IBlock above.
369 * @param height height of the IBlock in the tree (aka overall
370 * number of tree levels minus depth); 0 == DBlock
371 * @param offset current offset in the overall file
372 * @return (array of CHKs') offset in the above IBlock
375 compute_chk_offset (unsigned int height,
382 bds = GNUNET_FS_DBLOCK_SIZE; /* number of bytes each CHK at level "i"
384 for (i=0;i<height;i++)
385 bds *= GNUNET_FS_CHK_PER_INODE;
386 GNUNET_assert (0 == (offset % bds));
388 return ret % GNUNET_FS_CHK_PER_INODE;
393 * We are uploading a file or directory; load (if necessary) the next
394 * block into memory, encrypt it and send it to the FS service. Then
395 * continue with the main task.
397 * @param sc overall upload data
398 * @param p specific file or directory for which kblocks
402 publish_content (struct GNUNET_FS_PublishContext *sc,
403 struct GNUNET_FS_FileInformation *p)
405 struct GNUNET_FS_ProgressInfo pi;
406 struct ContentHashKey *mychk;
407 const void *pt_block;
410 char iob[GNUNET_FS_DBLOCK_SIZE];
411 char enc[GNUNET_FS_DBLOCK_SIZE];
412 struct GNUNET_CRYPTO_AesSessionKey sk;
413 struct GNUNET_CRYPTO_AesInitializationVector iv;
416 struct GNUNET_FS_DirectoryBuilder *db;
417 struct GNUNET_FS_FileInformation *dirpos;
421 // FIXME: figure out how to share this code
423 size = (p->is_directory) ? p->data.dir.dir_size : p->data.file.file_size;
424 if (NULL == p->chk_tree)
428 db = GNUNET_FS_directory_builder_create (p->meta);
429 dirpos = p->data.dir.entries;
430 while (NULL != dirpos)
432 if (dirpos->is_directory)
434 raw_data = dirpos->data.dir.dir_data;
435 dirpos->data.dir.dir_data = NULL;
440 if ( (dirpos->data.file.file_size < GNUNET_FS_MAX_INLINE_SIZE) &&
441 (dirpos->data.file.file_size > 0) )
443 raw_data = GNUNET_malloc (dirpos->data.file.file_size);
445 if (dirpos->data.file.file_size !=
446 dirpos->data.file.reader (dirpos->data.file.reader_cls,
448 dirpos->data.file.file_size,
452 GNUNET_free_non_null (emsg);
453 GNUNET_free (raw_data);
458 GNUNET_FS_directory_builder_add (db,
462 GNUNET_free_non_null (raw_data);
463 dirpos = dirpos->next;
465 GNUNET_FS_directory_builder_finish (db,
466 &p->data.dir.dir_size,
467 &p->data.dir.dir_data);
468 size = p->data.dir.dir_size;
470 p->chk_tree_depth = compute_depth (size);
471 p->chk_tree = GNUNET_malloc (p->chk_tree_depth *
472 sizeof (struct ContentHashKey) *
473 GNUNET_FS_CHK_PER_INODE);
474 p->current_depth = p->chk_tree_depth;
476 if (p->current_depth == p->chk_tree_depth)
480 pt_size = GNUNET_MIN(GNUNET_FS_DBLOCK_SIZE,
481 p->data.dir.dir_size - p->publish_offset);
482 dd = p->data.dir.dir_data;
483 pt_block = &dd[p->publish_offset];
487 pt_size = GNUNET_MIN(GNUNET_FS_DBLOCK_SIZE,
488 p->data.file.file_size - p->publish_offset);
491 p->data.file.reader (p->data.file.reader_cls,
497 GNUNET_asprintf (&p->emsg,
498 _("Upload failed: %s"),
501 GNUNET_FS_file_information_sync (p);
502 pi.status = GNUNET_FS_STATUS_PUBLISH_ERROR;
503 make_publish_status (&pi, sc, p);
504 pi.value.publish.eta = GNUNET_TIME_UNIT_FOREVER_REL;
505 pi.value.publish.specifics.error.message = p->emsg;
507 = sc->h->upcb (sc->h->upcb_cls,
509 /* continue with main (to propagate error up) */
511 = GNUNET_SCHEDULER_add_delayed (sc->h->sched,
513 GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
514 GNUNET_SCHEDULER_NO_TASK,
515 GNUNET_TIME_UNIT_ZERO,
525 pt_size = compute_iblock_size (p->chk_tree_depth - p->current_depth,
527 pt_block = &p->chk_tree[p->current_depth *
528 GNUNET_FS_CHK_PER_INODE];
530 off = compute_chk_offset (p->chk_tree_depth - p->current_depth,
532 mychk = &p->chk_tree[(p->current_depth-1)*GNUNET_FS_CHK_PER_INODE+off];
533 GNUNET_CRYPTO_hash (pt_block, pt_size, &mychk->key);
534 GNUNET_CRYPTO_hash_to_aes_key (&mychk->key, &sk, &iv);
535 GNUNET_CRYPTO_aes_encrypt (pt_block,
540 // NOTE: this call (and progress below) is all that really differs
541 // between publish/unindex! Parameterize & move this code!
542 // FIXME: something around here would need to change
544 publish_block (sc, p,
548 (p->current_depth == p->chk_tree_depth)
549 ? GNUNET_DATASTORE_BLOCKTYPE_DBLOCK
550 : GNUNET_DATASTORE_BLOCKTYPE_IBLOCK,
552 if (p->current_depth == p->chk_tree_depth)
554 pi.status = GNUNET_FS_STATUS_PUBLISH_PROGRESS;
555 make_publish_status (&pi, sc, p);
556 pi.value.publish.specifics.progress.data = pt_block;
557 pi.value.publish.specifics.progress.offset = p->publish_offset;
558 pi.value.publish.specifics.progress.data_len = pt_size;
560 = sc->h->upcb (sc->h->upcb_cls,
563 GNUNET_CRYPTO_hash (enc, pt_size, &mychk->query);
564 if (p->current_depth == p->chk_tree_depth)
566 p->publish_offset += pt_size;
567 if ( (p->publish_offset == size) ||
568 (0 == p->publish_offset % (GNUNET_FS_CHK_PER_INODE * GNUNET_FS_DBLOCK_SIZE) ) )
573 if ( (off == GNUNET_FS_CHK_PER_INODE) ||
574 (p->publish_offset == size) )
577 p->current_depth = p->chk_tree_depth;
579 if (0 == p->current_depth)
581 p->chk_uri = GNUNET_malloc (sizeof(struct GNUNET_FS_Uri));
582 p->chk_uri->type = chk;
583 p->chk_uri->data.chk.chk = p->chk_tree[0];
584 p->chk_uri->data.chk.file_length = size;
585 GNUNET_free (p->chk_tree);
592 * Main function that performs the upload.
593 * @param cls "struct GNUNET_FS_PublishContext" identifies the upload
594 * @param tc task context
597 do_upload (void *cls,
598 const struct GNUNET_SCHEDULER_TaskContext *tc)
600 struct GNUNET_FS_PublishContext *sc = cls;
601 struct GNUNET_FS_ProgressInfo pi;
602 struct GNUNET_FS_FileInformation *p;
605 sc->upload_task = GNUNET_SCHEDULER_NO_TASK;
609 /* upload of entire hierarchy complete,
610 publish namespace entries */
616 /* error with current file, abort all
617 related files as well! */
618 while (NULL != p->dir)
620 fn = GNUNET_CONTAINER_meta_data_get_by_type (p->meta,
623 GNUNET_asprintf (&p->emsg,
624 _("Recursive upload failed at `%s'"),
627 GNUNET_FS_file_information_sync (p);
628 pi.status = GNUNET_FS_STATUS_PUBLISH_ERROR;
629 make_publish_status (&pi, sc, p);
630 pi.value.publish.eta = GNUNET_TIME_UNIT_FOREVER_REL;
631 pi.value.publish.specifics.error.message = p->emsg;
633 = sc->h->upcb (sc->h->upcb_cls,
638 if (NULL != p->chk_uri)
640 /* move on to next file */
642 sc->fi_pos = p->next;
645 /* upload of "p" complete, publish KBlocks! */
646 publish_kblocks (sc, p);
649 if ( (!p->is_directory) &&
650 (p->data.file.do_index) )
652 // FIXME: need to pre-compute hash over
653 // the entire file and ask FS to prepare
657 publish_content (sc, p);
662 * Signal the FS's progress function that we are starting
665 * @param cls closure (of type "struct GNUNET_FS_PublishContext*")
666 * @param fi the entry in the publish-structure
667 * @param length length of the file or directory
668 * @param meta metadata for the file or directory (can be modified)
669 * @param uri pointer to the keywords that will be used for this entry (can be modified)
670 * @param anonymity pointer to selected anonymity level (can be modified)
671 * @param priority pointer to selected priority (can be modified)
672 * @param expirationTime pointer to selected expiration time (can be modified)
673 * @param client_info pointer to client context set upon creation (can be modified)
674 * @return GNUNET_OK to continue (always)
677 fip_signal_start(void *cls,
678 struct GNUNET_FS_FileInformation *fi,
680 struct GNUNET_CONTAINER_MetaData *meta,
681 struct GNUNET_FS_Uri **uri,
682 unsigned int *anonymity,
683 unsigned int *priority,
684 struct GNUNET_TIME_Absolute *expirationTime,
687 struct GNUNET_FS_PublishContext *sc = cls;
688 struct GNUNET_FS_ProgressInfo pi;
690 pi.status = GNUNET_FS_STATUS_PUBLISH_START;
691 make_publish_status (&pi, sc, fi);
692 *client_info = sc->h->upcb (sc->h->upcb_cls,
699 * Publish a file or directory.
701 * @param h handle to the file sharing subsystem
702 * @param ctx initial value to use for the '*ctx'
703 * in the callback (for the GNUNET_FS_STATUS_PUBLISH_START event).
704 * @param fi information about the file or directory structure to publish
705 * @param namespace namespace to publish the file in, NULL for no namespace
706 * @param nid identifier to use for the publishd content in the namespace
707 * (can be NULL, must be NULL if namespace is NULL)
708 * @param nuid update-identifier that will be used for future updates
709 * (can be NULL, must be NULL if namespace or nid is NULL)
710 * @return context that can be used to control the publish operation
712 struct GNUNET_FS_PublishContext *
713 GNUNET_FS_publish_start (struct GNUNET_FS_Handle *h,
715 struct GNUNET_FS_FileInformation *fi,
716 struct GNUNET_FS_Namespace *namespace,
720 struct GNUNET_FS_PublishContext *ret;
721 struct GNUNET_FS_FileInformation *p;
722 struct GNUNET_DATASTORE_Handle *dsh;
724 dsh = GNUNET_DATASTORE_connect (h->cfg,
728 ret = GNUNET_malloc (sizeof (struct GNUNET_FS_PublishContext));
731 ret->client_ctx = ctx;
733 ret->namespace = namespace;
734 if (namespace != NULL)
737 GNUNET_assert (NULL != nid);
738 ret->nid = GNUNET_strdup (nid);
740 ret->nuid = GNUNET_strdup (nuid);
742 // FIXME: make upload persistent!
745 GNUNET_FS_file_information_inspect (ret->fi,
748 /* find first leaf, DFS */
750 while ( (p->is_directory) &&
751 (NULL != p->data.dir.entries) )
752 p = p->data.dir.entries;
755 // FIXME: calculate space needed for "fi"
756 // and reserve as first task (then trigger
757 // "do_upload" from that continuation)!
759 = GNUNET_SCHEDULER_add_delayed (h->sched,
761 GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
762 GNUNET_SCHEDULER_NO_TASK,
763 GNUNET_TIME_UNIT_ZERO,
771 * Signal the FS's progress function that we are stopping
774 * @param cls closure (of type "struct GNUNET_FS_PublishContext*")
775 * @param fi the entry in the publish-structure
776 * @param length length of the file or directory
777 * @param meta metadata for the file or directory (can be modified)
778 * @param uri pointer to the keywords that will be used for this entry (can be modified)
779 * @param anonymity pointer to selected anonymity level (can be modified)
780 * @param priority pointer to selected priority (can be modified)
781 * @param expirationTime pointer to selected expiration time (can be modified)
782 * @param client_info pointer to client context set upon creation (can be modified)
783 * @return GNUNET_OK to continue (always)
786 fip_signal_stop(void *cls,
787 struct GNUNET_FS_FileInformation *fi,
789 struct GNUNET_CONTAINER_MetaData *meta,
790 struct GNUNET_FS_Uri **uri,
791 unsigned int *anonymity,
792 unsigned int *priority,
793 struct GNUNET_TIME_Absolute *expirationTime,
796 struct GNUNET_FS_PublishContext*sc = cls;
797 struct GNUNET_FS_ProgressInfo pi;
799 pi.status = GNUNET_FS_STATUS_PUBLISH_STOPPED;
800 make_publish_status (&pi, sc, fi);
801 GNUNET_break (NULL ==
802 sc->h->upcb (sc->h->upcb_cls,
810 * Stop an upload. Will abort incomplete uploads (but
811 * not remove blocks that have already been publishd) or
812 * simply clean up the state for completed uploads.
814 * @param sc context for the upload to stop
817 GNUNET_FS_publish_stop (struct GNUNET_FS_PublishContext *sc)
819 if (GNUNET_SCHEDULER_NO_TASK != sc->upload_task)
820 GNUNET_SCHEDULER_cancel (sc->h->sched, sc->upload_task);
821 // FIXME: remove from persistence DB (?) --- think more about
822 // shutdown / persistent-resume APIs!!!
823 GNUNET_FS_file_information_inspect (sc->fi,
826 if (GNUNET_YES == sc->in_network_wait)
828 sc->in_network_wait = GNUNET_SYSERR;
831 publish_cleanup (sc);
835 /* end of fs_publish.c */