6af1863479745ad39582df9805c106ff0f66ec03
[oweals/gnunet.git] / src / fs / fs_dirmetascan.c
1 /*\r
2      This file is part of GNUnet\r
3      (C) 2005-2012 Christian Grothoff (and other contributing authors)\r
4 \r
5      GNUnet is free software; you can redistribute it and/or modify\r
6      it under the terms of the GNU General Public License as published\r
7      by the Free Software Foundation; either version 2, or (at your\r
8      option) any later version.\r
9 \r
10      GNUnet is distributed in the hope that it will be useful, but\r
11      WITHOUT ANY WARRANTY; without even the implied warranty of\r
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
13      General Public License for more details.\r
14 \r
15      You should have received a copy of the GNU General Public License\r
16      along with GNUnet; see the file COPYING.  If not, write to the\r
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,\r
18      Boston, MA 02111-1307, USA.\r
19 */\r
20 \r
21 #include "platform.h"\r
22 #include "gnunet_fs_service.h"\r
23 #include "gnunet_scheduler_lib.h"\r
24 \r
25 /**\r
26  * Entry for each unique keyword to track how often\r
27  * it occured.  Contains the keyword and the counter.\r
28  */\r
29 struct KeywordCounter\r
30 {\r
31 \r
32   /**\r
33    * Keyword that was found.\r
34    */\r
35   const char *value;\r
36 \r
37   /**\r
38    * How many files have this keyword?\r
39    */\r
40   unsigned int count;\r
41 \r
42   /**\r
43    * This is a doubly-linked list\r
44    */\r
45   struct KeywordCounter *prev;\r
46 \r
47   /**\r
48    * This is a doubly-linked list\r
49    */\r
50   struct KeywordCounter *next;\r
51 };\r
52 \r
53 /**\r
54  * Aggregate information we keep for meta data in each directory.\r
55  */\r
56 struct MetaCounter\r
57 {\r
58   /**\r
59    * The actual meta data.\r
60    */\r
61   const char *data;\r
62 \r
63   /**\r
64    * Number of bytes in 'data'.\r
65    */\r
66   size_t data_size;\r
67 \r
68   /**\r
69    * Name of the plugin that provided that piece of metadata\r
70    */\r
71   const char *plugin_name;\r
72 \r
73   /**\r
74    * Type of the data\r
75    */\r
76   enum EXTRACTOR_MetaType type;\r
77 \r
78   /**\r
79    * Format of the data\r
80    */\r
81   enum EXTRACTOR_MetaFormat format;\r
82 \r
83   /**\r
84    * MIME-type of the metadata itself\r
85    */\r
86   const char *data_mime_type;\r
87 \r
88   /**\r
89    * How many files have meta entries matching this value?\r
90    * (type and format do not have to match).\r
91    */\r
92   unsigned int count;\r
93 \r
94   /**\r
95    * This is a doubly-linked list\r
96    */\r
97   struct MetaCounter *prev;\r
98 \r
99   /**\r
100    * This is a doubly-linked list\r
101    */\r
102   struct MetaCounter *next;\r
103 };\r
104 \r
105 /**\r
106  * Execution context for 'add_dir'\r
107  * Owned by the initiator thread.\r
108  */\r
109 struct AddDirContext\r
110 {\r
111   /**\r
112    * Parent directory (used to access keyword and metadata counters,\r
113    * and the like).\r
114    * After the scan is finished, it will contain a pointer to the\r
115    * top-level directory entry in the directory tree built by the\r
116    * scanner.\r
117    */\r
118   struct ShareTreeItem *parent;\r
119 \r
120   /**\r
121    * Expanded filename (as given by the scan initiator).\r
122    * The scanner thread stores a copy here, and frees it when it finishes.\r
123    */\r
124   char *filename_expanded;\r
125 \r
126   /**\r
127    * A synchronization privitive. Whenever its state is altered,\r
128    * it means that the initiator wants the scanner to wrap up.\r
129    * It is owned by the initiator thread.\r
130    */\r
131 #if WINDOWS\r
132   HANDLE stop;\r
133 #else\r
134   sem_t *stop;\r
135 #endif\r
136 \r
137   /**\r
138    * 1 if the scanner should stop, 0 otherwise. Set in response\r
139    * to communication errors or when the initiator wants the scanning\r
140    * process to stop.\r
141    */\r
142   char do_stop;\r
143 \r
144   /**\r
145    * Handle of the pipe end into which the progress messages are written\r
146    * The pipe is owned by the initiator thread, and there's no way to\r
147    * close this end without having access to the pipe, so it won't\r
148    * be closed by the scanner thread.\r
149    * The initiator MUST keep it alive until the scanner thread is finished.\r
150    */\r
151   const struct GNUNET_DISK_FileHandle *progress_write;\r
152 \r
153 \r
154   /**\r
155    * List of libextractor plugins to use for extracting.\r
156    * Initialized when the scan starts, removed when it finishes.\r
157    */\r
158   struct EXTRACTOR_PluginList *plugins;\r
159 };\r
160 \r
161 /**\r
162  * An opaque structure a pointer to which is returned to the\r
163  * caller to be used to control the scanner.\r
164  */\r
165 struct GNUNET_FS_DirScanner\r
166 {\r
167  /**\r
168   * A synchronization privitive that is used to signal the scanner to stop.\r
169   * Owned by the initiator thread.\r
170   */\r
171 #if WINDOWS\r
172   HANDLE stop;\r
173 #else\r
174   sem_t *stop;\r
175 #endif\r
176 \r
177  /**\r
178   * A thread object for the scanner thread.\r
179   * Owned by the initiator thread.\r
180   */\r
181 #if WINDOWS\r
182   HANDLE thread;\r
183 #else\r
184   pthread_t thread;\r
185 #endif\r
186 \r
187  /**\r
188   * A task for reading progress messages from the scanner.\r
189   */\r
190   GNUNET_SCHEDULER_TaskIdentifier progress_read_task;\r
191 \r
192  /**\r
193   * The end of the pipe that is used to read progress messages.\r
194   */\r
195   const struct GNUNET_DISK_FileHandle *progress_read;\r
196 \r
197  /**\r
198   * The pipe that is used to read progress messages.\r
199   * Owned (along with both of its ends) by the initiator thread.\r
200   * Only closed after the scanner thread is finished.\r
201   */\r
202   struct GNUNET_DISK_PipeHandle *progress_pipe;\r
203 \r
204  /**\r
205   * The function that will be called every time there's a progress\r
206   * message.\r
207   */\r
208   GNUNET_FS_DirScannerProgressCallback progress_callback;\r
209 \r
210  /**\r
211   * A closure for progress_callback.\r
212   */\r
213   void *cls;\r
214 \r
215  /**\r
216   * A pointer to the context of the scanner.\r
217   * Owned by the initiator thread.\r
218   * Initiator thread shouldn't touch it until the scanner thread\r
219   * is finished.\r
220   */\r
221   struct AddDirContext *adc;\r
222 };\r
223 \r
224 /**\r
225  * A structure that forms a singly-linked list that serves as a stack\r
226  * for metadata-processing function.\r
227  */\r
228 struct ProcessMetadataStackItem\r
229 {\r
230  /**\r
231   * A pointer to metadata-processing context.\r
232   * The same in every stack item.\r
233   */\r
234   struct ProcessMetadataContext *ctx;\r
235 \r
236  /**\r
237   * This is a singly-linked list. A pointer to its end is kept, and\r
238   * this pointer is used to walk it backwards.\r
239   */\r
240   struct ProcessMetadataStackItem *parent;\r
241 \r
242   /**\r
243    * Map from the hash over the keyword to an 'struct KeywordCounter *'\r
244    * counter that says how often this keyword was\r
245    * encountered in the current directory.\r
246    */\r
247   struct GNUNET_CONTAINER_MultiHashMap *keywordcounter;\r
248 \r
249   /**\r
250    * Map from the hash over the metadata to an 'struct MetaCounter *'\r
251    * counter that says how often this metadata was\r
252    * encountered in the current directory.\r
253    */\r
254   struct GNUNET_CONTAINER_MultiHashMap *metacounter;\r
255 \r
256   /**\r
257    * Number of files in the current directory.\r
258    */\r
259   unsigned int dir_entry_count;\r
260 \r
261   /**\r
262    * Keywords to exclude from using for KSK since they'll be associated\r
263    * with the parent as well.  NULL for nothing blocked.\r
264    */\r
265   struct GNUNET_FS_Uri *exclude_ksk;\r
266 \r
267  /**\r
268   * A share tree item that is being processed.\r
269   */\r
270   struct ShareTreeItem *item;\r
271 \r
272  /**\r
273   * Set to GNUNET_YES to indicate that the directory pointer by 'item'\r
274   * was processed, and we should move on to the next.\r
275   * Otherwise the directory will be recursed into.\r
276   */\r
277   int end_directory;\r
278 \r
279 };\r
280 \r
281 /**\r
282  * The structure to keep the state of metadata processing\r
283  */\r
284 struct ProcessMetadataContext\r
285 {\r
286  /**\r
287   * The top of the stack.\r
288   */\r
289   struct ProcessMetadataStackItem *stack;\r
290 \r
291  /**\r
292   * Callback to invoke when processing is finished\r
293   */\r
294   GNUNET_SCHEDULER_Task cb;\r
295 \r
296  /**\r
297   * Closure for 'cb'\r
298   */\r
299   void *cls;\r
300 \r
301  /**\r
302   * Toplevel directory item of the tree to process.\r
303   */\r
304   struct ShareTreeItem *toplevel;\r
305 };\r
306 \r
307 /**\r
308  * Called every now and then by the scanner.\r
309  * Checks the synchronization privitive.\r
310  * Returns 1 if the scanner should stop, 0 otherwise.\r
311  */\r
312 static int\r
313 should_stop (struct AddDirContext *adc)\r
314 {\r
315 #if WINDOWS\r
316   if (WaitForSingleObject (adc->stop, 0) == WAIT_TIMEOUT)\r
317     return 0;\r
318   adc->do_stop = 1;\r
319   return 1;\r
320 #else\r
321   int value;\r
322   sem_getvalue(adc->stop, &value);  \r
323   if (value > 0)\r
324   {\r
325     adc->do_stop = 1;\r
326     return 1;\r
327   }\r
328   return 0;\r
329 #endif\r
330 }\r
331 \r
332 /**\r
333  * Write progress message.\r
334  * Format is:\r
335  * <reason><filename length><filename><directory flag>\r
336  * If filename is NULL, filename is not written, and its length\r
337  * is written as 0, and nothing else is written. It signals the initiator\r
338  * thread that the scanner is finished, and that it can now join its thread.\r
339  *\r
340  * Also checks if the initiator thread wants the scanner to stop,\r
341  * Returns 1 to stop scanning (if the signal was received, or\r
342  * if the pipe was broken somehow), 0 otherwise.\r
343  */\r
344 static int\r
345 write_progress (struct AddDirContext *adc, const char *filename,\r
346     char is_directory, enum GNUNET_DirScannerProgressUpdateReason reason)\r
347 {\r
348   size_t filename_len;\r
349   ssize_t wr;\r
350   size_t total_write;\r
351   if ((adc->do_stop || should_stop (adc)) && reason != GNUNET_DIR_SCANNER_ASKED_TO_STOP\r
352       && reason != GNUNET_DIR_SCANNER_FINISHED)\r
353     return 1;\r
354   total_write = 0;\r
355   wr = 1;\r
356   while ((wr > 0 || errno == EAGAIN) && total_write < sizeof (reason))\r
357   {\r
358     wr = GNUNET_DISK_file_write_blocking (adc->progress_write,\r
359       &((char *)&reason)[total_write], sizeof (reason) - total_write);\r
360     if (wr > 0)\r
361       total_write += wr;\r
362   }\r
363   if (sizeof (reason) != total_write)\r
364     return adc->do_stop = 1;\r
365   if (filename)\r
366     filename_len = strlen (filename) + 1;\r
367   else\r
368     filename_len = 0;\r
369   total_write = 0;\r
370   wr = 1;\r
371   while ((wr > 0 || errno == EAGAIN) && total_write < sizeof (size_t))\r
372   {\r
373     wr = GNUNET_DISK_file_write_blocking (adc->progress_write,\r
374       &((char *)&filename_len)[total_write], sizeof (size_t) - total_write);\r
375     if (wr > 0)\r
376       total_write += wr;\r
377   }\r
378   if (sizeof (size_t) != total_write)\r
379     return adc->do_stop = 1;\r
380   if (filename)\r
381   {\r
382     total_write = 0;\r
383     wr = 1;\r
384     while ((wr > 0 || errno == EAGAIN) && total_write < filename_len)\r
385     {\r
386       wr = GNUNET_DISK_file_write_blocking (adc->progress_write,\r
387         &((char *)filename)[total_write], filename_len - total_write);\r
388       if (wr > 0)\r
389         total_write += wr;\r
390     }\r
391     if (filename_len != total_write)\r
392       return adc->do_stop = 1;\r
393     total_write = 0;\r
394     wr = 1;\r
395     while ((wr > 0 || errno == EAGAIN) && total_write < sizeof (char))\r
396     {\r
397       wr = GNUNET_DISK_file_write_blocking (adc->progress_write,\r
398         &((char *)&is_directory)[total_write], sizeof (char) - total_write);\r
399       if (wr > 0)\r
400         total_write += wr;\r
401     }\r
402     if (sizeof (char) != total_write)\r
403       return adc->do_stop = 1;\r
404   }\r
405   return 0;\r
406 }\r
407 \r
408 /**\r
409  * Add the given keyword to the\r
410  * keyword statistics tracker.\r
411  *\r
412  * @param cls closure (user-defined)\r
413  * @param keyword the keyword to count\r
414  * @param is_mandatory ignored\r
415  * @return always GNUNET_OK\r
416  */\r
417 static int\r
418 add_to_keyword_counter (void *cls, const char *keyword, int is_mandatory)\r
419 {\r
420   struct GNUNET_CONTAINER_MultiHashMap *mcm = cls;\r
421   struct KeywordCounter *cnt, *first_cnt;\r
422   GNUNET_HashCode hc;\r
423   size_t klen;\r
424 \r
425   klen = strlen (keyword) + 1;\r
426   GNUNET_CRYPTO_hash (keyword, klen - 1, &hc);\r
427   /* Since the map might contain multiple values per keyword, we only\r
428    * store one value, and attach all other to it, forming a linked list.\r
429    * Somewhat easier than retrieving multiple items via callback.\r
430    */\r
431   first_cnt = GNUNET_CONTAINER_multihashmap_get (mcm, &hc);\r
432   for (cnt = first_cnt; cnt && strcmp (cnt->value, keyword) != 0; cnt = cnt->next);\r
433   if (cnt == NULL)\r
434   {\r
435     cnt = GNUNET_malloc (sizeof (struct KeywordCounter) + klen);\r
436     cnt->value = (const char *) &cnt[1];\r
437     memcpy (&cnt[1], keyword, klen);\r
438     if (first_cnt != NULL)\r
439     {\r
440       if (first_cnt->prev != NULL)\r
441       {\r
442         first_cnt->prev->next = cnt;\r
443         cnt->prev = first_cnt->prev;\r
444       }\r
445       first_cnt->prev = cnt;\r
446       cnt->next = first_cnt;\r
447     }\r
448     else\r
449       GNUNET_CONTAINER_multihashmap_put (mcm, &hc, cnt,\r
450           GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);\r
451   }\r
452   cnt->count++;\r
453   return GNUNET_OK;\r
454 }\r
455 \r
456 /**\r
457  * Type of a function that libextractor calls for each\r
458  * meta data item found.\r
459  *\r
460  * @param cls the container multihashmap to update\r
461  * @param plugin_name name of the plugin that produced this value;\r
462  *        special values can be used (i.e. '&lt;zlib&gt;' for zlib being\r
463  *        used in the main libextractor library and yielding\r
464  *        meta data).\r
465  * @param type libextractor-type describing the meta data\r
466  * @param format basic format information about data\r
467  * @param data_mime_type mime-type of data (not of the original file);\r
468  *        can be NULL (if mime-type is not known)\r
469  * @param data actual meta-data found\r
470  * @param data_len number of bytes in data\r
471  * @return GNUNET_OK to continue extracting / iterating\r
472  */\r
473 static int\r
474 add_to_meta_counter (void *cls, const char *plugin_name,\r
475                 enum EXTRACTOR_MetaType type, enum EXTRACTOR_MetaFormat format,\r
476                 const char *data_mime_type, const char *data, size_t data_len)\r
477 {\r
478   struct GNUNET_CONTAINER_MultiHashMap *map = cls;\r
479   GNUNET_HashCode key;\r
480   struct MetaCounter *cnt, *first_cnt;\r
481 \r
482   GNUNET_CRYPTO_hash (data, data_len, &key);\r
483   first_cnt = GNUNET_CONTAINER_multihashmap_get (map, &key);\r
484   for (cnt = first_cnt; cnt\r
485       && cnt->data_size != data_len\r
486       && memcmp (cnt->data, data, cnt->data_size) != 0; cnt = cnt->next);\r
487   if (cnt == NULL)\r
488   {\r
489     cnt = GNUNET_malloc (sizeof (struct MetaCounter));\r
490     cnt->data = data;\r
491     cnt->data_size = data_len;\r
492     cnt->plugin_name = plugin_name;\r
493     cnt->type = type;\r
494     cnt->format = format;\r
495     cnt->data_mime_type = data_mime_type;\r
496 \r
497     if (first_cnt != NULL)\r
498     {\r
499       if (first_cnt->prev != NULL)\r
500       {\r
501         first_cnt->prev->next = cnt;\r
502         cnt->prev = first_cnt->prev;\r
503       }\r
504       first_cnt->prev = cnt;\r
505       cnt->next = first_cnt;\r
506     }\r
507     else\r
508       GNUNET_CONTAINER_multihashmap_put (map, &key, cnt,\r
509           GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);\r
510   }\r
511   cnt->count++;\r
512   return 0;\r
513 }\r
514 \r
515 /**\r
516  * Allocates a struct ShareTreeItem and adds it to its parent.\r
517  */\r
518 static struct ShareTreeItem *\r
519 make_item (struct ShareTreeItem *parent)\r
520 {\r
521   struct ShareTreeItem *item;\r
522   item = GNUNET_malloc (sizeof (struct ShareTreeItem));\r
523 \r
524   item->parent = parent;\r
525   if (parent)\r
526     GNUNET_CONTAINER_DLL_insert (parent->children_head, parent->children_tail,\r
527         item);\r
528   return item;\r
529 }\r
530 \r
531 /**\r
532  * Extract metadata from a file and add it to the share tree\r
533  *\r
534  * @param adc context to modify\r
535  * @param filename name of the file to process\r
536  */\r
537 static void\r
538 extract_file (struct AddDirContext *adc, const char *filename)\r
539 {\r
540   struct ShareTreeItem *item;\r
541   const char *short_fn;\r
542 \r
543   item = make_item (adc->parent);\r
544 \r
545   GNUNET_DISK_file_size (filename, &item->file_size, GNUNET_YES);\r
546   item->is_directory = GNUNET_NO;\r
547 \r
548   item->meta = GNUNET_CONTAINER_meta_data_create ();\r
549   GNUNET_FS_meta_data_extract_from_file (item->meta, filename,\r
550       adc->plugins);\r
551   GNUNET_CONTAINER_meta_data_delete (item->meta, EXTRACTOR_METATYPE_FILENAME,\r
552       NULL, 0);\r
553   short_fn = GNUNET_STRINGS_get_short_name (filename);\r
554 \r
555   item->filename = GNUNET_strdup (filename);\r
556   item->short_filename = GNUNET_strdup (short_fn);\r
557 \r
558   GNUNET_CONTAINER_meta_data_insert (item->meta, "<libgnunetfs>",\r
559                                      EXTRACTOR_METATYPE_FILENAME,\r
560                                      EXTRACTOR_METAFORMAT_UTF8, "text/plain",\r
561                                      short_fn, strlen (short_fn) + 1);\r
562 }\r
563 \r
564 /**\r
565  * Remove the keyword from the ksk URI.\r
566  *\r
567  * @param cls the ksk uri\r
568  * @param keyword the word to remove\r
569  * @param is_mandatory ignored\r
570  * @return always GNUNET_OK\r
571  */\r
572 static int\r
573 remove_keyword (void *cls, const char *keyword, int is_mandatory)\r
574 {\r
575   struct GNUNET_FS_Uri *ksk = cls;\r
576 \r
577   GNUNET_FS_uri_ksk_remove_keyword (ksk, keyword);\r
578   return GNUNET_OK;\r
579 }\r
580 \r
581 /**\r
582  * Remove keywords from current directory's children, if they are\r
583  * in the exluded keywords list of that directory.\r
584  *\r
585  * @param cls the ksk uri\r
586  * @param keyword the word to remove\r
587  * @param is_mandatory ignored\r
588  * @return always GNUNET_OK\r
589  */\r
590 static int\r
591 remove_keywords (struct ProcessMetadataStackItem *stack, struct ShareTreeItem *dir)\r
592 {\r
593   struct ShareTreeItem *item;\r
594 \r
595   for (item = dir->children_head; item; item = item->next)\r
596   {\r
597     if (stack->exclude_ksk != NULL)\r
598       GNUNET_FS_uri_ksk_get_keywords (stack->exclude_ksk, &remove_keyword, item->ksk_uri);\r
599   }\r
600   return GNUNET_OK;\r
601 }\r
602 \r
603 /**\r
604  * Context passed to 'migrate_and_drop'.\r
605  */\r
606 struct KeywordProcessContext\r
607 {\r
608   /**\r
609    * All the keywords we migrated to the parent.\r
610    */\r
611   struct GNUNET_FS_Uri *ksk;\r
612 \r
613   /**\r
614    * How often does a keyword have to occur to be\r
615    * migrated to the parent?\r
616    */\r
617   unsigned int threshold;\r
618 };\r
619 \r
620 /**\r
621  * Context passed to 'migrate_and_drop'.\r
622  */\r
623 struct MetaProcessContext\r
624 {\r
625   /**\r
626    * All the metadata we copy to the parent.\r
627    */\r
628   struct GNUNET_CONTAINER_MetaData *meta;\r
629 \r
630   /**\r
631    * How often does a metadata have to occur to be\r
632    * migrated to the parent?\r
633    */\r
634   unsigned int threshold;\r
635 };\r
636 \r
637 \r
638 /**\r
639  * Move "frequent" keywords over to the\r
640  * target ksk uri, free the counters.\r
641  *\r
642  */\r
643 static int\r
644 migrate_and_drop (void *cls, const GNUNET_HashCode * key, void *value)\r
645 {\r
646   struct KeywordProcessContext *kpc = cls;\r
647   struct KeywordCounter *counter = value;\r
648 \r
649   if (counter->count >= kpc->threshold && counter->count > 1)\r
650   {\r
651     GNUNET_FS_uri_ksk_add_keyword (kpc->ksk, counter->value, GNUNET_NO);\r
652   }\r
653   GNUNET_free (counter);\r
654   return GNUNET_YES;\r
655 }\r
656 /**\r
657  * Copy "frequent" metadata items over to the\r
658  * target metadata container, free the counters.\r
659  *\r
660  */\r
661 static int\r
662 migrate_and_drop_metadata (void *cls, const GNUNET_HashCode * key, void *value)\r
663 {\r
664   struct MetaProcessContext *mpc = cls;\r
665   struct MetaCounter *counter = value;\r
666 \r
667   if (counter->count >= mpc->threshold && counter->count > 1)\r
668   {\r
669     GNUNET_CONTAINER_meta_data_insert (mpc->meta,\r
670                                    counter->plugin_name,\r
671                                    counter->type,\r
672                                    counter->format,\r
673                                    counter->data_mime_type, counter->data,\r
674                                    counter->data_size);\r
675   }\r
676   GNUNET_free (counter);\r
677   return GNUNET_YES;\r
678 }\r
679 \r
680 /**\r
681  * Go over the collected keywords from all entries in the\r
682  * directory and push common keywords up one level (by\r
683  * adding it to the returned struct). Do the same for metadata.\r
684  * Destroys keywordcounter and metacoutner for current directory.\r
685  *\r
686  * @param adc collection of child meta data\r
687  * @param exclude_ksk pointer to where moveable keywords will be stored\r
688  * @param copy_meta pointer to where copyable metadata will be stored\r
689  */\r
690 static void\r
691 process_keywords_and_metadata (struct ProcessMetadataStackItem *stack,\r
692     struct GNUNET_FS_Uri **exclude_ksk,\r
693     struct GNUNET_CONTAINER_MetaData **copy_meta)\r
694 {\r
695   struct KeywordProcessContext kpc;\r
696   struct MetaProcessContext mpc;\r
697   struct GNUNET_CONTAINER_MetaData *tmp;\r
698 \r
699   /* Surprisingly, it's impossible to create a ksk with 0 keywords directly.\r
700    * But we can create one from an empty metadata set\r
701    */\r
702   tmp = GNUNET_CONTAINER_meta_data_create ();\r
703   kpc.ksk = GNUNET_FS_uri_ksk_create_from_meta_data (tmp);\r
704   GNUNET_CONTAINER_meta_data_destroy (tmp);\r
705   mpc.meta = GNUNET_CONTAINER_meta_data_create ();\r
706 \r
707   kpc.threshold = mpc.threshold = (stack->dir_entry_count + 1) / 2; /* 50% */\r
708 \r
709   GNUNET_CONTAINER_multihashmap_iterate (stack->keywordcounter,\r
710       &migrate_and_drop, &kpc);\r
711   GNUNET_CONTAINER_multihashmap_iterate (stack->metacounter,\r
712       &migrate_and_drop_metadata, &mpc);\r
713 \r
714   GNUNET_CONTAINER_multihashmap_destroy (stack->keywordcounter);\r
715   GNUNET_CONTAINER_multihashmap_destroy (stack->metacounter);\r
716   *exclude_ksk = kpc.ksk;\r
717   *copy_meta = mpc.meta;\r
718 }\r
719 \r
720 /**\r
721  * Function called by the directory iterator to\r
722  * (recursively) add all of the files in the\r
723  * directory to the tree.\r
724  * Called by the directory scanner to initiate the\r
725  * scan.\r
726  * TODO: find a way to make it non-recursive.\r
727  *\r
728  * @param cls the 'struct AddDirContext*' we're in\r
729  * @param filename file or directory to scan\r
730  */\r
731 static int\r
732 scan_directory (void *cls, const char *filename)\r
733 {\r
734   struct AddDirContext *adc = cls, recurse_adc;\r
735   struct stat sbuf;\r
736   struct ShareTreeItem *item;\r
737   const char *short_fn;\r
738   int do_stop = 0;\r
739 \r
740   /* Wrap up fast */\r
741   if (adc->do_stop)\r
742     return GNUNET_SYSERR;\r
743 \r
744   /* If the file doesn't exist (or is not statable for any other reason,\r
745    * skip it, and report it.\r
746    */\r
747   if (0 != STAT (filename, &sbuf))\r
748   {\r
749     do_stop = write_progress (adc, filename, S_ISDIR (sbuf.st_mode),\r
750       GNUNET_DIR_SCANNER_DOES_NOT_EXIST);\r
751     return GNUNET_OK;\r
752   }\r
753 \r
754   /* Report the progress */\r
755   do_stop = write_progress (adc, filename, S_ISDIR (sbuf.st_mode),\r
756     GNUNET_DIR_SCANNER_NEW_FILE);\r
757   if (do_stop)\r
758   {\r
759     /* We were asked to stop, acknowledge that and return */\r
760     do_stop = write_progress (adc, filename, S_ISDIR (sbuf.st_mode),\r
761       GNUNET_DIR_SCANNER_ASKED_TO_STOP);\r
762     return GNUNET_SYSERR;\r
763   }\r
764 \r
765   if (!S_ISDIR (sbuf.st_mode))\r
766     extract_file (adc, filename);\r
767   else\r
768   {\r
769     item = make_item (adc->parent);\r
770     item->meta = GNUNET_CONTAINER_meta_data_create ();\r
771 \r
772     item->is_directory = GNUNET_YES;\r
773 \r
774     /* copy fields from adc */\r
775     recurse_adc = *adc;\r
776     /* replace recurse_adc contents with the ones for this directory */\r
777     recurse_adc.parent = item;\r
778 \r
779     /* recurse into directory */\r
780     GNUNET_DISK_directory_scan (filename, &scan_directory, &recurse_adc);\r
781 \r
782     short_fn = GNUNET_STRINGS_get_short_name (filename);\r
783 \r
784     item->filename = GNUNET_strdup (filename);\r
785     item->short_filename = GNUNET_strdup (short_fn);\r
786 \r
787     if (adc->parent == NULL)\r
788     {\r
789       /* we're finished with the scan, make sure caller gets the top-level\r
790        * directory pointer\r
791        */\r
792       adc->parent = item;\r
793     }\r
794   }\r
795   return GNUNET_OK;\r
796 }\r
797 \r
798 /**\r
799  * Signals the scanner to finish the scan as fast as possible.\r
800  * Does not block.\r
801  * Can close the pipe if asked to, but that is only used by the\r
802  * internal call to this function during cleanup. The client\r
803  * must understand the consequences of closing the pipe too early.\r
804  *\r
805  * @param ds directory scanner structure\r
806  * @param close_pipe GNUNET_YES to close\r
807  */\r
808 void\r
809 GNUNET_FS_directory_scan_finish (struct GNUNET_FS_DirScanner *ds,\r
810     int close_pipe)\r
811 {\r
812 #if WINDOWS\r
813   SetEvent (ds->stop);\r
814 #else\r
815   sem_post (&ds->stop);\r
816 #endif\r
817   if (close_pipe)\r
818   {\r
819     if (ds->progress_read_task != GNUNET_SCHEDULER_NO_TASK)\r
820     {\r
821       GNUNET_SCHEDULER_cancel (ds->progress_read_task);\r
822       ds->progress_read_task = GNUNET_SCHEDULER_NO_TASK;\r
823     }\r
824     GNUNET_DISK_pipe_close_end (ds->progress_pipe, GNUNET_DISK_PIPE_END_READ);\r
825     ds->progress_read = NULL;\r
826   }\r
827 }\r
828 \r
829 /**\r
830  * Signals the scanner thread to finish (in case it isn't finishing\r
831  * already) and joins the scanner thread. Closes the pipes, frees the\r
832  * scanner contexts (both of them), returns the results of the scan.\r
833  * Results are valid (and have to be freed) even if the scanner had\r
834  * an error or was rushed to finish prematurely.\r
835  * Blocks until the scanner is finished.\r
836  *\r
837  * @param ds directory scanner structure\r
838  * @return the results of the scan (a directory tree)\r
839  */\r
840 struct ShareTreeItem *\r
841 GNUNET_FS_directory_scan_cleanup (struct GNUNET_FS_DirScanner *ds)\r
842 {\r
843   struct ShareTreeItem *result;\r
844 \r
845   GNUNET_FS_directory_scan_finish (ds, GNUNET_YES);\r
846 #if WINDOWS\r
847   WaitForSingleObject (ds->thread, INFINITE);\r
848   CloseHandle (ds->stop);\r
849   CloseHandle (ds->thread);\r
850 #else\r
851   pthread_join (ds->thread, NULL);\r
852   sem_destroy (&ds->stop);\r
853   pthread_detach (ds->thread);\r
854 #endif\r
855 \r
856   GNUNET_DISK_pipe_close (ds->progress_pipe);\r
857   result = ds->adc->parent;\r
858   GNUNET_free (ds->adc);\r
859   GNUNET_free (ds);\r
860   return result;\r
861 }\r
862 \r
863 /**\r
864  * The function from which the scanner thread starts\r
865  */\r
866 #if WINDOWS\r
867 static DWORD\r
868 #else\r
869 static int\r
870 #endif\r
871 run_directory_scan_thread (struct AddDirContext *adc)\r
872 {\r
873   scan_directory (adc, adc->filename_expanded);\r
874   GNUNET_free (adc->filename_expanded);\r
875   if (adc->plugins != NULL)\r
876     EXTRACTOR_plugin_remove_all (adc->plugins);\r
877   /* Tell the initiator that we're finished, it can now join the thread */\r
878   write_progress (adc, NULL, 0, GNUNET_DIR_SCANNER_FINISHED);\r
879   return 0;\r
880 }\r
881 \r
882 /**\r
883  * Called every time there is data to read from the scanner.\r
884  * Calls the scanner progress handler.\r
885  *\r
886  * @param cls the closure (directory scanner object)\r
887  * @param tc task context in which the task is running\r
888  */\r
889 static void\r
890 read_progress_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)\r
891 {\r
892   struct GNUNET_FS_DirScanner *ds;\r
893   int end_it = 0;\r
894   enum GNUNET_DirScannerProgressUpdateReason reason;\r
895   ssize_t rd;\r
896   ssize_t total_read;\r
897 \r
898   size_t filename_len;\r
899   char is_directory;\r
900   char *filename;\r
901 \r
902   ds = cls;\r
903 \r
904   ds->progress_read_task = GNUNET_SCHEDULER_NO_TASK;\r
905 \r
906   if (!(tc->reason & GNUNET_SCHEDULER_REASON_READ_READY))\r
907   {\r
908     ds->progress_callback (ds->cls, ds, NULL, 0, GNUNET_DIR_SCANNER_SHUTDOWN);\r
909     return;\r
910   }\r
911 \r
912   /* Read one message. If message is malformed or can't be read, end the scanner */\r
913   total_read = rd = GNUNET_DISK_file_read (ds->progress_read, &reason, sizeof (reason));\r
914   while (rd > 0 && total_read < sizeof (reason))\r
915   {\r
916     rd = GNUNET_DISK_file_read (ds->progress_read,\r
917         &((char *) &reason)[total_read],\r
918         sizeof (reason) - total_read);\r
919     if (rd > 0)\r
920       total_read += rd;\r
921   }\r
922   if (total_read != sizeof (reason)\r
923       || reason <= GNUNET_DIR_SCANNER_FIRST\r
924       || reason >= GNUNET_DIR_SCANNER_LAST)\r
925   {\r
926     end_it = 1;\r
927     reason = GNUNET_DIR_SCANNER_PROTOCOL_ERROR;\r
928   }\r
929 \r
930   if (!end_it)\r
931   {\r
932     total_read = rd = GNUNET_DISK_file_read (ds->progress_read, &filename_len,\r
933         sizeof (size_t));\r
934     while (rd > 0 && total_read < sizeof (size_t))\r
935     {\r
936       rd = GNUNET_DISK_file_read (ds->progress_read,\r
937           &((char *) &filename_len)[total_read],\r
938           sizeof (size_t) - total_read);\r
939       if (rd > 0)\r
940         total_read += rd;\r
941     }\r
942     if (rd != sizeof (size_t))\r
943     {\r
944       end_it = 1;\r
945       reason = GNUNET_DIR_SCANNER_PROTOCOL_ERROR;\r
946     }\r
947   }\r
948   if (!end_it)\r
949   {\r
950     if (filename_len == 0)\r
951       end_it = 1;\r
952     else if (filename_len > MAX_PATH)\r
953     {\r
954       end_it = 1;\r
955       reason = GNUNET_DIR_SCANNER_PROTOCOL_ERROR;\r
956     }\r
957   }\r
958   if (!end_it)\r
959   {\r
960     filename = GNUNET_malloc (filename_len);\r
961     total_read = rd = GNUNET_DISK_file_read (ds->progress_read, filename,\r
962         filename_len);\r
963     while (rd > 0 && total_read < filename_len)\r
964     {\r
965       rd = GNUNET_DISK_file_read (ds->progress_read, &filename[total_read],\r
966           filename_len - total_read);\r
967       if (rd > 0)\r
968         total_read += rd;\r
969     }\r
970     if (rd != filename_len)\r
971     {\r
972       GNUNET_free (filename);\r
973       reason = GNUNET_DIR_SCANNER_PROTOCOL_ERROR;\r
974       end_it = 1;\r
975     }\r
976   }\r
977   if (!end_it && filename_len > 0)\r
978   {\r
979     total_read = rd = GNUNET_DISK_file_read (ds->progress_read, &is_directory,\r
980         sizeof (char));\r
981     while (rd > 0 && total_read < sizeof (char))\r
982     {\r
983       rd = GNUNET_DISK_file_read (ds->progress_read, &(&is_directory)[total_read],\r
984           sizeof (char) - total_read);\r
985       if (rd > 0)\r
986         total_read += rd;\r
987     }\r
988     if (rd != sizeof (char))\r
989     {\r
990       GNUNET_free (filename);\r
991       reason = GNUNET_DIR_SCANNER_PROTOCOL_ERROR;\r
992       end_it = 1;\r
993     }\r
994   }\r
995   if (!end_it)\r
996   {\r
997     end_it = ds->progress_callback (ds->cls, ds, (const char *) filename, is_directory, reason);\r
998     GNUNET_free (filename);\r
999     if (!end_it)\r
1000     {\r
1001       ds->progress_read_task = GNUNET_SCHEDULER_add_read_file (\r
1002           GNUNET_TIME_UNIT_FOREVER_REL, ds->progress_read, &read_progress_task,\r
1003           cls);\r
1004     }\r
1005   }\r
1006   else\r
1007   {\r
1008     ds->progress_callback (ds->cls, ds, NULL, 0, reason);\r
1009   }\r
1010 }\r
1011 \r
1012 \r
1013 /**\r
1014  * Start a directory scanner thread.\r
1015  *\r
1016  * @param filename name of the directory to scan\r
1017  * @param GNUNET_YES to not to run libextractor on files (only build a tree)\r
1018  * @param ex if not NULL, must be a list of extra plugins for extractor\r
1019  * @param cb the callback to call when there are scanning progress messages\r
1020  * @param cls closure for 'cb'\r
1021  * @return directory scanner object to be used for controlling the scanner\r
1022  */\r
1023 struct GNUNET_FS_DirScanner *\r
1024 GNUNET_FS_directory_scan_start (const char *filename,\r
1025     int disable_extractor, const char *ex,\r
1026     GNUNET_FS_DirScannerProgressCallback cb, void *cls)\r
1027 {\r
1028   struct stat sbuf;\r
1029   struct AddDirContext *adc;\r
1030   char *filename_expanded;\r
1031   struct GNUNET_FS_DirScanner *ds;\r
1032   struct GNUNET_DISK_PipeHandle *progress_pipe;\r
1033   int ok;\r
1034 \r
1035   if (0 != STAT (filename, &sbuf))\r
1036     return NULL;\r
1037   /* TODO: consider generalizing this for files too! */\r
1038   if (!S_ISDIR (sbuf.st_mode))\r
1039   {\r
1040     GNUNET_break (0);\r
1041     return NULL;\r
1042   }\r
1043   /* scan_directory() is guaranteed to be given expanded filenames,\r
1044    * so expand we will!\r
1045    */\r
1046   filename_expanded = GNUNET_STRINGS_filename_expand (filename);\r
1047   if (filename_expanded == NULL)\r
1048     return NULL;\r
1049 \r
1050   progress_pipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO, GNUNET_NO, GNUNET_NO);\r
1051   if (progress_pipe == NULL)\r
1052   {\r
1053     GNUNET_free (filename_expanded);\r
1054     return NULL;\r
1055   }\r
1056 \r
1057   adc = GNUNET_malloc (sizeof (struct AddDirContext));\r
1058 \r
1059   ds = GNUNET_malloc (sizeof (struct GNUNET_FS_DirScanner));\r
1060 \r
1061   ds->adc = adc;\r
1062 \r
1063 #if WINDOWS\r
1064   ds->stop = CreateEvent (NULL, TRUE, FALSE, NULL);\r
1065   adc->stop = ds->stop;\r
1066   ok = ds->stop != INVALID_HANDLE_VALUE;\r
1067 #else\r
1068   ok = !sem_init (&ds->stop, 0, 0);\r
1069   adc = &ds->stop;\r
1070 #endif\r
1071   if (!ok)\r
1072   {\r
1073     GNUNET_free (adc);\r
1074     GNUNET_free (ds);\r
1075     GNUNET_free (filename_expanded);\r
1076     GNUNET_DISK_pipe_close (progress_pipe);\r
1077     return NULL;\r
1078   }\r
1079 \r
1080   adc->plugins = NULL;\r
1081   if (!disable_extractor)\r
1082   {\r
1083     adc->plugins = EXTRACTOR_plugin_add_defaults (\r
1084         EXTRACTOR_OPTION_DEFAULT_POLICY);\r
1085     if (ex && strlen (ex) > 0)\r
1086       adc->plugins = EXTRACTOR_plugin_add_config (adc->plugins, ex,\r
1087           EXTRACTOR_OPTION_DEFAULT_POLICY);\r
1088   }\r
1089 \r
1090   adc->filename_expanded = filename_expanded;\r
1091   adc->progress_write = GNUNET_DISK_pipe_handle (progress_pipe,\r
1092       GNUNET_DISK_PIPE_END_WRITE);\r
1093 \r
1094 \r
1095   ds->progress_read = GNUNET_DISK_pipe_handle (progress_pipe,\r
1096       GNUNET_DISK_PIPE_END_READ);\r
1097 \r
1098 #if WINDOWS\r
1099   ds->thread = CreateThread (NULL, 0,\r
1100       (LPTHREAD_START_ROUTINE) &run_directory_scan_thread, (LPVOID) adc,\r
1101       0, NULL);\r
1102   ok = ds->thread != NULL;\r
1103 #else\r
1104   ok = !pthread_create (&ds->thread, NULL, &run_directory_scan_thread,\r
1105       (void *) adc);\r
1106 #endif\r
1107   if (!ok)\r
1108   {\r
1109     GNUNET_free (adc);\r
1110     GNUNET_free (filename_expanded);\r
1111     GNUNET_DISK_pipe_close (progress_pipe);\r
1112     GNUNET_free (ds);\r
1113     return NULL;\r
1114   }\r
1115 \r
1116   ds->progress_callback = cb;\r
1117   ds->cls = cls;\r
1118   ds->adc = adc;\r
1119   ds->progress_pipe = progress_pipe;\r
1120 \r
1121   ds->progress_read_task = GNUNET_SCHEDULER_add_read_file (\r
1122       GNUNET_TIME_UNIT_FOREVER_REL, ds->progress_read, &read_progress_task,\r
1123       ds);\r
1124 \r
1125   return ds;\r
1126 }\r
1127 \r
1128 /**\r
1129  * Task that post-processes the share item tree.\r
1130  * This processing has to be done in the main thread, because\r
1131  * it requires access to libgcrypt's hashing functions, and\r
1132  * libgcrypt is not thread-safe without some special magic.\r
1133  *\r
1134  * @param cls top of the stack\r
1135  * @param tc task context\r
1136  */\r
1137 static void\r
1138 trim_share_tree_task (void *cls,\r
1139   const struct GNUNET_SCHEDULER_TaskContext *tc)\r
1140 {\r
1141   struct ProcessMetadataStackItem *stack = cls;\r
1142   struct ProcessMetadataStackItem *next = stack;\r
1143   /* FIXME: figure out what to do when tc says we're shutting down */\r
1144 \r
1145   /* item == NULL means that we've just finished going over the children of\r
1146    * current directory.\r
1147    */\r
1148   if (stack->item == NULL)\r
1149   {\r
1150     if (stack->parent->item != NULL)\r
1151     {\r
1152       /* end of a directory */\r
1153       struct GNUNET_FS_Uri *ksk;\r
1154 \r
1155       /* use keyword and metadata counters to create lists of keywords to move\r
1156        * and metadata to copy.\r
1157        */\r
1158       process_keywords_and_metadata (stack, &stack->parent->exclude_ksk, &stack->parent->item->meta);\r
1159 \r
1160       /* create keywords from metadata (copies all text-metadata as keywords,\r
1161        * AND parses the directory name we've just added, producing even more\r
1162        * keywords.\r
1163        * then merge these keywords with the ones moved from children.\r
1164        */\r
1165       ksk = GNUNET_FS_uri_ksk_create_from_meta_data (stack->parent->item->meta);\r
1166       stack->parent->item->ksk_uri = GNUNET_FS_uri_ksk_merge (ksk, stack->parent->exclude_ksk);\r
1167       GNUNET_FS_uri_destroy (ksk);\r
1168 \r
1169       /* remove moved keywords from children (complete the move) */\r
1170       remove_keywords (stack->parent, stack->parent->item);\r
1171       GNUNET_FS_uri_destroy (stack->parent->exclude_ksk);\r
1172 \r
1173       /* go up the stack */\r
1174       next = stack->parent;\r
1175       GNUNET_free (stack);\r
1176       next->end_directory = GNUNET_YES;\r
1177     }\r
1178     else\r
1179     {\r
1180       /* we've just finished processing the toplevel directory */\r
1181       struct ProcessMetadataContext *ctx = stack->ctx;\r
1182       next = NULL;\r
1183       GNUNET_SCHEDULER_add_continuation (ctx->cb, ctx->cls,\r
1184           GNUNET_SCHEDULER_REASON_PREREQ_DONE);\r
1185       GNUNET_free (stack->parent);\r
1186       GNUNET_free (stack);\r
1187       GNUNET_free (ctx);\r
1188     }\r
1189   }\r
1190   else if (stack->item->is_directory\r
1191       && !stack->end_directory\r
1192       && stack->item->children_head != NULL)\r
1193   {\r
1194     /* recurse into subdirectory */\r
1195     next = GNUNET_malloc (sizeof (struct ProcessMetadataStackItem));\r
1196     next->ctx = stack->ctx;\r
1197     next->item = stack->item->children_head;\r
1198     next->keywordcounter = GNUNET_CONTAINER_multihashmap_create (1024);\r
1199     next->metacounter = GNUNET_CONTAINER_multihashmap_create (1024);\r
1200     next->dir_entry_count = 0;\r
1201     next->parent = stack;\r
1202   }\r
1203   else\r
1204   {\r
1205     /* process a child entry (a file or a directory) and move to the next one*/\r
1206     if (stack->item->is_directory)\r
1207       stack->end_directory = GNUNET_NO;\r
1208     stack->dir_entry_count++;\r
1209     GNUNET_CONTAINER_meta_data_iterate (stack->item->meta, &add_to_meta_counter, stack->metacounter);\r
1210 \r
1211     if (stack->item->is_directory)\r
1212     {\r
1213       char *user = getenv ("USER");\r
1214       if ((user == NULL) || (0 != strncasecmp (user, stack->item->short_filename, strlen(user))))\r
1215       {\r
1216         /* only use filename if it doesn't match $USER */\r
1217         GNUNET_CONTAINER_meta_data_insert (stack->item->meta, "<libgnunetfs>",\r
1218                                            EXTRACTOR_METATYPE_FILENAME,\r
1219                                            EXTRACTOR_METAFORMAT_UTF8,\r
1220                                            "text/plain", stack->item->short_filename,\r
1221                                            strlen (stack->item->short_filename) + 1);\r
1222         GNUNET_CONTAINER_meta_data_insert (stack->item->meta, "<libgnunetfs>",\r
1223                                            EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME,\r
1224                                            EXTRACTOR_METAFORMAT_UTF8,\r
1225                                            "text/plain", stack->item->short_filename,\r
1226                                            strlen (stack->item->short_filename) + 1);\r
1227       }\r
1228     }\r
1229 \r
1230     stack->item->ksk_uri = GNUNET_FS_uri_ksk_create_from_meta_data (stack->item->meta);\r
1231     GNUNET_FS_uri_ksk_get_keywords (stack->item->ksk_uri, &add_to_keyword_counter, stack->keywordcounter);\r
1232     stack->item = stack->item->next;\r
1233   }\r
1234   /* Call this task again later, if there are more entries to process */\r
1235   if (next)\r
1236     GNUNET_SCHEDULER_add_continuation (&trim_share_tree_task, next,\r
1237         GNUNET_SCHEDULER_REASON_PREREQ_DONE);\r
1238 }\r
1239 \r
1240 /**\r
1241  * Process a share item tree, moving frequent keywords up and\r
1242  * copying frequent metadata up.\r
1243  *\r
1244  * @param toplevel toplevel directory in the tree, returned by the scanner\r
1245  * @param cb called after processing is done\r
1246  * @param cls closure for 'cb'\r
1247  */\r
1248 struct ProcessMetadataContext *\r
1249 GNUNET_FS_trim_share_tree (struct ShareTreeItem *toplevel,\r
1250     GNUNET_SCHEDULER_Task cb, void *cls)\r
1251 {\r
1252   struct ProcessMetadataContext *ret;\r
1253 \r
1254   if (toplevel == NULL)\r
1255   {\r
1256     struct GNUNET_SCHEDULER_TaskContext tc;\r
1257     tc.reason = GNUNET_SCHEDULER_REASON_PREREQ_DONE;\r
1258     cb (cls, &tc);\r
1259     return NULL;\r
1260   }\r
1261 \r
1262   ret = GNUNET_malloc (sizeof (struct ProcessMetadataContext));\r
1263   ret->toplevel = toplevel;\r
1264   ret->stack = GNUNET_malloc (sizeof (struct ProcessMetadataStackItem));\r
1265   ret->stack->ctx = ret;\r
1266   ret->stack->item = toplevel;\r
1267   ret->stack->keywordcounter = GNUNET_CONTAINER_multihashmap_create (1024);\r
1268   ret->stack->metacounter = GNUNET_CONTAINER_multihashmap_create (1024);\r
1269   ret->stack->dir_entry_count = 0;\r
1270   ret->stack->end_directory = GNUNET_NO;\r
1271 \r
1272   /* dummy stack entry that tells us we're at the top of the stack */\r
1273   ret->stack->parent = GNUNET_malloc (sizeof (struct ProcessMetadataStackItem));\r
1274   ret->stack->parent->ctx = ret;\r
1275 \r
1276   ret->cb = cb;\r
1277   ret->cls = cls;\r
1278 \r
1279   GNUNET_SCHEDULER_add_continuation (&trim_share_tree_task, ret->stack,\r
1280     GNUNET_SCHEDULER_REASON_PREREQ_DONE);\r
1281   return ret;\r
1282 }