import core.stdc.stdio; version (Posix) { extern (C) { import core.stdc.errno : errno, EAGAIN, ECHILD; import core.stdc.inttypes : PRIx64; import core.stdc.limits: PATH_MAX; import core.stdc.stdarg; import core.stdc.stdio; import core.stdc.stdlib : malloc, exit, getenv, strtol; import core.stdc.string : memcpy, memset, strchr, strcmp, strncpy, strdup, strlen, strncmp, strrchr; import core.sys.posix.fcntl; import core.sys.posix.sys.stat; import core.sys.posix.sys.types : off_t, ssize_t; import core.sys.posix.unistd; import core.sys.posix.sys.wait : waitpid, WNOHANG; int access(const char* pathname, int mode); int close(int fd); int dprintf(int fd, scope const char* format, ...) nothrow @nogc; int mkstemp(scope const(char*) tmplt) nothrow @nogc; int openat(int dirfd, scope const(char*) pathname, int flags) nothrow @nogc; ssize_t pread(int fd, void* buf, size_t count, off_t offset) @nogc; int setenv(scope const(char*) name, scope const(char*) value, int overwrite); int printf (scope const(char*) format, ...) nothrow @nogc; version(X86_64) { // from enum O_DIRECTORY = 0x10000; } extern (D) int __WTERMSIG( int status ) { return status & 0x7F; } extern (D) int WEXITSTATUS( int status ) { return ( status & 0xFF00 ) >> 8; } extern (D) bool WIFEXITED( int status ) { return __WTERMSIG( status ) == 0; } struct sha256 { ulong len; uint[8] h; ubyte[64] buf; } uint ror(uint n, int k) pure nothrow @nogc { return (n >> k) | (n << (32 - k)); } uint Ch(uint x, uint y, uint z) pure nothrow @nogc { return (z ^ (x & (y ^ z))); } uint Maj(uint x, uint y, uint z) pure nothrow @nogc { return ((x & y) | (z & (x | y))); } uint S0(uint x) pure nothrow @nogc { return (ror(x, 2) ^ ror(x, 13) ^ ror(x, 22)); } uint S1(uint x) pure nothrow @nogc { return (ror(x, 6) ^ ror(x, 11) ^ ror(x, 25)); } uint R0(uint x) pure nothrow @nogc { return (ror(x, 7) ^ ror(x, 18) ^ (x >> 3)); } uint R1(uint x) pure nothrow @nogc { return (ror(x, 17) ^ ror(x, 19) ^ (x >> 10)); } static const uint[64] K = [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 ]; static void processblock(sha256* s, const(ubyte*) buf) { uint[64] W; uint t1, t2, a, b, c, d, e, f, g, h; int i; for (i = 0; i < 16; i++) { W[i] = cast(uint) buf[4*i]<<24; W[i] |= cast(uint) buf[4*i+1]<<16; W[i] |= cast(uint) buf[4*i+2]<<8; W[i] |= buf[4*i+3]; } for (; i < 64; i++) { W[i] = R1(W[i-2]) + W[i-7] + R0(W[i-15]) + W[i-16]; } a = s.h[0]; b = s.h[1]; c = s.h[2]; d = s.h[3]; e = s.h[4]; f = s.h[5]; g = s.h[6]; h = s.h[7]; for (i = 0; i < 64; i++) { t1 = h + S1(e) + Ch(e, f, g) + K[i] + W[i]; t2 = S0(a) + Maj(a, b, c); h = g; g = f; f = e; e = d + t1; d = c; c = b; b = a; a = t1 + t2; } s.h[0] += a; s.h[1] += b; s.h[2] += c; s.h[3] += d; s.h[4] += e; s.h[5] += f; s.h[6] += g; s.h[7] += h; } static void pad(sha256* s) { ubyte r = s.len % 64; s.buf[r++] = 0x80; if (r > 56) { memset(cast(ubyte*) s.buf.ptr + r, 0, 64 - r); r = 0; processblock(s, cast(ubyte*) s.buf.ptr); } memset(cast(ubyte*) s.buf.ptr + r, 0, 56 - r); s.len *= 8; s.buf[56] = cast(ubyte) (s.len >> 56); s.buf[57] = cast(ubyte) (s.len >> 48); s.buf[58] = cast(ubyte) (s.len >> 40); s.buf[59] = cast(ubyte) (s.len >> 32); s.buf[60] = cast(ubyte) (s.len >> 24); s.buf[61] = cast(ubyte) (s.len >> 16); s.buf[62] = cast(ubyte) (s.len >> 8); s.buf[63] = cast(ubyte) s.len; processblock(s, cast(ubyte*) s.buf.ptr); } static void sha256_init(sha256* s) { s.len = 0; s.h[0] = 0x6a09e667; s.h[1] = 0xbb67ae85; s.h[2] = 0x3c6ef372; s.h[3] = 0xa54ff53a; s.h[4] = 0x510e527f; s.h[5] = 0x9b05688c; s.h[6] = 0x1f83d9ab; s.h[7] = 0x5be0cd19; } static void sha256_sum(sha256* s, ubyte* md) { int i; pad(s); for (i = 0; i < 8; i++) { md[4*i] = cast(ubyte) (s.h[i] >> 24); md[4*i+1] = cast(ubyte) (s.h[i] >> 16); md[4*i+2] = cast(ubyte) (s.h[i] >> 8); md[4*i+3] = cast(ubyte) (s.h[i]); } } static void sha256_update(sha256* s, const(void*) m, ulong len) { ubyte* p = cast(ubyte*) m; uint r = s.len % 64; s.len += len; if (r) { if (len < 64 - r) { memcpy(cast(ubyte*) s.buf.ptr + r, p, len); return; } memcpy(cast(ubyte*) s.buf.ptr + r, p, 64 - r); len -= 64 - r; p += 64 - r; processblock(s, cast(ubyte*) s.buf.ptr); } for (; len >= 64; len -= 64, p += 64) { processblock(s, cast(ubyte*) p); } memcpy(cast(ubyte*) s.buf.ptr, p, len); } __gshared int dir_fd = -1; __gshared int dep_fd = -1; __gshared int poolwr_fd = -1; __gshared int poolrd_fd = -1; __gshared int level = -1; __gshared int implicit_jobs = 1; __gshared int kflag, jflag, xflag, fflag, sflag; static void redo_ifcreate(int fd, char* target) { dprintf(fd, "-%s\n", target); } static char* check_dofile(const char *fmt, ...) { static char[PATH_MAX] dofile; va_list ap; va_start(ap, fmt); vsnprintf(cast(char*) dofile.ptr, dofile.sizeof, fmt, ap); va_end(ap); if (access(cast(char*) dofile.ptr, F_OK) == 0) { return cast(char*) dofile.ptr; } else { redo_ifcreate(dep_fd, cast(char*) dofile.ptr); return cast(char*) 0; } } static char* find_dofile(char* target) { char[PATH_MAX] updir; char* u = cast(char*) updir.ptr; char* dofile, s; stat_t st, ost; dofile = check_dofile("./%s.do", target); if (dofile) { return dofile; } *u++ = '.'; *u++ = '/'; *u = 0; st.st_dev = 0; ost.st_dev = 0; st.st_ino = 0; ost.st_ino = 0; while (1) { ost = st; if (stat(cast(char*) updir.ptr, &st) < 0) { return cast(char*) 0; } if ((ost.st_dev == st.st_dev) && (ost.st_ino == st.st_ino)) { break; } s = target; while (*s) { if (*s++ == '.') { dofile = check_dofile("%sdefault.%s.do", cast(char*) updir.ptr, s); if (dofile) { return dofile; } } } dofile = check_dofile("%sdefault.do", cast(char*) updir.ptr); if (dofile) { return dofile; } *u++ = '.'; *u++ = '.'; *u++ = '/'; *u = 0; } return cast(char*) 0; } static int envfd(const char* name) { long fd; char* s = getenv(name); if (!s) { return -1; } fd = strtol(s, null, 10); if ((fd < 0) || (fd > 255)) { fd = -1; } return cast(int) fd; } static void setenvfd(const char* name, int i) { char[16] buf; snprintf(cast(char*) buf.ptr, buf.sizeof, "%d", i); setenv(name, cast(char*) buf.ptr, 1); } static char* hashfile(int fd) { static char[16] hex = "0123456789abcdef"; static char[65] asciihash; sha256 ctx; off_t off = 0; char[4096] buf; char* a; char[32] hash; int i; ssize_t r; sha256_init(&ctx); while ((r = pread(fd, cast(char*) buf.ptr, buf.sizeof, off)) > 0) { sha256_update(&ctx, cast(char*) buf, r); off += r; } sha256_sum(&ctx, cast(ubyte*) hash); for (i = 0, a = cast(char*) asciihash.ptr; i < 32; i++) { *a++ = hex[hash[i] / 16]; *a++ = hex[hash[i] % 16]; } *a = 0; return cast(char*) asciihash.ptr; } static char* datefile(int fd) { static char[17] hexdate; stat_t st; fstat(fd, &st); snprintf(cast(char*) hexdate.ptr, hexdate.sizeof, cast(char*) ("%016" ~ cast(string) PRIx64), cast(ulong) st.st_ctime); return cast(char*) hexdate.ptr; } static int keepdir() { int fd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC); if (fd < 0) { perror("dir open"); exit(-1); } return fd; } static char* targetchdir(char* target) { char* base = strrchr(target, '/'); if (base) { int fd; *base = 0; fd = openat(dir_fd, target, O_RDONLY | O_DIRECTORY); if (fd < 0) { perror("openat dir"); exit(111); } *base = '/'; if (fchdir(fd) < 0) { perror("chdir"); exit(111); } close(fd); return base + 1; } else { fchdir(dir_fd); return target; } } static char* targetdep(char* target) { static char[PATH_MAX] buf; snprintf(cast(char*) buf.ptr, buf.sizeof, ".dep.%s", target); return cast(char*) buf.ptr; } static char* targetlock(char* target) { static char[PATH_MAX] buf; snprintf(cast(char*) buf.ptr, buf.sizeof, ".lock.%s", target); return cast(char*) buf.ptr; } static int sourcefile(char* target) { if (access(targetdep(target), F_OK) == 0) { return 0; } if (fflag < 0) { return access(target, F_OK) == 0; } return (find_dofile(target) is null); } static int check_deps(char* target) { char* depfile; FILE* f; int ok = 1; int fd; int old_dir_fd = dir_fd; target = targetchdir(target); if (sourcefile(target)) { return 1; } if (fflag > 0) { return 0; } depfile = targetdep(target); f = fopen(depfile, "r"); if (!f) { return 0; } dir_fd = keepdir(); while (ok && !feof(f)) { char[4096] line; char* hash = (cast(char*) line.ptr) + 1; char* timestamp = (cast(char*) line.ptr) + 1 + 64 + 1; char* filename = (cast(char*) line.ptr) + 1 + 64 + 1 + 16 + 1; if (fgets(cast(char*) line.ptr, line.sizeof, f)) { line[strlen(cast(char*) line.ptr)-1] = 0; switch (line[0]) { case '-': if (access(cast(char*) line.ptr + 1, F_OK) == 0) { ok = 0; } break; case '=': fd = open(filename, O_RDONLY); if (fd < 0) { ok = 0; } else { if (strncmp(timestamp, datefile(fd), 16) != 0 && strncmp(hash, hashfile(fd), 64) != 0) { ok = 0; } close(fd); } if (ok && strcmp(target, filename) != 0) { ok = check_deps(filename); fchdir(dir_fd); } break; case '!': default: ok = 0; } } else { if (!feof(f)) { ok = 0; break; } } } fclose(f); close(dir_fd); dir_fd = old_dir_fd; return ok; } void vacate(int implicit) { if (implicit) { implicit_jobs++; } else { write(poolwr_fd, cast(char*) "\0".ptr, 1); } } struct job { job* next; pid_t pid; int lock_fd; char* target, temp_depfile, temp_target; int implicit; } __gshared job* jobhead; static void insert_job(job* jobn) { jobn.next = jobhead; jobhead = jobn; } static void remove_job(job* jobn) { if (jobhead == jobn) { jobhead = jobhead.next; } else { job* j = jobhead; while (j.next != jobn) { j = j.next; } j.next = j.next.next; } } static job* find_job(pid_t pid) { job* j; for (j = jobhead; j; j = j.next) { if (j.pid == pid) { return j; } } return cast(job*) 0; } __gshared char[PATH_MAX] uprel = "\0"; void compute_uprel() { char* u = cast(char*) uprel.ptr; char* dp = getenv("REDO_DIRPREFIX"); *u = 0; while (dp && *dp) { *u++ = '.'; *u++ = '.'; *u++ = '/'; *u = 0; dp = strchr(dp + 1, '/'); } } static int write_dep(int dep_fd, const(char*) file) { int fd = open(file, O_RDONLY); if (fd < 0) { return 0; } dprintf(dep_fd, "=%s %s %s%s\n", hashfile(fd), datefile(fd), (*file == '/' ? "" : cast(char*) uprel.ptr), file); close(fd); return 0; } int new_waitjob(int lock_fd, int implicit) { pid_t pid; pid = fork(); if (pid < 0) { perror("fork"); vacate(implicit); exit(-1); } else if (pid == 0) { lockf(lock_fd, F_LOCK, 0); close(lock_fd); exit(0); } else { job* job; if (!job) { exit(-1); } job.target = cast(char*) 0; job.pid = pid; job.lock_fd = lock_fd; job.implicit = implicit; insert_job(job); } return cast(char*) 0; } static char* redo_basename(char* dofile, char* target) { static char[PATH_MAX] buf = "\0"; int stripext = 0; char* s; if (strncmp(dofile, "default.", 8) == 0) { for (stripext = -1, s = dofile; *s; s++) { if (*s == '.') { stripext++; } } } strncpy(cast(char*) buf.ptr, target, buf.sizeof); while (stripext--> 0) { if (strchr(cast(char*) buf.ptr, '.')) { char* e = strchr(cast(char*) buf.ptr, '\0'); while (*--e != '.') { *e = 0; } *e = 0; } } return cast(char*) buf.ptr; } static void run_script(char* target, int implicit) { char[15] temp_depfile = cast(char[]) ".depend.XXXXXX\0"; char[15] temp_target_base = cast(char[]) ".target.XXXXXX\0"; char[PATH_MAX] temp_target = "\0", rel_target = "\0", cwd = "\0"; char* orig_target = target; int old_dep_fd = dep_fd; int target_fd; char* dofile, dirprefix; pid_t pid; target = targetchdir(target); dofile = find_dofile(target); if (!dofile) { fprintf(stderr, "no dofile for %s.\n", target); exit(1); } int lock_fd = open(targetlock(target), O_WRONLY | O_TRUNC | O_CREAT, 0x1b6); if (lockf(lock_fd, F_TLOCK, 0) < 0) { if (errno == EAGAIN) { fprintf(stderr, "redo: %s already building, waiting.\n", orig_target); new_waitjob(lock_fd, implicit); return; } else { perror("lockf"); exit(111); } } dep_fd = mkstemp(cast(char*) temp_depfile.ptr); target_fd = mkstemp(cast(char*) temp_target_base.ptr); fprintf(stderr, "redo%*.*s %s # %s\n", level*2, level*2, cast(char*) " ".ptr, orig_target, dofile); write_dep(dep_fd, dofile); getcwd(cast(char*) cwd.ptr, cwd.sizeof); dirprefix = strchr(cast(char*) cwd.ptr, '\0'); dofile += 2; while (strncmp(dofile, "../", 3) == 0) { chdir(".."); dofile += 3; while (*--dirprefix != '/') {} } if (*dirprefix) { dirprefix++; } snprintf(cast(char*) temp_target.ptr, temp_target.sizeof, "%s%s%s", dirprefix, (*dirprefix ? cast(char*) "/".ptr : cast(char*) "".ptr), cast(char*) temp_target_base.ptr); snprintf(cast(char*) rel_target.ptr, rel_target.sizeof, "%s%s%s", dirprefix, (*dirprefix ? cast(char*) "/".ptr : cast(char*) "".ptr), target); setenv("REDO_DIRPREFIX", dirprefix, 1); pid = fork(); if (pid < 0) { perror("fork"); vacate(implicit); exit(-1); } else if (pid == 0) { char* basename = redo_basename(dofile, cast(char*) rel_target.ptr); if (old_dep_fd > 0) { close(old_dep_fd); } close(lock_fd); setenvfd("REDO_DEP_FD", dep_fd); setenvfd("REDO_LEVEL", level + 1); if (sflag > 0) { dup2(target_fd, 1); } else { close(target_fd); } if (access(dofile, X_OK) != 0) { execl( cast(char*) "/bin/sh\0".ptr, cast(char*) "/bin/sh\0".ptr, (xflag > 0) ? (cast(char*) "-ex\0".ptr) : (cast(char*) "-e\0".ptr), dofile, cast(char*) rel_target.ptr, basename, cast(char*) temp_target.ptr, cast(char*) 0 ); } else { execl( dofile, dofile, cast(char*) rel_target.ptr, basename, cast(char*) temp_target.ptr, cast(char*) 0 ); } vacate(implicit); exit(-1); } else { job* job; if (!job) { exit(-1); } close(target_fd); close(dep_fd); dep_fd = old_dep_fd; job.pid = pid; job.lock_fd = lock_fd; job.target = orig_target; job.temp_depfile = strdup(cast(char*) temp_depfile.ptr); job.temp_target = strdup(cast(char*) temp_target_base.ptr); job.implicit = implicit; insert_job(job); } } static int try_procure() { if (implicit_jobs > 0) { implicit_jobs--; return 1; } else { if (poolrd_fd < 0) { return 0; } fcntl(poolrd_fd, F_SETFL, O_NONBLOCK); char[1] buf; return read(poolrd_fd, &buf, 1) > 0; } } static int procure() { if (implicit_jobs > 0) { implicit_jobs--; return 1; } else { fcntl(poolrd_fd, F_SETFL, 0); char[1] buf; return read(poolrd_fd, &buf, 1) > 0; } } void create_pool() { poolrd_fd = envfd(cast(char*) "REDO_RD_FD".ptr); poolwr_fd = envfd(cast(char*) "REDO_WR_FD".ptr); if (poolrd_fd < 0 || poolwr_fd < 0) { int jobs = envfd(cast(char*) "JOBS".ptr); if (jobs > 1) { int i; int[2] fds; pipe(fds); poolrd_fd = fds[0]; poolwr_fd = fds[1]; for (i = 0; i < jobs-1; i++) { vacate(0); } setenvfd(cast(char*) "REDO_RD_FD".ptr, poolrd_fd); setenvfd(cast(char*) "REDO_WR_FD".ptr, poolwr_fd); } else { poolrd_fd = -1; poolwr_fd = -1; } } } static void redo_ifchange(int targetc, char** targetv) { pid_t pid; int status = 0; job* job = null; int targeti = 0; char[4096] buf = void; char* skip = cast(char*) buf.ptr; create_pool(); for (targeti = 0; targeti < targetc; targeti++) { skip[targeti] = cast(char) check_deps(targetv[targeti]); printf("steps %d-%s\n", targeti, targetv[targeti]); //printf("skips %d-%s\n", targeti, skip[targeti]); } printf("targetc = %d\n", targetc); targeti = 0; while (1) { int procured = 0; if (targeti < targetc) { char* target = cast(char*) targetv[targeti]; if (skip[targeti]) { targeti++; continue; } int implicit = implicit_jobs > 0; if (try_procure()) { procured = 1; targeti++; printf("run script, implicit - %d\n", implicit); run_script(target, implicit); } } printf("error here\n"); pid = waitpid(-1, &status, procured ? WNOHANG : 0); printf("pid = %d\n", pid); if (pid == 0) { continue; } if (pid < 0) { if ((errno == ECHILD) && (targeti < targetc)) { continue; } else { break; } } if (WIFEXITED(status)) { status = WEXITSTATUS(status); } job = find_job(pid); if (!job) { exit(-1); } remove_job(job); if (job.target) { if (status > 0) { remove(job.temp_depfile); remove(job.temp_target); } else { stat_t st; char* target = targetchdir(job.target); char* depfile = targetdep(target); int dfd; dfd = open(job.temp_depfile, O_WRONLY | O_APPEND); if (stat(job.temp_target, &st) == 0) { rename(job.temp_target, target); write_dep(dfd, target); } else { remove(job.temp_target); redo_ifcreate(dfd, target); } close(dfd); rename(job.temp_depfile, depfile); remove(targetlock(target)); } } close(job.lock_fd); vacate(job.implicit); if ((kflag < 0) && (status > 0)) { printf("failed with status %d\n", status); exit(status); } } } static void record_deps(int targetc, char** targetv) { int targeti = 0; int fd; dep_fd = envfd("REDO_DEP_FD"); if (dep_fd < 0) { return; } fchdir(dir_fd); for (targeti = 0; targeti < targetc; targeti++) { fd = open(targetv[targeti], O_RDONLY); if (fd < 0) { continue; } write_dep(dep_fd, targetv[targeti]); close(fd); } } int main(int argc, char** argv) { char* program; int opt = 0, i = 0; dep_fd = envfd(cast(char*) "REDO_DEP_FD".ptr); level = envfd(cast(char*) "REDO_LEVEL".ptr); if (level < 0) { level = 0; } char* progname = strrchr(cast(char*) argv[0], cast(char*) '/'); if (progname !is null) { program = progname; program++; } else { program = argv[0]; } while ((opt = getopt(argc, argv, "+kxfsj:C:")) != -1) { switch (opt) { case 'k': setenvfd(cast(char*) "REDO_KEEP_GOING".ptr, 1); break; case 'x': setenvfd(cast(char*) "REDO_TRACE".ptr, 1); break; case 'f': setenvfd(cast(char*) "REDO_FORCE".ptr, 1); break; case 's': setenvfd(cast(char*) "REDO_STDOUT".ptr, 1); break; case 'j': setenv(cast(char*) "JOBS".ptr, optarg, 1); break; case 'C': if (chdir(optarg) < 0) { perror("chdir"); exit(-1); } break; default: fprintf(stderr, "usage: %s [-kfsx] [-jN] [-Cdir] [TARGETS...]\n", program); exit(1); break; } } argc -= optind; argv += optind; fflag = envfd(cast(char*) "REDO_FORCE".ptr); kflag = envfd(cast(char*) "REDO_KEEP_GOING".ptr); xflag = envfd(cast(char*) "REDO_TRACE".ptr); sflag = envfd(cast(char*) "REDO_STDOUT".ptr); dir_fd = keepdir(); if (strcmp(program, cast(char*) "redo".ptr) == 0) { char* all = cast(char*) "all\0".ptr; char*[1] argv_def = [all]; if (argc == 0) { argc = 1; argv = argv_def.ptr; printf("%s", argv[0]); } fflag = 1; printf("%s", argv); redo_ifchange(argc, argv); procure(); } else if (strcmp(program, cast(char*) "redo-ifchange".ptr) == 0) { compute_uprel(); redo_ifchange(argc, argv); record_deps(argc, argv); procure(); } else if (strcmp(program, cast(char*) "redo-ifcreate".ptr) == 0) { for (i = 0; i < argc; i++) { redo_ifcreate(dep_fd, argv[i]); } } else if (strcmp(program, cast(char*) "redo-always".ptr) == 0) { dprintf(dep_fd, "!\n"); } else if (strcmp(program, cast(char*) "redo-hash".ptr) == 0) { for (i = 0; i < argc; i++) { write_dep(1, cast(const(char*)) argv[i]); } } else { fprintf(stderr, "not implemented %s\n", program); exit(-1); } return 0; } } }