fixing bugs, diagnosis summary
[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 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  * - check if blocks exist already (can wait, easy)
28  * - handle recursive downloads (need directory & 
29  *   fs-level download-parallelism management, can wait)
30  * - check if iblocks can be computed from existing blocks (can wait, hard)
31  * - persistence (can wait)
32  */
33 #include "platform.h"
34 #include "gnunet_constants.h"
35 #include "gnunet_fs_service.h"
36 #include "fs.h"
37 #include "fs_tree.h"
38
39 #define DEBUG_DOWNLOAD GNUNET_YES
40
41 /**
42  * We're storing the IBLOCKS after the
43  * DBLOCKS on disk (so that we only have
44  * to truncate the file once we're done).
45  *
46  * Given the offset of a block (with respect
47  * to the DBLOCKS) and its depth, return the
48  * offset where we would store this block
49  * in the file.
50
51  * 
52  * @param fsize overall file size
53  * @param off offset of the block in the file
54  * @param depth depth of the block in the tree
55  * @param treedepth maximum depth of the tree
56  * @return off for DBLOCKS (depth == treedepth),
57  *         otherwise an offset past the end
58  *         of the file that does not overlap
59  *         with the range for any other block
60  */
61 static uint64_t
62 compute_disk_offset (uint64_t fsize,
63                      uint64_t off,
64                      unsigned int depth,
65                      unsigned int treedepth)
66 {
67   unsigned int i;
68   uint64_t lsize; /* what is the size of all IBlocks for level "i"? */
69   uint64_t loff; /* where do IBlocks for level "i" start? */
70   unsigned int ioff; /* which IBlock corresponds to "off" at level "i"? */
71   
72   if (depth == treedepth)
73     return off;
74   /* first IBlocks start at the end of file, rounded up
75      to full DBLOCK_SIZE */
76   loff = ((fsize + DBLOCK_SIZE - 1) / DBLOCK_SIZE) * DBLOCK_SIZE;
77   lsize = ( (fsize + DBLOCK_SIZE-1) / DBLOCK_SIZE) * sizeof (struct ContentHashKey);
78   GNUNET_assert (0 == (off % DBLOCK_SIZE));
79   ioff = (off / DBLOCK_SIZE);
80   for (i=treedepth-1;i>depth;i--)
81     {
82       loff += lsize;
83       lsize = (lsize + CHK_PER_INODE - 1) / CHK_PER_INODE;
84       GNUNET_assert (lsize > 0);
85       GNUNET_assert (0 == (ioff % CHK_PER_INODE));
86       ioff /= CHK_PER_INODE;
87     }
88   return loff + ioff * sizeof (struct ContentHashKey);
89 }
90
91
92 /**
93  * Given a file of the specified treedepth and a block at the given
94  * offset and depth, calculate the offset for the CHK at the given
95  * index.
96  *
97  * @param offset the offset of the first
98  *        DBLOCK in the subtree of the 
99  *        identified IBLOCK
100  * @param depth the depth of the IBLOCK in the tree
101  * @param treedepth overall depth of the tree
102  * @param k which CHK in the IBLOCK are we 
103  *        talking about
104  * @return offset if k=0, otherwise an appropriately
105  *         larger value (i.e., if depth = treedepth-1,
106  *         the returned value should be offset+DBLOCK_SIZE)
107  */
108 static uint64_t
109 compute_dblock_offset (uint64_t offset,
110                        unsigned int depth,
111                        unsigned int treedepth,
112                        unsigned int k)
113 {
114   unsigned int i;
115   uint64_t lsize; /* what is the size of the sum of all DBlocks 
116                      that a CHK at level i corresponds to? */
117
118   if (depth == treedepth)
119     return offset;
120   lsize = DBLOCK_SIZE;
121   for (i=treedepth-1;i>depth;i--)
122     lsize *= CHK_PER_INODE;
123   return offset + k * lsize;
124 }
125
126
127 /**
128  * Fill in all of the generic fields for 
129  * a download event.
130  *
131  * @param pi structure to fill in
132  * @param dc overall download context
133  */
134 static void
135 make_download_status (struct GNUNET_FS_ProgressInfo *pi,
136                       struct GNUNET_FS_DownloadContext *dc)
137 {
138   pi->value.download.dc = dc;
139   pi->value.download.cctx
140     = dc->client_info;
141   pi->value.download.pctx
142     = (dc->parent == NULL) ? NULL : dc->parent->client_info;
143   pi->value.download.uri 
144     = dc->uri;
145   pi->value.download.filename
146     = dc->filename;
147   pi->value.download.size
148     = dc->length;
149   pi->value.download.duration
150     = GNUNET_TIME_absolute_get_duration (dc->start_time);
151   pi->value.download.completed
152     = dc->completed;
153   pi->value.download.anonymity
154     = dc->anonymity;
155 }
156
157 /**
158  * We're ready to transmit a search request to the
159  * file-sharing service.  Do it.  If there is 
160  * more than one request pending, try to send 
161  * multiple or request another transmission.
162  *
163  * @param cls closure
164  * @param size number of bytes available in buf
165  * @param buf where the callee should write the message
166  * @return number of bytes written to buf
167  */
168 static size_t
169 transmit_download_request (void *cls,
170                            size_t size, 
171                            void *buf);
172
173
174 /**
175  * Schedule the download of the specified
176  * block in the tree.
177  *
178  * @param dc overall download this block belongs to
179  * @param chk content-hash-key of the block
180  * @param offset offset of the block in the file
181  *         (for IBlocks, the offset is the lowest
182  *          offset of any DBlock in the subtree under
183  *          the IBlock)
184  * @param depth depth of the block, 0 is the root of the tree
185  */
186 static void
187 schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
188                          const struct ContentHashKey *chk,
189                          uint64_t offset,
190                          unsigned int depth)
191 {
192   struct DownloadRequest *sm;
193   uint64_t off;
194
195 #if DEBUG_DOWNLOAD
196   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
197               "Scheduling download at offset %llu and depth %u for `%s'\n",
198               (unsigned long long) offset,
199               depth,
200               GNUNET_h2s (&chk->query));
201 #endif
202   off = compute_disk_offset (GNUNET_ntohll (dc->uri->data.chk.file_length),
203                              offset,
204                              depth,
205                              dc->treedepth);
206   if ( (dc->old_file_size > off) &&
207        (dc->handle != NULL) &&
208        (off  == 
209         GNUNET_DISK_file_seek (dc->handle,
210                                off,
211                                GNUNET_DISK_SEEK_SET) ) )
212     {
213       // FIXME: check if block exists on disk!
214       // (read block, encode, compare with
215       // query; if matches, simply return)
216     }
217   if (depth < dc->treedepth)
218     {
219       // FIXME: try if we could
220       // reconstitute this IBLOCK
221       // from the existing blocks on disk (can wait)
222       // (read block(s), encode, compare with
223       // query; if matches, simply return)
224     }
225   sm = GNUNET_malloc (sizeof (struct DownloadRequest));
226   sm->chk = *chk;
227   sm->offset = offset;
228   sm->depth = depth;
229   sm->is_pending = GNUNET_YES;
230   sm->next = dc->pending;
231   dc->pending = sm;
232   GNUNET_CONTAINER_multihashmap_put (dc->active,
233                                      &chk->query,
234                                      sm,
235                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
236
237   if ( (dc->th == NULL) &&
238        (dc->client != NULL) )
239     dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
240                                                   sizeof (struct SearchMessage),
241                                                   GNUNET_CONSTANTS_SERVICE_TIMEOUT,
242                                                   &transmit_download_request,
243                                                   dc); 
244
245 }
246
247
248 /**
249  * We've lost our connection with the FS service.
250  * Re-establish it and re-transmit all of our
251  * pending requests.
252  *
253  * @param dc download context that is having trouble
254  */
255 static void
256 try_reconnect (struct GNUNET_FS_DownloadContext *dc);
257
258
259 /**
260  * Compute how many bytes of data should be stored in
261  * the specified node.
262  *
263  * @param fsize overall file size
264  * @param totaldepth depth of the entire tree
265  * @param offset offset of the node
266  * @param depth depth of the node
267  * @return number of bytes stored in this node
268  */
269 static size_t
270 calculate_block_size (uint64_t fsize,
271                       unsigned int totaldepth,
272                       uint64_t offset,
273                       unsigned int depth)
274 {
275   unsigned int i;
276   size_t ret;
277   uint64_t rsize;
278   uint64_t epos;
279   unsigned int chks;
280
281   GNUNET_assert (offset < fsize);
282   if (depth == totaldepth)
283     {
284       ret = DBLOCK_SIZE;
285       if (offset + ret > fsize)
286         ret = (size_t) (fsize - offset);
287       return ret;
288     }
289
290   rsize = DBLOCK_SIZE;
291   for (i = totaldepth-1; i > depth; i--)
292     rsize *= CHK_PER_INODE;
293   epos = offset + rsize * CHK_PER_INODE;
294   GNUNET_assert (epos > offset);
295   if (epos > fsize)
296     epos = fsize;
297   /* round up when computing #CHKs in our IBlock */
298   chks = (epos - offset + rsize - 1) / rsize;
299   GNUNET_assert (chks <= CHK_PER_INODE);
300   return chks * sizeof (struct ContentHashKey);
301 }
302
303
304 /**
305  * Process a download result.
306  *
307  * @param dc our download context
308  * @param type type of the result
309  * @param data the (encrypted) response
310  * @param size size of data
311  */
312 static void
313 process_result (struct GNUNET_FS_DownloadContext *dc,
314                 uint32_t type,
315                 const void *data,
316                 size_t size)
317 {
318   struct GNUNET_FS_ProgressInfo pi;
319   GNUNET_HashCode query;
320   struct DownloadRequest *sm;
321   struct GNUNET_CRYPTO_AesSessionKey skey;
322   struct GNUNET_CRYPTO_AesInitializationVector iv;
323   char pt[size];
324   uint64_t off;
325   size_t app;
326   int i;
327   struct ContentHashKey *chk;
328   char *emsg;
329
330
331   GNUNET_CRYPTO_hash (data, size, &query);
332 #if DEBUG_DOWNLOAD
333   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
334               "Received result for query `%s' from `%s'-service\n",
335               GNUNET_h2s (&query),
336               "FS");
337 #endif
338   sm = GNUNET_CONTAINER_multihashmap_get (dc->active,
339                                           &query);
340   if (NULL == sm)
341     {
342       GNUNET_break (0); /* FIXME: this assertion actually fails for one
343                            of my tests, the ascii-strings of the
344                            query match what was printed when the
345                            request was originally made; this does
346                            NOT happen if in line ~825 a HT size
347                            of 1 is used! => bug in HT? */
348       return;
349     }
350   if (size != calculate_block_size (GNUNET_ntohll (dc->uri->data.chk.file_length),
351                                     dc->treedepth,
352                                     sm->offset,
353                                     sm->depth))
354     {
355 #if DEBUG_DOWNLOAD
356       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
357                   "Internal error or bogus download URI (expected %u bytes, got %u)\n",
358                   calculate_block_size (GNUNET_ntohll (dc->uri->data.chk.file_length),
359                                         dc->treedepth,
360                                         sm->offset,
361                                         sm->depth),
362                   size);
363 #endif
364       dc->emsg = GNUNET_strdup ("Internal error or bogus download URI");
365       /* signal error */
366       pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
367       make_download_status (&pi, dc);
368       pi.value.download.specifics.error.message = dc->emsg;
369       dc->client_info = dc->h->upcb (dc->h->upcb_cls,
370                                      &pi);
371       /* abort all pending requests */
372       if (NULL != dc->th)
373         {
374           GNUNET_CONNECTION_notify_transmit_ready_cancel (dc->th);
375           dc->th = NULL;
376         }
377       GNUNET_CLIENT_disconnect (dc->client);
378       dc->client = NULL;
379       return;
380     }
381   GNUNET_assert (GNUNET_YES ==
382                  GNUNET_CONTAINER_multihashmap_remove (dc->active,
383                                                        &query,
384                                                        sm));
385   GNUNET_CRYPTO_hash_to_aes_key (&sm->chk.key, &skey, &iv);
386   GNUNET_CRYPTO_aes_decrypt (data,
387                              size,
388                              &skey,
389                              &iv,
390                              pt);
391   /* save to disk */
392   if ( (NULL != dc->handle) &&
393        ( (sm->depth == dc->treedepth) ||
394          (0 == (dc->options & GNUNET_FS_DOWNLOAD_NO_TEMPORARIES)) ) )
395     {
396       off = compute_disk_offset (GNUNET_ntohll (dc->uri->data.chk.file_length),
397                                  sm->offset,
398                                  sm->depth,
399                                  dc->treedepth);
400       emsg = NULL;
401 #if DEBUG_DOWNLOAD
402       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
403                   "Saving decrypted block to disk at offset %llu\n",
404                   (unsigned long long) off);
405 #endif
406       if ( (off  != 
407             GNUNET_DISK_file_seek (dc->handle,
408                                    off,
409                                    GNUNET_DISK_SEEK_SET) ) )
410         GNUNET_asprintf (&emsg,
411                          _("Failed to seek to offset %llu in file `%s': %s\n"),
412                          (unsigned long long) off,
413                          dc->filename,
414                          STRERROR (errno));
415       else if (size !=
416                GNUNET_DISK_file_write (dc->handle,
417                                        pt,
418                                        size))
419         GNUNET_asprintf (&emsg,
420                          _("Failed to write block of %u bytes at offset %llu in file `%s': %s\n"),
421                          (unsigned int) size,
422                          (unsigned long long) off,
423                          dc->filename,
424                          STRERROR (errno));
425       if (NULL != emsg)
426         {
427           dc->emsg = emsg;
428           // FIXME: make persistent
429
430           /* signal error */
431           pi.status = GNUNET_FS_STATUS_DOWNLOAD_ERROR;
432           make_download_status (&pi, dc);
433           pi.value.download.specifics.error.message = emsg;
434           dc->client_info = dc->h->upcb (dc->h->upcb_cls,
435                                          &pi);
436
437           /* abort all pending requests */
438           if (NULL != dc->th)
439             {
440               GNUNET_CONNECTION_notify_transmit_ready_cancel (dc->th);
441               dc->th = NULL;
442             }
443           GNUNET_CLIENT_disconnect (dc->client);
444           dc->client = NULL;
445           return;
446         }
447     }
448   if (sm->depth == dc->treedepth) 
449     {
450       app = size;
451       if (sm->offset < dc->offset)
452         {
453           /* starting offset begins in the middle of pt,
454              do not count first bytes as progress */
455           GNUNET_assert (app > (dc->offset - sm->offset));
456           app -= (dc->offset - sm->offset);       
457         }
458       if (sm->offset + size > dc->offset + dc->length)
459         {
460           /* end of block is after relevant range,
461              do not count last bytes as progress */
462           GNUNET_assert (app > (sm->offset + size) - (dc->offset + dc->length));
463           app -= (sm->offset + size) - (dc->offset + dc->length);
464         }
465       dc->completed += app;
466     }
467
468   pi.status = GNUNET_FS_STATUS_DOWNLOAD_PROGRESS;
469   make_download_status (&pi, dc);
470   pi.value.download.specifics.progress.data = pt;
471   pi.value.download.specifics.progress.offset = sm->offset;
472   pi.value.download.specifics.progress.data_len = size;
473   pi.value.download.specifics.progress.depth = sm->depth;
474   dc->client_info = dc->h->upcb (dc->h->upcb_cls,
475                                  &pi);
476   GNUNET_assert (dc->completed <= dc->length);
477   if (dc->completed == dc->length)
478     {
479 #if DEBUG_DOWNLOAD
480       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
481                   "Download completed, truncating file to desired length %llu\n",
482                   (unsigned long long) GNUNET_ntohll (dc->uri->data.chk.file_length));
483 #endif
484       /* truncate file to size (since we store IBlocks at the end) */
485       if (dc->handle != NULL)
486         {
487           GNUNET_DISK_file_close (dc->handle);
488           dc->handle = NULL;
489           if (0 != truncate (dc->filename,
490                              GNUNET_ntohll (dc->uri->data.chk.file_length)))
491             GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
492                                       "truncate",
493                                       dc->filename);
494         }
495       /* signal completion */
496       pi.status = GNUNET_FS_STATUS_DOWNLOAD_COMPLETED;
497       make_download_status (&pi, dc);
498       dc->client_info = dc->h->upcb (dc->h->upcb_cls,
499                                      &pi);
500       GNUNET_assert (sm->depth == dc->treedepth);
501     }
502   // FIXME: make persistent
503   if (sm->depth == dc->treedepth) 
504     return;
505 #if DEBUG_DOWNLOAD
506   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
507               "Triggering downloads of children (this block was at level %u and offset %llu)\n",
508               sm->depth,
509               (unsigned long long) sm->offset);
510 #endif
511   GNUNET_assert (0 == (size % sizeof(struct ContentHashKey)));
512   chk = (struct ContentHashKey*) pt;
513   for (i=(size / sizeof(struct ContentHashKey))-1;i>=0;i--)
514     {
515       off = compute_dblock_offset (sm->offset,
516                                    sm->depth,
517                                    dc->treedepth,
518                                    i);
519       if ( (off + DBLOCK_SIZE >= dc->offset) &&
520            (off < dc->offset + dc->length) ) 
521         schedule_block_download (dc,
522                                  &chk[i],
523                                  off,
524                                  sm->depth + 1);
525     }
526 }
527
528
529 /**
530  * Type of a function to call when we receive a message
531  * from the service.
532  *
533  * @param cls closure
534  * @param msg message received, NULL on timeout or fatal error
535  */
536 static void 
537 receive_results (void *cls,
538                  const struct GNUNET_MessageHeader * msg)
539 {
540   struct GNUNET_FS_DownloadContext *dc = cls;
541   const struct ContentMessage *cm;
542   uint16_t msize;
543
544   if ( (NULL == msg) ||
545        (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_CONTENT) ||
546        (ntohs (msg->size) <= sizeof (struct ContentMessage)) )
547     {
548       try_reconnect (dc);
549       return;
550     }
551   msize = ntohs (msg->size);
552   cm = (const struct ContentMessage*) msg;
553   process_result (dc, 
554                   ntohl (cm->type),
555                   &cm[1],
556                   msize - sizeof (struct ContentMessage));
557   if (dc->client == NULL)
558     return; /* fatal error */
559   /* continue receiving */
560   GNUNET_CLIENT_receive (dc->client,
561                          &receive_results,
562                          dc,
563                          GNUNET_TIME_UNIT_FOREVER_REL);
564 }
565
566
567
568 /**
569  * We're ready to transmit a search request to the
570  * file-sharing service.  Do it.  If there is 
571  * more than one request pending, try to send 
572  * multiple or request another transmission.
573  *
574  * @param cls closure
575  * @param size number of bytes available in buf
576  * @param buf where the callee should write the message
577  * @return number of bytes written to buf
578  */
579 static size_t
580 transmit_download_request (void *cls,
581                            size_t size, 
582                            void *buf)
583 {
584   struct GNUNET_FS_DownloadContext *dc = cls;
585   size_t msize;
586   struct SearchMessage *sm;
587
588   dc->th = NULL;
589   if (NULL == buf)
590     {
591       try_reconnect (dc);
592       return 0;
593     }
594   GNUNET_assert (size >= sizeof (struct SearchMessage));
595   msize = 0;
596   sm = buf;
597   while ( (dc->pending != NULL) &&
598           (size > msize + sizeof (struct SearchMessage)) )
599     {
600 #if DEBUG_DOWNLOAD
601       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
602                   "Transmitting download request for `%s' to `%s'-service\n",
603                   GNUNET_h2s (&dc->pending->chk.query),
604                   "FS");
605 #endif
606       memset (sm, 0, sizeof (struct SearchMessage));
607       sm->header.size = htons (sizeof (struct SearchMessage));
608       sm->header.type = htons (GNUNET_MESSAGE_TYPE_FS_START_SEARCH);
609       sm->anonymity_level = htonl (dc->anonymity);
610       sm->target = dc->target.hashPubKey;
611       sm->query = dc->pending->chk.query;
612       dc->pending->is_pending = GNUNET_NO;
613       dc->pending = dc->pending->next;
614       msize += sizeof (struct SearchMessage);
615       sm++;
616     }
617   if (dc->pending != NULL)
618     dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
619                                                   sizeof (struct SearchMessage),
620                                                   GNUNET_CONSTANTS_SERVICE_TIMEOUT,
621                                                   &transmit_download_request,
622                                                   dc); 
623   return msize;
624 }
625
626
627 /**
628  * Reconnect to the FS service and transmit
629  * our queries NOW.
630  *
631  * @param cls our download context
632  * @param tc unused
633  */
634 static void
635 do_reconnect (void *cls,
636               const struct GNUNET_SCHEDULER_TaskContext *tc)
637 {
638   struct GNUNET_FS_DownloadContext *dc = cls;
639   struct GNUNET_CLIENT_Connection *client;
640   
641   dc->task = GNUNET_SCHEDULER_NO_TASK;
642   client = GNUNET_CLIENT_connect (dc->h->sched,
643                                   "fs",
644                                   dc->h->cfg);
645   if (NULL == client)
646     {
647       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
648                   "Connecting to `%s'-service failed, will try again.\n",
649                   "FS");
650       try_reconnect (dc);
651       return;
652     }
653   dc->client = client;
654   dc->th = GNUNET_CLIENT_notify_transmit_ready (client,
655                                                 sizeof (struct SearchMessage),
656                                                 GNUNET_CONSTANTS_SERVICE_TIMEOUT,
657                                                 &transmit_download_request,
658                                                 dc);  
659   GNUNET_CLIENT_receive (client,
660                          &receive_results,
661                          dc,
662                          GNUNET_TIME_UNIT_FOREVER_REL);
663 }
664
665
666 /**
667  * Add entries that are not yet pending back to the pending list.
668  *
669  * @param cls our download context
670  * @param key unused
671  * @param entry entry of type "struct DownloadRequest"
672  * @return GNUNET_OK
673  */
674 static int
675 retry_entry (void *cls,
676              const GNUNET_HashCode *key,
677              void *entry)
678 {
679   struct GNUNET_FS_DownloadContext *dc = cls;
680   struct DownloadRequest *dr = entry;
681
682   if (! dr->is_pending)
683     {
684       dr->next = dc->pending;
685       dr->is_pending = GNUNET_YES;
686       dc->pending = entry;
687     }
688   return GNUNET_OK;
689 }
690
691
692 /**
693  * We've lost our connection with the FS service.
694  * Re-establish it and re-transmit all of our
695  * pending requests.
696  *
697  * @param dc download context that is having trouble
698  */
699 static void
700 try_reconnect (struct GNUNET_FS_DownloadContext *dc)
701 {
702   
703   if (NULL != dc->client)
704     {
705       if (NULL != dc->th)
706         {
707           GNUNET_CONNECTION_notify_transmit_ready_cancel (dc->th);
708           dc->th = NULL;
709         }
710       GNUNET_CONTAINER_multihashmap_iterate (dc->active,
711                                              &retry_entry,
712                                              dc);
713       GNUNET_CLIENT_disconnect (dc->client);
714       dc->client = NULL;
715     }
716   dc->task
717     = GNUNET_SCHEDULER_add_delayed (dc->h->sched,
718                                     GNUNET_NO,
719                                     GNUNET_SCHEDULER_PRIORITY_IDLE,
720                                     GNUNET_SCHEDULER_NO_TASK,
721                                     GNUNET_TIME_UNIT_SECONDS,
722                                     &do_reconnect,
723                                     dc);
724 }
725
726
727 /**
728  * Download parts of a file.  Note that this will store
729  * the blocks at the respective offset in the given file.  Also, the
730  * download is still using the blocking of the underlying FS
731  * encoding.  As a result, the download may *write* outside of the
732  * given boundaries (if offset and length do not match the 32k FS
733  * block boundaries). <p>
734  *
735  * This function should be used to focus a download towards a
736  * particular portion of the file (optimization), not to strictly
737  * limit the download to exactly those bytes.
738  *
739  * @param h handle to the file sharing subsystem
740  * @param uri the URI of the file (determines what to download); CHK or LOC URI
741  * @param meta known metadata for the file (can be NULL)
742  * @param filename where to store the file, maybe NULL (then no file is
743  *        created on disk and data must be grabbed from the callbacks)
744  * @param offset at what offset should we start the download (typically 0)
745  * @param length how many bytes should be downloaded starting at offset
746  * @param anonymity anonymity level to use for the download
747  * @param options various options
748  * @param parent parent download to associate this download with (use NULL
749  *        for top-level downloads; useful for manually-triggered recursive downloads)
750  * @return context that can be used to control this download
751  */
752 struct GNUNET_FS_DownloadContext *
753 GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
754                           const struct GNUNET_FS_Uri *uri,
755                           const struct GNUNET_CONTAINER_MetaData *meta,
756                           const char *filename,
757                           uint64_t offset,
758                           uint64_t length,
759                           uint32_t anonymity,
760                           enum GNUNET_FS_DownloadOptions options,
761                           struct GNUNET_FS_DownloadContext *parent)
762 {
763   struct GNUNET_FS_ProgressInfo pi;
764   struct GNUNET_FS_DownloadContext *dc;
765   struct GNUNET_CLIENT_Connection *client;
766
767   client = GNUNET_CLIENT_connect (h->sched,
768                                   "fs",
769                                   h->cfg);
770   if (NULL == client)
771     return NULL;
772   // FIXME: add support for "loc" URIs!
773   GNUNET_assert (GNUNET_FS_uri_test_chk (uri));
774   if ( (offset + length < offset) ||
775        (offset + length > uri->data.chk.file_length) )
776     {
777       GNUNET_break (0);
778       return NULL;
779     }
780 #if DEBUG_DOWNLOAD
781   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
782               "Starting download `%s' of %llu bytes\n",
783               filename,
784               (unsigned long long) length);
785 #endif
786   dc = GNUNET_malloc (sizeof(struct GNUNET_FS_DownloadContext));
787   dc->h = h;
788   dc->client = client;
789   dc->parent = parent;
790   dc->uri = GNUNET_FS_uri_dup (uri);
791   dc->meta = GNUNET_CONTAINER_meta_data_duplicate (meta);
792   if (NULL != filename)
793     {
794       dc->filename = GNUNET_strdup (filename);
795       if (GNUNET_YES == GNUNET_DISK_file_test (filename))
796         GNUNET_DISK_file_size (filename,
797                                &dc->old_file_size,
798                                GNUNET_YES);
799       dc->handle = GNUNET_DISK_file_open (filename, 
800                                           GNUNET_DISK_OPEN_READWRITE | 
801                                           GNUNET_DISK_OPEN_CREATE,
802                                           GNUNET_DISK_PERM_USER_READ |
803                                           GNUNET_DISK_PERM_USER_WRITE |
804                                           GNUNET_DISK_PERM_GROUP_READ |
805                                           GNUNET_DISK_PERM_OTHER_READ);
806       if (dc->handle == NULL)
807         {
808           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
809                       _("Download failed: could not open file `%s': %s\n"),
810                       dc->filename,
811                       STRERROR (errno));
812           GNUNET_CONTAINER_meta_data_destroy (dc->meta);
813           GNUNET_FS_uri_destroy (dc->uri);
814           GNUNET_free (dc->filename);
815           GNUNET_CLIENT_disconnect (dc->client);
816           GNUNET_free (dc);
817           return NULL;
818         }
819     }
820   // FIXME: set "dc->target" for LOC uris!
821   dc->offset = offset;
822   dc->length = length;
823   dc->anonymity = anonymity;
824   dc->options = options;
825   // dc->active = GNUNET_CONTAINER_multihashmap_create (1); // + (length / DBLOCK_SIZE));
826   dc->active = GNUNET_CONTAINER_multihashmap_create (1 + (length / DBLOCK_SIZE));
827   dc->treedepth = GNUNET_FS_compute_depth (GNUNET_ntohll(dc->uri->data.chk.file_length));
828 #if DEBUG_DOWNLOAD
829   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
830               "Download tree has depth %u\n",
831               dc->treedepth);
832 #endif
833   // FIXME: make persistent
834   schedule_block_download (dc, 
835                            &dc->uri->data.chk.chk,
836                            0, 
837                            1 /* 0 == CHK, 1 == top */);
838   GNUNET_CLIENT_receive (client,
839                          &receive_results,
840                          dc,
841                          GNUNET_TIME_UNIT_FOREVER_REL);
842   pi.status = GNUNET_FS_STATUS_DOWNLOAD_START;
843   make_download_status (&pi, dc);
844   pi.value.download.specifics.start.meta = meta;
845   dc->client_info = dc->h->upcb (dc->h->upcb_cls,
846                                  &pi);
847
848   return dc;
849 }
850
851
852 /**
853  * Free entries in the map.
854  *
855  * @param cls unused (NULL)
856  * @param key unused
857  * @param entry entry of type "struct DownloadRequest" which is freed
858  * @return GNUNET_OK
859  */
860 static int
861 free_entry (void *cls,
862             const GNUNET_HashCode *key,
863             void *entry)
864 {
865   GNUNET_free (entry);
866   return GNUNET_OK;
867 }
868
869
870 /**
871  * Stop a download (aborts if download is incomplete).
872  *
873  * @param dc handle for the download
874  * @param do_delete delete files of incomplete downloads
875  */
876 void
877 GNUNET_FS_download_stop (struct GNUNET_FS_DownloadContext *dc,
878                          int do_delete)
879 {
880   struct GNUNET_FS_ProgressInfo pi;
881
882   // FIXME: make unpersistent  
883   pi.status = GNUNET_FS_STATUS_DOWNLOAD_STOPPED;
884   make_download_status (&pi, dc);
885   dc->client_info = dc->h->upcb (dc->h->upcb_cls,
886                                  &pi);
887
888   if (GNUNET_SCHEDULER_NO_TASK != dc->task)
889     GNUNET_SCHEDULER_cancel (dc->h->sched,
890                              dc->task);
891   if (NULL != dc->th)
892     {
893       GNUNET_CONNECTION_notify_transmit_ready_cancel (dc->th);
894       dc->th = NULL;
895     }
896   if (NULL != dc->client)
897     GNUNET_CLIENT_disconnect (dc->client);
898   GNUNET_CONTAINER_multihashmap_iterate (dc->active,
899                                          &free_entry,
900                                          NULL);
901   GNUNET_CONTAINER_multihashmap_destroy (dc->active);
902   if (dc->filename != NULL)
903     {
904       if (NULL != dc->handle)
905         GNUNET_DISK_file_close (dc->handle);
906       if ( (dc->completed != dc->length) &&
907            (GNUNET_YES == do_delete) )
908         {
909           if (0 != UNLINK (dc->filename))
910             GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
911                                       "unlink",
912                                       dc->filename);
913         }
914       GNUNET_free (dc->filename);
915     }
916   GNUNET_CONTAINER_meta_data_destroy (dc->meta);
917   GNUNET_FS_uri_destroy (dc->uri);
918   GNUNET_free (dc);
919 }
920
921
922
923 #if 0
924
925
926 /**
927  * Check if self block is already present on the drive.  If the block
928  * is a dblock and present, the ProgressModel is notified. If the
929  * block is present and it is an iblock, downloading the children is
930  * triggered.
931  *
932  * Also checks if the block is within the range of blocks
933  * that we are supposed to download.  If not, the method
934  * returns as if the block is present but does NOT signal
935  * progress.
936  *
937  * @param node that is checked for presence
938  * @return GNUNET_YES if present, GNUNET_NO if not.
939  */
940 static int
941 check_node_present (const struct Node *node)
942 {
943   int res;
944   int ret;
945   char *data;
946   unsigned int size;
947   GNUNET_HashCode hc;
948
949   size = get_node_size (node);
950   /* first check if node is within range.
951      For now, keeping it simple, we only do
952      this for level-0 nodes */
953   if ((node->level == 0) &&
954       ((node->offset + size < node->ctx->offset) ||
955        (node->offset >= node->ctx->offset + node->ctx->length)))
956     return GNUNET_YES;
957   data = GNUNET_malloc (size);
958   ret = GNUNET_NO;
959   res = read_from_files (node->ctx, node->level, node->offset, data, size);
960   if (res == size)
961     {
962       GNUNET_hash (data, size, &hc);
963       if (0 == memcmp (&hc, &node->chk.key, sizeof (GNUNET_HashCode)))
964         {
965           notify_client_about_progress (node, data, size);
966           if (node->level > 0)
967             iblock_download_children (node, data, size);
968           ret = GNUNET_YES;
969         }
970     }
971   GNUNET_free (data);
972   return ret;
973 }
974
975 #endif
976
977
978 /* end of fs_download.c */