From f184258f0e5514caecfd429c600aa1e8f571e895 Mon Sep 17 00:00:00 2001 From: z-jxy Date: Sat, 28 Dec 2024 16:27:33 -0500 Subject: [PATCH 1/8] expand tilde to HOME in config --- src/config/Config.zig | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/config/Config.zig b/src/config/Config.zig index 64fea91eb1..b679e1757e 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -4363,6 +4363,33 @@ pub const RepeatablePath = struct { var buf: [std.fs.max_path_bytes]u8 = undefined; const abs = dir.realpath(path, &buf) catch |err| abs: { if (err == error.FileNotFound) { + // Check if the path starts with a tilde and expand it to the home directory on linux/mac + if (path[0] == '~') { + const home_env_var = switch (builtin.os.tag) { + .linux, .macos => std.posix.getenv("HOME"), + .windows => null, + else => null, + }; + if (home_env_var) |home_dir| { + const rest = path[1..]; // Skip the ~ + const expanded_len = home_dir.len + rest.len; + if (expanded_len > buf.len) { + try diags.append(alloc, .{ + .message = try std.fmt.allocPrintZ( + alloc, + "error resolving file path {s}: path too long after expanding home directory", + .{path}, + ), + }); + self.value.items[i] = .{ .required = "" }; + continue; + } + @memcpy(buf[0..home_dir.len], home_dir); + @memcpy(buf[home_dir.len..expanded_len], rest); + break :abs buf[0..expanded_len]; + } + } + // The file doesn't exist. Try to resolve the relative path // another way. const resolved = try std.fs.path.resolve(alloc, &.{ base, path }); From 7c9c982df7a18d0aadfd7d2d9756d94a8d14eefb Mon Sep 17 00:00:00 2001 From: z-jxy Date: Sat, 28 Dec 2024 18:06:08 -0500 Subject: [PATCH 2/8] refactor: handle tilde before checking realpath --- src/config/Config.zig | 80 ++++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index b679e1757e..2e121dc181 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -4361,35 +4361,61 @@ pub const RepeatablePath = struct { // If it isn't absolute, we need to make it absolute relative // to the base. var buf: [std.fs.max_path_bytes]u8 = undefined; - const abs = dir.realpath(path, &buf) catch |err| abs: { - if (err == error.FileNotFound) { - // Check if the path starts with a tilde and expand it to the home directory on linux/mac - if (path[0] == '~') { - const home_env_var = switch (builtin.os.tag) { - .linux, .macos => std.posix.getenv("HOME"), - .windows => null, - else => null, - }; - if (home_env_var) |home_dir| { - const rest = path[1..]; // Skip the ~ - const expanded_len = home_dir.len + rest.len; - if (expanded_len > buf.len) { - try diags.append(alloc, .{ - .message = try std.fmt.allocPrintZ( - alloc, - "error resolving file path {s}: path too long after expanding home directory", - .{path}, - ), - }); - self.value.items[i] = .{ .required = "" }; - continue; - } - @memcpy(buf[0..home_dir.len], home_dir); - @memcpy(buf[home_dir.len..expanded_len], rest); - break :abs buf[0..expanded_len]; - } + + // Check if the path starts with a tilde and expand it to the home directory on linux/mac + if (path[0] == '~') { + const home_env_var = switch (builtin.os.tag) { + .linux, .macos => std.posix.getenv("HOME"), + .windows => null, + else => null, + }; + + if (home_env_var) |home_dir| { + // very unlikely to happen + if (!std.fs.path.isAbsolute(home_dir)) { + try diags.append(alloc, .{ + .message = try std.fmt.allocPrintZ( + alloc, + "error resolving file path {s}: HOME environment variable is not an absolute path", + .{path}, + ), + }); + self.value.items[i] = .{ .required = "" }; + continue; } + const rest = path[1..]; // Skip the ~ + const expanded_len = home_dir.len + rest.len; + if (expanded_len > buf.len) { + try diags.append(alloc, .{ + .message = try std.fmt.allocPrintZ( + alloc, + "error resolving file path {s}: path too long after expanding home directory", + .{path}, + ), + }); + self.value.items[i] = .{ .required = "" }; + continue; + } + + @memcpy(buf[0..home_dir.len], home_dir); + @memcpy(buf[home_dir.len..expanded_len], rest); + + log.debug( + "expanding file path from home directory: path={s}", + .{buf[0..expanded_len]}, + ); + + switch (self.value.items[i]) { + .optional, .required => |*p| p.* = try alloc.dupeZ(u8, buf[0..expanded_len]), + } + + continue; + } + } + + const abs = dir.realpath(path, &buf) catch |err| abs: { + if (err == error.FileNotFound) { // The file doesn't exist. Try to resolve the relative path // another way. const resolved = try std.fs.path.resolve(alloc, &.{ base, path }); From d27761a49972d8786c40b9666aaec0d828ed76e0 Mon Sep 17 00:00:00 2001 From: z-jxy Date: Sat, 28 Dec 2024 20:04:49 -0500 Subject: [PATCH 3/8] use `home()` from `os/homedir`, check for `~/` rather than `~` --- src/config/Config.zig | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 2e121dc181..6ef84cae76 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -4363,27 +4363,9 @@ pub const RepeatablePath = struct { var buf: [std.fs.max_path_bytes]u8 = undefined; // Check if the path starts with a tilde and expand it to the home directory on linux/mac - if (path[0] == '~') { - const home_env_var = switch (builtin.os.tag) { - .linux, .macos => std.posix.getenv("HOME"), - .windows => null, - else => null, - }; - - if (home_env_var) |home_dir| { - // very unlikely to happen - if (!std.fs.path.isAbsolute(home_dir)) { - try diags.append(alloc, .{ - .message = try std.fmt.allocPrintZ( - alloc, - "error resolving file path {s}: HOME environment variable is not an absolute path", - .{path}, - ), - }); - self.value.items[i] = .{ .required = "" }; - continue; - } - + if (std.mem.startsWith(u8, path, "~/")) { + const home_var = try internal_os.home(&buf); // cache this? + if (home_var) |home_dir| { const rest = path[1..]; // Skip the ~ const expanded_len = home_dir.len + rest.len; if (expanded_len > buf.len) { @@ -4398,7 +4380,6 @@ pub const RepeatablePath = struct { continue; } - @memcpy(buf[0..home_dir.len], home_dir); @memcpy(buf[home_dir.len..expanded_len], rest); log.debug( From 138a8f16026a34c83207670dfad00e6c310ec4d6 Mon Sep 17 00:00:00 2001 From: z-jxy Date: Sat, 28 Dec 2024 20:38:58 -0500 Subject: [PATCH 4/8] move tilde expansion functionality to `os/homedir` --- src/config/Config.zig | 35 +++++++++++++---------------------- src/os/homedir.zig | 16 ++++++++++++++++ src/os/main.zig | 1 + 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 6ef84cae76..5a9fdcc3fd 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -4364,33 +4364,24 @@ pub const RepeatablePath = struct { // Check if the path starts with a tilde and expand it to the home directory on linux/mac if (std.mem.startsWith(u8, path, "~/")) { - const home_var = try internal_os.home(&buf); // cache this? - if (home_var) |home_dir| { - const rest = path[1..]; // Skip the ~ - const expanded_len = home_dir.len + rest.len; - if (expanded_len > buf.len) { - try diags.append(alloc, .{ - .message = try std.fmt.allocPrintZ( - alloc, - "error resolving file path {s}: path too long after expanding home directory", - .{path}, - ), - }); - self.value.items[i] = .{ .required = "" }; - continue; - } - - @memcpy(buf[home_dir.len..expanded_len], rest); - + if (try internal_os.expandHome(path, &buf)) |expanded_path| { log.debug( "expanding file path from home directory: path={s}", - .{buf[0..expanded_len]}, + .{expanded_path}, ); - switch (self.value.items[i]) { - .optional, .required => |*p| p.* = try alloc.dupeZ(u8, buf[0..expanded_len]), + .optional, .required => |*p| p.* = try alloc.dupeZ(u8, expanded_path), } - + continue; + } else { + try diags.append(alloc, .{ + .message = try std.fmt.allocPrintZ( + alloc, + "error expanding home path {s}", + .{path}, + ), + }); + self.value.items[i] = .{ .required = "" }; continue; } } diff --git a/src/os/homedir.zig b/src/os/homedir.zig index cf6931f229..b03e7f354a 100644 --- a/src/os/homedir.zig +++ b/src/os/homedir.zig @@ -110,6 +110,22 @@ fn trimSpace(input: []const u8) []const u8 { return std.mem.trim(u8, input, " \n\t"); } +/// Expands a path that starts with a tilde (~) to the home directory of the user. +/// +/// Errors if `home` fails or if the size of the expanded path is larger than `buf.len`. +/// +/// Returns null if the value returned from `home` is null, otherwise returns a slice to the expanded path. +pub fn expandHome(path: []const u8, buf: []u8) !?[]u8 { + const home_dir = try home(buf) orelse return null; + const rest = path[1..]; // Skip the ~ + const expanded_len = home_dir.len + rest.len; + + if (expanded_len > buf.len) return Error.BufferTooSmall; + @memcpy(buf[home_dir.len..expanded_len], rest); + + return buf[0..expanded_len]; +} + test { const testing = std.testing; diff --git a/src/os/main.zig b/src/os/main.zig index 98e57b4fc1..fb17828628 100644 --- a/src/os/main.zig +++ b/src/os/main.zig @@ -38,6 +38,7 @@ pub const freeTmpDir = file.freeTmpDir; pub const isFlatpak = flatpak.isFlatpak; pub const FlatpakHostCommand = flatpak.FlatpakHostCommand; pub const home = homedir.home; +pub const expandHome = homedir.expandHome; pub const ensureLocale = locale.ensureLocale; pub const clickInterval = mouse.clickInterval; pub const open = openpkg.open; From 5ae2cc01ac19da64fe95c7e9717971712ca3625c Mon Sep 17 00:00:00 2001 From: z-jxy Date: Sat, 28 Dec 2024 21:06:56 -0500 Subject: [PATCH 5/8] move current `expandHome` functionality into separate `expandHomeUnix` function --- src/config/Config.zig | 29 ++++++++++++----------------- src/os/homedir.zig | 11 ++++++++++- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 5a9fdcc3fd..27b5f9d03b 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -4364,26 +4364,21 @@ pub const RepeatablePath = struct { // Check if the path starts with a tilde and expand it to the home directory on linux/mac if (std.mem.startsWith(u8, path, "~/")) { - if (try internal_os.expandHome(path, &buf)) |expanded_path| { - log.debug( - "expanding file path from home directory: path={s}", - .{expanded_path}, - ); - switch (self.value.items[i]) { - .optional, .required => |*p| p.* = try alloc.dupeZ(u8, expanded_path), - } - continue; - } else { - try diags.append(alloc, .{ - .message = try std.fmt.allocPrintZ( - alloc, - "error expanding home path {s}", - .{path}, - ), - }); + const expanded: []u8 = try internal_os.expandHome(path, &buf) orelse { + // Blank this path so that we don't attempt to resolve it again self.value.items[i] = .{ .required = "" }; continue; + }; + + log.debug( + "expanding file path from home directory: path={s}", + .{expanded}, + ); + + switch (self.value.items[i]) { + .optional, .required => |*p| p.* = try alloc.dupeZ(u8, expanded), } + continue; } const abs = dir.realpath(path, &buf) catch |err| abs: { diff --git a/src/os/homedir.zig b/src/os/homedir.zig index b03e7f354a..aea7a00176 100644 --- a/src/os/homedir.zig +++ b/src/os/homedir.zig @@ -115,7 +115,16 @@ fn trimSpace(input: []const u8) []const u8 { /// Errors if `home` fails or if the size of the expanded path is larger than `buf.len`. /// /// Returns null if the value returned from `home` is null, otherwise returns a slice to the expanded path. -pub fn expandHome(path: []const u8, buf: []u8) !?[]u8 { +pub inline fn expandHome(path: []const u8, buf: []u8) !?[]u8 { + return switch (builtin.os.tag) { + inline .linux, .macos => expandHomeUnix(path, buf), + .ios => return null, + else => @compileError("unimplemented"), + }; +} + +fn expandHomeUnix(path: []const u8, buf: []u8) !?[]u8 { + if (!std.mem.startsWith(u8, path, "~/")) return null; const home_dir = try home(buf) orelse return null; const rest = path[1..]; // Skip the ~ const expanded_len = home_dir.len + rest.len; From 7bd842a53066b65350b5445f7c4e81b96440dddc Mon Sep 17 00:00:00 2001 From: z-jxy Date: Sun, 29 Dec 2024 05:22:39 -0500 Subject: [PATCH 6/8] add test for `expandHomeUnix` --- src/os/homedir.zig | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/os/homedir.zig b/src/os/homedir.zig index aea7a00176..b0247225b6 100644 --- a/src/os/homedir.zig +++ b/src/os/homedir.zig @@ -110,7 +110,7 @@ fn trimSpace(input: []const u8) []const u8 { return std.mem.trim(u8, input, " \n\t"); } -/// Expands a path that starts with a tilde (~) to the home directory of the user. +/// Expands a path that starts with a tilde (~) to the home directory of the current user. /// /// Errors if `home` fails or if the size of the expanded path is larger than `buf.len`. /// @@ -135,6 +135,35 @@ fn expandHomeUnix(path: []const u8, buf: []u8) !?[]u8 { return buf[0..expanded_len]; } +test "expandHomeUnix" { + const testing = std.testing; + const allocator = testing.allocator; + var buf: [std.fs.max_path_bytes]u8 = undefined; + const home_dir = (try expandHomeUnix("~/", &buf)).?; + // Joining the home directory `~` with the path `/` + // the result should end with a separator here. (e.g. `/home/user/`) + try testing.expect(home_dir[home_dir.len - 1] == std.fs.path.sep); + + const downloads = (try expandHomeUnix("~/Downloads/shader.glsl", &buf)).?; + const expected_downloads = try std.mem.concat(allocator, u8, &[_][]const u8{ home_dir, "Downloads/shader.glsl" }); + defer allocator.free(expected_downloads); + try testing.expectEqualStrings(expected_downloads, downloads); + + try testing.expect(try expandHomeUnix("~", &buf) == null); + try testing.expect(try expandHomeUnix("~abc/", &buf) == null); + try testing.expect(try expandHomeUnix("/home/user", &buf) == null); + try testing.expect(try expandHomeUnix("", &buf) == null); + + // Expect an error if the buffer is large enough to hold the home directory, + // but not the expanded path + var small_buf = try allocator.alloc(u8, home_dir.len); + defer allocator.free(small_buf); + try testing.expectError(error.BufferTooSmall, expandHomeUnix( + "~/Downloads", + small_buf[0..], + )); +} + test { const testing = std.testing; From a94cf4b3a2277c912ede1afd3d46a65b9a891551 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 2 Jan 2025 12:43:50 -0800 Subject: [PATCH 7/8] config: make diagnostic if homedir expansion fails --- src/config/Config.zig | 22 ++++++++++++++++++--- src/os/homedir.zig | 46 +++++++++++++++++++++++++------------------ 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 27b5f9d03b..171d9dd12b 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -4362,11 +4362,26 @@ pub const RepeatablePath = struct { // to the base. var buf: [std.fs.max_path_bytes]u8 = undefined; - // Check if the path starts with a tilde and expand it to the home directory on linux/mac + // Check if the path starts with a tilde and expand it to the + // home directory on Linux/macOS. We explicitly look for "~/" + // because we don't support alternate users such as "~alice/" if (std.mem.startsWith(u8, path, "~/")) { - const expanded: []u8 = try internal_os.expandHome(path, &buf) orelse { - // Blank this path so that we don't attempt to resolve it again + const expanded: []const u8 = internal_os.expandHome( + path, + &buf, + ) catch |err| { + try diags.append(alloc, .{ + .message = try std.fmt.allocPrintZ( + alloc, + "error expanding home directory for path {s}: {}", + .{ path, err }, + ), + }); + + // Blank this path so that we don't attempt to resolve it + // again self.value.items[i] = .{ .required = "" }; + continue; }; @@ -4378,6 +4393,7 @@ pub const RepeatablePath = struct { switch (self.value.items[i]) { .optional, .required => |*p| p.* = try alloc.dupeZ(u8, expanded), } + continue; } diff --git a/src/os/homedir.zig b/src/os/homedir.zig index b0247225b6..b5629fd658 100644 --- a/src/os/homedir.zig +++ b/src/os/homedir.zig @@ -12,7 +12,7 @@ const Error = error{ /// Determine the home directory for the currently executing user. This /// is generally an expensive process so the value should be cached. -pub inline fn home(buf: []u8) !?[]u8 { +pub inline fn home(buf: []u8) !?[]const u8 { return switch (builtin.os.tag) { inline .linux, .macos => try homeUnix(buf), .windows => try homeWindows(buf), @@ -24,7 +24,7 @@ pub inline fn home(buf: []u8) !?[]u8 { }; } -fn homeUnix(buf: []u8) !?[]u8 { +fn homeUnix(buf: []u8) !?[]const u8 { // First: if we have a HOME env var, then we use that. if (posix.getenv("HOME")) |result| { if (buf.len < result.len) return Error.BufferTooSmall; @@ -77,7 +77,7 @@ fn homeUnix(buf: []u8) !?[]u8 { return null; } -fn homeWindows(buf: []u8) !?[]u8 { +fn homeWindows(buf: []u8) !?[]const u8 { const drive_len = blk: { var fba_instance = std.heap.FixedBufferAllocator.init(buf); const fba = fba_instance.allocator(); @@ -110,22 +110,30 @@ fn trimSpace(input: []const u8) []const u8 { return std.mem.trim(u8, input, " \n\t"); } -/// Expands a path that starts with a tilde (~) to the home directory of the current user. -/// -/// Errors if `home` fails or if the size of the expanded path is larger than `buf.len`. +pub const ExpandError = error{ + HomeDetectionFailed, + BufferTooSmall, +}; + +/// Expands a path that starts with a tilde (~) to the home directory of +/// the current user. /// -/// Returns null if the value returned from `home` is null, otherwise returns a slice to the expanded path. -pub inline fn expandHome(path: []const u8, buf: []u8) !?[]u8 { +/// Errors if `home` fails or if the size of the expanded path is larger +/// than `buf.len`. +pub fn expandHome(path: []const u8, buf: []u8) ExpandError![]const u8 { return switch (builtin.os.tag) { - inline .linux, .macos => expandHomeUnix(path, buf), - .ios => return null, + .linux, .macos => try expandHomeUnix(path, buf), + .ios => return path, else => @compileError("unimplemented"), }; } -fn expandHomeUnix(path: []const u8, buf: []u8) !?[]u8 { - if (!std.mem.startsWith(u8, path, "~/")) return null; - const home_dir = try home(buf) orelse return null; +fn expandHomeUnix(path: []const u8, buf: []u8) ExpandError![]const u8 { + if (!std.mem.startsWith(u8, path, "~/")) return path; + const home_dir: []const u8 = if (home(buf)) |home_| + home_ orelse return error.HomeDetectionFailed + else |_| + return error.HomeDetectionFailed; const rest = path[1..]; // Skip the ~ const expanded_len = home_dir.len + rest.len; @@ -139,20 +147,20 @@ test "expandHomeUnix" { const testing = std.testing; const allocator = testing.allocator; var buf: [std.fs.max_path_bytes]u8 = undefined; - const home_dir = (try expandHomeUnix("~/", &buf)).?; + const home_dir = try expandHomeUnix("~/", &buf); // Joining the home directory `~` with the path `/` // the result should end with a separator here. (e.g. `/home/user/`) try testing.expect(home_dir[home_dir.len - 1] == std.fs.path.sep); - const downloads = (try expandHomeUnix("~/Downloads/shader.glsl", &buf)).?; + const downloads = try expandHomeUnix("~/Downloads/shader.glsl", &buf); const expected_downloads = try std.mem.concat(allocator, u8, &[_][]const u8{ home_dir, "Downloads/shader.glsl" }); defer allocator.free(expected_downloads); try testing.expectEqualStrings(expected_downloads, downloads); - try testing.expect(try expandHomeUnix("~", &buf) == null); - try testing.expect(try expandHomeUnix("~abc/", &buf) == null); - try testing.expect(try expandHomeUnix("/home/user", &buf) == null); - try testing.expect(try expandHomeUnix("", &buf) == null); + try testing.expectEqualStrings("~", try expandHomeUnix("~", &buf)); + try testing.expectEqualStrings("~abc/", try expandHomeUnix("~abc/", &buf)); + try testing.expectEqualStrings("/home/user", try expandHomeUnix("/home/user", &buf)); + try testing.expectEqualStrings("", try expandHomeUnix("", &buf)); // Expect an error if the buffer is large enough to hold the home directory, // but not the expanded path From d58b618c74216004da556413c0fafad2e9650c5d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 2 Jan 2025 12:55:38 -0800 Subject: [PATCH 8/8] config: windows can't expand homedir (yet) --- src/config/Config.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 171d9dd12b..e9052a66e1 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -4365,7 +4365,10 @@ pub const RepeatablePath = struct { // Check if the path starts with a tilde and expand it to the // home directory on Linux/macOS. We explicitly look for "~/" // because we don't support alternate users such as "~alice/" - if (std.mem.startsWith(u8, path, "~/")) { + if (std.mem.startsWith(u8, path, "~/")) expand: { + // Windows isn't supported yet + if (comptime builtin.os.tag == .windows) break :expand; + const expanded: []const u8 = internal_os.expandHome( path, &buf,