authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-10-16 15:10:38-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-10-16 15:24:48-04:00
log8cf3a4d586b675a239c9cfa1ea07fa9f59ebf0a4
tree8237d284aec8fc023b6fb17b058a5eae992e64ad
parent10f6176f3dea426d97b19e7d890947b5e07346af
signaturelock-open Commit is signed but in an unrecognized format.

[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

3 files changed, 79 insertions(+), 73 deletions(-)

lib/std/child_process.zig+2-1
...@@ -365,7 +365,8 @@ pub const ChildProcess = struct {...@@ -365,7 +365,8 @@ pub const ChildProcess = struct {
365 os.setreuid(uid, uid) catch |err| forkChildErrReport(err_pipe[1], err);365 os.setreuid(uid, uid) catch |err| forkChildErrReport(err_pipe[1], err);
366 }366 }
367367
368 os.execve(self.allocator, self.argv, env_map) catch |err| forkChildErrReport(err_pipe[1], err);368 const err = os.execvpe(self.allocator, self.argv, env_map);
369 forkChildErrReport(err_pipe[1], err);
369 }370 }
370371
371 // we are the parent372 // we are the parent
lib/std/fs.zig-1
...@@ -27,7 +27,6 @@ pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirE...@@ -27,7 +27,6 @@ pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirE
27/// This represents the maximum size of a UTF-8 encoded file path.27/// This represents the maximum size of a UTF-8 encoded file path.
28/// All file system operations which return a path are guaranteed to28/// All file system operations which return a path are guaranteed to
29/// fit into a UTF-8 encoded array of this length.29/// fit into a UTF-8 encoded array of this length.
30/// path being too long if it is this 0long
31pub const MAX_PATH_BYTES = switch (builtin.os) {30pub const MAX_PATH_BYTES = switch (builtin.os) {
32 .linux, .macosx, .ios, .freebsd, .netbsd => os.PATH_MAX,31 .linux, .macosx, .ios, .freebsd, .netbsd => os.PATH_MAX,
33 // Each UTF-16LE character may be expanded to 3 UTF-8 bytes.32 // Each UTF-16LE character may be expanded to 3 UTF-8 bytes.
lib/std/os.zig+77-71
...@@ -642,13 +642,86 @@ pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {...@@ -642,13 +642,86 @@ pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {
642 }642 }
643}643}
644644
645pub const ExecveError = error{
646 SystemResources,
647 AccessDenied,
648 InvalidExe,
649 FileSystem,
650 IsDir,
651 FileNotFound,
652 NotDir,
653 FileBusy,
654 ProcessFdQuotaExceeded,
655 SystemFdQuotaExceeded,
656 NameTooLong,
657} || UnexpectedError;
658
659/// Like `execve` except the parameters are null-terminated,
660/// matching the syscall API on all targets. This removes the need for an allocator.
661/// This function ignores PATH environment variable. See `execvpeC` for that.
662pub fn execveC(path: [*]const u8, child_argv: [*]const ?[*]const u8, envp: [*]const ?[*]const u8) ExecveError {
663 switch (errno(system.execve(path, child_argv, envp))) {
664 0 => unreachable,
665 EFAULT => unreachable,
666 E2BIG => return error.SystemResources,
667 EMFILE => return error.ProcessFdQuotaExceeded,
668 ENAMETOOLONG => return error.NameTooLong,
669 ENFILE => return error.SystemFdQuotaExceeded,
670 ENOMEM => return error.SystemResources,
671 EACCES => return error.AccessDenied,
672 EPERM => return error.AccessDenied,
673 EINVAL => return error.InvalidExe,
674 ENOEXEC => return error.InvalidExe,
675 EIO => return error.FileSystem,
676 ELOOP => return error.FileSystem,
677 EISDIR => return error.IsDir,
678 ENOENT => return error.FileNotFound,
679 ENOTDIR => return error.NotDir,
680 ETXTBSY => return error.FileBusy,
681 else => |err| return unexpectedErrno(err),
682 }
683}
684
685/// Like `execvpe` except the parameters are null-terminated,
686/// matching the syscall API on all targets. This removes the need for an allocator.
687/// This function also uses the PATH environment variable to get the full path to the executable.
688/// If `file` is an absolute path, this is the same as `execveC`.
689pub fn execvpeC(file: [*]const u8, child_argv: [*]const ?[*]const u8, envp: [*]const ?[*]const u8) ExecveError {
690 const file_slice = mem.toSliceConst(u8, file);
691 if (mem.indexOfScalar(u8, file_slice, '/') != null) return execveC(file, child_argv, envp);
692
693 const PATH = getenv("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
694 var path_buf: [MAX_PATH_BYTES]u8 = undefined;
695 var it = mem.tokenize(PATH, ":");
696 var seen_eacces = false;
697 var err: ExecveError = undefined;
698 while (it.next()) |search_path| {
699 if (path_buf.len < search_path.len + file_slice.len + 1) return error.NameTooLong;
700 mem.copy(u8, &path_buf, search_path);
701 path_buf[search_path.len] = '/';
702 mem.copy(u8, path_buf[search_path.len + 1 ..], file_slice);
703 path_buf[search_path.len + file_slice.len + 1] = 0;
704 err = execveC(&path_buf, child_argv, envp);
705 switch (err) {
706 error.AccessDenied => seen_eacces = true,
707 error.FileNotFound, error.NotDir => {},
708 else => |e| return e,
709 }
710 }
711 if (seen_eacces) return error.AccessDenied;
712 return err;
713}
714
645/// This function must allocate memory to add a null terminating bytes on path and each arg.715/// This function must allocate memory to add a null terminating bytes on path and each arg.
646/// It must also convert to KEY=VALUE\0 format for environment variables, and include null716/// It must also convert to KEY=VALUE\0 format for environment variables, and include null
647/// pointers after the args and after the environment variables.717/// pointers after the args and after the environment variables.
648/// `argv[0]` is the executable path.718/// `argv_slice[0]` is the executable path.
649/// This function also uses the PATH environment variable to get the full path to the executable.719/// This function also uses the PATH environment variable to get the full path to the executable.
650/// TODO provide execveC which does not take an allocator720pub fn execvpe(
651pub fn execve(allocator: *mem.Allocator, argv_slice: []const []const u8, env_map: *const std.BufMap) !void {721 allocator: *mem.Allocator,
722 argv_slice: []const []const u8,
723 env_map: *const std.BufMap,
724) (ExecveError || error{OutOfMemory}) {
652 const argv_buf = try allocator.alloc(?[*]u8, argv_slice.len + 1);725 const argv_buf = try allocator.alloc(?[*]u8, argv_slice.len + 1);
653 mem.set(?[*]u8, argv_buf, null);726 mem.set(?[*]u8, argv_buf, null);
654 defer {727 defer {
...@@ -670,37 +743,7 @@ pub fn execve(allocator: *mem.Allocator, argv_slice: []const []const u8, env_map...@@ -670,37 +743,7 @@ pub fn execve(allocator: *mem.Allocator, argv_slice: []const []const u8, env_map
670 const envp_buf = try createNullDelimitedEnvMap(allocator, env_map);743 const envp_buf = try createNullDelimitedEnvMap(allocator, env_map);
671 defer freeNullDelimitedEnvMap(allocator, envp_buf);744 defer freeNullDelimitedEnvMap(allocator, envp_buf);
672745
673 const exe_path = argv_slice[0];746 return execvpeC(argv_buf.ptr[0].?, argv_buf.ptr, envp_buf.ptr);
674 if (mem.indexOfScalar(u8, exe_path, '/') != null) {
675 return execveErrnoToErr(errno(system.execve(argv_buf[0].?, argv_buf.ptr, envp_buf.ptr)));
676 }
677
678 const PATH = getenv("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
679 // PATH.len because it is >= the largest search_path
680 // +1 for the / to join the search path and exe_path
681 // +1 for the null terminating byte
682 const path_buf = try allocator.alloc(u8, PATH.len + exe_path.len + 2);
683 defer allocator.free(path_buf);
684 var it = mem.tokenize(PATH, ":");
685 var seen_eacces = false;
686 var err: usize = undefined;
687 while (it.next()) |search_path| {
688 mem.copy(u8, path_buf, search_path);
689 path_buf[search_path.len] = '/';
690 mem.copy(u8, path_buf[search_path.len + 1 ..], exe_path);
691 path_buf[search_path.len + exe_path.len + 1] = 0;
692 err = errno(system.execve(path_buf.ptr, argv_buf.ptr, envp_buf.ptr));
693 assert(err > 0);
694 if (err == EACCES) {
695 seen_eacces = true;
696 } else if (err != ENOENT) {
697 return execveErrnoToErr(err);
698 }
699 }
700 if (seen_eacces) {
701 err = EACCES;
702 }
703 return execveErrnoToErr(err);
704}747}
705748
706pub fn createNullDelimitedEnvMap(allocator: *mem.Allocator, env_map: *const std.BufMap) ![]?[*]u8 {749pub 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...@@ -734,43 +777,6 @@ pub fn freeNullDelimitedEnvMap(allocator: *mem.Allocator, envp_buf: []?[*]u8) vo
734 allocator.free(envp_buf);777 allocator.free(envp_buf);
735}778}
736779
737pub const ExecveError = error{
738 SystemResources,
739 AccessDenied,
740 InvalidExe,
741 FileSystem,
742 IsDir,
743 FileNotFound,
744 NotDir,
745 FileBusy,
746 ProcessFdQuotaExceeded,
747 SystemFdQuotaExceeded,
748 NameTooLong,
749} || UnexpectedError;
750
751fn execveErrnoToErr(err: usize) ExecveError {
752 assert(err > 0);
753 switch (err) {
754 EFAULT => unreachable,
755 E2BIG => return error.SystemResources,
756 EMFILE => return error.ProcessFdQuotaExceeded,
757 ENAMETOOLONG => return error.NameTooLong,
758 ENFILE => return error.SystemFdQuotaExceeded,
759 ENOMEM => return error.SystemResources,
760 EACCES => return error.AccessDenied,
761 EPERM => return error.AccessDenied,
762 EINVAL => return error.InvalidExe,
763 ENOEXEC => return error.InvalidExe,
764 EIO => return error.FileSystem,
765 ELOOP => return error.FileSystem,
766 EISDIR => return error.IsDir,
767 ENOENT => return error.FileNotFound,
768 ENOTDIR => return error.NotDir,
769 ETXTBSY => return error.FileBusy,
770 else => return unexpectedErrno(err),
771 }
772}
773
774/// Get an environment variable.780/// Get an environment variable.
775/// See also `getenvC`.781/// See also `getenvC`.
776/// TODO make this go through libc when we have it782/// TODO make this go through libc when we have it