d001144f6ca7f013ce698b6b7911553ab11ce029
[oweals/gnunet.git] / src / fs / fs_download.c
1 /*
2      This file is part of GNUnet.
3      (C) 2001-2011 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20 /**
21  * @file fs/fs_download.c
22  * @brief download methods
23  * @author Christian Grothoff
24  *
25  * TODO:
26  * - different priority for scheduling probe downloads?
27  */
28 #include "platform.h"
29 #include "gnunet_constants.h"
30 #include "gnunet_fs_service.h"
31 #include "fs.h"
32 #include "fs_tree.h"
33
34 #define DEBUG_DOWNLOAD GNUNET_NO
35
36 /**
37  * Determine if the given download (options and meta data) should cause
38  * use to try to do a recursive download.
39  */
40 static int
41 is_recursive_download (struct GNUNET_FS_DownloadContext *dc)
42 {
43   return  (0 != (dc->options & GNUNET_FS_DOWNLOAD_OPTION_RECURSIVE)) &&
44     ( (GNUNET_YES == GNUNET_FS_meta_data_test_for_directory (dc->meta)) ||
45       ( (dc->meta == NULL) &&
46         ( (NULL == dc->filename) ||            
47           ( (strlen (dc->filename) >= strlen (GNUNET_FS_DIRECTORY_EXT)) &&
48             (NULL !=
49              strstr (dc->filename + strlen(dc->filename) - strlen(GNUNET_FS_DIRECTORY_EXT),
50                      GNUNET_FS_DIRECTORY_EXT)) ) ) ) );              
51 }
52
53
54 /**
55  * We're storing the IBLOCKS after the DBLOCKS on disk (so that we
56  * only have to truncate the file once we're done).
57  *
58  * Given the offset of a block (with respect to the DBLOCKS) and its
59  * depth, return the offset where we would store this block in the
60  * file.
61  * 
62  * @param fsize overall file size
63  * @param off offset of the block in the file
64  * @param depth depth of the block in the tree, 0 for DBLOCK
65  * @return off for DBLOCKS (depth == treedepth),
66  *         otherwise an offset past the end
67  *         of the file that does not overlap
68  *         with the range for any other block
69  */
70 static uint64_t
71 compute_disk_offset (uint64_t fsize,
72                      uint64_t off,
73                      unsigned int depth)
74 {
75   unsigned int i;
76   uint64_t lsize; /* what is the size of all IBlocks for depth "i"? */
77   uint64_t loff; /* where do IBlocks for depth "i" start? */
78   unsigned int ioff; /* which IBlock corresponds to "off" at depth "i"? */
79   
80   if (depth == 0)
81     return off;
82   /* first IBlocks start at the end of file, rounded up
83      to full DBLOCK_SIZE */
84   loff = ((fsize + DBLOCK_SIZE - 1) / DBLOCK_SIZE) * DBLOCK_SIZE;
85   lsize = ( (fsize + DBLOCK_SIZE - 1) / DBLOCK_SIZE) * sizeof (struct ContentHashKey);
86   GNUNET_assert (0 == (off % DBLOCK_SIZE));
87   ioff = (off / DBLOCK_SIZE);
88   for (i=1;i<depth;i++)
89     {
90       loff += lsize;
91       lsize = (lsize + CHK_PER_INODE - 1) / CHK_PER_INODE;
92       GNUNET_assert (lsize > 0);
93       GNUNET_assert (0 == (ioff % CHK_PER_INODE));
94       ioff /= CHK_PER_INODE;
95     }
96   return loff + ioff * sizeof (struct ContentHashKey);
97 }
98
99
100 /**
101  * Given a block at the given offset and depth, calculate the offset
102  * for the CHK at the given index.
103  *
104  * @param offset the offset of the first
105  *        DBLOCK in the subtree of the 
106  *        identified IBLOCK
107  * @param depth the depth of the IBLOCK in the tree, 0 for DBLOCK
108  * @param k which CHK in the IBLOCK are we 
109  *        talking about
110  * @return offset if k=0, otherwise an appropriately
111  *         larger value (i.e., if depth = 1,
112  *         the returned value should be offset+k*DBLOCK_SIZE)
113  */
114 static uint64_t
115 compute_dblock_offset (uint64_t offset,
116                        unsigned int depth,
117                        unsigned int k)
118 {
119   unsigned int i;
120   uint64_t lsize; /* what is the size of the sum of all DBlocks 
121                      that a CHK at depth i corresponds to? */
122
123   if (depth == 0)
124     return offset;
125   lsize = DBLOCK_SIZE;
126   for (i=1;i<depth;i++)
127     lsize *= CHK_PER_INODE;
128   return offset + k * lsize;
129 }
130
131
132 /**
133  * Fill in all of the generic fields for a download event and call the
134  * callback.
135  *
136  * @param pi structure to fill in
137  * @param dc overall download context
138  */
139 void
140 GNUNET_FS_download_make_status_ (struct GNUNET_FS_ProgressInfo *pi,
141                                  struct GNUNET_FS_DownloadContext *dc)
142 {
143   pi->value.download.dc = dc;
144   pi->value.download.cctx
145     = dc->client_info;
146   pi->value.download.pctx
147     = (dc->parent == NULL) ? NULL : dc->parent->client_info;
148   pi->value.download.sctx
149     = (dc->search == NULL) ? NULL : dc->search->client_info;
150   pi->value.download.uri 
151     = dc->uri;
152   pi->value.download.filename
153     = dc->filename;
154   pi->value.download.size
155     = dc->length;
156   pi->value.download.duration
157     = GNUNET_TIME_absolute_get_duration (dc->start_time);
158   pi->value.download.completed
159     = dc->completed;
160   pi->value.download.anonymity
161     = dc->anonymity;
162   pi->value.download.eta
163     = GNUNET_TIME_calculate_eta (dc->start_time,
164                                  dc->completed,
165                                  dc->length);
166   pi->value.download.is_active = (dc->client == NULL) ? GNUNET_NO : GNUNET_YES;
167   if (0 == (dc->options & GNUNET_FS_DOWNLOAD_IS_PROBE))
168     dc->client_info = dc->h->upcb (dc->h->upcb_cls,
169                                    pi);
170   else
171     dc->client_info = GNUNET_FS_search_probe_progress_ (NULL,
172                                                         pi);
173 }
174
175
176 /**
177  * We're ready to transmit a search request to the
178  * file-sharing service.  Do it.  If there is 
179  * more than one request pending, try to send 
180  * multiple or request another transmission.
181  *
182  * @param cls closure
183  * @param size number of bytes available in buf
184  * @param buf where the callee should write the message
185  * @return number of bytes written to buf
186  */
187 static size_t
188 transmit_download_request (void *cls,
189                            size_t size, 
190                            void *buf);
191
192
193 /**
194  * Closure for iterator processing results.
195  */
196 struct ProcessResultClosure
197 {
198   
199   /**
200    * Hash of data.
201    */
202   GNUNET_HashCode query;
203
204   /**
205    * Data found in P2P network.
206    */ 
207   const void *data;
208
209   /**
210    * Our download context.
211    */
212   struct GNUNET_FS_DownloadContext *dc;
213                 
214   /**
215    * Number of bytes in data.
216    */
217   size_t size;
218
219   /**
220    * Type of data.
221    */
222   enum GNUNET_BLOCK_Type type;
223
224   /**
225    * Flag to indicate if this block should be stored on disk.
226    */
227   int do_store;
228   
229 };
230
231
232 /**
233  * Iterator over entries in the pending requests in the 'active' map for the
234  * reply that we just got.
235  *
236  * @param cls closure (our 'struct ProcessResultClosure')
237  * @param key query for the given value / request
238  * @param value value in the hash map (a 'struct DownloadRequest')
239  * @return GNUNET_YES (we should continue to iterate); unless serious error
240  */
241 static int
242 process_result_with_request (void *cls,
243                              const GNUNET_HashCode * key,
244                              void *value);
245
246
247 /**
248  * We've found a matching block without downloading it.
249  * Encrypt it and pass it to our "receive" function as
250  * if we had received it from the network.
251  * 
252  * @param dc download in question
253  * @param chk request this relates to
254  * @param dr request details
255  * @param block plaintext data matching request
256  * @param len number of bytes in block
257  * @param do_store should we still store the block on disk?
258  * @return GNUNET_OK on success
259  */
260 static int
261 encrypt_existing_match (struct GNUNET_FS_DownloadContext *dc,
262                         const struct ContentHashKey *chk,
263                         struct DownloadRequest *dr,
264                         const char * block,                    
265                         size_t len,
266                         int do_store)
267 {
268   struct ProcessResultClosure prc;
269   char enc[len];
270   struct GNUNET_CRYPTO_AesSessionKey sk;
271   struct GNUNET_CRYPTO_AesInitializationVector iv;
272   GNUNET_HashCode query;
273   
274   GNUNET_CRYPTO_hash_to_aes_key (&chk->key, &sk, &iv);
275   if (-1 == GNUNET_CRYPTO_aes_encrypt (block, len,
276                                        &sk,
277                                        &iv,
278                                        enc))
279     {
280       GNUNET_break (0);
281       return GNUNET_SYSERR;
282     }
283   GNUNET_CRYPTO_hash (enc, len, &query);
284   if (0 != memcmp (&query,
285                    &chk->query,
286                    sizeof (GNUNET_HashCode)))
287     {
288       GNUNET_break_op (0);
289       return GNUNET_SYSERR;
290     }
291 #if DEBUG_DOWNLOAD
292   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
293               "Matching block for `%s' at offset %llu already present, no need for download!\n",
294               dc->filename,
295               (unsigned long long) dr->offset);
296 #endif
297   /* already got it! */
298   prc.dc = dc;
299   prc.data = enc;
300   prc.size = len;
301   prc.type = (0 == dr->depth) 
302     ? GNUNET_BLOCK_TYPE_FS_DBLOCK 
303     : GNUNET_BLOCK_TYPE_FS_IBLOCK;
304   prc.query = chk->query;
305   prc.do_store = do_store;
306   process_result_with_request (&prc,
307                                &chk->key,
308                                dr);
309   return GNUNET_OK;
310 }
311
312
313 /**
314  * We've lost our connection with the FS service.
315  * Re-establish it and re-transmit all of our
316  * pending requests.
317  *
318  * @param dc download context that is having trouble
319  */
320 static void
321 try_reconnect (struct GNUNET_FS_DownloadContext *dc);
322
323
324 /**
325  * We found an entry in a directory.  Check if the respective child
326  * already exists and if not create the respective child download.
327  *
328  * @param cls the parent download
329  * @param filename name of the file in the directory
330  * @param uri URI of the file (CHK or LOC)
331  * @param meta meta data of the file
332  * @param length number of bytes in data
333  * @param data contents of the file (or NULL if they were not inlined)
334  */
335 static void 
336 trigger_recursive_download (void *cls,
337                             const char *filename,
338                             const struct GNUNET_FS_Uri *uri,
339                             const struct GNUNET_CONTAINER_MetaData *meta,
340                             size_t length,
341                             const void *data);
342
343
344 /**
345  * We're done downloading a directory.  Open the file and
346  * trigger all of the (remaining) child downloads.
347  *
348  * @param dc context of download that just completed
349  */
350 static void
351 full_recursive_download (struct GNUNET_FS_DownloadContext *dc)
352 {
353   size_t size;
354   uint64_t size64;
355   void *data;
356   struct GNUNET_DISK_FileHandle *h;
357   struct GNUNET_DISK_MapHandle *m;
358   
359   size64 = GNUNET_FS_uri_chk_get_file_size (dc->uri);
360   size = (size_t) size64;
361   if (size64 != (uint64_t) size)
362     {
363       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
364                   _("Recursive downloads of directories larger than 4 GB are not supported on 32-bit systems\n"));
365       return;
366     }
367   if (dc->filename != NULL)
368     {
369       h = GNUNET_DISK_file_open (dc->filename,
370                                  GNUNET_DISK_OPEN_READ,
371                                  GNUNET_DISK_PERM_NONE);
372     }
373   else
374     {
375       GNUNET_assert (dc->temp_filename != NULL);
376       h = GNUNET_DISK_file_open (dc->temp_filename,
377                                  GNUNET_DISK_OPEN_READ,
378                                  GNUNET_DISK_PERM_NONE);
379     }
380   if (h == NULL)
381     return; /* oops */
382   data = GNUNET_DISK_file_map (h, &m, GNUNET_DISK_MAP_TYPE_READ, size);
383   if (data == NULL)
384     {
385       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
386                   _("Directory too large for system address space\n"));
387     }
388   else
389     {
390       GNUNET_FS_directory_list_contents (size,
391                                          data,
392                                          0,
393                                          &trigger_recursive_download,
394                                          dc);         
395       GNUNET_DISK_file_unmap (m);
396     }
397   GNUNET_DISK_file_close (h);
398   if (dc->filename == NULL)
399     {
400       if (0 != UNLINK (dc->temp_filename))
401         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
402                                   "unlink",
403                                   dc->temp_filename);
404       GNUNET_free (dc->temp_filename);
405       dc->temp_filename = NULL;
406     }
407 }
408
409
410 /**
411  * Check if all child-downloads have completed (or trigger them if
412  * necessary) and once we're completely done, signal completion (and
413  * possibly recurse to parent).  This function MUST be called when the
414  * download of a file itself is done or when the download of a file is
415  * done and then later a direct child download has completed (and
416  * hence this download may complete itself).
417  *
418  * @param dc download to check for completion of children
419  */
420 static void
421 check_completed (struct GNUNET_FS_DownloadContext *dc)
422 {
423   struct GNUNET_FS_ProgressInfo pi;
424   struct GNUNET_FS_DownloadContext *pos;
425
426   /* first, check if we need to download children */
427   if ( (dc->child_head == NULL) &&
428        (is_recursive_download (dc)) )
429     full_recursive_download (dc);  
430   /* then, check if children are done already */
431   pos = dc->child_head;
432   while (pos != NULL)
433     {
434       if ( (pos->emsg == NULL) &&
435            (pos->completed < pos->length) )
436         return; /* not done yet */
437       if ( (pos->child_head != NULL) &&
438            (pos->has_finished != GNUNET_YES) )
439         return; /* not transitively done yet */
440       pos = pos->next;
441     }
442   /* All of our children are done, so mark this download done */
443   dc->has_finished = GNUNET_YES;
444   if (dc->job_queue != NULL)
445     {
446       GNUNET_FS_dequeue_ (dc->job_queue);
447       dc->job_queue = NULL;
448     }
449   GNUNET_FS_download_sync_ (dc);
450
451   /* signal completion */
452   pi.status = GNUNET_FS_STATUS_DOWNLOAD_COMPLETED;
453   GNUNET_FS_download_make_status_ (&pi, dc);
454
455   /* let parent know */
456   if (dc->parent != NULL)
457     check_completed (dc->parent);  
458 }
459
460
461 /**
462  * We got a block of plaintext data (from the meta data).
463  * Try it for upward reconstruction of the data.  On success,
464  * the top-level block will move to state BRS_DOWNLOAD_UP.
465  *
466  * @param dr one of our request entries
467  * @param data plaintext data, starting from the beginning of the file
468  * @param data_len number of bytes in data
469  */ 
470 static void
471 try_match_block (struct GNUNET_FS_DownloadContext *dc,
472                  struct DownloadRequest *dr,
473                  const char *data,
474                  size_t data_len)
475 {
476   struct GNUNET_FS_ProgressInfo pi;
477   unsigned int i;
478   char enc[DBLOCK_SIZE];
479   struct ContentHashKey chks[CHK_PER_INODE];
480   struct ContentHashKey in_chk;
481   struct GNUNET_CRYPTO_AesSessionKey sk;
482   struct GNUNET_CRYPTO_AesInitializationVector iv;
483   size_t dlen;
484   struct DownloadRequest *drc;
485   struct GNUNET_DISK_FileHandle *fh;
486   int complete;
487   const char *fn;
488   const char *odata;
489   size_t odata_len;
490   
491   odata = data;
492   odata_len = data_len;
493   if (BRS_DOWNLOAD_UP == dr->state)
494     return;
495   if (dr->depth > 0)
496     {
497       complete = GNUNET_YES;
498       for (i=0;i<dr->num_children;i++)
499         {
500           drc = dr->children[i];
501           try_match_block (dc,
502                            drc,
503                            data, data_len);
504           if (drc->state != BRS_RECONSTRUCT_META_UP)
505             complete = GNUNET_NO;
506           else
507             chks[i] = drc->chk;
508         }
509       if (GNUNET_YES != complete)
510         return;
511       data = (const char*) chks;
512       dlen = dr->num_children * sizeof (struct ContentHashKey);
513     }
514   else
515     {
516       if (dr->offset > data_len) 
517         return; /* oops */
518       dlen = GNUNET_MIN (data_len - dr->offset,
519                          DBLOCK_SIZE);
520     }
521   GNUNET_CRYPTO_hash (&data[dr->offset],
522                       dlen,
523                       &in_chk.key);
524   GNUNET_CRYPTO_hash_to_aes_key (&in_chk.key, &sk, &iv);
525   if (-1 == GNUNET_CRYPTO_aes_encrypt (&data[dr->offset], dlen,
526                                        &sk,
527                                        &iv,
528                                        enc))
529     {
530       GNUNET_break (0);
531       return;
532     }
533   GNUNET_CRYPTO_hash (enc, dlen, &in_chk.query);
534   switch (dr->state)
535     {
536     case BRS_INIT:
537       dr->chk = in_chk;
538       dr->state = BRS_RECONSTRUCT_META_UP;
539       break;
540     case BRS_CHK_SET:
541       if (0 != memcmp (&in_chk,
542                        &dr->chk,
543                        sizeof (struct ContentHashKey)))
544         {
545           /* other peer provided bogus meta data */
546           GNUNET_break_op (0);
547           break;
548         }
549       /* write block to disk */
550       fn = dc->filename != NULL 
551         ? dc->filename 
552         : dc->temp_filename;
553       fh = GNUNET_DISK_file_open (fn,
554                                   GNUNET_DISK_OPEN_READWRITE | 
555                                   GNUNET_DISK_OPEN_CREATE | 
556                                   GNUNET_DISK_OPEN_TRUNCATE,
557                                   GNUNET_DISK_PERM_USER_READ |
558                                   GNUNET_DISK_PERM_USER_WRITE |
559                                   GNUNET_DISK_PERM_GROUP_READ |
560                                   GNUNET_DISK_PERM_OTHER_READ);     
561       if (fh == NULL)
562         {
563           GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
564                                     "open",
565                                     fn);
566           GNUNET_asprintf (&dc->emsg,
567                            _("Failed to open file `%s' for writing"),
568                            fn);
569           GNUNET_DISK_file_close (fh);
570           dr->state = BRS_ERROR;
571           pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
572           pi.value.download.specifics.error.message = dc->emsg;
573           GNUNET_FS_download_make_status_ (&pi, dc); 
574           return;
575         }
576       if (data_len != 
577           GNUNET_DISK_file_write (fh,
578                                   odata,
579                                   odata_len))
580         {
581           GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
582                                     "write",
583                                     fn);              
584           GNUNET_asprintf (&dc->emsg,
585                            _("Failed to open file `%s' for writing"),
586                            fn);
587           GNUNET_DISK_file_close (fh);
588           dr->state = BRS_ERROR;
589           pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
590           pi.value.download.specifics.error.message = dc->emsg;
591           GNUNET_FS_download_make_status_ (&pi, dc);      
592           return;
593         }
594       GNUNET_DISK_file_close (fh);
595       /* signal success */      
596       dr->state = BRS_DOWNLOAD_UP;
597       dc->completed = dc->length;
598       GNUNET_FS_download_sync_ (dc);
599       pi.status = GNUNET_FS_STATUS_DOWNLOAD_PROGRESS;
600       pi.value.download.specifics.progress.data = data;
601       pi.value.download.specifics.progress.offset = 0;
602       pi.value.download.specifics.progress.data_len = dlen;
603       pi.value.download.specifics.progress.depth = 0;
604       GNUNET_FS_download_make_status_ (&pi, dc);
605       if (0 != truncate (dc->filename,
606                          GNUNET_ntohll (dc->uri->data.chk.file_length)))
607         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
608                                   "truncate",
609                                   dc->filename);
610       check_completed (dc);      
611       break;
612     default:
613       /* how did we get here? */
614       GNUNET_break (0);
615       break;
616     }
617 }
618
619
620 /**
621  * Type of a function that libextractor calls for each
622  * meta data item found.  If we find full data meta data,
623  * call 'try_match_block' on it.
624  *
625  * @param cls our 'struct GNUNET_FS_DownloadContext*'
626  * @param plugin_name name of the plugin that produced this value;
627  *        special values can be used (i.e. '&lt;zlib&gt;' for zlib being
628  *        used in the main libextractor library and yielding
629  *        meta data).
630  * @param type libextractor-type describing the meta data
631  * @param format basic format information about data 
632  * @param data_mime_type mime-type of data (not of the original file);
633  *        can be NULL (if mime-type is not known)
634  * @param data actual meta-data found
635  * @param data_len number of bytes in data
636  * @return 0 to continue extracting, 1 to abort
637  */ 
638 static int
639 match_full_data (void *cls,
640                  const char *plugin_name,
641                  enum EXTRACTOR_MetaType type,
642                  enum EXTRACTOR_MetaFormat format,
643                  const char *data_mime_type,
644                  const char *data,
645                  size_t data_len)
646 {
647   struct GNUNET_FS_DownloadContext *dc = cls;
648
649   if (type != EXTRACTOR_METATYPE_GNUNET_FULL_DATA) 
650     return 0;
651 #if DEBUG_DOWNLOAD
652   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
653               "Found %u bytes of FD!\n",
654               (unsigned int) data_len);
655 #endif
656   if (GNUNET_FS_uri_chk_get_file_size (dc->uri) != data_len)
657     {
658       GNUNET_break_op (0);
659       return 1; /* bogus meta data */
660     }
661   try_match_block (dc,
662                    dc->top_request,
663                    data,
664                    data_len);
665   return 1;
666 }
667
668
669 /**
670  * Set the state of the given download request to
671  * BRS_DOWNLOAD_UP and propagate it up the tree.
672  *
673  * @param dr download request that is done
674  */
675 static void
676 propagate_up (struct DownloadRequest *dr)
677 {
678   unsigned int i;
679
680   do
681     {
682       dr->state = BRS_DOWNLOAD_UP;
683       dr = dr->parent;
684       if (dr == NULL)
685         break;
686       for (i=0;i<dr->num_children;i++)
687         if (dr->children[i]->state != BRS_DOWNLOAD_UP)
688           break;
689     }
690   while (i == dr->num_children);
691 }
692
693
694 /**
695  * Try top-down reconstruction.  Before, the given request node
696  * must have the state BRS_CHK_SET.  Afterwards, more nodes may
697  * have that state or advanced to BRS_DOWNLOAD_DOWN or even
698  * BRS_DOWNLOAD_UP.  It is also possible to get BRS_ERROR on the
699  * top level.
700  *
701  * @param dc overall download this block belongs to
702  * @param dr block to reconstruct
703  */
704 static void
705 try_top_down_reconstruction (struct GNUNET_FS_DownloadContext *dc,
706                              struct DownloadRequest *dr)
707 {
708   uint64_t off;
709   char block[DBLOCK_SIZE];
710   GNUNET_HashCode key;
711   uint64_t total;
712   size_t len;
713   unsigned int i;
714   unsigned int chk_off;
715   struct DownloadRequest *drc;
716   uint64_t child_block_size;
717   const struct ContentHashKey *chks;
718   int up_done;
719   
720   GNUNET_assert (dc->rfh != NULL);
721   GNUNET_assert (dr->state == BRS_CHK_SET);
722   total = GNUNET_FS_uri_chk_get_file_size (dc->uri);
723   GNUNET_assert (dr->depth < dc->treedepth);
724   len = GNUNET_FS_tree_calculate_block_size (total,
725                                              dr->offset,
726                                              dr->depth);
727   GNUNET_assert (len <= DBLOCK_SIZE);
728   off = compute_disk_offset (total,
729                              dr->offset,
730                              dr->depth);
731   if (dc->old_file_size < off + len) 
732     return; /* failure */
733   if (off  != 
734       GNUNET_DISK_file_seek (dc->rfh,
735                              off,
736                              GNUNET_DISK_SEEK_SET) ) 
737     {
738       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
739                                 "seek",
740                                 dc->filename);
741       return; /* failure */
742     }
743   if (len != 
744       GNUNET_DISK_file_read (dc->rfh,
745                              block,
746                              len)) 
747     {
748       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
749                                 "read",
750                                 dc->filename);
751       return; /* failure */
752     }
753   GNUNET_CRYPTO_hash (block, len, &key);
754   if (0 != memcmp (&key,
755                    &dr->chk.key,
756                    sizeof (GNUNET_HashCode)))
757     return; /* mismatch */
758   if (GNUNET_OK !=
759       encrypt_existing_match (dc,
760                               &dr->chk,
761                               dr,
762                               block,
763                               len,
764                               GNUNET_NO))
765     {
766       /* hash matches but encrypted block does not, really bad */
767       dr->state = BRS_ERROR;
768       /* propagate up */
769       while (dr->parent != NULL)
770         {
771           dr = dr->parent;
772           dr->state = BRS_ERROR;
773         }
774       return;
775     }
776   /* block matches */
777   dr->state = BRS_DOWNLOAD_DOWN;
778
779   /* set CHKs for children */
780   up_done = GNUNET_YES;
781   chks = (const struct ContentHashKey*) block;
782   for (i=0;i<dr->num_children;i++)
783     {
784       drc = dr->children[i];
785       GNUNET_assert (drc->offset >= dr->offset);
786       child_block_size = GNUNET_FS_tree_compute_tree_size (drc->depth);
787       GNUNET_assert (0 == (drc->offset - dr->offset) % child_block_size);
788       chk_off = (drc->offset - dr->offset) / child_block_size;
789       GNUNET_assert (drc->state == BRS_INIT);
790       drc->state = BRS_CHK_SET;
791       drc->chk = chks[chk_off];
792       try_top_down_reconstruction (dc, drc);
793       if (drc->state != BRS_DOWNLOAD_UP)
794         up_done = GNUNET_NO; /* children not all done */
795     } 
796   if (up_done == GNUNET_YES)
797     propagate_up (dr); /* children all done (or no children...) */
798 }
799
800
801 /**
802  * Schedule the download of the specified block in the tree.
803  *
804  * @param dc overall download this block belongs to
805  * @param chk content-hash-key of the block
806  * @param offset offset of the block in the file
807  *         (for IBlocks, the offset is the lowest
808  *          offset of any DBlock in the subtree under
809  *          the IBlock)
810  * @param depth depth of the block, 0 is the root of the tree
811  */
812 static void
813 schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
814                          struct DownloadRequest *dr)
815 {
816   unsigned int i;
817
818   switch (dr->state)
819     {
820     case BRS_INIT:
821       GNUNET_assert (0);
822       break;
823     case BRS_RECONSTRUCT_DOWN:
824       GNUNET_assert (0);
825       break;
826     case BRS_RECONSTRUCT_META_UP:
827       GNUNET_assert (0);
828       break;
829     case BRS_RECONSTRUCT_UP:
830       GNUNET_assert (0);
831       break;
832     case BRS_CHK_SET:
833       /* normal case, start download */
834       break;
835     case BRS_DOWNLOAD_DOWN:
836       for (i=0;i<dr->num_children;i++)
837         schedule_block_download (dc, dr->children[i]);
838       return;
839     case BRS_DOWNLOAD_UP:
840       /* We're done! */
841       return;
842     case BRS_ERROR:
843       GNUNET_break (0);
844       return;
845     }
846 #if DEBUG_DOWNLOAD
847   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
848               "Scheduling download at offset %llu and depth %u for `%s'\n",
849               (unsigned long long) dr->offset,
850               dr->depth,
851               GNUNET_h2s (&dr->chk.query));
852 #endif
853   GNUNET_CONTAINER_DLL_insert (dc->pending_head,
854                                dc->pending_tail,
855                                dr);
856   dr->is_pending = GNUNET_YES;
857   GNUNET_CONTAINER_multihashmap_put (dc->active,
858                                      &dr->chk.query,
859                                      dr,
860                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
861   if (dc->client == NULL)
862     return; /* download not active */
863   if (NULL == dc->th)
864     dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
865                                                   sizeof (struct SearchMessage),
866                                                   GNUNET_CONSTANTS_SERVICE_TIMEOUT,
867                                                   GNUNET_NO,
868                                                   &transmit_download_request,
869                                                   dc);
870 }
871
872
873 #define GNUNET_FS_URI_CHK_PREFIX GNUNET_FS_URI_PREFIX GNUNET_FS_URI_CHK_INFIX
874
875 /**
876  * We found an entry in a directory.  Check if the respective child
877  * already exists and if not create the respective child download.
878  *
879  * @param cls the parent download
880  * @param filename name of the file in the directory
881  * @param uri URI of the file (CHK or LOC)
882  * @param meta meta data of the file
883  * @param length number of bytes in data
884  * @param data contents of the file (or NULL if they were not inlined)
885  */
886 static void 
887 trigger_recursive_download (void *cls,
888                             const char *filename,
889                             const struct GNUNET_FS_Uri *uri,
890                             const struct GNUNET_CONTAINER_MetaData *meta,
891                             size_t length,
892                             const void *data)
893 {
894   struct GNUNET_FS_DownloadContext *dc = cls;  
895   struct GNUNET_FS_DownloadContext *cpos;
896   // struct GNUNET_DISK_FileHandle *fh;
897   char *temp_name;
898   // const char *real_name;
899   char *fn;
900   char *us;
901   char *ext;
902   char *dn;
903   char *pos;
904   char *full_name;
905
906   if (NULL == uri)
907     return; /* entry for the directory itself */
908   cpos = dc->child_head;
909   while (cpos != NULL)
910     {
911       if ( (GNUNET_FS_uri_test_equal (uri,
912                                       cpos->uri)) ||
913            ( (filename != NULL) &&
914              (0 == strcmp (cpos->filename,
915                            filename)) ) )
916         break;  
917       cpos = cpos->next;
918     }
919   if (cpos != NULL)
920     return; /* already exists */
921   fn = NULL;
922   if (NULL == filename)
923     {
924       fn = GNUNET_FS_meta_data_suggest_filename (meta);
925       if (fn == NULL)
926         {
927           us = GNUNET_FS_uri_to_string (uri);
928           fn = GNUNET_strdup (&us [strlen (GNUNET_FS_URI_CHK_PREFIX)]);
929           GNUNET_free (us);
930         }
931       else if (fn[0] == '.')
932         {
933           ext = fn;
934           us = GNUNET_FS_uri_to_string (uri);
935           GNUNET_asprintf (&fn,
936                            "%s%s",
937                            &us[strlen (GNUNET_FS_URI_CHK_PREFIX)], ext);
938           GNUNET_free (ext);
939           GNUNET_free (us);
940         }
941       /* change '\' to '/' (this should have happened
942        during insertion, but malicious peers may
943        not have done this) */
944       while (NULL != (pos = strstr (fn, "\\")))
945         *pos = '/';
946       /* remove '../' everywhere (again, well-behaved
947          peers don't do this, but don't trust that
948          we did not get something nasty) */
949       while (NULL != (pos = strstr (fn, "../")))
950         {
951           pos[0] = '_';
952           pos[1] = '_';
953           pos[2] = '_';
954         }
955       filename = fn;
956     }
957   if (dc->filename == NULL)
958     {
959       full_name = NULL;
960     }
961   else
962     {
963       dn = GNUNET_strdup (dc->filename);
964       GNUNET_break ( (strlen (dn) >= strlen (GNUNET_FS_DIRECTORY_EXT)) &&
965                      (NULL !=
966                       strstr (dn + strlen(dn) - strlen(GNUNET_FS_DIRECTORY_EXT),
967                               GNUNET_FS_DIRECTORY_EXT)) );
968       if ( (strlen (dn) >= strlen (GNUNET_FS_DIRECTORY_EXT)) &&
969            (NULL !=
970             strstr (dn + strlen(dn) - strlen(GNUNET_FS_DIRECTORY_EXT),
971                     GNUNET_FS_DIRECTORY_EXT)) )      
972         dn[strlen(dn) - strlen (GNUNET_FS_DIRECTORY_EXT)] = '\0';      
973       if ( (GNUNET_YES == GNUNET_FS_meta_data_test_for_directory (meta)) &&
974            ( (strlen (filename) < strlen (GNUNET_FS_DIRECTORY_EXT)) ||
975              (NULL ==
976               strstr (filename + strlen(filename) - strlen(GNUNET_FS_DIRECTORY_EXT),
977                       GNUNET_FS_DIRECTORY_EXT)) ) )
978         {
979           GNUNET_asprintf (&full_name,
980                            "%s%s%s%s",
981                            dn,
982                            DIR_SEPARATOR_STR,
983                            filename,
984                            GNUNET_FS_DIRECTORY_EXT);
985         }
986       else
987         {
988           GNUNET_asprintf (&full_name,
989                            "%s%s%s",
990                            dn,
991                            DIR_SEPARATOR_STR,
992                            filename);
993         }
994       GNUNET_free (dn);
995     }
996   if ( (full_name != NULL) &&
997        (GNUNET_OK !=
998         GNUNET_DISK_directory_create_for_file (full_name)) )
999     {
1000       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1001                   _("Failed to create directory for recursive download of `%s'\n"),
1002                   full_name);
1003       GNUNET_free (full_name);
1004       GNUNET_free_non_null (fn);
1005       return;
1006     }
1007
1008   temp_name = NULL;
1009 #if 0
1010   if (data != NULL) 
1011     {
1012       if (GNUNET_FS_uri_chk_get_file_size (uri) == length)
1013         {
1014           if (full_name == NULL)
1015             {
1016               temp_name = GNUNET_DISK_mktemp ("gnunet-download-trd");
1017               real_name = temp_name;
1018             }
1019           else
1020             {
1021               real_name = full_name;
1022             }
1023           /* write to disk, then trigger normal download which will instantly progress to completion */
1024           fh = GNUNET_DISK_file_open (real_name,
1025                                   GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_TRUNCATE | GNUNET_DISK_OPEN_CREATE,
1026                                       GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE);
1027           if (fh == NULL)
1028             {
1029               GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
1030                                         "open",
1031                                         real_name);           
1032               GNUNET_free (full_name);
1033               GNUNET_free_non_null (fn);
1034               return;
1035             }
1036           if (length != 
1037               GNUNET_DISK_file_write (fh,
1038                                       data,
1039                                       length))
1040             {
1041               GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
1042                                         "write",
1043                                         full_name);           
1044             }
1045 #if DEBUG_DOWNLOAD
1046           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1047                       "Wrote %llu bytes of plaintext from meta data to `%s' for validation\n",
1048                       (unsigned long long) length,
1049                       real_name);
1050 #endif
1051           GNUNET_DISK_file_close (fh);
1052         }
1053       else
1054         {
1055           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1056                       _("Length mismatch between supplied plaintext and expected file size (%llu != %llu)\n"),
1057                       (unsigned long long) GNUNET_FS_uri_chk_get_file_size (uri),
1058                       (unsigned long long) length);
1059         }
1060     }
1061 #endif
1062 #if DEBUG_DOWNLOAD
1063   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1064               "Triggering recursive download of size %llu with %u bytes MD\n",
1065               (unsigned long long) GNUNET_FS_uri_chk_get_file_size (uri),
1066               (unsigned int) GNUNET_CONTAINER_meta_data_get_serialized_size (meta));
1067 #endif
1068   GNUNET_FS_download_start (dc->h,
1069                             uri,
1070                             meta,
1071                             full_name, temp_name,
1072                             0,
1073                             GNUNET_FS_uri_chk_get_file_size (uri),
1074                             dc->anonymity,
1075                             dc->options,
1076                             NULL,
1077                             dc);
1078   GNUNET_free_non_null (full_name);
1079   GNUNET_free_non_null (temp_name);
1080   GNUNET_free_non_null (fn);
1081 }
1082
1083
1084 /**
1085  * (recursively) free download request structure
1086  *
1087  * @param dr request to free
1088  */
1089 void
1090 GNUNET_FS_free_download_request_ (struct DownloadRequest *dr)
1091 {
1092   unsigned int i;
1093
1094   if (dr == NULL)
1095     return;
1096   for (i=0;i<dr->num_children;i++)
1097     GNUNET_FS_free_download_request_ (dr->children[i]);
1098   GNUNET_free_non_null (dr->children);
1099   GNUNET_free (dr);
1100 }
1101
1102
1103 /**
1104  * Iterator over entries in the pending requests in the 'active' map for the
1105  * reply that we just got.
1106  *
1107  * @param cls closure (our 'struct ProcessResultClosure')
1108  * @param key query for the given value / request
1109  * @param value value in the hash map (a 'struct DownloadRequest')
1110  * @return GNUNET_YES (we should continue to iterate); unless serious error
1111  */
1112 static int
1113 process_result_with_request (void *cls,
1114                              const GNUNET_HashCode *key,
1115                              void *value)
1116 {
1117   struct ProcessResultClosure *prc = cls;
1118   struct DownloadRequest *dr = value;
1119   struct GNUNET_FS_DownloadContext *dc = prc->dc;
1120   struct DownloadRequest *drc;
1121   struct GNUNET_DISK_FileHandle *fh = NULL;
1122   struct GNUNET_CRYPTO_AesSessionKey skey;
1123   struct GNUNET_CRYPTO_AesInitializationVector iv;
1124   char pt[prc->size];
1125   struct GNUNET_FS_ProgressInfo pi;
1126   uint64_t off;
1127   size_t bs;
1128   size_t app;
1129   int i;
1130   struct ContentHashKey *chk;
1131
1132 #if DEBUG_DOWNLOAD
1133   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1134               "Received block `%s' matching pending request at depth %u and offset %llu/%llu\n",
1135               GNUNET_h2s (key),
1136               dr->depth,
1137               (unsigned long long) dr->offset,
1138               (unsigned long long) GNUNET_ntohll (dc->uri->data.chk.file_length));
1139               
1140 #endif
1141   bs = GNUNET_FS_tree_calculate_block_size (GNUNET_ntohll (dc->uri->data.chk.file_length),
1142                                             dr->offset,
1143                                             dr->depth);
1144   if (prc->size != bs)
1145     {
1146       GNUNET_asprintf (&dc->emsg,
1147                        _("Internal error or bogus download URI (expected %u bytes at depth %u and offset %llu/%llu, got %u bytes)\n"),
1148                        bs,
1149                        dr->depth,
1150                        (unsigned long long) dr->offset,
1151                        (unsigned long long) GNUNET_ntohll (dc->uri->data.chk.file_length),
1152                        prc->size);
1153       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1154                   "%s", 
1155                   dc->emsg);
1156       while (dr->parent != NULL)
1157         {
1158           dr->state = BRS_ERROR;
1159           dr = dr->parent;
1160         }
1161       dr->state = BRS_ERROR;
1162       goto signal_error;
1163     }
1164
1165   (void) GNUNET_CONTAINER_multihashmap_remove (dc->active,
1166                                                &prc->query,
1167                                                dr);
1168   if (GNUNET_YES == dr->is_pending)
1169     {
1170       GNUNET_CONTAINER_DLL_remove (dc->pending_head,
1171                                    dc->pending_tail,
1172                                    dr);
1173       dr->is_pending = GNUNET_NO;
1174     }
1175
1176
1177   GNUNET_CRYPTO_hash_to_aes_key (&dr->chk.key, &skey, &iv);
1178   if (-1 == GNUNET_CRYPTO_aes_decrypt (prc->data,
1179                                        prc->size,
1180                                        &skey,
1181                                        &iv,
1182                                        pt))
1183     {
1184       GNUNET_break (0);
1185       dc->emsg = GNUNET_strdup (_("internal error decrypting content"));
1186       goto signal_error;
1187     }
1188   off = compute_disk_offset (GNUNET_ntohll (dc->uri->data.chk.file_length),
1189                              dr->offset,
1190                              dr->depth);
1191   /* save to disk */
1192   if ( ( GNUNET_YES == prc->do_store) &&
1193        ( (dc->filename != NULL) ||
1194          (is_recursive_download (dc)) ) &&
1195        ( (dr->depth == dc->treedepth) ||
1196          (0 == (dc->options & GNUNET_FS_DOWNLOAD_NO_TEMPORARIES)) ) )
1197     {
1198       fh = GNUNET_DISK_file_open (dc->filename != NULL 
1199                                   ? dc->filename 
1200                                   : dc->temp_filename, 
1201                                   GNUNET_DISK_OPEN_READWRITE | 
1202                                   GNUNET_DISK_OPEN_CREATE,
1203                                   GNUNET_DISK_PERM_USER_READ |
1204                                   GNUNET_DISK_PERM_USER_WRITE |
1205                                   GNUNET_DISK_PERM_GROUP_READ |
1206                                   GNUNET_DISK_PERM_OTHER_READ);    
1207       if (NULL == fh)
1208         {
1209           GNUNET_asprintf (&dc->emsg,
1210                            _("Download failed: could not open file `%s': %s\n"),
1211                            dc->filename,
1212                            STRERROR (errno));
1213           goto signal_error;
1214         }      
1215 #if DEBUG_DOWNLOAD
1216       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1217                   "Saving decrypted block to disk at offset %llu\n",
1218                   (unsigned long long) off);
1219 #endif
1220       if ( (off  != 
1221             GNUNET_DISK_file_seek (fh,
1222                                    off,
1223                                    GNUNET_DISK_SEEK_SET) ) )
1224         {
1225           GNUNET_asprintf (&dc->emsg,
1226                            _("Failed to seek to offset %llu in file `%s': %s\n"),
1227                            (unsigned long long) off,
1228                            dc->filename,
1229                            STRERROR (errno));
1230           goto signal_error;
1231         }
1232       if (prc->size !=
1233           GNUNET_DISK_file_write (fh,
1234                                   pt,
1235                                   prc->size))
1236         {
1237           GNUNET_asprintf (&dc->emsg,
1238                            _("Failed to write block of %u bytes at offset %llu in file `%s': %s\n"),
1239                            (unsigned int) prc->size,
1240                            (unsigned long long) off,
1241                            dc->filename,
1242                            STRERROR (errno));
1243           goto signal_error;
1244         }
1245       GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
1246       fh = NULL;
1247     }
1248
1249   if (dr->depth == 0) 
1250     {
1251       /* DBLOCK, update progress and try recursion if applicable */
1252       app = prc->size;
1253       if (dr->offset < dc->offset)
1254         {
1255           /* starting offset begins in the middle of pt,
1256              do not count first bytes as progress */
1257           GNUNET_assert (app > (dc->offset - dr->offset));
1258           app -= (dc->offset - dr->offset);       
1259         }
1260       if (dr->offset + prc->size > dc->offset + dc->length)
1261         {
1262           /* end of block is after relevant range,
1263              do not count last bytes as progress */
1264           GNUNET_assert (app > (dr->offset + prc->size) - (dc->offset + dc->length));
1265           app -= (dr->offset + prc->size) - (dc->offset + dc->length);
1266         }
1267       dc->completed += app;
1268
1269       /* do recursive download if option is set and either meta data
1270          says it is a directory or if no meta data is given AND filename 
1271          ends in '.gnd' (top-level case) */
1272       if (is_recursive_download (dc))
1273         GNUNET_FS_directory_list_contents (prc->size,
1274                                            pt,
1275                                            off,
1276                                            &trigger_recursive_download,
1277                                            dc);         
1278             
1279     }
1280   dr->state = BRS_DOWNLOAD_DOWN;
1281   pi.status = GNUNET_FS_STATUS_DOWNLOAD_PROGRESS;
1282   pi.value.download.specifics.progress.data = pt;
1283   pi.value.download.specifics.progress.offset = dr->offset;
1284   pi.value.download.specifics.progress.data_len = prc->size;
1285   pi.value.download.specifics.progress.depth = dr->depth;
1286   GNUNET_FS_download_make_status_ (&pi, dc);
1287   GNUNET_assert (dc->completed <= dc->length);
1288   if (dr->depth == 0) 
1289     propagate_up (dr);
1290
1291   if (dc->completed == dc->length)
1292     {
1293       /* download completed, signal */
1294 #if DEBUG_DOWNLOAD
1295       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1296                   "Download completed, truncating file to desired length %llu\n",
1297                   (unsigned long long) GNUNET_ntohll (dc->uri->data.chk.file_length));
1298 #endif
1299       /* truncate file to size (since we store IBlocks at the end) */
1300       if (dc->filename != NULL)
1301         {
1302           if (0 != truncate (dc->filename,
1303                              GNUNET_ntohll (dc->uri->data.chk.file_length)))
1304             GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
1305                                       "truncate",
1306                                       dc->filename);
1307         }
1308       GNUNET_assert (dr->depth == 0);
1309       check_completed (dc);
1310     }
1311   if (dr->depth == 0) 
1312     {
1313       /* bottom of the tree, no child downloads possible, just sync */
1314       GNUNET_FS_download_sync_ (dc);
1315       return GNUNET_YES;
1316     }
1317
1318 #if DEBUG_DOWNLOAD
1319   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1320               "Triggering downloads of children (this block was at depth %u and offset %llu)\n",
1321               dr->depth,
1322               (unsigned long long) dr->offset);
1323 #endif
1324   GNUNET_assert (0 == (prc->size % sizeof(struct ContentHashKey)));
1325   chk = (struct ContentHashKey*) pt;
1326   for (i=(prc->size / sizeof(struct ContentHashKey))-1;i>=0;i--)
1327     {
1328       off = compute_dblock_offset (dr->offset,
1329                                    dr->depth,
1330                                    i);
1331       drc = dr->children[i];
1332       switch (drc->state)
1333         {
1334         case BRS_INIT:
1335           drc->chk = chk[i];
1336           drc->state = BRS_CHK_SET;
1337           schedule_block_download (dc, drc);
1338           break;
1339         case BRS_RECONSTRUCT_DOWN:
1340           GNUNET_assert (0);
1341           break;
1342         case BRS_RECONSTRUCT_META_UP:
1343           GNUNET_assert (0);
1344           break;
1345         case BRS_RECONSTRUCT_UP:
1346           GNUNET_assert (0);
1347           break;
1348         case BRS_CHK_SET:
1349           GNUNET_assert (0);
1350           break;
1351         case BRS_DOWNLOAD_DOWN:
1352           GNUNET_assert (0);
1353           break;
1354         case BRS_DOWNLOAD_UP:
1355           GNUNET_assert (0);
1356           break;
1357         case BRS_ERROR:
1358           GNUNET_assert (0);
1359           break;
1360         default:
1361           GNUNET_assert (0);
1362           break;
1363         }
1364     }
1365   GNUNET_FS_download_sync_ (dc);
1366   return GNUNET_YES;
1367
1368  signal_error:
1369   if (fh != NULL)
1370     GNUNET_DISK_file_close (fh);
1371   pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
1372   pi.value.download.specifics.error.message = dc->emsg;
1373   GNUNET_FS_download_make_status_ (&pi, dc);
1374   /* abort all pending requests */
1375   if (NULL != dc->th)
1376     {
1377       GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
1378       dc->th = NULL;
1379     }
1380   GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
1381   dc->client = NULL;
1382   GNUNET_FS_free_download_request_ (dc->top_request);
1383   dc->top_request = NULL;
1384   GNUNET_CONTAINER_multihashmap_destroy (dc->active);
1385   dc->active = NULL;
1386   dc->pending_head = NULL;
1387   dc->pending_tail = NULL;
1388   GNUNET_FS_download_sync_ (dc);
1389   return GNUNET_NO;
1390 }
1391
1392
1393 /**
1394  * Process a download result.
1395  *
1396  * @param dc our download context
1397  * @param type type of the result
1398  * @param data the (encrypted) response
1399  * @param size size of data
1400  */
1401 static void
1402 process_result (struct GNUNET_FS_DownloadContext *dc,
1403                 enum GNUNET_BLOCK_Type type,
1404                 const void *data,
1405                 size_t size)
1406 {
1407   struct ProcessResultClosure prc;
1408
1409   prc.dc = dc;
1410   prc.data = data;
1411   prc.size = size;
1412   prc.type = type;
1413   prc.do_store = GNUNET_YES;
1414   GNUNET_CRYPTO_hash (data, size, &prc.query);
1415 #if DEBUG_DOWNLOAD
1416   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1417               "Received result for query `%s' from `%s'-service\n",
1418               GNUNET_h2s (&prc.query),
1419               "FS");
1420 #endif
1421   GNUNET_CONTAINER_multihashmap_get_multiple (dc->active,
1422                                               &prc.query,
1423                                               &process_result_with_request,
1424                                               &prc);
1425 }
1426
1427
1428 /**
1429  * Type of a function to call when we receive a message
1430  * from the service.
1431  *
1432  * @param cls closure
1433  * @param msg message received, NULL on timeout or fatal error
1434  */
1435 static void 
1436 receive_results (void *cls,
1437                  const struct GNUNET_MessageHeader * msg)
1438 {
1439   struct GNUNET_FS_DownloadContext *dc = cls;
1440   const struct PutMessage *cm;
1441   uint16_t msize;
1442
1443   if ( (NULL == msg) ||
1444        (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_PUT) ||
1445        (sizeof (struct PutMessage) > ntohs(msg->size)) )
1446     {
1447       GNUNET_break (msg == NULL);       
1448       try_reconnect (dc);
1449       return;
1450     }
1451   msize = ntohs(msg->size);
1452   cm = (const struct PutMessage*) msg;
1453   process_result (dc, 
1454                   ntohl (cm->type),
1455                   &cm[1],
1456                   msize - sizeof (struct PutMessage));
1457   if (dc->client == NULL)
1458     return; /* fatal error */
1459   /* continue receiving */
1460   GNUNET_CLIENT_receive (dc->client,
1461                          &receive_results,
1462                          dc,
1463                          GNUNET_TIME_UNIT_FOREVER_REL);
1464 }
1465
1466
1467
1468 /**
1469  * We're ready to transmit a search request to the
1470  * file-sharing service.  Do it.  If there is 
1471  * more than one request pending, try to send 
1472  * multiple or request another transmission.
1473  *
1474  * @param cls closure
1475  * @param size number of bytes available in buf
1476  * @param buf where the callee should write the message
1477  * @return number of bytes written to buf
1478  */
1479 static size_t
1480 transmit_download_request (void *cls,
1481                            size_t size, 
1482                            void *buf)
1483 {
1484   struct GNUNET_FS_DownloadContext *dc = cls;
1485   size_t msize;
1486   struct SearchMessage *sm;
1487   struct DownloadRequest *dr;
1488
1489   dc->th = NULL;
1490   if (NULL == buf)
1491     {
1492 #if DEBUG_DOWNLOAD
1493       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1494                   "Transmitting download request failed, trying to reconnect\n");
1495 #endif
1496       try_reconnect (dc);
1497       return 0;
1498     }
1499   GNUNET_assert (size >= sizeof (struct SearchMessage));
1500   msize = 0;
1501   sm = buf;
1502   while ( (NULL != (dr = dc->pending_head)) &&
1503           (size >= msize + sizeof (struct SearchMessage)) )
1504     {
1505 #if DEBUG_DOWNLOAD
1506       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1507                   "Transmitting download request for `%s' to `%s'-service\n",
1508                   GNUNET_h2s (&dr->chk.query),
1509                   "FS");
1510 #endif
1511       memset (sm, 0, sizeof (struct SearchMessage));
1512       sm->header.size = htons (sizeof (struct SearchMessage));
1513       sm->header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
1514       if (0 != (dc->options & GNUNET_FS_DOWNLOAD_OPTION_LOOPBACK_ONLY))
1515         sm->options = htonl (1);
1516       else
1517         sm->options = htonl (0);      
1518       if (dr->depth == 0)
1519         sm->type = htonl (GNUNET_BLOCK_TYPE_FS_DBLOCK);
1520       else
1521         sm->type = htonl (GNUNET_BLOCK_TYPE_FS_IBLOCK);
1522       sm->anonymity_level = htonl (dc->anonymity);
1523       sm->target = dc->target.hashPubKey;
1524       sm->query = dr->chk.query;
1525       GNUNET_CONTAINER_DLL_remove (dc->pending_head,
1526                                    dc->pending_tail,
1527                                    dr);
1528       dr->is_pending = GNUNET_NO;
1529       msize += sizeof (struct SearchMessage);
1530       sm++;
1531     }
1532   if (dc->pending_head != NULL)
1533     {
1534       dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
1535                                                     sizeof (struct SearchMessage),
1536                                                     GNUNET_CONSTANTS_SERVICE_TIMEOUT,
1537                                                     GNUNET_NO,
1538                                                     &transmit_download_request,
1539                                                     dc); 
1540       GNUNET_assert (dc->th != NULL);
1541     }
1542   return msize;
1543 }
1544
1545
1546 /**
1547  * Reconnect to the FS service and transmit our queries NOW.
1548  *
1549  * @param cls our download context
1550  * @param tc unused
1551  */
1552 static void
1553 do_reconnect (void *cls,
1554               const struct GNUNET_SCHEDULER_TaskContext *tc)
1555 {
1556   struct GNUNET_FS_DownloadContext *dc = cls;
1557   struct GNUNET_CLIENT_Connection *client;
1558   
1559   dc->task = GNUNET_SCHEDULER_NO_TASK;
1560   client = GNUNET_CLIENT_connect ("fs",
1561                                   dc->h->cfg);
1562   if (NULL == client)
1563     {
1564       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1565                   "Connecting to `%s'-service failed, will try again.\n",
1566                   "FS");
1567       try_reconnect (dc);
1568       return;
1569     }
1570   dc->client = client;
1571   if (dc->pending_head != NULL)
1572     {
1573       dc->th = GNUNET_CLIENT_notify_transmit_ready (client,
1574                                                     sizeof (struct SearchMessage),
1575                                                     GNUNET_CONSTANTS_SERVICE_TIMEOUT,
1576                                                     GNUNET_NO,
1577                                                     &transmit_download_request,
1578                                                     dc);
1579       GNUNET_assert (dc->th != NULL);
1580     }
1581   GNUNET_CLIENT_receive (client,
1582                          &receive_results,
1583                          dc,
1584                          GNUNET_TIME_UNIT_FOREVER_REL);
1585 }
1586
1587
1588 /**
1589  * Add entries to the pending list.
1590  *
1591  * @param cls our download context
1592  * @param key unused
1593  * @param entry entry of type "struct DownloadRequest"
1594  * @return GNUNET_OK
1595  */
1596 static int
1597 retry_entry (void *cls,
1598              const GNUNET_HashCode *key,
1599              void *entry)
1600 {
1601   struct GNUNET_FS_DownloadContext *dc = cls;
1602   struct DownloadRequest *dr = entry;
1603
1604   GNUNET_CONTAINER_DLL_insert (dc->pending_head,
1605                                dc->pending_tail,
1606                                dr);
1607   dr->is_pending = GNUNET_YES;
1608   return GNUNET_OK;
1609 }
1610
1611
1612 /**
1613  * We've lost our connection with the FS service.
1614  * Re-establish it and re-transmit all of our
1615  * pending requests.
1616  *
1617  * @param dc download context that is having trouble
1618  */
1619 static void
1620 try_reconnect (struct GNUNET_FS_DownloadContext *dc)
1621 {
1622   
1623   if (NULL != dc->client)
1624     {
1625 #if DEBUG_DOWNLOAD
1626       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1627                   "Moving all requests back to pending list\n");
1628 #endif
1629       if (NULL != dc->th)
1630         {
1631           GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
1632           dc->th = NULL;
1633         }
1634       /* full reset of the pending list */
1635       dc->pending_head = NULL;
1636       dc->pending_tail = NULL;
1637       GNUNET_CONTAINER_multihashmap_iterate (dc->active,
1638                                              &retry_entry,
1639                                              dc);
1640       GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
1641       dc->client = NULL;
1642     }
1643 #if DEBUG_DOWNLOAD
1644   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1645               "Will try to reconnect in 1s\n");
1646 #endif
1647   dc->task
1648     = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
1649                                     &do_reconnect,
1650                                     dc);
1651 }
1652
1653
1654 /**
1655  * We're allowed to ask the FS service for our blocks.  Start the download.
1656  *
1657  * @param cls the 'struct GNUNET_FS_DownloadContext'
1658  * @param client handle to use for communcation with FS (we must destroy it!)
1659  */
1660 static void
1661 activate_fs_download (void *cls,
1662                       struct GNUNET_CLIENT_Connection *client)
1663 {
1664   struct GNUNET_FS_DownloadContext *dc = cls;
1665   struct GNUNET_FS_ProgressInfo pi;
1666
1667 #if DEBUG_DOWNLOAD
1668   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1669               "Download activated\n");
1670 #endif
1671   GNUNET_assert (NULL != client);
1672   GNUNET_assert (dc->client == NULL);
1673   GNUNET_assert (dc->th == NULL);
1674   dc->client = client;
1675   GNUNET_CLIENT_receive (client,
1676                          &receive_results,
1677                          dc,
1678                          GNUNET_TIME_UNIT_FOREVER_REL);
1679   pi.status = GNUNET_FS_STATUS_DOWNLOAD_ACTIVE;
1680   GNUNET_FS_download_make_status_ (&pi, dc);
1681   GNUNET_CONTAINER_multihashmap_iterate (dc->active,
1682                                          &retry_entry,
1683                                          dc);
1684 #if DEBUG_DOWNLOAD
1685   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1686               "Asking for transmission to FS service\n");
1687 #endif
1688   if (dc->pending_head != NULL)
1689     {
1690       dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
1691                                                     sizeof (struct SearchMessage),
1692                                                     GNUNET_CONSTANTS_SERVICE_TIMEOUT,
1693                                                     GNUNET_NO,
1694                                                     &transmit_download_request,
1695                                                     dc);    
1696       GNUNET_assert (dc->th != NULL);
1697     }
1698 }
1699
1700
1701 /**
1702  * We must stop to ask the FS service for our blocks.  Pause the download.
1703  *
1704  * @param cls the 'struct GNUNET_FS_DownloadContext'
1705  */
1706 static void
1707 deactivate_fs_download (void *cls)
1708 {
1709   struct GNUNET_FS_DownloadContext *dc = cls;
1710   struct GNUNET_FS_ProgressInfo pi;
1711
1712 #if DEBUG_DOWNLOAD
1713   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1714               "Download deactivated\n");
1715 #endif  
1716   if (NULL != dc->th)
1717     {
1718       GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
1719       dc->th = NULL;
1720     }
1721   if (NULL != dc->client)
1722     {
1723       GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
1724       dc->client = NULL;
1725     }
1726   pi.status = GNUNET_FS_STATUS_DOWNLOAD_INACTIVE;
1727   GNUNET_FS_download_make_status_ (&pi, dc);
1728 }
1729
1730
1731 /**
1732  * (recursively) Create a download request structure.
1733  *
1734  * @param parent parent of the current entry
1735  * @param depth depth of the current entry, 0 are the DBLOCKs,
1736  *              top level block is 'dc->treedepth - 1'
1737  * @param dr_offset offset in the original file this block maps to
1738  *              (as in, offset of the first byte of the first DBLOCK 
1739  *               in the subtree rooted in the returned download request tree)
1740  * @param file_start_offset desired starting offset for the download
1741  *             in the original file; requesting tree should not contain
1742  *             DBLOCKs prior to the file_start_offset
1743  * @param file_length desired number of bytes the user wanted to access
1744  *        (from file_start_offset).  Resulting tree should not contain
1745  *        DBLOCKs after file_start_offset + file_length.
1746  * @return download request tree for the given range of DBLOCKs at
1747  *         the specified depth
1748  */
1749 static struct DownloadRequest *
1750 create_download_request (struct DownloadRequest *parent,
1751                          unsigned int depth,
1752                          uint64_t dr_offset,
1753                          uint64_t file_start_offset,
1754                          uint64_t desired_length)
1755 {
1756   struct DownloadRequest *dr;
1757   unsigned int i;
1758   unsigned int head_skip;
1759   uint64_t child_block_size;
1760   
1761   dr = GNUNET_malloc (sizeof (struct DownloadRequest));
1762   dr->parent = parent;
1763   dr->depth = depth;
1764   dr->offset = dr_offset;
1765   if (depth > 0)
1766     {
1767       child_block_size = GNUNET_FS_tree_compute_tree_size (depth - 1);
1768       
1769       /* calculate how many blocks at this level are not interesting
1770          from the start (rounded down), either because of the requested
1771          file offset or because this IBlock is further along */
1772       if (dr_offset < file_start_offset)
1773         head_skip = file_start_offset / child_block_size;       
1774       else
1775         head_skip = dr_offset / child_block_size;       
1776
1777       /* calculate index of last block at this level that is interesting (rounded up) */
1778       dr->num_children = file_start_offset + desired_length / child_block_size;
1779       if (dr->num_children * child_block_size < file_start_offset + desired_length)
1780         dr->num_children++; /* round up */
1781
1782       /* now we can get the total number of children for this block */
1783       dr->num_children -= head_skip;
1784       if (dr->num_children > CHK_PER_INODE)
1785         dr->num_children = CHK_PER_INODE; /* cap at max */
1786
1787       /* why else would we have gotten here to begin with? (that'd be a bad logic error) */
1788       GNUNET_assert (dr->num_children > 0); 
1789
1790       dr->children = GNUNET_malloc (dr->num_children * 
1791                                     sizeof (struct DownloadRequest *));
1792       for (i=0;i<dr->num_children;i++)
1793         dr->children[i] = create_download_request (dr,
1794                                                    depth - 1,
1795                                                    dr_offset + i * child_block_size,
1796                                                    file_start_offset,
1797                                                    desired_length);      
1798     }
1799   return dr;
1800 }
1801
1802
1803 /**
1804  * Continuation after a possible attempt to reconstruct
1805  * the current IBlock from the existing file.
1806  *
1807  * @param cls the 'struct ReconstructContext'
1808  * @param tc scheduler context
1809  */
1810 static void
1811 reconstruct_cont (void *cls,
1812                   const struct GNUNET_SCHEDULER_TaskContext *tc)
1813 {
1814   struct GNUNET_FS_DownloadContext *dc = cls;
1815
1816   /* clean up state from tree encoder */
1817   if (dc->te != NULL)
1818     {
1819       GNUNET_FS_tree_encoder_finish (dc->te, NULL, NULL);
1820       dc->te = NULL;
1821     }
1822   if (dc->task != GNUNET_SCHEDULER_NO_TASK)
1823     {
1824       GNUNET_SCHEDULER_cancel (dc->task);
1825       dc->task = GNUNET_SCHEDULER_NO_TASK;
1826     }
1827   if (dc->rfh != NULL)
1828     {
1829       GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (dc->rfh));
1830       dc->rfh = NULL;
1831     }
1832   /* start "normal" download */
1833   schedule_block_download (dc, 
1834                            dc->top_request);
1835 }
1836
1837
1838 /**
1839  * Task requesting the next block from the tree encoder.
1840  *
1841  * @param tc task context
1842  */
1843 static void
1844 get_next_block (void *cls,
1845                 const struct GNUNET_SCHEDULER_TaskContext *tc)
1846 {
1847   struct GNUNET_FS_DownloadContext *dc = cls;
1848
1849   dc->task = GNUNET_SCHEDULER_NO_TASK;
1850   GNUNET_FS_tree_encoder_next (dc->te);
1851 }
1852
1853
1854
1855 /**
1856  * Function called asking for the current (encoded)
1857  * block to be processed.  After processing the
1858  * client should either call "GNUNET_FS_tree_encode_next"
1859  * or (on error) "GNUNET_FS_tree_encode_finish".
1860  *
1861  * This function checks if the content on disk matches
1862  * the expected content based on the URI.
1863  * 
1864  * @param cls closure
1865  * @param chk content hash key for the block
1866  * @param offset offset of the block
1867  * @param depth depth of the block, 0 for DBLOCK
1868  * @param type type of the block (IBLOCK or DBLOCK)
1869  * @param block the (encrypted) block
1870  * @param block_size size of block (in bytes)
1871  */
1872 static void 
1873 reconstruct_cb (void *cls,
1874                 const struct ContentHashKey *chk,
1875                 uint64_t offset,
1876                 unsigned int depth,
1877                 enum GNUNET_BLOCK_Type type,
1878                 const void *block,
1879                 uint16_t block_size)
1880 {
1881   struct GNUNET_FS_DownloadContext *dc = cls;
1882   struct GNUNET_FS_ProgressInfo pi;
1883   struct DownloadRequest *dr;
1884   uint64_t blen;
1885   unsigned int chld;
1886
1887   /* find corresponding request entry */
1888   dr = dc->top_request;
1889   while (dr->depth > depth)
1890     {
1891       blen = GNUNET_FS_tree_compute_tree_size (dr->depth);
1892       chld = (offset - dr->offset) / blen;
1893       GNUNET_assert (chld < dr->num_children);
1894       dr = dr->children[chld];
1895     }
1896   switch (dr->state)
1897     {
1898     case BRS_INIT:
1899       break;
1900     case BRS_RECONSTRUCT_DOWN:
1901       break;
1902     case BRS_RECONSTRUCT_META_UP:
1903       break;
1904     case BRS_RECONSTRUCT_UP:
1905       break;
1906     case BRS_CHK_SET:
1907       if (0 == memcmp (chk,
1908                        &dr->chk,
1909                        sizeof (struct ContentHashKey)))
1910         {
1911           /* block matches, hence tree below matches; 
1912              this request is done! */
1913           dr->state = BRS_DOWNLOAD_UP;
1914           /* calculate how many bytes of payload this block
1915              corresponds to */
1916           blen = GNUNET_FS_tree_compute_tree_size (dr->depth);
1917           /* how many of those bytes are in the requested range? */
1918           blen = GNUNET_MIN (blen,
1919                              dc->length + dc->offset - dr->offset);
1920           /* signal progress */
1921           dc->completed += blen;
1922           pi.status = GNUNET_FS_STATUS_DOWNLOAD_PROGRESS;
1923           pi.value.download.specifics.progress.data = NULL;
1924           pi.value.download.specifics.progress.offset = offset;
1925           pi.value.download.specifics.progress.data_len = 0;
1926           pi.value.download.specifics.progress.depth = 0;
1927           GNUNET_FS_download_make_status_ (&pi, dc);             
1928         }
1929       else
1930         {
1931         }
1932       break; 
1933     case BRS_DOWNLOAD_DOWN:
1934       break; 
1935     case BRS_DOWNLOAD_UP:
1936       break;
1937     case BRS_ERROR:
1938       break;
1939     default:
1940       GNUNET_assert (0);
1941       break;
1942     }
1943   if ( (dr == dc->top_request) &&
1944        (dr->state == BRS_DOWNLOAD_UP) )
1945     {
1946       check_completed (dc);
1947       return;
1948     }
1949   dc->task = GNUNET_SCHEDULER_add_now (&get_next_block,
1950                                        dc);
1951 }
1952
1953
1954 /**
1955  * Function called by the tree encoder to obtain a block of plaintext
1956  * data (for the lowest level of the tree).
1957  *
1958  * @param cls our 'struct ReconstructContext'
1959  * @param offset identifies which block to get
1960  * @param max (maximum) number of bytes to get; returning
1961  *        fewer will also cause errors
1962  * @param buf where to copy the plaintext buffer
1963  * @param emsg location to store an error message (on error)
1964  * @return number of bytes copied to buf, 0 on error
1965  */
1966 static size_t
1967 fh_reader (void *cls,
1968            uint64_t offset,
1969            size_t max, 
1970            void *buf,
1971            char **emsg)
1972 {
1973   struct GNUNET_FS_DownloadContext *dc = cls;
1974   struct GNUNET_DISK_FileHandle *fh = dc->rfh;
1975   ssize_t ret;
1976
1977   *emsg = NULL;
1978   if (offset !=
1979       GNUNET_DISK_file_seek (fh,
1980                              offset,
1981                              GNUNET_DISK_SEEK_SET))
1982     {
1983       *emsg = GNUNET_strdup (strerror (errno));
1984       return 0;
1985     }
1986   ret = GNUNET_DISK_file_read (fh, buf, max);
1987   if (ret < 0)
1988     {
1989       *emsg = GNUNET_strdup (strerror (errno));
1990       return 0;
1991     }
1992   return ret;
1993 }
1994
1995
1996 /**
1997  * Task that creates the initial (top-level) download
1998  * request for the file.
1999  *
2000  * @param cls the 'struct GNUNET_FS_DownloadContext'
2001  * @param tc scheduler context
2002  */
2003 void
2004 GNUNET_FS_download_start_task_ (void *cls,
2005                                 const struct GNUNET_SCHEDULER_TaskContext *tc)
2006 {
2007   struct GNUNET_FS_DownloadContext *dc = cls;  
2008   struct GNUNET_FS_ProgressInfo pi;
2009   struct GNUNET_DISK_FileHandle *fh;
2010
2011 #if DEBUG_DOWNLOAD
2012   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
2013               "Start task running...\n");
2014 #endif
2015   dc->task = GNUNET_SCHEDULER_NO_TASK;
2016   if (dc->length == 0)
2017     {
2018       /* no bytes required! */
2019       if (dc->filename != NULL) 
2020         {
2021           fh = GNUNET_DISK_file_open (dc->filename != NULL 
2022                                       ? dc->filename 
2023                                       : dc->temp_filename, 
2024                                       GNUNET_DISK_OPEN_READWRITE |
2025                                       GNUNET_DISK_OPEN_CREATE |
2026                                       ( (0 == GNUNET_FS_uri_chk_get_file_size (dc->uri)) 
2027                                         ? GNUNET_DISK_OPEN_TRUNCATE : 0),
2028                                       GNUNET_DISK_PERM_USER_READ |
2029                                       GNUNET_DISK_PERM_USER_WRITE |
2030                                       GNUNET_DISK_PERM_GROUP_READ |
2031                                       GNUNET_DISK_PERM_OTHER_READ);
2032           GNUNET_DISK_file_close (fh);
2033         }
2034       GNUNET_FS_download_sync_ (dc);
2035       check_completed (dc);
2036       return;
2037     }
2038   if (dc->emsg != NULL) 
2039     return;
2040   if (dc->top_request == NULL)
2041     {
2042       dc->top_request = create_download_request (NULL, dc->treedepth - 1, 0,
2043                                                  dc->offset, dc->length);
2044       dc->top_request->state = BRS_CHK_SET;
2045       dc->top_request->chk = (dc->uri->type == chk) 
2046         ? dc->uri->data.chk.chk
2047         : dc->uri->data.loc.fi.chk;
2048       /* signal start */
2049       GNUNET_FS_download_sync_ (dc);
2050       pi.status = GNUNET_FS_STATUS_DOWNLOAD_START;
2051       pi.value.download.specifics.start.meta = dc->meta;
2052       GNUNET_FS_download_make_status_ (&pi, dc);
2053     }
2054   GNUNET_FS_download_start_downloading_ (dc);
2055   /* attempt reconstruction from disk */
2056   if (GNUNET_YES == GNUNET_DISK_file_test (dc->filename))    
2057     dc->rfh = GNUNET_DISK_file_open (dc->filename,
2058                                      GNUNET_DISK_OPEN_READ,
2059                                      GNUNET_DISK_PERM_NONE);    
2060   if (dc->top_request->state == BRS_CHK_SET)
2061     {
2062       if (dc->rfh != NULL)
2063         {
2064           /* first, try top-down */
2065 #if DEBUG_DOWNLOAD
2066           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
2067                       "Trying top-down reconstruction for `%s'\n",
2068                       dc->filename);
2069 #endif
2070           try_top_down_reconstruction (dc, dc->top_request);
2071           switch (dc->top_request->state)
2072             {
2073             case BRS_CHK_SET:
2074               break; /* normal */
2075             case BRS_DOWNLOAD_DOWN:
2076               break; /* normal, some blocks already down */
2077             case BRS_DOWNLOAD_UP:
2078               /* already done entirely, party! */
2079               if (dc->rfh != NULL)
2080                 {
2081                   /* avoid hanging on to file handle longer than 
2082                      necessary */
2083                   GNUNET_DISK_file_close (dc->rfh);
2084                   dc->rfh = NULL;
2085                 }
2086               return;      
2087             case BRS_ERROR:
2088               GNUNET_asprintf (&dc->emsg,
2089                                _("Invalid URI"));
2090               GNUNET_FS_download_sync_ (dc);
2091               pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
2092               pi.value.download.specifics.error.message = dc->emsg;
2093               GNUNET_FS_download_make_status_ (&pi, dc);
2094               return;
2095             default:
2096               GNUNET_assert (0);
2097               break;
2098             }
2099         }
2100     }
2101   /* attempt reconstruction from meta data */
2102   if ( (GNUNET_FS_uri_chk_get_file_size (dc->uri) <= MAX_INLINE_SIZE) &&
2103        (NULL != dc->meta) )
2104     {
2105 #if DEBUG_DOWNLOAD
2106       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
2107                   "Trying to find embedded meta data for download of size %llu with %u bytes MD\n",
2108                   (unsigned long long) GNUNET_FS_uri_chk_get_file_size (dc->uri),
2109                   (unsigned int) GNUNET_CONTAINER_meta_data_get_serialized_size (dc->meta));
2110 #endif
2111       GNUNET_CONTAINER_meta_data_iterate (dc->meta,
2112                                           &match_full_data,
2113                                           dc);
2114       if (dc->top_request->state == BRS_DOWNLOAD_UP)
2115         {
2116           if (dc->rfh != NULL)
2117             {
2118               /* avoid hanging on to file handle longer than 
2119                  necessary */
2120               GNUNET_DISK_file_close (dc->rfh);
2121               dc->rfh = NULL;
2122             }
2123           return; /* finished, status update was already done for us */
2124         }
2125     }
2126   if (dc->rfh != NULL)
2127     {
2128       /* finally, try bottom-up */
2129 #if DEBUG_DOWNLOAD
2130       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
2131                   "Trying bottom-up reconstruction of file `%s'\n",
2132                   dc->filename);
2133 #endif
2134       dc->te = GNUNET_FS_tree_encoder_create (dc->h,
2135                                               dc->old_file_size,
2136                                               dc,
2137                                               &fh_reader,
2138                                               &reconstruct_cb,
2139                                               NULL,
2140                                               &reconstruct_cont);     
2141       dc->task = GNUNET_SCHEDULER_add_now (&get_next_block,
2142                                            dc);
2143     }
2144   else
2145     {
2146       /* simple, top-level download */
2147         schedule_block_download (dc, 
2148                                  dc->top_request);
2149     }
2150   if (dc->top_request->state == BRS_DOWNLOAD_UP)
2151     check_completed (dc);
2152 }
2153
2154
2155 /**
2156  * Create SUSPEND event for the given download operation
2157  * and then clean up our state (without stop signal).
2158  *
2159  * @param cls the 'struct GNUNET_FS_DownloadContext' to signal for
2160  */
2161 void
2162 GNUNET_FS_download_signal_suspend_ (void *cls)
2163 {
2164   struct GNUNET_FS_DownloadContext *dc = cls;
2165   struct GNUNET_FS_ProgressInfo pi;
2166
2167   if (dc->top != NULL)
2168     GNUNET_FS_end_top (dc->h, dc->top);
2169   while (NULL != dc->child_head)
2170     GNUNET_FS_download_signal_suspend_ (dc->child_head);  
2171   if (dc->search != NULL)
2172     {
2173       dc->search->download = NULL;
2174       dc->search = NULL;
2175     }
2176   if (dc->job_queue != NULL)
2177     {
2178       GNUNET_FS_dequeue_ (dc->job_queue);
2179       dc->job_queue = NULL;
2180     }
2181   if (dc->parent != NULL)
2182     GNUNET_CONTAINER_DLL_remove (dc->parent->child_head,
2183                                  dc->parent->child_tail,
2184                                  dc);  
2185   if (dc->task != GNUNET_SCHEDULER_NO_TASK)
2186     {
2187       GNUNET_SCHEDULER_cancel (dc->task);
2188       dc->task = GNUNET_SCHEDULER_NO_TASK;
2189     }
2190   pi.status = GNUNET_FS_STATUS_DOWNLOAD_SUSPEND;
2191   GNUNET_FS_download_make_status_ (&pi, dc);
2192   if (dc->te != NULL)
2193     {
2194       GNUNET_FS_tree_encoder_finish (dc->te, NULL, NULL);       
2195       dc->te = NULL;
2196     }
2197   if (dc->rfh != NULL)
2198     {
2199       GNUNET_DISK_file_close (dc->rfh);
2200       dc->rfh = NULL;
2201     }
2202   GNUNET_FS_free_download_request_ (dc->top_request);
2203   if (dc->active != NULL)
2204     {
2205       GNUNET_CONTAINER_multihashmap_destroy (dc->active);
2206       dc->active = NULL;
2207     }
2208   GNUNET_free_non_null (dc->filename);
2209   GNUNET_CONTAINER_meta_data_destroy (dc->meta);
2210   GNUNET_FS_uri_destroy (dc->uri);
2211   GNUNET_free_non_null (dc->temp_filename);
2212   GNUNET_free_non_null (dc->serialization);
2213   GNUNET_free (dc);
2214 }
2215
2216
2217 /**
2218  * Download parts of a file.  Note that this will store
2219  * the blocks at the respective offset in the given file.  Also, the
2220  * download is still using the blocking of the underlying FS
2221  * encoding.  As a result, the download may *write* outside of the
2222  * given boundaries (if offset and length do not match the 32k FS
2223  * block boundaries). <p>
2224  *
2225  * This function should be used to focus a download towards a
2226  * particular portion of the file (optimization), not to strictly
2227  * limit the download to exactly those bytes.
2228  *
2229  * @param h handle to the file sharing subsystem
2230  * @param uri the URI of the file (determines what to download); CHK or LOC URI
2231  * @param meta known metadata for the file (can be NULL)
2232  * @param filename where to store the file, maybe NULL (then no file is
2233  *        created on disk and data must be grabbed from the callbacks)
2234  * @param tempname where to store temporary file data, not used if filename is non-NULL;
2235  *        can be NULL (in which case we will pick a name if needed); the temporary file
2236  *        may already exist, in which case we will try to use the data that is there and
2237  *        if it is not what is desired, will overwrite it
2238  * @param offset at what offset should we start the download (typically 0)
2239  * @param length how many bytes should be downloaded starting at offset
2240  * @param anonymity anonymity level to use for the download
2241  * @param options various options
2242  * @param cctx initial value for the client context for this download
2243  * @param parent parent download to associate this download with (use NULL
2244  *        for top-level downloads; useful for manually-triggered recursive downloads)
2245  * @return context that can be used to control this download
2246  */
2247 struct GNUNET_FS_DownloadContext *
2248 GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
2249                           const struct GNUNET_FS_Uri *uri,
2250                           const struct GNUNET_CONTAINER_MetaData *meta,
2251                           const char *filename,
2252                           const char *tempname,
2253                           uint64_t offset,
2254                           uint64_t length,
2255                           uint32_t anonymity,
2256                           enum GNUNET_FS_DownloadOptions options,
2257                           void *cctx,
2258                           struct GNUNET_FS_DownloadContext *parent)
2259 {
2260   struct GNUNET_FS_DownloadContext *dc;
2261
2262   GNUNET_assert (GNUNET_FS_uri_test_chk (uri) ||
2263                  GNUNET_FS_uri_test_loc (uri) );
2264                  
2265   if ( (offset + length < offset) ||
2266        (offset + length > GNUNET_FS_uri_chk_get_file_size (uri)) )
2267     {      
2268       GNUNET_break (0);
2269       return NULL;
2270     }
2271 #if DEBUG_DOWNLOAD
2272   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
2273               "Starting download `%s' of %llu bytes\n",
2274               filename,
2275               (unsigned long long) length);
2276 #endif
2277   dc = GNUNET_malloc (sizeof(struct GNUNET_FS_DownloadContext));
2278   dc->h = h;
2279   dc->parent = parent;
2280   if (parent != NULL)
2281     {
2282       GNUNET_CONTAINER_DLL_insert (parent->child_head,
2283                                    parent->child_tail,
2284                                    dc);
2285     }
2286   dc->uri = GNUNET_FS_uri_dup (uri);
2287   dc->meta = GNUNET_CONTAINER_meta_data_duplicate (meta);
2288   dc->client_info = cctx;
2289   dc->start_time = GNUNET_TIME_absolute_get ();
2290   if (NULL != filename)
2291     {
2292       dc->filename = GNUNET_strdup (filename);
2293       if (GNUNET_YES == GNUNET_DISK_file_test (filename))
2294         GNUNET_DISK_file_size (filename,
2295                                &dc->old_file_size,
2296                                GNUNET_YES);
2297     }
2298   if (GNUNET_FS_uri_test_loc (dc->uri))
2299     GNUNET_assert (GNUNET_OK ==
2300                    GNUNET_FS_uri_loc_get_peer_identity (dc->uri,
2301                                                         &dc->target));
2302   dc->offset = offset;
2303   dc->length = length;
2304   dc->anonymity = anonymity;
2305   dc->options = options;
2306   dc->active = GNUNET_CONTAINER_multihashmap_create (1 + 2 * (length / DBLOCK_SIZE));
2307   dc->treedepth = GNUNET_FS_compute_depth (GNUNET_FS_uri_chk_get_file_size(dc->uri));
2308   if ( (filename == NULL) &&
2309        (is_recursive_download (dc) ) )
2310     {
2311       if (tempname != NULL)
2312         dc->temp_filename = GNUNET_strdup (tempname);
2313       else
2314         dc->temp_filename = GNUNET_DISK_mktemp ("gnunet-directory-download-tmp");    
2315     }
2316
2317 #if DEBUG_DOWNLOAD
2318   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
2319               "Download tree has depth %u\n",
2320               dc->treedepth);
2321 #endif
2322   if (parent == NULL)
2323     {
2324       dc->top = GNUNET_FS_make_top (dc->h,
2325                                     &GNUNET_FS_download_signal_suspend_,
2326                                     dc);
2327     }
2328   dc->task = GNUNET_SCHEDULER_add_now (&GNUNET_FS_download_start_task_, 
2329                                        dc);
2330   return dc;
2331 }
2332
2333
2334 /**
2335  * Download parts of a file based on a search result.  The download
2336  * will be associated with the search result (and the association
2337  * will be preserved when serializing/deserializing the state).
2338  * If the search is stopped, the download will not be aborted but
2339  * be 'promoted' to a stand-alone download.
2340  *
2341  * As with the other download function, this will store
2342  * the blocks at the respective offset in the given file.  Also, the
2343  * download is still using the blocking of the underlying FS
2344  * encoding.  As a result, the download may *write* outside of the
2345  * given boundaries (if offset and length do not match the 32k FS
2346  * block boundaries). <p>
2347  *
2348  * The given range can be used to focus a download towards a
2349  * particular portion of the file (optimization), not to strictly
2350  * limit the download to exactly those bytes.
2351  *
2352  * @param h handle to the file sharing subsystem
2353  * @param sr the search result to use for the download (determines uri and
2354  *        meta data and associations)
2355  * @param filename where to store the file, maybe NULL (then no file is
2356  *        created on disk and data must be grabbed from the callbacks)
2357  * @param tempname where to store temporary file data, not used if filename is non-NULL;
2358  *        can be NULL (in which case we will pick a name if needed); the temporary file
2359  *        may already exist, in which case we will try to use the data that is there and
2360  *        if it is not what is desired, will overwrite it
2361  * @param offset at what offset should we start the download (typically 0)
2362  * @param length how many bytes should be downloaded starting at offset
2363  * @param anonymity anonymity level to use for the download
2364  * @param options various download options
2365  * @param cctx initial value for the client context for this download
2366  * @return context that can be used to control this download
2367  */
2368 struct GNUNET_FS_DownloadContext *
2369 GNUNET_FS_download_start_from_search (struct GNUNET_FS_Handle *h,
2370                                       struct GNUNET_FS_SearchResult *sr,
2371                                       const char *filename,
2372                                       const char *tempname,
2373                                       uint64_t offset,
2374                                       uint64_t length,
2375                                       uint32_t anonymity,
2376                                       enum GNUNET_FS_DownloadOptions options,
2377                                       void *cctx)
2378 {
2379   struct GNUNET_FS_DownloadContext *dc;
2380
2381   if ( (sr == NULL) ||
2382        (sr->download != NULL) )
2383     {
2384       GNUNET_break (0);
2385       return NULL;
2386     }
2387   GNUNET_assert (GNUNET_FS_uri_test_chk (sr->uri) ||
2388                  GNUNET_FS_uri_test_loc (sr->uri) );             
2389   if ( (offset + length < offset) ||
2390        (offset + length > sr->uri->data.chk.file_length) )
2391     {      
2392       GNUNET_break (0);
2393       return NULL;
2394     }
2395 #if DEBUG_DOWNLOAD
2396   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
2397               "Starting download `%s' of %llu bytes\n",
2398               filename,
2399               (unsigned long long) length);
2400 #endif
2401   dc = GNUNET_malloc (sizeof(struct GNUNET_FS_DownloadContext));
2402   dc->h = h;
2403   dc->search = sr;
2404   sr->download = dc;
2405   if (sr->probe_ctx != NULL)
2406     {
2407       GNUNET_FS_download_stop (sr->probe_ctx, GNUNET_YES);
2408       sr->probe_ctx = NULL;      
2409     }
2410   dc->uri = GNUNET_FS_uri_dup (sr->uri);
2411   dc->meta = GNUNET_CONTAINER_meta_data_duplicate (sr->meta);
2412   dc->client_info = cctx;
2413   dc->start_time = GNUNET_TIME_absolute_get ();
2414   if (NULL != filename)
2415     {
2416       dc->filename = GNUNET_strdup (filename);
2417       if (GNUNET_YES == GNUNET_DISK_file_test (filename))
2418         GNUNET_DISK_file_size (filename,
2419                                &dc->old_file_size,
2420                                GNUNET_YES);
2421     }
2422   if (GNUNET_FS_uri_test_loc (dc->uri))
2423     GNUNET_assert (GNUNET_OK ==
2424                    GNUNET_FS_uri_loc_get_peer_identity (dc->uri,
2425                                                         &dc->target));
2426   dc->offset = offset;
2427   dc->length = length;
2428   dc->anonymity = anonymity;
2429   dc->options = options;
2430   dc->active = GNUNET_CONTAINER_multihashmap_create (1 + 2 * (length / DBLOCK_SIZE));
2431   dc->treedepth = GNUNET_FS_compute_depth (GNUNET_ntohll(dc->uri->data.chk.file_length));
2432   if ( (filename == NULL) &&
2433        (is_recursive_download (dc) ) )
2434     {
2435       if (tempname != NULL)
2436         dc->temp_filename = GNUNET_strdup (tempname);
2437       else
2438         dc->temp_filename = GNUNET_DISK_mktemp ("gnunet-directory-download-tmp");    
2439     }
2440
2441 #if DEBUG_DOWNLOAD
2442   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
2443               "Download tree has depth %u\n",
2444               dc->treedepth);
2445 #endif
2446   dc->task = GNUNET_SCHEDULER_add_now (&GNUNET_FS_download_start_task_, 
2447                                        dc);
2448   return dc;  
2449 }
2450
2451
2452 /**
2453  * Start the downloading process (by entering the queue).
2454  *
2455  * @param dc our download context
2456  */
2457 void
2458 GNUNET_FS_download_start_downloading_ (struct GNUNET_FS_DownloadContext *dc)
2459 {
2460   if (dc->completed == dc->length)
2461     return;
2462   GNUNET_assert (dc->job_queue == NULL);
2463   dc->job_queue = GNUNET_FS_queue_ (dc->h, 
2464                                     &activate_fs_download,
2465                                     &deactivate_fs_download,
2466                                     dc,
2467                                     (dc->length + DBLOCK_SIZE-1) / DBLOCK_SIZE);
2468 }
2469
2470
2471 /**
2472  * Stop a download (aborts if download is incomplete).
2473  *
2474  * @param dc handle for the download
2475  * @param do_delete delete files of incomplete downloads
2476  */
2477 void
2478 GNUNET_FS_download_stop (struct GNUNET_FS_DownloadContext *dc,
2479                          int do_delete)
2480 {
2481   struct GNUNET_FS_ProgressInfo pi;
2482   int have_children;
2483
2484   if (dc->top != NULL)
2485     GNUNET_FS_end_top (dc->h, dc->top);
2486
2487
2488   if (dc->task != GNUNET_SCHEDULER_NO_TASK)
2489     {
2490       GNUNET_SCHEDULER_cancel (dc->task);
2491       dc->task = GNUNET_SCHEDULER_NO_TASK;
2492     }
2493   if (dc->search != NULL)
2494     {
2495       dc->search->download = NULL;
2496       dc->search = NULL;
2497     }
2498   if (dc->job_queue != NULL)
2499     {
2500       GNUNET_FS_dequeue_ (dc->job_queue);
2501       dc->job_queue = NULL;
2502     }
2503   if (dc->te != NULL)
2504     {
2505       GNUNET_FS_tree_encoder_finish (dc->te, NULL, NULL);
2506       dc->te = NULL;
2507     }
2508   have_children = (NULL != dc->child_head) ? GNUNET_YES : GNUNET_NO;
2509   while (NULL != dc->child_head)
2510     GNUNET_FS_download_stop (dc->child_head, 
2511                              do_delete);
2512   if (dc->parent != NULL)
2513     GNUNET_CONTAINER_DLL_remove (dc->parent->child_head,
2514                                  dc->parent->child_tail,
2515                                  dc);  
2516   if (dc->serialization != NULL)
2517     GNUNET_FS_remove_sync_file_ (dc->h,
2518                                  ( (dc->parent != NULL)  || (dc->search != NULL) )
2519                                  ? GNUNET_FS_SYNC_PATH_CHILD_DOWNLOAD 
2520                                  : GNUNET_FS_SYNC_PATH_MASTER_DOWNLOAD , 
2521                                  dc->serialization);
2522   if ( (GNUNET_YES == have_children) &&
2523        (dc->parent == NULL) )
2524     GNUNET_FS_remove_sync_dir_ (dc->h, 
2525                                 (dc->search != NULL) 
2526                                 ? GNUNET_FS_SYNC_PATH_CHILD_DOWNLOAD 
2527                                 : GNUNET_FS_SYNC_PATH_MASTER_DOWNLOAD,
2528                                 dc->serialization);  
2529   pi.status = GNUNET_FS_STATUS_DOWNLOAD_STOPPED;
2530   GNUNET_FS_download_make_status_ (&pi, dc);
2531   GNUNET_FS_free_download_request_ (dc->top_request);
2532   dc->top_request = NULL;
2533   if (dc->active != NULL)
2534     {
2535       GNUNET_CONTAINER_multihashmap_destroy (dc->active);
2536       dc->active = NULL;
2537     }
2538   if (dc->filename != NULL)
2539     {
2540       if ( (dc->completed != dc->length) &&
2541            (GNUNET_YES == do_delete) )
2542         {
2543           if (0 != UNLINK (dc->filename))
2544             GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
2545                                       "unlink",
2546                                       dc->filename);
2547         }
2548       GNUNET_free (dc->filename);
2549     }
2550   GNUNET_CONTAINER_meta_data_destroy (dc->meta);
2551   GNUNET_FS_uri_destroy (dc->uri);
2552   if (NULL != dc->temp_filename)
2553     {
2554       if (0 != UNLINK (dc->temp_filename))
2555         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
2556                                   "unlink",
2557                                   dc->temp_filename);
2558       GNUNET_free (dc->temp_filename);
2559     }
2560   GNUNET_free_non_null (dc->serialization);
2561   GNUNET_free (dc);
2562 }
2563
2564 /* end of fs_download.c */