static LIST_HEAD(finished_tests);
 bool safe_mode = false;
 static struct btree *exclude;
+static bool fastmode = false;
 
 static void usage(const char *name)
 {
-       fprintf(stderr, "Usage: %s [-s] [-n] [-v] [-d <dirname>]\n"
+       fprintf(stderr, "Usage: %s [-s] [-n] [-v] [-t] [-d <dirname>]\n"
                "   -v: verbose mode\n"
                "   -s: simply give one line summary\n"
                "   -d: use this directory instead of the current one\n"
                "   -n: do not compile anything\n"
                "   -l: list tests ccanlint performs\n"
-               "   -x: exclude tests (e.g. -x trailing_whitespace,valgrind)\n",
+               "   -x: exclude tests (e.g. -x trailing_whitespace,valgrind)\n"
+               "   -t: ignore (terminate) tests that are slow\n",
                name);
        exit(1);
 }
                     struct manifest *m)
 {
        void *result;
-       unsigned int this_score;
+       unsigned int this_score, timeleft;
        const struct dependent *d;
        const char *skip;
 
                d->dependent->num_depends--;
 
        skip = should_skip(m, i);
+
        if (skip) {
+       skip:
                if (verbose)
                        printf("  %s: skipped (%s)\n", i->name, skip);
 
                return true;
        }
 
-       result = i->check(m);
+       timeleft = fastmode ? 1000 : default_timeout_ms;
+       result = i->check(m, &timeleft);
+       if (fastmode && timeleft == 0) {
+               skip = "timeout";
+               goto skip;
+       }
        if (!result) {
                if (verbose) {
                        printf("  %s: OK", i->name);
 
        /* I'd love to use long options, but that's not standard. */
        /* FIXME: getopt_long ccan package? */
-       while ((c = getopt(argc, argv, "sd:vnlx:")) != -1) {
+       while ((c = getopt(argc, argv, "sd:vnlx:t")) != -1) {
                switch (c) {
                case 'd':
                        dir = optarg;
                        for (i = 0; exclude_strs[i]; i++)
                                btree_insert(exclude, exclude_strs[i]);
                } break;
+               case 't':
+                       fastmode = true;
+                       break;
                default:
                        usage(argv[0]);
                }
 
        /* Can we run this test?  Return string explaining why, if not. */
        const char *(*can_run)(struct manifest *m);
 
-       /* If this returns non-NULL, it means the check failed. */
-       void *(*check)(struct manifest *m);
+       /* If this returns non-NULL, it means the check failed.
+        * If timeleft is set to 0, means it timed out. */
+       void *(*check)(struct manifest *m, unsigned int *timeleft);
 
        /* The non-NULL return from check is passed to one of these: */
 
 
        return list;
 }
 
-static void *do_build(struct manifest *m)
+static void *do_build(struct manifest *m, unsigned int *timeleft)
 {
        char *filename, *err;
 
 
        return NULL;
 }
 
-static void *check_objs_build(struct manifest *m)
+static void *check_objs_build(struct manifest *m, unsigned int *timeleft)
 {
        char *report = NULL;
        struct ccan_file *i;
 
        return ret;
 }
 
-static void *check_use_build(struct manifest *m)
+static void *check_use_build(struct manifest *m, unsigned int *timeleft)
 {
        char *contents;
        char *tmpfile, *err;
 
        return has_c_files;
 }
 
-static void *check_depends_built(struct manifest *m)
+static void *check_depends_built(struct manifest *m, unsigned int *timeleft)
 {
        struct ccan_file *i;
        struct stat st;
 
        return sofar;
 }
 
-static void *check_depends_exist(struct manifest *m)
+static void *check_depends_exist(struct manifest *m, unsigned int *timeleft)
 {
        unsigned int i;
        char *report = NULL;
 
        return NULL;
 }
 
-static void *check_includes_build(struct manifest *m)
+static void *check_includes_build(struct manifest *m, unsigned int *timeleft)
 {
        char *contents;
        char *tmpfile, *err;
 
        return err;
 }
 
-static void *do_compile_test_helpers(struct manifest *m)
+static void *do_compile_test_helpers(struct manifest *m, unsigned int *timeleft)
 {
        char *cmdout = NULL;
        struct ccan_file *i;
 
        const char *output;
 };
 
-static void *do_compile_tests(struct manifest *m)
+static void *do_compile_tests(struct manifest *m, unsigned int *timeleft)
 {
        struct list_head *list = talloc(m, struct list_head);
        char *cmdout;
 
 #include <string.h>
 #include <ccan/noerr/noerr.h>
 
-static void *check_has_info(struct manifest *m)
+static void *check_has_info(struct manifest *m, unsigned int *timeleft)
 {
        if (m->info_file)
                return NULL;
 
 #include <ccan/talloc/talloc.h>
 #include <ccan/noerr/noerr.h>
 
-static void *check_has_main_header(struct manifest *m)
+static void *check_has_main_header(struct manifest *m, unsigned int *timeleft)
 {
        struct ccan_file *f;
 
 
 
 static char test_is_not_dir[] = "test is not a directory";
 
-static void *check_has_tests(struct manifest *m)
+static void *check_has_tests(struct manifest *m, unsigned int *timeleft)
 {
        struct stat st;
        char *test_dir = talloc_asprintf(m, "%s/test", m->dir);
 
        const char *output;
 };
 
-static void *do_run_tests(struct manifest *m)
+static void *do_run_tests(struct manifest *m, unsigned int *timeleft)
 {
        struct list_head *list = talloc(m, struct list_head);
        struct run_tests_result *res;
 
        list_for_each(&m->run_tests, i, list) {
                run_tests.total_score++;
-               /* FIXME: timeout here */
-               cmdout = run_command(m, i->compiled);
+               cmdout = run_command(m, timeleft, i->compiled);
                if (cmdout) {
                        res = talloc(list, struct run_tests_result);
                        res->file = i;
 
        list_for_each(&m->api_tests, i, list) {
                run_tests.total_score++;
-               /* FIXME: timeout here */
-               cmdout = run_command(m, i->compiled);
+               cmdout = run_command(m, timeleft, i->compiled);
                if (cmdout) {
                        res = talloc(list, struct run_tests_result);
                        res->file = i;
 
        bool example;
 };
 
-static void *check_has_info_documentation(struct manifest *m)
+static void *check_has_info_documentation(struct manifest *m,
+                                         unsigned int *timeleft)
 {
        struct list_head *infodocs = get_ccan_file_docs(m->info_file);
        struct doc_section *d;
 
        return sofar;
 }
 
-static void *check_idempotent(struct manifest *m)
+static void *check_idempotent(struct manifest *m, unsigned int *timeleft)
 {
        struct ccan_file *f;
        char *report = NULL;
 
 /* Note: we already test safe_mode in run_tests.c */
 static const char *can_run_vg(struct manifest *m)
 {
-       char *output = run_command(m, "valgrind -q true");
+       unsigned int timeleft = default_timeout_ms;
+       char *output = run_command(m, &timeleft, "valgrind -q true");
 
        if (output)
                return talloc_asprintf(m, "No valgrind support: %s", output);
        const char *output;
 };
 
-static void *do_run_tests_vg(struct manifest *m)
+static void *do_run_tests_vg(struct manifest *m, unsigned int *timeleft)
 {
        struct list_head *list = talloc(m, struct list_head);
        struct run_tests_result *res;
 
        list_for_each(&m->run_tests, i, list) {
                run_tests_vg.total_score++;
-               /* FIXME: timeout here */
-               cmdout = run_command(m, "valgrind -q %s", i->compiled);
+               cmdout = run_command(m, timeleft,
+                                    "valgrind -q %s", i->compiled);
                if (cmdout) {
                        res = talloc(list, struct run_tests_result);
                        res->file = i;
 
        list_for_each(&m->api_tests, i, list) {
                run_tests_vg.total_score++;
-               /* FIXME: timeout here */
-               cmdout = run_command(m, "valgrind -q %s", i->compiled);
+               cmdout = run_command(m, timeleft,
+                                    "valgrind -q %s", i->compiled);
                if (cmdout) {
                        res = talloc(list, struct run_tests_result);
                        res->file = i;
 
        return talloc_asprintf(line, "'%s'", line);
 }
 
-static void *check_trailing_whitespace(struct manifest *m)
+static void *check_trailing_whitespace(struct manifest *m,
+                                      unsigned int *timeleft)
 {
        char *report;
 
 
 {
        char *file = temp_file(ctx, ".o");
 
-       *errmsg = run_command(ctx, "ld -r -o %s %s", file, objs);
+       *errmsg = run_command(ctx, NULL, "ld -r -o %s %s", file, objs);
        if (*errmsg) {
                talloc_free(file);
                return NULL;
 {
        char *file = temp_file(ctx, ".o");
 
-       *errmsg = run_command(ctx, "cc " CFLAGS " -I%s -c -o %s %s",
+       *errmsg = run_command(ctx, NULL, "cc " CFLAGS " -I%s -c -o %s %s",
                              ccandir, file, cfile);
        if (*errmsg) {
                talloc_free(file);
 {
        char *file = temp_file(ctx, "");
 
-       *errmsg = run_command(ctx, "cc " CFLAGS " -I%s %s -o %s %s %s %s",
+       *errmsg = run_command(ctx, NULL, "cc " CFLAGS " -I%s %s -o %s %s %s %s",
                              ccandir, extra_cflags, file, cfile, objs, libs);
        if (*errmsg) {
                talloc_free(file);
 
 #include <ccan/grab_file/grab_file.h>
 #include <ccan/noerr/noerr.h>
 #include <ccan/read_write_all/read_write_all.h>
+#include <ccan/noerr/noerr.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <sys/time.h>
+#include <sys/wait.h>
 #include <fcntl.h>
 #include <string.h>
 #include <unistd.h>
 static char *tmpdir = NULL;
 static unsigned int count;
 
+/* Ten minutes. */
+const unsigned int default_timeout_ms = 10 * 60 * 1000;
+
 char *talloc_basename(const void *ctx, const char *dir)
 {
        char *p = strrchr(dir, '/');
        return cwd;
 }
 
+static char *run_with_timeout(const void *ctx,
+                             const char *cmd,
+                             bool *ok,
+                             unsigned *timeout_ms)
+{
+       pid_t pid;
+       int p[2];
+       char *ret;
+       int status, ms;
+       struct timeval start, end;
+
+       *ok = false;
+       if (pipe(p) != 0)
+               return talloc_asprintf(ctx, "Failed to create pipe: %s",
+                                      strerror(errno));
+
+       pid = fork();
+       if (pid == -1) {
+               close_noerr(p[0]);
+               close_noerr(p[1]);
+               return talloc_asprintf(ctx, "Failed to fork: %s",
+                                      strerror(errno));
+               return NULL;
+       }
+
+       if (pid == 0) {
+               struct itimerval itim;
+
+               if (dup2(p[1], STDOUT_FILENO) != STDOUT_FILENO
+                   || dup2(p[1], STDERR_FILENO) != STDERR_FILENO
+                   || close(p[0]) != 0
+                   || close(STDIN_FILENO) != 0
+                   || open("/dev/null", O_RDONLY) != STDIN_FILENO)
+                       exit(128);
+
+               itim.it_interval.tv_sec = itim.it_interval.tv_usec = 0;
+               itim.it_value.tv_sec = *timeout_ms / 1000;
+               itim.it_value.tv_usec = (*timeout_ms % 1000) * 1000;
+               setitimer(ITIMER_REAL, &itim, NULL);
+
+               status = system(cmd);
+               if (WIFEXITED(status))
+                       exit(WEXITSTATUS(status));
+               /* Here's a hint... */
+               exit(128 + WTERMSIG(status));
+       }
+
+       close(p[1]);
+       gettimeofday(&start, NULL);
+       ret = grab_fd(ctx, p[0], NULL);
+       /* This shouldn't fail... */
+       if (waitpid(pid, &status, 0) != pid)
+               err(1, "Failed to wait for child");
+
+       gettimeofday(&end, NULL);
+       if (WIFSIGNALED(status)) {
+               *timeout_ms = 0;
+               return ret;
+       }
+       if (end.tv_usec < start.tv_usec) {
+               end.tv_usec += 1000000;
+               end.tv_sec--;
+       }
+       ms = (end.tv_sec - start.tv_sec) * 1000
+               + (end.tv_usec - start.tv_usec) / 1000;
+       if (ms > *timeout_ms)
+               *timeout_ms = 0;
+       else
+               *timeout_ms -= ms;
+
+       *ok = (WEXITSTATUS(status) == 0);
+       return ret;
+}
+
 /* Returns output if command fails. */
-char *run_command(const void *ctx, const char *fmt, ...)
+char *run_command(const void *ctx, unsigned int *time_ms, const char *fmt, ...)
 {
        va_list ap;
        char *cmd, *contents;
-       FILE *pipe;
+       bool ok;
+       unsigned int default_time = default_timeout_ms;
+
+       if (!time_ms)
+               time_ms = &default_time;
 
        va_start(ap, fmt);
        cmd = talloc_vasprintf(ctx, fmt, ap);
        va_end(ap);
 
-       /* Ensure stderr gets to us too. */
-       cmd = talloc_asprintf_append(cmd, " 2>&1");
-
-       pipe = popen(cmd, "r");
-       if (!pipe)
-               return talloc_asprintf(ctx, "Failed to run '%s'", cmd);
-
-       contents = grab_fd(cmd, fileno(pipe), NULL);
-       if (pclose(pipe) != 0)
-               return talloc_asprintf(ctx, "Running '%s':\n%s",
-                                      cmd, contents);
+       contents = run_with_timeout(ctx, cmd, &ok, time_ms);
+       if (ok) {
+               talloc_free(contents);
+               return NULL;
+       }
 
-       talloc_free(cmd);
-       return NULL;
+       if (!contents)
+               err(1, "Problem running child");
+       if (*time_ms == 0)
+               talloc_asprintf_append(contents, "\n== TIMED OUT ==\n");
+       return contents;
 }
 
 static int unlink_all(char *dir)
 
 char *talloc_basename(const void *ctx, const char *dir);
 char *talloc_dirname(const void *ctx, const char *dir);
 char *talloc_getcwd(const void *ctx);
-char *run_command(const void *ctx, const char *fmt, ...);
+char *run_command(const void *ctx, unsigned int *time_ms, const char *fmt, ...);
 char *temp_file(const void *ctx, const char *extension);
 bool move_file(const char *oldname, const char *newname);
 
 char *compile_and_link(const void *ctx, const char *cfile, const char *ccandir,
                       const char *objs, const char *extra_cflags,
                       const char *libs, char **errmsg);
+
+/* Default wait for run_command.  Should never time out. */
+extern const unsigned int default_timeout_ms;
+
 #endif /* CCAN_TOOLS_H */