comparison lib/dirtree.c @ 565:44abf4d901f3

Rewrite dirtree so we don't need readdir, scandir, and fts.h. Rewrite ls (from scratch) to use new dirtree infrastructure. (This breaks everything else that currently uses dirtree.)
author Rob Landley <rob@landley.net>
date Sat, 14 Apr 2012 22:30:41 -0500
parents 7259b853cb8b
children 2e0367cb9585
comparison
equal deleted inserted replaced
564:9530899eee51 565:44abf4d901f3
4 * Copyright 2007 Rob Landley <rob@landley.net> 4 * Copyright 2007 Rob Landley <rob@landley.net>
5 */ 5 */
6 6
7 #include "toys.h" 7 #include "toys.h"
8 8
9 // NOTE: This uses toybuf. Possibly it shouldn't do that. 9 // Create a dirtree node from a path, with stat and symlink info.
10 10
11 // Create a dirtree node from a path. 11 struct dirtree *dirtree_add_node(int dirfd, char *name)
12 {
13 struct dirtree *dt = NULL;
14 struct stat st;
15 char buf[4096];
16 int len = 0, linklen = 0;
12 17
13 struct dirtree *dirtree_add_node(char *path) 18 if (name) {
14 { 19 if (fstatat(dirfd, name, &st, AT_SYMLINK_NOFOLLOW)) goto error;
15 struct dirtree *dt; 20 if (S_ISLNK(st.st_mode)) {
16 char *name; 21 if (0>(linklen = readlinkat(dirfd, name, buf, 4095))) goto error;
22 buf[linklen++]=0;
23 }
24 len = strlen(name);
25 }
26 dt = xzalloc((len = sizeof(struct dirtree)+len+1)+linklen);
27 if (name) {
28 memcpy(&(dt->st), &st, sizeof(struct stat));
29 strcpy(dt->name, name);
17 30
18 // Find last chunk of name. 31 if (linklen) {
19 32 dt->symlink = memcpy(len+(char *)dt, buf, linklen);
20 for (;;) { 33 dt->data = --linklen;
21 name = strrchr(path, '/');
22
23 if (!name) name = path;
24 else {
25 if (*(name+1)) name++;
26 else {
27 *name=0;
28 continue;
29 }
30 } 34 }
31 break;
32 } 35 }
33 36
34 dt = xzalloc(sizeof(struct dirtree)+strlen(name)+1); 37 return dt;
35 if (lstat(path, &(dt->st))) {
36 error_msg("Skipped '%s'",name);
37 free(dt);
38 return 0;
39 }
40 strcpy(dt->name, name);
41 38
42 return dt; 39 error:
40 perror_msg("%s",name);
41 free(dt);
42 return 0;
43 } 43 }
44 44
45 // Given a directory (in a writeable PATH_MAX buffer), recursively read in a 45 // Return path to this node.
46 // directory tree. 46
47 char *dirtree_path(struct dirtree *node, int *plen)
48 {
49 char *path;
50 int len;
51
52 if (!node || !node->name) return xmalloc(*plen);
53
54 len = (plen ? *plen : 0) + strlen(node->name)+1;
55 path = dirtree_path(node->parent, &len);
56 len = plen ? *plen : 0;
57 if (len) path[len++]='/';
58 strcpy(path+len, node->name);
59
60 return path;
61 }
62
63 // Default callback, filters out "." and "..".
64
65 int dirtree_isdotdot(struct dirtree *catch)
66 {
67 // Should we skip "." and ".."?
68 if (catch->name[0]=='.' && (!catch->name[1] ||
69 (catch->name[1]=='.' && !catch->name[2])))
70 return DIRTREE_NOSAVE|DIRTREE_NORECURSE;
71
72 return 0;
73 }
74
75 // Handle callback for a node in the tree. Returns saved node(s) or NULL.
47 // 76 //
48 // If callback==NULL, allocate tree of struct dirtree and 77 // By default, allocates a tree of struct dirtree, not following symlinks
49 // return root of tree. Otherwise call callback(node) on each hit, free 78 // If callback==NULL, or callback always returns 0, allocate tree of struct
79 // dirtree and return root of tree. Otherwise call callback(node) on each hit, free
50 // structures after use, and return NULL. 80 // structures after use, and return NULL.
81 //
51 82
52 struct dirtree *dirtree_read(char *path, struct dirtree *parent, 83 struct dirtree *handle_callback(struct dirtree *new,
53 int (*callback)(char *path, struct dirtree *node)) 84 int (*callback)(struct dirtree *node))
54 { 85 {
55 struct dirtree *dtroot = NULL, *this, **ddt = &dtroot; 86 int flags;
56 DIR *dir;
57 int len = strlen(path);
58 87
59 if (!(dir = opendir(path))) perror_msg("No %s", path); 88 if (!callback) callback = dirtree_isdotdot;
60 else for (;;) { 89
61 int norecurse = 0; 90 flags = callback(new);
62 struct dirent *entry = readdir(dir); 91 if (S_ISDIR(new->st.st_mode)) {
63 if (!entry) { 92 if (!(flags & DIRTREE_NORECURSE)) {
64 closedir(dir); 93 new->data = openat(new->data, new->name, 0);
65 break; 94 dirtree_recurse(new, callback);
66 } 95 }
67 96 new->data = -1;
68 // Skip "." and ".." 97 if (flags & DIRTREE_COMEAGAIN) flags = callback(new);
69 if (entry->d_name[0]=='.') { 98 }
70 if (!entry->d_name[1]) continue; 99 // If this had children, it was callback's job to free them already.
71 if (entry->d_name[1]=='.' && !entry->d_name[2]) continue; 100 if (flags & DIRTREE_NOSAVE) {
72 } 101 free(new);
73 102 new = NULL;
74 snprintf(path+len, sizeof(toybuf)-len, "/%s", entry->d_name);
75 *ddt = this = dirtree_add_node(path);
76 if (!this) continue;
77 this->parent = parent;
78 this->depth = parent ? parent->depth + 1 : 1;
79 if (callback) norecurse = callback(path, this);
80 if (!norecurse && S_ISDIR(this->st.st_mode))
81 this->child = dirtree_read(path, this, callback);
82 if (callback) free(this);
83 else ddt = &(this->next);
84 path[len]=0;
85 } 103 }
86 104
87 return dtroot; 105 return (flags & DIRTREE_ABORT)==DIRTREE_ABORT ? DIRTREE_ABORTVAL : new;
88 } 106 }
89 107
108 // Recursively read/process children of directory node (with dirfd in data),
109 // filtering through callback().
90 110
111 void dirtree_recurse(struct dirtree *node,
112 int (*callback)(struct dirtree *node))
113 {
114 struct dirtree *new, **ddt = &(node->child);
115 struct dirent *entry;
116 DIR *dir;
117 int dirfd;
118
119 if (!(dir = fdopendir(node->data))) {
120 char *path = dirtree_path(node, 0);
121 perror_msg("No %s", path);
122 free(path);
123 close(node->data);
124 }
125 // Dunno if I really need to do this, but the fdopendir man page insists
126 dirfd = xdup(node->data);
127
128 // The extra parentheses are to shut the stupid compiler up.
129 while ((entry = readdir(dir))) {
130 if (!(new = dirtree_add_node(dirfd, entry->d_name))) continue;
131 new->parent = node;
132 new = handle_callback(new, callback);
133 if (new == DIRTREE_ABORTVAL) break;
134 if (new) {
135 *ddt = new;
136 ddt = &((*ddt)->next);
137 }
138 }
139
140 closedir(dir);
141 close(dirfd);
142 }
143
144 // Create dirtree from path, using callback to filter nodes.
145 // If callback == NULL allocate a tree of struct dirtree nodes and return
146 // pointer to root node.
147
148 struct dirtree *dirtree_read(char *path, int (*callback)(struct dirtree *node))
149 {
150 int fd = open(".", 0);
151 struct dirtree *root = dirtree_add_node(fd, path);
152 root->data = fd;
153
154 return handle_callback(root, callback);
155 }