diff options
Diffstat (limited to 'std')
| -rw-r--r-- | std/build.zig | 38 | ||||
| -rw-r--r-- | std/io.zig | 13 | ||||
| -rw-r--r-- | std/os/darwin.zig | 4 | ||||
| -rw-r--r-- | std/os/index.zig | 343 | ||||
| -rw-r--r-- | std/os/linux.zig | 74 |
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; |
