summaryrefslogtreecommitdiff
path: root/libmisc/walk_tree.c
blob: 62bc74db0f7afb086647abb71d91db95e33de70a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/*
  File: walk_tree.c

  Copyright (C) 2007 Andreas Gruenbacher <a.gruenbacher@computer.org>

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include "walk_tree.h"

static int walk_tree_rec(const char *path, int walk_flags,
			 int (*func)(const char *, const struct stat *, int, void *),
			 void *arg, int depth)
{
	int (*xstat)(const char *, struct stat *) = lstat;
	struct stat st;
	int local_walk_flags = walk_flags, err;

	/* Default to traversing symlinks on the command line, traverse all symlinks
	 * with -L, and do not traverse symlinks with -P. (This is similar to chown.)
	 */

follow_symlink:
	if (xstat(path, &st) != 0)
		return func(path, NULL, local_walk_flags | WALK_TREE_FAILED, arg);
	if (S_ISLNK(st.st_mode)) {
		if ((local_walk_flags & WALK_TREE_PHYSICAL) ||
		    (!(local_walk_flags & WALK_TREE_LOGICAL) && depth > 1))
			return 0;
		local_walk_flags |= WALK_TREE_SYMLINK;
		xstat = stat;
		if (local_walk_flags & WALK_TREE_DEREFERENCE)
			goto follow_symlink;
	}
	err = func(path, &st, local_walk_flags, arg);
	if ((local_walk_flags & WALK_TREE_RECURSIVE) &&
	    (S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))) {
		char path2[FILENAME_MAX];
		DIR *dir;
		struct dirent *entry;
		int err2;

		dir = opendir(path);
		if (!dir) {
			/* PATH may be a symlink to a regular file or a dead symlink
			 * which we didn't follow above.
			 */
			if (errno != ENOTDIR && errno != ENOENT)
				err += func(path, &st,
					    local_walk_flags | WALK_TREE_FAILED, arg);
			return err;
		}
		while ((entry = readdir(dir)) != NULL) {
			if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
				continue;
			err2 = snprintf(path2, sizeof(path2), "%s/%s", path,
				       entry->d_name);
			if (err2 < 0 || err2 > FILENAME_MAX) {
				errno = ENAMETOOLONG;
				err += func(path, NULL,
					    local_walk_flags | WALK_TREE_FAILED, arg);
				continue;
			}
			err += walk_tree_rec(path2, walk_flags, func, arg, depth + 1);
		}
		if (closedir(dir) != 0)
			err += func(path, &st, local_walk_flags | WALK_TREE_FAILED, arg);
	}
	return err;
}

int walk_tree(const char *path, int walk_flags,
	      int (*func)(const char *, const struct stat *, int, void *), void *arg)
{
	if (strlen(path) >= FILENAME_MAX) {
		errno = ENAMETOOLONG;
		return func(path, NULL, WALK_TREE_FAILED, arg);
	}
	return walk_tree_rec(path, walk_flags, func, arg, 1);
}