diff options
author | Tavian Barnes <tavianator@tavianator.com> | 2015-07-25 20:14:34 -0400 |
---|---|---|
committer | Tavian Barnes <tavianator@tavianator.com> | 2015-07-25 20:14:34 -0400 |
commit | 781f5902b7bbb91811f1f810f8a419607ed36294 (patch) | |
tree | 8ff1acf48ba81b2f35190a4f3d811ad1b6bf68f7 | |
parent | e674297be6be114a530cd06d2ca773baff1ce7cc (diff) | |
download | bfs-781f5902b7bbb91811f1f810f8a419607ed36294.tar.xz |
Recover from errors in diropen().
Fixes #4.
-rw-r--r-- | bfs.c | 11 | ||||
-rw-r--r-- | bftw.c | 200 | ||||
-rw-r--r-- | bftw.h | 13 | ||||
-rw-r--r-- | color.c | 31 | ||||
-rw-r--r-- | color.h | 18 |
5 files changed, 183 insertions, 90 deletions
@@ -23,9 +23,14 @@ typedef struct { bool hidden; } options; -static int callback(const char *fpath, const struct BFTW* ftwbuf, void *ptr) { +static int callback(const char *fpath, const struct BFTW *ftwbuf, void *ptr) { const options *opts = ptr; + if (ftwbuf->typeflag == BFTW_ERROR) { + print_error(opts->colors, fpath, ftwbuf); + return BFTW_SKIP_SUBTREE; + } + if (!opts->hidden) { if (ftwbuf->base > 0 && fpath[ftwbuf->base] == '.') { return BFTW_SKIP_SUBTREE; @@ -36,7 +41,7 @@ static int callback(const char *fpath, const struct BFTW* ftwbuf, void *ptr) { return BFTW_CONTINUE; } -int main(int argc, char* argv[]) { +int main(int argc, char *argv[]) { int ret = EXIT_FAILURE; options opts; @@ -73,7 +78,7 @@ int main(int argc, char* argv[]) { opts.path = "."; } - int flags = 0; + int flags = BFTW_RECOVER; if (color) { flags |= BFTW_STAT; @@ -105,6 +105,8 @@ struct dircache_entry { /** Reference count. */ size_t refcount; + /** The offset of this directory in the full path. */ + size_t nameoff; /** The length of the directory's name. */ size_t namelen; /** The directory's name. */ @@ -131,17 +133,28 @@ static void dircache_init(dircache *cache, size_t lru_size) { } /** Add an entry to the dircache. */ -static dircache_entry *dircache_add(dircache *cache, dircache_entry *parent, const char *path) { - size_t pathsize = strlen(path) + 1; - dircache_entry *entry = malloc(sizeof(dircache_entry) + pathsize); +static dircache_entry *dircache_add(dircache *cache, dircache_entry *parent, const char *name) { + size_t namesize = strlen(name) + 1; + dircache_entry *entry = malloc(sizeof(dircache_entry) + namesize); if (entry) { entry->parent = parent; - entry->depth = parent ? parent->depth + 1 : 0; + + if (parent) { + entry->depth = parent->depth + 1; + entry->nameoff = parent->nameoff + parent->namelen; + if (parent->namelen > 0 && parent->name[parent->namelen - 1] != '/') { + ++entry->nameoff; + } + } else { + entry->depth = 0; + entry->nameoff = 0; + } + entry->lru_prev = entry->lru_next = NULL; entry->dir = NULL; entry->refcount = 1; - entry->namelen = pathsize - 1; - memcpy(entry->name, path, pathsize); + entry->namelen = namesize - 1; + memcpy(entry->name, name, namesize); while (parent) { ++parent->refcount; @@ -212,81 +225,73 @@ static DIR *opendirat(int fd, const char *name) { } /** - * Open a dircache_entry. + * Get the full path do a dircache_entry. * - * @param cache - * The cache containing the entry. * @param entry - * The entry to open. + * The entry to look up. * @param[out] path * Will hold the full path to the entry, with a trailing '/'. - * @return - * The opened DIR *, or NULL on error. */ -static DIR *dircache_entry_open(dircache *cache, dircache_entry *entry, dynstr *path) { - assert(!entry->dir); +static int dircache_entry_path(dircache_entry *entry, dynstr *path) { + size_t pathlen = entry->nameoff + entry->namelen + 1; - if (cache->lru_remaining == 0) { - dircache_entry_close(cache, cache->lru_tail); + if (dynstr_grow(path, pathlen) != 0) { + return -1; } + path->length = pathlen; - // First, reserve enough space for the path - size_t pathlen = 0; + // Build the path backwards + path->str[pathlen] = '\0'; - dircache_entry *parent = entry; do { - size_t namelen = parent->namelen; - pathlen += namelen; + char *segment = path->str + entry->nameoff; + size_t namelen = entry->namelen; - if (namelen > 0 && parent->name[namelen - 1] != '/') { - ++pathlen; + memcpy(segment, entry->name, namelen); + if (namelen > 0 && entry->name[namelen - 1] != '/') { + segment[namelen] = '/'; } - parent = parent->parent; - } while (parent); - - if (dynstr_grow(path, pathlen) != 0) { - return NULL; - } - path->length = pathlen; - - // Now, build the path backwards while looking for a parent - char *segment = path->str + pathlen; - *segment = '\0'; - - int fd = AT_FDCWD; - const char *relpath = path->str; - - parent = entry; - while (true) { - size_t namelen = parent->namelen; - bool needs_slash = namelen > 0 && parent->name[namelen - 1] != '/'; + entry = entry->parent; + } while (entry); - segment -= namelen; - if (needs_slash) { - segment -= 1; - } + return 0; +} - memcpy(segment, parent->name, namelen); +/** + * Open a dircache_entry. + * + * @param cache + * The cache containing the entry. + * @param entry + * The entry to open. + * @param path + * The full path to the entry (see dircache_entry_path()). + * @return + * The opened DIR *, or NULL on error. + */ +static DIR *dircache_entry_open(dircache *cache, dircache_entry *entry, const char *path) { + assert(!entry->dir); - if (needs_slash) { - segment[namelen] = '/'; - } + if (cache->lru_remaining == 0) { + dircache_entry_close(cache, cache->lru_tail); + } - parent = parent->parent; - if (!parent) { - break; - } + size_t nameoff; + dircache_entry *base = entry; + do { + nameoff = base->nameoff; + base = base->parent; + } while (base && !base->dir); - if (parent->dir && fd == AT_FDCWD) { - dircache_lru_remove(cache, parent); - dircache_lru_add(cache, parent); - fd = dirfd(parent->dir); - relpath = segment; - } + int fd = AT_FDCWD; + if (base) { + dircache_lru_remove(cache, base); + dircache_lru_add(cache, base); + fd = dirfd(base->dir); } - DIR *dir = opendirat(fd, relpath); + DIR *dir = opendirat(fd, path + nameoff); if (dir) { entry->dir = dir; dircache_lru_add(cache, entry); @@ -395,7 +400,7 @@ static dircache_entry *dirqueue_pop(dirqueue *queue) { } int bftw(const char *dirpath, bftw_fn *fn, int nopenfd, int flags, void *ptr) { - int ret = -1, err; + int ret = -1, err = 0; dircache cache; dircache_init(&cache, nopenfd); @@ -408,34 +413,68 @@ int bftw(const char *dirpath, bftw_fn *fn, int nopenfd, int flags, void *ptr) { dircache_entry *current = dircache_add(&cache, NULL, dirpath); if (!current) { - goto done; + goto fail; } do { - DIR *dir = dircache_entry_open(&cache, current, &path); - if (!dir) { - goto done; + if (dircache_entry_path(current, &path) != 0) { + goto fail; } - size_t pathlen = path.length; + DIR *dir = dircache_entry_open(&cache, current, path.str); + if (!dir) { + if (!(flags & BFTW_RECOVER)) { + goto fail; + } + + err = errno; + + struct BFTW ftwbuf = { + .statbuf = NULL, + .typeflag = BFTW_ERROR, + .base = current->nameoff, + .level = current->depth, + .error = err, + }; + + int action = fn(path.str, &ftwbuf, ptr); + + switch (action) { + case BFTW_CONTINUE: + case BFTW_SKIP_SIBLINGS: + case BFTW_SKIP_SUBTREE: + goto next; + + case BFTW_STOP: + goto done; + + default: + err = EINVAL; + goto fail; + } + + goto next; + } + struct dirent *de; while ((de = readdir(dir)) != NULL) { if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) { continue; } + if (dynstr_concat(&path, pathlen, de->d_name) != 0) { + goto fail; + } + struct BFTW ftwbuf = { .statbuf = NULL, .typeflag = BFTW_UNKNOWN, .base = pathlen, .level = current->depth + 1, + .error = 0, }; - if (dynstr_concat(&path, pathlen, de->d_name) != 0) { - goto done; - } - #if defined(_DIRENT_HAVE_D_TYPE) || defined(DT_DIR) switch (de->d_type) { case DT_DIR: @@ -477,11 +516,11 @@ int bftw(const char *dirpath, bftw_fn *fn, int nopenfd, int flags, void *ptr) { if (ftwbuf.typeflag == BFTW_D) { dircache_entry *next = dircache_add(&cache, current, de->d_name); if (!next) { - goto done; + goto fail; } if (dirqueue_push(&queue, next) != 0) { - goto done; + goto fail; } } break; @@ -493,12 +532,11 @@ int bftw(const char *dirpath, bftw_fn *fn, int nopenfd, int flags, void *ptr) { break; case BFTW_STOP: - ret = 0; goto done; default: - errno = EINVAL; - goto done; + err = EINVAL; + goto fail; } } @@ -507,9 +545,15 @@ int bftw(const char *dirpath, bftw_fn *fn, int nopenfd, int flags, void *ptr) { current = dirqueue_pop(&queue); } while (current); - ret = 0; done: - err = errno; + if (err == 0) { + ret = 0; + } + +fail: + if (err == 0) { + err = errno; + } while (current) { dircache_entry_free(&cache, current); @@ -9,6 +9,9 @@ * the COPYING file or http://www.wtfpl.net/ for more details. * *********************************************************************/ +#ifndef BFS_BFTW_H +#define BFS_BFTW_H + #include <sys/stat.h> /** @@ -23,6 +26,8 @@ struct BFTW { int base; /** The depth of this file in the walk. */ int level; + /** The errno that occurred, if typeflag == BFTW_ERROR. */ + int error; }; /** @@ -69,6 +74,8 @@ int bftw(const char *dirpath, bftw_fn *fn, int nopenfd, int flags, void *ptr); #define BFTW_SL 2 /** typeflag: Unknown type. */ #define BFTW_UNKNOWN 3 +/** typeflag: An error occurred for this file. */ +#define BFTW_ERROR 4 /** action: Keep walking. */ #define BFTW_CONTINUE 0 @@ -80,4 +87,8 @@ int bftw(const char *dirpath, bftw_fn *fn, int nopenfd, int flags, void *ptr); #define BFTW_STOP 3 /** flag: stat() each encountered file. */ -#define BFTW_STAT (1 << 0) +#define BFTW_STAT (1 << 0) +/** flag: Attempt to recover from encountered errors. */ +#define BFTW_RECOVER (1 << 1) + +#endif // BFS_BFTW_H @@ -274,10 +274,10 @@ static const char *file_color(const color_table *colors, const char *filename, c return color; } -static void print_esc(const char *esc) { - fputs("\033[", stdout); - fputs(esc, stdout); - fputs("m", stdout); +static void print_esc(const char *esc, FILE *file) { + fputs("\033[", file); + fputs(esc, file); + fputs("m", file); } void pretty_print(const color_table *colors, const char *fpath, const struct stat *sb) { @@ -294,24 +294,39 @@ void pretty_print(const color_table *colors, const char *fpath, const struct sta } if (colors->dir) { - print_esc(colors->dir); + print_esc(colors->dir, stdout); } fwrite(fpath, 1, filename - fpath, stdout); if (colors->dir) { - print_esc(colors->reset); + print_esc(colors->reset, stdout); } const char *color = file_color(colors, filename, sb); if (color) { - print_esc(color); + print_esc(color, stdout); } fputs(filename, stdout); if (color) { - print_esc(colors->reset); + print_esc(colors->reset, stdout); } fputs("\n", stdout); } +void print_error(const color_table *colors, const char *fpath, const struct BFTW *ftwbuf) { + const char *color = NULL; + if (colors) { + color = colors->orphan; + } + + if (color) { + print_esc(color, stderr); + } + fprintf(stderr, "Error at %s: %s\n", fpath, strerror(ftwbuf->error)); + if (color) { + print_esc(colors->reset, stderr); + } +} + void free_colors(color_table *colors) { if (colors) { ext_color *ext = colors->ext_list; @@ -9,6 +9,10 @@ * the COPYING file or http://www.wtfpl.net/ for more details. * *********************************************************************/ +#ifndef BFS_COLOR_H +#define BFS_COLOR_H + +#include "bftw.h" #include <sys/stat.h> /** @@ -38,9 +42,23 @@ color_table *parse_colors(char *ls_colors); void pretty_print(const color_table *colors, const char *fpath, const struct stat *sb); /** + * Pretty-print an error. + * + * @param colors + * The color table to use. + * @param fpath + * The file path in error. + * @param ftwbuf + * The bftw() data for fpath. + */ +void print_error(const color_table *colors, const char *fpath, const struct BFTW *ftwbuf); + +/** * Free a color table. * * @param colors * The color table to free. */ void free_colors(color_table *colors); + +#endif // BFS_COLOR_H |