aboutsummaryrefslogtreecommitdiff
path: root/std
diff options
context:
space:
mode:
authorAndrew Kelley <superjoe30@gmail.com>2017-04-02 18:19:59 -0400
committerAndrew Kelley <superjoe30@gmail.com>2017-04-02 18:19:59 -0400
commit8fd0fddce5d44344dd7914ae86a4d976b99f9cc3 (patch)
tree69d6a1b6e32e8c8a622eb149c0e60f33f426996d /std
parent0594487a2e98e18a18c0c6bdb2532c5e36fc6ea7 (diff)
downloadzig-8fd0fddce5d44344dd7914ae86a4d976b99f9cc3.tar.gz
zig-8fd0fddce5d44344dd7914ae86a4d976b99f9cc3.zip
zig build system progress
* In-progress os.ChildProcess.spawn implementation. See #204 * Add explicit cast from integer to error. Closes #294 * fix casting from error to integer * fix compiler crash when initializing variable to undefined with no type
Diffstat (limited to 'std')
-rw-r--r--std/build.zig38
-rw-r--r--std/io.zig13
-rw-r--r--std/os/darwin.zig4
-rw-r--r--std/os/index.zig343
-rw-r--r--std/os/linux.zig74
5 files changed, 458 insertions, 14 deletions
diff --git a/std/build.zig b/std/build.zig
index 746800668a..8daac949c2 100644
--- a/std/build.zig
+++ b/std/build.zig
@@ -3,8 +3,12 @@ const mem = @import("mem.zig");
const debug = @import("debug.zig");
const List = @import("list.zig").List;
const Allocator = @import("mem.zig").Allocator;
+const os = @import("os/index.zig");
+const StdIo = os.ChildProcess.StdIo;
+const Term = os.ChildProcess.Term;
error ExtraArg;
+error UncleanExit;
pub const Builder = struct {
zig_exe: []const u8,
@@ -33,9 +37,9 @@ pub const Builder = struct {
return exe;
}
- pub fn make(self: &Builder, args: []const []const u8) -> %void {
+ pub fn make(self: &Builder, cli_args: []const []const u8) -> %void {
var verbose = false;
- for (args) |arg| {
+ for (cli_args) |arg| {
if (mem.eql(u8, arg, "--verbose")) {
verbose = true;
} else {
@@ -44,7 +48,27 @@ pub const Builder = struct {
}
}
for (self.exe_list.toSlice()) |exe| {
- %%io.stderr.printf("TODO: invoke this command:\nzig build_exe {} --name {}\n", exe.root_src, exe.name);
+ var zig_args = List([]const u8).init(self.allocator);
+ defer zig_args.deinit();
+
+ %return zig_args.append("build_exe"[0...]); // TODO issue #296
+ %return zig_args.append(exe.root_src);
+ %return zig_args.append("--name"[0...]); // TODO issue #296
+ %return zig_args.append(exe.name);
+
+ printInvocation(self.zig_exe, zig_args);
+ const TODO_env: []const []const u8 = undefined; // TODO
+ var child = %return os.ChildProcess.spawn(self.zig_exe, zig_args.toSliceConst(), TODO_env,
+ StdIo.Ignore, StdIo.Inherit, StdIo.Inherit);
+ const term = %return child.wait();
+ switch (term) {
+ Term.Clean => |code| {
+ if (code != 0) {
+ return error.UncleanExit;
+ }
+ },
+ else => return error.UncleanExit,
+ }
}
}
};
@@ -57,3 +81,11 @@ const Exe = struct {
fn handleErr(err: error) -> noreturn {
debug.panic("error: {}\n", @errorName(err));
}
+
+fn printInvocation(exe_name: []const u8, args: &const List([]const u8)) {
+ %%io.stderr.printf("{}", exe_name);
+ for (args.toSliceConst()) |arg| {
+ %%io.stderr.printf(" {}", arg);
+ }
+ %%io.stderr.printf("\n");
+}
diff --git a/std/io.zig b/std/io.zig
index 109ed4072e..89b3a8c21f 100644
--- a/std/io.zig
+++ b/std/io.zig
@@ -13,22 +13,18 @@ const mem = @import("mem.zig");
const Buffer0 = @import("cstr.zig").Buffer0;
const fmt = @import("fmt.zig");
-pub const stdin_fileno = 0;
-pub const stdout_fileno = 1;
-pub const stderr_fileno = 2;
-
pub var stdin = InStream {
- .fd = stdin_fileno,
+ .fd = system.STDIN_FILENO,
};
pub var stdout = OutStream {
- .fd = stdout_fileno,
+ .fd = system.STDOUT_FILENO,
.buffer = undefined,
.index = 0,
};
pub var stderr = OutStream {
- .fd = stderr_fileno,
+ .fd = system.STDERR_FILENO,
.buffer = undefined,
.index = 0,
};
@@ -234,7 +230,6 @@ pub const InStream = struct {
if (read_err > 0) {
switch (read_err) {
errno.EINTR => continue,
-
errno.EINVAL => unreachable,
errno.EFAULT => unreachable,
errno.EBADF => return error.BadFd,
@@ -247,7 +242,7 @@ pub const InStream = struct {
}
return index;
},
- else => @compileError("unsupported OS"),
+ else => @compileError("Unsupported OS"),
}
}
diff --git a/std/os/darwin.zig b/std/os/darwin.zig
index 647fee40e2..fca34960bf 100644
--- a/std/os/darwin.zig
+++ b/std/os/darwin.zig
@@ -6,6 +6,10 @@ const arch = switch (@compileVar("arch")) {
const errno = @import("errno.zig");
+pub const STDIN_FILENO = 0;
+pub const STDOUT_FILENO = 1;
+pub const STDERR_FILENO = 2;
+
pub const O_LARGEFILE = 0x0000;
pub const O_RDONLY = 0x0000;
diff --git a/std/os/index.zig b/std/os/index.zig
index 63e74924fa..baa43e23de 100644
--- a/std/os/index.zig
+++ b/std/os/index.zig
@@ -7,12 +7,26 @@ pub const posix = switch(@compileVar("os")) {
Os.windows => windows,
else => @compileError("Unsupported OS"),
};
+const debug = @import("../debug.zig");
+const assert = debug.assert;
const errno = @import("errno.zig");
const linking_libc = @import("../target.zig").linking_libc;
const c = @import("../c/index.zig");
+const mem = @import("../mem.zig");
+const Allocator = mem.Allocator;
+
+const io = @import("../io.zig");
+
error Unexpected;
+error SysResources;
+error AccessDenied;
+error InvalidExe;
+error FileSystem;
+error IsDir;
+error FileNotFound;
+error FileBusy;
/// Fills `buf` with random bytes. If linking against libc, this calls the
/// appropriate OS-specific library call. Otherwise it uses the zig standard
@@ -76,3 +90,332 @@ pub coldcc fn abort() -> noreturn {
else => @compileError("Unsupported OS"),
}
}
+
+fn makePipe() -> %[2]i32 {
+ var fds: [2]i32 = undefined;
+ const err = posix.getErrno(posix.pipe(&fds));
+ if (err > 0) {
+ return switch (err) {
+ errno.EMFILE, errno.ENFILE => error.SysResources,
+ else => error.Unexpected,
+ }
+ }
+ return fds;
+}
+
+fn destroyPipe(pipe: &const [2]i32) {
+ closeNoIntr((*pipe)[0]);
+ closeNoIntr((*pipe)[1]);
+}
+
+fn closeNoIntr(fd: i32) {
+ while (true) {
+ const err = posix.getErrno(posix.close(fd));
+ if (err == errno.EINTR) {
+ continue;
+ } else {
+ return;
+ }
+ }
+}
+
+fn openNoIntr(path: []const u8, flags: usize, perm: usize) -> %i32 {
+ while (true) {
+ const result = posix.open(path, flags, perm);
+ const err = posix.getErrno(result);
+ if (err > 0) {
+ return switch (err) {
+ errno.EINTR => continue,
+
+ errno.EFAULT => unreachable,
+ errno.EINVAL => unreachable,
+ errno.EACCES => error.BadPerm,
+ errno.EFBIG, errno.EOVERFLOW => error.FileTooBig,
+ errno.EISDIR => error.IsDir,
+ errno.ELOOP => error.SymLinkLoop,
+ errno.EMFILE => error.ProcessFdQuotaExceeded,
+ errno.ENAMETOOLONG => error.NameTooLong,
+ errno.ENFILE => error.SystemFdQuotaExceeded,
+ errno.ENODEV => error.NoDevice,
+ errno.ENOENT => error.PathNotFound,
+ errno.ENOMEM => error.NoMem,
+ errno.ENOSPC => error.NoSpaceLeft,
+ errno.ENOTDIR => error.NotDir,
+ errno.EPERM => error.BadPerm,
+ else => error.Unexpected,
+ }
+ }
+ return i32(result);
+ }
+}
+
+const ErrInt = @intType(false, @sizeOf(error) * 8);
+fn writeIntFd(fd: i32, value: ErrInt) -> %void {
+ var bytes: [@sizeOf(ErrInt)]u8 = undefined;
+ mem.writeInt(bytes[0...], value, true);
+
+ var index: usize = 0;
+ while (index < bytes.len) {
+ const amt_written = posix.write(fd, &bytes[index], bytes.len - index);
+ const err = posix.getErrno(amt_written);
+ if (err > 0) {
+ switch (err) {
+ errno.EINTR => continue,
+ errno.EINVAL => unreachable,
+ else => return error.SysResources,
+ }
+ }
+ index += amt_written;
+ }
+}
+
+fn readIntFd(fd: i32) -> %ErrInt {
+ var bytes: [@sizeOf(ErrInt)]u8 = undefined;
+
+ var index: usize = 0;
+ while (index < bytes.len) {
+ const amt_written = posix.read(fd, &bytes[index], bytes.len - index);
+ const err = posix.getErrno(amt_written);
+ if (err > 0) {
+ switch (err) {
+ errno.EINTR => continue,
+ errno.EINVAL => unreachable,
+ else => return error.SysResources,
+ }
+ }
+ index += amt_written;
+ }
+
+ return mem.readInt(bytes[0...], ErrInt, true);
+}
+
+// Child of fork calls this to report an error to the fork parent.
+// Then the child exits.
+fn forkChildErrReport(fd: i32, err: error) -> noreturn {
+ _ = writeIntFd(fd, ErrInt(err));
+ posix.exit(1);
+}
+
+fn dup2NoIntr(old_fd: i32, new_fd: i32) -> %void {
+ while (true) {
+ const err = posix.getErrno(posix.dup2(old_fd, new_fd));
+ if (err > 0) {
+ return switch (err) {
+ errno.EBUSY, errno.EINTR => continue,
+ errno.EMFILE => error.SysResources,
+ errno.EINVAL => unreachable,
+ else => error.Unexpected,
+ };
+ }
+ return;
+ }
+}
+
+pub const ChildProcess = struct {
+ pid: i32,
+ err_pipe: [2]i32,
+
+ stdin: ?io.OutStream,
+ stdout: ?io.InStream,
+ stderr: ?io.InStream,
+
+ pub const Term = enum {
+ Clean: i32,
+ Signal: i32,
+ Stopped: i32,
+ Unknown: i32,
+ };
+
+ pub const StdIo = enum {
+ Inherit,
+ Ignore,
+ Pipe,
+ Close,
+ };
+
+ pub fn spawn(exe_path: []const u8, args: []const []const u8, env: []const []const u8,
+ stdin: StdIo, stdout: StdIo, stderr: StdIo) -> %ChildProcess
+ {
+ switch (@compileVar("os")) {
+ Os.linux, Os.macosx, Os.ios, Os.darwin => {
+ return spawnPosix(exe_path, args, env, stdin, stdout, stderr);
+ },
+ else => @compileError("Unsupported OS"),
+ }
+ }
+
+ pub fn wait(self: &ChildProcess) -> %Term {
+ defer {
+ closeNoIntr(self.err_pipe[0]);
+ closeNoIntr(self.err_pipe[1]);
+ };
+
+ var status: i32 = undefined;
+ while (true) {
+ const err = posix.getErrno(posix.waitpid(self.pid, &status, 0));
+ if (err > 0) {
+ switch (err) {
+ errno.EINVAL, errno.ECHILD => unreachable,
+ errno.EINTR => continue,
+ else => {
+ if (const *stdin ?= self.stdin) { stdin.close(); }
+ if (const *stdout ?= self.stdin) { stdout.close(); }
+ if (const *stderr ?= self.stdin) { stderr.close(); }
+ return error.Unexpected;
+ },
+ }
+ }
+ break;
+ }
+
+ if (const *stdin ?= self.stdin) { stdin.close(); }
+ if (const *stdout ?= self.stdin) { stdout.close(); }
+ if (const *stderr ?= self.stdin) { stderr.close(); }
+
+ // Write @maxValue(ErrInt) to the write end of the err_pipe. This is after
+ // waitpid, so this write is guaranteed to be after the child
+ // pid potentially wrote an error. This way we can do a blocking
+ // read on the error pipe and either get @maxValue(ErrInt) (no error) or
+ // an error code.
+ %return writeIntFd(self.err_pipe[1], @maxValue(ErrInt));
+ const err_int = %return readIntFd(self.err_pipe[0]);
+ // Here we potentially return the fork child's error
+ // from the parent pid.
+ if (err_int != @maxValue(ErrInt)) {
+ return error(err_int);
+ }
+
+ return statusToTerm(status);
+ }
+
+ fn statusToTerm(status: i32) -> Term {
+ return if (posix.WIFEXITED(status)) {
+ Term.Clean { posix.WEXITSTATUS(status) }
+ } else if (posix.WIFSIGNALED(status)) {
+ Term.Signal { posix.WTERMSIG(status) }
+ } else if (posix.WIFSTOPPED(status)) {
+ Term.Stopped { posix.WSTOPSIG(status) }
+ } else {
+ Term.Unknown { status }
+ };
+ }
+
+ fn spawnPosix(exe_path: []const u8, args: []const []const u8, env: []const []const u8,
+ stdin: StdIo, stdout: StdIo, stderr: StdIo) -> %ChildProcess
+ {
+ // TODO issue #295
+ //const stdin_pipe = if (stdin == StdIo.Pipe) %return makePipe() else undefined;
+ var stdin_pipe: [2]i32 = undefined;
+ if (stdin == StdIo.Pipe)
+ stdin_pipe = %return makePipe();
+ %defer if (stdin == StdIo.Pipe) { destroyPipe(stdin_pipe); };
+
+ // TODO issue #295
+ //const stdout_pipe = if (stdout == StdIo.Pipe) %return makePipe() else undefined;
+ var stdout_pipe: [2]i32 = undefined;
+ if (stdout == StdIo.Pipe)
+ stdout_pipe = %return makePipe();
+ %defer if (stdout == StdIo.Pipe) { destroyPipe(stdout_pipe); };
+
+ // TODO issue #295
+ //const stderr_pipe = if (stderr == StdIo.Pipe) %return makePipe() else undefined;
+ var stderr_pipe: [2]i32 = undefined;
+ if (stderr == StdIo.Pipe)
+ stderr_pipe = %return makePipe();
+ %defer if (stderr == StdIo.Pipe) { destroyPipe(stderr_pipe); };
+
+ const any_ignore = (stdin == StdIo.Ignore or stdout == StdIo.Ignore or stderr == StdIo.Ignore);
+ // TODO issue #295
+ //const dev_null_fd = if (any_ignore) {
+ // %return openNoIntr("/dev/null", posix.O_RDWR, 0)
+ //} else {
+ // undefined
+ //};
+ var dev_null_fd: i32 = undefined;
+ if (any_ignore)
+ dev_null_fd = %return openNoIntr("/dev/null", posix.O_RDWR, 0);
+
+ // This pipe is used to communicate errors between the time of fork
+ // and execve from the child process to the parent process.
+ const err_pipe = %return makePipe();
+ %defer destroyPipe(err_pipe);
+
+ const pid = posix.fork();
+ const pid_err = linux.getErrno(pid);
+ if (pid_err > 0) {
+ return switch (pid_err) {
+ errno.EAGAIN, errno.ENOMEM, errno.ENOSYS => error.SysResources,
+ else => error.Unexpected,
+ };
+ }
+ if (pid == 0) {
+ // we are the child
+ setUpChildIo(stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) %%
+ |err| forkChildErrReport(err_pipe[1], err);
+ setUpChildIo(stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) %%
+ |err| forkChildErrReport(err_pipe[1], err);
+ setUpChildIo(stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) %%
+ |err| forkChildErrReport(err_pipe[1], err);
+
+ const err = posix.getErrno(posix.execve(exe_path, args, env));
+ assert(err > 0);
+ forkChildErrReport(err_pipe[1], switch (err) {
+ errno.EFAULT => unreachable,
+ errno.E2BIG, errno.EMFILE, errno.ENAMETOOLONG, errno.ENFILE, errno.ENOMEM => error.SysResources,
+ errno.EACCES, errno.EPERM => error.AccessDenied,
+ errno.EINVAL, errno.ENOEXEC => error.InvalidExe,
+ errno.EIO, errno.ELOOP => error.FileSystem,
+ errno.EISDIR => error.IsDir,
+ errno.ENOENT, errno.ENOTDIR => error.FileNotFound,
+ errno.ETXTBSY => error.FileBusy,
+ else => error.Unexpected,
+ });
+ }
+
+ // we are the parent
+ if (stdin == StdIo.Pipe) { closeNoIntr(stdin_pipe[0]); }
+ if (stdout == StdIo.Pipe) { closeNoIntr(stdout_pipe[1]); }
+ if (stderr == StdIo.Pipe) { closeNoIntr(stderr_pipe[1]); }
+ if (any_ignore) { closeNoIntr(dev_null_fd); }
+
+ return ChildProcess {
+ .pid = i32(pid),
+ .err_pipe = err_pipe,
+
+ .stdin = if (stdin == StdIo.Pipe) {
+ io.OutStream {
+ .fd = stdin_pipe[1],
+ }
+ } else {
+ null
+ },
+ .stdout = if (stdout == StdIo.Pipe) {
+ io.InStream {
+ .fd = stdout_pipe[0],
+ .buffer = undefined,
+ .index = 0,
+ }
+ } else {
+ null
+ },
+ .stderr = if (stderr == StdIo.Pipe) {
+ io.InStream {
+ .fd = stderr_pipe[0],
+ .buffer = undefined,
+ .index = 0,
+ }
+ } else {
+ null
+ },
+ };
+ }
+
+ fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) -> %void {
+ switch (stdio) {
+ StdIo.Pipe => %return dup2NoIntr(pipe_fd, std_fileno),
+ StdIo.Close => closeNoIntr(std_fileno),
+ StdIo.Inherit => {},
+ StdIo.Ignore => %return dup2NoIntr(dev_null_fd, std_fileno),
+ }
+ }
+};
diff --git a/std/os/linux.zig b/std/os/linux.zig
index a42bebe4c8..8e1bf54aee 100644
--- a/std/os/linux.zig
+++ b/std/os/linux.zig
@@ -5,6 +5,10 @@ const arch = switch (@compileVar("arch")) {
};
const errno = @import("errno.zig");
+pub const STDIN_FILENO = 0;
+pub const STDOUT_FILENO = 1;
+pub const STDERR_FILENO = 2;
+
pub const PROT_NONE = 0;
pub const PROT_READ = 1;
pub const PROT_WRITE = 2;
@@ -237,12 +241,66 @@ pub const AF_NFC = PF_NFC;
pub const AF_VSOCK = PF_VSOCK;
pub const AF_MAX = PF_MAX;
+
+fn unsigned(s: i32) -> u32 { *@ptrcast(&u32, &s) }
+fn signed(s: u32) -> i32 { *@ptrcast(&i32, &s) }
+pub fn WEXITSTATUS(s: i32) -> i32 { signed((unsigned(s) & 0xff00) >> 8) }
+pub fn WTERMSIG(s: i32) -> i32 { signed(unsigned(s) & 0x7f) }
+pub fn WSTOPSIG(s: i32) -> i32 { WEXITSTATUS(s) }
+pub fn WIFEXITED(s: i32) -> bool { WTERMSIG(s) == 0 }
+pub fn WIFSTOPPED(s: i32) -> bool { (u16)(((unsigned(s)&0xffff)*%0x10001)>>8) > 0x7f00 }
+pub fn WIFSIGNALED(s: i32) -> bool { (unsigned(s)&0xffff)-%1 < 0xff }
+
/// Get the errno from a syscall return value, or 0 for no error.
pub fn getErrno(r: usize) -> usize {
const signed_r = *@ptrcast(&isize, &r);
if (signed_r > -4096 and signed_r < 0) usize(-signed_r) else 0
}
+pub fn dup2(old: i32, new: i32) -> usize {
+ arch.syscall2(arch.SYS_dup2, usize(old), usize(new))
+}
+
+pub fn execve_c(path: &const u8, argv: &const ?&const u8, envp: &const ?&const u8) -> usize {
+ arch.syscall3(arch.SYS_execve, path, argv, envp)
+}
+
+/// This function must allocate memory to add a null terminating bytes on path, each arg,
+/// and each environment variable line, as well as a null pointer after the arg list and
+/// environment variable list. We allocate stack memory since the process is about to get
+/// wiped anyway.
+pub fn execve(path: []const u8, argv: []const []const u8, envp: []const []const u8) -> usize {
+ const path_buf = @alloca(u8, path.len + 1);
+ @memcpy(&path_buf[0], &path[0], path.len);
+ path_buf[path.len] = 0;
+
+ const argv_buf = @alloca([]const ?&const u8, argv.len + 1);
+ for (argv) |arg, i| {
+ const arg_buf = @alloca(u8, arg.len + 1);
+ @memcpy(&arg_buf[0], &arg[0], arg.len);
+ arg_buf[arg.len] = 0;
+
+ argv[i] = arg_buf;
+ }
+ argv_buf[argv.len] = null;
+
+ const envp_buf = @alloca([]const ?&const u8, envp.len + 1);
+ for (envp) |env, i| {
+ const env_buf = @alloca(u8, env.len + 1);
+ @memcpy(&env_buf[0], &env[0], env.len);
+ env_buf[env.len] = 0;
+
+ envp[i] = env_buf;
+ }
+ envp_buf[envp.len] = null;
+
+ return execve_c(path_buf.ptr, argv_buf.ptr, envp_buf.ptr);
+}
+
+pub fn fork() -> usize {
+ arch.syscall0(arch.SYS_fork)
+}
+
pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32, offset: usize)
-> usize
{
@@ -261,6 +319,14 @@ pub fn pread(fd: i32, buf: &u8, count: usize, offset: usize) -> usize {
arch.syscall4(arch.SYS_pread, usize(fd), usize(buf), count, offset)
}
+pub fn pipe(fd: &[2]i32) -> usize {
+ pipe2(fd, 0)
+}
+
+pub fn pipe2(fd: &[2]i32, flags: usize) -> usize {
+ arch.syscall2(arch.SYS_pipe2, usize(fd), flags)
+}
+
pub fn write(fd: i32, buf: &const u8, count: usize) -> usize {
arch.syscall3(arch.SYS_write, usize(fd), usize(buf), count)
}
@@ -319,8 +385,12 @@ pub fn getrandom(buf: &u8, count: usize, flags: u32) -> usize {
arch.syscall3(arch.SYS_getrandom, usize(buf), count, usize(flags))
}
-pub fn kill(pid: i32, sig: i32) -> i32 {
- i32(arch.syscall2(arch.SYS_kill, usize(pid), usize(sig)))
+pub fn kill(pid: i32, sig: i32) -> usize {
+ arch.syscall2(arch.SYS_kill, usize(pid), usize(sig))
+}
+
+pub fn waitpid(pid: i32, status: &i32, options: i32) -> usize {
+ arch.syscall4(arch.SYS_wait4, usize(pid), usize(status), usize(options), 0)
}
const NSIG = 65;