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 if (NULL != sc->namespace)
262 GNUNET_FS_publish_sks (sc->h,
271 // FIXME: release the datastore reserve here!
272 signal_publish_completion (p, sc);
277 * We have uploaded a file or directory; now publish
278 * the KBlocks in the global keyword space so that
279 * it can be found. Then continue with the
282 * @param sc overall upload data
283 * @param p specific file or directory for which kblocks
287 publish_kblocks (struct GNUNET_FS_PublishContext *sc,
288 struct GNUNET_FS_FileInformation *p)
292 // FIXME: use cps here instead...
293 for (i=0;i<p->keywords->data.ksk.keywordCount;i++)
294 GNUNET_FS_publish_ksk (sc->h,
295 p->keywords->data.ksk.keywords[i],
301 GNUNET_FS_file_information_sync (p);
303 signal_publish_completion (p, sc);
305 = GNUNET_SCHEDULER_add_delayed (sc->h->sched,
307 GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
308 GNUNET_SCHEDULER_NO_TASK,
309 GNUNET_TIME_UNIT_ZERO,
316 * Compute the depth of the CHK tree.
318 * @param flen file length for which to compute the depth
319 * @return depth of the tree
322 compute_depth (uint64_t flen)
324 unsigned int treeDepth;
328 fl = GNUNET_FS_DBLOCK_SIZE;
332 if (fl * GNUNET_FS_CHK_PER_INODE < fl)
334 /* integer overflow, this is a HUGE file... */
337 fl = fl * GNUNET_FS_CHK_PER_INODE;
344 * Compute the size of the current IBlock.
346 * @param height height of the IBlock in the tree (aka overall
347 * number of tree levels minus depth); 0 == DBlock
348 * @param offset current offset in the overall file
349 * @return size of the corresponding IBlock
352 compute_iblock_size (unsigned int height,
360 GNUNET_assert (height > 0);
361 bds = GNUNET_FS_DBLOCK_SIZE; /* number of bytes each CHK at level "i"
363 for (i=0;i<height;i++)
364 bds *= GNUNET_FS_CHK_PER_INODE;
368 /* we were triggered at the end of a full block */
369 ret = GNUNET_FS_CHK_PER_INODE;
373 /* we were triggered at the end of the file */
374 bds /= GNUNET_FS_CHK_PER_INODE;
379 return (uint16_t) (ret * sizeof(struct ContentHashKey));
384 * Compute the offset of the CHK for the
385 * current block in the IBlock above.
387 * @param height height of the IBlock in the tree (aka overall
388 * number of tree levels minus depth); 0 == DBlock
389 * @param offset current offset in the overall file
390 * @return (array of CHKs') offset in the above IBlock
393 compute_chk_offset (unsigned int height,
400 bds = GNUNET_FS_DBLOCK_SIZE; /* number of bytes each CHK at level "i"
402 for (i=0;i<height;i++)
403 bds *= GNUNET_FS_CHK_PER_INODE;
404 GNUNET_assert (0 == (offset % bds));
406 return ret % GNUNET_FS_CHK_PER_INODE;
411 * We are uploading a file or directory; load (if necessary) the next
412 * block into memory, encrypt it and send it to the FS service. Then
413 * continue with the main task.
415 * @param sc overall upload data
416 * @param p specific file or directory for which kblocks
420 publish_content (struct GNUNET_FS_PublishContext *sc,
421 struct GNUNET_FS_FileInformation *p)
423 struct GNUNET_FS_ProgressInfo pi;
424 struct ContentHashKey *mychk;
425 const void *pt_block;
428 char iob[GNUNET_FS_DBLOCK_SIZE];
429 char enc[GNUNET_FS_DBLOCK_SIZE];
430 struct GNUNET_CRYPTO_AesSessionKey sk;
431 struct GNUNET_CRYPTO_AesInitializationVector iv;
434 struct GNUNET_FS_DirectoryBuilder *db;
435 struct GNUNET_FS_FileInformation *dirpos;
439 // FIXME: figure out how to share this code
441 size = (p->is_directory) ? p->data.dir.dir_size : p->data.file.file_size;
442 if (NULL == p->chk_tree)
446 db = GNUNET_FS_directory_builder_create (p->meta);
447 dirpos = p->data.dir.entries;
448 while (NULL != dirpos)
450 if (dirpos->is_directory)
452 raw_data = dirpos->data.dir.dir_data;
453 dirpos->data.dir.dir_data = NULL;
458 if ( (dirpos->data.file.file_size < GNUNET_FS_MAX_INLINE_SIZE) &&
459 (dirpos->data.file.file_size > 0) )
461 raw_data = GNUNET_malloc (dirpos->data.file.file_size);
463 if (dirpos->data.file.file_size !=
464 dirpos->data.file.reader (dirpos->data.file.reader_cls,
466 dirpos->data.file.file_size,
470 GNUNET_free_non_null (emsg);
471 GNUNET_free (raw_data);
476 GNUNET_FS_directory_builder_add (db,
480 GNUNET_free_non_null (raw_data);
481 dirpos = dirpos->next;
483 GNUNET_FS_directory_builder_finish (db,
484 &p->data.dir.dir_size,
485 &p->data.dir.dir_data);
486 size = p->data.dir.dir_size;
488 p->chk_tree_depth = compute_depth (size);
489 p->chk_tree = GNUNET_malloc (p->chk_tree_depth *
490 sizeof (struct ContentHashKey) *
491 GNUNET_FS_CHK_PER_INODE);
492 p->current_depth = p->chk_tree_depth;
494 if (p->current_depth == p->chk_tree_depth)
498 pt_size = GNUNET_MIN(GNUNET_FS_DBLOCK_SIZE,
499 p->data.dir.dir_size - p->publish_offset);
500 dd = p->data.dir.dir_data;
501 pt_block = &dd[p->publish_offset];
505 pt_size = GNUNET_MIN(GNUNET_FS_DBLOCK_SIZE,
506 p->data.file.file_size - p->publish_offset);
509 p->data.file.reader (p->data.file.reader_cls,
515 GNUNET_asprintf (&p->emsg,
516 _("Upload failed: %s"),
519 GNUNET_FS_file_information_sync (p);
520 pi.status = GNUNET_FS_STATUS_PUBLISH_ERROR;
521 make_publish_status (&pi, sc, p);
522 pi.value.publish.eta = GNUNET_TIME_UNIT_FOREVER_REL;
523 pi.value.publish.specifics.error.message = p->emsg;
525 = sc->h->upcb (sc->h->upcb_cls,
527 /* continue with main (to propagate error up) */
529 = GNUNET_SCHEDULER_add_delayed (sc->h->sched,
531 GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
532 GNUNET_SCHEDULER_NO_TASK,
533 GNUNET_TIME_UNIT_ZERO,
543 pt_size = compute_iblock_size (p->chk_tree_depth - p->current_depth,
545 pt_block = &p->chk_tree[p->current_depth *
546 GNUNET_FS_CHK_PER_INODE];
548 off = compute_chk_offset (p->chk_tree_depth - p->current_depth,
550 mychk = &p->chk_tree[(p->current_depth-1)*GNUNET_FS_CHK_PER_INODE+off];
551 GNUNET_CRYPTO_hash (pt_block, pt_size, &mychk->key);
552 GNUNET_CRYPTO_hash_to_aes_key (&mychk->key, &sk, &iv);
553 GNUNET_CRYPTO_aes_encrypt (pt_block,
558 // NOTE: this call (and progress below) is all that really differs
559 // between publish/unindex! Parameterize & move this code!
560 // FIXME: something around here would need to change
562 publish_block (sc, p,
566 (p->current_depth == p->chk_tree_depth)
567 ? GNUNET_DATASTORE_BLOCKTYPE_DBLOCK
568 : GNUNET_DATASTORE_BLOCKTYPE_IBLOCK,
570 if (p->current_depth == p->chk_tree_depth)
572 pi.status = GNUNET_FS_STATUS_PUBLISH_PROGRESS;
573 make_publish_status (&pi, sc, p);
574 pi.value.publish.specifics.progress.data = pt_block;
575 pi.value.publish.specifics.progress.offset = p->publish_offset;
576 pi.value.publish.specifics.progress.data_len = pt_size;
578 = sc->h->upcb (sc->h->upcb_cls,
581 GNUNET_CRYPTO_hash (enc, pt_size, &mychk->query);
582 if (p->current_depth == p->chk_tree_depth)
584 p->publish_offset += pt_size;
585 if ( (p->publish_offset == size) ||
586 (0 == p->publish_offset % (GNUNET_FS_CHK_PER_INODE * GNUNET_FS_DBLOCK_SIZE) ) )
591 if ( (off == GNUNET_FS_CHK_PER_INODE) ||
592 (p->publish_offset == size) )
595 p->current_depth = p->chk_tree_depth;
597 if (0 == p->current_depth)
599 p->chk_uri = GNUNET_malloc (sizeof(struct GNUNET_FS_Uri));
600 p->chk_uri->type = chk;
601 p->chk_uri->data.chk.chk = p->chk_tree[0];
602 p->chk_uri->data.chk.file_length = size;
603 GNUNET_free (p->chk_tree);
610 * Main function that performs the upload.
611 * @param cls "struct GNUNET_FS_PublishContext" identifies the upload
612 * @param tc task context
615 do_upload (void *cls,
616 const struct GNUNET_SCHEDULER_TaskContext *tc)
618 struct GNUNET_FS_PublishContext *sc = cls;
619 struct GNUNET_FS_ProgressInfo pi;
620 struct GNUNET_FS_FileInformation *p;
623 sc->upload_task = GNUNET_SCHEDULER_NO_TASK;
627 /* upload of entire hierarchy complete,
628 publish namespace entries */
634 /* error with current file, abort all
635 related files as well! */
636 while (NULL != p->dir)
638 fn = GNUNET_CONTAINER_meta_data_get_by_type (p->meta,
641 GNUNET_asprintf (&p->emsg,
642 _("Recursive upload failed at `%s'"),
645 GNUNET_FS_file_information_sync (p);
646 pi.status = GNUNET_FS_STATUS_PUBLISH_ERROR;
647 make_publish_status (&pi, sc, p);
648 pi.value.publish.eta = GNUNET_TIME_UNIT_FOREVER_REL;
649 pi.value.publish.specifics.error.message = p->emsg;
651 = sc->h->upcb (sc->h->upcb_cls,
656 if (NULL != p->chk_uri)
658 /* move on to next file */
660 sc->fi_pos = p->next;
663 /* upload of "p" complete, publish KBlocks! */
664 publish_kblocks (sc, p);
667 if ( (!p->is_directory) &&
668 (p->data.file.do_index) )
670 // FIXME: need to pre-compute hash over
671 // the entire file and ask FS to prepare
675 publish_content (sc, p);
680 * Signal the FS's progress function that we are starting
683 * @param cls closure (of type "struct GNUNET_FS_PublishContext*")
684 * @param fi the entry in the publish-structure
685 * @param length length of the file or directory
686 * @param meta metadata for the file or directory (can be modified)
687 * @param uri pointer to the keywords that will be used for this entry (can be modified)
688 * @param anonymity pointer to selected anonymity level (can be modified)
689 * @param priority pointer to selected priority (can be modified)
690 * @param expirationTime pointer to selected expiration time (can be modified)
691 * @param client_info pointer to client context set upon creation (can be modified)
692 * @return GNUNET_OK to continue (always)
695 fip_signal_start(void *cls,
696 struct GNUNET_FS_FileInformation *fi,
698 struct GNUNET_CONTAINER_MetaData *meta,
699 struct GNUNET_FS_Uri **uri,
700 unsigned int *anonymity,
701 unsigned int *priority,
702 struct GNUNET_TIME_Absolute *expirationTime,
705 struct GNUNET_FS_PublishContext *sc = cls;
706 struct GNUNET_FS_ProgressInfo pi;
708 pi.status = GNUNET_FS_STATUS_PUBLISH_START;
709 make_publish_status (&pi, sc, fi);
710 *client_info = sc->h->upcb (sc->h->upcb_cls,
717 * Publish a file or directory.
719 * @param h handle to the file sharing subsystem
720 * @param ctx initial value to use for the '*ctx'
721 * in the callback (for the GNUNET_FS_STATUS_PUBLISH_START event).
722 * @param fi information about the file or directory structure to publish
723 * @param namespace namespace to publish the file in, NULL for no namespace
724 * @param nid identifier to use for the publishd content in the namespace
725 * (can be NULL, must be NULL if namespace is NULL)
726 * @param nuid update-identifier that will be used for future updates
727 * (can be NULL, must be NULL if namespace or nid is NULL)
728 * @return context that can be used to control the publish operation
730 struct GNUNET_FS_PublishContext *
731 GNUNET_FS_publish_start (struct GNUNET_FS_Handle *h,
733 struct GNUNET_FS_FileInformation *fi,
734 struct GNUNET_FS_Namespace *namespace,
738 struct GNUNET_FS_PublishContext *ret;
739 struct GNUNET_FS_FileInformation *p;
740 struct GNUNET_DATASTORE_Handle *dsh;
742 dsh = GNUNET_DATASTORE_connect (h->cfg,
746 ret = GNUNET_malloc (sizeof (struct GNUNET_FS_PublishContext));
749 ret->client_ctx = ctx;
751 ret->namespace = namespace;
752 if (namespace != NULL)
755 GNUNET_assert (NULL != nid);
756 ret->nid = GNUNET_strdup (nid);
758 ret->nuid = GNUNET_strdup (nuid);
760 // FIXME: make upload persistent!
763 GNUNET_FS_file_information_inspect (ret->fi,
766 /* find first leaf, DFS */
768 while ( (p->is_directory) &&
769 (NULL != p->data.dir.entries) )
770 p = p->data.dir.entries;
773 // FIXME: calculate space needed for "fi"
774 // and reserve as first task (then trigger
775 // "do_upload" from that continuation)!
777 = GNUNET_SCHEDULER_add_delayed (h->sched,
779 GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
780 GNUNET_SCHEDULER_NO_TASK,
781 GNUNET_TIME_UNIT_ZERO,
789 * Signal the FS's progress function that we are stopping
792 * @param cls closure (of type "struct GNUNET_FS_PublishContext*")
793 * @param fi the entry in the publish-structure
794 * @param length length of the file or directory
795 * @param meta metadata for the file or directory (can be modified)
796 * @param uri pointer to the keywords that will be used for this entry (can be modified)
797 * @param anonymity pointer to selected anonymity level (can be modified)
798 * @param priority pointer to selected priority (can be modified)
799 * @param expirationTime pointer to selected expiration time (can be modified)
800 * @param client_info pointer to client context set upon creation (can be modified)
801 * @return GNUNET_OK to continue (always)
804 fip_signal_stop(void *cls,
805 struct GNUNET_FS_FileInformation *fi,
807 struct GNUNET_CONTAINER_MetaData *meta,
808 struct GNUNET_FS_Uri **uri,
809 unsigned int *anonymity,
810 unsigned int *priority,
811 struct GNUNET_TIME_Absolute *expirationTime,
814 struct GNUNET_FS_PublishContext*sc = cls;
815 struct GNUNET_FS_ProgressInfo pi;
817 pi.status = GNUNET_FS_STATUS_PUBLISH_STOPPED;
818 make_publish_status (&pi, sc, fi);
819 GNUNET_break (NULL ==
820 sc->h->upcb (sc->h->upcb_cls,
828 * Stop an upload. Will abort incomplete uploads (but
829 * not remove blocks that have already been publishd) or
830 * simply clean up the state for completed uploads.
832 * @param sc context for the upload to stop
835 GNUNET_FS_publish_stop (struct GNUNET_FS_PublishContext *sc)
837 if (GNUNET_SCHEDULER_NO_TASK != sc->upload_task)
838 GNUNET_SCHEDULER_cancel (sc->h->sched, sc->upload_task);
839 // FIXME: remove from persistence DB (?) --- think more about
840 // shutdown / persistent-resume APIs!!!
841 GNUNET_FS_file_information_inspect (sc->fi,
844 if (GNUNET_YES == sc->in_network_wait)
846 sc->in_network_wait = GNUNET_SYSERR;
849 publish_cleanup (sc);
854 * Publish a KBlock on GNUnet.
856 * @param h handle to the file sharing subsystem
857 * @param keyword keyword to use
858 * @param meta metadata to use
859 * @param uri URI to refer to in the KBlock
860 * @param expirationTime when the KBlock expires
861 * @param anonymity anonymity level for the KBlock
862 * @param priority priority for the KBlock
864 // FIXME: cps this one
866 GNUNET_FS_publish_ksk (struct GNUNET_FS_Handle *h,
868 struct GNUNET_CONTAINER_MetaData *meta,
869 struct GNUNET_FS_Uri *uri,
870 struct GNUNET_TIME_Absolute expirationTime,
871 unsigned int anonymity,
872 unsigned int priority)
880 * Publish an SBlock on GNUnet.
882 * @param h handle to the file sharing subsystem
883 * @param namespace namespace to publish in
884 * @param identifier identifier to use
885 * @param update update identifier to use
886 * @param meta metadata to use
887 * @param uri URI to refer to in the SBlock
888 * @param expirationTime when the SBlock expires
889 * @param anonymity anonymity level for the SBlock
890 * @param priority priority for the SBlock
892 // FIXME: cps this one
894 GNUNET_FS_publish_sks (struct GNUNET_FS_Handle *h,
895 struct GNUNET_FS_Namespace *namespace,
896 const char *identifier,
898 struct GNUNET_CONTAINER_MetaData *meta,
899 struct GNUNET_FS_Uri *uri,
900 struct GNUNET_TIME_Absolute expirationTime,
901 unsigned int anonymity,
902 unsigned int priority)
907 /* end of fs_publish.c */