fixing block reconstruction start/shutdown code
[oweals/gnunet.git] / src / fs / fs_download.c
index 617e2810309b807e476687ccd92d9c0d924a5e64..d7693a93fbb38d0361b031d32e7a686f73e8e056 100644 (file)
@@ -24,7 +24,6 @@
  *
  * TODO:
  * - 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"
@@ -415,6 +414,232 @@ match_full_data (void *cls,
 }
 
 
+
+/**
+ * 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.
  *
@@ -440,8 +665,9 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
   GNUNET_HashCode key;
   struct MatchDataContext mdc;
   struct GNUNET_DISK_FileHandle *fh;
+  struct ReconstructContext *rcc;
 
-  total = GNUNET_ntohll (dc->uri->data.chk.file_length);
+  total = GNUNET_FS_uri_chk_get_file_size (dc->uri);
   len = GNUNET_FS_tree_calculate_block_size (total,
                                             dc->treedepth,
                                             offset,
@@ -485,12 +711,15 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
              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,
@@ -517,46 +746,32 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
          return;
        }
     }
-  if (fh != NULL)
-    GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
-  if (depth < dc->treedepth)
-    {
-      // 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
+  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) )
     {
-#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.
  * 
@@ -821,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.
@@ -874,8 +1091,7 @@ trigger_recursive_download (void *cls,
       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] == '.')
@@ -884,8 +1100,7 @@ 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);
        }
@@ -1623,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).
@@ -1634,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)
@@ -1653,10 +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->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);
@@ -1713,7 +1998,6 @@ 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) ||
@@ -1782,17 +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->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);
+  dc->start_task = GNUNET_SCHEDULER_add_now (&GNUNET_FS_download_start_task_, dc);
   return dc;
 }
 
@@ -1842,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) ||
@@ -1910,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).
  *
@@ -1931,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,
@@ -1955,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;
@@ -1965,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,