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