OSDN Git Service

dirtree logic cleanup: switch DIRTREE_NORECURSE and DIRTREE_NOSAVE to DIRTREE_RECURSE...
[android-x86/external-toybox.git] / lib / dirtree.c
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
13 struct 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
41 error:
42         perror_msg("%s",name);
43         free(dt);
44         return 0;
45 }
46
47 // Return path to this node, assembled recursively.
48
49 char *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
71 int dirtree_notdotdot(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 0;
77
78         return DIRTREE_SAVE|DIRTREE_RECURSE;
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
89 struct dirtree *handle_callback(struct dirtree *new,
90                                         int (*callback)(struct dirtree *node))
91 {
92         int flags;
93
94         if (!callback) callback = dirtree_notdotdot;
95
96         flags = callback(new);
97         if (S_ISDIR(new->st.st_mode)) {
98                 if (flags & DIRTREE_RECURSE) {
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_SAVE)) {
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
118 void 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
156 struct 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 }