From 781f5902b7bbb91811f1f810f8a419607ed36294 Mon Sep 17 00:00:00 2001
From: Tavian Barnes <tavianator@tavianator.com>
Date: Sat, 25 Jul 2015 20:14:34 -0400
Subject: Recover from errors in diropen().

Fixes #4.
---
 bfs.c   |  11 +++-
 bftw.c  | 200 +++++++++++++++++++++++++++++++++++++++-------------------------
 bftw.h  |  13 ++++-
 color.c |  31 +++++++---
 color.h |  18 ++++++
 5 files changed, 183 insertions(+), 90 deletions(-)

diff --git a/bfs.c b/bfs.c
index a3a969c..fcd1383 100644
--- a/bfs.c
+++ b/bfs.c
@@ -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;
diff --git a/bftw.c b/bftw.c
index 9b990e7..31e6fe2 100644
--- a/bftw.c
+++ b/bftw.c
@@ -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);
diff --git a/bftw.h b/bftw.h
index 37e1524..45cc0c3 100644
--- a/bftw.h
+++ b/bftw.h
@@ -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
diff --git a/color.c b/color.c
index 6dda4cd..521fd09 100644
--- a/color.c
+++ b/color.c
@@ -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;
diff --git a/color.h b/color.h
index 9bc5b5a..1432072 100644
--- a/color.h
+++ b/color.h
@@ -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>
 
 /**
@@ -37,6 +41,18 @@ 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.
  *
@@ -44,3 +60,5 @@ void pretty_print(const color_table *colors, const char *fpath, const struct sta
  *         The color table to free.
  */
 void free_colors(color_table *colors);
+
+#endif // BFS_COLOR_H
-- 
cgit v1.2.3