Mercurial > hg > toybox
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 } |