diff --git a/std/mem.zig b/std/mem.zig index fd5ff89d0502..16277386b973 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -136,6 +136,16 @@ pub fn eql(comptime T: type, a: []const T, b: []const T) -> bool { return true; } +/// Linear search for the index of a scalar value inside a slice. +pub fn indexOfScalar(comptime T: type, slice: []const T, value: T) -> ?usize { + for (slice) |item, i| { + if (item == value) { + return i; + } + } + return null; +} + /// Reads an integer from memory with size equal to bytes.len. /// T specifies the return type, which must be large enough to store /// the result. diff --git a/std/os/child_process.zig b/std/os/child_process.zig index 0c87f9e6066c..01428ed5eb2d 100644 --- a/std/os/child_process.zig +++ b/std/os/child_process.zig @@ -154,19 +154,8 @@ pub const ChildProcess = struct { setUpChildIo(stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) %% |err| forkChildErrReport(err_pipe[1], err); - const err = posix.getErrno(%return os.posixExecve(exe_path, args, env_map, allocator)); - assert(err > 0); - forkChildErrReport(err_pipe[1], switch (err) { - errno.EFAULT => unreachable, - errno.E2BIG, errno.EMFILE, errno.ENAMETOOLONG, errno.ENFILE, errno.ENOMEM => error.SysResources, - errno.EACCES, errno.EPERM => error.AccessDenied, - errno.EINVAL, errno.ENOEXEC => error.InvalidExe, - errno.EIO, errno.ELOOP => error.FileSystem, - errno.EISDIR => error.IsDir, - errno.ENOENT, errno.ENOTDIR => error.FileNotFound, - errno.ETXTBSY => error.FileBusy, - else => error.Unexpected, - }); + os.posixExecve(exe_path, args, env_map, allocator) %% + |err| forkChildErrReport(err_pipe[1], err); } // we are the parent diff --git a/std/os/index.zig b/std/os/index.zig index 90ff053dba82..0699e9acd726 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -203,15 +203,11 @@ pub fn posixDup2(old_fd: i32, new_fd: i32) -> %void { /// 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. -/// Also make the first arg equal to path. -pub fn posixExecve(path: []const u8, argv: []const []const u8, env_map: &const BufMap, - allocator: &Allocator) -> %usize +/// Also make the first arg equal to exe_path. +/// This function also uses the PATH environment variable to get the full path to the executable. +pub fn posixExecve(exe_path: []const u8, argv: []const []const u8, env_map: &const BufMap, + allocator: &Allocator) -> %void { - const path_buf = %return allocator.alloc(u8, path.len + 1); - defer allocator.free(path_buf); - @memcpy(&path_buf[0], &path[0], path.len); - path_buf[path.len] = 0; - const argv_buf = %return allocator.alloc(?&const u8, argv.len + 2); mem.set(?&const u8, argv_buf, null); defer { @@ -222,10 +218,10 @@ pub fn posixExecve(path: []const u8, argv: []const []const u8, env_map: &const B allocator.free(argv_buf); } { - // Add path to the first argument. - const arg_buf = %return allocator.alloc(u8, path.len + 1); - @memcpy(&arg_buf[0], path.ptr, path.len); - arg_buf[path.len] = 0; + // Add exe_path to the first argument. + const arg_buf = %return allocator.alloc(u8, exe_path.len + 1); + @memcpy(&arg_buf[0], exe_path.ptr, exe_path.len); + arg_buf[exe_path.len] = 0; argv_buf[0] = arg_buf.ptr; } @@ -266,7 +262,58 @@ pub fn posixExecve(path: []const u8, argv: []const []const u8, env_map: &const B } envp_buf[envp_count] = null; - return posix.execve(path_buf.ptr, argv_buf.ptr, envp_buf.ptr); + + if (mem.indexOfScalar(u8, exe_path, '/') != null) { + // +1 for the null terminating byte + const path_buf = %return allocator.alloc(u8, exe_path.len + 1); + defer allocator.free(path_buf); + @memcpy(&path_buf[0], &exe_path[0], exe_path.len); + path_buf[exe_path.len] = 0; + return posixExecveErrnoToErr(posix.getErrno(posix.execve(path_buf.ptr, argv_buf.ptr, envp_buf.ptr))); + } + + const PATH = getEnv("PATH") ?? ([]const u8)("/usr/local/bin:/bin/:/usr/bin"); // TODO issue #299 + // 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 = %return allocator.alloc(u8, PATH.len + exe_path.len + 2); + defer allocator.free(path_buf); + var it = mem.split(PATH, ':'); + var seen_eacces = false; + var err: usize = undefined; + while (true) { + const search_path = it.next() ?? break; + 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 + 2] = 0; + err = posix.getErrno(posix.execve(path_buf.ptr, argv_buf.ptr, envp_buf.ptr)); + assert(err > 0); + if (err == errno.EACCES) { + seen_eacces = true; + } else if (err != errno.ENOENT) { + return posixExecveErrnoToErr(err); + } + } + if (seen_eacces) { + err = errno.EACCES; + } + return posixExecveErrnoToErr(err); +} + +fn posixExecveErrnoToErr(err: usize) -> error { + assert(err > 0); + return switch (err) { + errno.EFAULT => unreachable, + errno.E2BIG, errno.EMFILE, errno.ENAMETOOLONG, errno.ENFILE, errno.ENOMEM => error.SysResources, + errno.EACCES, errno.EPERM => error.AccessDenied, + errno.EINVAL, errno.ENOEXEC => error.InvalidExe, + errno.EIO, errno.ELOOP => error.FileSystem, + errno.EISDIR => error.IsDir, + errno.ENOENT, errno.ENOTDIR => error.FileNotFound, + errno.ETXTBSY => error.FileBusy, + else => error.Unexpected, + }; } pub var environ_raw: []&u8 = undefined;