Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990const std = @import("std");const builtin = @import("builtin");const Allocator = std.mem.Allocator;const testing = std.testing;
/// Search for "cmd" in the PATH and return the absolute path. This will/// always allocate if there is a non-null result. The caller must free the/// resulting value.pub fn expand(alloc: Allocator, cmd: []const u8) !?[]u8 { // If the command already contains a slash, then we return it as-is // because it is assumed to be absolute or relative. if (std.mem.indexOfScalar(u8, cmd, '/') != null) { return try alloc.dupe(u8, cmd); }
const PATH = switch (builtin.os.tag) { .windows => blk: { const win_path = std.process.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse return null; const path = try std.unicode.utf16LeToUtf8Alloc(alloc, win_path); break :blk path; }, else => std.posix.getenvZ("PATH") orelse return null, }; defer if (builtin.os.tag == .windows) alloc.free(PATH);
var path_buf: [std.fs.max_path_bytes]u8 = undefined; var it = std.mem.tokenizeScalar(u8, PATH, std.fs.path.delimiter); var seen_eacces = false; while (it.next()) |search_path| { // We need enough space in our path buffer to store this const path_len = search_path.len + cmd.len + 1; if (path_buf.len < path_len) return error.PathTooLong;
// Copy in the full path @memcpy(path_buf[0..search_path.len], search_path); path_buf[search_path.len] = std.fs.path.sep; @memcpy(path_buf[search_path.len + 1 ..][0..cmd.len], cmd); path_buf[path_len] = 0; const full_path = path_buf[0..path_len :0];
// Stat it const f = std.fs.cwd().openFile( full_path, .{}, ) catch |err| switch (err) { error.FileNotFound => continue, error.AccessDenied => { // Accumulate this and return it later so we can try other // paths that we have access to. seen_eacces = true; continue; }, else => return err, }; defer f.close(); const stat = try f.stat(); if (stat.kind != .directory and isExecutable(stat.mode)) { return try alloc.dupe(u8, full_path); } }
if (seen_eacces) return error.AccessDenied;
return null;}
fn isExecutable(mode: std.fs.File.Mode) bool { if (builtin.os.tag == .windows) return true; return mode & 0o0111 != 0;}
// `uname -n` is the *nix equivalent of `hostname.exe` on Windowstest "expand: hostname" { const executable = if (builtin.os.tag == .windows) "hostname.exe" else "uname"; const path = (try expand(testing.allocator, executable)).?; defer testing.allocator.free(path); try testing.expect(path.len > executable.len);}
test "expand: does not exist" { const path = try expand(testing.allocator, "thisreallyprobablydoesntexist123"); try testing.expect(path == null);}
test "expand: slash" { const path = (try expand(testing.allocator, "foo/env")).?; defer testing.allocator.free(path); try testing.expect(path.len == 7);}