fixing block reconstruction start/shutdown code
[oweals/gnunet.git] / src / fs / fs_download.c
index 1ee4355adc5516975e6248fd1767fa8c29ce736f..d7693a93fbb38d0361b031d32e7a686f73e8e056 100644 (file)
@@ -23,9 +23,7 @@
  * @author Christian Grothoff
  *
  * TODO:
- * - location URI suppport (can wait, easy)
  * - different priority for scheduling probe downloads?
- * - check if iblocks can be computed from existing blocks (can wait, hard)
  */
 #include "platform.h"
 #include "gnunet_constants.h"
@@ -250,6 +248,397 @@ 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_FS_DBLOCK 
+    : GNUNET_BLOCK_TYPE_FS_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;
+}
+
+
+
+/**
+ * Closure for 'reconstruct_cont' and 'reconstruct_cb'.
+ */
+struct ReconstructContext
+{
+  /**
+   * File handle open for the reconstruction.
+   */
+  struct GNUNET_DISK_FileHandle *fh;
+
+  /**
+   * the download context.
+   */
+  struct GNUNET_FS_DownloadContext *dc;
+
+  /**
+   * Tree encoder used for the reconstruction.
+   */
+  struct GNUNET_FS_TreeEncoder *te;
+
+  /**
+   * CHK of block we are trying to reconstruct.
+   */
+  struct ContentHashKey chk;
+
+  /**
+   * Request that was generated.
+   */
+  struct DownloadRequest *sm;
+
+  /**
+   * Helper task.
+   */
+  GNUNET_SCHEDULER_TaskIdentifier task;
+
+  /**
+   * Offset of block we are trying to reconstruct.
+   */
+  uint64_t offset;
+
+  /**
+   * Depth of block we are trying to reconstruct.
+   */
+  unsigned int depth;
+
+};
+
+
+/**
+ * Continuation after a possible attempt to reconstruct
+ * the current IBlock from the existing file.
+ *
+ * @param cls the 'struct ReconstructContext'
+ * @param tc scheduler context
+ */
+static void
+reconstruct_cont (void *cls,
+                 const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  struct ReconstructContext *rcc = cls;
+
+  if (rcc->te != NULL)
+    {
+      GNUNET_FS_tree_encoder_finish (rcc->te, NULL, NULL);
+    }
+  rcc->dc->reconstruct_failed = GNUNET_YES;
+  rcc->dc->rcc = NULL;
+  if (rcc->fh != NULL)
+    GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (rcc->fh));
+  if ( (rcc->dc->th == NULL) &&
+       (rcc->dc->client != NULL) )
+    {
+#if DEBUG_DOWNLOAD
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Asking for transmission to FS service\n");
+#endif
+      rcc->dc->th = GNUNET_CLIENT_notify_transmit_ready (rcc->dc->client,
+                                                        sizeof (struct SearchMessage),
+                                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
+                                                        GNUNET_NO,
+                                                        &transmit_download_request,
+                                                        rcc->dc);
+    }
+  else
+    {
+#if DEBUG_DOWNLOAD
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Transmission request not issued (%p %p)\n",
+                 rcc->dc->th, 
+                 rcc->dc->client);
+#endif
+    }
+  GNUNET_free (rcc);
+}
+
+
+static void
+get_next_block (void *cls,
+               const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  struct ReconstructContext *rcc = cls;  
+
+  rcc->task = GNUNET_SCHEDULER_NO_TASK;
+  GNUNET_FS_tree_encoder_next (rcc->te);
+}
+
+
+/**
+ * Function called asking for the current (encoded)
+ * block to be processed.  After processing the
+ * client should either call "GNUNET_FS_tree_encode_next"
+ * or (on error) "GNUNET_FS_tree_encode_finish".
+ *
+ * This function checks if the content on disk matches
+ * the expected content based on the URI.
+ * 
+ * @param cls closure
+ * @param query the query for the block (key for lookup in the datastore)
+ * @param offset offset of the block
+ * @param type type of the block (IBLOCK or DBLOCK)
+ * @param block the (encrypted) block
+ * @param block_size size of block (in bytes)
+ */
+static void 
+reconstruct_cb (void *cls,
+               const GNUNET_HashCode *query,
+               uint64_t offset,
+               unsigned int depth,
+               enum GNUNET_BLOCK_Type type,
+               const void *block,
+               uint16_t block_size)
+{
+  struct ReconstructContext *rcc = cls;
+  struct ProcessResultClosure prc;
+  struct GNUNET_FS_TreeEncoder *te;
+  uint64_t off;
+  uint64_t boff;
+  uint64_t roff;
+  unsigned int i;
+
+  roff = offset / DBLOCK_SIZE;
+  for (i=rcc->dc->treedepth;i>depth;i--)
+    roff /= CHK_PER_INODE;
+  boff = roff * DBLOCK_SIZE;
+  for (i=rcc->dc->treedepth;i>depth;i--)
+    boff *= CHK_PER_INODE;
+  /* convert reading offset into IBLOCKs on-disk offset */
+  off = compute_disk_offset (GNUNET_FS_uri_chk_get_file_size (rcc->dc->uri),
+                            boff,
+                            depth,
+                            rcc->dc->treedepth);
+  if ( (off == rcc->offset) &&
+       (depth == rcc->depth) &&
+       (0 == memcmp (query,
+                    &rcc->chk.query,
+                    sizeof (GNUNET_HashCode))) )
+    {
+      /* already got it! */
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 _("Block reconstruction at offset %llu and depth %u successful\n"),
+                 (unsigned long long) offset,
+                 depth);
+      prc.dc = rcc->dc;
+      prc.data = block;
+      prc.size = block_size;
+      prc.type = type;
+      prc.query = rcc->chk.query;
+      prc.do_store = GNUNET_NO;
+      process_result_with_request (&prc,
+                                  &rcc->chk.key,
+                                  rcc->sm);
+      te = rcc->te;
+      rcc->te = NULL;
+      GNUNET_FS_tree_encoder_finish (te, NULL, NULL);
+      GNUNET_free (rcc);
+      return;     
+    }
+  rcc->task = GNUNET_SCHEDULER_add_now (&get_next_block,
+                                       rcc);
+}
+
+
+/**
+ * Function called by the tree encoder to obtain
+ * a block of plaintext data (for the lowest level
+ * of the tree).
+ *
+ * @param cls our 'struct ReconstructContext'
+ * @param offset identifies which block to get
+ * @param max (maximum) number of bytes to get; returning
+ *        fewer will also cause errors
+ * @param buf where to copy the plaintext buffer
+ * @param emsg location to store an error message (on error)
+ * @return number of bytes copied to buf, 0 on error
+ */
+static size_t
+fh_reader (void *cls,
+          uint64_t offset,
+          size_t max, 
+          void *buf,
+          char **emsg)
+{
+  struct ReconstructContext *rcc = cls;
+  struct GNUNET_DISK_FileHandle *fh = rcc->fh;
+  ssize_t ret;
+
+  *emsg = NULL;
+  if (offset !=
+      GNUNET_DISK_file_seek (fh,
+                            offset,
+                            GNUNET_DISK_SEEK_SET))
+    {
+      *emsg = GNUNET_strdup (strerror (errno));
+      return 0;
+    }
+  ret = GNUNET_DISK_file_read (fh, buf, max);
+  if (ret < 0)
+    {
+      *emsg = GNUNET_strdup (strerror (errno));
+      return 0;
+    }
+  return ret;
+}
+
 
 /**
  * Schedule the download of the specified block in the tree.
@@ -274,25 +663,19 @@ 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;
+  struct ReconstructContext *rcc;
 
-#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);
+  total = GNUNET_FS_uri_chk_get_file_size (dc->uri);
   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;
@@ -304,13 +687,39 @@ 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) &&
+  if ( ( (dc->old_file_size > off) ||
+        ( (depth < dc->treedepth) &&
+          (dc->reconstruct_failed == GNUNET_NO) ) ) &&
        (dc->filename != NULL) )    
     fh = GNUNET_DISK_file_open (dc->filename,
                                GNUNET_DISK_OPEN_READ,
                                GNUNET_DISK_PERM_NONE);    
   if ( (fh != NULL) &&
+       (dc->old_file_size > off) &&
        (off  == 
        GNUNET_DISK_file_seek (fh,
                               off,
@@ -321,95 +730,48 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
                               len)) )
     {
       GNUNET_CRYPTO_hash (block, len, &key);
-      if (0 == memcmp (&key,
-                      &chk->key,
-                      sizeof (GNUNET_HashCode)))
+      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);
-         if (-1 == GNUNET_CRYPTO_aes_encrypt (block, len,
-                                              &sk,
-                                              &iv,
-                                              enc))
-           {
-             GNUNET_break (0);
-             goto do_download;
-           }
-         GNUNET_CRYPTO_hash (enc, len, &query);
-         if (0 == memcmp (&query,
-                          &chk->query,
-                          sizeof (GNUNET_HashCode)))
-           {
-#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 = 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;
        }
     }
- do_download:
-  if (fh != NULL)
-    GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
-  if (depth < dc->treedepth)
+  rcc = GNUNET_malloc (sizeof (struct ReconstructContext));
+  rcc->fh = fh;
+  rcc->dc = dc;
+  rcc->sm = sm;
+  rcc->chk = *chk;
+  rcc->offset = off;
+  rcc->depth = depth;
+  dc->rcc = rcc;
+  if ( (depth < dc->treedepth) &&
+       (dc->reconstruct_failed == GNUNET_NO) &&
+       (fh != NULL) )
     {
-      // FIXME: try if we could
-      // reconstitute this IBLOCK
-      // from the existing blocks on disk (can wait)
-      // (read block(s), encode, compare with
-      // query; if matches, simply return)
-    }
-
-  if ( (dc->th == NULL) &&
-       (dc->client != NULL) )
-    {
-#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
-
+      rcc->te = GNUNET_FS_tree_encoder_create (dc->h,
+                                              dc->old_file_size,
+                                              rcc,
+                                              fh_reader,
+                                              &reconstruct_cb,
+                                              NULL,
+                                              &reconstruct_cont);
+      GNUNET_FS_tree_encoder_next (rcc->te);
+      return;
     }
-
+  reconstruct_cont (rcc, NULL);
 }
 
 
-
 /**
  * Suggest a filename based on given metadata.
  * 
@@ -674,6 +1036,8 @@ check_completed (struct GNUNET_FS_DownloadContext *dc)
 }
 
 
+#define GNUNET_FS_URI_CHK_PREFIX GNUNET_FS_URI_PREFIX GNUNET_FS_URI_CHK_INFIX
+
 /**
  * We found an entry in a directory.  Check if the respective child
  * already exists and if not create the respective child download.
@@ -702,6 +1066,7 @@ trigger_recursive_download (void *cls,
   char *us;
   char *ext;
   char *dn;
+  char *pos;
   char *full_name;
 
   if (NULL == uri)
@@ -722,12 +1087,11 @@ 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);
-         fn = GNUNET_strdup (&us [strlen (GNUNET_FS_URI_PREFIX 
-                                          GNUNET_FS_URI_CHK_INFIX)]);
+         fn = GNUNET_strdup (&us [strlen (GNUNET_FS_URI_CHK_PREFIX)]);
          GNUNET_free (us);
        }
       else if (fn[0] == '.')
@@ -736,11 +1100,24 @@ trigger_recursive_download (void *cls,
          us = GNUNET_FS_uri_to_string (uri);
          GNUNET_asprintf (&fn,
                           "%s%s",
-                          &us[strlen (GNUNET_FS_URI_PREFIX 
-                                      GNUNET_FS_URI_CHK_INFIX)], ext);
+                          &us[strlen (GNUNET_FS_URI_CHK_PREFIX)], ext);
          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)
@@ -1261,9 +1638,9 @@ transmit_download_request (void *cls,
       else
        sm->options = htonl (0);      
       if (dc->pending->depth == dc->treedepth)
-       sm->type = htonl (GNUNET_BLOCK_TYPE_DBLOCK);
+       sm->type = htonl (GNUNET_BLOCK_TYPE_FS_DBLOCK);
       else
-       sm->type = htonl (GNUNET_BLOCK_TYPE_IBLOCK);
+       sm->type = htonl (GNUNET_BLOCK_TYPE_FS_IBLOCK);
       sm->anonymity_level = htonl (dc->anonymity);
       sm->target = dc->target.hashPubKey;
       sm->query = dc->pending->chk.query;
@@ -1297,8 +1674,7 @@ do_reconnect (void *cls,
   struct GNUNET_CLIENT_Connection *client;
   
   dc->task = GNUNET_SCHEDULER_NO_TASK;
-  client = GNUNET_CLIENT_connect (dc->h->sched,
-                                 "fs",
+  client = GNUNET_CLIENT_connect ("fs",
                                  dc->h->cfg);
   if (NULL == client)
     {
@@ -1381,8 +1757,7 @@ try_reconnect (struct GNUNET_FS_DownloadContext *dc)
              "Will try to reconnect in 1s\n");
 #endif
   dc->task
-    = GNUNET_SCHEDULER_add_delayed (dc->h->sched,
-                                   GNUNET_TIME_UNIT_SECONDS,
+    = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
                                    &do_reconnect,
                                    dc);
 }
@@ -1463,6 +1838,60 @@ deactivate_fs_download (void *cls)
 }
 
 
+/**
+ * Task that creates the initial (top-level) download
+ * request for the file.
+ *
+ * @param cls the 'struct GNUNET_FS_DownloadContext'
+ * @param tc scheduler context
+ */
+void
+GNUNET_FS_download_start_task_ (void *cls,
+                               const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  struct GNUNET_FS_DownloadContext *dc = cls;  
+  struct GNUNET_FS_ProgressInfo pi;
+  struct GNUNET_DISK_FileHandle *fh;
+
+  dc->start_task = GNUNET_SCHEDULER_NO_TASK;
+  if (dc->length == 0)
+    {
+      /* no bytes required! */
+      if (dc->filename != NULL) 
+       {
+         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);
+         GNUNET_DISK_file_close (fh);
+       }
+
+      pi.status = GNUNET_FS_STATUS_DOWNLOAD_COMPLETED;
+      GNUNET_FS_download_make_status_ (&pi, dc);
+      GNUNET_FS_download_sync_ (dc);
+       if (dc->parent != NULL)
+       check_completed (dc->parent);      
+      return;
+    }
+  schedule_block_download (dc, 
+                          (dc->uri->type == chk) 
+                          ? &dc->uri->data.chk.chk
+                          : &dc->uri->data.loc.fi.chk,
+                          0, 
+                          1 /* 0 == CHK, 1 == top */); 
+  GNUNET_FS_download_sync_ (dc);
+  GNUNET_FS_download_start_downloading_ (dc);
+  pi.status = GNUNET_FS_STATUS_DOWNLOAD_START;
+  pi.value.download.specifics.start.meta = dc->meta;
+  GNUNET_FS_download_make_status_ (&pi, dc);
+}
+
+
 /**
  * Create SUSPEND event for the given download operation
  * and then clean up our state (without stop signal).
@@ -1474,7 +1903,7 @@ 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)
@@ -1493,11 +1922,26 @@ GNUNET_FS_download_signal_suspend_ (void *cls)
     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_SCHEDULER_cancel (dc->task);
+  if (dc->start_task != GNUNET_SCHEDULER_NO_TASK)
+    {
+      GNUNET_SCHEDULER_cancel (dc->start_task);
+      dc->start_task = GNUNET_SCHEDULER_NO_TASK;
+    }
+  else
+    {
+      pi.status = GNUNET_FS_STATUS_DOWNLOAD_SUSPEND;
+      GNUNET_FS_download_make_status_ (&pi, dc);
+    }
+  if (dc->rcc != NULL)
+    {
+      if (dc->rcc->task != GNUNET_SCHEDULER_NO_TASK)
+       GNUNET_SCHEDULER_cancel (dc->rcc->task);
+      if (dc->rcc->te != NULL)
+       GNUNET_FS_tree_encoder_finish (dc->rcc->te, NULL, NULL);        
+      dc->rcc = NULL;
+    }
   GNUNET_CONTAINER_multihashmap_iterate (dc->active,
                                         &free_entry,
                                         NULL);
@@ -1554,12 +1998,13 @@ GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
                          void *cctx,
                          struct GNUNET_FS_DownloadContext *parent)
 {
-  struct GNUNET_FS_ProgressInfo pi;
   struct GNUNET_FS_DownloadContext *dc;
 
-  GNUNET_assert (GNUNET_FS_uri_test_chk (uri));
+  GNUNET_assert (GNUNET_FS_uri_test_chk (uri) ||
+                GNUNET_FS_uri_test_loc (uri) );
+                
   if ( (offset + length < offset) ||
-       (offset + length > uri->data.chk.file_length) )
+       (offset + length > GNUNET_FS_uri_chk_get_file_size (uri)) )
     {      
       GNUNET_break (0);
       return NULL;
@@ -1600,7 +2045,7 @@ GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
   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));
+  dc->treedepth = GNUNET_FS_compute_depth (GNUNET_FS_uri_chk_get_file_size(dc->uri));
   if ( (filename == NULL) &&
        (is_recursive_download (dc) ) )
     {
@@ -1621,15 +2066,7 @@ GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
                                    &GNUNET_FS_download_signal_suspend_,
                                    dc);
     }
-  pi.status = GNUNET_FS_STATUS_DOWNLOAD_START;
-  pi.value.download.specifics.start.meta = 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);
+  dc->start_task = GNUNET_SCHEDULER_add_now (&GNUNET_FS_download_start_task_, dc);
   return dc;
 }
 
@@ -1679,7 +2116,6 @@ GNUNET_FS_download_start_from_search (struct GNUNET_FS_Handle *h,
                                      enum GNUNET_FS_DownloadOptions options,
                                      void *cctx)
 {
-  struct GNUNET_FS_ProgressInfo pi;
   struct GNUNET_FS_DownloadContext *dc;
 
   if ( (sr == NULL) ||
@@ -1688,7 +2124,8 @@ GNUNET_FS_download_start_from_search (struct GNUNET_FS_Handle *h,
       GNUNET_break (0);
       return NULL;
     }
-  GNUNET_assert (GNUNET_FS_uri_test_chk (sr->uri));
+  GNUNET_assert (GNUNET_FS_uri_test_chk (sr->uri) ||
+                GNUNET_FS_uri_test_loc (sr->uri) );             
   if ( (offset + length < offset) ||
        (offset + length > sr->uri->data.chk.file_length) )
     {      
@@ -1746,19 +2183,10 @@ GNUNET_FS_download_start_from_search (struct GNUNET_FS_Handle *h,
              "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);
+  dc->start_task = GNUNET_SCHEDULER_add_now (&GNUNET_FS_download_start_task_, dc);
   return dc;  
 }
 
-
 /**
  * Start the downloading process (by entering the queue).
  *
@@ -1767,6 +2195,8 @@ GNUNET_FS_download_start_from_search (struct GNUNET_FS_Handle *h,
 void
 GNUNET_FS_download_start_downloading_ (struct GNUNET_FS_DownloadContext *dc)
 {
+  if (dc->completed == dc->length)
+    return;
   GNUNET_assert (dc->job_queue == NULL);
   dc->job_queue = GNUNET_FS_queue_ (dc->h, 
                                    &activate_fs_download,
@@ -1791,6 +2221,11 @@ GNUNET_FS_download_stop (struct GNUNET_FS_DownloadContext *dc,
 
   if (dc->top != NULL)
     GNUNET_FS_end_top (dc->h, dc->top);
+  if (dc->start_task != GNUNET_SCHEDULER_NO_TASK)
+    {
+      GNUNET_SCHEDULER_cancel (dc->start_task);
+      dc->start_task = GNUNET_SCHEDULER_NO_TASK;
+    }
   if (dc->search != NULL)
     {
       dc->search->download = NULL;
@@ -1801,6 +2236,14 @@ GNUNET_FS_download_stop (struct GNUNET_FS_DownloadContext *dc,
       GNUNET_FS_dequeue_ (dc->job_queue);
       dc->job_queue = NULL;
     }
+  if (dc->rcc != NULL)
+    {
+      if (dc->rcc->task != GNUNET_SCHEDULER_NO_TASK)
+       GNUNET_SCHEDULER_cancel (dc->rcc->task);
+      if (dc->rcc->te != NULL)
+       GNUNET_FS_tree_encoder_finish (dc->rcc->te, NULL, NULL);
+      dc->rcc = NULL;
+    }
   have_children = (NULL != dc->child_head) ? GNUNET_YES : GNUNET_NO;
   while (NULL != dc->child_head)
     GNUNET_FS_download_stop (dc->child_head, 
@@ -1825,8 +2268,7 @@ GNUNET_FS_download_stop (struct GNUNET_FS_DownloadContext *dc,
   pi.status = GNUNET_FS_STATUS_DOWNLOAD_STOPPED;
   GNUNET_FS_download_make_status_ (&pi, dc);
   if (GNUNET_SCHEDULER_NO_TASK != dc->task)
-    GNUNET_SCHEDULER_cancel (dc->h->sched,
-                            dc->task);
+    GNUNET_SCHEDULER_cancel (dc->task);
   GNUNET_CONTAINER_multihashmap_iterate (dc->active,
                                         &free_entry,
                                         NULL);