2 This file is part of GNUnet.
3 (C) 2001, 2002, 2003, 2004, 2005, 2006, 2008 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.
21 * @file applications/fs/ecrs/download.c
22 * @brief DOWNLOAD helper methods (which do the real work).
23 * @author Christian Grothoff
27 #include "gnunet_protocols.h"
28 #include "gnunet_ecrs_lib.h"
29 #include "gnunet_fs_lib.h"
30 #include "gnunet_identity_lib.h"
31 #include "ecrs_core.h"
36 #define DEBUG_DOWNLOAD GNUNET_NO
39 * Node-specific data (not shared, keep small!). 152 bytes.
40 * Nodes are kept in a doubly-linked list.
45 * Pointer to shared data between all nodes (request manager,
46 * progress data, etc.).
48 struct GNUNET_ECRS_DownloadContext *ctx;
51 * Previous entry in DLL.
61 * What is the GNUNET_EC_ContentHashKey for this block?
63 GNUNET_EC_ContentHashKey chk;
66 * At what offset (on the respective level!) is this
69 unsigned long long offset;
72 * 0 for dblocks, >0 for iblocks.
79 * @brief structure that keeps track of currently pending requests for
82 * Handle to the state of a request manager. Here we keep track of
83 * which queries went out with which priorities and which nodes in
84 * the merkle-tree are waiting for the replies.
86 struct GNUNET_ECRS_DownloadContext
90 * Total number of bytes in the file.
92 unsigned long long total;
95 * Number of bytes already obtained
97 unsigned long long completed;
100 * Starting-offset in file (for partial download)
102 unsigned long long offset;
105 * Length of the download (starting at offset).
107 unsigned long long length;
110 * Time download was started.
112 GNUNET_CronTime startTime;
115 * Doubly linked list of all pending requests (head)
120 * Doubly linked list of all pending requests (tail)
125 * FSLIB context for issuing requests.
127 struct GNUNET_FS_SearchContext *sctx;
130 * Context for error reporting.
132 struct GNUNET_GE_Context *ectx;
135 * Configuration information.
137 struct GNUNET_GC_Configuration *cfg;
145 * Do we exclusively own this sctx?
155 * Main thread running the operation.
157 struct GNUNET_ThreadHandle *main;
160 * Function to call when we make progress.
162 GNUNET_ECRS_DownloadProgressCallback dpcb;
165 * Extra argument to dpcb.
170 * Identity of the peer having the content, or all-zeros
171 * if we don't know of such a peer.
173 GNUNET_PeerIdentity target;
176 * Abort? Flag that can be set at any time
177 * to abort the RM as soon as possible. Set
178 * to GNUNET_YES during orderly shutdown,
179 * set to GNUNET_SYSERR on error.
184 * Do we have a specific peer from which we download
190 * Desired anonymity level for the download.
192 unsigned int anonymityLevel;
195 * The depth of the file-tree.
197 unsigned int treedepth;
202 content_receive_callback (const GNUNET_HashCode * query,
203 const GNUNET_DatastoreValue * reply, void *cls,
204 unsigned long long uid);
208 * Close the files and free the associated resources.
210 * @param self reference to the download context
213 free_request_manager (struct GNUNET_ECRS_DownloadContext *rm)
217 if (rm->abortFlag == GNUNET_NO)
218 rm->abortFlag = GNUNET_YES;
219 if (rm->my_sctx == GNUNET_YES)
220 GNUNET_FS_destroy_search_context (rm->sctx);
222 GNUNET_FS_suspend_search_context (rm->sctx);
223 while (rm->head != NULL)
226 GNUNET_DLL_remove (rm->head, rm->tail, pos);
227 if (rm->my_sctx != GNUNET_YES)
228 GNUNET_FS_stop_search (rm->sctx, &content_receive_callback, pos);
231 if (rm->my_sctx != GNUNET_YES)
232 GNUNET_FS_resume_search_context (rm->sctx);
233 GNUNET_GE_ASSERT (NULL, rm->tail == NULL);
236 if (rm->main != NULL)
237 GNUNET_thread_release_self (rm->main);
238 GNUNET_free_non_null (rm->filename);
246 * @param self reference to the download context
247 * @param level level in the tree to read/write at
248 * @param pos position where to read or write
249 * @param buf where to read from or write to
250 * @param len how many bytes to read or write
251 * @return number of bytes read, GNUNET_SYSERR on error
254 read_from_files (struct GNUNET_ECRS_DownloadContext *self,
256 unsigned long long pos, void *buf, unsigned int len)
258 if ((level > 0) || (self->handle == -1))
259 return GNUNET_SYSERR;
260 LSEEK (self->handle, pos, SEEK_SET);
261 return READ (self->handle, buf, len);
267 * @param self reference to the download context
268 * @param level level in the tree to write to
269 * @param pos position where to write
270 * @param buf where to write to
271 * @param len how many bytes to write
272 * @return number of bytes written, GNUNET_SYSERR on error
275 write_to_files (struct GNUNET_ECRS_DownloadContext *self,
277 unsigned long long pos, void *buf, unsigned int len)
282 return len; /* lie -- no more temps */
283 if (self->handle == -1)
285 LSEEK (self->handle, pos, SEEK_SET);
286 ret = WRITE (self->handle, buf, len);
288 GNUNET_GE_LOG_STRERROR_FILE (self->ectx,
289 GNUNET_GE_ERROR | GNUNET_GE_BULK |
290 GNUNET_GE_USER, "write", self->filename);
295 * Queue a request for execution.
297 * @param rm the request manager struct from createRequestManager
298 * @param node the node to call once a reply is received
301 add_request (struct Node *node)
303 struct GNUNET_ECRS_DownloadContext *rm = node->ctx;
305 GNUNET_DLL_insert (rm->head, rm->tail, node);
306 GNUNET_FS_start_search (rm->sctx,
307 rm->have_target == GNUNET_NO ? NULL : &rm->target,
308 GNUNET_ECRS_BLOCKTYPE_DATA, 1,
311 &content_receive_callback, node);
315 signal_abort (struct GNUNET_ECRS_DownloadContext *rm, const char *msg)
317 rm->abortFlag = GNUNET_SYSERR;
318 if ((rm->head != NULL) && (rm->dpcb != NULL))
319 rm->dpcb (rm->length + 1, 0, 0, 0, msg, 0, rm->dpcbClosure);
320 GNUNET_thread_stop_sleep (rm->main);
326 * @param self the request manager struct from createRequestManager
327 * @param node the block for which the request is canceled
330 delete_node (struct Node *node)
332 struct GNUNET_ECRS_DownloadContext *rm = node->ctx;
334 GNUNET_DLL_remove (rm->head, rm->tail, node);
336 if (rm->head == NULL)
337 GNUNET_thread_stop_sleep (rm->main);
341 * Compute how many bytes of data are stored in
345 get_node_size (const struct Node *node)
349 unsigned long long rsize;
350 unsigned long long spos;
351 unsigned long long epos;
353 GNUNET_GE_ASSERT (node->ctx->ectx, node->offset < node->ctx->total);
354 if (node->level == 0)
356 ret = GNUNET_ECRS_DBLOCK_SIZE;
357 if (node->offset + (unsigned long long) ret > node->ctx->total)
358 ret = (unsigned int) (node->ctx->total - node->offset);
360 GNUNET_GE_LOG (node->ctx->rm->ectx,
361 GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
362 "Node at offset %llu and level %d has size %u\n",
363 node->offset, node->level, ret);
367 rsize = GNUNET_ECRS_DBLOCK_SIZE;
368 for (i = 0; i < node->level - 1; i++)
369 rsize *= GNUNET_ECRS_CHK_PER_INODE;
370 spos = rsize * (node->offset / sizeof (GNUNET_EC_ContentHashKey));
371 epos = spos + rsize * GNUNET_ECRS_CHK_PER_INODE;
372 if (epos > node->ctx->total)
373 epos = node->ctx->total;
374 ret = (epos - spos) / rsize;
375 if (ret * rsize < epos - spos)
376 ret++; /* need to round up! */
378 GNUNET_GE_LOG (node->ctx->rm->ectx,
379 GNUNET_GE_DEBUG | GNUNET_GE_REQUEST | GNUNET_GE_USER,
380 "Node at offset %llu and level %d has size %u\n",
381 node->offset, node->level,
382 ret * sizeof (GNUNET_EC_ContentHashKey));
384 return ret * sizeof (GNUNET_EC_ContentHashKey);
388 * Notify client about progress.
391 notify_client_about_progress (const struct Node *node,
392 const char *data, unsigned int size)
394 struct GNUNET_ECRS_DownloadContext *rm = node->ctx;
397 if ((rm->abortFlag != GNUNET_NO) || (node->level != 0))
399 rm->completed += size;
400 eta = GNUNET_get_time ();
401 if (rm->completed > 0)
402 eta = (GNUNET_CronTime) (rm->startTime +
403 (((double) (eta - rm->startTime) /
404 (double) rm->completed)) *
405 (double) rm->length);
406 if (rm->dpcb != NULL)
407 rm->dpcb (rm->length,
408 rm->completed, eta, node->offset, data, size, rm->dpcbClosure);
413 * DOWNLOAD children of this GNUNET_EC_IBlock.
415 * @param node the node for which the children should be downloaded
416 * @param data data for the node
417 * @param size size of data
419 static void iblock_download_children (const struct Node *node,
420 const char *data, unsigned int size);
423 * Check if self block is already present on the drive. If the block
424 * is a dblock and present, the ProgressModel is notified. If the
425 * block is present and it is an iblock, downloading the children is
428 * Also checks if the block is within the range of blocks
429 * that we are supposed to download. If not, the method
430 * returns as if the block is present but does NOT signal
433 * @param node that is checked for presence
434 * @return GNUNET_YES if present, GNUNET_NO if not.
437 check_node_present (const struct Node *node)
445 size = get_node_size (node);
446 /* first check if node is within range.
447 For now, keeping it simple, we only do
448 this for level-0 nodes */
449 if ((node->level == 0) &&
450 ((node->offset + size < node->ctx->offset) ||
451 (node->offset >= node->ctx->offset + node->ctx->length)))
453 data = GNUNET_malloc (size);
455 res = read_from_files (node->ctx, node->level, node->offset, data, size);
458 GNUNET_hash (data, size, &hc);
459 if (0 == memcmp (&hc, &node->chk.key, sizeof (GNUNET_HashCode)))
461 notify_client_about_progress (node, data, size);
463 iblock_download_children (node, data, size);
472 * DOWNLOAD children of this GNUNET_EC_IBlock.
474 * @param node the node that should be downloaded
477 iblock_download_children (const struct Node *node,
478 const char *data, unsigned int size)
480 struct GNUNET_GE_Context *ectx = node->ctx->ectx;
483 unsigned int childcount;
484 const GNUNET_EC_ContentHashKey *chks;
485 unsigned int levelSize;
486 unsigned long long baseOffset;
488 GNUNET_GE_ASSERT (ectx, node->level > 0);
489 childcount = size / sizeof (GNUNET_EC_ContentHashKey);
490 if (size != childcount * sizeof (GNUNET_EC_ContentHashKey))
492 GNUNET_GE_BREAK (ectx, 0);
495 if (node->level == 1)
497 levelSize = GNUNET_ECRS_DBLOCK_SIZE;
499 node->offset / sizeof (GNUNET_EC_ContentHashKey) *
500 GNUNET_ECRS_DBLOCK_SIZE;
505 sizeof (GNUNET_EC_ContentHashKey) * GNUNET_ECRS_CHK_PER_INODE;
506 baseOffset = node->offset * GNUNET_ECRS_CHK_PER_INODE;
508 chks = (const GNUNET_EC_ContentHashKey *) data;
509 for (i = 0; i < childcount; i++)
511 child = GNUNET_malloc (sizeof (struct Node));
512 child->ctx = node->ctx;
513 child->chk = chks[i];
514 child->offset = baseOffset + i * levelSize;
515 GNUNET_GE_ASSERT (ectx, child->offset < node->ctx->total);
516 child->level = node->level - 1;
517 GNUNET_GE_ASSERT (ectx, (child->level != 0) ||
518 ((child->offset % GNUNET_ECRS_DBLOCK_SIZE) == 0));
519 if (GNUNET_NO == check_node_present (child))
522 GNUNET_free (child); /* done already! */
528 * Decrypts a given data block
530 * @param data represents the data block
531 * @param hashcode represents the key concatenated with the initial
532 * value used in the alg
533 * @param result where to store the result (encrypted block)
534 * @returns GNUNET_OK on success, GNUNET_SYSERR on error
537 decrypt_content (const char *data,
538 unsigned int size, const GNUNET_HashCode * hashcode,
541 GNUNET_AES_InitializationVector iv;
542 GNUNET_AES_SessionKey skey;
544 /* get key and init value from the GNUNET_HashCode */
545 GNUNET_hash_to_AES_key (hashcode, &skey, &iv);
546 return GNUNET_AES_decrypt (&skey, data, size, &iv, result);
550 * We received a GNUNET_EC_ContentHashKey reply for a block. Decrypt. Note
551 * that the caller (fslib) has already aquired the
552 * RM lock (we sometimes aquire it again in callees,
553 * mostly because our callees could be also be theoretically
554 * called from elsewhere).
556 * @param cls the node for which the reply is given, freed in
558 * @param query the query for which reply is the answer
559 * @param reply the reply
560 * @return GNUNET_OK if the reply was valid, GNUNET_SYSERR on error
563 content_receive_callback (const GNUNET_HashCode * query,
564 const GNUNET_DatastoreValue * reply, void *cls,
565 unsigned long long uid)
567 struct Node *node = cls;
568 struct GNUNET_ECRS_DownloadContext *rm = node->ctx;
569 struct GNUNET_GE_Context *ectx = rm->ectx;
574 if (rm->abortFlag != GNUNET_NO)
575 return GNUNET_SYSERR;
576 GNUNET_GE_ASSERT (ectx,
577 0 == memcmp (query, &node->chk.query,
578 sizeof (GNUNET_HashCode)));
579 size = ntohl (reply->size) - sizeof (GNUNET_DatastoreValue);
580 if ((size <= sizeof (GNUNET_EC_DBlock)) ||
581 (size - sizeof (GNUNET_EC_DBlock) != get_node_size (node)))
583 GNUNET_GE_BREAK (ectx, 0);
584 return GNUNET_SYSERR; /* invalid size! */
586 size -= sizeof (GNUNET_EC_DBlock);
587 data = GNUNET_malloc (size);
589 decrypt_content ((const char *)
590 &((const GNUNET_EC_DBlock *) &reply[1])[1], size,
591 &node->chk.key, data))
592 GNUNET_GE_ASSERT (ectx, 0);
593 GNUNET_hash (data, size, &hc);
594 if (0 != memcmp (&hc, &node->chk.key, sizeof (GNUNET_HashCode)))
597 GNUNET_GE_BREAK (ectx, 0);
599 _("Decrypted content does not match key. "
600 "This is either a bug or a maliciously inserted "
601 "file. Download aborted.\n"));
602 return GNUNET_SYSERR;
604 if (size != write_to_files (rm, node->level, node->offset, data, size))
606 GNUNET_GE_LOG_STRERROR (ectx,
607 GNUNET_GE_ERROR | GNUNET_GE_ADMIN |
608 GNUNET_GE_USER | GNUNET_GE_BULK, "WRITE");
609 signal_abort (rm, _("IO error."));
610 return GNUNET_SYSERR;
612 notify_client_about_progress (node, data, size);
614 iblock_download_children (node, data, size);
616 /* request satisfied, stop requesting! */
623 * Helper function to sanitize filename
624 * and create necessary directories.
627 get_real_download_filename (struct GNUNET_GE_Context *ectx,
628 const char *filename)
635 if ((filename[strlen (filename) - 1] == '/') ||
636 (filename[strlen (filename) - 1] == '\\'))
639 GNUNET_malloc (strlen (filename) + strlen (GNUNET_DIRECTORY_EXT));
640 strcpy (realFN, filename);
641 realFN[strlen (filename) - 1] = '\0';
642 strcat (realFN, GNUNET_DIRECTORY_EXT);
646 realFN = GNUNET_strdup (filename);
648 path = GNUNET_malloc (strlen (realFN) * strlen (GNUNET_DIRECTORY_EXT) + 1);
649 strcpy (path, realFN);
653 if (*pos == DIR_SEPARATOR)
656 if ((0 == STAT (path, &buf)) && (!S_ISDIR (buf.st_mode)))
658 *pos = DIR_SEPARATOR;
659 memmove (pos + strlen (GNUNET_DIRECTORY_EXT),
662 GNUNET_DIRECTORY_EXT, strlen (GNUNET_DIRECTORY_EXT));
663 pos += strlen (GNUNET_DIRECTORY_EXT);
667 *pos = DIR_SEPARATOR;
672 GNUNET_free (realFN);
676 /* ***************** main method **************** */
680 * Download parts of a file. Note that this will store
681 * the blocks at the respective offset in the given file.
682 * Also, the download is still using the blocking of the
683 * underlying ECRS encoding. As a result, the download
684 * may *write* outside of the given boundaries (if offset
685 * and length do not match the 32k ECRS block boundaries).
688 * This function should be used to focus a download towards a
689 * particular portion of the file (optimization), not to strictly
690 * limit the download to exactly those bytes.
692 * @param uri the URI of the file (determines what to download)
693 * @param filename where to store the file
694 * @param no_temporaries set to GNUNET_YES to disallow generation of temporary files
695 * @param start starting offset
696 * @param length length of the download (starting at offset)
698 struct GNUNET_ECRS_DownloadContext *
699 GNUNET_ECRS_file_download_partial_start (struct GNUNET_GE_Context *ectx,
700 struct GNUNET_GC_Configuration *cfg,
701 struct GNUNET_FS_SearchContext *sc,
702 const struct GNUNET_ECRS_URI *uri,
703 const char *filename,
704 unsigned long long offset,
705 unsigned long long length,
706 unsigned int anonymityLevel,
708 GNUNET_ECRS_DownloadProgressCallback
709 dpcb, void *dpcbClosure)
711 struct GNUNET_ECRS_DownloadContext *rm;
716 if ((!GNUNET_ECRS_uri_test_chk (uri)) && (!GNUNET_ECRS_uri_test_loc (uri)))
718 GNUNET_GE_BREAK (ectx, 0);
721 rm = GNUNET_malloc (sizeof (struct GNUNET_ECRS_DownloadContext));
722 memset (rm, 0, sizeof (struct GNUNET_ECRS_DownloadContext));
725 rm->sctx = GNUNET_FS_create_search_context (ectx, cfg);
726 if (rm->sctx == NULL)
731 rm->my_sctx = GNUNET_YES;
736 rm->my_sctx = GNUNET_NO;
740 rm->startTime = GNUNET_get_time ();
741 rm->anonymityLevel = anonymityLevel;
745 rm->dpcbClosure = dpcbClosure;
746 rm->main = GNUNET_thread_get_self ();
747 rm->total = GNUNET_ntohll (uri->data.fi.file_length);
749 filename != NULL ? get_real_download_filename (ectx, filename) : NULL;
751 if ((rm->filename != NULL) &&
753 GNUNET_disk_directory_create_for_file (ectx, rm->filename)))
755 free_request_manager (rm);
760 if (rm->filename != NULL)
762 ret = GNUNET_disk_file_open (ectx,
764 O_CREAT | O_WRONLY | O_TRUNC,
768 free_request_manager (rm);
773 dpcb (0, 0, rm->startTime, 0, NULL, 0, dpcbClosure);
774 free_request_manager (rm);
777 rm->treedepth = GNUNET_ECRS_compute_depth (rm->total);
778 if ((NULL != rm->filename) &&
779 ((0 == STAT (rm->filename, &buf))
780 && ((size_t) buf.st_size > rm->total)))
782 /* if exists and oversized, truncate */
783 if (truncate (rm->filename, rm->total) != 0)
785 GNUNET_GE_LOG_STRERROR_FILE (ectx,
786 GNUNET_GE_ERROR | GNUNET_GE_ADMIN |
787 GNUNET_GE_BULK, "truncate",
789 free_request_manager (rm);
793 if (rm->filename != NULL)
795 rm->handle = GNUNET_disk_file_open (ectx,
801 free_request_manager (rm);
807 if (GNUNET_ECRS_uri_test_loc (uri))
809 GNUNET_hash (&uri->data.loc.peer, sizeof (GNUNET_RSA_PublicKey),
810 &rm->target.hashPubKey);
811 rm->have_target = GNUNET_YES;
813 top = GNUNET_malloc (sizeof (struct Node));
814 memset (top, 0, sizeof (struct Node));
816 top->chk = uri->data.fi.chk;
818 top->level = rm->treedepth;
819 if (GNUNET_NO == check_node_present (top))
827 GNUNET_ECRS_file_download_partial_stop (struct GNUNET_ECRS_DownloadContext
833 free_request_manager (rm);
834 if (ret == GNUNET_NO)
835 ret = GNUNET_OK; /* normal termination */
840 * Download parts of a file. Note that this will store
841 * the blocks at the respective offset in the given file.
842 * Also, the download is still using the blocking of the
843 * underlying ECRS encoding. As a result, the download
844 * may *write* outside of the given boundaries (if offset
845 * and length do not match the 32k ECRS block boundaries).
848 * This function should be used to focus a download towards a
849 * particular portion of the file (optimization), not to strictly
850 * limit the download to exactly those bytes.
852 * @param uri the URI of the file (determines what to download)
853 * @param filename where to store the file
854 * @param no_temporaries set to GNUNET_YES to disallow generation of temporary files
855 * @param start starting offset
856 * @param length length of the download (starting at offset)
859 GNUNET_ECRS_file_download_partial (struct GNUNET_GE_Context *ectx,
860 struct GNUNET_GC_Configuration *cfg,
861 const struct GNUNET_ECRS_URI *uri,
862 const char *filename,
863 unsigned long long offset,
864 unsigned long long length,
865 unsigned int anonymityLevel,
867 GNUNET_ECRS_DownloadProgressCallback dpcb,
869 GNUNET_ECRS_TestTerminate tt,
872 struct GNUNET_ECRS_DownloadContext *rm;
877 rm = GNUNET_ECRS_file_download_partial_start (ectx,
888 return GNUNET_SYSERR;
889 while ((GNUNET_OK == tt (ttClosure)) &&
890 (GNUNET_YES != GNUNET_shutdown_test ()) &&
891 (rm->abortFlag == GNUNET_NO) && (rm->head != NULL))
892 GNUNET_thread_sleep (5 * GNUNET_CRON_SECONDS);
893 ret = GNUNET_ECRS_file_download_partial_stop (rm);
898 * Download a file (simplified API).
900 * @param uri the URI of the file (determines what to download)
901 * @param filename where to store the file
904 GNUNET_ECRS_file_download (struct GNUNET_GE_Context *ectx,
905 struct GNUNET_GC_Configuration *cfg,
906 const struct GNUNET_ECRS_URI *uri,
907 const char *filename,
908 unsigned int anonymityLevel,
909 GNUNET_ECRS_DownloadProgressCallback dpcb,
910 void *dpcbClosure, GNUNET_ECRS_TestTerminate tt,
913 return GNUNET_ECRS_file_download_partial (ectx,
918 GNUNET_ECRS_uri_get_file_size
919 (uri), anonymityLevel, GNUNET_NO,
920 dpcb, dpcbClosure, tt, ttClosure);
923 /* end of download.c */