aboutsummaryrefslogtreecommitdiffstats
path: root/executor
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2016-10-18 21:07:40 +0200
committerDmitry Vyukov <dvyukov@google.com>2016-11-19 10:00:36 +0100
commit59f7c210d0584164a821bde6686debe169660f30 (patch)
treeffb942e9f3af91fb6e6fb26ca1ae4e48f9a54962 /executor
parentdbc7ff38051cba31976238c743b1d8c53ce64470 (diff)
repro: factor out of syz-repro tool
Factor out repro logic from syz-repro tool, so that it can be used in syz-manager. Also, support sandboxes in code generated by csoure. This is required to reproduce crashes that require e.g. namespace sandbox.
Diffstat (limited to 'executor')
-rw-r--r--executor/common.h365
-rw-r--r--executor/executor.cc311
2 files changed, 368 insertions, 308 deletions
diff --git a/executor/common.h b/executor/common.h
index 52f2aeb9b..62462817f 100644
--- a/executor/common.h
+++ b/executor/common.h
@@ -2,21 +2,88 @@
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
// This file is shared between executor and csource package.
+#include <dirent.h>
+#include <errno.h>
#include <fcntl.h>
+#include <grp.h>
+#include <linux/capability.h>
#include <pthread.h>
#include <setjmp.h>
#include <signal.h>
+#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <sys/prctl.h>
+#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/syscall.h>
+#include <sys/time.h>
#include <sys/types.h>
+#include <sys/wait.h>
#include <unistd.h>
+const int kFailStatus = 67;
+const int kErrorStatus = 68;
+const int kRetryStatus = 69;
+
+// logical error (e.g. invalid input program)
+__attribute__((noreturn)) void fail(const char* msg, ...)
+{
+ int e = errno;
+ fflush(stdout);
+ va_list args;
+ va_start(args, msg);
+ vfprintf(stderr, msg, args);
+ va_end(args);
+ fprintf(stderr, " (errno %d)\n", e);
+ exit(kFailStatus);
+}
+
+#if defined(SYZ_EXECUTOR)
+// kernel error (e.g. wrong syscall return value)
+__attribute__((noreturn)) void error(const char* msg, ...)
+{
+ fflush(stdout);
+ va_list args;
+ va_start(args, msg);
+ vfprintf(stderr, msg, args);
+ va_end(args);
+ fprintf(stderr, "\n");
+ exit(kErrorStatus);
+}
+#endif
+
+// just exit (e.g. due to temporal ENOMEM error)
+__attribute__((noreturn)) void exitf(const char* msg, ...)
+{
+ int e = errno;
+ fflush(stdout);
+ va_list args;
+ va_start(args, msg);
+ vfprintf(stderr, msg, args);
+ va_end(args);
+ fprintf(stderr, " (errno %d)\n", e);
+ exit(kRetryStatus);
+}
+
+static int flag_debug;
+
+void debug(const char* msg, ...)
+{
+ if (!flag_debug)
+ return;
+ va_list args;
+ va_start(args, msg);
+ vfprintf(stdout, msg, args);
+ va_end(args);
+ fflush(stdout);
+}
+
__thread int skip_segv;
__thread jmp_buf segv_env;
@@ -154,3 +221,301 @@ static uintptr_t execute_syscall(int nr, uintptr_t a0, uintptr_t a1, uintptr_t a
return syz_fuseblk_mount(a0, a1, a2, a3, a4, a5, a6, a7);
}
}
+
+static void setup_main_process()
+{
+ // Don't need that SIGCANCEL/SIGSETXID glibc stuff.
+ // SIGCANCEL sent to main thread causes it to exit
+ // without bringing down the whole group.
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = SIG_IGN;
+ syscall(SYS_rt_sigaction, 0x20, &sa, NULL, 8);
+ syscall(SYS_rt_sigaction, 0x21, &sa, NULL, 8);
+ install_segv_handler();
+
+ char tmpdir_template[] = "./syzkaller.XXXXXX";
+ char* tmpdir = mkdtemp(tmpdir_template);
+ if (!tmpdir)
+ fail("failed to mkdtemp");
+ if (chdir(tmpdir))
+ fail("failed to chdir");
+}
+
+static void loop();
+
+static void sandbox_common()
+{
+ prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
+ setpgrp();
+ setsid();
+
+ struct rlimit rlim;
+ rlim.rlim_cur = rlim.rlim_max = 128 << 20;
+ setrlimit(RLIMIT_AS, &rlim);
+ rlim.rlim_cur = rlim.rlim_max = 1 << 20;
+ setrlimit(RLIMIT_FSIZE, &rlim);
+ rlim.rlim_cur = rlim.rlim_max = 1 << 20;
+ setrlimit(RLIMIT_STACK, &rlim);
+ rlim.rlim_cur = rlim.rlim_max = 0;
+ setrlimit(RLIMIT_CORE, &rlim);
+
+ // CLONE_NEWIPC/CLONE_IO cause EINVAL on some systems, so we do them separately of clone.
+ unshare(CLONE_NEWNS);
+ unshare(CLONE_NEWIPC);
+ unshare(CLONE_IO);
+}
+
+#if defined(SYZ_EXECUTOR) || defined(SYZ_SANDBOX_NONE)
+static int do_sandbox_none()
+{
+ int pid = fork();
+ if (pid)
+ return pid;
+ sandbox_common();
+ loop();
+ exit(1);
+}
+#endif
+
+#if defined(SYZ_EXECUTOR) || defined(SYZ_SANDBOX_SETUID)
+static int do_sandbox_setuid()
+{
+ int pid = fork();
+ if (pid)
+ return pid;
+
+ sandbox_common();
+
+ const int nobody = 65534;
+ if (setgroups(0, NULL))
+ fail("failed to setgroups");
+ // glibc versions do not we want -- they force all threads to setuid.
+ // We want to preserve the thread above as root.
+ if (syscall(SYS_setresgid, nobody, nobody, nobody))
+ fail("failed to setresgid");
+ if (syscall(SYS_setresuid, nobody, nobody, nobody))
+ fail("failed to setresuid");
+
+ loop();
+ exit(1);
+}
+#endif
+
+#if defined(SYZ_EXECUTOR) || defined(SYZ_SANDBOX_NAMESPACE)
+static int real_uid;
+static int real_gid;
+static char sandbox_stack[1 << 20];
+
+static bool write_file(const char* file, const char* what, ...)
+{
+ char buf[1024];
+ va_list args;
+ va_start(args, what);
+ vsnprintf(buf, sizeof(buf), what, args);
+ va_end(args);
+ buf[sizeof(buf) - 1] = 0;
+ int len = strlen(buf);
+
+ int fd = open(file, O_WRONLY | O_CLOEXEC);
+ if (fd == -1)
+ return false;
+ if (write(fd, buf, len) != len) {
+ close(fd);
+ return false;
+ }
+ close(fd);
+ return true;
+}
+
+static int namespace_sandbox_proc(void* arg)
+{
+ sandbox_common();
+
+ // /proc/self/setgroups is not present on some systems, ignore error.
+ write_file("/proc/self/setgroups", "deny");
+ if (!write_file("/proc/self/uid_map", "0 %d 1\n", real_uid))
+ fail("write of /proc/self/uid_map failed");
+ if (!write_file("/proc/self/gid_map", "0 %d 1\n", real_gid))
+ fail("write of /proc/self/gid_map failed");
+
+ if (mkdir("./syz-tmp", 0777))
+ fail("mkdir(syz-tmp) failed");
+ if (mount("", "./syz-tmp", "tmpfs", 0, NULL))
+ fail("mount(tmpfs) failed");
+ if (mkdir("./syz-tmp/newroot", 0777))
+ fail("mkdir failed");
+ if (mkdir("./syz-tmp/newroot/dev", 0700))
+ fail("mkdir failed");
+ if (mount("/dev", "./syz-tmp/newroot/dev", NULL, MS_BIND | MS_REC | MS_PRIVATE, NULL))
+ fail("mount(dev) failed");
+ if (mkdir("./syz-tmp/pivot", 0777))
+ fail("mkdir failed");
+ if (syscall(SYS_pivot_root, "./syz-tmp", "./syz-tmp/pivot")) {
+ debug("pivot_root failed");
+ if (chdir("./syz-tmp"))
+ fail("chdir failed");
+ } else {
+ if (chdir("/"))
+ fail("chdir failed");
+ if (umount2("./pivot", MNT_DETACH))
+ fail("umount failed");
+ }
+ if (chroot("./newroot"))
+ fail("chroot failed");
+ if (chdir("/"))
+ fail("chdir failed");
+
+ // Drop CAP_SYS_PTRACE so that test processes can't attach to parent processes.
+ // Previously it lead to hangs because the loop process stopped due to SIGSTOP.
+ // Note that a process can always ptrace its direct children, which is enough
+ // for testing purposes.
+ __user_cap_header_struct cap_hdr = {};
+ __user_cap_data_struct cap_data[2] = {};
+ cap_hdr.version = _LINUX_CAPABILITY_VERSION_3;
+ cap_hdr.pid = getpid();
+ if (syscall(SYS_capget, &cap_hdr, &cap_data))
+ fail("capget failed");
+ cap_data[0].effective &= ~(1 << CAP_SYS_PTRACE);
+ cap_data[0].permitted &= ~(1 << CAP_SYS_PTRACE);
+ cap_data[0].inheritable &= ~(1 << CAP_SYS_PTRACE);
+ if (syscall(SYS_capset, &cap_hdr, &cap_data))
+ fail("capset failed");
+
+ loop();
+ exit(1);
+}
+
+static int do_sandbox_namespace()
+{
+ real_uid = getuid();
+ real_gid = getgid();
+ return clone(namespace_sandbox_proc, &sandbox_stack[sizeof(sandbox_stack) - 8],
+ CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNET, NULL);
+}
+#endif
+
+// One does not simply remove a directory.
+// There can be mounts, so we need to try to umount.
+// Moreover, a mount can be mounted several times, so we need to try to umount in a loop.
+// Moreover, after umount a dir can become non-empty again, so we need another loop.
+// Moreover, a mount can be re-mounted as read-only and then we will fail to make a dir empty.
+static void remove_dir(const char* dir)
+{
+ DIR* dp;
+ struct dirent* ep;
+ int iter = 0;
+retry:
+ dp = opendir(dir);
+ if (dp == NULL) {
+ if (errno == EMFILE) {
+ // This happens when the test process casts prlimit(NOFILE) on us.
+ // Ideally we somehow prevent test processes from messing with parent processes.
+ // But full sandboxing is expensive, so let's ignore this error for now.
+ exitf("opendir(%s) failed due to NOFILE, exiting");
+ }
+ exitf("opendir(%s) failed", dir);
+ }
+ while ((ep = readdir(dp))) {
+ if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0)
+ continue;
+ char filename[FILENAME_MAX];
+ snprintf(filename, sizeof(filename), "%s/%s", dir, ep->d_name);
+ struct stat st;
+ if (lstat(filename, &st))
+ exitf("lstat(%s) failed", filename);
+ if (S_ISDIR(st.st_mode)) {
+ remove_dir(filename);
+ continue;
+ }
+ for (int i = 0;; i++) {
+ debug("unlink(%s)\n", filename);
+ if (unlink(filename) == 0)
+ break;
+ if (errno == EROFS) {
+ debug("ignoring EROFS\n");
+ break;
+ }
+ if (errno != EBUSY || i > 100)
+ exitf("unlink(%s) failed", filename);
+ debug("umount(%s)\n", filename);
+ if (umount2(filename, MNT_DETACH))
+ exitf("umount(%s) failed", filename);
+ }
+ }
+ closedir(dp);
+ for (int i = 0;; i++) {
+ debug("rmdir(%s)\n", dir);
+ if (rmdir(dir) == 0)
+ break;
+ if (i < 100) {
+ if (errno == EROFS) {
+ debug("ignoring EROFS\n");
+ break;
+ }
+ if (errno == EBUSY) {
+ debug("umount(%s)\n", dir);
+ if (umount2(dir, MNT_DETACH))
+ exitf("umount(%s) failed", dir);
+ continue;
+ }
+ if (errno == ENOTEMPTY) {
+ if (iter < 100) {
+ iter++;
+ goto retry;
+ }
+ }
+ }
+ exitf("rmdir(%s) failed", dir);
+ }
+}
+
+static uint64_t current_time_ms()
+{
+ struct timespec ts;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &ts))
+ fail("clock_gettime failed");
+ return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
+}
+
+#if defined(SYZ_REPEAT)
+static void test();
+
+void loop()
+{
+ for (int iter = 0;; iter++) {
+ char cwdbuf[256];
+ sprintf(cwdbuf, "./%d", iter);
+ if (mkdir(cwdbuf, 0777))
+ fail("failed to mkdir");
+ int pid = fork();
+ if (pid < 0)
+ fail("clone failed");
+ if (pid == 0) {
+ prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
+ setpgrp();
+ if (chdir(cwdbuf))
+ fail("failed to chdir");
+ test();
+ exit(0);
+ }
+ int status = 0;
+ uint64_t start = current_time_ms();
+ for (;;) {
+ int res = waitpid(pid, &status, __WALL | WNOHANG);
+ int errno0 = errno;
+ if (res == pid)
+ break;
+ usleep(1000);
+ if (current_time_ms() - start > 5 * 1000) {
+ kill(-pid, SIGKILL);
+ kill(pid, SIGKILL);
+ waitpid(pid, &status, __WALL);
+ break;
+ }
+ }
+ remove_dir(cwdbuf);
+ }
+}
+#endif
diff --git a/executor/executor.cc b/executor/executor.cc
index 972a80d24..b155c578d 100644
--- a/executor/executor.cc
+++ b/executor/executor.cc
@@ -2,18 +2,14 @@
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
#include <algorithm>
-#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
-#include <grp.h>
#include <limits.h>
-#include <linux/capability.h>
#include <linux/futex.h>
#include <linux/reboot.h>
#include <pthread.h>
#include <setjmp.h>
#include <signal.h>
-#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
@@ -21,10 +17,8 @@
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
-#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/reboot.h>
-#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
@@ -35,6 +29,7 @@
#include "syscalls.h"
+#define SYZ_EXECUTOR
#include "common.h"
#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long long)
@@ -61,10 +56,6 @@ const uint64_t arg_const = 0;
const uint64_t arg_result = 1;
const uint64_t arg_data = 2;
-const int kFailStatus = 67;
-const int kErrorStatus = 68;
-const int kRetryStatus = 69;
-
// We use the default value instead of results of failed syscalls.
// -1 is an invalid fd and an invalid address and deterministic,
// so good enough for our purposes.
@@ -76,7 +67,6 @@ enum sandbox_type {
sandbox_namespace,
};
-bool flag_debug;
bool flag_cover;
bool flag_threaded;
bool flag_collide;
@@ -90,8 +80,6 @@ uint32_t* output_pos;
int completed;
int running;
bool collide;
-int real_uid;
-int real_gid;
struct res_t {
bool executed;
@@ -121,18 +109,7 @@ struct thread_t {
};
thread_t threads[kMaxThreads];
-char sandbox_stack[1 << 20];
-
-__attribute__((noreturn)) void fail(const char* msg, ...);
-__attribute__((noreturn)) void error(const char* msg, ...);
-__attribute__((noreturn)) void exitf(const char* msg, ...);
-void debug(const char* msg, ...);
-int sandbox_proc(void* arg);
-int do_sandbox_none();
-int do_sandbox_setuid();
-int do_sandbox_namespace();
-void sandbox_common();
-void loop();
+
void execute_one();
uint64_t read_input(uint64_t** input_posp, bool peek = false);
uint64_t read_arg(uint64_t** input_posp);
@@ -146,14 +123,11 @@ void handle_completion(thread_t* th);
void thread_create(thread_t* th, int id);
void* worker_thread(void* arg);
bool write_file(const char* file, const char* what, ...);
-void remove_dir(const char* dir);
-uint64_t current_time_ms();
void cover_open();
void cover_enable(thread_t* th);
void cover_reset(thread_t* th);
uint64_t cover_read(thread_t* th);
uint64_t cover_dedup(thread_t* th, uint64_t n);
-void handle_segv(int sig, siginfo_t* info, void* uctx);
int main(int argc, char** argv)
{
@@ -189,16 +163,7 @@ int main(int argc, char** argv)
flag_collide = false;
cover_open();
-
- // Don't need that SIGCANCEL/SIGSETXID glibc stuff.
- // SIGCANCEL sent to main thread causes it to exit
- // without bringing down the whole group.
- struct sigaction sa;
- memset(&sa, 0, sizeof(sa));
- sa.sa_handler = SIG_IGN;
- syscall(SYS_rt_sigaction, 0x20, &sa, NULL, 8);
- syscall(SYS_rt_sigaction, 0x21, &sa, NULL, 8);
- install_segv_handler();
+ setup_main_process();
int pid = -1;
switch (flag_sandbox) {
@@ -303,125 +268,6 @@ void loop()
}
}
-int do_sandbox_none()
-{
- int pid = fork();
- if (pid)
- return pid;
- loop();
- exit(1);
-}
-
-int do_sandbox_setuid()
-{
- int pid = fork();
- if (pid)
- return pid;
-
- sandbox_common();
-
- const int nobody = 65534;
- if (setgroups(0, NULL))
- fail("failed to setgroups");
- // glibc versions do not we want -- they force all threads to setuid.
- // We want to preserve the thread above as root.
- if (syscall(SYS_setresgid, nobody, nobody, nobody))
- fail("failed to setresgid");
- if (syscall(SYS_setresuid, nobody, nobody, nobody))
- fail("failed to setresuid");
-
- loop();
- exit(1);
-}
-
-int do_sandbox_namespace()
-{
- real_uid = getuid();
- real_gid = getgid();
- return clone(sandbox_proc, &sandbox_stack[sizeof(sandbox_stack) - 8],
- CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNET, NULL);
-}
-
-void sandbox_common()
-{
- prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
- setpgrp();
- setsid();
-
- struct rlimit rlim;
- rlim.rlim_cur = rlim.rlim_max = 128 << 20;
- setrlimit(RLIMIT_AS, &rlim);
- rlim.rlim_cur = rlim.rlim_max = 1 << 20;
- setrlimit(RLIMIT_FSIZE, &rlim);
- rlim.rlim_cur = rlim.rlim_max = 1 << 20;
- setrlimit(RLIMIT_STACK, &rlim);
- rlim.rlim_cur = rlim.rlim_max = 0;
- setrlimit(RLIMIT_CORE, &rlim);
-
- // CLONE_NEWIPC/CLONE_IO cause EINVAL on some systems, so we do them separately of clone.
- unshare(CLONE_NEWNS);
- unshare(CLONE_NEWIPC);
- unshare(CLONE_IO);
-}
-
-int sandbox_proc(void* arg)
-{
- sandbox_common();
-
- // /proc/self/setgroups is not present on some systems, ignore error.
- write_file("/proc/self/setgroups", "deny");
- if (!write_file("/proc/self/uid_map", "0 %d 1\n", real_uid))
- fail("write of /proc/self/uid_map failed");
- if (!write_file("/proc/self/gid_map", "0 %d 1\n", real_gid))
- fail("write of /proc/self/gid_map failed");
-
- if (mkdir("./syz-tmp", 0777))
- fail("mkdir(syz-tmp) failed");
- if (mount("", "./syz-tmp", "tmpfs", 0, NULL))
- fail("mount(tmpfs) failed");
- if (mkdir("./syz-tmp/newroot", 0777))
- fail("mkdir failed");
- if (mkdir("./syz-tmp/newroot/dev", 0700))
- fail("mkdir failed");
- if (mount("/dev", "./syz-tmp/newroot/dev", NULL, MS_BIND | MS_REC | MS_PRIVATE, NULL))
- fail("mount(dev) failed");
- if (mkdir("./syz-tmp/pivot", 0777))
- fail("mkdir failed");
- if (syscall(SYS_pivot_root, "./syz-tmp", "./syz-tmp/pivot")) {
- debug("pivot_root failed\n");
- if (chdir("./syz-tmp"))
- fail("chdir failed");
- } else {
- if (chdir("/"))
- fail("chdir failed");
- if (umount2("./pivot", MNT_DETACH))
- fail("umount failed");
- }
- if (chroot("./newroot"))
- fail("chroot failed");
- if (chdir("/"))
- fail("chdir failed");
-
- // Drop CAP_SYS_PTRACE so that test processes can't attach to parent processes.
- // Previously it lead to hangs because the loop process stopped due to SIGSTOP.
- // Note that a process can always ptrace its direct children, which is enough
- // for testing purposes.
- __user_cap_header_struct cap_hdr = {};
- __user_cap_data_struct cap_data[2] = {};
- cap_hdr.version = _LINUX_CAPABILITY_VERSION_3;
- cap_hdr.pid = getpid();
- if (syscall(SYS_capget, &cap_hdr, &cap_data))
- fail("capget failed");
- cap_data[0].effective &= ~(1 << CAP_SYS_PTRACE);
- cap_data[0].permitted &= ~(1 << CAP_SYS_PTRACE);
- cap_data[0].inheritable &= ~(1 << CAP_SYS_PTRACE);
- if (syscall(SYS_capset, &cap_hdr, &cap_data))
- fail("capset failed");
-
- loop();
- exit(1);
-}
-
void execute_one()
{
retry:
@@ -831,154 +677,3 @@ void write_output(uint32_t v)
fail("output overflow");
*output_pos++ = v;
}
-
-bool write_file(const char* file, const char* what, ...)
-{
- char buf[1024];
- va_list args;
- va_start(args, what);
- vsnprintf(buf, sizeof(buf), what, args);
- va_end(args);
- buf[sizeof(buf) - 1] = 0;
- int len = strlen(buf);
-
- int fd = open(file, O_WRONLY | O_CLOEXEC);
- if (fd == -1)
- return false;
- if (write(fd, buf, len) != len) {
- close(fd);
- return false;
- }
- close(fd);
- return true;
-}
-
-// One does not simply remove a directory.
-// There can be mounts, so we need to try to umount.
-// Moreover, a mount can be mounted several times, so we need to try to umount in a loop.
-// Moreover, after umount a dir can become non-empty again, so we need another loop.
-// Moreover, a mount can be re-mounted as read-only and then we will fail to make a dir empty.
-void remove_dir(const char* dir)
-{
- int iter = 0;
-retry:
- DIR* dp = opendir(dir);
- if (dp == NULL) {
- if (errno == EMFILE) {
- // This happens when the test process casts prlimit(NOFILE) on us.
- // Ideally we somehow prevent test processes from messing with parent processes.
- // But full sandboxing is expensive, so let's ignore this error for now.
- exitf("opendir(%s) failed due to NOFILE, exiting");
- }
- exitf("opendir(%s) failed", dir);
- }
- while (dirent* ep = readdir(dp)) {
- if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0)
- continue;
- char filename[FILENAME_MAX];
- snprintf(filename, sizeof(filename), "%s/%s", dir, ep->d_name);
- struct stat st;
- if (lstat(filename, &st))
- exitf("lstat(%s) failed", filename);
- if (S_ISDIR(st.st_mode)) {
- remove_dir(filename);
- continue;
- }
- for (int i = 0;; i++) {
- debug("unlink(%s)\n", filename);
- if (unlink(filename) == 0)
- break;
- if (errno == EROFS) {
- debug("ignoring EROFS\n");
- break;
- }
- if (errno != EBUSY || i > 100)
- exitf("unlink(%s) failed", filename);
- debug("umount(%s)\n", filename);
- if (umount2(filename, MNT_DETACH))
- exitf("umount(%s) failed", filename);
- }
- }
- closedir(dp);
- for (int i = 0;; i++) {
- debug("rmdir(%s)\n", dir);
- if (rmdir(dir) == 0)
- break;
- if (i < 100) {
- if (errno == EROFS) {
- debug("ignoring EROFS\n");
- break;
- }
- if (errno == EBUSY) {
- debug("umount(%s)\n", dir);
- if (umount2(dir, MNT_DETACH))
- exitf("umount(%s) failed", dir);
- continue;
- }
- if (errno == ENOTEMPTY) {
- if (iter < 100) {
- iter++;
- goto retry;
- }
- }
- }
- exitf("rmdir(%s) failed", dir);
- }
-}
-
-uint64_t current_time_ms()
-{
- timespec ts;
- if (clock_gettime(CLOCK_MONOTONIC, &ts))
- fail("clock_gettime failed");
- return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
-}
-
-// logical error (e.g. invalid input program)
-void fail(const char* msg, ...)
-{
- int e = errno;
- fflush(stdout);
- va_list args;
- va_start(args, msg);
- vfprintf(stderr, msg, args);
- va_end(args);
- fprintf(stderr, " (errno %d)\n", e);
- exit(kFailStatus);
-}
-
-// kernel error (e.g. wrong syscall return value)
-void error(const char* msg, ...)
-{
- fflush(stdout);
- va_list args;
- va_start(args, msg);
- vfprintf(stderr, msg, args);
- va_end(args);
- fprintf(stderr, "\n");
- exit(kErrorStatus);
-}
-
-// just exit (e.g. due to temporal ENOMEM error)
-void exitf(const char* msg, ...)
-{
- int e = errno;
- fflush(stdout);
- va_list args;
- va_start(args, msg);
- vfprintf(stderr, msg, args);
- va_end(args);
- fprintf(stderr, " (errno %d)\n", e);
- exit(kRetryStatus);
-}
-
-void debug(const char* msg, ...)
-{
- if (!flag_debug)
- return;
- va_list args;
- va_start(args, msg);
- vfprintf(stdout, msg, args);
- va_end(args);
- fflush(stdout);
-}