more
[oweals/gnunet.git] / src / fs / fs_publish.c
1 /*
2      This file is part of GNUnet.
3      (C) 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 /**
22  * @file fs/fs_publish.c
23  * @brief publish a file or directory in GNUnet
24  * @see http://gnunet.org/encoding.php3
25  * @author Krista Bennett
26  * @author Christian Grothoff
27  *
28  * TODO:
29  * - KBlocks
30  * - SBlocks
31  * - indexing support
32  * - calling of progress function
33  * - handling of IO errors (emsg)
34  * - code-sharing with unindex
35  * - datastore reservation support
36  * - persistence support
37  */
38
39 #include "platform.h"
40 #include "gnunet_constants.h"
41 #include "gnunet_util_lib.h"
42 #include "gnunet_fs_service.h"
43 #include "fs.h"
44
45 #define DEBUG_PUBLISH GNUNET_YES
46
47 /**
48  * Main function that performs the upload.
49  * @param cls "struct GNUNET_FS_PublishContext" identifies the upload
50  * @param tc task context
51  */
52 static void
53 do_upload (void *cls,
54            const struct GNUNET_SCHEDULER_TaskContext *tc);
55
56
57 /**
58  * Context for "ds_put_cont".
59  */
60 struct PutContCtx
61 {
62   /**
63    * Publishing context for which the datastore
64    * PUT request was executed.
65    */
66   struct GNUNET_FS_PublishContext *sc;
67
68   /**
69    * Specific file with the block.
70    */
71   struct GNUNET_FS_FileInformation *p;
72
73   /**
74    * Function to run next, if any (can be NULL).
75    */
76   GNUNET_SCHEDULER_Task cont;
77 };
78
79
80 /**
81  * Fill in all of the generic fields for 
82  * a publish event.
83  *
84  * @param pc structure to fill in
85  * @param sc overall publishing context
86  * @param p file information for the file being published
87  */
88 static void
89 make_publish_status (struct GNUNET_FS_ProgressInfo *pi,
90                      struct GNUNET_FS_PublishContext *sc,
91                      const struct GNUNET_FS_FileInformation *p)
92 {
93   pi->value.publish.sc = sc;
94   pi->value.publish.fi = p;
95   pi->value.publish.cctx
96     = p->client_info;
97   pi->value.publish.pctx
98     = (NULL == p->dir) ? NULL : p->dir->client_info;
99   pi->value.publish.size
100     = (p->is_directory) ? p->data.dir.dir_size : p->data.file.file_size;
101   pi->value.publish.eta 
102     = GNUNET_TIME_calculate_eta (p->start_time,
103                                  p->publish_offset,
104                                  pi->value.publish.size);
105   pi->value.publish.duration = GNUNET_TIME_absolute_get_duration (p->start_time);
106   pi->value.publish.completed = p->publish_offset;
107   pi->value.publish.anonymity = p->anonymity;
108 }
109
110
111 /**
112  * Function called by the datastore API with
113  * the result from the PUT request.
114  *
115  * @param cls our closure
116  * @param success GNUNET_OK on success
117  * @param msg error message (or NULL)
118  */
119 static void
120 ds_put_cont (void *cls,
121              int success,
122              const char *msg)
123 {
124   struct PutContCtx *pcc = cls;
125   struct GNUNET_FS_ProgressInfo pi;
126
127   if (GNUNET_OK != success)
128     {
129       GNUNET_asprintf (&pcc->p->emsg, 
130                        _("Upload failed: %s"),
131                        msg);
132       GNUNET_FS_file_information_sync (pcc->p);
133       pi.status = GNUNET_FS_STATUS_PUBLISH_ERROR;
134       make_publish_status (&pi, pcc->sc, pcc->p);
135       pi.value.publish.eta = GNUNET_TIME_UNIT_FOREVER_REL;
136       pi.value.publish.specifics.error.message = pcc->p->emsg;
137       pcc->sc->h->upcb (pcc->sc->h->upcb_cls,
138                         &pi);
139       return;
140     }
141   GNUNET_FS_file_information_sync (pcc->p);
142   if (NULL != pcc->cont)
143     pcc->sc->upload_task 
144       = GNUNET_SCHEDULER_add_delayed (pcc->sc->h->sched,
145                                       GNUNET_NO,
146                                       GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
147                                       GNUNET_SCHEDULER_NO_TASK,
148                                       GNUNET_TIME_UNIT_ZERO,
149                                       pcc->cont,
150                                       pcc->sc);
151   GNUNET_free (pcc);
152 }
153
154
155 /**
156  * We need to publish a specific block.  Do it.  Then continue with
157  * the main task.
158  *
159  * @param sc overall upload data
160  * @param p file that the block belongs to (needed for options!)
161  * @param query what the block should be indexed under
162  * @param blk encoded block to publish
163  * @param blk_size size of the block
164  * @param blk_type type of the block
165  * @param cont function to run when done
166  */
167 static void
168 publish_block (struct GNUNET_FS_PublishContext *sc,
169                struct GNUNET_FS_FileInformation *p,
170                const GNUNET_HashCode *query,
171                const void* blk,
172                uint16_t blk_size,
173                uint32_t blk_type,
174                GNUNET_SCHEDULER_Task cont)
175 {
176   struct PutContCtx * dpc_cls;
177
178   dpc_cls = GNUNET_malloc(sizeof(struct PutContCtx));
179   dpc_cls->cont = cont;
180   dpc_cls->sc = sc;
181   dpc_cls->p = p;
182   // FIXME: need to do something to "sc" to mark
183   // that "sc" can not be freed right now due to this
184   // pending, scheduled operation for which we don't have
185   // a task ID!  
186   GNUNET_DATASTORE_put (sc->dsh,
187                         sc->rid,
188                         query,
189                         blk_size,
190                         blk,
191                         blk_type,
192                         p->priority,
193                         p->anonymity,
194                         p->expirationTime,
195                         GNUNET_CONSTANTS_SERVICE_TIMEOUT,
196                         &ds_put_cont,
197                         dpc_cls);
198 }
199
200
201 /**
202  * Generate the callback that signals clients
203  * that a file (or directory) has been completely
204  * published.
205  *
206  * @param p the completed upload
207  * @param sc context of the publication
208  */
209 static void 
210 signal_publish_completion (struct GNUNET_FS_FileInformation *p,
211                            struct GNUNET_FS_PublishContext *sc)
212 {
213   struct GNUNET_FS_ProgressInfo pi;
214   
215   pi.status = GNUNET_FS_STATUS_PUBLISH_COMPLETED;
216   make_publish_status (&pi, sc, p);
217   pi.value.publish.eta = GNUNET_TIME_UNIT_ZERO;
218   pi.value.publish.specifics.completed.chk_uri = p->chk_uri;
219   sc->h->upcb (sc->h->upcb_cls,
220                &pi);
221 }
222
223
224 /**
225  * We are almost done publishing the structure,
226  * add SBlocks (if needed).
227  *
228  * @param sc overall upload data
229  */
230 static void
231 publish_sblock (struct GNUNET_FS_PublishContext *sc)
232 {
233   struct GNUNET_FS_FileInformation *p;
234   p = sc->fi;
235
236   // FIXME: build sblock & call publish_block!
237   
238   // FIXME: continuation should
239   // be releasing the datastore reserve
240   // (once implemented)
241   // FIXME: finally, signal overall completion
242   signal_publish_completion (p, sc);
243 }
244
245
246 /**
247  * We have uploaded a file or directory; now publish
248  * the KBlocks in the global keyword space so that
249  * it can be found.  Then continue with the
250  * main task.
251  *
252  * @param sc overall upload data
253  * @param p specific file or directory for which kblocks
254  *          should be created
255  */
256 static void
257 publish_kblocks (struct GNUNET_FS_PublishContext *sc,
258                  struct GNUNET_FS_FileInformation *p)
259 {
260   // FIXME: build all kblocks
261   // call publish_kblock on each
262
263
264   GNUNET_FS_file_information_sync (p);
265   if (NULL != p->dir)
266     signal_publish_completion (p, sc);
267
268   // last continuation should then call the main continuation again
269 }
270
271
272 /**
273  * Compute the depth of the CHK tree.
274  *
275  * @param flen file length for which to compute the depth
276  * @return depth of the tree
277  */
278 static unsigned int
279 compute_depth (uint64_t flen)
280 {
281   unsigned int treeDepth;
282   uint64_t fl;
283
284   treeDepth = 1;
285   fl = GNUNET_FS_DBLOCK_SIZE;
286   while (fl < flen)
287     {
288       treeDepth++;
289       if (fl * GNUNET_FS_CHK_PER_INODE < fl)
290         {
291           /* integer overflow, this is a HUGE file... */
292           return treeDepth;
293         }
294       fl = fl * GNUNET_FS_CHK_PER_INODE;
295     }
296   return treeDepth;
297 }
298
299
300 /**
301  * Compute the size of the current IBlock.
302  *
303  * @param height height of the IBlock in the tree (aka overall
304  *               number of tree levels minus depth); 0 == DBlock
305  * @param offset current offset in the overall file
306  * @return size of the corresponding IBlock
307  */
308 static uint16_t 
309 compute_iblock_size (unsigned int height,
310                      uint64_t offset)
311 {
312   unsigned int ret;
313   unsigned int i;
314   uint64_t mod;
315   uint64_t bds;
316
317   GNUNET_assert (height > 0);
318   bds = GNUNET_FS_DBLOCK_SIZE; /* number of bytes each CHK at level "i"
319                                   corresponds to */
320   for (i=0;i<height;i++)
321     bds *= GNUNET_FS_CHK_PER_INODE;
322   mod = offset % bds;
323   if (0 == mod)
324     {
325       /* we were triggered at the end of a full block */
326       ret = GNUNET_FS_CHK_PER_INODE;
327     }
328   else
329     {
330       /* we were triggered at the end of the file */
331       bds /= GNUNET_FS_CHK_PER_INODE;
332       ret = mod / bds;
333       if (0 != mod % bds)
334         ret++; 
335     }
336   return (uint16_t) (ret * sizeof(struct ContentHashKey));
337 }
338
339
340 /**
341  * Compute the offset of the CHK for the
342  * current block in the IBlock above.
343  *
344  * @param height height of the IBlock in the tree (aka overall
345  *               number of tree levels minus depth); 0 == DBlock
346  * @param offset current offset in the overall file
347  * @return (array of CHKs') offset in the above IBlock
348  */
349 static unsigned int
350 compute_chk_offset (unsigned int height,
351                     uint64_t offset)
352 {
353   uint64_t bds;
354   unsigned int ret;
355   unsigned int i;
356
357   bds = GNUNET_FS_DBLOCK_SIZE; /* number of bytes each CHK at level "i"
358                                   corresponds to */
359   for (i=0;i<height;i++)
360     bds *= GNUNET_FS_CHK_PER_INODE;
361   GNUNET_assert (0 == (offset % bds));
362   ret = offset / bds;
363   return ret % GNUNET_FS_CHK_PER_INODE; 
364 }
365
366
367 /**
368  * We are uploading a file or directory; load (if necessary) the next
369  * block into memory, encrypt it and send it to the FS service.  Then
370  * continue with the main task.
371  *
372  * @param sc overall upload data
373  * @param p specific file or directory for which kblocks
374  *          should be created
375  */
376 static void
377 publish_content (struct GNUNET_FS_PublishContext *sc,
378                  struct GNUNET_FS_FileInformation *p)
379 {
380   struct GNUNET_FS_ProgressInfo pi;
381   struct ContentHashKey *mychk;
382   const void *pt_block;
383   uint16_t pt_size;
384   char *emsg;
385   char iob[GNUNET_FS_DBLOCK_SIZE];
386   char enc[GNUNET_FS_DBLOCK_SIZE];
387   struct GNUNET_CRYPTO_AesSessionKey sk;
388   struct GNUNET_CRYPTO_AesInitializationVector iv;
389   uint64_t size;
390   unsigned int off;
391   struct GNUNET_FS_DirectoryBuilder *db;
392   struct GNUNET_FS_FileInformation *dirpos;
393   void *raw_data;
394   char *dd;
395
396   // FIXME: figure out how to share this code
397   // with unindex!
398   size = (p->is_directory) ? p->data.dir.dir_size : p->data.file.file_size;
399   if (NULL == p->chk_tree)
400     {
401       if (p->is_directory)
402         {
403           db = GNUNET_FS_directory_builder_create (p->meta);
404           dirpos = p->data.dir.entries;
405           while (NULL != dirpos)
406             {
407               if (dirpos->is_directory)
408                 {
409                   raw_data = dirpos->data.dir.dir_data;
410                   dirpos->data.dir.dir_data = NULL;
411                 }
412               else
413                 {
414                   raw_data = NULL;
415                   if ( (dirpos->data.file.file_size < GNUNET_FS_MAX_INLINE_SIZE) &&
416                        (dirpos->data.file.file_size > 0) )
417                     {
418                       raw_data = GNUNET_malloc (dirpos->data.file.file_size);
419                       emsg = NULL;
420                       if (dirpos->data.file.file_size !=
421                           dirpos->data.file.reader (dirpos->data.file.reader_cls,
422                                                     0,
423                                                     dirpos->data.file.file_size,
424                                                     raw_data,
425                                                     &emsg))
426                         {
427                           GNUNET_free_non_null (emsg);
428                           GNUNET_free (raw_data);
429                           raw_data = NULL;
430                         } 
431                     }
432                 }
433               GNUNET_FS_directory_builder_add (db,
434                                                dirpos->chk_uri,
435                                                dirpos->meta,
436                                                raw_data);
437               GNUNET_free_non_null (raw_data);
438               dirpos = dirpos->next;
439             }
440           GNUNET_FS_directory_builder_finish (db,
441                                               &p->data.dir.dir_size,
442                                               &p->data.dir.dir_data);
443           size = p->data.dir.dir_size;
444         }
445       p->chk_tree_depth = compute_depth (size);
446       p->chk_tree = GNUNET_malloc (p->chk_tree_depth * 
447                                    sizeof (struct ContentHashKey) *
448                                    GNUNET_FS_CHK_PER_INODE);
449       p->current_depth = p->chk_tree_depth;
450     }
451   if (p->current_depth == p->chk_tree_depth)
452     {
453       if (p->is_directory)
454         {
455           pt_size = GNUNET_MIN(GNUNET_FS_DBLOCK_SIZE,
456                                p->data.dir.dir_size - p->publish_offset);
457           dd = p->data.dir.dir_data;
458           pt_block = &dd[p->publish_offset];
459         }
460       else
461         {
462           pt_size = GNUNET_MIN(GNUNET_FS_DBLOCK_SIZE,
463                                p->data.file.file_size - p->publish_offset);
464           emsg = NULL;
465           if (pt_size !=
466               p->data.file.reader (p->data.file.reader_cls,
467                                    p->publish_offset,
468                                    pt_size,
469                                    iob,
470                                    &emsg))
471             {
472               GNUNET_asprintf (&p->emsg, 
473                                _("Upload failed: %s"),
474                                emsg);
475               GNUNET_free (emsg);
476               GNUNET_FS_file_information_sync (p);
477               pi.status = GNUNET_FS_STATUS_PUBLISH_ERROR;
478               make_publish_status (&pi, sc, p);
479               pi.value.publish.eta = GNUNET_TIME_UNIT_FOREVER_REL;
480               pi.value.publish.specifics.error.message = p->emsg;
481               sc->h->upcb (sc->h->upcb_cls,
482                            &pi);
483               /* continue with main (to propagate error up) */
484               sc->upload_task 
485                 = GNUNET_SCHEDULER_add_delayed (sc->h->sched,
486                                                 GNUNET_NO,
487                                                 GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
488                                                 GNUNET_SCHEDULER_NO_TASK,
489                                                 GNUNET_TIME_UNIT_ZERO,
490                                                 &do_upload,
491                                                 sc);
492               return;
493             }
494           pt_block = iob;
495         }
496     }
497   else
498     {
499       pt_size = compute_iblock_size (p->chk_tree_depth - p->current_depth,
500                                      p->publish_offset); 
501       pt_block = &p->chk_tree[p->current_depth *
502                               GNUNET_FS_CHK_PER_INODE];
503     }
504   off = compute_chk_offset (p->chk_tree_depth - p->current_depth,
505                             p->publish_offset);
506   mychk = &p->chk_tree[(p->current_depth-1)*GNUNET_FS_CHK_PER_INODE+off];
507   GNUNET_CRYPTO_hash (pt_block, pt_size, &mychk->key);
508   GNUNET_CRYPTO_hash_to_aes_key (&mychk->key, &sk, &iv);
509   GNUNET_CRYPTO_aes_encrypt (pt_block,
510                              pt_size,
511                              &sk,
512                              &iv,
513                              enc);
514   // NOTE: this call (and progress below) is all that really differs
515   // between publish/unindex!  Parameterize & move this code!
516   // FIXME: something around here would need to change
517   // for indexing!
518   publish_block (sc, p, 
519                  &mychk->query,
520                  enc, 
521                  pt_size, 
522                  (p->current_depth == p->chk_tree_depth) 
523                  ? GNUNET_DATASTORE_BLOCKTYPE_DBLOCK 
524                  : GNUNET_DATASTORE_BLOCKTYPE_IBLOCK,
525                  &do_upload);
526   if (p->current_depth == p->chk_tree_depth)
527     {
528       pi.status = GNUNET_FS_STATUS_PUBLISH_PROGRESS;
529       make_publish_status (&pi, sc, p);
530       pi.value.publish.specifics.progress.data = pt_block;
531       pi.value.publish.specifics.progress.offset = p->publish_offset;
532       pi.value.publish.specifics.progress.data_len = pt_size;
533       sc->h->upcb (sc->h->upcb_cls,
534                         &pi);
535     }
536   GNUNET_CRYPTO_hash (enc, pt_size, &mychk->query);
537   if (p->current_depth == p->chk_tree_depth) 
538     { 
539       p->publish_offset += pt_size;
540       if ( (p->publish_offset == size) ||
541            (0 == p->publish_offset % (GNUNET_FS_CHK_PER_INODE * GNUNET_FS_DBLOCK_SIZE) ) )
542         p->current_depth--;
543     }
544   else
545     {
546       if ( (off == GNUNET_FS_CHK_PER_INODE) ||
547            (p->publish_offset == size) )
548         p->current_depth--;
549       else
550         p->current_depth = p->chk_tree_depth;
551     }
552   if (0 == p->current_depth)
553     {
554       p->chk_uri = GNUNET_malloc (sizeof(struct GNUNET_FS_Uri));
555       p->chk_uri->type = chk;
556       p->chk_uri->data.chk.chk = p->chk_tree[0];
557       p->chk_uri->data.chk.file_length = size;
558       GNUNET_free (p->chk_tree);
559       p->chk_tree = NULL;
560     }
561 }
562
563
564 /**
565  * Main function that performs the upload.
566  * @param cls "struct GNUNET_FS_PublishContext" identifies the upload
567  * @param tc task context
568  */
569 static void
570 do_upload (void *cls,
571            const struct GNUNET_SCHEDULER_TaskContext *tc)
572 {
573   struct GNUNET_FS_PublishContext *sc = cls;
574   struct GNUNET_FS_ProgressInfo pi;
575   struct GNUNET_FS_FileInformation *p;
576   char *fn;
577
578   sc->upload_task = GNUNET_SCHEDULER_NO_TASK;  
579   p = sc->fi_pos;
580   if (NULL == p)
581     {
582       /* upload of entire hierarchy complete,
583          publish namespace entries */
584       publish_sblock (sc);
585       return;
586     }
587   if (NULL != p->emsg)
588     {
589       /* error with current file, abort all
590          related files as well! */
591       while (NULL != p->dir)
592         {
593           fn = GNUNET_CONTAINER_meta_data_get_by_type (p->meta,
594                                                        EXTRACTOR_FILENAME);
595           p = p->dir;
596           GNUNET_asprintf (&p->emsg, 
597                            _("Recursive upload failed at `%s'"),
598                            fn);
599           GNUNET_free (fn);
600           GNUNET_FS_file_information_sync (p);
601           pi.status = GNUNET_FS_STATUS_PUBLISH_ERROR;
602           make_publish_status (&pi, sc, p);
603           pi.value.publish.eta = GNUNET_TIME_UNIT_FOREVER_REL;
604           pi.value.publish.specifics.error.message = p->emsg;
605           sc->h->upcb (sc->h->upcb_cls,
606                        &pi);
607         }
608       return;
609     }
610   if (NULL != p->chk_uri)
611     {
612       /* move on to next file */
613       if (NULL != p->next)
614         sc->fi_pos = p->next;
615       else
616         sc->fi_pos = p->dir;
617       /* upload of "p" complete, publish KBlocks! */
618       publish_kblocks (sc, p);
619       return;
620     }
621   if ( (!p->is_directory) &&
622        (p->data.file.do_index) )
623     {
624       // FIXME: need to pre-compute hash over
625       // the entire file and ask FS to prepare
626       // for indexing!
627       return;
628     }
629   publish_content (sc, p);
630 }
631
632
633 /**
634  * Publish a file or directory.
635  *
636  * @param h handle to the file sharing subsystem
637  * @param ctx initial value to use for the '*ctx'
638  *        in the callback (for the GNUNET_FS_STATUS_PUBLISH_START event).
639  * @param fi information about the file or directory structure to publish
640  * @param namespace namespace to publish the file in, NULL for no namespace
641  * @param nid identifier to use for the publishd content in the namespace
642  *        (can be NULL, must be NULL if namespace is NULL)
643  * @param nuid update-identifier that will be used for future updates 
644  *        (can be NULL, must be NULL if namespace or nid is NULL)
645  * @return context that can be used to control the publish operation
646  */
647 struct GNUNET_FS_PublishContext *
648 GNUNET_FS_publish_start (struct GNUNET_FS_Handle *h,
649                          void *ctx,
650                          struct GNUNET_FS_FileInformation *fi,
651                          struct GNUNET_FS_Namespace *namespace,
652                          const char *nid,
653                          const char *nuid)
654 {
655   struct GNUNET_FS_PublishContext *ret;
656   struct GNUNET_FS_FileInformation *p;
657   struct GNUNET_DATASTORE_Handle *dsh;
658
659   dsh = GNUNET_DATASTORE_connect (h->cfg,
660                                   h->sched);
661   if (NULL == dsh)
662     return NULL;
663   ret = GNUNET_malloc (sizeof (struct GNUNET_FS_PublishContext));
664   ret->dsh = dsh;
665   ret->h = h;
666   ret->client_ctx = ctx;
667   ret->fi = fi;
668   ret->namespace = namespace;
669   if (namespace != NULL)
670     {
671       namespace->rc++;
672       GNUNET_assert (NULL != nid);
673       ret->nid = GNUNET_strdup (nid);
674       if (NULL != nuid)
675         ret->nuid = GNUNET_strdup (nuid);
676     }
677   // FIXME: make upload persistent!
678
679   /* signal start */
680   
681
682   /* find first leaf, DFS */
683   p = ret->fi;
684   while ( (p->is_directory) &&
685           (NULL != p->data.dir.entries) )
686     p = p->data.dir.entries;          
687   ret->fi_pos = p;
688
689   // FIXME: calculate space needed for "fi"
690   // and reserve as first task (then trigger
691   // "do_upload" from that continuation)!
692   ret->upload_task 
693     = GNUNET_SCHEDULER_add_delayed (h->sched,
694                                     GNUNET_NO,
695                                     GNUNET_SCHEDULER_PRIORITY_BACKGROUND,
696                                     GNUNET_SCHEDULER_NO_TASK,
697                                     GNUNET_TIME_UNIT_ZERO,
698                                     &do_upload,
699                                     ret);
700   return ret;
701 }
702
703
704 /**
705  * Stop an upload.  Will abort incomplete uploads (but 
706  * not remove blocks that have already been publishd) or
707  * simply clean up the state for completed uploads.
708  *
709  * @param sc context for the upload to stop
710  */
711 void 
712 GNUNET_FS_publish_stop (struct GNUNET_FS_PublishContext *sc)
713 {
714   if (GNUNET_SCHEDULER_NO_TASK != sc->upload_task)
715     GNUNET_SCHEDULER_cancel (sc->h->sched, sc->upload_task);
716   // FIXME: remove from persistence DB (?) --- think more about
717   //        shutdown / persistent-resume APIs!!!
718   GNUNET_FS_file_information_destroy (sc->fi, NULL, NULL);
719   GNUNET_FS_namespace_delete (sc->namespace, GNUNET_NO);
720   GNUNET_free_non_null (sc->nid);  
721   GNUNET_free_non_null (sc->nuid);
722   GNUNET_DATASTORE_disconnect (sc->dsh, GNUNET_NO);
723   GNUNET_free (sc);
724 }
725
726 /* end of fs_publish.c */