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