aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/os/passwd.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/os/passwd.zig
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (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.zig157
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);
+}