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