diff --git a/src/os/flatpak.zig b/src/os/flatpak.zig index 58141baf3..f41ea22de 100644 --- a/src/os/flatpak.zig +++ b/src/os/flatpak.zig @@ -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); diff --git a/src/passwd.zig b/src/passwd.zig index 8e834e20c..f334341f5 100644 --- a/src/passwd.zig +++ b/src/passwd.zig @@ -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| {