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