aboutsummaryrefslogtreecommitdiff
path: root/src/shell.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/shell.zig')
-rw-r--r--src/shell.zig271
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]});
+ }
+ }
+ },
+ }
+}