mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 05:44:20 +00:00
[breaking] standardize std.os execve functions
* `std.os.execve` had the wrong name; it should have been `std.os.execvpe`. This is now corrected. * introduce `std.os.execveC` which does not look at PATH, and uses null terminated parameters, matching POSIX ABIs. It does not require an allocator. * fix typo nonsense doc comment in `std.fs.MAX_PATH_BYTES`. * introduce `std.os.execvpeC`, which is like `execvpe` except it uses null terminated parameters, matching POSIX ABIs, and thus does not require an allocator. * `std.os.execvpe` implementation is reworked to only convert parameters and then delegate to `std.os.execvpeC`. * `std.os.execvpeC` improved to handle `ENOTDIR`. See #3415
This commit is contained in:
parent
10f6176f3d
commit
8cf3a4d586
3 changed files with 79 additions and 73 deletions
|
|
@ -365,7 +365,8 @@ pub const ChildProcess = struct {
|
|||
os.setreuid(uid, uid) catch |err| forkChildErrReport(err_pipe[1], err);
|
||||
}
|
||||
|
||||
os.execve(self.allocator, self.argv, env_map) catch |err| forkChildErrReport(err_pipe[1], err);
|
||||
const err = os.execvpe(self.allocator, self.argv, env_map);
|
||||
forkChildErrReport(err_pipe[1], err);
|
||||
}
|
||||
|
||||
// we are the parent
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirE
|
|||
/// This represents the maximum size of a UTF-8 encoded file path.
|
||||
/// All file system operations which return a path are guaranteed to
|
||||
/// fit into a UTF-8 encoded array of this length.
|
||||
/// path being too long if it is this 0long
|
||||
pub const MAX_PATH_BYTES = switch (builtin.os) {
|
||||
.linux, .macosx, .ios, .freebsd, .netbsd => os.PATH_MAX,
|
||||
// Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
|
||||
|
|
|
|||
148
lib/std/os.zig
148
lib/std/os.zig
|
|
@ -642,13 +642,86 @@ pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {
|
|||
}
|
||||
}
|
||||
|
||||
pub const ExecveError = error{
|
||||
SystemResources,
|
||||
AccessDenied,
|
||||
InvalidExe,
|
||||
FileSystem,
|
||||
IsDir,
|
||||
FileNotFound,
|
||||
NotDir,
|
||||
FileBusy,
|
||||
ProcessFdQuotaExceeded,
|
||||
SystemFdQuotaExceeded,
|
||||
NameTooLong,
|
||||
} || UnexpectedError;
|
||||
|
||||
/// Like `execve` except the parameters are null-terminated,
|
||||
/// matching the syscall API on all targets. This removes the need for an allocator.
|
||||
/// This function ignores PATH environment variable. See `execvpeC` for that.
|
||||
pub fn execveC(path: [*]const u8, child_argv: [*]const ?[*]const u8, envp: [*]const ?[*]const u8) ExecveError {
|
||||
switch (errno(system.execve(path, child_argv, envp))) {
|
||||
0 => unreachable,
|
||||
EFAULT => unreachable,
|
||||
E2BIG => return error.SystemResources,
|
||||
EMFILE => return error.ProcessFdQuotaExceeded,
|
||||
ENAMETOOLONG => return error.NameTooLong,
|
||||
ENFILE => return error.SystemFdQuotaExceeded,
|
||||
ENOMEM => return error.SystemResources,
|
||||
EACCES => return error.AccessDenied,
|
||||
EPERM => return error.AccessDenied,
|
||||
EINVAL => return error.InvalidExe,
|
||||
ENOEXEC => return error.InvalidExe,
|
||||
EIO => return error.FileSystem,
|
||||
ELOOP => return error.FileSystem,
|
||||
EISDIR => return error.IsDir,
|
||||
ENOENT => return error.FileNotFound,
|
||||
ENOTDIR => return error.NotDir,
|
||||
ETXTBSY => return error.FileBusy,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `execvpe` except the parameters are null-terminated,
|
||||
/// matching the syscall API on all targets. This removes the need for an allocator.
|
||||
/// This function also uses the PATH environment variable to get the full path to the executable.
|
||||
/// If `file` is an absolute path, this is the same as `execveC`.
|
||||
pub fn execvpeC(file: [*]const u8, child_argv: [*]const ?[*]const u8, envp: [*]const ?[*]const u8) ExecveError {
|
||||
const file_slice = mem.toSliceConst(u8, file);
|
||||
if (mem.indexOfScalar(u8, file_slice, '/') != null) return execveC(file, child_argv, envp);
|
||||
|
||||
const PATH = getenv("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
|
||||
var path_buf: [MAX_PATH_BYTES]u8 = undefined;
|
||||
var it = mem.tokenize(PATH, ":");
|
||||
var seen_eacces = false;
|
||||
var err: ExecveError = undefined;
|
||||
while (it.next()) |search_path| {
|
||||
if (path_buf.len < search_path.len + file_slice.len + 1) return error.NameTooLong;
|
||||
mem.copy(u8, &path_buf, search_path);
|
||||
path_buf[search_path.len] = '/';
|
||||
mem.copy(u8, path_buf[search_path.len + 1 ..], file_slice);
|
||||
path_buf[search_path.len + file_slice.len + 1] = 0;
|
||||
err = execveC(&path_buf, child_argv, envp);
|
||||
switch (err) {
|
||||
error.AccessDenied => seen_eacces = true,
|
||||
error.FileNotFound, error.NotDir => {},
|
||||
else => |e| return e,
|
||||
}
|
||||
}
|
||||
if (seen_eacces) return error.AccessDenied;
|
||||
return err;
|
||||
}
|
||||
|
||||
/// This function must allocate memory to add a null terminating bytes on path and each arg.
|
||||
/// It must also convert to KEY=VALUE\0 format for environment variables, and include null
|
||||
/// pointers after the args and after the environment variables.
|
||||
/// `argv[0]` is the executable path.
|
||||
/// `argv_slice[0]` is the executable path.
|
||||
/// This function also uses the PATH environment variable to get the full path to the executable.
|
||||
/// TODO provide execveC which does not take an allocator
|
||||
pub fn execve(allocator: *mem.Allocator, argv_slice: []const []const u8, env_map: *const std.BufMap) !void {
|
||||
pub fn execvpe(
|
||||
allocator: *mem.Allocator,
|
||||
argv_slice: []const []const u8,
|
||||
env_map: *const std.BufMap,
|
||||
) (ExecveError || error{OutOfMemory}) {
|
||||
const argv_buf = try allocator.alloc(?[*]u8, argv_slice.len + 1);
|
||||
mem.set(?[*]u8, argv_buf, null);
|
||||
defer {
|
||||
|
|
@ -670,37 +743,7 @@ pub fn execve(allocator: *mem.Allocator, argv_slice: []const []const u8, env_map
|
|||
const envp_buf = try createNullDelimitedEnvMap(allocator, env_map);
|
||||
defer freeNullDelimitedEnvMap(allocator, envp_buf);
|
||||
|
||||
const exe_path = argv_slice[0];
|
||||
if (mem.indexOfScalar(u8, exe_path, '/') != null) {
|
||||
return execveErrnoToErr(errno(system.execve(argv_buf[0].?, argv_buf.ptr, envp_buf.ptr)));
|
||||
}
|
||||
|
||||
const PATH = getenv("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
|
||||
// PATH.len because it is >= the largest search_path
|
||||
// +1 for the / to join the search path and exe_path
|
||||
// +1 for the null terminating byte
|
||||
const path_buf = try allocator.alloc(u8, PATH.len + exe_path.len + 2);
|
||||
defer allocator.free(path_buf);
|
||||
var it = mem.tokenize(PATH, ":");
|
||||
var seen_eacces = false;
|
||||
var err: usize = undefined;
|
||||
while (it.next()) |search_path| {
|
||||
mem.copy(u8, path_buf, search_path);
|
||||
path_buf[search_path.len] = '/';
|
||||
mem.copy(u8, path_buf[search_path.len + 1 ..], exe_path);
|
||||
path_buf[search_path.len + exe_path.len + 1] = 0;
|
||||
err = errno(system.execve(path_buf.ptr, argv_buf.ptr, envp_buf.ptr));
|
||||
assert(err > 0);
|
||||
if (err == EACCES) {
|
||||
seen_eacces = true;
|
||||
} else if (err != ENOENT) {
|
||||
return execveErrnoToErr(err);
|
||||
}
|
||||
}
|
||||
if (seen_eacces) {
|
||||
err = EACCES;
|
||||
}
|
||||
return execveErrnoToErr(err);
|
||||
return execvpeC(argv_buf.ptr[0].?, argv_buf.ptr, envp_buf.ptr);
|
||||
}
|
||||
|
||||
pub fn createNullDelimitedEnvMap(allocator: *mem.Allocator, env_map: *const std.BufMap) ![]?[*]u8 {
|
||||
|
|
@ -734,43 +777,6 @@ pub fn freeNullDelimitedEnvMap(allocator: *mem.Allocator, envp_buf: []?[*]u8) vo
|
|||
allocator.free(envp_buf);
|
||||
}
|
||||
|
||||
pub const ExecveError = error{
|
||||
SystemResources,
|
||||
AccessDenied,
|
||||
InvalidExe,
|
||||
FileSystem,
|
||||
IsDir,
|
||||
FileNotFound,
|
||||
NotDir,
|
||||
FileBusy,
|
||||
ProcessFdQuotaExceeded,
|
||||
SystemFdQuotaExceeded,
|
||||
NameTooLong,
|
||||
} || UnexpectedError;
|
||||
|
||||
fn execveErrnoToErr(err: usize) ExecveError {
|
||||
assert(err > 0);
|
||||
switch (err) {
|
||||
EFAULT => unreachable,
|
||||
E2BIG => return error.SystemResources,
|
||||
EMFILE => return error.ProcessFdQuotaExceeded,
|
||||
ENAMETOOLONG => return error.NameTooLong,
|
||||
ENFILE => return error.SystemFdQuotaExceeded,
|
||||
ENOMEM => return error.SystemResources,
|
||||
EACCES => return error.AccessDenied,
|
||||
EPERM => return error.AccessDenied,
|
||||
EINVAL => return error.InvalidExe,
|
||||
ENOEXEC => return error.InvalidExe,
|
||||
EIO => return error.FileSystem,
|
||||
ELOOP => return error.FileSystem,
|
||||
EISDIR => return error.IsDir,
|
||||
ENOENT => return error.FileNotFound,
|
||||
ENOTDIR => return error.NotDir,
|
||||
ETXTBSY => return error.FileBusy,
|
||||
else => return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an environment variable.
|
||||
/// See also `getenvC`.
|
||||
/// TODO make this go through libc when we have it
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue