diff --git a/src/main.zig b/src/main.zig index e7dd6c633..17ec5b0f8 100644 --- a/src/main.zig +++ b/src/main.zig @@ -243,9 +243,9 @@ pub const GlobalState = struct { // maybe once for logging) so for now this is an easy way to do // this. Env vars are useful for logging too because they are // easy to set. - if ((try internal_os.getEnvVarOwned(self.alloc, "GHOSTTY_LOG"))) |v| { - defer self.alloc.free(v); - if (v.len > 0) { + if ((try internal_os.getenv(self.alloc, "GHOSTTY_LOG"))) |v| { + defer v.deinit(self.alloc); + if (v.value.len > 0) { self.logging = .{ .stderr = {} }; } } diff --git a/src/os/env.zig b/src/os/env.zig index a3c34bd79..3f94b2a76 100644 --- a/src/os/env.zig +++ b/src/os/env.zig @@ -25,6 +25,61 @@ pub fn appendEnv( }); } +/// The result of getenv, with a shared deinit to properly handle allocation +/// on Windows. +pub const GetEnvResult = struct { + value: []const u8, + + pub fn deinit(self: GetEnvResult, alloc: Allocator) void { + switch (builtin.os.tag) { + .windows => alloc.free(self.value), + else => {}, + } + } +}; + +/// Gets the value of an environment variable, or null if not found. +/// This will allocate on Windows but not on other platforms. The returned +/// value should have deinit called to do the proper cleanup no matter what +/// platform you are on. +pub fn getenv(alloc: Allocator, key: []const u8) !?GetEnvResult { + return switch (builtin.os.tag) { + // Non-Windows doesn't need to allocate + else => if (std.os.getenv(key)) |v| .{ .value = v } else null, + + // Windows needs to allocate + .windows => if (std.process.getEnvVarOwned(alloc, key)) |v| .{ + .value = v, + } else |err| switch (err) { + error.EnvironmentVariableNotFound => null, + else => err, + }, + }; +} + +pub fn setenv(key: [:0]const u8, value: [:0]const u8) c_int { + return switch (builtin.os.tag) { + .windows => c._putenv_s(key.ptr, value.ptr), + else => c.setenv(key.ptr, value.ptr, 1), + }; +} + +pub fn unsetenv(key: [:0]const u8) c_int { + return switch (builtin.os.tag) { + .windows => c._putenv_s(key.ptr, ""), + else => c.unsetenv(key.ptr), + }; +} + +const c = struct { + // POSIX + extern "c" fn setenv(name: ?[*]const u8, value: ?[*]const u8, overwrite: c_int) c_int; + extern "c" fn unsetenv(name: ?[*]const u8) c_int; + + // Windows + extern "c" fn _putenv_s(varname: ?[*]const u8, value_string: ?[*]const u8) c_int; +}; + test "appendEnv empty" { const testing = std.testing; const alloc = testing.allocator; @@ -46,36 +101,3 @@ test "appendEnv existing" { try testing.expectEqualStrings(result, "a:b:foo"); } } - -extern "c" fn setenv(name: ?[*]const u8, value: ?[*]const u8, overwrite: c_int) c_int; -extern "c" fn unsetenv(name: ?[*]const u8) c_int; -extern "c" fn _putenv_s(varname: ?[*]const u8, value_string: ?[*]const u8) c_int; - -pub fn setEnv(key: [:0]const u8, value: [:0]const u8) c_int { - if (builtin.os.tag == .windows) { - return _putenv_s(key.ptr, value.ptr); - } else { - return setenv(key.ptr, value.ptr, 1); - } -} - -pub fn unsetEnv(key: [:0]const u8) c_int { - if (builtin.os.tag == .windows) { - return _putenv_s(key.ptr, ""); - } else { - return unsetenv(key.ptr); - } -} - -/// Returns the value of an environment variable, or null if not found. -/// The returned value is always allocated so it must be freed. -pub fn getEnvVarOwned(alloc: std.mem.Allocator, key: []const u8) !?[]u8 { - if (std.process.getEnvVarOwned(alloc, key)) |v| { - return v; - } else |err| switch (err) { - error.EnvironmentVariableNotFound => {}, - else => return err, - } - - return null; -} diff --git a/src/os/locale.zig b/src/os/locale.zig index 2efdd1c69..c05fa9d6b 100644 --- a/src/os/locale.zig +++ b/src/os/locale.zig @@ -12,8 +12,8 @@ pub fn ensureLocale(alloc: std.mem.Allocator) !void { // Get our LANG env var. We use this many times but we also need // the original value later. - const lang = try internal_os.getEnvVarOwned(alloc, "LANG"); - defer if (lang) |v| alloc.free(v); + const lang = try internal_os.getenv(alloc, "LANG"); + defer if (lang) |v| v.deinit(alloc); // On macOS, pre-populate the LANG env var with system preferences. // When launching the .app, LANG is not set so we must query it from the @@ -22,7 +22,7 @@ pub fn ensureLocale(alloc: std.mem.Allocator) !void { if (comptime builtin.target.isDarwin()) { // Set the lang if it is not set or if its empty. if (lang) |l| { - if (l.len == 0) { + if (l.value.len == 0) { setLangFromCocoa(); } } @@ -37,13 +37,13 @@ pub fn ensureLocale(alloc: std.mem.Allocator) !void { // setlocale failed. This is probably because the LANG env var is // invalid. Try to set it without the LANG var set to use the system // default. - if ((try internal_os.getEnvVarOwned(alloc, "LANG"))) |old_lang| { - defer alloc.free(old_lang); - if (old_lang.len > 0) { + if ((try internal_os.getenv(alloc, "LANG"))) |old_lang| { + defer old_lang.deinit(alloc); + if (old_lang.value.len > 0) { // We don't need to do both of these things but we do them // both to be sure that lang is either empty or unset completely. - _ = internal_os.setEnv("LANG", ""); - _ = internal_os.unsetEnv("LANG"); + _ = internal_os.setenv("LANG", ""); + _ = internal_os.unsetenv("LANG"); if (setlocale(LC_ALL, "")) |v| { log.info("setlocale after unset lang result={s}", .{v}); @@ -59,7 +59,7 @@ pub fn ensureLocale(alloc: std.mem.Allocator) !void { // Failure again... fallback to en_US.UTF-8 log.warn("setlocale failed with LANG and system default. Falling back to en_US.UTF-8", .{}); if (setlocale(LC_ALL, "en_US.UTF-8")) |v| { - _ = internal_os.setEnv("LANG", "en_US.UTF-8"); + _ = internal_os.setenv("LANG", "en_US.UTF-8"); log.info("setlocale default result={s}", .{v}); return; } else log.err("setlocale failed even with the fallback, uncertain results", .{}); @@ -100,7 +100,7 @@ fn setLangFromCocoa() void { log.info("detected system locale={s}", .{env_value}); // Set it onto our environment - if (internal_os.setEnv("LANG", env_value) < 0) { + if (internal_os.setenv("LANG", env_value) < 0) { log.err("error setting locale env var", .{}); return; }