ghostty/src/os/passwd.zig
Yorick Peterse 50f7632d81 Fix building with -Dflatpak=true
While running a Ghostty instance built with this option currently
crashes under Flatpak, at least it ensures we're able to build it again.
2024-12-27 16:27:19 +01:00

161 lines
5.3 KiB
Zig

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.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: ?[]const u8 = null,
home: ?[]const u8 = null,
name: ?[]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.splitBackwards(u8, std.mem.trimRight(u8, output, " \r\n"), ":");
result.shell = it.next() orelse null;
result.home = it.next() orelse null;
return result;
}
if (pw.pw_shell) |ptr| {
const source = std.mem.sliceTo(ptr, 0);
const sh = try alloc.alloc(u8, source.len);
@memcpy(sh, source);
result.shell = sh;
}
if (pw.pw_dir) |ptr| {
const source = std.mem.sliceTo(ptr, 0);
const dir = try alloc.alloc(u8, source.len);
@memcpy(dir, source);
result.home = dir;
}
if (pw.pw_name) |ptr| {
const source = std.mem.sliceTo(ptr, 0);
const name = try alloc.alloc(u8, source.len);
@memcpy(name, source);
result.name = name;
}
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);
}