Let fuzzers recurse into directories
diff --git a/build-all.sh b/build-all.sh
index 56ef78d..d0da6f8 100755
--- a/build-all.sh
+++ b/build-all.sh
@@ -67,3 +67,7 @@
 
 ./build-example.sh
 ./build-fuzz.sh
+for f in gen/bin/fuzz-*; do
+  echo "Running $f"
+  $f test/data > /dev/null
+done
diff --git a/fuzz/c/fuzzlib/fuzzlib.c b/fuzz/c/fuzzlib/fuzzlib.c
index 0b78e89..03050f9 100644
--- a/fuzz/c/fuzzlib/fuzzlib.c
+++ b/fuzz/c/fuzzlib/fuzzlib.c
@@ -78,58 +78,170 @@
 
 #ifdef WUFFS_CONFIG__FUZZLIB_MAIN
 
+#include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <stdbool.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
-int main(int argc, char** argv) {
-  int i;
-  for (i = 1; i < argc; i++) {
-    printf("%-50s", argv[i]);
+static int num_files_processed;
 
-    struct stat z;
-    int fd = open(argv[i], O_RDONLY, 0);
-    if (fd == -1) {
-      printf("\n");
-      fprintf(stderr, "FAIL: open: %s\n", strerror(errno));
-      return 1;
+static struct {
+  char buf[PATH_MAX];
+  size_t len;
+} relative_cwd;
+
+static int visit(char* filename);
+
+static int visit_dir(int fd) {
+  int cwd_fd = open(".", O_RDONLY, 0);
+  if (fchdir(fd)) {
+    printf("failed\n");
+    fprintf(stderr, "FAIL: fchdir: %s\n", strerror(errno));
+    return 1;
+  }
+
+  DIR* d = fdopendir(fd);
+  if (!d) {
+    printf("failed\n");
+    fprintf(stderr, "FAIL: fdopendir: %s\n", strerror(errno));
+    return 1;
+  }
+
+  printf("+dir\n");
+  while (true) {
+    struct dirent* e = readdir(d);
+    if (!e) {
+      break;
     }
-    if (fstat(fd, &z)) {
-      printf("\n");
-      fprintf(stderr, "FAIL: fstat: %s\n", strerror(errno));
-      return 1;
+    if ((e->d_name[0] == '\x00') || (e->d_name[0] == '.')) {
+      continue;
     }
-    if ((z.st_size < 0) || (0x7FFFFFFF < z.st_size)) {
-      printf("\n");
-      fprintf(stderr, "FAIL: file size out of bounds");
-      return 1;
+    int v = visit(e->d_name);
+    if (v) {
+      return v;
     }
-    size_t n = z.st_size;
-    void* data = mmap(NULL, n, PROT_READ, MAP_SHARED, fd, 0);
+  }
+
+  if (closedir(d)) {
+    fprintf(stderr, "FAIL: closedir: %s\n", strerror(errno));
+    return 1;
+  }
+  if (fchdir(cwd_fd)) {
+    fprintf(stderr, "FAIL: fchdir: %s\n", strerror(errno));
+    return 1;
+  }
+  if (close(cwd_fd)) {
+    fprintf(stderr, "FAIL: close: %s\n", strerror(errno));
+    return 1;
+  }
+  return 0;
+}
+
+static int visit_reg(int fd, off_t size) {
+  if ((size < 0) || (0x7FFFFFFF < size)) {
+    printf("failed\n");
+    fprintf(stderr, "FAIL: file size out of bounds");
+    return 1;
+  }
+
+  void* data = NULL;
+  if (size > 0) {
+    data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
     if (data == MAP_FAILED) {
-      printf("\n");
+      printf("failed\n");
       fprintf(stderr, "FAIL: mmap: %s\n", strerror(errno));
       return 1;
     }
-
-    const char* msg = llvmFuzzerTestOneInput((const uint8_t*)(data), n);
-    if (!msg) {
-      msg = "(null)";
-    }
-    printf(" %s\n", msg);
-
-    if (munmap(data, n)) {
-      fprintf(stderr, "FAIL: mmap: %s\n", strerror(errno));
-      return 1;
-    }
-    if (close(fd)) {
-      fprintf(stderr, "FAIL: close: %s\n", strerror(errno));
-      return 1;
-    }
   }
-  printf("PASS: %d files processed\n", argc > 1 ? argc - 1 : 0);
+
+  const char* msg = llvmFuzzerTestOneInput((const uint8_t*)(data), size);
+  if (!msg) {
+    msg = "+ok";
+  }
+  printf("%s\n", msg);
+
+  if ((size > 0) && munmap(data, size)) {
+    fprintf(stderr, "FAIL: mmap: %s\n", strerror(errno));
+    return 1;
+  }
+  if (close(fd)) {
+    fprintf(stderr, "FAIL: close: %s\n", strerror(errno));
+    return 1;
+  }
+  return 0;
+}
+
+static int visit(char* filename) {
+  num_files_processed++;
+  if (!filename || (filename[0] == '\x00')) {
+    fprintf(stderr, "FAIL: invalid filename\n");
+    return 1;
+  }
+  int n = printf("- %s%s", relative_cwd.buf, filename);
+  printf("%*s", (60 > n) ? (60 - n) : 1, "");
+
+  struct stat z;
+  int fd = open(filename, O_RDONLY, 0);
+  if (fd == -1) {
+    printf("failed\n");
+    fprintf(stderr, "FAIL: open: %s\n", strerror(errno));
+    return 1;
+  }
+  if (fstat(fd, &z)) {
+    printf("failed\n");
+    fprintf(stderr, "FAIL: fstat: %s\n", strerror(errno));
+    return 1;
+  }
+
+  if (S_ISREG(z.st_mode)) {
+    return visit_reg(fd, z.st_size);
+  } else if (!S_ISDIR(z.st_mode)) {
+    printf("skipped\n");
+    return 0;
+  }
+
+  size_t old_len = relative_cwd.len;
+  size_t filename_len = strlen(filename);
+  size_t new_len = old_len + strlen(filename);
+  bool slash = filename[filename_len - 1] != '/';
+  if (slash) {
+    new_len++;
+  }
+  if ((filename_len >= PATH_MAX) || (new_len >= PATH_MAX)) {
+    printf("failed\n");
+    fprintf(stderr, "FAIL: path is too long\n");
+    return 1;
+  }
+  memcpy(relative_cwd.buf + old_len, filename, filename_len);
+
+  if (slash) {
+    relative_cwd.buf[new_len - 1] = '/';
+  }
+  relative_cwd.buf[new_len] = '\x00';
+  relative_cwd.len = new_len;
+
+  int v = visit_dir(fd);
+
+  relative_cwd.buf[old_len] = '\x00';
+  relative_cwd.len = old_len;
+  return v;
+}
+
+int main(int argc, char** argv) {
+  num_files_processed = 0;
+  relative_cwd.len = 0;
+
+  int i;
+  for (i = 1; i < argc; i++) {
+    int v = visit(argv[i]);
+    if (v) {
+      return v;
+    }
+  }
+  printf("PASS: %d files processed\n", num_files_processed);
   return 0;
 }