diff options
Diffstat (limited to 'src/shell.zig')
| -rw-r--r-- | src/shell.zig | 271 |
1 files changed, 182 insertions, 89 deletions
diff --git a/src/shell.zig b/src/shell.zig index d361b09..f933777 100644 --- a/src/shell.zig +++ b/src/shell.zig @@ -4,86 +4,43 @@ const assert = std.debug.assert; const cli = @import("cli.zig"); const fs = @import("fs.zig"); const conf = @import("conf.zig"); +const time = @import("time.zig"); const Env = @import("env.zig"); const fatal = @import("cli.zig").fatal; pub const Exec_Args = struct { - env: Env, + env: *const Env, argv: []const []const u8, - cwd: ?fs.Path = null, + cwd: ?*const fs.Path = null, }; pub const Exec_Result = union(enum) { - success: []const u8, - failure: []const u8, + success: ?[]const u8, + failure: ?[]const u8, }; pub fn exec(args: Exec_Args) Exec_Result { - assert(args.argv.len > 0); - - const res = std.process.Child.run(.{ - .allocator = args.env.allocator, - .argv = args.argv, - .cwd = if (args.cwd) |cwd| cwd.name() else null, - .cwd_dir = if (args.cwd == null) std.fs.cwd() else null, - }) catch |err| { - fatal("error executing {s}: {s}", .{ args.argv[0], @errorName(err) }); - }; - switch (res.term) { - .Exited => |code| { - if (code == 0) { - args.env.dealloc(res.stderr); - return .{ .success = res.stdout }; - } else if (res.stderr.len > 0) { - args.env.dealloc(res.stdout); - return .{ .failure = res.stderr }; - } else { - args.env.dealloc(res.stderr); - return .{ .failure = res.stdout }; - } - }, - .Signal => fatal("signal received executing {s}", .{args.argv[0]}), - .Stopped => fatal("stopped executing {s}", .{args.argv[0]}), - .Unknown => fatal("unknown error executing {s}", .{args.argv[0]}), - } + return spawn_proc(args, true); } pub fn run(args: Exec_Args) void { - assert(args.argv.len > 0); - - var child = std.process.Child.init(args.argv, args.env.allocator); - if (args.cwd) |cwd| child.cwd = cwd.name(); - child.cwd_dir = if (args.cwd == null) std.fs.cwd() else null; - child.spawn() catch |err| { - fatal("error executing {s}: {s}", .{ args.argv[0], @errorName(err) }); - }; - errdefer _ = child.kill() catch {}; - - const term = child.wait() catch |err| { - fatal("error waiting for {s}: {s}", .{ args.argv[0], @errorName(err) }); - }; - switch (term) { - .Exited => |code| if (code != 0) std.process.exit(code), - .Signal => fatal("signal received executing {s}", .{args.argv[0]}), - .Stopped => fatal("stopped executing {s}", .{args.argv[0]}), - .Unknown => fatal("unknown error executing {s}", .{args.argv[0]}), - } + _ = spawn_proc(args, false); } -pub fn edit_config_file(env: Env, file: fs.Path, comptime verify: fn (env: Env, vars: conf.Config) bool) !void { +pub fn edit_config_file(env: *const Env, file: *const fs.Path, comptime verify: fn (env: *const Env, vars: conf.Config) bool) !void { const tmp_file = tmp_edit_file(env, file); - fs.cp_file(file, tmp_file) catch |err| { + fs.cp_file(file, &tmp_file) catch |err| { switch (err) { - error.FileNotFound => try fs.touch(tmp_file), + error.FileNotFound => try fs.touch(&tmp_file), else => return err, } }; edit: while (true) { const vars: ?conf.Config = parse: while (true) { - try exec_editor(env, tmp_file); - const parsed = try conf.Config.from_file(env.allocator, file.name()); + try exec_editor(env, &tmp_file); + const parsed = try conf.Config.from_file(env.allocator, file); switch (parsed) { .conf => |c| break :parse c, .err => |e| { @@ -99,7 +56,7 @@ pub fn edit_config_file(env: Env, file: fs.Path, comptime verify: fn (env: Env, if (vars) |v| { defer v.deinit(); if (verify(env, v)) { - try fs.mv_file(tmp_file, file); + try fs.mv_file(&tmp_file, file); return; } switch (ask_edit_again()) { @@ -110,12 +67,12 @@ pub fn edit_config_file(env: Env, file: fs.Path, comptime verify: fn (env: Env, break :edit; } } - try fs.rm(tmp_file); + try fs.rm(&tmp_file); } -pub fn edit_config_file_noverify(env: Env, file: fs.Path) !void { +pub fn edit_config_file_noverify(env: *const Env, file: *const fs.Path) !void { try edit_config_file(env, file, struct { - fn noverify(_: Env, _: conf.Config) bool { + fn noverify(_: *const Env, _: conf.Config) bool { return true; } }.noverify); @@ -137,49 +94,45 @@ fn ask_edit_again() enum { again, cancel } { } } -fn exec_editor(env: Env, file: fs.Path) !void { - const pid = std.posix.fork() catch |err| { - fatal("cannot fork editor: {s}", .{@errorName(err)}); - }; +fn exec_editor(env: *const Env, file: *const fs.Path) !void { + var configured_editor_buf: [fs.max_path_bytes]u8 = undefined; + var configured_editor: ?[:0]u8 = null; + if (env.editor_cmd) |cmd| { + @memcpy(configured_editor_buf[0..cmd.len], cmd); + configured_editor_buf[cmd.len] = 0; + configured_editor = configured_editor_buf[0..cmd.len :0]; + } - if (pid == 0) { - // editor process - var editor_env: [512:null]?[*:0]const u8 = undefined; - var editor_envlen: usize = 0; - const environ = if (std.os.environ.len < 512) std.os.environ else std.os.environ[0..511]; - for (environ) |e| { - editor_env[editor_envlen] = e; - editor_envlen += 1; - } - editor_env[editor_envlen] = null; + const editor = configured_editor orelse + env.get("VISUAL") orelse + env.get("EDITOR") orelse + "vi"; - var configured_editor_buf: [std.fs.max_path_bytes]u8 = undefined; - var configured_editor: ?[:0]u8 = null; - if (env.editor_cmd) |cmd| { - @memcpy(configured_editor_buf[0..cmd.len], cmd); - configured_editor_buf[cmd.len] = 0; - configured_editor = configured_editor_buf[0..cmd.len :0]; + const args: Exec_Args = .{ + .env = env, + .argv = &.{ editor, file.name() }, + }; + const res = spawn_proc(args, false); + if (res.failure) |failure| { + if (failure.len == 0) { + fatal("failed to open editor", .{}); + } else { + fatal("{s}", .{failure}); } - - const editor = configured_editor orelse std.posix.getenv("VISUAL") orelse std.posix.getenv("EDITOR") orelse "vi"; - const editor_argv: [*:null]const ?[*:0]const u8 = &.{ editor, file.name(), null }; - std.posix.execvpeZ(editor_argv[0].?, editor_argv, &editor_env) catch unreachable; - std.process.exit(1); } - - _ = std.posix.waitpid(pid, 0); } /// Derive the filename for the temporary file of `file`. /// The filename is "{relname}.{timestamp}", where `relname` is the relative /// part of file regarding to etc. -fn tmp_edit_file(env: Env, file: fs.Path) fs.Path { - const relname = file.relname(env.etc_path(.{})).?; +fn tmp_edit_file(env: *const Env, file: *const fs.Path) fs.Path { + const etc = env.etc_path(.{}); + const relname = file.relname(&etc).?; var tmp_filename_buf: [std.fs.max_name_bytes]u8 = undefined; var tmp_filename_len: usize = 0; - tmp_filename_len += std.fmt.printInt(tmp_filename_buf[tmp_filename_len..], std.time.timestamp(), 16, .lower, .{}); + tmp_filename_len += std.fmt.printInt(tmp_filename_buf[tmp_filename_len..], time.now(), 16, .lower, .{}); tmp_filename_buf[tmp_filename_len] = '.'; tmp_filename_len += 1; for (relname) |c| { @@ -189,3 +142,143 @@ fn tmp_edit_file(env: Env, file: fs.Path) fs.Path { return env.work_path(.{ "edit", tmp_filename_buf[0..tmp_filename_len] }); } + +fn spawn_proc(args: Exec_Args, need_stdout: bool) Exec_Result { + assert(args.argv.len > 0); + + var stdout_pipe: [2]std.posix.fd_t = undefined; + if (need_stdout) { + if (std.posix.system.pipe(&stdout_pipe) != 0) { + fatal("error executing {s}: unable to create pipe", .{args.argv[0]}); + } + } + + const pid = std.posix.system.fork(); + switch (pid) { + -1 => { + if (need_stdout) { + _ = std.posix.system.close(stdout_pipe[0]); + _ = std.posix.system.close(stdout_pipe[1]); + } + fatal("error executing {s}: unable to fork", .{args.argv[0]}); + }, + 0 => { + // child process + if (need_stdout) { + if (std.posix.system.dup2(stdout_pipe[1], std.posix.STDOUT_FILENO) == -1) unreachable; + if (std.posix.system.dup2(stdout_pipe[1], std.posix.STDERR_FILENO) == -1) unreachable; + _ = std.posix.system.close(stdout_pipe[0]); + _ = std.posix.system.close(stdout_pipe[1]); + } + + const argv = args.env.allocator.allocSentinel(?[*:0]const u8, args.argv.len, null) catch { + fatal("error executing {s}: out of memory", .{args.argv[0]}); + }; + for (args.argv, 0..) |arg, i| { + argv[i] = args.env.allocator.dupeZ(u8, arg) catch { + fatal("error executing {s}: out of memory", .{args.argv[0]}); + }; + } + + var env: [512:null]?[*:0]const u8 = undefined; + var envlen: usize = 0; + while (std.posix.system.environ[envlen]) |e| { + env[envlen] = e; + envlen += 1; + if (envlen >= env.len - 1) break; + } + env[envlen] = null; + + if (args.cwd) |cwd| { + if (std.posix.system.chdir(cwd.name()) == -1) { + fatal("error executing {s}: chdir failed", .{args.argv[0]}); + } + } + + const sent = std.mem.findSentinel(u8, 0, argv[0].?); + const name = argv[0].?[0..sent :0]; + + var eaccess = false; + if (std.mem.findScalar(u8, name, '/')) |_| { + // We have a relative or absolute path. No need to consult the PATH env. + const res = std.posix.system.execve(name, argv, &env); + switch (std.posix.errno(res)) { + .NOENT => {}, + .ACCES => eaccess = true, + else => |err| fatal("error executing {s}: {}", .{ name, err }), + } + } else { + var path_env = args.env.get("PATH") orelse ""; + var path: fs.Path = undefined; + while (path_env.len > 0) { + const colon = std.mem.findScalar(u8, path_env, ':') orelse path_env.len; + path = .join(.{ if (colon == 0) "." else path_env[0..colon], name }); + path_env = if (colon == path_env.len) "" else path_env[colon + 1 ..]; + + const res = std.posix.system.execve(path.name(), argv, &env); + switch (std.posix.errno(res)) { + .NOENT => {}, + .ACCES => eaccess = true, + else => |err| fatal("error executing {s}: {}", .{ name, err }), + } + } + } + + if (eaccess) { + fatal("permission denied: {s}", .{name}); + } else { + fatal("not found: {s}", .{name}); + } + }, + else => { + // parent process + var stdout_buf: []u8 = &[0]u8{}; + var stdout_len: usize = 0; + defer if (stdout_buf.len > 0) args.env.dealloc(stdout_buf); + if (need_stdout) { + _ = std.posix.system.close(stdout_pipe[1]); + defer _ = std.posix.system.close(stdout_pipe[0]); + + while (true) { + if (stdout_len >= stdout_buf.len) { + const newbuf = args.env.alloc(stdout_buf.len + 1024); + @memcpy(newbuf[0..stdout_len], stdout_buf[0..stdout_len]); + args.env.dealloc(stdout_buf); + stdout_buf = newbuf; + } + + const res = std.posix.system.read(stdout_pipe[0], stdout_buf.ptr + stdout_len, stdout_buf.len - stdout_len); + if (res < 0) { + if (std.posix.errno(res) == .INTR) continue; + fatal("error executing {s}: cannot read stdout", .{args.argv[0]}); + } + if (res == 0) break; + const n: usize = @intCast(res); + stdout_len += n; + } + } + + var status: c_int = undefined; + while (true) { + const res = std.posix.system.waitpid(pid, &status, 0); + if (res < 0) { + if (std.posix.errno(res) == std.posix.E.INTR) continue; + fatal("error executing {s}: failed waiting for process", .{args.argv[0]}); + } + + const s: u32 = @intCast(status); + if (std.posix.W.IFEXITED(s)) { + const code = std.posix.W.EXITSTATUS(s); + const output: ?[]const u8 = if (stdout_len > 0) args.env.dupe(stdout_buf[0..stdout_len]) else null; + return if (code == 0) .{ .success = output } else .{ .failure = output }; + } else if (std.posix.W.IFSIGNALED(s)) { + fatal("signal received executing {s}", .{args.argv[0]}); + } else if (std.posix.W.IFSTOPPED(s)) { + unreachable; + } else { + fatal("unknown error executing {s}", .{args.argv[0]}); + } + } + }, + } +} |