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