Commit d15bcdce69

Andrew Kelley <superjoe30@gmail.com>
2017-04-05 23:55:50
std: ChildProcess.spawn looks at PATH env var
closes #303
1 parent d65cd73
Changed files (3)
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
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;
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.