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