diff options
Diffstat (limited to 'lib/dirtree.c')
-rw-r--r-- | lib/dirtree.c | 185 |
1 files changed, 125 insertions, 60 deletions
diff --git a/lib/dirtree.c b/lib/dirtree.c index 1993d007..fb74f8d8 100644 --- a/lib/dirtree.c +++ b/lib/dirtree.c @@ -6,85 +6,150 @@ #include "toys.h" -// NOTE: This uses toybuf. Possibly it shouldn't do that. +// Create a dirtree node from a path, with stat and symlink info. -// Create a dirtree node from a path. +struct dirtree *dirtree_add_node(int dirfd, char *name) +{ + struct dirtree *dt = NULL; + struct stat st; + char buf[4096]; + int len = 0, linklen = 0; + + if (name) { + if (fstatat(dirfd, name, &st, AT_SYMLINK_NOFOLLOW)) goto error; + if (S_ISLNK(st.st_mode)) { + if (0>(linklen = readlinkat(dirfd, name, buf, 4095))) goto error; + buf[linklen++]=0; + } + len = strlen(name); + } + dt = xzalloc((len = sizeof(struct dirtree)+len+1)+linklen); + if (name) { + memcpy(&(dt->st), &st, sizeof(struct stat)); + strcpy(dt->name, name); + + if (linklen) { + dt->symlink = memcpy(len+(char *)dt, buf, linklen); + dt->data = --linklen; + } + } + + return dt; + +error: + perror_msg("%s",name); + free(dt); + return 0; +} -struct dirtree *dirtree_add_node(char *path) +// Return path to this node. + +char *dirtree_path(struct dirtree *node, int *plen) { - struct dirtree *dt; - char *name; + char *path; + int len; - // Find last chunk of name. + if (!node || !node->name) return xmalloc(*plen); - for (;;) { - name = strrchr(path, '/'); + len = (plen ? *plen : 0) + strlen(node->name)+1; + path = dirtree_path(node->parent, &len); + len = plen ? *plen : 0; + if (len) path[len++]='/'; + strcpy(path+len, node->name); - if (!name) name = path; - else { - if (*(name+1)) name++; - else { - *name=0; - continue; - } - } - break; - } + return path; +} - dt = xzalloc(sizeof(struct dirtree)+strlen(name)+1); - if (lstat(path, &(dt->st))) { - error_msg("Skipped '%s'",name); - free(dt); - return 0; - } - strcpy(dt->name, name); +// Default callback, filters out "." and "..". - return dt; +int dirtree_isdotdot(struct dirtree *catch) +{ + // Should we skip "." and ".."? + if (catch->name[0]=='.' && (!catch->name[1] || + (catch->name[1]=='.' && !catch->name[2]))) + return DIRTREE_NOSAVE|DIRTREE_NORECURSE; + + return 0; } -// Given a directory (in a writeable PATH_MAX buffer), recursively read in a -// directory tree. +// Handle callback for a node in the tree. Returns saved node(s) or NULL. // -// If callback==NULL, allocate tree of struct dirtree and -// return root of tree. Otherwise call callback(node) on each hit, free +// By default, allocates a tree of struct dirtree, not following symlinks +// If callback==NULL, or callback always returns 0, allocate tree of struct +// dirtree and return root of tree. Otherwise call callback(node) on each hit, free // structures after use, and return NULL. +// -struct dirtree *dirtree_read(char *path, struct dirtree *parent, - int (*callback)(char *path, struct dirtree *node)) +struct dirtree *handle_callback(struct dirtree *new, + int (*callback)(struct dirtree *node)) { - struct dirtree *dtroot = NULL, *this, **ddt = &dtroot; - DIR *dir; - int len = strlen(path); - - if (!(dir = opendir(path))) perror_msg("No %s", path); - else for (;;) { - int norecurse = 0; - struct dirent *entry = readdir(dir); - if (!entry) { - closedir(dir); - break; - } + int flags; - // Skip "." and ".." - if (entry->d_name[0]=='.') { - if (!entry->d_name[1]) continue; - if (entry->d_name[1]=='.' && !entry->d_name[2]) continue; + if (!callback) callback = dirtree_isdotdot; + + flags = callback(new); + if (S_ISDIR(new->st.st_mode)) { + if (!(flags & DIRTREE_NORECURSE)) { + new->data = openat(new->data, new->name, 0); + dirtree_recurse(new, callback); } + new->data = -1; + if (flags & DIRTREE_COMEAGAIN) flags = callback(new); + } + // If this had children, it was callback's job to free them already. + if (flags & DIRTREE_NOSAVE) { + free(new); + new = NULL; + } + + return (flags & DIRTREE_ABORT)==DIRTREE_ABORT ? DIRTREE_ABORTVAL : new; +} + +// Recursively read/process children of directory node (with dirfd in data), +// filtering through callback(). - snprintf(path+len, sizeof(toybuf)-len, "/%s", entry->d_name); - *ddt = this = dirtree_add_node(path); - if (!this) continue; - this->parent = parent; - this->depth = parent ? parent->depth + 1 : 1; - if (callback) norecurse = callback(path, this); - if (!norecurse && S_ISDIR(this->st.st_mode)) - this->child = dirtree_read(path, this, callback); - if (callback) free(this); - else ddt = &(this->next); - path[len]=0; +void dirtree_recurse(struct dirtree *node, + int (*callback)(struct dirtree *node)) +{ + struct dirtree *new, **ddt = &(node->child); + struct dirent *entry; + DIR *dir; + int dirfd; + + if (!(dir = fdopendir(node->data))) { + char *path = dirtree_path(node, 0); + perror_msg("No %s", path); + free(path); + close(node->data); + } + // Dunno if I really need to do this, but the fdopendir man page insists + dirfd = xdup(node->data); + + // The extra parentheses are to shut the stupid compiler up. + while ((entry = readdir(dir))) { + if (!(new = dirtree_add_node(dirfd, entry->d_name))) continue; + new->parent = node; + new = handle_callback(new, callback); + if (new == DIRTREE_ABORTVAL) break; + if (new) { + *ddt = new; + ddt = &((*ddt)->next); + } } - return dtroot; + closedir(dir); + close(dirfd); } +// Create dirtree from path, using callback to filter nodes. +// If callback == NULL allocate a tree of struct dirtree nodes and return +// pointer to root node. + +struct dirtree *dirtree_read(char *path, int (*callback)(struct dirtree *node)) +{ + int fd = open(".", 0); + struct dirtree *root = dirtree_add_node(fd, path); + root->data = fd; + return handle_callback(root, callback); +} |