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