expose our hello to plugins
[oweals/gnunet.git] / src / fs / fs_download.c
index 1f03082e82f48a704d03efe58ce3202fe85918b7..80758ebc72dc3b16b69177d67b19b5f7ee0c49d6 100644 (file)
@@ -4,7 +4,7 @@
 
      GNUnet is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published
-     by the Free Software Foundation; either version 2, or (at your
+     by the Free Software Foundation; either version 3, or (at your
      option) any later version.
 
      GNUnet is distributed in the hope that it will be useful, but
  * @author Christian Grothoff
  *
  * TODO:
- * - handle recursive downloads (need directory & 
- *   fs-level download-parallelism management)
- * - location URI suppport (can wait, easy)
- * - check if blocks exist already (can wait, easy)
+ * - different priority for scheduling probe downloads?
  * - check if iblocks can be computed from existing blocks (can wait, hard)
- * - persistence (can wait)
  */
 #include "platform.h"
 #include "gnunet_constants.h"
@@ -140,21 +136,23 @@ compute_dblock_offset (uint64_t offset,
 
 
 /**
- * Fill in all of the generic fields for 
- * a download event.
+ * Fill in all of the generic fields for a download event and call the
+ * callback.
  *
  * @param pi structure to fill in
  * @param dc overall download context
  */
-static void
-make_download_status (struct GNUNET_FS_ProgressInfo *pi,
-                     struct GNUNET_FS_DownloadContext *dc)
+void
+GNUNET_FS_download_make_status_ (struct GNUNET_FS_ProgressInfo *pi,
+                                struct GNUNET_FS_DownloadContext *dc)
 {
   pi->value.download.dc = dc;
   pi->value.download.cctx
     = dc->client_info;
   pi->value.download.pctx
     = (dc->parent == NULL) ? NULL : dc->parent->client_info;
+  pi->value.download.sctx
+    = (dc->search == NULL) ? NULL : dc->search->client_info;
   pi->value.download.uri 
     = dc->uri;
   pi->value.download.filename
@@ -171,6 +169,13 @@ make_download_status (struct GNUNET_FS_ProgressInfo *pi,
     = GNUNET_TIME_calculate_eta (dc->start_time,
                                 dc->completed,
                                 dc->length);
+  pi->value.download.is_active = (dc->client == NULL) ? GNUNET_NO : GNUNET_YES;
+  if (0 == (dc->options & GNUNET_FS_DOWNLOAD_IS_PROBE))
+    dc->client_info = dc->h->upcb (dc->h->upcb_cls,
+                                  pi);
+  else
+    dc->client_info = GNUNET_FS_search_probe_progress_ (NULL,
+                                                       pi);
 }
 
 /**
@@ -244,6 +249,171 @@ process_result_with_request (void *cls,
                             void *value);
 
 
+/**
+ * We've found a matching block without downloading it.
+ * Encrypt it and pass it to our "receive" function as
+ * if we had received it from the network.
+ * 
+ * @param dc download in question
+ * @param chk request this relates to
+ * @param sm request details
+ * @param block plaintext data matching request
+ * @param len number of bytes in block
+ * @param depth depth of the block
+ * @param do_store should we still store the block on disk?
+ * @return GNUNET_OK on success
+ */
+static int
+encrypt_existing_match (struct GNUNET_FS_DownloadContext *dc,
+                       const struct ContentHashKey *chk,
+                       struct DownloadRequest *sm,
+                       const char * block,                    
+                       size_t len,
+                       int depth,
+                       int do_store)
+{
+  struct ProcessResultClosure prc;
+  char enc[len];
+  struct GNUNET_CRYPTO_AesSessionKey sk;
+  struct GNUNET_CRYPTO_AesInitializationVector iv;
+  GNUNET_HashCode query;
+  
+  GNUNET_CRYPTO_hash_to_aes_key (&chk->key, &sk, &iv);
+  if (-1 == GNUNET_CRYPTO_aes_encrypt (block, len,
+                                      &sk,
+                                      &iv,
+                                      enc))
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+  GNUNET_CRYPTO_hash (enc, len, &query);
+  if (0 != memcmp (&query,
+                  &chk->query,
+                  sizeof (GNUNET_HashCode)))
+    {
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Matching block already present, no need for download!\n");
+#endif
+  /* already got it! */
+  prc.dc = dc;
+  prc.data = enc;
+  prc.size = len;
+  prc.type = (dc->treedepth == depth) 
+    ? GNUNET_BLOCK_TYPE_DBLOCK 
+    : GNUNET_BLOCK_TYPE_IBLOCK;
+  prc.query = chk->query;
+  prc.do_store = do_store;
+  process_result_with_request (&prc,
+                              &chk->key,
+                              sm);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Closure for match_full_data.
+ */
+struct MatchDataContext 
+{
+  /**
+   * CHK we are looking for.
+   */
+  const struct ContentHashKey *chk;
+
+  /**
+   * Download we're processing.
+   */
+  struct GNUNET_FS_DownloadContext *dc;
+
+  /**
+   * Request details.
+   */
+  struct DownloadRequest *sm;
+
+  /**
+   * Overall offset in the file.
+   */
+  uint64_t offset;
+
+  /**
+   * Desired length of the block.
+   */
+  size_t len;
+
+  /**
+   * Flag set to GNUNET_YES on success.
+   */
+  int done;
+};
+
+/**
+ * Type of a function that libextractor calls for each
+ * meta data item found.
+ *
+ * @param cls closure (user-defined)
+ * @param plugin_name name of the plugin that produced this value;
+ *        special values can be used (i.e. '<zlib>' for zlib being
+ *        used in the main libextractor library and yielding
+ *        meta data).
+ * @param type libextractor-type describing the meta data
+ * @param format basic format information about data 
+ * @param data_mime_type mime-type of data (not of the original file);
+ *        can be NULL (if mime-type is not known)
+ * @param data actual meta-data found
+ * @param data_len number of bytes in data
+ * @return 0 to continue extracting, 1 to abort
+ */ 
+static int
+match_full_data (void *cls,
+                const char *plugin_name,
+                enum EXTRACTOR_MetaType type,
+                enum EXTRACTOR_MetaFormat format,
+                const char *data_mime_type,
+                const char *data,
+                size_t data_len)
+{
+  struct MatchDataContext *mdc = cls;
+  GNUNET_HashCode key;
+
+  if (type == EXTRACTOR_METATYPE_GNUNET_FULL_DATA) 
+    {
+      if ( (mdc->offset > data_len) ||
+          (mdc->offset + mdc->len > data_len) )
+       return 1;
+      GNUNET_CRYPTO_hash (&data[mdc->offset],
+                         mdc->len,
+                         &key);
+      if (0 != memcmp (&key,
+                      &mdc->chk->key,
+                      sizeof (GNUNET_HashCode)))
+       {
+         GNUNET_break_op (0);
+         return 1;
+       }
+      /* match found! */
+      if (GNUNET_OK !=
+         encrypt_existing_match (mdc->dc,
+                                 mdc->chk,
+                                 mdc->sm,
+                                 &data[mdc->offset],
+                                 mdc->len,
+                                 0,
+                                 GNUNET_YES))
+       {
+         GNUNET_break_op (0);
+         return 1;
+       }
+      mdc->done = GNUNET_YES;
+      return 1;
+    }
+  return 0;
+}
+
 
 /**
  * Schedule the download of the specified block in the tree.
@@ -268,24 +438,18 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
   size_t len;
   char block[DBLOCK_SIZE];
   GNUNET_HashCode key;
-  struct ProcessResultClosure prc;
+  struct MatchDataContext mdc;
+  struct GNUNET_DISK_FileHandle *fh;
 
-#if DEBUG_DOWNLOAD
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-             "Scheduling download at offset %llu and depth %u for `%s'\n",
-             (unsigned long long) offset,
-             depth,
-             GNUNET_h2s (&chk->query));
-#endif
   total = GNUNET_ntohll (dc->uri->data.chk.file_length);
-  off = compute_disk_offset (total,
-                            offset,
-                            depth,
-                            dc->treedepth);
   len = GNUNET_FS_tree_calculate_block_size (total,
                                             dc->treedepth,
                                             offset,
                                             depth);
+  off = compute_disk_offset (total,
+                            offset,
+                            depth,
+                            dc->treedepth);
   sm = GNUNET_malloc (sizeof (struct DownloadRequest));
   sm->chk = *chk;
   sm->offset = offset;
@@ -297,57 +461,64 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
                                     &chk->query,
                                     sm,
                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
-
+  if ( (dc->tried_full_data == GNUNET_NO) &&
+       (depth == 0) )
+    {      
+      mdc.dc = dc;
+      mdc.sm = sm;
+      mdc.chk = chk;
+      mdc.offset = offset;
+      mdc.len = len;
+      mdc.done = GNUNET_NO;
+      GNUNET_CONTAINER_meta_data_iterate (dc->meta,
+                                         &match_full_data,
+                                         &mdc);
+      if (mdc.done == GNUNET_YES)
+       return;
+      dc->tried_full_data = GNUNET_YES; 
+    }
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Scheduling download at offset %llu and depth %u for `%s'\n",
+             (unsigned long long) offset,
+             depth,
+             GNUNET_h2s (&chk->query));
+#endif
+  fh = NULL;
   if ( (dc->old_file_size > off) &&
-       (dc->handle != NULL) &&
+       (dc->filename != NULL) )    
+    fh = GNUNET_DISK_file_open (dc->filename,
+                               GNUNET_DISK_OPEN_READ,
+                               GNUNET_DISK_PERM_NONE);    
+  if ( (fh != NULL) &&
        (off  == 
-       GNUNET_DISK_file_seek (dc->handle,
+       GNUNET_DISK_file_seek (fh,
                               off,
                               GNUNET_DISK_SEEK_SET) ) &&
        (len == 
-       GNUNET_DISK_file_read (dc->handle,
+       GNUNET_DISK_file_read (fh,
                               block,
                               len)) )
     {
-      if (0 == memcmp (&key,
-                      &chk->key,
-                      sizeof (GNUNET_HashCode)))
+      GNUNET_CRYPTO_hash (block, len, &key);
+      if ( (0 == memcmp (&key,
+                        &chk->key,
+                        sizeof (GNUNET_HashCode))) &&
+          (GNUNET_OK ==
+           encrypt_existing_match (dc,
+                                   chk,
+                                   sm,
+                                   block,
+                                   len,
+                                   depth,
+                                   GNUNET_NO)) )
        {
-         char enc[len];
-         struct GNUNET_CRYPTO_AesSessionKey sk;
-         struct GNUNET_CRYPTO_AesInitializationVector iv;
-         GNUNET_HashCode query;
-
-         GNUNET_CRYPTO_hash_to_aes_key (&key, &sk, &iv);
-         GNUNET_CRYPTO_aes_encrypt (block, len,
-                                    &sk,
-                                    &iv,
-                                    enc);
-         GNUNET_CRYPTO_hash (enc, len, &query);
-         if (0 == memcmp (&query,
-                          &chk->query,
-                          sizeof (GNUNET_HashCode)))
-           {
-             /* already got it! */
-             prc.dc = dc;
-             prc.data = enc;
-             prc.size = len;
-             prc.type = (dc->treedepth == depth) 
-               ? GNUNET_BLOCK_TYPE_DBLOCK 
-               : GNUNET_BLOCK_TYPE_IBLOCK;
-             prc.query = chk->query;
-             prc.do_store = GNUNET_NO; /* useless */
-             process_result_with_request (&prc,
-                                          &key,
-                                          sm);
-           }
-         else
-           {
-             GNUNET_break_op (0);
-           }
+         GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
          return;
        }
     }
+  if (fh != NULL)
+    GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
   if (depth < dc->treedepth)
     {
       // FIXME: try if we could
@@ -359,12 +530,29 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
 
   if ( (dc->th == NULL) &&
        (dc->client != NULL) )
-    dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
-                                                 sizeof (struct SearchMessage),
-                                                 GNUNET_CONSTANTS_SERVICE_TIMEOUT,
-                                                 GNUNET_NO,
-                                                 &transmit_download_request,
-                                                 dc);
+    {
+#if DEBUG_DOWNLOAD
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Asking for transmission to FS service\n");
+#endif
+      dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
+                                                   sizeof (struct SearchMessage),
+                                                   GNUNET_CONSTANTS_SERVICE_TIMEOUT,
+                                                   GNUNET_NO,
+                                                   &transmit_download_request,
+                                                   dc);
+    }
+  else
+    {
+#if DEBUG_DOWNLOAD
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Transmission request not issued (%p %p)\n",
+                 dc->th, 
+                 dc->client);
+#endif
+
+    }
+
 }
 
 
