paragraph for gnunet devs that don't know how to use the web
[oweals/gnunet.git] / src / fs / gnunet-service-fs_indexing.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2009, 2010 GNUnet e.V.
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your 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      Affero General Public License for more details.
14     
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /**
20  * @file fs/gnunet-service-fs_indexing.c
21  * @brief program that provides indexing functions of the file-sharing service
22  * @author Christian Grothoff
23  */
24 #include "platform.h"
25 #include <float.h>
26 #include "gnunet_core_service.h"
27 #include "gnunet_datastore_service.h"
28 #include "gnunet_peer_lib.h"
29 #include "gnunet_protocols.h"
30 #include "gnunet_signatures.h"
31 #include "gnunet_util_lib.h"
32 #include "gnunet-service-fs.h"
33 #include "gnunet-service-fs_indexing.h"
34 #include "fs.h"
35
36 /**
37  * In-memory information about indexed files (also available
38  * on-disk).
39  */
40 struct IndexInfo
41 {
42
43   /**
44    * This is a doubly linked list.
45    */
46   struct IndexInfo *next;
47
48   /**
49    * This is a doubly linked list.
50    */
51   struct IndexInfo *prev;
52
53   /**
54    * Name of the indexed file.  Memory allocated
55    * at the end of this struct (do not free).
56    */
57   const char *filename;
58
59   /**
60    * Context for transmitting confirmation to client,
61    * NULL if we've done this already.
62    */
63   struct GNUNET_SERVER_TransmitContext *tc;
64
65   /**
66    * Context for hashing of the file.
67    */
68   struct GNUNET_CRYPTO_FileHashContext *fhc;
69
70   /**
71    * Hash of the contents of the file.
72    */
73   struct GNUNET_HashCode file_id;
74
75 };
76
77
78 /**
79  * Head of linked list of indexed files.
80  * FIXME: we don't need both a DLL and a hashmap here!
81  */
82 static struct IndexInfo *indexed_files_head;
83
84 /**
85  * Tail of linked list of indexed files.
86  */
87 static struct IndexInfo *indexed_files_tail;
88
89 /**
90  * Maps hash over content of indexed files to the respective 'struct IndexInfo'.
91  * The filenames are pointers into the indexed_files linked list and
92  * do not need to be freed.
93  */
94 static struct GNUNET_CONTAINER_MultiHashMap *ifm;
95
96 /**
97  * Our configuration.
98  */
99 static const struct GNUNET_CONFIGURATION_Handle *cfg;
100
101 /**
102  * Datastore handle.  Created and destroyed by code in
103  * gnunet-service-fs (this is an alias).
104  */
105 static struct GNUNET_DATASTORE_Handle *dsh;
106
107
108 /**
109  * Write the current index information list to disk.
110  */
111 static void
112 write_index_list ()
113 {
114   struct GNUNET_BIO_WriteHandle *wh;
115   char *fn;
116   struct IndexInfo *pos;
117
118   if (GNUNET_OK !=
119       GNUNET_CONFIGURATION_get_value_filename (cfg, "FS",
120                                                "INDEXDB",
121                                                &fn))
122   {
123     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
124                                "fs",
125                                "INDEXDB");
126     return;
127   }
128   wh = GNUNET_BIO_write_open (fn);
129   if (NULL == wh)
130   {
131     GNUNET_log (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
132                 _("Could not open `%s'.\n"),
133                 fn);
134     GNUNET_free (fn);
135     return;
136   }
137   for (pos = indexed_files_head; NULL != pos; pos = pos->next)
138     if ((GNUNET_OK !=
139          GNUNET_BIO_write (wh,
140                            &pos->file_id,
141                            sizeof (struct GNUNET_HashCode))) ||
142         (GNUNET_OK !=
143          GNUNET_BIO_write_string (wh,
144                                   pos->filename)))
145       break;
146   if (GNUNET_OK != GNUNET_BIO_write_close (wh))
147   {
148     GNUNET_log (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
149                 _("Error writing `%s'.\n"),
150                 fn);
151     GNUNET_free (fn);
152     return;
153   }
154   GNUNET_free (fn);
155 }
156
157
158 /**
159  * Read index information from disk.
160  */
161 static void
162 read_index_list ()
163 {
164   struct GNUNET_BIO_ReadHandle *rh;
165   char *fn;
166   struct IndexInfo *pos;
167   char *fname;
168   struct GNUNET_HashCode hc;
169   size_t slen;
170   char *emsg;
171
172   if (GNUNET_OK !=
173       GNUNET_CONFIGURATION_get_value_filename (cfg,
174                                                "FS",
175                                                "INDEXDB",
176                                                &fn))
177   {
178     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
179                                "fs",
180                                "INDEXDB");
181     return;
182   }
183   if (GNUNET_NO == GNUNET_DISK_file_test (fn))
184   {
185     /* no index info yet */
186     GNUNET_free (fn);
187     return;
188   }
189   rh = GNUNET_BIO_read_open (fn);
190   if (NULL == rh)
191   {
192     GNUNET_log (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
193                 _("Could not open `%s'.\n"),
194                 fn);
195     GNUNET_free (fn);
196     return;
197   }
198   while ( (GNUNET_OK ==
199            GNUNET_BIO_read (rh,
200                             "Hash of indexed file",
201                             &hc,
202                             sizeof (struct GNUNET_HashCode))) &&
203           (GNUNET_OK ==
204            GNUNET_BIO_read_string (rh,
205                                    "Name of indexed file",
206                                    &fname,
207                                    1024 * 16)) &&
208           (fname != NULL) )
209   {
210     slen = strlen (fname) + 1;
211     pos = GNUNET_malloc (sizeof (struct IndexInfo) + slen);
212     pos->file_id = hc;
213     pos->filename = (const char *) &pos[1];
214     GNUNET_memcpy (&pos[1], fname, slen);
215     if (GNUNET_SYSERR ==
216         GNUNET_CONTAINER_multihashmap_put (ifm, &pos->file_id, pos,
217                                            GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
218     {
219       GNUNET_free (pos);
220     }
221     else
222     {
223       GNUNET_CONTAINER_DLL_insert (indexed_files_head,
224                                    indexed_files_tail,
225                                    pos);
226     }
227     GNUNET_free (fname);
228   }
229   if (GNUNET_OK != GNUNET_BIO_read_close (rh, &emsg))
230     GNUNET_free (emsg);
231   GNUNET_free (fn);
232 }
233
234
235 /**
236  * Continuation called from datastore's remove
237  * function.
238  *
239  * @param cls unused
240  * @param success did the deletion work?
241  * @param min_expiration minimum expiration time required for content to be stored
242  * @param msg error message
243  */
244 static void
245 remove_cont (void *cls, int success,
246              struct GNUNET_TIME_Absolute min_expiration,
247              const char *msg)
248 {
249   if (GNUNET_OK != success)
250     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
251                 _("Failed to delete bogus block: %s\n"), msg);
252 }
253
254
255 /**
256  * We've received an on-demand encoded block from the datastore.
257  * Attempt to do on-demand encoding and (if successful), call the
258  * continuation with the resulting block.  On error, clean up and ask
259  * the datastore for more results.
260  *
261  * @param key key for the content
262  * @param size number of bytes in data
263  * @param data content stored
264  * @param type type of the content
265  * @param priority priority of the content
266  * @param anonymity anonymity-level for the content
267  * @param replication replication-level for the content
268  * @param expiration expiration time for the content
269  * @param uid unique identifier for the datum;
270  *        maybe 0 if no unique identifier is available
271  * @param cont function to call with the actual block (at most once, on success)
272  * @param cont_cls closure for cont
273  * @return GNUNET_OK on success
274  */
275 int
276 GNUNET_FS_handle_on_demand_block (const struct GNUNET_HashCode * key,
277                                   uint32_t size,
278                                   const void *data,
279                                   enum GNUNET_BLOCK_Type type,
280                                   uint32_t priority,
281                                   uint32_t anonymity,
282                                   uint32_t replication,
283                                   struct GNUNET_TIME_Absolute expiration,
284                                   uint64_t uid,
285                                   GNUNET_DATASTORE_DatumProcessor cont,
286                                   void *cont_cls)
287 {
288   const struct OnDemandBlock *odb;
289   struct GNUNET_HashCode nkey;
290   struct GNUNET_CRYPTO_SymmetricSessionKey skey;
291   struct GNUNET_CRYPTO_SymmetricInitializationVector iv;
292   struct GNUNET_HashCode query;
293   ssize_t nsize;
294   char ndata[DBLOCK_SIZE];
295   char edata[DBLOCK_SIZE];
296   const char *fn;
297   struct GNUNET_DISK_FileHandle *fh;
298   uint64_t off;
299   struct IndexInfo *ii;
300
301   if (size != sizeof (struct OnDemandBlock))
302   {
303     GNUNET_break (0);
304     GNUNET_DATASTORE_remove (dsh,
305                              key,
306                              size,
307                              data,
308                              -1,
309                              -1,
310                              &remove_cont, NULL);
311     return GNUNET_SYSERR;
312   }
313   odb = (const struct OnDemandBlock *) data;
314   off = GNUNET_ntohll (odb->offset);
315   ii = GNUNET_CONTAINER_multihashmap_get (ifm,
316                                           &odb->file_id);
317   if (NULL == ii)
318   {
319     GNUNET_break (0);
320     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
321                 "Failed to find index %s\n",
322                 GNUNET_h2s (&odb->file_id));
323     return GNUNET_SYSERR;
324   }
325   fn = ii->filename;
326   if ((NULL == fn) || (0 != ACCESS (fn, R_OK)))
327   {
328     GNUNET_STATISTICS_update (GSF_stats,
329                               gettext_noop ("# index blocks removed: original file inaccessible"),
330                               1,
331                               GNUNET_YES);
332     GNUNET_DATASTORE_remove (dsh,
333                              key,
334                              size,
335                              data,
336                              -1,
337                              -1,
338                              &remove_cont,
339                              NULL);
340     return GNUNET_SYSERR;
341   }
342   if ( (NULL ==
343         (fh =
344          GNUNET_DISK_file_open (fn,
345                                 GNUNET_DISK_OPEN_READ,
346                                 GNUNET_DISK_PERM_NONE))) ||
347        (off != GNUNET_DISK_file_seek (fh,
348                                       off,
349                                       GNUNET_DISK_SEEK_SET)) ||
350       (-1 == (nsize = GNUNET_DISK_file_read (fh,
351                                              ndata,
352                                              sizeof (ndata)))) )
353   {
354     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
355                 _("Could not access indexed file `%s' (%s) at offset %llu: %s\n"),
356                 GNUNET_h2s (&odb->file_id),
357                 fn,
358                 (unsigned long long) off,
359                 (fn == NULL) ? _("not indexed") : STRERROR (errno));
360     if (fh != NULL)
361       GNUNET_DISK_file_close (fh);
362     GNUNET_DATASTORE_remove (dsh,
363                              key,
364                              size,
365                              data,
366                              -1,
367                              -1,
368                              &remove_cont,
369                              NULL);
370     return GNUNET_SYSERR;
371   }
372   GNUNET_DISK_file_close (fh);
373   GNUNET_CRYPTO_hash (ndata,
374                       nsize,
375                       &nkey);
376   GNUNET_CRYPTO_hash_to_aes_key (&nkey,
377                                  &skey,
378                                  &iv);
379   GNUNET_CRYPTO_symmetric_encrypt (ndata,
380                                    nsize,
381                                    &skey,
382                                    &iv,
383                                    edata);
384   GNUNET_CRYPTO_hash (edata,
385                       nsize,
386                       &query);
387   if (0 != memcmp (&query,
388                    key,
389                    sizeof (struct GNUNET_HashCode)))
390   {
391     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
392                 _("Indexed file `%s' changed at offset %llu\n"),
393                 fn,
394                 (unsigned long long) off);
395     GNUNET_DATASTORE_remove (dsh,
396                              key,
397                              size,
398                              data,
399                              -1,
400                              -1,
401                              &remove_cont,
402                              NULL);
403     return GNUNET_SYSERR;
404   }
405   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
406               "On-demand encoded block for query `%s'\n",
407               GNUNET_h2s (key));
408   cont (cont_cls,
409         key,
410         nsize,
411         edata,
412         GNUNET_BLOCK_TYPE_FS_DBLOCK,
413         priority,
414         anonymity,
415         replication,
416         expiration,
417         uid);
418   return GNUNET_OK;
419 }
420
421
422 /**
423  * Transmit information about indexed files to @a mq.
424  *
425  * @param mq message queue to send information to
426  */
427 void
428 GNUNET_FS_indexing_send_list (struct GNUNET_MQ_Handle *mq)
429 {
430   struct GNUNET_MQ_Envelope *env;
431   struct IndexInfoMessage *iim;
432   struct GNUNET_MessageHeader *iem;
433   size_t slen;
434   const char *fn;
435   struct IndexInfo *pos;
436
437   for (pos = indexed_files_head; NULL != pos; pos = pos->next)
438   {
439     fn = pos->filename;
440     slen = strlen (fn) + 1;
441     if (slen + sizeof (struct IndexInfoMessage) >=
442         GNUNET_MAX_MESSAGE_SIZE)
443     {
444       GNUNET_break (0);
445       break;
446     }
447     env = GNUNET_MQ_msg_extra (iim,
448                                slen,
449                                GNUNET_MESSAGE_TYPE_FS_INDEX_LIST_ENTRY);
450     iim->reserved = 0;
451     iim->file_id = pos->file_id;
452     GNUNET_memcpy (&iim[1],
453                    fn,
454                    slen);
455     GNUNET_MQ_send (mq,
456                     env);
457   }
458   env = GNUNET_MQ_msg (iem,
459                        GNUNET_MESSAGE_TYPE_FS_INDEX_LIST_END);
460   GNUNET_MQ_send (mq,
461                   env);
462 }
463
464
465 /**
466  * Remove a file from the index.
467  *
468  * @param fid identifier of the file to remove
469  * @return #GNUNET_YES if the @a fid was found
470  */
471 int
472 GNUNET_FS_indexing_do_unindex (const struct GNUNET_HashCode *fid)
473 {
474   struct IndexInfo *pos;
475
476   for (pos = indexed_files_head; NULL != pos; pos = pos->next)
477   {
478     if (0 == memcmp (&pos->file_id,
479                      fid,
480                      sizeof (struct GNUNET_HashCode)))
481     {
482       GNUNET_CONTAINER_DLL_remove (indexed_files_head,
483                                    indexed_files_tail,
484                                    pos);
485       GNUNET_break (GNUNET_OK ==
486                     GNUNET_CONTAINER_multihashmap_remove (ifm,
487                                                           &pos->file_id,
488                                                           pos));
489       GNUNET_free (pos);
490       write_index_list ();
491       return GNUNET_YES;
492     }
493   }
494   return GNUNET_NO;
495 }
496
497
498 /**
499  * Add the given file to the list of indexed files.
500  *
501  * @param filename name of the file
502  * @param file_id hash identifier for @a filename
503  */
504 void
505 GNUNET_FS_add_to_index (const char *filename,
506                         const struct GNUNET_HashCode *file_id)
507 {
508   struct IndexInfo *ii;
509   size_t slen;
510
511   ii = GNUNET_CONTAINER_multihashmap_get (ifm,
512                                           file_id);
513   if (NULL != ii)
514   {
515     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
516                 _("Index request received for file `%s' is already indexed as `%s'.  Permitting anyway.\n"),
517                 filename,
518                 ii->filename);
519     return;
520   }
521   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
522               "Adding file %s to index as %s\n",
523               filename,
524               GNUNET_h2s (file_id));
525   slen = strlen (filename) + 1;
526   ii = GNUNET_malloc (sizeof (struct IndexInfo) + slen);
527   ii->file_id = *file_id;
528   ii->filename = (const char *) &ii[1];
529   GNUNET_memcpy (&ii[1],
530                  filename,
531                  slen);
532   GNUNET_CONTAINER_DLL_insert (indexed_files_head,
533                                indexed_files_tail,
534                                ii);
535   GNUNET_assert (GNUNET_OK ==
536                  GNUNET_CONTAINER_multihashmap_put (ifm,
537                                                     &ii->file_id,
538                                                     ii,
539                                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
540   write_index_list ();
541 }
542
543
544 /**
545  * Shutdown the module.
546  */
547 void
548 GNUNET_FS_indexing_done ()
549 {
550   struct IndexInfo *pos;
551
552   while (NULL != (pos = indexed_files_head))
553   {
554     GNUNET_CONTAINER_DLL_remove (indexed_files_head,
555                                  indexed_files_tail,
556                                  pos);
557     if (pos->fhc != NULL)
558       GNUNET_CRYPTO_hash_file_cancel (pos->fhc);
559     GNUNET_break (GNUNET_OK ==
560                   GNUNET_CONTAINER_multihashmap_remove (ifm,
561                                                         &pos->file_id,
562                                                         pos));
563     GNUNET_free (pos);
564   }
565   GNUNET_CONTAINER_multihashmap_destroy (ifm);
566   ifm = NULL;
567   cfg = NULL;
568 }
569
570
571 /**
572  * Initialize the indexing submodule.
573  *
574  * @param c configuration to use
575  * @param d datastore to use
576  */
577 int
578 GNUNET_FS_indexing_init (const struct GNUNET_CONFIGURATION_Handle *c,
579                          struct GNUNET_DATASTORE_Handle *d)
580 {
581   cfg = c;
582   dsh = d;
583   ifm = GNUNET_CONTAINER_multihashmap_create (128,
584                                               GNUNET_YES);
585   read_index_list ();
586   return GNUNET_OK;
587 }
588
589 /* end of gnunet-service-fs_indexing.c */