2 This file is part of GNUnet
3 Copyright (C) 2005-2012 GNUnet e.V.
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.
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.
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/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
22 * @file fs/fs_dirmetascan.c
23 * @brief code to asynchronously build a 'struct GNUNET_FS_ShareTreeItem'
24 * from an on-disk directory for publishing; use the 'gnunet-helper-fs-publish'.
26 * @author Christian Grothoff
29 #include "gnunet_fs_service.h"
30 #include "gnunet_scheduler_lib.h"
35 * An opaque structure a pointer to which is returned to the
36 * caller to be used to control the scanner.
38 struct GNUNET_FS_DirScanner {
42 struct GNUNET_HELPER_Handle *helper;
45 * Expanded filename (as given by the scan initiator).
46 * The scanner thread stores a copy here, and frees it when it finishes.
48 char *filename_expanded;
51 * Second argument to helper process.
56 * The function that will be called every time there's a progress
59 GNUNET_FS_DirScannerProgressCallback progress_callback;
62 * A closure for progress_callback.
64 void *progress_callback_cls;
67 * After the scan is finished, it will contain a pointer to the
68 * top-level directory entry in the directory tree built by the
71 struct GNUNET_FS_ShareTreeItem *toplevel;
74 * Current position during processing.
76 struct GNUNET_FS_ShareTreeItem *pos;
79 * Task scheduled when we are done.
81 struct GNUNET_SCHEDULER_Task *stop_task;
84 * Arguments for helper.
91 * Abort the scan. Must not be called from within the progress_callback
94 * @param ds directory scanner structure
97 GNUNET_FS_directory_scan_abort(struct GNUNET_FS_DirScanner *ds)
99 /* terminate helper */
100 if (NULL != ds->helper)
101 GNUNET_HELPER_stop(ds->helper, GNUNET_NO);
104 if (NULL != ds->toplevel)
105 GNUNET_FS_share_tree_free(ds->toplevel);
106 if (NULL != ds->stop_task)
107 GNUNET_SCHEDULER_cancel(ds->stop_task);
108 GNUNET_free_non_null(ds->ex_arg);
109 GNUNET_free(ds->filename_expanded);
115 * Obtain the result of the scan after the scan has signalled
116 * completion. Must not be called prior to completion. The 'ds' is
117 * freed as part of this call.
119 * @param ds directory scanner structure
120 * @return the results of the scan (a directory tree)
122 struct GNUNET_FS_ShareTreeItem *
123 GNUNET_FS_directory_scan_get_result(struct GNUNET_FS_DirScanner *ds)
125 struct GNUNET_FS_ShareTreeItem *result;
127 /* check that we're actually done */
128 GNUNET_assert(NULL == ds->helper);
129 /* preserve result */
130 result = ds->toplevel;
132 GNUNET_FS_directory_scan_abort(ds);
138 * Move in the directory from the given position to the next file
141 * @param pos current position
142 * @return next file, NULL for none
144 static struct GNUNET_FS_ShareTreeItem *
145 advance(struct GNUNET_FS_ShareTreeItem *pos)
149 GNUNET_assert(NULL != pos);
150 moved = 0; /* must not terminate, even on file, otherwise "normal" */
151 while ((pos->is_directory == GNUNET_YES) || (0 == moved))
153 if ((moved != -1) && (NULL != pos->children_head))
155 pos = pos->children_head;
156 moved = 1; /* can terminate if file */
159 if (NULL != pos->next)
162 moved = 1; /* can terminate if file */
165 if (NULL != pos->parent)
168 moved = -1; /* force move to 'next' or 'parent' */
171 /* no more options, end of traversal */
179 * Add another child node to the tree.
181 * @param parent parent of the child, NULL for top level
182 * @param filename name of the file or directory
183 * @param is_directory GNUNET_YES for directories
184 * @return new entry that was just created
186 static struct GNUNET_FS_ShareTreeItem *
187 expand_tree(struct GNUNET_FS_ShareTreeItem *parent,
188 const char *filename,
191 struct GNUNET_FS_ShareTreeItem *chld;
194 chld = GNUNET_new(struct GNUNET_FS_ShareTreeItem);
195 chld->parent = parent;
196 chld->filename = GNUNET_strdup(filename);
197 GNUNET_asprintf(&chld->short_filename,
199 GNUNET_STRINGS_get_short_name(filename),
200 is_directory == GNUNET_YES ? "/" : "");
201 /* make sure we do not end with '//' */
202 slen = strlen(chld->short_filename);
203 if ((slen >= 2) && (chld->short_filename[slen - 1] == '/') &&
204 (chld->short_filename[slen - 2] == '/'))
205 chld->short_filename[slen - 1] = '\0';
206 chld->is_directory = is_directory;
208 GNUNET_CONTAINER_DLL_insert(parent->children_head,
209 parent->children_tail,
216 * Task run last to shut everything down.
218 * @param cls the 'struct GNUNET_FS_DirScanner'
221 finish_scan(void *cls)
223 struct GNUNET_FS_DirScanner *ds = cls;
225 ds->stop_task = NULL;
226 if (NULL != ds->helper)
228 GNUNET_HELPER_stop(ds->helper, GNUNET_NO);
231 ds->progress_callback(ds->progress_callback_cls,
234 GNUNET_FS_DIRSCANNER_FINISHED);
239 * Called every time there is data to read from the scanner.
240 * Calls the scanner progress handler.
242 * @param cls the closure (directory scanner object)
243 * @param msg message from the helper process
244 * @return #GNUNET_OK on success,
245 * #GNUNET_NO to stop further processing (no error)
246 * #GNUNET_SYSERR to stop further processing with error
249 process_helper_msgs(void *cls, const struct GNUNET_MessageHeader *msg)
251 struct GNUNET_FS_DirScanner *ds = cls;
252 const char *filename;
257 "DMS parses %u-byte message of type %u\n",
258 (unsigned int)ntohs(msg->size),
259 (unsigned int)ntohs(msg->type));
261 left = ntohs(msg->size) - sizeof(struct GNUNET_MessageHeader);
262 filename = (const char *)&msg[1];
263 switch (ntohs(msg->type))
265 case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_PROGRESS_FILE:
266 if (filename[left - 1] != '\0')
271 ds->progress_callback(ds->progress_callback_cls,
274 GNUNET_FS_DIRSCANNER_FILE_START);
275 if (NULL == ds->toplevel)
277 ds->toplevel = expand_tree(ds->pos, filename, GNUNET_NO);
281 GNUNET_assert(NULL != ds->pos);
282 (void)expand_tree(ds->pos, filename, GNUNET_NO);
286 case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_PROGRESS_DIRECTORY:
287 if (filename[left - 1] != '\0')
292 if (0 == strcmp("..", filename))
299 ds->pos = ds->pos->parent;
302 ds->progress_callback(ds->progress_callback_cls,
305 GNUNET_FS_DIRSCANNER_FILE_START);
306 ds->pos = expand_tree(ds->pos, filename, GNUNET_YES);
307 if (NULL == ds->toplevel)
308 ds->toplevel = ds->pos;
311 case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_ERROR:
314 case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_SKIP_FILE:
315 if ('\0' != filename[left - 1])
317 ds->progress_callback(ds->progress_callback_cls,
320 GNUNET_FS_DIRSCANNER_FILE_IGNORED);
323 case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_COUNTING_DONE:
329 if (NULL == ds->toplevel)
331 ds->progress_callback(ds->progress_callback_cls,
334 GNUNET_FS_DIRSCANNER_ALL_COUNTED);
335 ds->pos = ds->toplevel;
336 if (GNUNET_YES == ds->pos->is_directory)
337 ds->pos = advance(ds->pos);
340 case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_META_DATA: {
349 end = memchr(filename, 0, left);
356 nlen = end - filename;
358 if (0 != strcmp(filename, ds->pos->filename))
363 ds->progress_callback(ds->progress_callback_cls,
366 GNUNET_FS_DIRSCANNER_EXTRACT_FINISHED);
369 ds->pos->meta = GNUNET_CONTAINER_meta_data_deserialize(end, left);
370 if (NULL == ds->pos->meta)
375 /* having full filenames is too dangerous; always make sure we clean them up */
376 GNUNET_CONTAINER_meta_data_delete(ds->pos->meta,
377 EXTRACTOR_METATYPE_FILENAME,
380 /* instead, put in our 'safer' original filename */
381 GNUNET_CONTAINER_meta_data_insert(ds->pos->meta,
383 EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME,
384 EXTRACTOR_METAFORMAT_UTF8,
386 ds->pos->short_filename,
387 strlen(ds->pos->short_filename) + 1);
389 ds->pos->ksk_uri = GNUNET_FS_uri_ksk_create_from_meta_data(ds->pos->meta);
390 ds->pos = advance(ds->pos);
394 case GNUNET_MESSAGE_TYPE_FS_PUBLISH_HELPER_FINISHED:
405 if (NULL == ds->toplevel)
407 ds->stop_task = GNUNET_SCHEDULER_add_now(&finish_scan, ds);
414 ds->progress_callback(ds->progress_callback_cls,
417 GNUNET_FS_DIRSCANNER_INTERNAL_ERROR);
423 * Function called if our helper process died.
425 * @param cls the 'struct GNUNET_FS_DirScanner' callback.
428 helper_died_cb(void *cls)
430 struct GNUNET_FS_DirScanner *ds = cls;
433 if (NULL != ds->stop_task)
434 return; /* normal death, was finished */
435 ds->progress_callback(ds->progress_callback_cls,
438 GNUNET_FS_DIRSCANNER_INTERNAL_ERROR);
443 * Start a directory scanner thread.
445 * @param filename name of the directory to scan
446 * @param disable_extractor #GNUNET_YES to not run libextractor on files (only
448 * @param ex if not NULL, must be a list of extra plugins for extractor
449 * @param cb the callback to call when there are scanning progress messages
450 * @param cb_cls closure for 'cb'
451 * @return directory scanner object to be used for controlling the scanner
453 struct GNUNET_FS_DirScanner *
454 GNUNET_FS_directory_scan_start(const char *filename,
455 int disable_extractor,
457 GNUNET_FS_DirScannerProgressCallback cb,
461 char *filename_expanded;
462 struct GNUNET_FS_DirScanner *ds;
464 if (0 != stat(filename, &sbuf))
466 filename_expanded = GNUNET_STRINGS_filename_expand(filename);
467 if (NULL == filename_expanded)
469 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG,
470 "Starting to scan directory `%s'\n",
472 ds = GNUNET_new(struct GNUNET_FS_DirScanner);
473 ds->progress_callback = cb;
474 ds->progress_callback_cls = cb_cls;
475 ds->filename_expanded = filename_expanded;
476 if (disable_extractor)
477 ds->ex_arg = GNUNET_strdup("-");
479 ds->ex_arg = (NULL != ex) ? GNUNET_strdup(ex) : NULL;
480 ds->args[0] = "gnunet-helper-fs-publish";
481 ds->args[1] = ds->filename_expanded;
482 ds->args[2] = ds->ex_arg;
484 ds->helper = GNUNET_HELPER_start(GNUNET_NO,
485 "gnunet-helper-fs-publish",
487 &process_helper_msgs,
490 if (NULL == ds->helper)
492 GNUNET_free(filename_expanded);
500 /* end of fs_dirmetascan.c */