+ else
+ {
+ GNUNET_asprintf (&full_name, "%s%s%s", dn, DIR_SEPARATOR_STR, sfn);
+ }
+ GNUNET_free (sfn);
+ GNUNET_free (dn);
+ }
+ if ((NULL != full_name) &&
+ (GNUNET_OK != GNUNET_DISK_directory_create_for_file (full_name)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ _
+ ("Failed to create directory for recursive download of `%s'\n"),
+ full_name);
+ GNUNET_free (full_name);
+ GNUNET_free_non_null (fn);
+ return;
+ }
+
+ temp_name = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Triggering recursive download of size %llu with %u bytes MD\n",
+ (unsigned long long) GNUNET_FS_uri_chk_get_file_size (uri),
+ (unsigned int)
+ GNUNET_CONTAINER_meta_data_get_serialized_size (meta));
+ GNUNET_FS_download_start (dc->h, uri, meta, full_name, temp_name, 0,
+ GNUNET_FS_uri_chk_get_file_size (uri),
+ dc->anonymity, dc->options, NULL, dc);
+ GNUNET_free_non_null (full_name);
+ GNUNET_free_non_null (temp_name);
+ GNUNET_free_non_null (fn);
+}
+
+
+/**
+ * (recursively) free download request structure
+ *
+ * @param dr request to free
+ */
+void
+GNUNET_FS_free_download_request_ (struct DownloadRequest *dr)
+{
+ unsigned int i;
+
+ if (NULL == dr)
+ return;
+ for (i = 0; i < dr->num_children; i++)
+ GNUNET_FS_free_download_request_ (dr->children[i]);
+ GNUNET_free_non_null (dr->children);
+ GNUNET_free (dr);
+}
+
+
+/**
+ * Iterator over entries in the pending requests in the 'active' map for the
+ * reply that we just got.
+ *
+ * @param cls closure (our 'struct ProcessResultClosure')
+ * @param key query for the given value / request
+ * @param value value in the hash map (a 'struct DownloadRequest')
+ * @return GNUNET_YES (we should continue to iterate); unless serious error
+ */
+static int
+process_result_with_request (void *cls, const GNUNET_HashCode * key,
+ void *value)
+{
+ struct ProcessResultClosure *prc = cls;
+ struct DownloadRequest *dr = value;
+ struct GNUNET_FS_DownloadContext *dc = prc->dc;
+ struct DownloadRequest *drc;
+ struct GNUNET_DISK_FileHandle *fh = NULL;
+ struct GNUNET_CRYPTO_AesSessionKey skey;
+ struct GNUNET_CRYPTO_AesInitializationVector iv;
+ char pt[prc->size];
+ struct GNUNET_FS_ProgressInfo pi;
+ uint64_t off;
+ size_t bs;
+ size_t app;
+ int i;
+ struct ContentHashKey *chkarr;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received %u byte block `%s' matching pending request at depth %u and offset %llu/%llu\n",
+ (unsigned int) prc->size,
+ GNUNET_h2s (key), dr->depth, (unsigned long long) dr->offset,
+ (unsigned long long) GNUNET_ntohll (dc->uri->data.
+ chk.file_length));
+ bs = GNUNET_FS_tree_calculate_block_size (GNUNET_ntohll
+ (dc->uri->data.chk.file_length),
+ dr->offset, dr->depth);
+ if (prc->size != bs)
+ {
+ GNUNET_asprintf (&dc->emsg,
+ _
+ ("Internal error or bogus download URI (expected %u bytes at depth %u and offset %llu/%llu, got %u bytes)"),
+ bs, dr->depth, (unsigned long long) dr->offset,
+ (unsigned long long) GNUNET_ntohll (dc->uri->data.
+ chk.file_length),
+ prc->size);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "%s\n", dc->emsg);
+ while (NULL != dr->parent)
+ {
+ dr->state = BRS_ERROR;
+ dr = dr->parent;
+ }
+ dr->state = BRS_ERROR;
+ goto signal_error;
+ }
+
+ (void) GNUNET_CONTAINER_multihashmap_remove (dc->active, &prc->query, dr);
+ if (GNUNET_YES == dr->is_pending)
+ {
+ GNUNET_CONTAINER_DLL_remove (dc->pending_head, dc->pending_tail, dr);
+ dr->is_pending = GNUNET_NO;
+ }
+
+ GNUNET_CRYPTO_hash_to_aes_key (&dr->chk.key, &skey, &iv);
+ if (-1 == GNUNET_CRYPTO_aes_decrypt (prc->data, prc->size, &skey, &iv, pt))
+ {
+ GNUNET_break (0);
+ dc->emsg = GNUNET_strdup (_("internal error decrypting content"));
+ goto signal_error;
+ }
+ off =
+ compute_disk_offset (GNUNET_ntohll (dc->uri->data.chk.file_length),
+ dr->offset, dr->depth);
+ /* save to disk */
+ if ((GNUNET_YES == prc->do_store) &&
+ ((NULL != dc->filename) || (is_recursive_download (dc))) &&
+ ((dr->depth == dc->treedepth) ||
+ (0 == (dc->options & GNUNET_FS_DOWNLOAD_NO_TEMPORARIES))))
+ {
+ fh = GNUNET_DISK_file_open (NULL != dc->filename
+ ? dc->filename : dc->temp_filename,
+ GNUNET_DISK_OPEN_READWRITE |
+ GNUNET_DISK_OPEN_CREATE,
+ GNUNET_DISK_PERM_USER_READ |
+ GNUNET_DISK_PERM_USER_WRITE |
+ GNUNET_DISK_PERM_GROUP_READ |
+ GNUNET_DISK_PERM_OTHER_READ);
+ if (NULL == fh)
+ {
+ GNUNET_asprintf (&dc->emsg,
+ _("Download failed: could not open file `%s': %s"),
+ dc->filename, STRERROR (errno));
+ goto signal_error;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Saving decrypted block to disk at offset %llu\n",
+ (unsigned long long) off);
+ if ((off != GNUNET_DISK_file_seek (fh, off, GNUNET_DISK_SEEK_SET)))
+ {
+ GNUNET_asprintf (&dc->emsg,
+ _("Failed to seek to offset %llu in file `%s': %s"),
+ (unsigned long long) off, dc->filename,
+ STRERROR (errno));
+ goto signal_error;
+ }
+ if (prc->size != GNUNET_DISK_file_write (fh, pt, prc->size))
+ {
+ GNUNET_asprintf (&dc->emsg,
+ _
+ ("Failed to write block of %u bytes at offset %llu in file `%s': %s"),
+ (unsigned int) prc->size, (unsigned long long) off,
+ dc->filename, STRERROR (errno));
+ goto signal_error;
+ }
+ GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
+ fh = NULL;
+ }
+
+ if (0 == dr->depth)
+ {
+ /* DBLOCK, update progress and try recursion if applicable */
+ app = prc->size;
+ if (dr->offset < dc->offset)
+ {
+ /* starting offset begins in the middle of pt,
+ * do not count first bytes as progress */
+ GNUNET_assert (app > (dc->offset - dr->offset));
+ app -= (dc->offset - dr->offset);
+ }
+ if (dr->offset + prc->size > dc->offset + dc->length)
+ {
+ /* end of block is after relevant range,
+ * do not count last bytes as progress */
+ GNUNET_assert (app >
+ (dr->offset + prc->size) - (dc->offset + dc->length));
+ app -= (dr->offset + prc->size) - (dc->offset + dc->length);
+ }
+ dc->completed += app;
+
+ /* do recursive download if option is set and either meta data
+ * says it is a directory or if no meta data is given AND filename
+ * ends in '.gnd' (top-level case) */
+ if (is_recursive_download (dc))
+ GNUNET_FS_directory_list_contents (prc->size, pt, off,
+ &trigger_recursive_download, dc);
+ }
+ GNUNET_assert (dc->completed <= dc->length);
+ dr->state = BRS_DOWNLOAD_DOWN;
+ pi.status = GNUNET_FS_STATUS_DOWNLOAD_PROGRESS;
+ pi.value.download.specifics.progress.data = pt;
+ pi.value.download.specifics.progress.offset = dr->offset;
+ pi.value.download.specifics.progress.data_len = prc->size;
+ pi.value.download.specifics.progress.depth = dr->depth;
+ pi.value.download.specifics.progress.trust_offered = 0;
+ if (prc->last_transmission.abs_value != GNUNET_TIME_UNIT_FOREVER_ABS.abs_value)
+ pi.value.download.specifics.progress.block_download_duration
+ = GNUNET_TIME_absolute_get_duration (prc->last_transmission);
+ else
+ pi.value.download.specifics.progress.block_download_duration
+ = GNUNET_TIME_UNIT_ZERO; /* found locally */
+ GNUNET_FS_download_make_status_ (&pi, dc);
+ if (0 == dr->depth)
+ propagate_up (dr);
+
+ if (dc->completed == dc->length)
+ {
+ /* download completed, signal */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Download completed, truncating file to desired length %llu\n",
+ (unsigned long long) GNUNET_ntohll (dc->uri->data.
+ chk.file_length));
+ /* truncate file to size (since we store IBlocks at the end) */
+ if (NULL != dc->filename)
+ {
+ if (0 !=
+ truncate (dc->filename,
+ GNUNET_ntohll (dc->uri->data.chk.file_length)))
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "truncate",
+ dc->filename);
+ }
+ GNUNET_assert (0 == dr->depth);
+ check_completed (dc);
+ }
+ if (0 == dr->depth)
+ {
+ /* bottom of the tree, no child downloads possible, just sync */
+ GNUNET_FS_download_sync_ (dc);
+ return GNUNET_YES;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Triggering downloads of children (this block was at depth %u and offset %llu)\n",
+ dr->depth, (unsigned long long) dr->offset);
+ GNUNET_assert (0 == (prc->size % sizeof (struct ContentHashKey)));
+ chkarr = (struct ContentHashKey *) pt;
+ for (i = dr->num_children - 1; i >= 0; i--)
+ {
+ drc = dr->children[i];
+ switch (drc->state)
+ {
+ case BRS_INIT:
+ if ((drc->chk_idx + 1) * sizeof (struct ContentHashKey) > prc->size)
+ {
+ /* 'chkarr' does not have enough space for this chk_idx;
+ internal error! */
+ GNUNET_break (0);
+ dc->emsg = GNUNET_strdup (_("internal error decoding tree"));
+ goto signal_error;
+ }
+ drc->chk = chkarr[drc->chk_idx];
+ drc->state = BRS_CHK_SET;
+ if (GNUNET_YES == dc->issue_requests)
+ schedule_block_download (dc, drc);
+ break;
+ case BRS_RECONSTRUCT_DOWN:
+ GNUNET_assert (0);
+ break;
+ case BRS_RECONSTRUCT_META_UP:
+ GNUNET_assert (0);
+ break;
+ case BRS_RECONSTRUCT_UP:
+ GNUNET_assert (0);
+ break;
+ case BRS_CHK_SET:
+ GNUNET_assert (0);
+ break;
+ case BRS_DOWNLOAD_DOWN:
+ GNUNET_assert (0);
+ break;
+ case BRS_DOWNLOAD_UP:
+ GNUNET_assert (0);
+ break;
+ case BRS_ERROR:
+ GNUNET_assert (0);
+ break;
+ default:
+ GNUNET_assert (0);
+ break;
+ }
+ }
+ GNUNET_FS_download_sync_ (dc);
+ return GNUNET_YES;
+
+signal_error:
+ if (NULL != fh)
+ GNUNET_DISK_file_close (fh);
+ pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
+ pi.value.download.specifics.error.message = dc->emsg;
+ GNUNET_FS_download_make_status_ (&pi, dc);
+ /* abort all pending requests */
+ if (NULL != dc->th)
+ {
+ GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
+ dc->th = NULL;
+ }
+ GNUNET_CLIENT_disconnect (dc->client);
+ dc->in_receive = GNUNET_NO;
+ dc->client = NULL;
+ GNUNET_FS_free_download_request_ (dc->top_request);
+ dc->top_request = NULL;
+ GNUNET_CONTAINER_multihashmap_destroy (dc->active);
+ dc->active = NULL;
+ dc->pending_head = NULL;
+ dc->pending_tail = NULL;
+ GNUNET_FS_download_sync_ (dc);
+ return GNUNET_NO;
+}
+
+
+/**
+ * Process a download result.
+ *
+ * @param dc our download context
+ * @param type type of the result
+ * @param last_transmission when was this block requested the last time? (FOREVER if unknown/not applicable)
+ * @param data the (encrypted) response
+ * @param size size of data
+ */
+static void
+process_result (struct GNUNET_FS_DownloadContext *dc,
+ enum GNUNET_BLOCK_Type type,
+ struct GNUNET_TIME_Absolute last_transmission,
+ const void *data, size_t size)
+{
+ struct ProcessResultClosure prc;
+
+ prc.dc = dc;
+ prc.data = data;
+ prc.size = size;
+ prc.type = type;
+ prc.do_store = GNUNET_YES;
+ prc.last_transmission = last_transmission;
+ GNUNET_CRYPTO_hash (data, size, &prc.query);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received result for query `%s' from `%s'-service\n",
+ GNUNET_h2s (&prc.query), "FS");
+ GNUNET_CONTAINER_multihashmap_get_multiple (dc->active, &prc.query,
+ &process_result_with_request,
+ &prc);
+}
+
+
+/**
+ * Type of a function to call when we receive a message
+ * from the service.
+ *
+ * @param cls closure
+ * @param msg message received, NULL on timeout or fatal error
+ */
+static void
+receive_results (void *cls, const struct GNUNET_MessageHeader *msg)
+{
+ struct GNUNET_FS_DownloadContext *dc = cls;
+ const struct ClientPutMessage *cm;
+ uint16_t msize;
+
+ if ((NULL == msg) || (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_PUT) ||
+ (sizeof (struct ClientPutMessage) > ntohs (msg->size)))
+ {
+ GNUNET_break (NULL == msg);
+ try_reconnect (dc);
+ return;
+ }
+ msize = ntohs (msg->size);
+ cm = (const struct ClientPutMessage *) msg;
+ process_result (dc, ntohl (cm->type),
+ GNUNET_TIME_absolute_ntoh (cm->last_transmission), &cm[1],
+ msize - sizeof (struct ClientPutMessage));
+ if (NULL == dc->client)
+ return; /* fatal error */
+ /* continue receiving */
+ GNUNET_CLIENT_receive (dc->client, &receive_results, dc,
+ GNUNET_TIME_UNIT_FOREVER_REL);
+}
+
+
+/**
+ * We're ready to transmit a search request to the
+ * file-sharing service. Do it. If there is
+ * more than one request pending, try to send
+ * multiple or request another transmission.
+ *
+ * @param cls closure
+ * @param size number of bytes available in buf
+ * @param buf where the callee should write the message
+ * @return number of bytes written to buf
+ */
+static size_t
+transmit_download_request (void *cls, size_t size, void *buf)
+{
+ struct GNUNET_FS_DownloadContext *dc = cls;
+ size_t msize;
+ struct SearchMessage *sm;
+ struct DownloadRequest *dr;
+
+ dc->th = NULL;
+ if (NULL == buf)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Transmitting download request failed, trying to reconnect\n");
+ try_reconnect (dc);
+ return 0;
+ }
+ GNUNET_assert (size >= sizeof (struct SearchMessage));
+ msize = 0;
+ sm = buf;
+ while ((NULL != (dr = dc->pending_head)) &&
+ (size >= msize + sizeof (struct SearchMessage)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Transmitting download request for `%s' to `%s'-service\n",
+ GNUNET_h2s (&dr->chk.query), "FS");
+ memset (sm, 0, sizeof (struct SearchMessage));
+ sm->header.size = htons (sizeof (struct SearchMessage));
+ sm->header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
+ if (0 != (dc->options & GNUNET_FS_DOWNLOAD_OPTION_LOOPBACK_ONLY))
+ sm->options = htonl (GNUNET_FS_SEARCH_OPTION_LOOPBACK_ONLY);
+ else
+ sm->options = htonl (GNUNET_FS_SEARCH_OPTION_NONE);
+ if (0 == dr->depth)
+ sm->type = htonl (GNUNET_BLOCK_TYPE_FS_DBLOCK);
+ else
+ sm->type = htonl (GNUNET_BLOCK_TYPE_FS_IBLOCK);
+ sm->anonymity_level = htonl (dc->anonymity);
+ sm->target = dc->target.hashPubKey;
+ sm->query = dr->chk.query;
+ GNUNET_CONTAINER_DLL_remove (dc->pending_head, dc->pending_tail, dr);
+ dr->is_pending = GNUNET_NO;
+ msize += sizeof (struct SearchMessage);
+ sm++;
+ }
+ if (NULL != dc->pending_head)
+ {
+ dc->th =
+ GNUNET_CLIENT_notify_transmit_ready (dc->client,
+ sizeof (struct SearchMessage),
+ GNUNET_CONSTANTS_SERVICE_TIMEOUT,
+ GNUNET_NO,
+ &transmit_download_request, dc);
+ GNUNET_assert (NULL != dc->th);
+ }
+ if (GNUNET_NO == dc->in_receive)
+ {
+ dc->in_receive = GNUNET_YES;
+ GNUNET_CLIENT_receive (dc->client, &receive_results, dc,
+ GNUNET_TIME_UNIT_FOREVER_REL);
+ }
+ return msize;
+}
+
+
+/**
+ * Reconnect to the FS service and transmit our queries NOW.
+ *
+ * @param cls our download context
+ * @param tc unused
+ */
+static void
+do_reconnect (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+ struct GNUNET_FS_DownloadContext *dc = cls;
+ struct GNUNET_CLIENT_Connection *client;
+
+ dc->task = GNUNET_SCHEDULER_NO_TASK;
+ client = GNUNET_CLIENT_connect ("fs", dc->h->cfg);
+ if (NULL == client)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Connecting to `%s'-service failed, will try again.\n", "FS");
+ try_reconnect (dc);
+ return;
+ }