dpkg (1.18.25) stretch; urgency=medium
[dpkg] / lib / dpkg / treewalk.c
CommitLineData
1479465f
GJ
1/*
2 * libdpkg - Debian packaging suite library routines
3 * treewalk.c - directory tree walk support
4 *
5 * Copyright © 2013-2016 Guillem Jover <guillem@debian.org>
6 *
7 * This is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include <config.h>
22#include <compat.h>
23
24#include <sys/stat.h>
25#include <sys/types.h>
26#include <dirent.h>
27#include <string.h>
28#include <stdbool.h>
29#include <stdio.h>
30#include <stdlib.h>
31
32#include <dpkg/dpkg.h>
33#include <dpkg/i18n.h>
34#include <dpkg/treewalk.h>
35
36/* treenode functions. */
37
38typedef int treewalk_stat_func(const char *pathname, struct stat *st);
39
40struct treenode {
41 struct treenode *up; /* Parent dir. */
42 struct treenode *next; /* Next node in dir. */
43 struct treenode **down; /* Dir contents. */
44
45 char *pathname; /* Node pathname. */
46 char *virtname; /* Node virtname. */
47 char *name; /* Node name. */
48
49 struct stat *stat; /* Node metadata. */
50 mode_t mode;
51
52 size_t pathname_len; /* Node pathname length. */
53
54 size_t down_used; /* Number of used nodes in dir. */
55 size_t down_size; /* Number of allocated nodes in dir. */
56};
57
58static inline struct treenode *
59treenode_alloc(void)
60{
61 return m_calloc(1, sizeof(struct treenode));
62}
63
64static struct treenode *
65treenode_root_new(const char *rootdir)
66{
67 struct treenode *node;
68
69 node = treenode_alloc();
70 node->up = NULL;
71 node->pathname = m_strdup(rootdir);
72 node->pathname_len = strlen(node->pathname);
73 node->virtname = node->pathname + node->pathname_len;
74 node->name = strrchr(node->pathname, '/');
75 if (node->name == NULL)
76 node->name = node->pathname;
77 else
78 node->name++;
79
80 return node;
81}
82
83static struct treenode *
84treenode_node_new(struct treenode *root, struct treenode *dir, const char *name)
85{
86 struct treenode *node;
87
88 node = treenode_alloc();
89 node->up = dir;
90
91 node->pathname = str_fmt("%s/%s", node->up->pathname, name);
92 node->pathname_len = strlen(node->pathname);
93 node->virtname = node->pathname + root->pathname_len + 1;
94 node->name = node->pathname + node->up->pathname_len + 1;
95
96 return node;
97}
98
99static void
100treenode_stat(struct treenode *node, treewalk_stat_func *stat_func)
101{
102 if (node->stat)
103 return;
104
105 node->stat = m_malloc(sizeof(*node->stat));
106
107 if (stat_func(node->pathname, node->stat) < 0)
108 ohshite(_("cannot stat pathname '%s'"), node->pathname);
109
110 node->mode = node->stat->st_mode;
111}
112
113static mode_t
114dirent_to_mode_type(struct dirent *e)
115{
116#ifdef _DIRENT_HAVE_D_TYPE
117 switch (e->d_type) {
118 case DT_REG:
119 return S_IFREG;
120 case DT_DIR:
121 return S_IFDIR;
122 case DT_LNK:
123 return S_IFLNK;
124 case DT_CHR:
125 return S_IFCHR;
126 case DT_BLK:
127 return S_IFBLK;
128 case DT_FIFO:
129 return S_IFIFO;
130 case DT_SOCK:
131 return S_IFSOCK;
132 case DT_UNKNOWN:
133 default:
134 return 0;
135 }
136#else
137 return 0;
138#endif
139}
140
141static void
142treenode_stat_shallow(struct treenode *node, struct dirent *e,
143 treewalk_stat_func *stat_func)
144{
145 mode_t mode;
146
147 mode = dirent_to_mode_type(e);
148 if (mode == 0 || S_ISDIR(mode) || S_ISLNK(mode))
149 treenode_stat(node, stat_func);
150 else
151 node->mode |= mode;
152}
153
154const char *
155treenode_get_name(struct treenode *node)
156{
157 return node->name;
158}
159
160const char *
161treenode_get_pathname(struct treenode *node)
162{
163 return node->pathname;
164}
165
166const char *
167treenode_get_virtname(struct treenode *node)
168{
169 return node->virtname;
170}
171
172mode_t
173treenode_get_mode(struct treenode *node)
174{
175 return node->mode;
176}
177
178struct stat *
179treenode_get_stat(struct treenode *node)
180{
181 treenode_stat(node, lstat);
182 return node->stat;
183}
184
185struct treenode *
186treenode_get_parent(struct treenode *node)
187{
188 return node->up;
189}
190
191static inline bool
192treenode_is_dir(struct treenode *node)
193{
194 return node && S_ISDIR(node->mode);
195}
196
197static void
198treenode_resize_down(struct treenode *node)
199{
200 size_t new_size;
201
202 if (node->down_size)
203 node->down_size *= 2;
204 else if (node->stat->st_nlink > 4)
205 node->down_size = node->stat->st_nlink * 2;
206 else
207 node->down_size = 8;
208
209 new_size = node->down_size * sizeof(struct treenode *);
210 node->down = m_realloc(node->down, new_size);
211}
212
213static int
214treenode_cmp(const void *a, const void *b)
215{
216 return strcmp((*(const struct treenode **)a)->name,
217 (*(const struct treenode **)b)->name);
218}
219
220static void
221treenode_sort_down(struct treenode *dir)
222{
223 size_t i;
224
225 qsort(dir->down, dir->down_used, sizeof(struct treenode *), treenode_cmp);
226
227 /* Relink the nodes. */
228 for (i = 0; i < dir->down_used - 1; i++)
229 dir->down[i]->next = dir->down[i + 1];
230 dir->down[i]->next = NULL;
231}
232
233static void
234treenode_fill_down(struct treenode *root, struct treenode *dir,
235 enum treewalk_options options)
236{
237 DIR *d;
238 struct dirent *e;
239 treewalk_stat_func *stat_func;
240
241 if (options & TREEWALK_FOLLOW_LINKS)
242 stat_func = stat;
243 else
244 stat_func = lstat;
245
246 d = opendir(dir->pathname);
247 if (!d)
248 ohshite(_("cannot open directory '%s'"), dir->pathname);
249
250 while ((e = readdir(d)) != NULL) {
251 struct treenode *node;
252
253 if (strcmp(e->d_name, ".") == 0 ||
254 strcmp(e->d_name, "..") == 0)
255 continue;
256
257 if (dir->down_used >= dir->down_size)
258 treenode_resize_down(dir);
259
260 node = treenode_node_new(root, dir, e->d_name);
261 if (options & TREEWALK_FORCE_STAT)
262 treenode_stat(node, stat_func);
263 else
264 treenode_stat_shallow(node, e, stat_func);
265
266 dir->down[dir->down_used] = node;
267 dir->down_used++;
268 }
269
270 closedir(d);
271}
272
273static void
274treenode_free_node(struct treenode *node)
275{
276 free(node->pathname);
277 free(node);
278}
279
280static void
281treenode_free_down(struct treenode *node)
282{
283 size_t i;
284
285 if (!node->down_size)
286 return;
287
288 for (i = 0; i < node->down_used; i++)
289 treenode_free_node(node->down[i]);
290 free(node->down);
291}
292
293
294/* treeroot functions. */
295
296struct treeroot {
297 struct treenode *rootnode;
298
299 struct treenode *curdir;
300 struct treenode *curnode;
301
302 enum treewalk_options options;
303 struct treewalk_funcs func;
304};
305
306static inline void
307treeroot_set_curdir(struct treeroot *tree, struct treenode *node)
308{
309 tree->curdir = node;
310}
311
312static inline void
313treeroot_set_curnode(struct treeroot *tree, struct treenode *node)
314{
315 tree->curnode = node;
316}
317
318static bool
319treeroot_skip_node(struct treeroot *tree, struct treenode *node)
320{
321 if (tree->func.skip)
322 return tree->func.skip(node);
323
324 return false;
325}
326
327static void
328treeroot_fill_node(struct treeroot *tree, struct treenode *dir)
329{
330 treenode_fill_down(tree->rootnode, dir, tree->options);
331}
332
333static void
334treeroot_sort_node(struct treeroot *tree, struct treenode *dir)
335{
336 static struct treenode *down_empty[] = { NULL, NULL };
337
338 if (dir->down_used == 0)
339 dir->down = down_empty;
340 else if (tree->func.sort)
341 tree->func.sort(dir);
342 else
343 treenode_sort_down(dir);
344}
345
346static void
347treeroot_visit_node(struct treeroot *tree, struct treenode *node)
348{
349 if (tree->func.visit == NULL)
350 return;
351
352 if (!treeroot_skip_node(tree, node))
353 tree->func.visit(node);
354}
355
356/**
357 * Open a new tree to be walked.
358 *
359 * @param rootdir The root directory to start walking the tree.
360 * @param options The options specifying how to walk the tree.
361 * @param func The functions callbacks.
362 */
363struct treeroot *
364treewalk_open(const char *rootdir, enum treewalk_options options,
365 struct treewalk_funcs *func)
366{
367 struct treeroot *tree;
368 struct treenode *root;
369
370 tree = m_malloc(sizeof(struct treeroot));
371
372 tree->options = options;
373 if (func)
374 tree->func = *func;
375 else
376 tree->func = (struct treewalk_funcs){ };
377
378 root = treenode_root_new(rootdir);
379 treenode_stat(root, lstat);
380
381 if (!treenode_is_dir(root))
382 ohshit(_("treewalk root %s is not a directory"), rootdir);
383
384 treeroot_set_curnode(tree, root);
385 tree->rootnode = tree->curdir = root;
386
387 return tree;
388}
389
390/**
391 * Return the current node.
392 *
393 * This function is only needed if you want to start walking the tree from
394 * the root node. With something like:
395 *
396 * @code
397 * struct treeroot *tree;
398 * struct treenode *node;
399 *
400 * tree = treewalk_open(...);
401 * for (node = treewalk_node(tree); node; node = treewalk_next(tree))
402 * visitor(node);
403 * treewalk_close(tree);
404 * @endcode
405 *
406 * @param tree The tree structure.
407 */
408struct treenode *
409treewalk_node(struct treeroot *tree)
410{
411 return tree->curnode;
412}
413
414/**
415 * Return the next node.
416 *
417 * This function works basically as an iterator. And will return NULL when
418 * the whole tree has been traversed. When starting it will skip the root
419 * node, so you might want to use treewalk_node() to get that, otherwise
420 * you could use it like this:
421 *
422 * @code
423 * struct treeroot *tree;
424 * struct treenode *node;
425 *
426 * tree = treewalk_open(...);
427 * while ((node = treewalk_next(tree))
428 * visitor(node);
429 * treewalk_close(tree);
430 * @endcode
431 *
432 * @param tree The tree structure.
433 */
434struct treenode *
435treewalk_next(struct treeroot *tree)
436{
437 struct treenode *node;
438
439 node = tree->curnode;
440
441 /* Handle end of tree. */
442 if (node == NULL)
443 return NULL;
444
445 /* Get next node, descending or sidewide. */
446 if (treenode_is_dir(node) && !treeroot_skip_node(tree, node)) {
447 struct treenode *dir;
448
449 treeroot_fill_node(tree, node);
450 treeroot_sort_node(tree, node);
451 treeroot_set_curdir(tree, node);
452
453 /* Check for directory loops. */
454 for (dir = node->up; dir; dir = dir->up) {
455 if (dir->stat->st_dev == node->stat->st_dev &&
456 dir->stat->st_ino == node->stat->st_ino)
457 break;
458 }
459
460 /* Skip directory loops. */
461 if (dir)
462 node = node->next;
463 else
464 node = node->down[0];
465 } else {
466 node = node->next;
467 }
468
469 /* Back track node, ascending. */
470 while (node == NULL) {
471 struct treenode *olddir = tree->curdir;
472
473 if (tree->curdir->next) {
474 /* Next entry in the parent directory. */
475 node = tree->curdir->next;
476 treeroot_set_curdir(tree, olddir->up);
477 treenode_free_down(olddir);
478 } else if (tree->curdir->up) {
479 /* Next entry in the grand-parent directory. */
480 node = tree->curdir->up->next;
481 treeroot_set_curdir(tree, olddir->up->up);
482 treenode_free_down(olddir);
483 treenode_free_down(olddir->up);
484 } else {
485 /* Otherwise, we're in the rootnode. */
486 treenode_free_down(olddir);
487 treenode_free_node(olddir);
488 break;
489 }
490
491 if (tree->curdir == NULL)
492 break;
493 }
494
495 treeroot_set_curnode(tree, node);
496
497 return node;
498}
499
500/**
501 * Closes the tree being walked.
502 *
503 * It will free any resources previously allocated.
504 */
505void
506treewalk_close(struct treeroot *tree)
507{
508 free(tree);
509}
510
511/**
512 * Tree walker.
513 *
514 * @param rootdir The root directory to start walking the tree.
515 * @param options The options specifying how to walk the tree.
516 * @param func The function callbacks.
517 */
518int
519treewalk(const char *rootdir, enum treewalk_options options,
520 struct treewalk_funcs *func)
521{
522 struct treeroot *tree;
523 struct treenode *node;
524
525 tree = treewalk_open(rootdir, options, func);
526
527 /* Breath first visit. */
528 for (node = treewalk_node(tree); node; node = treewalk_next(tree))
529 treeroot_visit_node(tree, node);
530
531 treewalk_close(tree);
532
533 return 0;
534}