diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/os/passwd.zig | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (diff) | |
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/os/passwd.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/os/passwd.zig | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/os/passwd.zig b/vendor/libghostty-vt/src/os/passwd.zig new file mode 100644 index 00000000..e9bbff06 --- /dev/null +++ b/vendor/libghostty-vt/src/os/passwd.zig @@ -0,0 +1,157 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const internal_os = @import("main.zig"); +const build_config = @import("../build_config.zig"); +const Allocator = std.mem.Allocator; +const ArenaAllocator = std.heap.ArenaAllocator; +const posix = std.posix; + +const log = std.log.scoped(.passwd); + +// We want to be extra sure since this will force bad symbols into our import table +comptime { + if (builtin.target.cpu.arch.isWasm()) { + @compileError("passwd is not available for wasm"); + } +} + +/// Used to determine the default shell and directory on Unixes. +const c = if (builtin.os.tag != .windows) @cImport({ + @cInclude("sys/types.h"); + @cInclude("unistd.h"); + @cInclude("pwd.h"); +}) else {}; + +// Entry that is retrieved from the passwd API. This only contains the fields +// we care about. +pub const Entry = struct { + shell: ?[:0]const u8 = null, + home: ?[:0]const u8 = null, + name: ?[:0]const u8 = null, +}; + +/// Get the passwd entry for the currently executing user. +pub fn get(alloc: Allocator) !Entry { + if (builtin.os.tag == .windows) @compileError("passwd is not available on windows"); + + var buf: [1024]u8 = undefined; + var pw: c.struct_passwd = undefined; + var pw_ptr: ?*c.struct_passwd = null; + const res = c.getpwuid_r(c.getuid(), &pw, &buf, buf.len, &pw_ptr); + if (res != 0) { + log.warn("error retrieving pw entry code={d}", .{res}); + return Entry{}; + } + + if (pw_ptr == null) { + // Future: let's check if a better shell is available like zsh + log.warn("no pw entry to detect default shell, will default to 'sh'", .{}); + return Entry{}; + } + + var result: Entry = .{}; + + // If we're in flatpak then our entry is always empty so we grab it + // by shelling out to the host. note that we do HAVE an entry in the + // sandbox but only the username is correct. + if (internal_os.isFlatpak()) flatpak: { + if (comptime !build_config.flatpak) { + log.warn("flatpak detected, but this build doesn't contain flatpak support", .{}); + break :flatpak; + } + + log.info("flatpak detected, will use host command to get our entry", .{}); + + // Note: we wrap our getent call in a /bin/sh login shell because + // some operating systems (NixOS tested) don't set the PATH for various + // utilities properly until we get a login shell. + const Pty = @import("../pty.zig").Pty; + var pty = try Pty.open(.{}); + defer pty.deinit(); + var cmd: internal_os.FlatpakHostCommand = .{ + .argv = &[_][]const u8{ + "/bin/sh", + "-l", + "-c", + try std.fmt.allocPrint( + alloc, + "getent passwd {s}", + .{std.mem.sliceTo(pw.pw_name, 0)}, + ), + }, + .stdin = pty.slave, + .stdout = pty.slave, + .stderr = pty.slave, + }; + _ = try cmd.spawn(alloc); + _ = try cmd.wait(); + + // Once started, we can close the child side. We do this after + // wait right now but that is fine too. This lets us read the + // parent and detect EOF. + _ = posix.close(pty.slave); + + // Read all of our output + const output = output: { + var output: std.ArrayListUnmanaged(u8) = .{}; + while (true) { + const n = posix.read(pty.master, &buf) catch |err| { + switch (err) { + // EIO is triggered at the end since we closed our + // child side. This is just EOF for this. I'm not sure + // if I'm doing this wrong. + error.InputOutput => break, + else => return err, + } + }; + + try output.appendSlice(alloc, buf[0..n]); + + // Max total size is buf.len. We can do better here by trimming + // the front and continuing reading but we choose to just exit. + if (output.items.len > buf.len) break; + } + + break :output try output.toOwnedSlice(alloc); + }; + + // Shell and home are the last two entries + var it = std.mem.splitBackwardsScalar(u8, std.mem.trimRight(u8, output, " \r\n"), ':'); + result.shell = if (it.next()) |v| try alloc.dupeZ(u8, v) else null; + result.home = if (it.next()) |v| try alloc.dupeZ(u8, v) else null; + return result; + } + + if (pw.pw_shell) |ptr| { + const source = std.mem.sliceTo(ptr, 0); + const value = try alloc.dupeZ(u8, source); + result.shell = value; + } + + if (pw.pw_dir) |ptr| { + const source = std.mem.sliceTo(ptr, 0); + const value = try alloc.dupeZ(u8, source); + result.home = value; + } + + if (pw.pw_name) |ptr| { + const source = std.mem.sliceTo(ptr, 0); + const value = try alloc.dupeZ(u8, source); + result.name = value; + } + + return result; +} + +test { + if (builtin.os.tag == .windows) return error.SkipZigTest; + const testing = std.testing; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + // We should be able to get an entry + const entry = try get(alloc); + try testing.expect(entry.shell != null); + try testing.expect(entry.home != null); +} |
