1/* dirtree.c - Functions for dealing with directory trees.
2 *
3 * Copyright 2007 Rob Landley <rob@landley.net>
4 */
5
6#include "toys.h"
7
8
9int isdot(char *name)
10{
11  if (name[0]=='.' && (!name[1] || (name[1]=='/' && !name[2]))) return 1;
12
13  return 0;
14}
15
16int isdotdot(char *name)
17{
18  if (name[0]=='.' && (!name[1] || (name[1]=='.' && !name[2]))) return 1;
19
20  return 0;
21}
22
23// Default callback, filters out "." and ".." except at top level.
24
25int dirtree_notdotdot(struct dirtree *catch)
26{
27  // Should we skip "." and ".."?
28  return (!catch->parent||!isdotdot(catch->name))
29    *(DIRTREE_SAVE|DIRTREE_RECURSE);
30}
31
32// Create a dirtree node from a path, with stat and symlink info.
33// (This doesn't open directory filehandles yet so as not to exhaust the
34// filehandle space on large trees, dirtree_handle_callback() does that.)
35
36struct dirtree *dirtree_add_node(struct dirtree *parent, char *name, int flags)
37{
38  struct dirtree *dt = 0;
39  struct stat st;
40  int len = 0, linklen = 0, statless = 0;
41
42  if (name) {
43    // open code this because haven't got node to call dirtree_parentfd() on yet
44    int fd = parent ? parent->dirfd : AT_FDCWD;
45
46    if (fstatat(fd, name, &st,AT_SYMLINK_NOFOLLOW*!(flags&DIRTREE_SYMFOLLOW))) {
47      if (flags&DIRTREE_STATLESS) statless++;
48      else goto error;
49    }
50    if (S_ISLNK(st.st_mode)) {
51      if (0>(linklen = readlinkat(fd, name, libbuf, 4095))) goto error;
52      libbuf[linklen++]=0;
53    }
54    len = strlen(name);
55  }
56
57  // Allocate/populate return structure
58  dt = xmalloc((len = sizeof(struct dirtree)+len+1)+linklen);
59  memset(dt, 0, statless ? offsetof(struct dirtree, again)
60    : offsetof(struct dirtree, st));
61  dt->parent = parent;
62  dt->again = statless ? 2 : 0;
63  if (!statless) memcpy(&dt->st, &st, sizeof(struct stat));
64  strcpy(dt->name, name ? name : "");
65  if (linklen) dt->symlink = memcpy(len+(char *)dt, libbuf, linklen);
66
67  return dt;
68
69error:
70  if (!(flags&DIRTREE_SHUTUP) && !isdotdot(name)) {
71    char *path = parent ? dirtree_path(parent, 0) : "";
72
73    perror_msg("%s%s%s", path, parent ? "/" : "", name);
74    if (parent) free(path);
75  }
76  if (parent) parent->symlink = (char *)1;
77  free(dt);
78  return 0;
79}
80
81// Return path to this node, assembled recursively.
82
83// Initial call can pass in NULL to plen, or point to an int initialized to 0
84// to return the length of the path, or a value greater than 0 to allocate
85// extra space if you want to append your own text to the string.
86
87char *dirtree_path(struct dirtree *node, int *plen)
88{
89  char *path;
90  int len;
91
92  if (!node) {
93    path = xmalloc(*plen);
94    *plen = 0;
95    return path;
96  }
97
98  len = (plen ? *plen : 0)+strlen(node->name)+1;
99  path = dirtree_path(node->parent, &len);
100  if (len && path[len-1] != '/') path[len++]='/';
101  len = stpcpy(path+len, node->name) - path;
102  if (plen) *plen = len;
103
104  return path;
105}
106
107int dirtree_parentfd(struct dirtree *node)
108{
109  return node->parent ? node->parent->dirfd : AT_FDCWD;
110}
111
112// Handle callback for a node in the tree. Returns saved node(s) if
113// callback returns DIRTREE_SAVE, otherwise frees consumed nodes and
114// returns NULL. If !callback return top node unchanged.
115// If !new return DIRTREE_ABORTVAL
116
117struct dirtree *dirtree_handle_callback(struct dirtree *new,
118          int (*callback)(struct dirtree *node))
119{
120  int flags;
121
122  if (!new) return DIRTREE_ABORTVAL;
123  if (!callback) return new;
124  flags = callback(new);
125
126  if (S_ISDIR(new->st.st_mode) && (flags & (DIRTREE_RECURSE|DIRTREE_COMEAGAIN)))
127    flags = dirtree_recurse(new, callback,
128      openat(dirtree_parentfd(new), new->name, O_CLOEXEC|O_DIRECTORY), flags);
129  // If this had children, it was callback's job to free them already.
130  if (!(flags & DIRTREE_SAVE)) {
131    free(new);
132    new = 0;
133  }
134
135  return (flags & DIRTREE_ABORT)==DIRTREE_ABORT ? DIRTREE_ABORTVAL : new;
136}
137
138// Recursively read/process children of directory node, filtering through
139// callback(). Uses and closes supplied ->dirfd.
140
141int dirtree_recurse(struct dirtree *node,
142          int (*callback)(struct dirtree *node), int dirfd, int flags)
143{
144  struct dirtree *new, **ddt = &(node->child);
145  struct dirent *entry;
146  DIR *dir;
147
148  node->dirfd = dirfd;
149  if (node->dirfd == -1 || !(dir = fdopendir(node->dirfd))) {
150    if (!(flags & DIRTREE_SHUTUP)) {
151      char *path = dirtree_path(node, 0);
152      perror_msg_raw(path);
153      free(path);
154    }
155    close(node->dirfd);
156
157    return flags;
158  }
159
160  // according to the fddir() man page, the filehandle in the DIR * can still
161  // be externally used by things that don't lseek() it.
162
163  // The extra parentheses are to shut the stupid compiler up.
164  while ((entry = readdir(dir))) {
165    if ((flags&DIRTREE_PROC) && !isdigit(*entry->d_name)) continue;
166    if (!(new = dirtree_add_node(node, entry->d_name, flags))) continue;
167    if (!new->st.st_blksize && !new->st.st_mode)
168      new->st.st_mode = entry->d_type<<12;
169    new = dirtree_handle_callback(new, callback);
170    if (new == DIRTREE_ABORTVAL) break;
171    if (new) {
172      *ddt = new;
173      ddt = &((*ddt)->next);
174    }
175  }
176
177  if (flags & DIRTREE_COMEAGAIN) {
178    node->again |= 1;
179    flags = callback(node);
180  }
181
182  // This closes filehandle as well, so note it
183  closedir(dir);
184  node->dirfd = -1;
185
186  return flags;
187}
188
189// Create dirtree from path, using callback to filter nodes. If !callback
190// return just the top node. Use dirtree_notdotdot callback to allocate a
191// tree of struct dirtree nodes and return pointer to root node for later
192// processing.
193// Returns DIRTREE_ABORTVAL if path didn't exist (use DIRTREE_SHUTUP to handle
194// error message yourself).
195
196struct dirtree *dirtree_flagread(char *path, int flags,
197  int (*callback)(struct dirtree *node))
198{
199  return dirtree_handle_callback(dirtree_add_node(0, path, flags), callback);
200}
201
202// Common case
203struct dirtree *dirtree_read(char *path, int (*callback)(struct dirtree *node))
204{
205  return dirtree_flagread(path, 0, callback);
206}
207