notes
[oweals/gnunet.git] / src / fs / fs_unindex.c
1 /*
2      This file is part of GNUnet.
3      (C) 2003, 2004, 2006, 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_unindex.c
23  * @author Krista Bennett
24  * @author Christian Grothoff
25  * @brief Unindex file.
26  *
27  * TODO:
28  * - code cleanup (share more with upload.c)
29  */
30
31 #include "platform.h"
32 #include "gnunet_constants.h"
33 #include "gnunet_fs_service.h"
34 #include "gnunet_protocols.h"
35 #include "fs.h"
36
37
38 /**
39  * Context for "GNUNET_FS_get_indexed_files".
40  */
41 struct GetIndexedContext
42 {
43   /**
44    * Handle to global FS context.
45    */
46   struct GNUNET_FS_Handle *h;
47
48   /**
49    * Connection to the FS service.
50    */
51   struct GNUNET_CLIENT_Connection *client;
52
53   /**
54    * Function to call for each indexed file.
55    */
56   GNUNET_FS_IndexedFileProcessor iterator;
57
58   /**
59    * Closure for iterator.
60    */
61   void *iterator_cls;
62
63   /**
64    * Continuation to trigger at the end.
65    */
66   GNUNET_SCHEDULER_Task cont;
67
68   /**
69    * Closure for cont.
70    */
71   void *cont_cls;
72 };
73
74
75 /**
76  * Function called on each response from the FS
77  * service with information about indexed files.
78  *
79  * @param cls closure (of type "struct GetIndexedContext*")
80  * @param msg message with indexing information
81  */
82 static void
83 handle_index_info (void *cls,
84                    const struct GNUNET_MessageHeader *msg)
85 {
86   struct GetIndexedContext *gic = cls;
87   const struct IndexInfoMessage *iim;
88   uint16_t msize;
89   const char *filename;
90
91   if (NULL == msg)
92     {
93       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
94                   _("Failed to receive response for `%s' request from `%s' service.\n"),
95                   "GET_INDEXED",
96                   "fs");
97       GNUNET_SCHEDULER_add_continuation (gic->h->sched,
98                                          GNUNET_NO,
99                                          gic->cont,
100                                          gic->cont_cls,
101                                          GNUNET_SCHEDULER_REASON_TIMEOUT);
102       GNUNET_CLIENT_disconnect (gic->client);
103       GNUNET_free (gic);
104       return;
105     }
106   if (ntohs (msg->type) == GNUNET_MESSAGE_TYPE_FS_INDEX_LIST_END)
107     {
108       /* normal end-of-list */
109       GNUNET_SCHEDULER_add_continuation (gic->h->sched,
110                                          GNUNET_NO,
111                                          gic->cont,
112                                          gic->cont_cls,
113                                          GNUNET_SCHEDULER_REASON_PREREQ_DONE);
114       GNUNET_CLIENT_disconnect (gic->client);
115       GNUNET_free (gic);
116       return;
117     }
118   msize = ntohs (msg->size);
119   iim = (const struct IndexInfoMessage*) msg;
120   filename = (const char*) &iim[1];
121   if ( (ntohs (msg->type) != GNUNET_MESSAGE_TYPE_FS_INDEX_LIST_ENTRY) ||
122        (msize <= sizeof (struct IndexInfoMessage)) ||
123        (filename[msize-sizeof (struct IndexInfoMessage) -1] != '\0') )
124     {
125       /* bogus reply */
126       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
127                   _("Failed to receive valid response for `%s' request from `%s' service.\n"),
128                   "GET_INDEXED",
129                   "fs");
130       GNUNET_SCHEDULER_add_continuation (gic->h->sched,
131                                          GNUNET_NO,
132                                          gic->cont,
133                                          gic->cont_cls,
134                                          GNUNET_SCHEDULER_REASON_TIMEOUT);
135       GNUNET_CLIENT_disconnect (gic->client);
136       GNUNET_free (gic);
137       return;
138     }
139   if (GNUNET_OK !=
140       gic->iterator (gic->iterator_cls,
141                      filename,
142                      &iim->file_id))
143     {
144       GNUNET_SCHEDULER_add_continuation (gic->h->sched,
145                                          GNUNET_NO,
146                                          gic->cont,
147                                          gic->cont_cls,
148                                          GNUNET_SCHEDULER_REASON_PREREQ_DONE);
149       GNUNET_CLIENT_disconnect (gic->client);
150       GNUNET_free (gic);
151       return;
152     }
153   /* get more */
154   GNUNET_CLIENT_receive (gic->client,
155                          &handle_index_info,
156                          gic,
157                          GNUNET_CONSTANTS_SERVICE_TIMEOUT);  
158 }
159
160
161 /**
162  * Transmit the request to get a list of all 
163  * indexed files to the "FS" service.
164  *
165  * @param cls closure (of type "struct GetIndexedContext*")
166  * @param size number of bytes availabe in buf
167  * @param buf where to write the message, NULL on error
168  * @return number of bytes written to buf
169  */
170 static size_t
171 transmit_get_indexed (void *cls,
172                       size_t size,
173                       void *buf)
174 {
175   struct GetIndexedContext *gic = cls;
176   struct GNUNET_MessageHeader *hdr;
177
178   if (NULL == buf)
179     {
180       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
181                   _("Failed to transmit `%s' request to `%s' service.\n"),
182                   "GET_INDEXED",
183                   "fs");
184       GNUNET_SCHEDULER_add_continuation (gic->h->sched,
185                                          GNUNET_NO,
186                                          gic->cont,
187                                          gic->cont_cls,
188                                          GNUNET_SCHEDULER_REASON_TIMEOUT);
189       GNUNET_CLIENT_disconnect (gic->client);
190       GNUNET_free (gic);
191       return 0;
192     }
193   GNUNET_assert (size >= sizeof (struct GNUNET_MessageHeader));
194   hdr = buf;
195   hdr->size = htons (sizeof (struct GNUNET_MessageHeader));
196   hdr->type = htons (GNUNET_MESSAGE_TYPE_FS_INDEX_LIST_GET);
197   GNUNET_CLIENT_receive (gic->client,
198                          &handle_index_info,
199                          gic,
200                          GNUNET_CONSTANTS_SERVICE_TIMEOUT);
201   return sizeof (struct GNUNET_MessageHeader);
202 }
203
204
205 /**
206  * Iterate over all indexed files.
207  *
208  * @param h handle to the file sharing subsystem
209  * @param iterator function to call on each indexed file
210  * @param iterator_cls closure for iterator
211  * @param cont continuation to call when done;
212  *             reason should be "TIMEOUT" (on
213  *             error) or  "PREREQ_DONE" (on success)
214  * @param cont_cls closure for cont
215  */
216 void 
217 GNUNET_FS_get_indexed_files (struct GNUNET_FS_Handle *h,
218                              GNUNET_FS_IndexedFileProcessor iterator,
219                              void *iterator_cls,
220                              GNUNET_SCHEDULER_Task cont,
221                              void *cont_cls)
222 {
223   struct GNUNET_CLIENT_Connection *client;
224   struct GetIndexedContext *gic;
225
226   client = GNUNET_CLIENT_connect (h->sched,
227                                   "fs",
228                                   h->cfg);
229   if (NULL == client)
230     {
231       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
232                   _("Failed to not connect to `%s' service.\n"),
233                   "fs");
234       GNUNET_SCHEDULER_add_continuation (h->sched,
235                                          GNUNET_NO,
236                                          cont,
237                                          cont_cls,
238                                          GNUNET_SCHEDULER_REASON_TIMEOUT);
239       return;
240     }
241
242   gic = GNUNET_malloc (sizeof (struct GetIndexedContext));
243   gic->h = h;
244   gic->client = client;
245   gic->iterator = iterator;
246   gic->iterator_cls = iterator_cls;
247   gic->cont = cont;
248   gic->cont_cls = cont_cls;
249   GNUNET_CLIENT_notify_transmit_ready (client,
250                                        sizeof (struct GNUNET_MessageHeader),
251                                        GNUNET_CONSTANTS_SERVICE_TIMEOUT,
252                                        &transmit_get_indexed,
253                                        gic);
254 }
255
256
257 /**
258  * Unindex a file.
259  *
260  * @param h handle to the file sharing subsystem
261  * @param filename file to unindex
262  * @return NULL on error, otherwise handle 
263  */
264 struct GNUNET_FS_UnindexContext *
265 GNUNET_FS_unindex (struct GNUNET_FS_Handle *h,
266                    const char *filename)
267 {
268   // 1: compute file-id (hash over entire file)
269   // 2: notify FS service about file no longer being indexed
270   // 3: remove corresponding blocks from datastore!
271   return NULL;
272 }
273
274
275 /**
276  * Clean up after completion of an unindex operation.
277  *
278  * @param uc handle
279  */
280 void
281 GNUNET_FS_unindex_stop (struct GNUNET_FS_UnindexContext *uc)
282 {
283 }
284
285
286
287 #if 0
288
289 #define STRICT_CHECKS GNUNET_NO
290
291 /**
292  * Append the given key and query to the iblock[level].
293  * If iblock[level] is already full, compute its chk
294  * and push it to level+1.  iblocks is guaranteed to
295  * be big enough.
296  *
297  * This function matches exactly upload.c::pushBlock,
298  * except in the call to 'GNUNET_FS_delete'.  TODO: refactor
299  * to avoid code duplication (move to block.c, pass
300  * GNUNET_FS_delete as argument!).
301  */
302 static int
303 pushBlock (struct GNUNET_ClientServerConnection *sock,
304            const GNUNET_EC_ContentHashKey * chk, unsigned int level,
305            GNUNET_DatastoreValue ** iblocks)
306 {
307   unsigned int size;
308   unsigned int present;
309   GNUNET_DatastoreValue *value;
310   GNUNET_EC_DBlock *db;
311   GNUNET_EC_ContentHashKey ichk;
312
313   size = ntohl (iblocks[level]->size) - sizeof (GNUNET_DatastoreValue);
314   present =
315     (size - sizeof (GNUNET_EC_DBlock)) / sizeof (GNUNET_EC_ContentHashKey);
316   db = (GNUNET_EC_DBlock *) & iblocks[level][1];
317   if (present == GNUNET_ECRS_CHK_PER_INODE)
318     {
319       GNUNET_EC_file_block_get_key (db, size, &ichk.key);
320       GNUNET_EC_file_block_get_query (db, size, &ichk.query);
321       if (GNUNET_OK != pushBlock (sock, &ichk, level + 1, iblocks))
322         {
323           GNUNET_GE_BREAK (NULL, 0);
324           return GNUNET_SYSERR;
325         }
326       GNUNET_EC_file_block_encode (db, size, &ichk.query, &value);
327 #if STRICT_CHECKS
328       if (GNUNET_SYSERR == GNUNET_FS_delete (sock, value))
329         {
330           GNUNET_free (value);
331           GNUNET_GE_BREAK (NULL, 0);
332           return GNUNET_SYSERR;
333         }
334 #else
335       GNUNET_FS_delete (sock, value);
336 #endif
337       GNUNET_free (value);
338       size = sizeof (GNUNET_EC_DBlock);
339     }
340   /* append GNUNET_EC_ContentHashKey */
341   memcpy (&((char *) db)[size], chk, sizeof (GNUNET_EC_ContentHashKey));
342   iblocks[level]->size = htonl (size +
343                                 sizeof (GNUNET_EC_ContentHashKey) +
344                                 sizeof (GNUNET_DatastoreValue));
345   return GNUNET_OK;
346 }
347
348
349
350 /**
351  * Undo sym-linking operation:
352  * a) check if we have a symlink
353  * b) delete symbolic link
354  */
355 static int
356 undoSymlinking (struct GNUNET_GE_Context *ectx,
357                 const char *fn,
358                 const GNUNET_HashCode * fileId,
359                 struct GNUNET_ClientServerConnection *sock)
360 {
361   GNUNET_EncName enc;
362   char *serverDir;
363   char *serverFN;
364   struct stat buf;
365
366 #ifndef S_ISLNK
367   if (1)
368     return GNUNET_OK;           /* symlinks do not exist? */
369 #endif
370   if (0 != LSTAT (fn, &buf))
371     {
372       GNUNET_GE_LOG_STRERROR_FILE (ectx,
373                                    GNUNET_GE_ERROR | GNUNET_GE_BULK |
374                                    GNUNET_GE_USER | GNUNET_GE_ADMIN, "stat",
375                                    fn);
376       return GNUNET_SYSERR;
377     }
378 #ifdef S_ISLNK
379   if (!S_ISLNK (buf.st_mode))
380     return GNUNET_OK;
381 #endif
382   serverDir =
383     GNUNET_get_daemon_configuration_value (sock, "FS", "INDEX-DIRECTORY");
384   if (serverDir == NULL)
385     return GNUNET_OK;
386   serverFN = GNUNET_malloc (strlen (serverDir) + 2 + sizeof (GNUNET_EncName));
387   strcpy (serverFN, serverDir);
388   GNUNET_free (serverDir);
389   if (serverFN[strlen (serverFN) - 1] != DIR_SEPARATOR)
390     strcat (serverFN, DIR_SEPARATOR_STR);
391   GNUNET_hash_to_enc (fileId, &enc);
392   strcat (serverFN, (char *) &enc);
393
394   if (0 != UNLINK (serverFN))
395     {
396       GNUNET_GE_LOG_STRERROR_FILE (ectx,
397                                    GNUNET_GE_ERROR | GNUNET_GE_BULK |
398                                    GNUNET_GE_USER | GNUNET_GE_ADMIN, "unlink",
399                                    serverFN);
400       GNUNET_free (serverFN);
401       return GNUNET_SYSERR;
402     }
403   GNUNET_free (serverFN);
404   return GNUNET_OK;
405 }
406
407
408
409 /**
410  * Unindex a file.
411  *
412  * @return GNUNET_SYSERR if the unindexing failed (i.e. not indexed)
413  */
414 int
415 GNUNET_ECRS_file_unindex (struct GNUNET_GE_Context *ectx,
416                           struct GNUNET_GC_Configuration *cfg,
417                           const char *filename,
418                           GNUNET_ECRS_UploadProgressCallback upcb,
419                           void *upcbClosure, GNUNET_ECRS_TestTerminate tt,
420                           void *ttClosure)
421 {
422   unsigned long long filesize;
423   unsigned long long pos;
424   unsigned int treedepth;
425   int fd;
426   int i;
427   unsigned int size;
428   GNUNET_DatastoreValue **iblocks;
429   GNUNET_DatastoreValue *dblock;
430   GNUNET_EC_DBlock *db;
431   GNUNET_DatastoreValue *value;
432   struct GNUNET_ClientServerConnection *sock;
433   GNUNET_HashCode fileId;
434   GNUNET_EC_ContentHashKey chk;
435   GNUNET_CronTime eta;
436   GNUNET_CronTime start;
437   GNUNET_CronTime now;
438   int wasIndexed;
439
440   start = GNUNET_get_time ();
441   if (GNUNET_YES != GNUNET_disk_file_test (ectx, filename))
442     {
443       GNUNET_GE_BREAK (ectx, 0);
444       return GNUNET_SYSERR;
445     }
446   if (GNUNET_OK !=
447       GNUNET_disk_file_size (ectx, filename, &filesize, GNUNET_YES))
448     return GNUNET_SYSERR;
449   sock = GNUNET_client_connection_create (ectx, cfg);
450   if (sock == NULL)
451     return GNUNET_SYSERR;
452   eta = 0;
453   if (upcb != NULL)
454     upcb (filesize, 0, eta, upcbClosure);
455   if (GNUNET_SYSERR == GNUNET_hash_file (ectx, filename, &fileId))
456     {
457       GNUNET_client_connection_destroy (sock);
458       GNUNET_GE_BREAK (ectx, 0);
459       return GNUNET_SYSERR;
460     }
461   now = GNUNET_get_time ();
462   eta = now + 2 * (now - start);
463   /* very rough estimate: GNUNET_hash reads once through the file,
464      we'll do that once more and write it.  But of course
465      the second read may be cached, and we have the encryption,
466      so a factor of two is really, really just a rough estimate */
467   start = now;
468   /* reset the counter since the formula later does not
469      take the time for GNUNET_hash_file into account */
470   treedepth = GNUNET_ECRS_compute_depth (filesize);
471
472   /* Test if file is indexed! */
473   wasIndexed = GNUNET_FS_test_indexed (sock, &fileId);
474
475   fd = GNUNET_disk_file_open (ectx, filename, O_RDONLY | O_LARGEFILE);
476   if (fd == -1)
477     {
478       GNUNET_client_connection_destroy (sock);
479       return GNUNET_SYSERR;
480     }
481   dblock =
482     GNUNET_malloc (sizeof (GNUNET_DatastoreValue) + GNUNET_ECRS_DBLOCK_SIZE +
483                    sizeof (GNUNET_EC_DBlock));
484   dblock->size =
485     htonl (sizeof (GNUNET_DatastoreValue) + GNUNET_ECRS_DBLOCK_SIZE +
486            sizeof (GNUNET_EC_DBlock));
487   dblock->anonymity_level = htonl (0);
488   dblock->priority = htonl (0);
489   dblock->type = htonl (GNUNET_ECRS_BLOCKTYPE_DATA);
490   dblock->expiration_time = GNUNET_htonll (0);
491   db = (GNUNET_EC_DBlock *) & dblock[1];
492   db->type = htonl (GNUNET_ECRS_BLOCKTYPE_DATA);
493   iblocks =
494     GNUNET_malloc (sizeof (GNUNET_DatastoreValue *) * (treedepth + 1));
495   for (i = 0; i <= treedepth; i++)
496     {
497       iblocks[i] =
498         GNUNET_malloc (sizeof (GNUNET_DatastoreValue) +
499                        GNUNET_ECRS_IBLOCK_SIZE + sizeof (GNUNET_EC_DBlock));
500       iblocks[i]->size =
501         htonl (sizeof (GNUNET_DatastoreValue) + sizeof (GNUNET_EC_DBlock));
502       iblocks[i]->anonymity_level = htonl (0);
503       iblocks[i]->priority = htonl (0);
504       iblocks[i]->type = htonl (GNUNET_ECRS_BLOCKTYPE_DATA);
505       iblocks[i]->expiration_time = GNUNET_htonll (0);
506       ((GNUNET_EC_DBlock *) & iblocks[i][1])->type =
507         htonl (GNUNET_ECRS_BLOCKTYPE_DATA);
508     }
509
510   pos = 0;
511   while (pos < filesize)
512     {
513       if (upcb != NULL)
514         upcb (filesize, pos, eta, upcbClosure);
515       if (tt != NULL)
516         if (GNUNET_OK != tt (ttClosure))
517           goto FAILURE;
518       size = GNUNET_ECRS_DBLOCK_SIZE;
519       if (size > filesize - pos)
520         {
521           size = filesize - pos;
522           memset (&db[1], 0, GNUNET_ECRS_DBLOCK_SIZE);
523         }
524       dblock->size =
525         htonl (sizeof (GNUNET_DatastoreValue) + size +
526                sizeof (GNUNET_EC_DBlock));
527       if (size != READ (fd, &db[1], size))
528         {
529           GNUNET_GE_LOG_STRERROR_FILE (ectx,
530                                        GNUNET_GE_ERROR | GNUNET_GE_USER |
531                                        GNUNET_GE_ADMIN | GNUNET_GE_BULK,
532                                        "READ", filename);
533           goto FAILURE;
534         }
535       if (tt != NULL)
536         if (GNUNET_OK != tt (ttClosure))
537           goto FAILURE;
538       GNUNET_EC_file_block_get_key (db, size + sizeof (GNUNET_EC_DBlock),
539                                     &chk.key);
540       GNUNET_EC_file_block_get_query (db, size + sizeof (GNUNET_EC_DBlock),
541                                       &chk.query);
542       if (GNUNET_OK != pushBlock (sock, &chk, 0,        /* dblocks are on level 0 */
543                                   iblocks))
544         {
545           GNUNET_GE_BREAK (ectx, 0);
546           goto FAILURE;
547         }
548       if (!wasIndexed)
549         {
550           if (GNUNET_OK ==
551               GNUNET_EC_file_block_encode (db, size, &chk.query, &value))
552             {
553               *value = *dblock; /* copy options! */
554 #if STRICT_CHECKS
555               if (GNUNET_OK != GNUNET_FS_delete (sock, value))
556                 {
557                   GNUNET_free (value);
558                   GNUNET_GE_BREAK (ectx, 0);
559                   goto FAILURE;
560                 }
561 #else
562               GNUNET_FS_delete (sock, value);
563 #endif
564               GNUNET_free (value);
565             }
566           else
567             {
568               goto FAILURE;
569             }
570         }
571       pos += size;
572       now = GNUNET_get_time ();
573       eta = (GNUNET_CronTime) (start +
574                                (((double) (now - start) / (double) pos))
575                                * (double) filesize);
576     }
577   if (tt != NULL)
578     if (GNUNET_OK != tt (ttClosure))
579       goto FAILURE;
580   for (i = 0; i < treedepth; i++)
581     {
582       size = ntohl (iblocks[i]->size) - sizeof (GNUNET_DatastoreValue);
583       db = (GNUNET_EC_DBlock *) & iblocks[i][1];
584       GNUNET_EC_file_block_get_key (db, size, &chk.key);
585       GNUNET_EC_file_block_get_query (db, size, &chk.query);
586       if (GNUNET_OK != pushBlock (sock, &chk, i + 1, iblocks))
587         {
588           GNUNET_GE_BREAK (ectx, 0);
589           goto FAILURE;
590         }
591       GNUNET_EC_file_block_encode (db, size, &chk.query, &value);
592 #if STRICT_CHECKS
593       if (GNUNET_OK != GNUNET_FS_delete (sock, value))
594         {
595           GNUNET_free (value);
596           GNUNET_GE_BREAK (ectx, 0);
597           goto FAILURE;
598         }
599 #else
600       GNUNET_FS_delete (sock, value);
601 #endif
602       GNUNET_free (value);
603       GNUNET_free (iblocks[i]);
604       iblocks[i] = NULL;
605     }
606
607   if (wasIndexed)
608     {
609       if (GNUNET_OK == undoSymlinking (ectx, filename, &fileId, sock))
610         {
611           if (GNUNET_OK !=
612               GNUNET_FS_unindex (sock, GNUNET_ECRS_DBLOCK_SIZE, &fileId))
613             {
614               GNUNET_GE_BREAK (ectx, 0);
615               goto FAILURE;
616             }
617         }
618       else
619         {
620           GNUNET_GE_BREAK (ectx, 0);
621           goto FAILURE;
622         }
623     }
624   GNUNET_free (iblocks[treedepth]);
625   /* free resources */
626   GNUNET_free (iblocks);
627   GNUNET_free (dblock);
628   CLOSE (fd);
629   GNUNET_client_connection_destroy (sock);
630   return GNUNET_OK;
631 FAILURE:
632   for (i = 0; i <= treedepth; i++)
633     GNUNET_free_non_null (iblocks[i]);
634   GNUNET_free (iblocks);
635   GNUNET_free (dblock);
636   CLOSE (fd);
637   GNUNET_client_connection_destroy (sock);
638   return GNUNET_SYSERR;
639 }
640
641 #endif 
642
643 /* end of fs_unindex.c */