bounded parallelism
[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 2, 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  * - location URI suppport (can wait, easy)
27  * - persistence (can wait)
28  * - check if iblocks can be computed from existing blocks (can wait, hard)
29  */
30 #include "platform.h"
31 #include "gnunet_constants.h"
32 #include "gnunet_fs_service.h"
33 #include "fs.h"
34 #include "fs_tree.h"
35
36 #define DEBUG_DOWNLOAD GNUNET_NO
37
38 /**
39  * Determine if the given download (options and meta data) should cause
40  * use to try to do a recursive download.
41  */
42 static int
43 is_recursive_download (struct GNUNET_FS_DownloadContext *dc)
44 {
45   return  (0 != (dc->options & GNUNET_FS_DOWNLOAD_OPTION_RECURSIVE)) &&
46     ( (GNUNET_YES == GNUNET_FS_meta_data_test_for_directory (dc->meta)) ||
47       ( (dc->meta == NULL) &&
48         ( (NULL == dc->filename) ||            
49           ( (strlen (dc->filename) >= strlen (GNUNET_FS_DIRECTORY_EXT)) &&
50             (NULL !=
51              strstr (dc->filename + strlen(dc->filename) - strlen(GNUNET_FS_DIRECTORY_EXT),
52                      GNUNET_FS_DIRECTORY_EXT)) ) ) ) );              
53 }
54
55
56 /**
57  * We're storing the IBLOCKS after the DBLOCKS on disk (so that we
58  * only have to truncate the file once we're done).
59  *
60  * Given the offset of a block (with respect to the DBLOCKS) and its
61  * depth, return the offset where we would store this block in the
62  * file.
63  * 
64  * @param fsize overall file size
65  * @param off offset of the block in the file
66  * @param depth depth of the block in the tree
67  * @param treedepth maximum depth of the tree
68  * @return off for DBLOCKS (depth == treedepth),
69  *         otherwise an offset past the end
70  *         of the file that does not overlap
71  *         with the range for any other block
72  */
73 static uint64_t
74 compute_disk_offset (uint64_t fsize,
75                      uint64_t off,
76                      unsigned int depth,
77                      unsigned int treedepth)
78 {
79   unsigned int i;
80   uint64_t lsize; /* what is the size of all IBlocks for depth "i"? */
81   uint64_t loff; /* where do IBlocks for depth "i" start? */
82   unsigned int ioff; /* which IBlock corresponds to "off" at depth "i"? */
83   
84   if (depth == treedepth)
85     return off;
86   /* first IBlocks start at the end of file, rounded up
87      to full DBLOCK_SIZE */
88   loff = ((fsize + DBLOCK_SIZE - 1) / DBLOCK_SIZE) * DBLOCK_SIZE;
89   lsize = ( (fsize + DBLOCK_SIZE-1) / DBLOCK_SIZE) * sizeof (struct ContentHashKey);
90   GNUNET_assert (0 == (off % DBLOCK_SIZE));
91   ioff = (off / DBLOCK_SIZE);
92   for (i=treedepth-1;i>depth;i--)
93     {
94       loff += lsize;
95       lsize = (lsize + CHK_PER_INODE - 1) / CHK_PER_INODE;
96       GNUNET_assert (lsize > 0);
97       GNUNET_assert (0 == (ioff % CHK_PER_INODE));
98       ioff /= CHK_PER_INODE;
99     }
100   return loff + ioff * sizeof (struct ContentHashKey);
101 }
102
103
104 /**
105  * Given a file of the specified treedepth and a block at the given
106  * offset and depth, calculate the offset for the CHK at the given
107  * index.
108  *
109  * @param offset the offset of the first
110  *        DBLOCK in the subtree of the 
111  *        identified IBLOCK
112  * @param depth the depth of the IBLOCK in the tree
113  * @param treedepth overall depth of the tree
114  * @param k which CHK in the IBLOCK are we 
115  *        talking about
116  * @return offset if k=0, otherwise an appropriately
117  *         larger value (i.e., if depth = treedepth-1,
118  *         the returned value should be offset+DBLOCK_SIZE)
119  */
120 static uint64_t
121 compute_dblock_offset (uint64_t offset,
122                        unsigned int depth,
123                        unsigned int treedepth,
124                        unsigned int k)
125 {
126   unsigned int i;
127   uint64_t lsize; /* what is the size of the sum of all DBlocks 
128                      that a CHK at depth i corresponds to? */
129
130   if (depth == treedepth)
131     return offset;
132   lsize = DBLOCK_SIZE;
133   for (i=treedepth-1;i>depth;i--)
134     lsize *= CHK_PER_INODE;
135   return offset + k * lsize;
136 }
137
138
139 /**
140  * Fill in all of the generic fields for 
141  * a download event.
142  *
143  * @param pi structure to fill in
144  * @param dc overall download context
145  */
146 static void
147 make_download_status (struct GNUNET_FS_ProgressInfo *pi,
148                       struct GNUNET_FS_DownloadContext *dc)
149 {
150   pi->value.download.dc = dc;
151   pi->value.download.cctx
152     = dc->client_info;
153   pi->value.download.pctx
154     = (dc->parent == NULL) ? NULL : dc->parent->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 }
172
173 /**
174  * We're ready to transmit a search request to the
175  * file-sharing service.  Do it.  If there is 
176  * more than one request pending, try to send 
177  * multiple or request another transmission.
178  *
179  * @param cls closure
180  * @param size number of bytes available in buf
181  * @param buf where the callee should write the message
182  * @return number of bytes written to buf
183  */
184 static size_t
185 transmit_download_request (void *cls,
186                            size_t size, 
187                            void *buf);
188
189
190 /**
191  * Closure for iterator processing results.
192  */
193 struct ProcessResultClosure
194 {
195   
196   /**
197    * Hash of data.
198    */
199   GNUNET_HashCode query;
200
201   /**
202    * Data found in P2P network.
203    */ 
204   const void *data;
205
206   /**
207    * Our download context.
208    */
209   struct GNUNET_FS_DownloadContext *dc;
210                 
211   /**
212    * Number of bytes in data.
213    */
214   size_t size;
215
216   /**
217    * Type of data.
218    */
219   enum GNUNET_BLOCK_Type type;
220
221   /**
222    * Flag to indicate if this block should be stored on disk.
223    */
224   int do_store;
225   
226 };
227
228
229 /**
230  * Iterator over entries in the pending requests in the 'active' map for the
231  * reply that we just got.
232  *
233  * @param cls closure (our 'struct ProcessResultClosure')
234  * @param key query for the given value / request
235  * @param value value in the hash map (a 'struct DownloadRequest')
236  * @return GNUNET_YES (we should continue to iterate); unless serious error
237  */
238 static int
239 process_result_with_request (void *cls,
240                              const GNUNET_HashCode * key,
241                              void *value);
242
243
244
245 /**
246  * Schedule the download of the specified block in the tree.
247  *
248  * @param dc overall download this block belongs to
249  * @param chk content-hash-key of the block
250  * @param offset offset of the block in the file
251  *         (for IBlocks, the offset is the lowest
252  *          offset of any DBlock in the subtree under
253  *          the IBlock)
254  * @param depth depth of the block, 0 is the root of the tree
255  */
256 static void
257 schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
258                          const struct ContentHashKey *chk,
259                          uint64_t offset,
260                          unsigned int depth)
261 {
262   struct DownloadRequest *sm;
263   uint64_t total;
264   uint64_t off;
265   size_t len;
266   char block[DBLOCK_SIZE];
267   GNUNET_HashCode key;
268   struct ProcessResultClosure prc;
269
270 #if DEBUG_DOWNLOAD
271   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
272               "Scheduling download at offset %llu and depth %u for `%s'\n",
273               (unsigned long long) offset,
274               depth,
275               GNUNET_h2s (&chk->query));
276 #endif
277   total = GNUNET_ntohll (dc->uri->data.chk.file_length);
278   off = compute_disk_offset (total,
279                              offset,
280                              depth,
281                              dc->treedepth);
282   len = GNUNET_FS_tree_calculate_block_size (total,
283                                              dc->treedepth,
284                                              offset,
285                                              depth);
286   sm = GNUNET_malloc (sizeof (struct DownloadRequest));
287   sm->chk = *chk;
288   sm->offset = offset;
289   sm->depth = depth;
290   sm->is_pending = GNUNET_YES;
291   sm->next = dc->pending;
292   dc->pending = sm;
293   GNUNET_CONTAINER_multihashmap_put (dc->active,
294                                      &chk->query,
295                                      sm,
296                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
297
298   if ( (dc->old_file_size > off) &&
299        (dc->handle != NULL) &&
300        (off  == 
301         GNUNET_DISK_file_seek (dc->handle,
302                                off,
303                                GNUNET_DISK_SEEK_SET) ) &&
304        (len == 
305         GNUNET_DISK_file_read (dc->handle,
306                                block,
307                                len)) )
308     {
309       if (0 == memcmp (&key,
310                        &chk->key,
311                        sizeof (GNUNET_HashCode)))
312         {
313           char enc[len];
314           struct GNUNET_CRYPTO_AesSessionKey sk;
315           struct GNUNET_CRYPTO_AesInitializationVector iv;
316           GNUNET_HashCode query;
317
318           GNUNET_CRYPTO_hash_to_aes_key (&key, &sk, &iv);
319           GNUNET_CRYPTO_aes_encrypt (block, len,
320                                      &sk,
321                                      &iv,
322                                      enc);
323           GNUNET_CRYPTO_hash (enc, len, &query);
324           if (0 == memcmp (&query,
325                            &chk->query,
326                            sizeof (GNUNET_HashCode)))
327             {
328               /* already got it! */
329               prc.dc = dc;
330               prc.data = enc;
331               prc.size = len;
332               prc.type = (dc->treedepth == depth) 
333                 ? GNUNET_BLOCK_TYPE_DBLOCK 
334                 : GNUNET_BLOCK_TYPE_IBLOCK;
335               prc.query = chk->query;
336               prc.do_store = GNUNET_NO; /* useless */
337               process_result_with_request (&prc,
338                                            &key,
339                                            sm);
340             }
341           else
342             {
343               GNUNET_break_op (0);
344             }
345           return;
346         }
347     }
348   if (depth < dc->treedepth)
349     {
350       // FIXME: try if we could
351       // reconstitute this IBLOCK
352       // from the existing blocks on disk (can wait)
353       // (read block(s), encode, compare with
354       // query; if matches, simply return)
355     }
356
357   if ( (dc->th == NULL) &&
358        (dc->client != NULL) )
359     dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
360                                                   sizeof (struct SearchMessage),
361                                                   GNUNET_CONSTANTS_SERVICE_TIMEOUT,
362                                                   GNUNET_NO,
363                                                   &transmit_download_request,
364                                                   dc);
365 }
366
367
368
369 /**
370  * Suggest a filename based on given metadata.
371  * 
372  * @param md given meta data
373  * @return NULL if meta data is useless for suggesting a filename
374  */
375 char *
376 GNUNET_FS_meta_data_suggest_filename (const struct GNUNET_CONTAINER_MetaData *md)
377 {
378   static const char *mimeMap[][2] = {
379     {"application/bz2", ".bz2"},
380     {"application/gnunet-directory", ".gnd"},
381     {"application/java", ".class"},
382     {"application/msword", ".doc"},
383     {"application/ogg", ".ogg"},
384     {"application/pdf", ".pdf"},
385     {"application/pgp-keys", ".key"},
386     {"application/pgp-signature", ".pgp"},
387     {"application/postscript", ".ps"},
388     {"application/rar", ".rar"},
389     {"application/rtf", ".rtf"},
390     {"application/xml", ".xml"},
391     {"application/x-debian-package", ".deb"},
392     {"application/x-dvi", ".dvi"},
393     {"applixation/x-flac", ".flac"},
394     {"applixation/x-gzip", ".gz"},
395     {"application/x-java-archive", ".jar"},
396     {"application/x-java-vm", ".class"},
397     {"application/x-python-code", ".pyc"},
398     {"application/x-redhat-package-manager", ".rpm"},
399     {"application/x-rpm", ".rpm"},
400     {"application/x-tar", ".tar"},
401     {"application/x-tex-pk", ".pk"},
402     {"application/x-texinfo", ".texinfo"},
403     {"application/x-xcf", ".xcf"},
404     {"application/x-xfig", ".xfig"},
405     {"application/zip", ".zip"},
406     
407     {"audio/midi", ".midi"},
408     {"audio/mpeg", ".mp3"},
409     {"audio/real", ".rm"},
410     {"audio/x-wav", ".wav"},
411     
412     {"image/gif", ".gif"},
413     {"image/jpeg", ".jpg"},
414     {"image/pcx", ".pcx"},
415     {"image/png", ".png"},
416     {"image/tiff", ".tiff"},
417     {"image/x-ms-bmp", ".bmp"},
418     {"image/x-xpixmap", ".xpm"},
419     
420     {"text/css", ".css"},
421     {"text/html", ".html"},
422     {"text/plain", ".txt"},
423     {"text/rtf", ".rtf"},
424     {"text/x-c++hdr", ".h++"},
425     {"text/x-c++src", ".c++"},
426     {"text/x-chdr", ".h"},
427     {"text/x-csrc", ".c"},
428     {"text/x-java", ".java"},
429     {"text/x-moc", ".moc"},
430     {"text/x-pascal", ".pas"},
431     {"text/x-perl", ".pl"},
432     {"text/x-python", ".py"},
433     {"text/x-tex", ".tex"},
434     
435     {"video/avi", ".avi"},
436     {"video/mpeg", ".mpeg"},
437     {"video/quicktime", ".qt"},
438     {"video/real", ".rm"},
439     {"video/x-msvideo", ".avi"},
440     {NULL, NULL},
441   };
442   char *ret;
443   unsigned int i;
444   char *mime;
445   char *base;
446   const char *ext;
447
448   ret = GNUNET_CONTAINER_meta_data_get_by_type (md,
449                                                 EXTRACTOR_METATYPE_FILENAME);
450   if (ret != NULL)
451     return ret;  
452   ext = NULL;
453   mime = GNUNET_CONTAINER_meta_data_get_by_type (md,
454                                                  EXTRACTOR_METATYPE_MIMETYPE);
455   if (mime != NULL)
456     {
457       i = 0;
458       while ( (mimeMap[i][0] != NULL) && 
459               (0 != strcmp (mime, mimeMap[i][0])))
460         i++;
461       if (mimeMap[i][1] == NULL)
462         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG | 
463                     GNUNET_ERROR_TYPE_BULK,
464                     _("Did not find mime type `%s' in extension list.\n"),
465                     mime);
466       else
467         ext = mimeMap[i][1];
468       GNUNET_free (mime);
469     }
470   base = GNUNET_CONTAINER_meta_data_get_first_by_types (md,
471                                                         EXTRACTOR_METATYPE_TITLE,
472                                                         EXTRACTOR_METATYPE_BOOK_TITLE,
473                                                         EXTRACTOR_METATYPE_ORIGINAL_TITLE,
474                                                         EXTRACTOR_METATYPE_PACKAGE_NAME,
475                                                         EXTRACTOR_METATYPE_URL,
476                                                         EXTRACTOR_METATYPE_URI, 
477                                                         EXTRACTOR_METATYPE_DESCRIPTION,
478                                                         EXTRACTOR_METATYPE_ISRC,
479                                                         EXTRACTOR_METATYPE_JOURNAL_NAME,
480                                                         EXTRACTOR_METATYPE_AUTHOR_NAME,
481                                                         EXTRACTOR_METATYPE_SUBJECT,
482                                                         EXTRACTOR_METATYPE_ALBUM,
483                                                         EXTRACTOR_METATYPE_ARTIST,
484                                                         EXTRACTOR_METATYPE_KEYWORDS,
485                                                         EXTRACTOR_METATYPE_COMMENT,
486                                                         EXTRACTOR_METATYPE_UNKNOWN,
487                                                         -1);
488   if ( (base == NULL) &&
489        (ext == NULL) )
490     return NULL;
491   if (base == NULL)
492     return GNUNET_strdup (ext);
493   if (ext == NULL)
494     return base;
495   GNUNET_asprintf (&ret,
496                    "%s%s",
497                    base,
498                    ext);
499   GNUNET_free (base);
500   return ret;
501 }
502
503
504 /**
505  * We've lost our connection with the FS service.
506  * Re-establish it and re-transmit all of our
507  * pending requests.
508  *
509  * @param dc download context that is having trouble
510  */
511 static void
512 try_reconnect (struct GNUNET_FS_DownloadContext *dc);
513
514
515 /**
516  * We found an entry in a directory.  Check if the respective child
517  * already exists and if not create the respective child download.
518  *
519  * @param cls the parent download
520  * @param filename name of the file in the directory
521  * @param uri URI of the file (CHK or LOC)
522  * @param meta meta data of the file
523  * @param length number of bytes in data
524  * @param data contents of the file (or NULL if they were not inlined)
525  */
526 static void 
527 trigger_recursive_download (void *cls,
528                             const char *filename,
529                             const struct GNUNET_FS_Uri *uri,
530                             const struct GNUNET_CONTAINER_MetaData *meta,
531                             size_t length,
532                             const void *data);
533
534
535 /**
536  * We're done downloading a directory.  Open the file and
537  * trigger all of the (remaining) child downloads.
538  *
539  * @param dc context of download that just completed
540  */
541 static void
542 full_recursive_download (struct GNUNET_FS_DownloadContext *dc)
543 {
544   size_t size;
545   uint64_t size64;
546   void *data;
547   struct GNUNET_DISK_FileHandle *h;
548   struct GNUNET_DISK_MapHandle *m;
549   
550   size64 = GNUNET_FS_uri_chk_get_file_size (dc->uri);
551   size = (size_t) size64;
552   if (size64 != (uint64_t) size)
553     {
554       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
555                   _("Recursive downloads of directories larger than 4 GB are not supported on 32-bit systems\n"));
556       return;
557     }
558   if (dc->filename != NULL)
559     {
560       h = GNUNET_DISK_file_open (dc->filename,
561                                  GNUNET_DISK_OPEN_READ,
562                                  GNUNET_DISK_PERM_NONE);
563     }
564   else
565     {
566       GNUNET_assert (dc->temp_filename != NULL);
567       h = GNUNET_DISK_file_open (dc->temp_filename,
568                                  GNUNET_DISK_OPEN_READ,
569                                  GNUNET_DISK_PERM_NONE);
570     }
571   if (h == NULL)
572     return; /* oops */
573   data = GNUNET_DISK_file_map (h, &m, GNUNET_DISK_MAP_TYPE_READ, size);
574   if (data == NULL)
575     {
576       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
577                   _("Directory too large for system address space\n"));
578     }
579   else
580     {
581       GNUNET_FS_directory_list_contents (size,
582                                          data,
583                                          0,
584                                          &trigger_recursive_download,
585                                          dc);         
586       GNUNET_DISK_file_unmap (m);
587     }
588   GNUNET_DISK_file_close (h);
589   if (dc->filename == NULL)
590     {
591       if (0 != UNLINK (dc->temp_filename))
592         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
593                                   "unlink",
594                                   dc->temp_filename);
595       GNUNET_free (dc->temp_filename);
596       dc->temp_filename = NULL;
597     }
598 }
599
600
601 /**
602  * Check if all child-downloads have completed and
603  * if so, signal completion (and possibly recurse to
604  * parent).
605  */
606 static void
607 check_completed (struct GNUNET_FS_DownloadContext *dc)
608 {
609   struct GNUNET_FS_ProgressInfo pi;
610   struct GNUNET_FS_DownloadContext *pos;
611
612   pos = dc->child_head;
613   while (pos != NULL)
614     {
615       if ( (pos->emsg == NULL) &&
616            (pos->completed < pos->length) )
617         return; /* not done yet */
618       if ( (pos->child_head != NULL) &&
619            (pos->has_finished != GNUNET_YES) )
620         return; /* not transitively done yet */
621       pos = pos->next;
622     }
623   dc->has_finished = GNUNET_YES;
624   /* signal completion */
625   pi.status = GNUNET_FS_STATUS_DOWNLOAD_COMPLETED;
626   make_download_status (&pi, dc);
627   dc->client_info = dc->h->upcb (dc->h->upcb_cls,
628                                  &pi);
629   if (dc->parent != NULL)
630     check_completed (dc->parent);  
631 }
632
633
634 /**
635  * We found an entry in a directory.  Check if the respective child
636  * already exists and if not create the respective child download.
637  *
638  * @param cls the parent download
639  * @param filename name of the file in the directory
640  * @param uri URI of the file (CHK or LOC)
641  * @param meta meta data of the file
642  * @param length number of bytes in data
643  * @param data contents of the file (or NULL if they were not inlined)
644  */
645 static void 
646 trigger_recursive_download (void *cls,
647                             const char *filename,
648                             const struct GNUNET_FS_Uri *uri,
649                             const struct GNUNET_CONTAINER_MetaData *meta,
650                             size_t length,
651                             const void *data)
652 {
653   struct GNUNET_FS_DownloadContext *dc = cls;  
654   struct GNUNET_FS_DownloadContext *cpos;
655   struct GNUNET_DISK_FileHandle *fh;
656   char *temp_name;
657   const char *real_name;
658   char *fn;
659   char *us;
660   char *ext;
661   char *dn;
662   char *full_name;
663
664   if (NULL == uri)
665     return; /* entry for the directory itself */
666   cpos = dc->child_head;
667   while (cpos != NULL)
668     {
669       if ( (GNUNET_FS_uri_test_equal (uri,
670                                       cpos->uri)) ||
671            ( (filename != NULL) &&
672              (0 == strcmp (cpos->filename,
673                            filename)) ) )
674         break;  
675       cpos = cpos->next;
676     }
677   if (cpos != NULL)
678     return; /* already exists */
679   fn = NULL;
680   if (NULL == filename)
681     {
682       fn = GNUNET_FS_meta_data_suggest_filename (meta);      
683       if (fn == NULL)
684         {
685           us = GNUNET_FS_uri_to_string (uri);
686           fn = GNUNET_strdup (&us [strlen (GNUNET_FS_URI_PREFIX 
687                                            GNUNET_FS_URI_CHK_INFIX)]);
688           GNUNET_free (us);
689         }
690       else if (fn[0] == '.')
691         {
692           ext = fn;
693           us = GNUNET_FS_uri_to_string (uri);
694           GNUNET_asprintf (&fn,
695                            "%s%s",
696                            &us[strlen (GNUNET_FS_URI_PREFIX 
697                                        GNUNET_FS_URI_CHK_INFIX)], ext);
698           GNUNET_free (ext);
699           GNUNET_free (us);
700         }
701       filename = fn;
702     }
703   if (dc->filename == NULL)
704     {
705       full_name = NULL;
706     }
707   else
708     {
709       dn = GNUNET_strdup (dc->filename);
710       GNUNET_break ( (strlen (dn) >= strlen (GNUNET_FS_DIRECTORY_EXT)) &&
711                      (NULL !=
712                       strstr (dn + strlen(dn) - strlen(GNUNET_FS_DIRECTORY_EXT),
713                               GNUNET_FS_DIRECTORY_EXT)) );
714       if ( (strlen (dn) >= strlen (GNUNET_FS_DIRECTORY_EXT)) &&
715            (NULL !=
716             strstr (dn + strlen(dn) - strlen(GNUNET_FS_DIRECTORY_EXT),
717                     GNUNET_FS_DIRECTORY_EXT)) )      
718         dn[strlen(dn) - strlen (GNUNET_FS_DIRECTORY_EXT)] = '\0';      
719       if ( (GNUNET_YES == GNUNET_FS_meta_data_test_for_directory (meta)) &&
720            ( (strlen (filename) < strlen (GNUNET_FS_DIRECTORY_EXT)) ||
721              (NULL ==
722               strstr (filename + strlen(filename) - strlen(GNUNET_FS_DIRECTORY_EXT),
723                       GNUNET_FS_DIRECTORY_EXT)) ) )
724         {
725           GNUNET_asprintf (&full_name,
726                            "%s%s%s%s",
727                            dn,
728                            DIR_SEPARATOR_STR,
729                            filename,
730                            GNUNET_FS_DIRECTORY_EXT);
731         }
732       else
733         {
734           GNUNET_asprintf (&full_name,
735                            "%s%s%s",
736                            dn,
737                            DIR_SEPARATOR_STR,
738                            filename);
739         }
740       GNUNET_free (dn);
741     }
742   if ( (full_name != NULL) &&
743        (GNUNET_OK !=
744         GNUNET_DISK_directory_create_for_file (full_name)) )
745     {
746       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
747                   _("Failed to create directory for recursive download of `%s'\n"),
748                   full_name);
749       GNUNET_free (full_name);
750       GNUNET_free_non_null (fn);
751       return;
752     }
753
754   temp_name = NULL;
755   if ( (data != NULL) &&
756        (GNUNET_FS_uri_chk_get_file_size (uri) == length) )
757     {
758       if (full_name == NULL)
759         {
760           temp_name = GNUNET_DISK_mktemp ("gnunet-directory-download-tmp");
761           real_name = temp_name;
762         }
763       else
764         {
765           real_name = full_name;
766         }
767       /* write to disk, then trigger normal download which will instantly progress to completion */
768       fh = GNUNET_DISK_file_open (real_name,
769                                   GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_TRUNCATE | GNUNET_DISK_OPEN_CREATE,
770                                   GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE);
771       if (fh == NULL)
772         {
773           GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
774                                     "open",
775                                     real_name);       
776           GNUNET_free (full_name);
777           GNUNET_free_non_null (fn);
778           return;
779         }
780       if (length != 
781           GNUNET_DISK_file_write (fh,
782                                   data,
783                                   length))
784         {
785           GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
786                                     "write",
787                                     full_name);       
788         }
789       GNUNET_DISK_file_close (fh);
790     }
791   GNUNET_FS_download_start (dc->h,
792                             uri,
793                             meta,
794                             full_name, temp_name,
795                             0,
796                             GNUNET_FS_uri_chk_get_file_size (uri),
797                             dc->anonymity,
798                             dc->options,
799                             NULL,
800                             dc);
801   GNUNET_free_non_null (full_name);
802   GNUNET_free_non_null (temp_name);
803   GNUNET_free_non_null (fn);
804 }
805
806
807 /**
808  * Iterator over entries in the pending requests in the 'active' map for the
809  * reply that we just got.
810  *
811  * @param cls closure (our 'struct ProcessResultClosure')
812  * @param key query for the given value / request
813  * @param value value in the hash map (a 'struct DownloadRequest')
814  * @return GNUNET_YES (we should continue to iterate); unless serious error
815  */
816 static int
817 process_result_with_request (void *cls,
818                              const GNUNET_HashCode * key,
819                              void *value)
820 {
821   struct ProcessResultClosure *prc = cls;
822   struct DownloadRequest *sm = value;
823   struct GNUNET_FS_DownloadContext *dc = prc->dc;
824   struct GNUNET_CRYPTO_AesSessionKey skey;
825   struct GNUNET_CRYPTO_AesInitializationVector iv;
826   char pt[prc->size];
827   struct GNUNET_FS_ProgressInfo pi;
828   uint64_t off;
829   size_t bs;
830   size_t app;
831   int i;
832   struct ContentHashKey *chk;
833   char *emsg;
834
835   bs = GNUNET_FS_tree_calculate_block_size (GNUNET_ntohll (dc->uri->data.chk.file_length),
836                                             dc->treedepth,
837                                             sm->offset,
838                                             sm->depth);
839     if (prc->size != bs)
840     {
841 #if DEBUG_DOWNLOAD
842       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
843                   "Internal error or bogus download URI (expected %u bytes, got %u)\n",
844                   bs,
845                   prc->size);
846 #endif
847       dc->emsg = GNUNET_strdup ("Internal error or bogus download URI");
848       /* signal error */
849       pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
850       make_download_status (&pi, dc);
851       pi.value.download.specifics.error.message = dc->emsg;
852       dc->client_info = dc->h->upcb (dc->h->upcb_cls,
853                                      &pi);
854       /* abort all pending requests */
855       if (NULL != dc->th)
856         {
857           GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
858           dc->th = NULL;
859         }
860       GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
861       dc->client = NULL;
862       return GNUNET_NO;
863     }
864   GNUNET_assert (GNUNET_YES ==
865                  GNUNET_CONTAINER_multihashmap_remove (dc->active,
866                                                        &prc->query,
867                                                        sm));
868   GNUNET_CRYPTO_hash_to_aes_key (&sm->chk.key, &skey, &iv);
869   GNUNET_CRYPTO_aes_decrypt (prc->data,
870                              prc->size,
871                              &skey,
872                              &iv,
873                              pt);
874   off = compute_disk_offset (GNUNET_ntohll (dc->uri->data.chk.file_length),
875                              sm->offset,
876                              sm->depth,
877                              dc->treedepth);
878   /* save to disk */
879   if ( ( GNUNET_YES == prc->do_store) &&
880        (NULL != dc->handle) &&
881        ( (sm->depth == dc->treedepth) ||
882          (0 == (dc->options & GNUNET_FS_DOWNLOAD_NO_TEMPORARIES)) ) )
883     {
884       emsg = NULL;
885 #if DEBUG_DOWNLOAD
886       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
887                   "Saving decrypted block to disk at offset %llu\n",
888                   (unsigned long long) off);
889 #endif
890       if ( (off  != 
891             GNUNET_DISK_file_seek (dc->handle,
892                                    off,
893                                    GNUNET_DISK_SEEK_SET) ) )
894         GNUNET_asprintf (&emsg,
895                          _("Failed to seek to offset %llu in file `%s': %s\n"),
896                          (unsigned long long) off,
897                          dc->filename,
898                          STRERROR (errno));
899       else if (prc->size !=
900                GNUNET_DISK_file_write (dc->handle,
901                                        pt,
902                                        prc->size))
903         GNUNET_asprintf (&emsg,
904                          _("Failed to write block of %u bytes at offset %llu in file `%s': %s\n"),
905                          (unsigned int) prc->size,
906                          (unsigned long long) off,
907                          dc->filename,
908                          STRERROR (errno));
909       if (NULL != emsg)
910         {
911           dc->emsg = emsg;
912           // FIXME: make persistent
913
914           /* signal error */
915           pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
916           make_download_status (&pi, dc);
917           pi.value.download.specifics.error.message = emsg;
918           dc->client_info = dc->h->upcb (dc->h->upcb_cls,
919                                          &pi);
920
921           /* abort all pending requests */
922           if (NULL != dc->th)
923             {
924               GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
925               dc->th = NULL;
926             }
927           GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
928           dc->client = NULL;
929           GNUNET_free (sm);
930           return GNUNET_NO;
931         }
932     }
933   if (sm->depth == dc->treedepth) 
934     {
935       app = prc->size;
936       if (sm->offset < dc->offset)
937         {
938           /* starting offset begins in the middle of pt,
939              do not count first bytes as progress */
940           GNUNET_assert (app > (dc->offset - sm->offset));
941           app -= (dc->offset - sm->offset);       
942         }
943       if (sm->offset + prc->size > dc->offset + dc->length)
944         {
945           /* end of block is after relevant range,
946              do not count last bytes as progress */
947           GNUNET_assert (app > (sm->offset + prc->size) - (dc->offset + dc->length));
948           app -= (sm->offset + prc->size) - (dc->offset + dc->length);
949         }
950       dc->completed += app;
951
952       /* do recursive download if option is set and either meta data
953          says it is a directory or if no meta data is given AND filename 
954          ends in '.gnd' (top-level case) */
955       if (is_recursive_download (dc))
956         GNUNET_FS_directory_list_contents (prc->size,
957                                            pt,
958                                            off,
959                                            &trigger_recursive_download,
960                                            dc);         
961             
962     }
963   pi.status = GNUNET_FS_STATUS_DOWNLOAD_PROGRESS;
964   make_download_status (&pi, dc);
965   pi.value.download.specifics.progress.data = pt;
966   pi.value.download.specifics.progress.offset = sm->offset;
967   pi.value.download.specifics.progress.data_len = prc->size;
968   pi.value.download.specifics.progress.depth = sm->depth;
969   dc->client_info = dc->h->upcb (dc->h->upcb_cls,
970                                  &pi);
971   GNUNET_assert (dc->completed <= dc->length);
972   if (dc->completed == dc->length)
973     {
974 #if DEBUG_DOWNLOAD
975       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
976                   "Download completed, truncating file to desired length %llu\n",
977                   (unsigned long long) GNUNET_ntohll (dc->uri->data.chk.file_length));
978 #endif
979       /* truncate file to size (since we store IBlocks at the end) */
980       if (dc->handle != NULL)
981         {
982           GNUNET_DISK_file_close (dc->handle);
983           dc->handle = NULL;
984           if (0 != truncate (dc->filename,
985                              GNUNET_ntohll (dc->uri->data.chk.file_length)))
986             GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
987                                       "truncate",
988                                       dc->filename);
989         }
990       if (dc->job_queue != NULL)
991         {
992           GNUNET_FS_dequeue_ (dc->job_queue);
993           dc->job_queue = NULL;
994         }
995       if (is_recursive_download (dc))
996         full_recursive_download (dc);
997       if (dc->child_head == NULL)
998         {
999           /* signal completion */
1000           pi.status = GNUNET_FS_STATUS_DOWNLOAD_COMPLETED;
1001           make_download_status (&pi, dc);
1002           dc->client_info = dc->h->upcb (dc->h->upcb_cls,
1003                                          &pi);
1004           if (dc->parent != NULL)
1005             check_completed (dc->parent);
1006         }
1007       GNUNET_assert (sm->depth == dc->treedepth);
1008     }
1009   // FIXME: make persistent
1010   if (sm->depth == dc->treedepth) 
1011     {
1012       GNUNET_free (sm);      
1013       return GNUNET_YES;
1014     }
1015 #if DEBUG_DOWNLOAD
1016   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1017               "Triggering downloads of children (this block was at depth %u and offset %llu)\n",
1018               sm->depth,
1019               (unsigned long long) sm->offset);
1020 #endif
1021   GNUNET_assert (0 == (prc->size % sizeof(struct ContentHashKey)));
1022   chk = (struct ContentHashKey*) pt;
1023   for (i=(prc->size / sizeof(struct ContentHashKey))-1;i>=0;i--)
1024     {
1025       off = compute_dblock_offset (sm->offset,
1026                                    sm->depth,
1027                                    dc->treedepth,
1028                                    i);
1029       if ( (off + DBLOCK_SIZE >= dc->offset) &&
1030            (off < dc->offset + dc->length) ) 
1031         schedule_block_download (dc,
1032                                  &chk[i],
1033                                  off,
1034                                  sm->depth + 1);
1035     }
1036   GNUNET_free (sm);
1037   return GNUNET_YES;
1038 }
1039
1040
1041 /**
1042  * Process a download result.
1043  *
1044  * @param dc our download context
1045  * @param type type of the result
1046  * @param data the (encrypted) response
1047  * @param size size of data
1048  */
1049 static void
1050 process_result (struct GNUNET_FS_DownloadContext *dc,
1051                 enum GNUNET_BLOCK_Type type,
1052                 const void *data,
1053                 size_t size)
1054 {
1055   struct ProcessResultClosure prc;
1056
1057   prc.dc = dc;
1058   prc.data = data;
1059   prc.size = size;
1060   prc.type = type;
1061   prc.do_store = GNUNET_YES;
1062   GNUNET_CRYPTO_hash (data, size, &prc.query);
1063 #if DEBUG_DOWNLOAD
1064   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1065               "Received result for query `%s' from `%s'-service\n",
1066               GNUNET_h2s (&prc.query),
1067               "FS");
1068 #endif
1069   GNUNET_CONTAINER_multihashmap_get_multiple (dc->active,
1070                                               &prc.query,
1071                                               &process_result_with_request,
1072                                               &prc);
1073 }
1074
1075
1076 /**
1077  * Type of a function to call when we receive a message
1078  * from the service.
1079  *
1080  * @param cls closure
1081  * @param msg message received, NULL on timeout or fatal error
1082  */
1083 static void 
1084 receive_results (void *cls,
1085                  const struct GNUNET_MessageHeader * msg)
1086 {
1087   struct GNUNET_FS_DownloadContext *dc = cls;
1088   const struct PutMessage *cm;
1089   uint16_t msize;
1090
1091   if ( (NULL == msg) ||
1092        (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_PUT) ||
1093        (sizeof (struct PutMessage) > ntohs(msg->size)) )
1094     {
1095       GNUNET_break (msg == NULL);       
1096       try_reconnect (dc);
1097       return;
1098     }
1099   msize = ntohs(msg->size);
1100   cm = (const struct PutMessage*) msg;
1101   process_result (dc, 
1102                   ntohl (cm->type),
1103                   &cm[1],
1104                   msize - sizeof (struct PutMessage));
1105   if (dc->client == NULL)
1106     return; /* fatal error */
1107   /* continue receiving */
1108   GNUNET_CLIENT_receive (dc->client,
1109                          &receive_results,
1110                          dc,
1111                          GNUNET_TIME_UNIT_FOREVER_REL);
1112 }
1113
1114
1115
1116 /**
1117  * We're ready to transmit a search request to the
1118  * file-sharing service.  Do it.  If there is 
1119  * more than one request pending, try to send 
1120  * multiple or request another transmission.
1121  *
1122  * @param cls closure
1123  * @param size number of bytes available in buf
1124  * @param buf where the callee should write the message
1125  * @return number of bytes written to buf
1126  */
1127 static size_t
1128 transmit_download_request (void *cls,
1129                            size_t size, 
1130                            void *buf)
1131 {
1132   struct GNUNET_FS_DownloadContext *dc = cls;
1133   size_t msize;
1134   struct SearchMessage *sm;
1135
1136   dc->th = NULL;
1137   if (NULL == buf)
1138     {
1139       try_reconnect (dc);
1140       return 0;
1141     }
1142   GNUNET_assert (size >= sizeof (struct SearchMessage));
1143   msize = 0;
1144   sm = buf;
1145   while ( (dc->pending != NULL) &&
1146           (size > msize + sizeof (struct SearchMessage)) )
1147     {
1148 #if DEBUG_DOWNLOAD
1149       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1150                   "Transmitting download request for `%s' to `%s'-service\n",
1151                   GNUNET_h2s (&dc->pending->chk.query),
1152                   "FS");
1153 #endif
1154       memset (sm, 0, sizeof (struct SearchMessage));
1155       sm->header.size = htons (sizeof (struct SearchMessage));
1156       sm->header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
1157       if (dc->pending->depth == dc->treedepth)
1158         sm->type = htonl (GNUNET_BLOCK_TYPE_DBLOCK);
1159       else
1160         sm->type = htonl (GNUNET_BLOCK_TYPE_IBLOCK);
1161       sm->anonymity_level = htonl (dc->anonymity);
1162       sm->target = dc->target.hashPubKey;
1163       sm->query = dc->pending->chk.query;
1164       dc->pending->is_pending = GNUNET_NO;
1165       dc->pending = dc->pending->next;
1166       msize += sizeof (struct SearchMessage);
1167       sm++;
1168     }
1169   if (dc->pending != NULL)
1170     dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
1171                                                   sizeof (struct SearchMessage),
1172                                                   GNUNET_CONSTANTS_SERVICE_TIMEOUT,
1173                                                   GNUNET_NO,
1174                                                   &transmit_download_request,
1175                                                   dc); 
1176   return msize;
1177 }
1178
1179
1180 /**
1181  * Reconnect to the FS service and transmit our queries NOW.
1182  *
1183  * @param cls our download context
1184  * @param tc unused
1185  */
1186 static void
1187 do_reconnect (void *cls,
1188               const struct GNUNET_SCHEDULER_TaskContext *tc)
1189 {
1190   struct GNUNET_FS_DownloadContext *dc = cls;
1191   struct GNUNET_CLIENT_Connection *client;
1192   
1193   dc->task = GNUNET_SCHEDULER_NO_TASK;
1194   client = GNUNET_CLIENT_connect (dc->h->sched,
1195                                   "fs",
1196                                   dc->h->cfg);
1197   if (NULL == client)
1198     {
1199       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1200                   "Connecting to `%s'-service failed, will try again.\n",
1201                   "FS");
1202       try_reconnect (dc);
1203       return;
1204     }
1205   dc->client = client;
1206   dc->th = GNUNET_CLIENT_notify_transmit_ready (client,
1207                                                 sizeof (struct SearchMessage),
1208                                                 GNUNET_CONSTANTS_SERVICE_TIMEOUT,
1209                                                 GNUNET_NO,
1210                                                 &transmit_download_request,
1211                                                 dc);  
1212   GNUNET_CLIENT_receive (client,
1213                          &receive_results,
1214                          dc,
1215                          GNUNET_TIME_UNIT_FOREVER_REL);
1216 }
1217
1218
1219 /**
1220  * Add entries that are not yet pending back to the pending list.
1221  *
1222  * @param cls our download context
1223  * @param key unused
1224  * @param entry entry of type "struct DownloadRequest"
1225  * @return GNUNET_OK
1226  */
1227 static int
1228 retry_entry (void *cls,
1229              const GNUNET_HashCode *key,
1230              void *entry)
1231 {
1232   struct GNUNET_FS_DownloadContext *dc = cls;
1233   struct DownloadRequest *dr = entry;
1234
1235   if (! dr->is_pending)
1236     {
1237       dr->next = dc->pending;
1238       dr->is_pending = GNUNET_YES;
1239       dc->pending = entry;
1240     }
1241   return GNUNET_OK;
1242 }
1243
1244
1245 /**
1246  * We've lost our connection with the FS service.
1247  * Re-establish it and re-transmit all of our
1248  * pending requests.
1249  *
1250  * @param dc download context that is having trouble
1251  */
1252 static void
1253 try_reconnect (struct GNUNET_FS_DownloadContext *dc)
1254 {
1255   
1256   if (NULL != dc->client)
1257     {
1258       if (NULL != dc->th)
1259         {
1260           GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
1261           dc->th = NULL;
1262         }
1263       GNUNET_CONTAINER_multihashmap_iterate (dc->active,
1264                                              &retry_entry,
1265                                              dc);
1266       GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
1267       dc->client = NULL;
1268     }
1269   dc->task
1270     = GNUNET_SCHEDULER_add_delayed (dc->h->sched,
1271                                     GNUNET_TIME_UNIT_SECONDS,
1272                                     &do_reconnect,
1273                                     dc);
1274 }
1275
1276
1277
1278 /**
1279  * We're allowed to ask the FS service for our blocks.  Start the download.
1280  *
1281  * @param cls the 'struct GNUNET_FS_DownloadContext'
1282  * @param client handle to use for communcation with FS (we must destroy it!)
1283  */
1284 static void
1285 activate_fs_download (void *cls,
1286                       struct GNUNET_CLIENT_Connection *client)
1287 {
1288   struct GNUNET_FS_DownloadContext *dc = cls;
1289   struct GNUNET_FS_ProgressInfo pi;
1290
1291   GNUNET_assert (NULL != client);
1292   dc->client = client;
1293   GNUNET_CLIENT_receive (client,
1294                          &receive_results,
1295                          dc,
1296                          GNUNET_TIME_UNIT_FOREVER_REL);
1297   pi.status = GNUNET_FS_STATUS_DOWNLOAD_ACTIVE;
1298   make_download_status (&pi, dc);
1299   dc->client_info = dc->h->upcb (dc->h->upcb_cls,
1300                                  &pi);
1301   GNUNET_CONTAINER_multihashmap_iterate (dc->active,
1302                                          &retry_entry,
1303                                          dc);
1304   if ( (dc->th == NULL) &&
1305        (dc->client != NULL) )
1306     dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
1307                                                   sizeof (struct SearchMessage),
1308                                                   GNUNET_CONSTANTS_SERVICE_TIMEOUT,
1309                                                   GNUNET_NO,
1310                                                   &transmit_download_request,
1311                                                   dc);
1312 }
1313
1314
1315 /**
1316  * We must stop to ask the FS service for our blocks.  Pause the download.
1317  *
1318  * @param cls the 'struct GNUNET_FS_DownloadContext'
1319  * @param client handle to use for communcation with FS (we must destroy it!)
1320  */
1321 static void
1322 deactivate_fs_download (void *cls)
1323 {
1324   struct GNUNET_FS_DownloadContext *dc = cls;
1325   struct GNUNET_FS_ProgressInfo pi;
1326   
1327   if (NULL != dc->th)
1328     {
1329       GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
1330       dc->th = NULL;
1331     }
1332   if (NULL != dc->client)
1333     {
1334       GNUNET_CLIENT_disconnect (dc->client, GNUNET_NO);
1335       dc->client = NULL;
1336     }
1337   pi.status = GNUNET_FS_STATUS_DOWNLOAD_INACTIVE;
1338   make_download_status (&pi, dc);
1339   dc->client_info = dc->h->upcb (dc->h->upcb_cls,
1340                                  &pi);
1341 }
1342
1343
1344 /**
1345  * Download parts of a file.  Note that this will store
1346  * the blocks at the respective offset in the given file.  Also, the
1347  * download is still using the blocking of the underlying FS
1348  * encoding.  As a result, the download may *write* outside of the
1349  * given boundaries (if offset and length do not match the 32k FS
1350  * block boundaries). <p>
1351  *
1352  * This function should be used to focus a download towards a
1353  * particular portion of the file (optimization), not to strictly
1354  * limit the download to exactly those bytes.
1355  *
1356  * @param h handle to the file sharing subsystem
1357  * @param uri the URI of the file (determines what to download); CHK or LOC URI
1358  * @param meta known metadata for the file (can be NULL)
1359  * @param filename where to store the file, maybe NULL (then no file is
1360  *        created on disk and data must be grabbed from the callbacks)
1361  * @param tempname where to store temporary file data, not used if filename is non-NULL;
1362  *        can be NULL (in which case we will pick a name if needed); the temporary file
1363  *        may already exist, in which case we will try to use the data that is there and
1364  *        if it is not what is desired, will overwrite it
1365  * @param offset at what offset should we start the download (typically 0)
1366  * @param length how many bytes should be downloaded starting at offset
1367  * @param anonymity anonymity level to use for the download
1368  * @param options various options
1369  * @param cctx initial value for the client context for this download
1370  * @param parent parent download to associate this download with (use NULL
1371  *        for top-level downloads; useful for manually-triggered recursive downloads)
1372  * @return context that can be used to control this download
1373  */
1374 struct GNUNET_FS_DownloadContext *
1375 GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
1376                           const struct GNUNET_FS_Uri *uri,
1377                           const struct GNUNET_CONTAINER_MetaData *meta,
1378                           const char *filename,
1379                           const char *tempname,
1380                           uint64_t offset,
1381                           uint64_t length,
1382                           uint32_t anonymity,
1383                           enum GNUNET_FS_DownloadOptions options,
1384                           void *cctx,
1385                           struct GNUNET_FS_DownloadContext *parent)
1386 {
1387   struct GNUNET_FS_ProgressInfo pi;
1388   struct GNUNET_FS_DownloadContext *dc;
1389
1390   GNUNET_assert (GNUNET_FS_uri_test_chk (uri));
1391   if ( (offset + length < offset) ||
1392        (offset + length > uri->data.chk.file_length) )
1393     {      
1394       GNUNET_break (0);
1395       return NULL;
1396     }
1397   // FIXME: add support for "loc" URIs!
1398 #if DEBUG_DOWNLOAD
1399   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1400               "Starting download `%s' of %llu bytes\n",
1401               filename,
1402               (unsigned long long) length);
1403 #endif
1404   dc = GNUNET_malloc (sizeof(struct GNUNET_FS_DownloadContext));
1405   dc->h = h;
1406   dc->parent = parent;
1407   if (parent != NULL)
1408     {
1409       GNUNET_CONTAINER_DLL_insert (parent->child_head,
1410                                    parent->child_tail,
1411                                    dc);
1412     }
1413   dc->uri = GNUNET_FS_uri_dup (uri);
1414   dc->meta = GNUNET_CONTAINER_meta_data_duplicate (meta);
1415   dc->client_info = cctx;
1416   dc->start_time = GNUNET_TIME_absolute_get ();
1417   if (NULL != filename)
1418     {
1419       dc->filename = GNUNET_strdup (filename);
1420       if (GNUNET_YES == GNUNET_DISK_file_test (filename))
1421         GNUNET_DISK_file_size (filename,
1422                                &dc->old_file_size,
1423                                GNUNET_YES);
1424       dc->handle = GNUNET_DISK_file_open (filename, 
1425                                           GNUNET_DISK_OPEN_READWRITE | 
1426                                           GNUNET_DISK_OPEN_CREATE,
1427                                           GNUNET_DISK_PERM_USER_READ |
1428                                           GNUNET_DISK_PERM_USER_WRITE |
1429                                           GNUNET_DISK_PERM_GROUP_READ |
1430                                           GNUNET_DISK_PERM_OTHER_READ);
1431       if (dc->handle == NULL)
1432         {
1433           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1434                       _("Download failed: could not open file `%s': %s\n"),
1435                       dc->filename,
1436                       STRERROR (errno));
1437           GNUNET_CONTAINER_meta_data_destroy (dc->meta);
1438           GNUNET_FS_uri_destroy (dc->uri);
1439           GNUNET_free (dc->filename);
1440           GNUNET_free (dc);
1441           return NULL;
1442         }
1443     }
1444   // FIXME: set "dc->target" for LOC uris!
1445   dc->offset = offset;
1446   dc->length = length;
1447   dc->anonymity = anonymity;
1448   dc->options = options;
1449   dc->active = GNUNET_CONTAINER_multihashmap_create (1 + 2 * (length / DBLOCK_SIZE));
1450   dc->treedepth = GNUNET_FS_compute_depth (GNUNET_ntohll(dc->uri->data.chk.file_length));
1451   if ( (filename == NULL) &&
1452        (is_recursive_download (dc) ) )
1453     {
1454       if (tempname != NULL)
1455         dc->temp_filename = GNUNET_strdup (tempname);
1456       else
1457         dc->temp_filename = GNUNET_DISK_mktemp ("gnunet-directory-download-tmp");    
1458     }
1459
1460 #if DEBUG_DOWNLOAD
1461   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1462               "Download tree has depth %u\n",
1463               dc->treedepth);
1464 #endif
1465   // FIXME: make persistent
1466   pi.status = GNUNET_FS_STATUS_DOWNLOAD_START;
1467   make_download_status (&pi, dc);
1468   pi.value.download.specifics.start.meta = meta;
1469   dc->client_info = dc->h->upcb (dc->h->upcb_cls,
1470                                  &pi);
1471   schedule_block_download (dc, 
1472                            &dc->uri->data.chk.chk,
1473                            0, 
1474                            1 /* 0 == CHK, 1 == top */); 
1475   dc->job_queue = GNUNET_FS_queue_ (h, 
1476                                     &activate_fs_download,
1477                                     &deactivate_fs_download,
1478                                     dc,
1479                                     (length + DBLOCK_SIZE-1) / DBLOCK_SIZE);
1480   return dc;
1481 }
1482
1483
1484 /**
1485  * Free entries in the map.
1486  *
1487  * @param cls unused (NULL)
1488  * @param key unused
1489  * @param entry entry of type "struct DownloadRequest" which is freed
1490  * @return GNUNET_OK
1491  */
1492 static int
1493 free_entry (void *cls,
1494             const GNUNET_HashCode *key,
1495             void *entry)
1496 {
1497   GNUNET_free (entry);
1498   return GNUNET_OK;
1499 }
1500
1501
1502 /**
1503  * Stop a download (aborts if download is incomplete).
1504  *
1505  * @param dc handle for the download
1506  * @param do_delete delete files of incomplete downloads
1507  */
1508 void
1509 GNUNET_FS_download_stop (struct GNUNET_FS_DownloadContext *dc,
1510                          int do_delete)
1511 {
1512   struct GNUNET_FS_ProgressInfo pi;
1513
1514   if (dc->job_queue != NULL)
1515     {
1516       GNUNET_FS_dequeue_ (dc->job_queue);
1517       dc->job_queue = NULL;
1518     }
1519   while (NULL != dc->child_head)
1520     GNUNET_FS_download_stop (dc->child_head, 
1521                              do_delete);
1522   // FIXME: make unpersistent  
1523   if (dc->parent != NULL)
1524     GNUNET_CONTAINER_DLL_remove (dc->parent->child_head,
1525                                  dc->parent->child_tail,
1526                                  dc);
1527   
1528   pi.status = GNUNET_FS_STATUS_DOWNLOAD_STOPPED;
1529   make_download_status (&pi, dc);
1530   dc->client_info = dc->h->upcb (dc->h->upcb_cls,
1531                                  &pi);
1532
1533   if (GNUNET_SCHEDULER_NO_TASK != dc->task)
1534     GNUNET_SCHEDULER_cancel (dc->h->sched,
1535                              dc->task);
1536   GNUNET_CONTAINER_multihashmap_iterate (dc->active,
1537                                          &free_entry,
1538                                          NULL);
1539   GNUNET_CONTAINER_multihashmap_destroy (dc->active);
1540   if (dc->filename != NULL)
1541     {
1542       if (NULL != dc->handle)
1543         GNUNET_DISK_file_close (dc->handle);
1544       if ( (dc->completed != dc->length) &&
1545            (GNUNET_YES == do_delete) )
1546         {
1547           if (0 != UNLINK (dc->filename))
1548             GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
1549                                       "unlink",
1550                                       dc->filename);
1551         }
1552       GNUNET_free (dc->filename);
1553     }
1554   GNUNET_CONTAINER_meta_data_destroy (dc->meta);
1555   GNUNET_FS_uri_destroy (dc->uri);
1556   if (NULL != dc->temp_filename)
1557     {
1558       if (0 != UNLINK (dc->temp_filename))
1559         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
1560                                   "unlink",
1561                                   dc->temp_filename);
1562       GNUNET_free (dc->temp_filename);
1563     }
1564   GNUNET_free (dc);
1565 }
1566
1567 /* end of fs_download.c */