diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2016-10-18 21:07:40 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2016-11-19 10:00:36 +0100 |
| commit | 59f7c210d0584164a821bde6686debe169660f30 (patch) | |
| tree | ffb942e9f3af91fb6e6fb26ca1ae4e48f9a54962 /executor | |
| parent | dbc7ff38051cba31976238c743b1d8c53ce64470 (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.h | 365 | ||||
| -rw-r--r-- | executor/executor.cc | 311 |
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); -} |
