passwd uses new FlatpakHostCommand

This commit is contained in:
Mitchell Hashimoto
2023-02-27 11:02:59 -08:00
parent f64d871847
commit 630374060d
2 changed files with 57 additions and 44 deletions

View File

@ -19,6 +19,10 @@ pub fn isFlatpak() bool {
/// This makes it easy for the command to behave synchronously similar to
/// std.process.ChildProcess.
///
/// There are lots of chances for low-hanging improvements here (automatic
/// pipes, /dev/null, etc.) but this was purpose built for my needs so
/// it doesn't have all of those.
///
/// Requires GIO, GLib to be available and linked.
pub const FlatpakHostCommand = struct {
const Allocator = std.mem.Allocator;
@ -78,16 +82,13 @@ pub const FlatpakHostCommand = struct {
},
};
/// Execute the command and wait for it to finish. This will automatically
/// read all the data from the provided stdout/stderr fds and return them
/// in the result.
///
/// This runs the exec in a dedicated thread with a dedicated GLib
/// event loop so that it can run synchronously.
pub fn exec(self: *FlatpakHostCommand, alloc: Allocator) !void {
const thread = try std.Thread.spawn(.{}, threadMain, .{ self, alloc });
thread.join();
}
/// Errors that are possible from us.
pub const Error = error{
FlatpakMustBeStarted,
FlatpakSpawnFail,
FlatpakSetupFail,
FlatpakRPCFail,
};
/// Spawn the command. This will start the host command. On return,
/// the pid will be available. This must only be called with the
@ -105,7 +106,7 @@ pub const FlatpakHostCommand = struct {
return switch (self.state) {
.init => unreachable,
.err => error.FlatpakSpawnFail,
.err => Error.FlatpakSpawnFail,
.started => |v| v.pid,
.exited => |v| v.pid,
};
@ -119,8 +120,8 @@ pub const FlatpakHostCommand = struct {
while (true) {
switch (self.state) {
.init => return error.FlatpakCommandNotStarted,
.err => return error.FlatpakSpawnFail,
.init => return Error.FlatpakMustBeStarted,
.err => return Error.FlatpakSpawnFail,
.started => {},
.exited => |v| {
self.state = .{ .init = {} };
@ -187,15 +188,15 @@ pub const FlatpakHostCommand = struct {
defer c.g_object_unref(fd_list);
if (c.g_unix_fd_list_append(fd_list, self.stdin, &err) < 0) {
log.warn("error adding fd: {s}", .{err.*.message});
return error.FlatpakFdFailed;
return Error.FlatpakSetupFail;
}
if (c.g_unix_fd_list_append(fd_list, self.stdout, &err) < 0) {
log.warn("error adding fd: {s}", .{err.*.message});
return error.FlatpakFdFailed;
return Error.FlatpakSetupFail;
}
if (c.g_unix_fd_list_append(fd_list, self.stderr, &err) < 0) {
log.warn("error adding fd: {s}", .{err.*.message});
return error.FlatpakFdFailed;
return Error.FlatpakSetupFail;
}
// Build our arguments for the file descriptors.
@ -279,7 +280,7 @@ pub const FlatpakHostCommand = struct {
&err,
) orelse {
log.warn("Flatpak.HostCommand failed: {s}", .{err.*.message});
return error.FlatpakHostCommandFailed;
return Error.FlatpakRPCFail;
};
defer c.g_variant_unref(reply);
@ -335,6 +336,7 @@ pub const FlatpakHostCommand = struct {
.status = std.math.cast(u8, exit_status) orelse 255,
},
});
log.debug("HostCommand exited pid={} status={}", .{ pid, exit_status });
// We're done now, so we can unsubscribe
c.g_dbus_connection_signal_unsubscribe(bus.?, state.subscription);

View File

@ -49,13 +49,12 @@ pub fn get(alloc: Allocator) !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.
//
// 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.
if (internal_os.isFlatpak()) {
log.info("flatpak detected, will use host-spawn 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");
var pty = try Pty.open(.{});
defer pty.deinit();
@ -76,30 +75,42 @@ pub fn get(alloc: Allocator) !Entry {
};
_ = try cmd.spawn(alloc);
_ = try cmd.wait();
if (true) @panic("END");
const exec = try std.ChildProcess.exec(.{
.allocator = alloc,
.argv = &[_][]const u8{
"/app/bin/host-spawn",
"-pty",
"/bin/sh",
"-l",
"-c",
try std.fmt.allocPrint(
alloc,
"getent passwd {s}",
.{std.mem.sliceTo(pw.pw_name, 0)},
),
},
});
if (exec.term == .Exited) {
// Shell and home are the last two entries
var it = std.mem.splitBackwards(u8, std.mem.trimRight(u8, exec.stdout, " \r\n"), ":");
result.shell = it.next() orelse null;
result.home = it.next() orelse null;
return result;
}
// 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.
_ = std.os.close(pty.slave);
// Read all of our output
const output = output: {
var output: std.ArrayListUnmanaged(u8) = .{};
while (true) {
const n = std.os.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);
};
log.warn("DONE output={s}", .{output});
// 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| {