dirtree.c revision 9b3af46b888a57c2183545455e2de1cb457d9f2a
1/* vi: set sw=4 ts=4 :*/
2/* dirtree.c - Functions for dealing with directory trees.
3 *
4 * Copyright 2007 Rob Landley <rob@landley.net>
5 */
6
7#include "toys.h"
8
9// Create a dirtree node from a path, with stat and symlink info.
10// (This doesn't open directory filehandles yet so as not to exhaust the
11// filehandle space on large trees. handle_callback() does that instead.)
12
13struct dirtree *dirtree_add_node(int dirfd, char *name)
14{
15	struct dirtree *dt = NULL;
16	struct stat st;
17	char buf[4096];
18	int len = 0, linklen = 0;
19
20	if (name) {
21		if (fstatat(dirfd, name, &st, AT_SYMLINK_NOFOLLOW)) goto error;
22		if (S_ISLNK(st.st_mode)) {
23			if (0>(linklen = readlinkat(dirfd, name, buf, 4095))) goto error;
24			buf[linklen++]=0;
25		}
26		len = strlen(name);
27	}
28   	dt = xzalloc((len = sizeof(struct dirtree)+len+1)+linklen);
29	if (name) {
30		memcpy(&(dt->st), &st, sizeof(struct stat));
31		strcpy(dt->name, name);
32
33		if (linklen) {
34			dt->symlink = memcpy(len+(char *)dt, buf, linklen);
35			dt->data = --linklen;
36		}
37	}
38
39	return dt;
40
41error:
42	perror_msg("%s",name);
43	free(dt);
44	return 0;
45}
46
47// Return path to this node, assembled recursively.
48
49char *dirtree_path(struct dirtree *node, int *plen)
50{
51	char *path;
52	int len;
53
54	if (!node || !node->name) {
55		path = xmalloc(*plen);
56		*plen = 0;
57		return path;
58	}
59
60	len = (plen ? *plen : 0)+strlen(node->name)+1;
61	path = dirtree_path(node->parent, &len);
62    if (len) path[len++]='/';
63	len = (stpcpy(path+len, node->name) - path);
64    if (plen) *plen = len;
65
66	return path;
67}
68
69// Default callback, filters out "." and "..".
70
71int dirtree_isdotdot(struct dirtree *catch)
72{
73	// Should we skip "." and ".."?
74	if (catch->name[0]=='.' && (!catch->name[1] ||
75			(catch->name[1]=='.' && !catch->name[2])))
76				return DIRTREE_NOSAVE|DIRTREE_NORECURSE;
77
78	return 0;
79}
80
81// Handle callback for a node in the tree. Returns saved node(s) or NULL.
82//
83// By default, allocates a tree of struct dirtree, not following symlinks
84// If callback==NULL, or callback always returns 0, allocate tree of struct
85// dirtree and return root of tree.  Otherwise call callback(node) on each hit, free
86// structures after use, and return NULL.
87//
88
89struct dirtree *handle_callback(struct dirtree *new,
90					int (*callback)(struct dirtree *node))
91{
92	int flags;
93
94	if (!callback) callback = dirtree_isdotdot;
95
96	flags = callback(new);
97	if (S_ISDIR(new->st.st_mode)) {
98		if (!(flags & DIRTREE_NORECURSE)) {
99			new->data = openat (new->parent ? new->parent->data : AT_FDCWD,
100				new->name, 0);
101			dirtree_recurse(new, callback);
102		}
103		new->data = -1;
104		if (flags & DIRTREE_COMEAGAIN) flags = callback(new);
105	}
106	// If this had children, it was callback's job to free them already.
107	if (flags & DIRTREE_NOSAVE) {
108		free(new);
109		new = NULL;
110	}
111
112	return (flags & DIRTREE_ABORT)==DIRTREE_ABORT ? DIRTREE_ABORTVAL : new;
113}
114
115// Recursively read/process children of directory node (with dirfd in data),
116// filtering through callback().
117
118void dirtree_recurse(struct dirtree *node,
119					int (*callback)(struct dirtree *node))
120{
121	struct dirtree *new, **ddt = &(node->child);
122	struct dirent *entry;
123	DIR *dir;
124
125	if (!(dir = fdopendir(node->data))) {
126		char *path = dirtree_path(node, 0);
127		perror_msg("No %s", path);
128		free(path);
129		close(node->data);
130
131        return;
132	}
133
134	// according to the fddir() man page, the filehandle in the DIR * can still
135	// be externally used by things that don't lseek() it.
136
137	// The extra parentheses are to shut the stupid compiler up.
138	while ((entry = readdir(dir))) {
139		if (!(new = dirtree_add_node(node->data, entry->d_name))) continue;
140		new->parent = node;
141		new = handle_callback(new, callback);
142		if (new == DIRTREE_ABORTVAL) break;
143		if (new) {
144			*ddt = new;
145			ddt = &((*ddt)->next);
146		}
147	}
148
149	closedir(dir);
150}
151
152// Create dirtree from path, using callback to filter nodes.
153// If callback == NULL allocate a tree of struct dirtree nodes and return
154// pointer to root node.
155
156struct dirtree *dirtree_read(char *path, int (*callback)(struct dirtree *node))
157{
158	struct dirtree *root = dirtree_add_node(AT_FDCWD, path);
159
160	return handle_callback(root, callback);
161}
162