work
This commit is contained in:
parent
6c60ecd1c6
commit
265c035bd4
6 changed files with 187 additions and 194 deletions
25
build.zig
25
build.zig
|
@ -1,4 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const path = @import("src/path.zig");
|
||||||
|
|
||||||
pub fn build(b: *std.Build) void {
|
pub fn build(b: *std.Build) void {
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
|
@ -8,13 +9,27 @@ pub fn build(b: *std.Build) void {
|
||||||
[]const u8,
|
[]const u8,
|
||||||
"ssh",
|
"ssh",
|
||||||
"path to ssh binary",
|
"path to ssh binary",
|
||||||
) orelse "ssh";
|
) orelse ssh: {
|
||||||
|
const p = path.expandPath(b.allocator, "ssh") catch {
|
||||||
|
break :ssh "ssh";
|
||||||
|
} orelse {
|
||||||
|
break :ssh "ssh";
|
||||||
|
};
|
||||||
|
break :ssh p;
|
||||||
|
};
|
||||||
|
|
||||||
const telnet_path = b.option(
|
const telnet_path = b.option(
|
||||||
[]const u8,
|
[]const u8,
|
||||||
"telnet",
|
"telnet",
|
||||||
"path to telnet binary",
|
"path to telnet binary",
|
||||||
) orelse "telnet";
|
) orelse telnet: {
|
||||||
|
const p = path.expandPath(b.allocator, "telnet") catch {
|
||||||
|
break :telnet "telnet";
|
||||||
|
} orelse {
|
||||||
|
break :telnet "telnet";
|
||||||
|
};
|
||||||
|
break :telnet p;
|
||||||
|
};
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "hostapps",
|
.name = "hostapps",
|
||||||
|
@ -49,15 +64,15 @@ pub fn build(b: *std.Build) void {
|
||||||
|
|
||||||
exe.root_module.addImport("logz", logz.module("logz"));
|
exe.root_module.addImport("logz", logz.module("logz"));
|
||||||
|
|
||||||
const yazap = b.dependency(
|
const @"zig-cli" = b.dependency(
|
||||||
"yazap",
|
"zig-cli",
|
||||||
.{
|
.{
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
exe.root_module.addImport("yazap", yazap.module("yazap"));
|
exe.root_module.addImport("zig-cli", @"zig-cli".module("zig-cli"));
|
||||||
|
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
|
|
||||||
|
|
|
@ -4,25 +4,26 @@
|
||||||
.minimum_zig_version = "0.12.0",
|
.minimum_zig_version = "0.12.0",
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.logz = .{
|
.logz = .{
|
||||||
.url = "https://github.com/karlseguin/log.zig/archive/a70984c80eb67c448480377849ba2adb6e51cf73.tar.gz",
|
.url = "https://github.com/karlseguin/log.zig/archive/eb86766b91cf2ca0a6e87dd8568fab80695cf2e8.tar.gz",
|
||||||
.hash = "122090c83a4e52b454e006da4a804b2210136ddec0dccfae00e7097314e5acfd01d0",
|
.hash = "1220b6cf56de826aead943a6db2a62ed40d81a150c24dcb019b3eb88673c82a6e7b7",
|
||||||
},
|
|
||||||
.yazap = .{
|
|
||||||
.url = "https://github.com/prajwalch/yazap/archive/5f0d5d8928d5cd1907760dc41fa6f05dd232aaa5.tar.gz",
|
|
||||||
.hash = "1220e4674826a70402974f13b7e2aaa4e9242e1b2b9d592015de9e21c7fa5fe200bd",
|
|
||||||
},
|
},
|
||||||
|
// .yazap = .{
|
||||||
|
// .url = "https://github.com/prajwalch/yazap/archive/5f0d5d8928d5cd1907760dc41fa6f05dd232aaa5.tar.gz",
|
||||||
|
// .hash = "1220e4674826a70402974f13b7e2aaa4e9242e1b2b9d592015de9e21c7fa5fe200bd",
|
||||||
|
// },
|
||||||
.anzi = .{
|
.anzi = .{
|
||||||
.url = "https://git.ocjtech.us/jeff/anzi/archive/0e9d395a55b16da1d5bf7bfb59e2cfa59a7f2630.tar.gz",
|
.url = "https://git.ocjtech.us/jeff/anzi/archive/0e9d395a55b16da1d5bf7bfb59e2cfa59a7f2630.tar.gz",
|
||||||
.hash = "12203f0ed986047bcd860b00496979a7734c2f462da4e56a72add4b17a1a7981f8ec",
|
.hash = "12203f0ed986047bcd860b00496979a7734c2f462da4e56a72add4b17a1a7981f8ec",
|
||||||
},
|
},
|
||||||
|
.@"zig-cli" = .{
|
||||||
|
.url = "https://github.com/sam701/zig-cli/archive/50201f0086da689cd104c2fef350f3dbe894eea7.tar.gz",
|
||||||
|
// .hash = "12208a4377c7699927605e5a797bad5ae2ba8be4b49f68b9f522b3a755597a21f1cf",
|
||||||
|
.hash = "12208a4377c7699927605e5a797bad5ae2ba8be4b49f68b9f522b3a755597a21f1cf",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
.paths = .{
|
.paths = .{
|
||||||
// "",
|
|
||||||
// For example...
|
|
||||||
"build.zig",
|
"build.zig",
|
||||||
"build.zig.zon",
|
"build.zig.zon",
|
||||||
"src",
|
"src",
|
||||||
// "LICENSE",
|
|
||||||
// "README.md",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const Config = @import("config.zig").Config;
|
const Config = @import("config.zig").Config;
|
||||||
|
const options = @import("options.zig");
|
||||||
const ansi = @import("anzi").ANSI(.{});
|
const ansi = @import("anzi").ANSI(.{});
|
||||||
|
|
||||||
const kex_algorithms = [_][]const u8{
|
const kex_algorithms = [_][]const u8{
|
||||||
|
@ -30,7 +31,7 @@ const macs = [_][]const u8{
|
||||||
"hmac-sha2-512",
|
"hmac-sha2-512",
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = [_]struct { key: []const u8, value: []const u8 }{
|
const ssh_options = [_]struct { key: []const u8, value: []const u8 }{
|
||||||
.{ .key = "ControlMaster", .value = "no" },
|
.{ .key = "ControlMaster", .value = "no" },
|
||||||
.{ .key = "ControlPath", .value = "none" },
|
.{ .key = "ControlPath", .value = "none" },
|
||||||
.{ .key = "ForwardX11", .value = "no" },
|
.{ .key = "ForwardX11", .value = "no" },
|
||||||
|
@ -39,19 +40,14 @@ const options = [_]struct { key: []const u8, value: []const u8 }{
|
||||||
.{ .key = "PubkeyAcceptedKeyTypes", .value = "+ssh-rsa" },
|
.{ .key = "PubkeyAcceptedKeyTypes", .value = "+ssh-rsa" },
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn connect(
|
pub fn connect() !void {
|
||||||
allocator: std.mem.Allocator,
|
var arena = std.heap.ArenaAllocator.init(options.options.alloc);
|
||||||
config_path: []const u8,
|
|
||||||
identities: ?[][]const u8,
|
|
||||||
proxy_jump: ?[]const u8,
|
|
||||||
ssh_path: []const u8,
|
|
||||||
telnet_path: []const u8,
|
|
||||||
) !void {
|
|
||||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
|
||||||
const alloc = arena.allocator();
|
const alloc = arena.allocator();
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
|
|
||||||
const config = try Config.read(alloc, config_path);
|
std.log.info("config: {}", .{options.options});
|
||||||
|
|
||||||
|
const config = try Config.read(options.options.alloc, options.options.config);
|
||||||
|
|
||||||
var path: []const u8 = undefined;
|
var path: []const u8 = undefined;
|
||||||
var args = std.ArrayList([]const u8).init(alloc);
|
var args = std.ArrayList([]const u8).init(alloc);
|
||||||
|
@ -69,23 +65,21 @@ pub fn connect(
|
||||||
|
|
||||||
switch (config.type) {
|
switch (config.type) {
|
||||||
.ssh => {
|
.ssh => {
|
||||||
path = ssh_path;
|
path = options.options.ssh_path;
|
||||||
try args.append("ssh");
|
try args.append("ssh");
|
||||||
try args.append("-y");
|
try args.append("-y");
|
||||||
|
|
||||||
if (identities) |i| {
|
for (options.options.identities) |identity| {
|
||||||
for (i) |identity| {
|
try args.append("-i");
|
||||||
try args.append("-i");
|
try args.append(identity);
|
||||||
try args.append(identity);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proxy_jump) |p| {
|
if (options.options.proxy_jump) |p| {
|
||||||
try args.append("-o");
|
try args.append("-o");
|
||||||
try args.append(try std.fmt.allocPrint(alloc, "ProxyJump={s}", .{p}));
|
try args.append(try std.fmt.allocPrint(alloc, "ProxyJump={s}", .{p}));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (options) |option| {
|
for (ssh_options) |option| {
|
||||||
try args.append("-o");
|
try args.append("-o");
|
||||||
try args.append(try std.fmt.allocPrint(alloc, "{s}={s}", .{ option.key, option.value }));
|
try args.append(try std.fmt.allocPrint(alloc, "{s}={s}", .{ option.key, option.value }));
|
||||||
}
|
}
|
||||||
|
@ -111,14 +105,14 @@ pub fn connect(
|
||||||
},
|
},
|
||||||
|
|
||||||
.telnet => {
|
.telnet => {
|
||||||
if (proxy_jump) |p| {
|
if (options.options.proxy_jump) |p| {
|
||||||
path = ssh_path;
|
path = options.options.ssh_path;
|
||||||
try args.append("ssh");
|
try args.append("ssh");
|
||||||
try args.append("-t");
|
try args.append("-t");
|
||||||
try args.append(try std.fmt.allocPrint(alloc, "ssh://{s}", .{p}));
|
try args.append(try std.fmt.allocPrint(alloc, "ssh://{s}", .{p}));
|
||||||
try args.append("telnet");
|
try args.append("telnet");
|
||||||
} else {
|
} else {
|
||||||
path = telnet_path;
|
path = options.options.telnet_path;
|
||||||
try args.append("telnet");
|
try args.append("telnet");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
163
src/main.zig
163
src/main.zig
|
@ -1,169 +1,18 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const build_options = @import("build_options");
|
const build_options = @import("build_options");
|
||||||
const yazap = @import("yazap");
|
const cli = @import("zig-cli");
|
||||||
const logz = @import("logz");
|
|
||||||
const Config = @import("config.zig").Config;
|
|
||||||
const connect = @import("connect.zig");
|
const connect = @import("connect.zig");
|
||||||
|
const options = @import("options.zig");
|
||||||
const App = yazap.App;
|
|
||||||
const Arg = yazap.Arg;
|
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
const alloc = gpa.allocator();
|
const alloc = gpa.allocator();
|
||||||
|
|
||||||
try logz.setup(alloc, .{
|
options.options.alloc = alloc;
|
||||||
.level = .Debug,
|
|
||||||
.pool_size = 16,
|
|
||||||
.max_size = 4096,
|
|
||||||
.output = .stderr,
|
|
||||||
});
|
|
||||||
|
|
||||||
var app = App.init(alloc, "hostapps", "hostapps");
|
var iter = try std.process.argsWithAllocator(alloc);
|
||||||
defer app.deinit();
|
defer iter.deinit();
|
||||||
|
|
||||||
var root = app.rootCommand();
|
return cli.run(options.app, alloc);
|
||||||
try root.addArg(Arg.booleanOption("version", null, "Print version and exit"));
|
|
||||||
|
|
||||||
var connect_command = app.createCommand("connect", "connect to remote host");
|
|
||||||
try connect_command.addArg(Arg.singleValueOption("config", null, "Path to configuration file"));
|
|
||||||
try connect_command.addArg(Arg.multiValuesOption("identity", null, "Path to identity file", 8));
|
|
||||||
try connect_command.addArg(Arg.singleValueOption("proxy-jump", null, "Proxy jump"));
|
|
||||||
try connect_command.addArg(Arg.singleValueOption("ssh-command", null, "Path to ssh command"));
|
|
||||||
try connect_command.addArg(Arg.singleValueOption("telnet-command", null, "Path to telnet command"));
|
|
||||||
|
|
||||||
try root.addSubcommand(connect_command);
|
|
||||||
|
|
||||||
const matches = try app.parseProcess();
|
|
||||||
|
|
||||||
if (!(matches.containsArgs())) {
|
|
||||||
try app.displayHelp();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matches.containsArg("version")) {
|
|
||||||
std.log.info("version number", .{});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matches.subcommandMatches("connect")) |connect_matches| {
|
|
||||||
const config_path = connect_matches.getSingleValue("config") orelse {
|
|
||||||
try app.displaySubcommandHelp();
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
const identities = id: {
|
|
||||||
const ids = connect_matches.getMultiValues("identity");
|
|
||||||
if (ids != null) break :id ids;
|
|
||||||
const id = connect_matches.getSingleValue("identity");
|
|
||||||
if (id) |i| {
|
|
||||||
break :id @as(?[][]const u8, @constCast(&[_][]const u8{i}));
|
|
||||||
}
|
|
||||||
break :id null;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (identities) |i| {
|
|
||||||
for (i) |identity| {
|
|
||||||
logz.info().string("identity", identity).log();
|
|
||||||
const fullpath = fp: {
|
|
||||||
if (identity[0] == '~') {
|
|
||||||
const home = std.os.getenv("HOME") orelse {
|
|
||||||
logz.err().src(@src()).string("message", "unable to get HOME environment variable to expand tilde").log();
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
var fp = try alloc.alloc(u8, home.len + identity.len - 1);
|
|
||||||
@memcpy(fp[0..home.len], home);
|
|
||||||
@memcpy(fp[home.len..], identity[1..]);
|
|
||||||
break :fp fp;
|
|
||||||
}
|
|
||||||
break :fp identity;
|
|
||||||
};
|
|
||||||
logz.info().string("fullpath", fullpath).log();
|
|
||||||
std.fs.cwd().access(fullpath, .{ .mode = .read_only }) catch |err| {
|
|
||||||
logz.err().src(@src()).err(err).string("identity", identity).string("fullpath", fullpath).log();
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if (identity.ptr != fullpath.ptr) alloc.free(fullpath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const proxy_jump = connect_matches.getSingleValue("proxy-jump");
|
|
||||||
|
|
||||||
var ssh_path = connect_matches.getSingleValue("ssh-command") orelse build_options.ssh_path;
|
|
||||||
|
|
||||||
if (!std.fs.path.isAbsolute(ssh_path)) {
|
|
||||||
const expanded = expandPath(alloc, ssh_path) catch |err| expanded: {
|
|
||||||
std.log.warn("failed to expand ssh path={s} err={}", .{ ssh_path, err });
|
|
||||||
break :expanded null;
|
|
||||||
};
|
|
||||||
if (expanded) |v| {
|
|
||||||
ssh_path = v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var telnet_path = connect_matches.getSingleValue("telnet-command") orelse build_options.telnet_path;
|
|
||||||
|
|
||||||
if (!std.fs.path.isAbsolute(telnet_path)) {
|
|
||||||
const expanded = expandPath(alloc, telnet_path) catch |err| expanded: {
|
|
||||||
std.log.warn("failed to expand telnet path={s} err={}", .{ telnet_path, err });
|
|
||||||
break :expanded null;
|
|
||||||
};
|
|
||||||
if (expanded) |v| {
|
|
||||||
telnet_path = v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try connect.connect(alloc, config_path, identities, proxy_jump, ssh_path, telnet_path);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expandPath(alloc: std.mem.Allocator, cmd: []const u8) !?[]u8 {
|
|
||||||
const PATH = switch (builtin.os.tag) {
|
|
||||||
.windows => blk: {
|
|
||||||
const win_path = std.os.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse return null;
|
|
||||||
const path = try std.unicode.utf16leToUtf8Alloc(alloc, win_path);
|
|
||||||
break :blk path;
|
|
||||||
},
|
|
||||||
else => std.os.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_eaccess = false;
|
|
||||||
while (it.next()) |search_path| {
|
|
||||||
const path_len = search_path.len + cmd.len + 1;
|
|
||||||
if (path_buf.len < path_len) return error.PathTooLong;
|
|
||||||
|
|
||||||
@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];
|
|
||||||
|
|
||||||
const stat = std.fs.cwd().statFile(full_path) catch |err| switch (err) {
|
|
||||||
error.FileNotFound => continue,
|
|
||||||
error.AccessDenied => {
|
|
||||||
seen_eaccess = true;
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
else => return err,
|
|
||||||
};
|
|
||||||
if (stat.kind != .directory and isExecutable(stat.mode)) {
|
|
||||||
std.log.debug("executable: {s}", .{full_path});
|
|
||||||
return try alloc.dupe(u8, full_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seen_eaccess) return error.AccessDenied;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isExecutable(mode: std.fs.File.Mode) bool {
|
|
||||||
if (builtin.os.tag == .windows) return true;
|
|
||||||
return mode & 0o0111 != 0;
|
|
||||||
}
|
}
|
||||||
|
|
84
src/options.zig
Normal file
84
src/options.zig
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const cli = @import("zig-cli");
|
||||||
|
const build_options = @import("build_options");
|
||||||
|
const connect = @import("connect.zig");
|
||||||
|
|
||||||
|
pub const Options = struct {
|
||||||
|
alloc: std.mem.Allocator = undefined,
|
||||||
|
config: []const u8 = undefined,
|
||||||
|
identities: []const []const u8 = undefined,
|
||||||
|
proxy_jump: ?[]const u8 = null,
|
||||||
|
ssh_path: []const u8,
|
||||||
|
telnet_path: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub var options: Options = .{
|
||||||
|
.ssh_path = build_options.ssh_path,
|
||||||
|
.telnet_path = build_options.telnet_path,
|
||||||
|
};
|
||||||
|
|
||||||
|
var config_option = cli.Option{
|
||||||
|
.long_name = "config",
|
||||||
|
.help = "config file",
|
||||||
|
.value_ref = cli.mkRef(&options.config),
|
||||||
|
.required = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var identity_option = cli.Option{
|
||||||
|
.long_name = "identity",
|
||||||
|
.help = "ssh identity",
|
||||||
|
.value_ref = cli.mkRef(&options.identities),
|
||||||
|
};
|
||||||
|
|
||||||
|
var proxy_jump_option = cli.Option{
|
||||||
|
.long_name = "proxy-jump",
|
||||||
|
.help = "proxy jump",
|
||||||
|
.value_ref = cli.mkRef(&options.proxy_jump),
|
||||||
|
};
|
||||||
|
|
||||||
|
var ssh_command_option = cli.Option{
|
||||||
|
.long_name = "ssh-command",
|
||||||
|
.help = "ssh command",
|
||||||
|
.value_ref = cli.mkRef(&options.ssh_path),
|
||||||
|
};
|
||||||
|
|
||||||
|
var telnet_command_option = cli.Option{
|
||||||
|
.long_name = "telnet-command",
|
||||||
|
.help = "telnet command",
|
||||||
|
.value_ref = cli.mkRef(&options.telnet_path),
|
||||||
|
};
|
||||||
|
|
||||||
|
var connect_command = cli.Command{
|
||||||
|
.name = "connect",
|
||||||
|
.description = cli.Description{
|
||||||
|
.one_line = "connect",
|
||||||
|
},
|
||||||
|
.options = &.{
|
||||||
|
&config_option,
|
||||||
|
&identity_option,
|
||||||
|
&proxy_jump_option,
|
||||||
|
&ssh_command_option,
|
||||||
|
&telnet_command_option,
|
||||||
|
},
|
||||||
|
.target = cli.CommandTarget{
|
||||||
|
.action = cli.CommandAction{
|
||||||
|
.exec = connect.connect,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const app = &cli.App{
|
||||||
|
.command = cli.Command{
|
||||||
|
.name = "hostapps",
|
||||||
|
.description = cli.Description{
|
||||||
|
.one_line = "hostapps",
|
||||||
|
},
|
||||||
|
.target = cli.CommandTarget{
|
||||||
|
.subcommands = &.{
|
||||||
|
&connect_command,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.version = "0.1.0",
|
||||||
|
.author = "Jeffrey C. Ollie <jcollie@dmacc.edu>",
|
||||||
|
};
|
50
src/path.zig
Normal file
50
src/path.zig
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
pub fn expandPath(alloc: std.mem.Allocator, cmd: []const u8) !?[]u8 {
|
||||||
|
const PATH = switch (builtin.os.tag) {
|
||||||
|
.windows => blk: {
|
||||||
|
const win_path = std.os.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse return null;
|
||||||
|
const path = try std.unicode.utf16leToUtf8Alloc(alloc, win_path);
|
||||||
|
break :blk path;
|
||||||
|
},
|
||||||
|
else => std.os.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_eaccess = false;
|
||||||
|
while (it.next()) |search_path| {
|
||||||
|
const path_len = search_path.len + cmd.len + 1;
|
||||||
|
if (path_buf.len < path_len) return error.PathTooLong;
|
||||||
|
|
||||||
|
@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];
|
||||||
|
|
||||||
|
const stat = std.fs.cwd().statFile(full_path) catch |err| switch (err) {
|
||||||
|
error.FileNotFound => continue,
|
||||||
|
error.AccessDenied => {
|
||||||
|
seen_eaccess = true;
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
if (stat.kind != .directory and isExecutable(stat.mode)) {
|
||||||
|
std.log.debug("executable: {s}", .{full_path});
|
||||||
|
return try alloc.dupe(u8, full_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seen_eaccess) return error.AccessDenied;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isExecutable(mode: std.fs.File.Mode) bool {
|
||||||
|
if (builtin.os.tag == .windows) return true;
|
||||||
|
return mode & 0o0111 != 0;
|
||||||
|
}
|
Loading…
Reference in a new issue