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 
38 typedef int treewalk_stat_func(const char *pathname, struct stat *st);
39 
40 struct 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 
58 static inline struct treenode *
treenode_alloc(void)59 treenode_alloc(void)
60 {
61 	return m_calloc(1, sizeof(struct treenode));
62 }
63 
64 static struct treenode *
treenode_root_new(const char * rootdir)65 treenode_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 
83 static struct treenode *
treenode_node_new(struct treenode * root,struct treenode * dir,const char * name)84 treenode_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 
99 static void
treenode_stat(struct treenode * node,treewalk_stat_func * stat_func)100 treenode_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 
113 static mode_t
dirent_to_mode_type(struct dirent * e)114 dirent_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 
141 static void
treenode_stat_shallow(struct treenode * node,struct dirent * e,treewalk_stat_func * stat_func)142 treenode_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 
154 const char *
treenode_get_name(struct treenode * node)155 treenode_get_name(struct treenode *node)
156 {
157 	return node->name;
158 }
159 
160 const char *
treenode_get_pathname(struct treenode * node)161 treenode_get_pathname(struct treenode *node)
162 {
163 	return node->pathname;
164 }
165 
166 const char *
treenode_get_virtname(struct treenode * node)167 treenode_get_virtname(struct treenode *node)
168 {
169 	return node->virtname;
170 }
171 
172 mode_t
treenode_get_mode(struct treenode * node)173 treenode_get_mode(struct treenode *node)
174 {
175 	return node->mode;
176 }
177 
178 struct stat *
treenode_get_stat(struct treenode * node)179 treenode_get_stat(struct treenode *node)
180 {
181 	treenode_stat(node, lstat);
182 	return node->stat;
183 }
184 
185 struct treenode *
treenode_get_parent(struct treenode * node)186 treenode_get_parent(struct treenode *node)
187 {
188 	return node->up;
189 }
190 
191 static inline bool
treenode_is_dir(struct treenode * node)192 treenode_is_dir(struct treenode *node)
193 {
194 	return node && S_ISDIR(node->mode);
195 }
196 
197 static void
treenode_resize_down(struct treenode * node)198 treenode_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(*node);
210 	node->down = m_realloc(node->down, new_size);
211 }
212 
213 static int
treenode_cmp(const void * a,const void * b)214 treenode_cmp(const void *a, const void *b)
215 {
216 	return strcmp((*(const struct treenode **)a)->name,
217 	              (*(const struct treenode **)b)->name);
218 }
219 
220 static void
treenode_sort_down(struct treenode * dir)221 treenode_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 
233 static void
treenode_fill_down(struct treenode * root,struct treenode * dir,enum treewalk_options options)234 treenode_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 
273 static void
treenode_free_node(struct treenode * node)274 treenode_free_node(struct treenode *node)
275 {
276 	free(node->pathname);
277 	free(node);
278 }
279 
280 static void
treenode_free_down(struct treenode * node)281 treenode_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 
296 struct 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 
306 static inline void
treeroot_set_curdir(struct treeroot * tree,struct treenode * node)307 treeroot_set_curdir(struct treeroot *tree, struct treenode *node)
308 {
309 	tree->curdir = node;
310 }
311 
312 static inline void
treeroot_set_curnode(struct treeroot * tree,struct treenode * node)313 treeroot_set_curnode(struct treeroot *tree, struct treenode *node)
314 {
315 	tree->curnode = node;
316 }
317 
318 static bool
treeroot_skip_node(struct treeroot * tree,struct treenode * node)319 treeroot_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 
327 static void
treeroot_fill_node(struct treeroot * tree,struct treenode * dir)328 treeroot_fill_node(struct treeroot *tree, struct treenode *dir)
329 {
330 	treenode_fill_down(tree->rootnode, dir, tree->options);
331 }
332 
333 static void
treeroot_sort_node(struct treeroot * tree,struct treenode * dir)334 treeroot_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 
346 static void
treeroot_visit_node(struct treeroot * tree,struct treenode * node)347 treeroot_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  */
363 struct treeroot *
treewalk_open(const char * rootdir,enum treewalk_options options,struct treewalk_funcs * func)364 treewalk_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(*tree));
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  */
408 struct treenode *
treewalk_node(struct treeroot * tree)409 treewalk_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  */
434 struct treenode *
treewalk_next(struct treeroot * tree)435 treewalk_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  */
505 void
treewalk_close(struct treeroot * tree)506 treewalk_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  */
518 int
treewalk(const char * rootdir,enum treewalk_options options,struct treewalk_funcs * func)519 treewalk(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 }
535