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