@@ -624,11 +812,10 @@ check_completed (struct GNUNET_FS_DownloadContext *dc)
       pos = pos->next;
     }
   dc->has_finished = GNUNET_YES;
+  GNUNET_FS_download_sync_ (dc);
   /* signal completion */
   pi.status = GNUNET_FS_STATUS_DOWNLOAD_COMPLETED;
-  make_download_status (&pi, dc);
-  dc->client_info = dc->h->upcb (dc->h->upcb_cls,
-                                &pi);
+  GNUNET_FS_download_make_status_ (&pi, dc);
   if (dc->parent != NULL)
     check_completed (dc->parent);  
 }
@@ -662,6 +849,7 @@ trigger_recursive_download (void *cls,
   char *us;
   char *ext;
   char *dn;
+  char *pos;
   char *full_name;
 
   if (NULL == uri)
@@ -682,7 +870,7 @@ trigger_recursive_download (void *cls,
   fn = NULL;
   if (NULL == filename)
     {
-      fn = GNUNET_FS_meta_data_suggest_filename (meta);      
+      fn = GNUNET_FS_meta_data_suggest_filename (meta);
       if (fn == NULL)
        {
          us = GNUNET_FS_uri_to_string (uri);
@@ -701,6 +889,20 @@ trigger_recursive_download (void *cls,
          GNUNET_free (ext);
          GNUNET_free (us);
        }
+      /* change '\' to '/' (this should have happened
+       during insertion, but malicious peers may
+       not have done this) */
+      while (NULL != (pos = strstr (fn, "\\")))
+       *pos = '/';
+      /* remove '../' everywhere (again, well-behaved
+        peers don't do this, but don't trust that
+        we did not get something nasty) */
+      while (NULL != (pos = strstr (fn, "../")))
+       {
+         pos[0] = '_';
+         pos[1] = '_';
+         pos[2] = '_';
+       }
       filename = fn;
     }
   if (dc->filename == NULL)
@@ -807,6 +1009,24 @@ trigger_recursive_download (void *cls,
 }
 
 
+/**
+ * Free entries in the map.
+ *
+ * @param cls unused (NULL)
+ * @param key unused
+ * @param entry entry of type "struct DownloadRequest" which is freed
+ * @return GNUNET_OK
+ */
+static int
+free_entry (void *cls,
+           const GNUNET_HashCode *key,
+           void *entry)
+{
+  GNUNET_free (entry);
+  return GNUNET_OK;
+}
+
+
 /**
  * Iterator over entries in the pending requests in the 'active' map for the
  * reply that we just got.
@@ -823,6 +1043,9 @@ process_result_with_request (void *cls,
 {
   struct ProcessResultClosure *prc = cls;
   struct DownloadRequest *sm = value;
+  struct DownloadRequest *ppos;
+  struct DownloadRequest *pprev;
+  struct GNUNET_DISK_FileHandle *fh;
   struct GNUNET_FS_DownloadContext *dc = prc->dc;
   struct GNUNET_CRYPTO_AesSessionKey skey;
   struct GNUNET_CRYPTO_AesInitializationVector iv;
@@ -833,13 +1056,13 @@ process_result_with_request (void *cls,
   size_t app;
   int i;
   struct ContentHashKey *chk;
-  char *emsg;
 
+  fh = NULL;
   bs = GNUNET_FS_tree_calculate_block_size (GNUNET_ntohll (dc->uri->data.chk.file_length),
                                            dc->treedepth,
                                            sm->offset,
                                            sm->depth);
-    if (prc->size != bs)
+  if (prc->size != bs)
     {
 #if DEBUG_DOWNLOAD
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -848,90 +1071,107 @@ process_result_with_request (void *cls,
                  prc->size);
 #endif
       dc->emsg = GNUNET_strdup ("Internal error or bogus download URI");
-      /* signal error */
-      pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
-      make_download_status (&pi, dc);
-      pi.value.download.specifics.error.message = dc->emsg;
-      dc->client_info = dc->h->upcb (dc->h->upcb_cls,
-                                    &pi);
-      /* abort all pending requests */
-      if (NULL != dc->th)
-       {
-         GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
-         dc->th = NULL;
-       }
-      GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
-      dc->client = NULL;
-      return GNUNET_NO;
+      goto signal_error;
     }
   GNUNET_assert (GNUNET_YES ==
                 GNUNET_CONTAINER_multihashmap_remove (dc->active,
                                                       &prc->query,
                                                       sm));
+  /* if this request is on the pending list, remove it! */
+  pprev = NULL;
+  ppos = dc->pending;
+  while (ppos != NULL)
+    {
+      if (ppos == sm)
+       {
+         if (pprev == NULL)
+           dc->pending = ppos->next;
+         else
+           pprev->next = ppos->next;
+         break;
+       }
+      pprev = ppos;
+      ppos = ppos->next;
+    }
   GNUNET_CRYPTO_hash_to_aes_key (&sm->chk.key, &skey, &iv);
-  GNUNET_CRYPTO_aes_decrypt (prc->data,
-                            prc->size,
-                            &skey,
-                            &iv,
-                            pt);
+  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),
                             sm->offset,
                             sm->depth,
                             dc->treedepth);
   /* save to disk */
   if ( ( GNUNET_YES == prc->do_store) &&
-       (NULL != dc->handle) &&
+       ( (dc->filename != NULL) ||
+        (is_recursive_download (dc)) ) &&
+       ( (sm->depth == dc->treedepth) ||
+        (0 == (dc->options & GNUNET_FS_DOWNLOAD_NO_TEMPORARIES)) ) )
+    {
+      fh = GNUNET_DISK_file_open (dc->filename != NULL 
+                                 ? 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_YES == prc->do_store) &&
+       ( (dc->filename != NULL) ||
+        (is_recursive_download (dc)) ) &&
        ( (sm->depth == dc->treedepth) ||
         (0 == (dc->options & GNUNET_FS_DOWNLOAD_NO_TEMPORARIES)) ) )
     {
-      emsg = NULL;
+      GNUNET_asprintf (&dc->emsg,
+                      _("Download failed: could not open file `%s': %s\n"),
+                      dc->filename,
+                      STRERROR (errno));
+      goto signal_error;
+    }
+  if (fh != NULL)
+    {
 #if DEBUG_DOWNLOAD
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "Saving decrypted block to disk at offset %llu\n",
                  (unsigned long long) off);
 #endif
       if ( (off  != 
-           GNUNET_DISK_file_seek (dc->handle,
+           GNUNET_DISK_file_seek (fh,
                                   off,
                                   GNUNET_DISK_SEEK_SET) ) )
-       GNUNET_asprintf (&emsg,
-                        _("Failed to seek to offset %llu in file `%s': %s\n"),
-                        (unsigned long long) off,
-                        dc->filename,
-                        STRERROR (errno));
-      else if (prc->size !=
-              GNUNET_DISK_file_write (dc->handle,
-                                      pt,
-                                      prc->size))
-       GNUNET_asprintf (&emsg,
-                        _("Failed to write block of %u bytes at offset %llu in file `%s': %s\n"),
-                        (unsigned int) prc->size,
-                        (unsigned long long) off,
-                        dc->filename,
-                        STRERROR (errno));
-      if (NULL != emsg)
        {
-         dc->emsg = emsg;
-         // FIXME: make persistent
-
-         /* signal error */
-         pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
-         make_download_status (&pi, dc);
-         pi.value.download.specifics.error.message = emsg;
-         dc->client_info = dc->h->upcb (dc->h->upcb_cls,
-                                        &pi);
-
-         /* abort all pending requests */
-         if (NULL != dc->th)
-           {
-             GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
-             dc->th = NULL;
-           }
-         GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
-         dc->client = NULL;
-         GNUNET_free (sm);
-         return GNUNET_NO;
+         GNUNET_asprintf (&dc->emsg,
+                          _("Failed to seek to offset %llu in file `%s': %s\n"),
+                          (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\n"),
+                          (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 (sm->depth == dc->treedepth) 
     {
@@ -964,13 +1204,11 @@ process_result_with_request (void *cls,
            
     }
   pi.status = GNUNET_FS_STATUS_DOWNLOAD_PROGRESS;
-  make_download_status (&pi, dc);
   pi.value.download.specifics.progress.data = pt;
   pi.value.download.specifics.progress.offset = sm->offset;
   pi.value.download.specifics.progress.data_len = prc->size;
   pi.value.download.specifics.progress.depth = sm->depth;
-  dc->client_info = dc->h->upcb (dc->h->upcb_cls,
-                                &pi);
+  GNUNET_FS_download_make_status_ (&pi, dc);
   GNUNET_assert (dc->completed <= dc->length);
   if (dc->completed == dc->length)
     {
@@ -980,34 +1218,34 @@ process_result_with_request (void *cls,
                  (unsigned long long) GNUNET_ntohll (dc->uri->data.chk.file_length));
 #endif
       /* truncate file to size (since we store IBlocks at the end) */
-      if (dc->handle != NULL)
+      if (dc->filename != NULL)
        {
-         GNUNET_DISK_file_close (dc->handle);
-         dc->handle = NULL;
          if (0 != truncate (dc->filename,
                             GNUNET_ntohll (dc->uri->data.chk.file_length)))
            GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
                                      "truncate",
                                      dc->filename);
        }
-
+      if (dc->job_queue != NULL)
+       {
+         GNUNET_FS_dequeue_ (dc->job_queue);
+         dc->job_queue = NULL;
+       }
       if (is_recursive_download (dc))
        full_recursive_download (dc);
       if (dc->child_head == NULL)
        {
          /* signal completion */
          pi.status = GNUNET_FS_STATUS_DOWNLOAD_COMPLETED;
-         make_download_status (&pi, dc);
-         dc->client_info = dc->h->upcb (dc->h->upcb_cls,
-                                        &pi);
+         GNUNET_FS_download_make_status_ (&pi, dc);
          if (dc->parent != NULL)
            check_completed (dc->parent);
        }
       GNUNET_assert (sm->depth == dc->treedepth);
     }
-  // FIXME: make persistent
   if (sm->depth == dc->treedepth) 
     {
+      GNUNET_FS_download_sync_ (dc);
       GNUNET_free (sm);      
       return GNUNET_YES;
     }
@@ -1033,7 +1271,30 @@ process_result_with_request (void *cls,
                                 sm->depth + 1);
     }
   GNUNET_free (sm);
+  GNUNET_FS_download_sync_ (dc);
   return GNUNET_YES;
+
+ signal_error:
+  if (fh != NULL)
+    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, GNUNET_NO);
+  GNUNET_CONTAINER_multihashmap_iterate (dc->active,
+                                        &free_entry,
+                                        NULL);
+  dc->pending = NULL;
+  dc->client = NULL;
+  GNUNET_free (sm);
+  GNUNET_FS_download_sync_ (dc);
+  return GNUNET_NO;
 }
 
 
@@ -1135,6 +1396,10 @@ transmit_download_request (void *cls,
   dc->th = NULL;
   if (NULL == buf)
     {
+#if DEBUG_DOWNLOAD
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Transmitting download request failed, trying to reconnect\n");
+#endif
       try_reconnect (dc);
       return 0;
     }
@@ -1142,7 +1407,7 @@ transmit_download_request (void *cls,
   msize = 0;
   sm = buf;
   while ( (dc->pending != NULL) &&
-         (size > msize + sizeof (struct SearchMessage)) )
+         (size >= msize + sizeof (struct SearchMessage)) )
     {
 #if DEBUG_DOWNLOAD
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -1153,6 +1418,10 @@ transmit_download_request (void *cls,
       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 (1);
+      else
+       sm->options = htonl (0);      
       if (dc->pending->depth == dc->treedepth)
        sm->type = htonl (GNUNET_BLOCK_TYPE_DBLOCK);
       else
@@ -1254,6 +1523,10 @@ try_reconnect (struct GNUNET_FS_DownloadContext *dc)
   
   if (NULL != dc->client)
     {
+#if DEBUG_DOWNLOAD
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Moving all requests back to pending list\n");
+#endif
       if (NULL != dc->th)
        {
          GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
@@ -1265,6 +1538,10 @@ try_reconnect (struct GNUNET_FS_DownloadContext *dc)
       GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
       dc->client = NULL;
     }
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Will try to reconnect in 1s\n");
+#endif
   dc->task
     = GNUNET_SCHEDULER_add_delayed (dc->h->sched,
                                    GNUNET_TIME_UNIT_SECONDS,
@@ -1273,6 +1550,129 @@ try_reconnect (struct GNUNET_FS_DownloadContext *dc)
 }
 
 
+
+/**
+ * We're allowed to ask the FS service for our blocks.  Start the download.
+ *
+ * @param cls the 'struct GNUNET_FS_DownloadContext'
+ * @param client handle to use for communcation with FS (we must destroy it!)
+ */
+static void
+activate_fs_download (void *cls,
+                     struct GNUNET_CLIENT_Connection *client)
+{
+  struct GNUNET_FS_DownloadContext *dc = cls;
+  struct GNUNET_FS_ProgressInfo pi;
+
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Download activated\n");
+#endif
+  GNUNET_assert (NULL != client);
+  GNUNET_assert (dc->client == NULL);
+  GNUNET_assert (dc->th == NULL);
+  dc->client = client;
+  GNUNET_CLIENT_receive (client,
+                        &receive_results,
+                        dc,
+                        GNUNET_TIME_UNIT_FOREVER_REL);
+  pi.status = GNUNET_FS_STATUS_DOWNLOAD_ACTIVE;
+  GNUNET_FS_download_make_status_ (&pi, dc);
+  GNUNET_CONTAINER_multihashmap_iterate (dc->active,
+                                        &retry_entry,
+                                        dc);
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Asking for transmission to FS service\n");
+#endif
+  dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
+                                               sizeof (struct SearchMessage),
+                                               GNUNET_CONSTANTS_SERVICE_TIMEOUT,
+                                               GNUNET_NO,
+                                               &transmit_download_request,
+                                               dc);    
+  GNUNET_assert (dc->th != NULL);
+}
+
+
+/**
+ * We must stop to ask the FS service for our blocks.  Pause the download.
+ *
+ * @param cls the 'struct GNUNET_FS_DownloadContext'
+ */
+static void
+deactivate_fs_download (void *cls)
+{
+  struct GNUNET_FS_DownloadContext *dc = cls;
+  struct GNUNET_FS_ProgressInfo pi;
+
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Download deactivated\n");
+#endif  
+  if (NULL != dc->th)
+    {
+      GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
+      dc->th = NULL;
+    }
+  if (NULL != dc->client)
+    {
+      GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
+      dc->client = NULL;
+    }
+  pi.status = GNUNET_FS_STATUS_DOWNLOAD_INACTIVE;
+  GNUNET_FS_download_make_status_ (&pi, dc);
+}
+
+
+/**
+ * Create SUSPEND event for the given download operation
+ * and then clean up our state (without stop signal).
+ *
+ * @param cls the 'struct GNUNET_FS_DownloadContext' to signal for
+ */
+void
+GNUNET_FS_download_signal_suspend_ (void *cls)
+{
+  struct GNUNET_FS_DownloadContext *dc = cls;
+  struct GNUNET_FS_ProgressInfo pi;
+  
+  if (dc->top != NULL)
+    GNUNET_FS_end_top (dc->h, dc->top);
+  while (NULL != dc->child_head)
+    GNUNET_FS_download_signal_suspend_ (dc->child_head);  
+  if (dc->search != NULL)
+    {
+      dc->search->download = NULL;
+      dc->search = NULL;
+    }
+  if (dc->job_queue != NULL)
+    {
+      GNUNET_FS_dequeue_ (dc->job_queue);
+      dc->job_queue = NULL;
+    }
+  if (dc->parent != NULL)
+    GNUNET_CONTAINER_DLL_remove (dc->parent->child_head,
+                                dc->parent->child_tail,
+                                dc);  
+  pi.status = GNUNET_FS_STATUS_DOWNLOAD_SUSPEND;
+  GNUNET_FS_download_make_status_ (&pi, dc);
+  if (GNUNET_SCHEDULER_NO_TASK != dc->task)
+    GNUNET_SCHEDULER_cancel (dc->h->sched,
+                            dc->task);
+  GNUNET_CONTAINER_multihashmap_iterate (dc->active,
+                                        &free_entry,
+                                        NULL);
+  GNUNET_CONTAINER_multihashmap_destroy (dc->active);
+  GNUNET_free_non_null (dc->filename);
+  GNUNET_CONTAINER_meta_data_destroy (dc->meta);
+  GNUNET_FS_uri_destroy (dc->uri);
+  GNUNET_free_non_null (dc->temp_filename);
+  GNUNET_free_non_null (dc->serialization);
+  GNUNET_free (dc);
+}
+
+
 /**
  * Download parts of a file.  Note that this will store
  * the blocks at the respective offset in the given file.  Also, the
@@ -1318,7 +1718,6 @@ GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
 {
   struct GNUNET_FS_ProgressInfo pi;
   struct GNUNET_FS_DownloadContext *dc;
-  struct GNUNET_CLIENT_Connection *client;
 
   GNUNET_assert (GNUNET_FS_uri_test_chk (uri));
   if ( (offset + length < offset) ||
@@ -1327,7 +1726,6 @@ GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
       GNUNET_break (0);
       return NULL;
     }
-  // FIXME: add support for "loc" URIs!
 #if DEBUG_DOWNLOAD
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Starting download `%s' of %llu bytes\n",
@@ -1354,27 +1752,11 @@ GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
        GNUNET_DISK_file_size (filename,
                               &dc->old_file_size,
                               GNUNET_YES);
-      dc->handle = GNUNET_DISK_file_open (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 (dc->handle == NULL)
-       {
-         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                     _("Download failed: could not open file `%s': %s\n"),
-                     dc->filename,
-                     STRERROR (errno));
-         GNUNET_CONTAINER_meta_data_destroy (dc->meta);
-         GNUNET_FS_uri_destroy (dc->uri);
-         GNUNET_free (dc->filename);
-         GNUNET_free (dc);
-         return NULL;
-       }
     }
-  // FIXME: set "dc->target" for LOC uris!
+  if (GNUNET_FS_uri_test_loc (dc->uri))
+    GNUNET_assert (GNUNET_OK ==
+                  GNUNET_FS_uri_loc_get_peer_identity (dc->uri,
+                                                       &dc->target));
   dc->offset = offset;
   dc->length = length;
   dc->anonymity = anonymity;
@@ -1395,51 +1777,164 @@ GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
              "Download tree has depth %u\n",
              dc->treedepth);
 #endif
-  // FIXME: make persistent
+  if (parent == NULL)
+    {
+      dc->top = GNUNET_FS_make_top (dc->h,
+                                   &GNUNET_FS_download_signal_suspend_,
+                                   dc);
+    }
   pi.status = GNUNET_FS_STATUS_DOWNLOAD_START;
-  make_download_status (&pi, dc);
   pi.value.download.specifics.start.meta = meta;
-  dc->client_info = dc->h->upcb (dc->h->upcb_cls,
-                                &pi);
-
-  
-  // FIXME: bound parallelism here
-  client = GNUNET_CLIENT_connect (h->sched,
-                                 "fs",
-                                 h->cfg);
-  GNUNET_assert (NULL != client);
-  dc->client = client;
-  GNUNET_CLIENT_receive (client,
-                        &receive_results,
-                        dc,
-                        GNUNET_TIME_UNIT_FOREVER_REL);
-  pi.status = GNUNET_FS_STATUS_DOWNLOAD_ACTIVE;
-  make_download_status (&pi, dc);
-  dc->client_info = dc->h->upcb (dc->h->upcb_cls,
-                                &pi);
+  GNUNET_FS_download_make_status_ (&pi, dc);
   schedule_block_download (dc, 
                           &dc->uri->data.chk.chk,
                           0, 
-                          1 /* 0 == CHK, 1 == top */);
+                          1 /* 0 == CHK, 1 == top */); 
+  GNUNET_FS_download_sync_ (dc);
+  GNUNET_FS_download_start_downloading_ (dc);
   return dc;
 }
 
 
 /**
- * Free entries in the map.
+ * Download parts of a file based on a search result.  The download
+ * will be associated with the search result (and the association
+ * will be preserved when serializing/deserializing the state).
+ * If the search is stopped, the download will not be aborted but
+ * be 'promoted' to a stand-alone download.
  *
- * @param cls unused (NULL)
- * @param key unused
- * @param entry entry of type "struct DownloadRequest" which is freed
- * @return GNUNET_OK
+ * As with the other download function, this will store
+ * the blocks at the respective offset in the given file.  Also, the
+ * download is still using the blocking of the underlying FS
+ * encoding.  As a result, the download may *write* outside of the
+ * given boundaries (if offset and length do not match the 32k FS
+ * block boundaries). <p>
+ *
+ * The given range can be used to focus a download towards a
+ * particular portion of the file (optimization), not to strictly
+ * limit the download to exactly those bytes.
+ *
+ * @param h handle to the file sharing subsystem
+ * @param sr the search result to use for the download (determines uri and
+ *        meta data and associations)
+ * @param filename where to store the file, maybe NULL (then no file is
+ *        created on disk and data must be grabbed from the callbacks)
+ * @param tempname where to store temporary file data, not used if filename is non-NULL;
+ *        can be NULL (in which case we will pick a name if needed); the temporary file
+ *        may already exist, in which case we will try to use the data that is there and
+ *        if it is not what is desired, will overwrite it
+ * @param offset at what offset should we start the download (typically 0)
+ * @param length how many bytes should be downloaded starting at offset
+ * @param anonymity anonymity level to use for the download
+ * @param options various download options
+ * @param cctx initial value for the client context for this download
+ * @return context that can be used to control this download
  */
-static int
-free_entry (void *cls,
-           const GNUNET_HashCode *key,
-           void *entry)
+struct GNUNET_FS_DownloadContext *
+GNUNET_FS_download_start_from_search (struct GNUNET_FS_Handle *h,
+                                     struct GNUNET_FS_SearchResult *sr,
+                                     const char *filename,
+                                     const char *tempname,
+                                     uint64_t offset,
+                                     uint64_t length,
+                                     uint32_t anonymity,
+                                     enum GNUNET_FS_DownloadOptions options,
+                                     void *cctx)
 {
-  GNUNET_free (entry);
-  return GNUNET_OK;
+  struct GNUNET_FS_ProgressInfo pi;
+  struct GNUNET_FS_DownloadContext *dc;
+
+  if ( (sr == NULL) ||
+       (sr->download != NULL) )
+    {
+      GNUNET_break (0);
+      return NULL;
+    }
+  GNUNET_assert (GNUNET_FS_uri_test_chk (sr->uri));
+  if ( (offset + length < offset) ||
+       (offset + length > sr->uri->data.chk.file_length) )
+    {      
+      GNUNET_break (0);
+      return NULL;
+    }
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Starting download `%s' of %llu bytes\n",
+             filename,
+             (unsigned long long) length);
+#endif
+  dc = GNUNET_malloc (sizeof(struct GNUNET_FS_DownloadContext));
+  dc->h = h;
+  dc->search = sr;
+  sr->download = dc;
+  if (sr->probe_ctx != NULL)
+    {
+      GNUNET_FS_download_stop (sr->probe_ctx, GNUNET_YES);
+      sr->probe_ctx = NULL;      
+    }
+  dc->uri = GNUNET_FS_uri_dup (sr->uri);
+  dc->meta = GNUNET_CONTAINER_meta_data_duplicate (sr->meta);
+  dc->client_info = cctx;
+  dc->start_time = GNUNET_TIME_absolute_get ();
+  if (NULL != filename)
+    {
+      dc->filename = GNUNET_strdup (filename);
+      if (GNUNET_YES == GNUNET_DISK_file_test (filename))
+       GNUNET_DISK_file_size (filename,
+                              &dc->old_file_size,
+                              GNUNET_YES);
+    }
+  if (GNUNET_FS_uri_test_loc (dc->uri))
+    GNUNET_assert (GNUNET_OK ==
+                  GNUNET_FS_uri_loc_get_peer_identity (dc->uri,
+                                                       &dc->target));
+  dc->offset = offset;
+  dc->length = length;
+  dc->anonymity = anonymity;
+  dc->options = options;
+  dc->active = GNUNET_CONTAINER_multihashmap_create (1 + 2 * (length / DBLOCK_SIZE));
+  dc->treedepth = GNUNET_FS_compute_depth (GNUNET_ntohll(dc->uri->data.chk.file_length));
+  if ( (filename == NULL) &&
+       (is_recursive_download (dc) ) )
+    {
+      if (tempname != NULL)
+       dc->temp_filename = GNUNET_strdup (tempname);
+      else
+       dc->temp_filename = GNUNET_DISK_mktemp ("gnunet-directory-download-tmp");    
+    }
+
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Download tree has depth %u\n",
+             dc->treedepth);
+#endif
+  pi.status = GNUNET_FS_STATUS_DOWNLOAD_START;
+  pi.value.download.specifics.start.meta = dc->meta;
+  GNUNET_FS_download_make_status_ (&pi, dc);
+  schedule_block_download (dc, 
+                          &dc->uri->data.chk.chk,
+                          0, 
+                          1 /* 0 == CHK, 1 == top */); 
+  GNUNET_FS_download_sync_ (dc);
+  GNUNET_FS_download_start_downloading_ (dc);
+  return dc;  
+}
+
+
+/**
+ * Start the downloading process (by entering the queue).
+ *
+ * @param dc our download context
+ */
+void
+GNUNET_FS_download_start_downloading_ (struct GNUNET_FS_DownloadContext *dc)
+{
+  GNUNET_assert (dc->job_queue == NULL);
+  dc->job_queue = GNUNET_FS_queue_ (dc->h, 
+                                   &activate_fs_download,
+                                   &deactivate_fs_download,
+                                   dc,
+                                   (dc->length + DBLOCK_SIZE-1) / DBLOCK_SIZE);
 }
 
 
@@ -1454,39 +1949,52 @@ GNUNET_FS_download_stop (struct GNUNET_FS_DownloadContext *dc,
                         int do_delete)
 {
   struct GNUNET_FS_ProgressInfo pi;
+  int have_children;
 
+  if (dc->top != NULL)
+    GNUNET_FS_end_top (dc->h, dc->top);
+  if (dc->search != NULL)
+    {
+      dc->search->download = NULL;
+      dc->search = NULL;
+    }
+  if (dc->job_queue != NULL)
+    {
+      GNUNET_FS_dequeue_ (dc->job_queue);
+      dc->job_queue = NULL;
+    }
+  have_children = (NULL != dc->child_head) ? GNUNET_YES : GNUNET_NO;
   while (NULL != dc->child_head)
     GNUNET_FS_download_stop (dc->child_head, 
                             do_delete);
-  // FIXME: make unpersistent  
   if (dc->parent != NULL)
     GNUNET_CONTAINER_DLL_remove (dc->parent->child_head,
                                 dc->parent->child_tail,
-                                dc);
-  
+                                dc);  
+  if (dc->serialization != NULL)
+    GNUNET_FS_remove_sync_file_ (dc->h,
+                                ( (dc->parent != NULL)  || (dc->search != NULL) )
+                                ? GNUNET_FS_SYNC_PATH_CHILD_DOWNLOAD 
+                                : GNUNET_FS_SYNC_PATH_MASTER_DOWNLOAD , 
+                                dc->serialization);
+  if ( (GNUNET_YES == have_children) &&
+       (dc->parent == NULL) )
+    GNUNET_FS_remove_sync_dir_ (dc->h, 
+                               (dc->search != NULL) 
+                               ? GNUNET_FS_SYNC_PATH_CHILD_DOWNLOAD 
+                               : GNUNET_FS_SYNC_PATH_MASTER_DOWNLOAD,
+                               dc->serialization);  
   pi.status = GNUNET_FS_STATUS_DOWNLOAD_STOPPED;
-  make_download_status (&pi, dc);
-  dc->client_info = dc->h->upcb (dc->h->upcb_cls,
-                                &pi);
-
+  GNUNET_FS_download_make_status_ (&pi, dc);
   if (GNUNET_SCHEDULER_NO_TASK != dc->task)
     GNUNET_SCHEDULER_cancel (dc->h->sched,
                             dc->task);
-  if (NULL != dc->th)
-    {
-      GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
-      dc->th = NULL;
-    }
-  if (NULL != dc->client)
-    GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
   GNUNET_CONTAINER_multihashmap_iterate (dc->active,
                                         &free_entry,
                                         NULL);
   GNUNET_CONTAINER_multihashmap_destroy (dc->active);
   if (dc->filename != NULL)
     {
-      if (NULL != dc->handle)
-       GNUNET_DISK_file_close (dc->handle);
       if ( (dc->completed != dc->length) &&
           (GNUNET_YES == do_delete) )
        {
@@ -1507,6 +2015,7 @@ GNUNET_FS_download_stop (struct GNUNET_FS_DownloadContext *dc,
                                  dc->temp_filename);
       GNUNET_free (dc->temp_filename);
     }
+  GNUNET_free_non_null (dc->serialization);
   GNUNET_free (dc);
 }