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 * Function called by the datastore API with
111 * the result from the PUT request.
113 * @param cls our closure
114 * @param success GNUNET_OK on success
115 * @param msg error message (or NULL)
118 ds_put_cont (void *cls,
122 struct PutContCtx *pcc = cls;
123 struct GNUNET_FS_ProgressInfo pi;
125 if (GNUNET_OK != success)
127 GNUNET_asprintf (&pcc->p->emsg,
128 _("Upload failed: %s"),
130 GNUNET_FS_file_information_sync (pcc->p);
131 pi.status = GNUNET_FS_STATUS_PUBLISH_ERROR;
132 make_publish_status (&pi, pcc->sc, pcc->p);
133 pi.value.publish.eta = GNUNET_TIME_UNIT_FOREVER_REL;
134 pi.value.publish.specifics.error.message = pcc->p->emsg;
136 = pcc->sc->h->upcb (pcc->sc->h->upcb_cls,
140 GNUNET_FS_file_information_sync (pcc->p);
141 if (NULL != pcc->cont)
143 = GNUNET_SCHEDULER_add_delayed (pcc->sc->h->sched,
145 GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
146 GNUNET_SCHEDULER_NO_TASK,
147 GNUNET_TIME_UNIT_ZERO,
155 * We need to publish a specific block. Do it. Then continue with
158 * @param sc overall upload data
159 * @param p file that the block belongs to (needed for options!)
160 * @param query what the block should be indexed under
161 * @param blk encoded block to publish
162 * @param blk_size size of the block
163 * @param blk_type type of the block
164 * @param cont function to run when done
167 publish_block (struct GNUNET_FS_PublishContext *sc,
168 struct GNUNET_FS_FileInformation *p,
169 const GNUNET_HashCode *query,
173 GNUNET_SCHEDULER_Task cont)
175 struct PutContCtx * dpc_cls;
177 dpc_cls = GNUNET_malloc(sizeof(struct PutContCtx));
178 dpc_cls->cont = cont;
181 // FIXME: need to do something to "sc" to mark
182 // that "sc" can not be freed right now due to this
183 // pending, scheduled operation for which we don't have
185 GNUNET_DATASTORE_put (sc->dsh,
194 GNUNET_CONSTANTS_SERVICE_TIMEOUT,
201 * Generate the callback that signals clients
202 * that a file (or directory) has been completely
205 * @param p the completed upload
206 * @param sc context of the publication
209 signal_publish_completion (struct GNUNET_FS_FileInformation *p,
210 struct GNUNET_FS_PublishContext *sc)
212 struct GNUNET_FS_ProgressInfo pi;
214 pi.status = GNUNET_FS_STATUS_PUBLISH_COMPLETED;
215 make_publish_status (&pi, sc, p);
216 pi.value.publish.eta = GNUNET_TIME_UNIT_ZERO;
217 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;
482 = sc->h->upcb (sc->h->upcb_cls,
484 /* continue with main (to propagate error up) */
486 = GNUNET_SCHEDULER_add_delayed (sc->h->sched,
488 GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
489 GNUNET_SCHEDULER_NO_TASK,
490 GNUNET_TIME_UNIT_ZERO,
500 pt_size = compute_iblock_size (p->chk_tree_depth - p->current_depth,
502 pt_block = &p->chk_tree[p->current_depth *
503 GNUNET_FS_CHK_PER_INODE];
505 off = compute_chk_offset (p->chk_tree_depth - p->current_depth,
507 mychk = &p->chk_tree[(p->current_depth-1)*GNUNET_FS_CHK_PER_INODE+off];
508 GNUNET_CRYPTO_hash (pt_block, pt_size, &mychk->key);
509 GNUNET_CRYPTO_hash_to_aes_key (&mychk->key, &sk, &iv);
510 GNUNET_CRYPTO_aes_encrypt (pt_block,
515 // NOTE: this call (and progress below) is all that really differs
516 // between publish/unindex! Parameterize & move this code!
517 // FIXME: something around here would need to change
519 publish_block (sc, p,
523 (p->current_depth == p->chk_tree_depth)
524 ? GNUNET_DATASTORE_BLOCKTYPE_DBLOCK
525 : GNUNET_DATASTORE_BLOCKTYPE_IBLOCK,
527 if (p->current_depth == p->chk_tree_depth)
529 pi.status = GNUNET_FS_STATUS_PUBLISH_PROGRESS;
530 make_publish_status (&pi, sc, p);
531 pi.value.publish.specifics.progress.data = pt_block;
532 pi.value.publish.specifics.progress.offset = p->publish_offset;
533 pi.value.publish.specifics.progress.data_len = pt_size;
535 = sc->h->upcb (sc->h->upcb_cls,
538 GNUNET_CRYPTO_hash (enc, pt_size, &mychk->query);
539 if (p->current_depth == p->chk_tree_depth)
541 p->publish_offset += pt_size;
542 if ( (p->publish_offset == size) ||
543 (0 == p->publish_offset % (GNUNET_FS_CHK_PER_INODE * GNUNET_FS_DBLOCK_SIZE) ) )
548 if ( (off == GNUNET_FS_CHK_PER_INODE) ||
549 (p->publish_offset == size) )
552 p->current_depth = p->chk_tree_depth;
554 if (0 == p->current_depth)
556 p->chk_uri = GNUNET_malloc (sizeof(struct GNUNET_FS_Uri));
557 p->chk_uri->type = chk;
558 p->chk_uri->data.chk.chk = p->chk_tree[0];
559 p->chk_uri->data.chk.file_length = size;
560 GNUNET_free (p->chk_tree);
567 * Main function that performs the upload.
568 * @param cls "struct GNUNET_FS_PublishContext" identifies the upload
569 * @param tc task context
572 do_upload (void *cls,
573 const struct GNUNET_SCHEDULER_TaskContext *tc)
575 struct GNUNET_FS_PublishContext *sc = cls;
576 struct GNUNET_FS_ProgressInfo pi;
577 struct GNUNET_FS_FileInformation *p;
580 sc->upload_task = GNUNET_SCHEDULER_NO_TASK;
584 /* upload of entire hierarchy complete,
585 publish namespace entries */
591 /* error with current file, abort all
592 related files as well! */
593 while (NULL != p->dir)
595 fn = GNUNET_CONTAINER_meta_data_get_by_type (p->meta,
598 GNUNET_asprintf (&p->emsg,
599 _("Recursive upload failed at `%s'"),
602 GNUNET_FS_file_information_sync (p);
603 pi.status = GNUNET_FS_STATUS_PUBLISH_ERROR;
604 make_publish_status (&pi, sc, p);
605 pi.value.publish.eta = GNUNET_TIME_UNIT_FOREVER_REL;
606 pi.value.publish.specifics.error.message = p->emsg;
608 = sc->h->upcb (sc->h->upcb_cls,
613 if (NULL != p->chk_uri)
615 /* move on to next file */
617 sc->fi_pos = p->next;
620 /* upload of "p" complete, publish KBlocks! */
621 publish_kblocks (sc, p);
624 if ( (!p->is_directory) &&
625 (p->data.file.do_index) )
627 // FIXME: need to pre-compute hash over
628 // the entire file and ask FS to prepare
632 publish_content (sc, p);
637 * Signal the FS's progress function that we are starting
640 * @param cls closure (of type "struct GNUNET_FS_PublishContext*")
641 * @param fi the entry in the publish-structure
642 * @param length length of the file or directory
643 * @param meta metadata for the file or directory (can be modified)
644 * @param uri pointer to the keywords that will be used for this entry (can be modified)
645 * @param anonymity pointer to selected anonymity level (can be modified)
646 * @param priority pointer to selected priority (can be modified)
647 * @param expirationTime pointer to selected expiration time (can be modified)
648 * @param client_info pointer to client context set upon creation (can be modified)
649 * @return GNUNET_OK to continue (always)
652 fip_signal_start(void *cls,
653 struct GNUNET_FS_FileInformation *fi,
655 struct GNUNET_CONTAINER_MetaData *meta,
656 struct GNUNET_FS_Uri **uri,
657 unsigned int *anonymity,
658 unsigned int *priority,
659 struct GNUNET_TIME_Absolute *expirationTime,
662 struct GNUNET_FS_PublishContext *sc = cls;
663 struct GNUNET_FS_ProgressInfo pi;
665 pi.status = GNUNET_FS_STATUS_PUBLISH_START;
666 make_publish_status (&pi, sc, fi);
667 *client_info = sc->h->upcb (sc->h->upcb_cls,
674 * Publish a file or directory.
676 * @param h handle to the file sharing subsystem
677 * @param ctx initial value to use for the '*ctx'
678 * in the callback (for the GNUNET_FS_STATUS_PUBLISH_START event).
679 * @param fi information about the file or directory structure to publish
680 * @param namespace namespace to publish the file in, NULL for no namespace
681 * @param nid identifier to use for the publishd content in the namespace
682 * (can be NULL, must be NULL if namespace is NULL)
683 * @param nuid update-identifier that will be used for future updates
684 * (can be NULL, must be NULL if namespace or nid is NULL)
685 * @return context that can be used to control the publish operation
687 struct GNUNET_FS_PublishContext *
688 GNUNET_FS_publish_start (struct GNUNET_FS_Handle *h,
690 struct GNUNET_FS_FileInformation *fi,
691 struct GNUNET_FS_Namespace *namespace,
695 struct GNUNET_FS_PublishContext *ret;
696 struct GNUNET_FS_FileInformation *p;
697 struct GNUNET_DATASTORE_Handle *dsh;
699 dsh = GNUNET_DATASTORE_connect (h->cfg,
703 ret = GNUNET_malloc (sizeof (struct GNUNET_FS_PublishContext));
706 ret->client_ctx = ctx;
708 ret->namespace = namespace;
709 if (namespace != NULL)
712 GNUNET_assert (NULL != nid);
713 ret->nid = GNUNET_strdup (nid);
715 ret->nuid = GNUNET_strdup (nuid);
717 // FIXME: make upload persistent!
720 GNUNET_FS_file_information_inspect (ret->fi,
723 /* find first leaf, DFS */
725 while ( (p->is_directory) &&
726 (NULL != p->data.dir.entries) )
727 p = p->data.dir.entries;
730 // FIXME: calculate space needed for "fi"
731 // and reserve as first task (then trigger
732 // "do_upload" from that continuation)!
734 = GNUNET_SCHEDULER_add_delayed (h->sched,
736 GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
737 GNUNET_SCHEDULER_NO_TASK,
738 GNUNET_TIME_UNIT_ZERO,
746 * Signal the FS's progress function that we are stopping
749 * @param cls closure (of type "struct GNUNET_FS_PublishContext*")
750 * @param fi the entry in the publish-structure
751 * @param length length of the file or directory
752 * @param meta metadata for the file or directory (can be modified)
753 * @param uri pointer to the keywords that will be used for this entry (can be modified)
754 * @param anonymity pointer to selected anonymity level (can be modified)
755 * @param priority pointer to selected priority (can be modified)
756 * @param expirationTime pointer to selected expiration time (can be modified)
757 * @param client_info pointer to client context set upon creation (can be modified)
758 * @return GNUNET_OK to continue (always)
761 fip_signal_stop(void *cls,
762 struct GNUNET_FS_FileInformation *fi,
764 struct GNUNET_CONTAINER_MetaData *meta,
765 struct GNUNET_FS_Uri **uri,
766 unsigned int *anonymity,
767 unsigned int *priority,
768 struct GNUNET_TIME_Absolute *expirationTime,
771 struct GNUNET_FS_PublishContext*sc = cls;
772 struct GNUNET_FS_ProgressInfo pi;
774 pi.status = GNUNET_FS_STATUS_PUBLISH_STOPPED;
775 make_publish_status (&pi, sc, fi);
776 GNUNET_break (NULL ==
777 sc->h->upcb (sc->h->upcb_cls,
785 * Stop an upload. Will abort incomplete uploads (but
786 * not remove blocks that have already been publishd) or
787 * simply clean up the state for completed uploads.
789 * @param sc context for the upload to stop
792 GNUNET_FS_publish_stop (struct GNUNET_FS_PublishContext *sc)
794 if (GNUNET_SCHEDULER_NO_TASK != sc->upload_task)
795 GNUNET_SCHEDULER_cancel (sc->h->sched, sc->upload_task);
796 // FIXME: remove from persistence DB (?) --- think more about
797 // shutdown / persistent-resume APIs!!!
798 GNUNET_FS_file_information_inspect (sc->fi,
801 GNUNET_FS_file_information_destroy (sc->fi, NULL, NULL);
802 GNUNET_FS_namespace_delete (sc->namespace, GNUNET_NO);
803 GNUNET_free_non_null (sc->nid);
804 GNUNET_free_non_null (sc->nuid);
805 GNUNET_DATASTORE_disconnect (sc->dsh, GNUNET_NO);
809 /* end of fs_publish.c */