aboutsummaryrefslogtreecommitdiff
path: root/lib/std/os.zig
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2019-10-16 15:10:38 -0400
committerAndrew Kelley <andrew@ziglang.org>2019-10-16 15:24:48 -0400
commit8cf3a4d586b675a239c9cfa1ea07fa9f59ebf0a4 (patch)
tree8237d284aec8fc023b6fb17b058a5eae992e64ad /lib/std/os.zig
parent10f6176f3dea426d97b19e7d890947b5e07346af (diff)
downloadzig-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.zig148
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