commit 7e657ecd8c171350c5b440c9cf8c9a56116328e3
parent 9c2a84de884dd95c13081b775897edd86cd13851
Author: Drew DeVault <sir@cmpwn.com>
Date: Mon, 22 Nov 2021 10:00:04 +0100
os::exec: improve fd manipulation
Signed-off-by: Drew DeVault <sir@cmpwn.com>
Diffstat:
4 files changed, 75 insertions(+), 20 deletions(-)
diff --git a/cmd/hare/release.ha b/cmd/hare/release.ha
@@ -37,8 +37,7 @@ const changelog_template: str = "# This is the changelog for your release. It ha
{} version {}
";
-const initial_template: str = "
-# These are the release notes for the initial release of {0}.
+const initial_template: str = "# These are the release notes for the initial release of {0}.
#
# Any lines which begin with \"#\", like this one, are for your information
# only, and will be removed from the final release notes. Edit this file to your
@@ -275,7 +274,6 @@ fn choosekey() (str | release_error) = {
};
fn signtag(name: str, tag: str, key: str) (void | release_error) = {
- fmt::printfln("Using SSH key {} for release signature.", key)!;
const prefix = fmt::asprintf("--prefix={}-{}/", name, tag);
defer free(prefix);
const archive = exec::cmd("git", "archive",
@@ -285,6 +283,12 @@ fn signtag(name: str, tag: str, key: str) (void | release_error) = {
const note = exec::cmd("git", "notes", "add", "-F", "-", tag)?;
exec::setenv(¬e, "GIT_NOTES_REF", "refs/notes/signatures/tar.gz");
+ // Squelch "Signing data on standard input"
+ // TODO: It might be better to capture this and print it to stderr
+ // ourselves if ssh-keygen exits nonzero, so that the error details are
+ // available to the user for diagnosis.
+ exec::addfile(&ssh, exec::nullfd, os::stderr);
+
const pipe1 = unix::pipe()?;
const pipe2 = unix::pipe()?;
exec::addfile(&archive, pipe1.1, os::stdout_file);
@@ -305,6 +309,7 @@ fn signtag(name: str, tag: str, key: str) (void | release_error) = {
fn git_runcmd(args: str...) (void | release_error) = {
const cmd = exec::cmd("git", args...)?;
+ exec::addfile(&cmd, exec::nullfd, os::stderr);
const proc = exec::start(&cmd)?;
const status = exec::wait(&proc)?;
return exec::check(&status)?;
@@ -315,6 +320,7 @@ fn git_readcmd(args: str...) (str | release_error) = {
defer io::close(pipe.0);
const cmd = exec::cmd("git", args...)?;
exec::addfile(&cmd, pipe.1, os::stdout_file);
+ exec::addfile(&cmd, exec::nullfd, os::stderr);
const proc = exec::start(&cmd)?;
io::close(pipe.1);
const result = io::drain(pipe.0)?;
diff --git a/os/exec/cmd.ha b/os/exec/cmd.ha
@@ -116,26 +116,44 @@ export fn setenv(cmd: *command, key: str, value: str) void = {
append(cmd.env, strings::concat(fullkey, value));
};
-// Adds an [[io::file]] to the child process's file table. All mappings are
-// performed atomically, such that the following code swaps stdout and stderr:
+// Configures a file in the child process's file table, such that 'from' is
+// mapped onto file descriptor slot 'to' via one of the dup(2) family of
+// syscalls. This is done atomically, such that the following code swaps stdout
+// and stderr:
//
// exec::addfile(&cmd, os::stdout_file, os::stderr);
// exec::addfile(&cmd, os::stderr, os::stdout_file);
//
-// If the same [[io::file]] is mapped to multiple times, only the last mapping
-// will take effect.
-export fn addfile(cmd: *command, old: io::file, new: io::file) void = {
- // TODO: Can we make old be an io::handle?
- append(cmd.files, (old, new));
+// Pass [[os::exec::nullfd]] in the 'to' argument to map the child's file
+// descriptor to /dev/null or the appropriate platform-specific equivalent.
+//
+// Pass [[os::exec::closefd]] in the 'to' argument to close a file descriptor
+// which was not opened with the CLOEXEC flag. Note that Hare opens all files
+// with CLOEXEC by default, so this is not usually necessary.
+export fn addfile(
+ cmd: *command,
+ from: (io::file | nullfd | closefd),
+ to: io::file,
+) void = {
+ // TODO: Can we make 'to' be an io::handle?
+ append(cmd.files, (from, to));
+};
+
+// Closes all standard files (stdin, stdout, and stderr) in the child process.
+// Many programs do not work well under these conditions; you may want
+// [[nullstd]] instead.
+export fn closestd(cmd: *command) void = {
+ addfile(cmd, closefd, os::stdin_file);
+ addfile(cmd, closefd, os::stdout_file);
+ addfile(cmd, closefd, os::stderr);
};
-// Adds [[os::stdin]], [[os::stdout]], and [[os::stderr]] from the parent
-// process (i.e. the one which calls this function) into the child process's
-// file table.
-export fn addstd(cmd: *command) void = {
- addfile(cmd, os::stdin_file, os::stdin_file);
- addfile(cmd, os::stdout_file, os::stdout_file);
- addfile(cmd, os::stderr, os::stderr);
+// Redirects all standard files (stdin, stdout, and stderr) to /dev/null or the
+// platform-specific equivalent.
+export fn nullstd(cmd: *command) void = {
+ addfile(cmd, nullfd, os::stdin_file);
+ addfile(cmd, nullfd, os::stdout_file);
+ addfile(cmd, nullfd, os::stderr);
};
fn lookup(name: str) (platform_cmd | void) = {
diff --git a/os/exec/exec+linux.ha b/os/exec/exec+linux.ha
@@ -57,8 +57,19 @@ fn platform_exec(cmd: *command) error = {
envp = env: *[*]nullable *const char;
};
+ let need_devnull = false;
for (let i = 0z; i < len(cmd.files); i += 1) {
- cmd.files[i].0 = match (rt::fcntl(cmd.files[i].0, rt::F_DUPFD_CLOEXEC, 0)) {
+ const from = match (cmd.files[i].0) {
+ case file: io::file =>
+ yield file;
+ case nullfd =>
+ need_devnull = true;
+ continue;
+ case closefd =>
+ continue;
+ };
+
+ cmd.files[i].0 = match (rt::fcntl(from, rt::F_DUPFD_CLOEXEC, 0)) {
case fd: int =>
yield fd;
case err: rt::errno =>
@@ -66,8 +77,22 @@ fn platform_exec(cmd: *command) error = {
};
};
+ const devnull: io::file = if (need_devnull) {
+ yield os::open("/dev/null")!;
+ } else -1;
+
for (let i = 0z; i < len(cmd.files); i += 1) {
- match (rt::dup2(cmd.files[i].0, cmd.files[i].1)) {
+ const from = match (cmd.files[i].0) {
+ case file: io::file =>
+ yield file;
+ case nullfd =>
+ yield devnull;
+ case closefd =>
+ io::close(cmd.files[i].1);
+ continue;
+ };
+
+ match (rt::dup2(from, cmd.files[i].1)) {
case int => void;
case e: rt::errno =>
return errors::errno(e);
diff --git a/os/exec/types.ha b/os/exec/types.ha
@@ -1,12 +1,18 @@
use errors;
use io;
+// Represents a "null" file descriptor, e.g. /dev/null.
+export type nullfd = void;
+
+// Used to close a file descriptor which does not have the CLOEXEC flag set.
+export type closefd = void;
+
// An executable command.
export type command = struct {
platform: platform_cmd,
argv: []str,
env: []str,
- files: [](io::file, io::file),
+ files: []((io::file | nullfd | closefd), io::file),
};
// Returned when path resolution fails to find a command by its name.