hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit dca536b40b983284dc6febf98b526bd6274d37b3
parent 6f18f08a09d74f724fb0ffafc4a4618d44933f5b
Author: Drew DeVault <sir@cmpwn.com>
Date:   Tue,  2 Jan 2024 13:26:13 +0100

os::exec: expose [[lookup]] to public API

Signed-off-by: Drew DeVault <sir@cmpwn.com>

Diffstat:
Mos/exec/cmd.ha | 73++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 68 insertions(+), 5 deletions(-)

diff --git a/os/exec/cmd.ha b/os/exec/cmd.ha @@ -5,6 +5,7 @@ use ascii; use errors; use io; use os; +use path; use strings; // Prepares a [[command]] based on its name and a list of arguments. The argument @@ -29,7 +30,7 @@ export fn cmd(name: str, args: str...) (command | error) = { return nocmd; }; } else { - yield match (lookup(name)?) { + yield match (lookup_open(name)?) { case void => return nocmd; case let p: platform_cmd => @@ -163,13 +164,30 @@ export fn chdir(cmd: *command, dir: str) void = { cmd.dir = dir; }; -fn lookup(name: str) (platform_cmd | void | error) = { +// Similar to [[lookup]] but TOCTOU-proof +fn lookup_open(name: str) (platform_cmd | void | error) = { + static let buf = path::buffer { ... }; + path::set(&buf)!; + + // Try to open file directly + if (strings::contains(name, "/")) { + match (open(name)) { + case (errors::noaccess | errors::noentry) => + yield; + case let err: error => + return err; + case let p: platform_cmd => + return p; + }; + }; + const path = match (os::getenv("PATH")) { case void => return; case let s: str => yield s; }; + let tok = strings::tokenize(path, ":"); for (true) { const item = match (strings::next_token(&tok)) { @@ -178,9 +196,8 @@ fn lookup(name: str) (platform_cmd | void | error) = { case let s: str => yield s; }; - let path = strings::concat(item, "/", name); - defer free(path); - match (open(path)) { + path::set(&buf, item, name)!; + match (open(path::string(&buf))) { case (errors::noaccess | errors::noentry) => continue; case let err: error => @@ -190,3 +207,49 @@ fn lookup(name: str) (platform_cmd | void | error) = { }; }; }; + +// Looks up an executable by name in the system PATH. The return value is +// statically allocated. +// +// The use of this function is lightly discouraged if [[cmd]] is suitable; +// otherwise you may have a TOCTOU issue. +export fn lookup(name: str) (str | void) = { + static let buf = path::buffer { ... }; + path::set(&buf)!; + + // Try to open file directly + if (strings::contains(name, "/")) { + match (os::access(name, os::amode::X_OK)) { + case let exec: bool => + if (exec) { + return name; + }; + case => void; // Keep looking + }; + }; + + const path = match (os::getenv("PATH")) { + case void => + return; + case let s: str => + yield s; + }; + + let tok = strings::tokenize(path, ":"); + for (true) { + const item = match (strings::next_token(&tok)) { + case void => + break; + case let s: str => + yield s; + }; + path::set(&buf, item, name)!; + match (os::access(path::string(&buf), os::amode::X_OK)) { + case let exec: bool => + if (exec) { + return path::string(&buf); + }; + case => void; // Keep looking + }; + }; +};