diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2019-10-16 15:10:38 -0400 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2019-10-16 15:24:48 -0400 |
| commit | 8cf3a4d586b675a239c9cfa1ea07fa9f59ebf0a4 (patch) | |
| tree | 8237d284aec8fc023b6fb17b058a5eae992e64ad /lib/std/os.zig | |
| parent | 10f6176f3dea426d97b19e7d890947b5e07346af (diff) | |
| download | zig-8cf3a4d586b675a239c9cfa1ea07fa9f59ebf0a4.tar.gz zig-8cf3a4d586b675a239c9cfa1ea07fa9f59ebf0a4.zip | |
[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
Diffstat (limited to 'lib/std/os.zig')
| -rw-r--r-- | lib/std/os.zig | 148 |
1 files changed, 77 insertions, 71 deletions
diff --git a/lib/std/os.zig b/lib/std/os.zig index 13cb4bf587..de01da2fa5 100644 --- a/lib/std/os.zig +++ b/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 |
