Location | Tool | Test ID | Function | Issue |
---|---|---|---|---|
tracker-file-system.c:828:6 | clang-analyzer | Branch condition evaluates to a garbage value |
1 /*
2 * Copyright (C) 2011, Nokia <ivan.frade@nokia.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 *
19 * Author: Carlos Garnacho <carlos@lanedo.com>
20 */
21
22 #include <string.h>
23 #include <stdlib.h>
24
25 #include "tracker-file-system.h"
26
27 typedef struct _TrackerFileSystemPrivate TrackerFileSystemPrivate;
28 typedef struct _FileNodeProperty FileNodeProperty;
29 typedef struct _FileNodeData FileNodeData;
30 typedef struct _NodeLookupData NodeLookupData;
31
32 static GHashTable *properties = NULL;
33
34 struct _TrackerFileSystemPrivate {
35 GNode *file_tree;
36 };
37
38 struct _FileNodeProperty {
39 GQuark prop_quark;
40 gpointer value;
41 };
42
43 struct _FileNodeData {
44 GFile *file;
45 gchar *uri_suffix;
46 GArray *properties;
47 guint shallow : 1;
48 guint unowned : 1;
49 guint file_type : 4;
50 };
51
52 struct _NodeLookupData {
53 TrackerFileSystem *file_system;
54 GNode *node;
55 };
56
57 static GQuark quark_file_node = 0;
58
59 static void file_weak_ref_notify (gpointer user_data,
60 GObject *prev_location);
61
62 G_DEFINE_TYPE (TrackerFileSystem, tracker_file_system, G_TYPE_OBJECT)
63
64 /*
65 * TrackerFileSystem is a filesystem abstraction, it mainly serves 2 purposes:
66 * - Canonicalizes GFiles, so it is possible later to perform pointer
67 * comparisons on them.
68 * - Stores data for the GFile lifetime, so it may be used as cache store
69 * as long as some file is needed.
70 *
71 * The TrackerFileSystem holds a reference on each GFile. There are two cases
72 * when we want to force a cached GFile to be freed: when it no longer exists
73 * on disk, and once crawling a directory has completed and we only need to
74 * remember the directories. Objects may persist in the cache even after
75 * tracker_file_system_forget_files() is called to delete them if there are
76 * references held on them elsewhere, and they will stay until all references
77 * are dropped.
78 */
79
80
81 static void
82 file_node_data_free (FileNodeData *data,
83 GNode *node)
84 {
85 guint i;
86
87 if (data->file) {
88 if (!data->shallow) {
89 g_object_weak_unref (G_OBJECT (data->file),
90 file_weak_ref_notify,
91 node);
92 }
93
94 if (!data->unowned) {
95 g_object_unref (data->file);
96 }
97 }
98
99 data->file = NULL;
100 g_free (data->uri_suffix);
101
102 for (i = 0; i < data->properties->len; i++) {
103 FileNodeProperty *property;
104 GDestroyNotify destroy_notify;
105
106 property = &g_array_index (data->properties,
107 FileNodeProperty, i);
108
109 destroy_notify = g_hash_table_lookup (properties,
110 GUINT_TO_POINTER (property->prop_quark));
111
112 if (destroy_notify) {
113 (destroy_notify) (property->value);
114 }
115 }
116
117 g_array_free (data->properties, TRUE);
118 g_slice_free (FileNodeData, data);
119 }
120
121 static FileNodeData *
122 file_node_data_new (TrackerFileSystem *file_system,
123 GFile *file,
124 GFileType file_type,
125 GNode *node)
126 {
127 FileNodeData *data;
128 NodeLookupData lookup_data;
129 GArray *node_data;
130
131 data = g_slice_new0 (FileNodeData);
132 data->file = g_object_ref (file);
133 data->file_type = file_type;
134 data->properties = g_array_new (FALSE, TRUE, sizeof (FileNodeProperty));
135
136 /* We use weak refs to keep track of files */
137 g_object_weak_ref (G_OBJECT (data->file), file_weak_ref_notify, node);
138
139 node_data = g_object_get_qdata (G_OBJECT (data->file),
140 quark_file_node);
141
142 if (!node_data) {
143 node_data = g_array_new (FALSE, FALSE, sizeof (NodeLookupData));
144 g_object_set_qdata_full (G_OBJECT (data->file),
145 quark_file_node,
146 node_data,
147 (GDestroyNotify) g_array_unref);
148 }
149
150 lookup_data.file_system = file_system;
151 lookup_data.node = node;
152 g_array_append_val (node_data, lookup_data);
153
154 g_assert (node->data == NULL);
155 node->data = data;
156
157 return data;
158 }
159
160 static FileNodeData *
161 file_node_data_root_new (void)
162 {
163 FileNodeData *data;
164
165 data = g_slice_new0 (FileNodeData);
166 data->uri_suffix = g_strdup ("file:///");
167 data->file = g_file_new_for_uri (data->uri_suffix);
168 data->properties = g_array_new (FALSE, TRUE, sizeof (FileNodeProperty));
169 data->file_type = G_FILE_TYPE_DIRECTORY;
170 data->shallow = TRUE;
171
172 return data;
173 }
174
175 static gboolean
176 file_node_data_equal_or_child (GNode *node,
177 gchar *uri_suffix,
178 gchar **uri_remainder)
179 {
180 FileNodeData *data;
181 gsize len;
182
183 data = node->data;
184 len = strlen (data->uri_suffix);
185
186 if (strncmp (uri_suffix, data->uri_suffix, len) == 0) {
187 uri_suffix += len;
188
189 if (uri_suffix[0] == '/') {
190 uri_suffix++;
191 } else if (uri_suffix[0] != '\0' &&
192 (len < 4 ||
193 strcmp (data->uri_suffix + len - 4, ":///") != 0)) {
194 /* If the first char isn't an uri separator
195 * nor \0, node represents a similarly named
196 * file, but not a parent after all.
197 */
198 return FALSE;
199 }
200
201 if (uri_remainder) {
202 *uri_remainder = uri_suffix;
203 }
204
205 return TRUE;
206 } else {
207 return FALSE;
208 }
209 }
210
211 static GNode *
212 file_tree_lookup (GNode *tree,
213 GFile *file,
214 GNode **parent_node,
215 gchar **uri_remainder)
216 {
217 GNode *parent, *node_found, *parent_found;
218 FileNodeData *data;
219 gchar *uri, *ptr;
220
221 uri = ptr = g_file_get_uri (file);
222 node_found = parent_found = NULL;
223
224 /* Run through the filesystem tree, comparing chunks of
225 * uri with the uri suffix in the file nodes, this would
226 * get us to the closest registered parent, or the file
227 * itself.
228 */
229
230 if (parent_node) {
231 *parent_node = NULL;
232 }
233
234 if (uri_remainder) {
235 *uri_remainder = NULL;
236 }
237
238 if (!tree) {
239 return NULL;
240 }
241
242 if (!G_NODE_IS_ROOT (tree)) {
243 FileNodeData *parent_data;
244 gchar *parent_uri;
245
246 parent_data = tree->data;
247 parent_uri = g_file_get_uri (parent_data->file);
248
249 /* Sanity check */
250 if (!g_str_has_prefix (uri, parent_uri)) {
251 g_free (parent_uri);
252 return NULL;
253 }
254
255 ptr += strlen (parent_uri);
256
257 g_assert (ptr[0] == '/');
258 ptr++;
259
260 g_free (parent_uri);
261 } else {
262 /* First check the root node */
263 if (!file_node_data_equal_or_child (tree, uri, &ptr)) {
264 g_free (uri);
265 return NULL;
266 }
267
268 /* Second check there is no basename and if there isn't,
269 * then this node MUST be the closest registered node
270 * we can use for the uri. The difference here is that
271 * we return tree not NULL.
272 */
273 else if (ptr[0] == '\0') {
274 g_free (uri);
275 return tree;
276 }
277 }
278
279 parent = tree;
280
281 while (parent) {
282 GNode *child, *next = NULL;
283 gchar *ret_ptr;
284
285 for (child = g_node_first_child (parent);
286 child != NULL;
287 child = g_node_next_sibling (child)) {
288 data = child->data;
289
290 if (data->uri_suffix[0] != ptr[0])
291 continue;
292
293 if (file_node_data_equal_or_child (child, ptr, &ret_ptr)) {
294 ptr = ret_ptr;
295 next = child;
296 break;
297 }
298 }
299
300 if (next) {
301 if (ptr[0] == '\0') {
302 /* Exact match */
303 node_found = next;
304 parent_found = parent;
305 break;
306 } else {
307 /* Descent down the child */
308 parent = next;
309 }
310 } else {
311 parent_found = parent;
312 break;
313 }
314 }
315
316 if (parent_node) {
317 *parent_node = parent_found;
318 }
319
320 if (ptr && *ptr && uri_remainder) {
321 *uri_remainder = g_strdup (ptr);
322 }
323
324 g_free (uri);
325
326 return node_found;
327 }
328
329 static gboolean
330 file_tree_free_node_foreach (GNode *node,
331 gpointer user_data)
332 {
333 file_node_data_free (node->data, node);
334 return FALSE;
335 }
336
337 /* TrackerFileSystem implementation */
338
339 static void
340 tracker_file_system_finalize (GObject *object)
341 {
342 TrackerFileSystemPrivate *priv;
343
344 priv = TRACKER_FILE_SYSTEM (object)->priv;
345
346 g_node_traverse (priv->file_tree,
347 G_POST_ORDER,
348 G_TRAVERSE_ALL, -1,
349 file_tree_free_node_foreach,
350 NULL);
351 g_node_destroy (priv->file_tree);
352
353 G_OBJECT_CLASS (tracker_file_system_parent_class)->finalize (object);
354 }
355
356 static void
357 tracker_file_system_class_init (TrackerFileSystemClass *klass)
358 {
359 GObjectClass *object_class = G_OBJECT_CLASS (klass);
360
361 object_class->finalize = tracker_file_system_finalize;
362
363 g_type_class_add_private (object_class,
364 sizeof (TrackerFileSystemPrivate));
365
366 quark_file_node =
367 g_quark_from_static_string ("tracker-quark-file-node");
368 }
369 static void
370 tracker_file_system_init (TrackerFileSystem *file_system)
371 {
372 TrackerFileSystemPrivate *priv;
373 FileNodeData *root_data;
374
375 file_system->priv = priv =
376 G_TYPE_INSTANCE_GET_PRIVATE (file_system,
377 TRACKER_TYPE_FILE_SYSTEM,
378 TrackerFileSystemPrivate);
379
380 root_data = file_node_data_root_new ();
381 priv->file_tree = g_node_new (root_data);
382 }
383
384 TrackerFileSystem *
385 tracker_file_system_new (void)
386 {
387 return g_object_new (TRACKER_TYPE_FILE_SYSTEM, NULL);
388 }
389
390 static void
391 reparent_child_nodes_to_parent (GNode *node)
392 {
393 FileNodeData *node_data;
394 GNode *child, *parent;
395
396 if (!node->parent) {
397 return;
398 }
399
400 parent = node->parent;
401 node_data = node->data;
402 child = g_node_first_child (node);
403
404 while (child) {
405 FileNodeData *data;
406 gchar *uri_suffix;
407 GNode *cur;
408
409 cur = child;
410 data = cur->data;
411 child = g_node_next_sibling (child);
412
413 uri_suffix = g_strdup_printf ("%s/%s",
414 node_data->uri_suffix,
415 data->uri_suffix);
416
417 g_free (data->uri_suffix);
418 data->uri_suffix = uri_suffix;
419
420 g_node_unlink (cur);
421 g_node_prepend (parent, cur);
422 }
423 }
424
425 static void
426 file_weak_ref_notify (gpointer user_data,
427 GObject *prev_location)
428 {
429 FileNodeData *data;
430 GNode *node;
431
432 node = user_data;
433 data = node->data;
434
435 g_assert (data->file == (GFile *) prev_location);
436
437 data->file = NULL;
438 reparent_child_nodes_to_parent (node);
439
440 /* Delete node tree here */
441 file_node_data_free (data, NULL);
442 g_node_destroy (node);
443 }
444
445 static GNode *
446 file_system_get_node (TrackerFileSystem *file_system,
447 GFile *file)
448 {
449 TrackerFileSystemPrivate *priv;
450 GArray *node_data;
451 GNode *node = NULL;
452
453 node_data = g_object_get_qdata (G_OBJECT (file), quark_file_node);
454
455 if (node_data) {
456 NodeLookupData *cur;
457 guint i;
458
459 for (i = 0; i < node_data->len; i++) {
460 cur = &g_array_index (node_data, NodeLookupData, i);
461
462 if (cur->file_system == file_system) {
463 node = cur->node;
464 }
465 }
466 }
467
468 if (!node) {
469 priv = file_system->priv;
470 node = file_tree_lookup (priv->file_tree, file,
471 NULL, NULL);
472 }
473
474 return node;
475 }
476
477 GFile *
478 tracker_file_system_get_file (TrackerFileSystem *file_system,
479 GFile *file,
480 GFileType file_type,
481 GFile *parent)
482 {
483 TrackerFileSystemPrivate *priv;
484 FileNodeData *data;
485 GNode *node, *parent_node;
486 gchar *uri_suffix = NULL;
487
488 g_return_val_if_fail (G_IS_FILE (file), NULL);
489 g_return_val_if_fail (TRACKER_IS_FILE_SYSTEM (file_system), NULL);
490
491 priv = file_system->priv;
492 node = NULL;
493 parent_node = NULL;
494
495 if (parent) {
496 parent_node = file_system_get_node (file_system, parent);
497 node = file_tree_lookup (parent_node, file,
498 NULL, &uri_suffix);
499 } else {
500 node = file_tree_lookup (priv->file_tree, file,
501 &parent_node, &uri_suffix);
502 }
503
504 if (!node) {
505 if (!parent_node) {
506 gchar *uri;
507
508 uri = g_file_get_uri (file);
509 g_warning ("Could not find parent node for URI:'%s'", uri);
510 g_warning ("NOTE: URI themes other than 'file://' are not supported currently.");
511 g_free (uri);
512
513 return NULL;
514 }
515
516 node = g_node_new (NULL);
517
518 /* Parent was found, add file as child */
519 data = file_node_data_new (file_system, file,
520 file_type, node);
521 data->uri_suffix = uri_suffix;
522
523 g_node_append (parent_node, node);
524 } else {
525 data = node->data;
526 g_free (uri_suffix);
527
528 /* Update file type if it was unknown */
529 if (data->file_type == G_FILE_TYPE_UNKNOWN) {
530 data->file_type = file_type;
531 }
532 }
533
534 return data->file;
535 }
536
537 GFile *
538 tracker_file_system_peek_file (TrackerFileSystem *file_system,
539 GFile *file)
540 {
541 GNode *node;
542
543 g_return_val_if_fail (G_IS_FILE (file), NULL);
544 g_return_val_if_fail (TRACKER_IS_FILE_SYSTEM (file_system), NULL);
545
546 node = file_system_get_node (file_system, file);
547
548 if (node) {
549 FileNodeData *data;
550
551 data = node->data;
552 return data->file;
553 }
554
555 return NULL;
556 }
557
558 GFile *
559 tracker_file_system_peek_parent (TrackerFileSystem *file_system,
560 GFile *file)
561 {
562 GNode *node;
563
564 g_return_val_if_fail (file != NULL, NULL);
565 g_return_val_if_fail (TRACKER_IS_FILE_SYSTEM (file_system), NULL);
566
567 node = file_system_get_node (file_system, file);
568
569 if (node) {
570 FileNodeData *parent_data;
571 GNode *parent;
572
573 parent = node->parent;
574 parent_data = parent->data;
575
576 return parent_data->file;
577 }
578
579 return NULL;
580 }
581
582 typedef struct {
583 TrackerFileSystemTraverseFunc func;
584 gpointer user_data;
585 GSList *ignore_children;
586 } TraverseData;
587
588 static gint
589 node_is_child_of_ignored (gconstpointer a,
590 gconstpointer b)
591 {
592 if (g_node_is_ancestor ((GNode *) a, (GNode *) b))
593 return 0;
594
595 return 1;
596 }
597
598 static gboolean
599 traverse_filesystem_func (GNode *node,
600 gpointer user_data)
601 {
602 TraverseData *data = user_data;
603 FileNodeData *node_data;
604 gboolean retval = FALSE;
605
606 node_data = node->data;
607
608 if (!data->ignore_children ||
609 !g_slist_find_custom (data->ignore_children,
610 node, node_is_child_of_ignored)) {
611 /* This node isn't a child of an
612 * ignored one, execute callback
613 */
614 retval = data->func (node_data->file, data->user_data);
615 }
616
617 /* Avoid recursing within the children of this node */
618 if (retval) {
619 data->ignore_children = g_slist_prepend (data->ignore_children,
620 node);
621 }
622
623 return FALSE;
624 }
625
626 void
627 tracker_file_system_traverse (TrackerFileSystem *file_system,
628 GFile *root,
629 GTraverseType order,
630 TrackerFileSystemTraverseFunc func,
631 gpointer user_data)
632 {
633 TrackerFileSystemPrivate *priv;
634 TraverseData data;
635 GNode *node;
636
637 g_return_if_fail (TRACKER_IS_FILE_SYSTEM (file_system));
638 g_return_if_fail (func != NULL);
639
640 priv = file_system->priv;
641
642 if (root) {
643 node = file_system_get_node (file_system, root);
644 } else {
645 node = priv->file_tree;
646 }
647
648 data.func = func;
649 data.user_data = user_data;
650 data.ignore_children = NULL;
651
652 g_node_traverse (node,
653 order,
654 G_TRAVERSE_ALL,
655 -1,
656 traverse_filesystem_func,
657 &data);
658
659 g_slist_free (data.ignore_children);
660 }
661
662 void
663 tracker_file_system_register_property (GQuark prop,
664 GDestroyNotify destroy_notify)
665 {
666 g_return_if_fail (prop != 0);
667
668 if (!properties) {
669 properties = g_hash_table_new (NULL, NULL);
670 }
671
672 if (g_hash_table_lookup (properties, GUINT_TO_POINTER (prop))) {
673 g_warning ("FileSystem: property '%s' has been already registered",
674 g_quark_to_string (prop));
675 return;
676 }
677
678 g_hash_table_insert (properties,
679 GUINT_TO_POINTER (prop),
680 destroy_notify);
681 }
682
683 static int
684 search_property_node (gconstpointer key,
685 gconstpointer item)
686 {
687 const FileNodeProperty *key_prop, *prop;
688
689 key_prop = key;
690 prop = item;
691
692 if (key_prop->prop_quark < prop->prop_quark)
693 return -1;
694 else if (key_prop->prop_quark > prop->prop_quark)
695 return 1;
696
697 return 0;
698 }
699
700 void
701 tracker_file_system_set_property (TrackerFileSystem *file_system,
702 GFile *file,
703 GQuark prop,
704 gpointer prop_data)
705 {
706 FileNodeProperty property, *match;
707 GDestroyNotify destroy_notify;
708 FileNodeData *data;
709 GNode *node;
710
711 g_return_if_fail (TRACKER_IS_FILE_SYSTEM (file_system));
712 g_return_if_fail (file != NULL);
713 g_return_if_fail (prop != 0);
714
715 if (!properties ||
716 !g_hash_table_lookup_extended (properties,
717 GUINT_TO_POINTER (prop),
718 NULL, (gpointer *) &destroy_notify)) {
719 g_warning ("FileSystem: property '%s' is not registered",
720 g_quark_to_string (prop));
721 return;
722 }
723
724 node = file_system_get_node (file_system, file);
725 g_return_if_fail (node != NULL);
726
727 data = node->data;
728
729 property.prop_quark = prop;
730 match = bsearch (&property, data->properties->data,
731 data->properties->len, sizeof (FileNodeProperty),
732 search_property_node);
733
734 if (match) {
735 if (destroy_notify) {
736 (destroy_notify) (match->value);
737 }
738
739 match->value = prop_data;
740 } else {
741 FileNodeProperty *item;
742 guint i;
743
744 /* No match, insert new element */
745 for (i = 0; i < data->properties->len; i++) {
746 item = &g_array_index (data->properties,
747 FileNodeProperty, i);
748
749 if (item->prop_quark > prop) {
750 break;
751 }
752 }
753
754 property.value = prop_data;
755
756 if (i >= data->properties->len) {
757 g_array_append_val (data->properties, property);
758 } else {
759 g_array_insert_val (data->properties, i, property);
760 }
761 }
762 }
763
764 gpointer
765 tracker_file_system_get_property (TrackerFileSystem *file_system,
766 GFile *file,
767 GQuark prop)
768 {
769 FileNodeData *data;
770 FileNodeProperty property, *match;
771 GNode *node;
772
773 g_return_val_if_fail (TRACKER_IS_FILE_SYSTEM (file_system), NULL);
774 g_return_val_if_fail (file != NULL, NULL);
775 g_return_val_if_fail (prop > 0, NULL);
776
777 node = file_system_get_node (file_system, file);
778 g_return_val_if_fail (node != NULL, NULL);
779
780 data = node->data;
781 property.prop_quark = prop;
782
783 match = bsearch (&property, data->properties->data,
784 data->properties->len, sizeof (FileNodeProperty),
785 search_property_node);
786
787 return (match) ? match->value : NULL;
788 }
789
790 void
791 tracker_file_system_unset_property (TrackerFileSystem *file_system,
792 GFile *file,
793 GQuark prop)
794 {
795 FileNodeData *data;
796 FileNodeProperty property, *match;
797 GDestroyNotify destroy_notify;
798 GNode *node;
799 guint index;
800
801 g_return_if_fail (TRACKER_IS_FILE_SYSTEM (file_system));
802 g_return_if_fail (file != NULL);
803 g_return_if_fail (prop > 0);
804
805 if (!properties ||
806 !g_hash_table_lookup_extended (properties,
807 GUINT_TO_POINTER (prop),
808 NULL,
809 (gpointer *) &destroy_notify)) {
810 g_warning ("FileSystem: property '%s' is not registered",
811 g_quark_to_string (prop));
812 }
813
814 node = file_system_get_node (file_system, file);
815 g_return_if_fail (node != NULL);
816
817 data = node->data;
818 property.prop_quark = prop;
819
820 match = bsearch (&property, data->properties->data,
821 data->properties->len, sizeof (FileNodeProperty),
822 search_property_node);
823
824 if (!match) {
825 return;
826 }
827
828 if (destroy_notify) {
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
829 (destroy_notify) (match->value);
830 }
831
832 /* Find out the index from memory positions */
833 index = (guint) ((FileNodeProperty *) match -
834 (FileNodeProperty *) data->properties->data);
835 g_assert (index >= 0 && index < data->properties->len);
836
837 g_array_remove_index (data->properties, index);
838 }
839
840 typedef struct {
841 TrackerFileSystem *file_system;
842 GList *list;
843 GFileType file_type;
844 } ForgetFilesData;
845
846 static gboolean
847 append_deleted_files (GNode *node,
848 gpointer user_data)
849 {
850 ForgetFilesData *data;
851 FileNodeData *node_data;
852
853 data = user_data;
854 node_data = node->data;
855
856 if (data->file_type == G_FILE_TYPE_UNKNOWN ||
857 node_data->file_type == data->file_type) {
858 data->list = g_list_prepend (data->list, node_data);
859 }
860
861 return FALSE;
862 }
863
864 static void
865 forget_file (FileNodeData *node_data)
866 {
867 if (!node_data->unowned) {
868 node_data->unowned = TRUE;
869
870 /* Weak reference handler will remove the file from the tree and
871 * clean up node_data if this is the final reference.
872 */
873 g_object_unref (node_data->file);
874 }
875 }
876
877 void
878 tracker_file_system_forget_files (TrackerFileSystem *file_system,
879 GFile *root,
880 GFileType file_type)
881 {
882 ForgetFilesData data = { file_system, NULL, file_type };
883 GNode *node;
884
885 g_return_if_fail (TRACKER_IS_FILE_SYSTEM (file_system));
886 g_return_if_fail (G_IS_FILE (root));
887
888 node = file_system_get_node (file_system, root);
889 g_return_if_fail (node != NULL);
890
891 /* We need to get the files to delete into a list, so
892 * the node tree isn't modified during traversal.
893 */
894 g_node_traverse (node,
895 G_PRE_ORDER,
896 (file_type == G_FILE_TYPE_REGULAR) ?
897 G_TRAVERSE_LEAVES : G_TRAVERSE_ALL,
898 -1, append_deleted_files,
899 &data);
900
901 g_list_foreach (data.list, (GFunc) forget_file, NULL);
902 g_list_free (data.list);
903 }