From 02f5aa702d5e6e86aac63192f49503c5c818e31e Mon Sep 17 00:00:00 2001 From: AnErrupTion Date: Sat, 18 Oct 2025 00:28:12 +0200 Subject: [PATCH] Implement /etc/login.defs in interop, TODO for FreeBSD We should be able to parse the "minuid" and "maxuid" values in /etc/rc.conf to get the UID range of the system, with default values of 1000 to 32000 (as they don't seem to be present by default). Signed-off-by: AnErrupTion --- res/config.ini | 3 ++- src/interop.zig | 56 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 42 +------------------------------------ 3 files changed, 59 insertions(+), 42 deletions(-) diff --git a/res/config.ini b/res/config.ini index 17503fe..cb229f7 100644 --- a/res/config.ini +++ b/res/config.ini @@ -211,7 +211,8 @@ lang = en # You can also set environment variables in there, they'll persist until logout login_cmd = null -# Path for login.defs file (used for listing all local users on the system) +# Path for login.defs file (used for listing all local users on the system on +# Linux) login_defs_path = /etc/login.defs # Command executed when logging out diff --git a/src/interop.zig b/src/interop.zig index 2cdd746..af7a571 100644 --- a/src/interop.zig +++ b/src/interop.zig @@ -1,5 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); +const UidRange = @import("UidRange.zig"); pub const termbox = @import("termbox2"); @@ -155,6 +156,46 @@ fn PlatformStruct() type { return error.NoTtyFound; } + // This is very bad parsing, but we only need to get 2 values.. + // and the format of the file seems to be standard? So this should + // be fine... + pub fn getUserIdRange(allocator: std.mem.Allocator, file_path: []const u8) !UidRange { + const login_defs_file = try std.fs.cwd().openFile(file_path, .{}); + defer login_defs_file.close(); + + const login_defs_buffer = try login_defs_file.readToEndAlloc(allocator, std.math.maxInt(u16)); + defer allocator.free(login_defs_buffer); + + var iterator = std.mem.splitScalar(u8, login_defs_buffer, '\n'); + var uid_range = UidRange{}; + + while (iterator.next()) |line| { + const trimmed_line = std.mem.trim(u8, line, " \n\r\t"); + + if (std.mem.startsWith(u8, trimmed_line, "UID_MIN")) { + uid_range.uid_min = try parseValue(std.posix.uid_t, "UID_MIN", trimmed_line); + } else if (std.mem.startsWith(u8, trimmed_line, "UID_MAX")) { + uid_range.uid_max = try parseValue(std.posix.uid_t, "UID_MAX", trimmed_line); + } + } + + return uid_range; + } + + fn parseValue(comptime T: type, name: []const u8, buffer: []const u8) !T { + var iterator = std.mem.splitAny(u8, buffer, " \t"); + var maybe_value: ?T = null; + + while (iterator.next()) |slice| { + // Skip the slice if it's empty (whitespace) or is the name of the + // property (e.g. UID_MIN or UID_MAX) + if (slice.len == 0 or std.mem.eql(u8, slice, name)) continue; + maybe_value = std.fmt.parseInt(T, slice, 10) catch continue; + } + + return maybe_value orelse error.ValueNotFound; + } + fn readBuffer(reader: *std.Io.Reader, buffer: []u8) !usize { var bytes_read: usize = 0; var byte: u8 = try reader.takeByte(); @@ -198,6 +239,15 @@ fn PlatformStruct() type { pub fn getActiveTtyImpl(_: std.mem.Allocator) !u8 { return error.FeatureUnimplemented; } + + pub fn getUserIdRange(_: std.mem.Allocator, _: []const u8) !UidRange { + return .{ + // Hardcoded default values chosen from + // /usr/src/usr.sbin/pw/pw_conf.c + .uid_min = 1000, + .uid_max = 32000, + }; + } }, else => @compileError("Unsupported target: " ++ builtin.os.tag), }; @@ -330,3 +380,9 @@ pub fn getUsernameEntry(username: [:0]const u8) ?UsernameEntry { pub fn closePasswordDatabase() void { pwd.endpwent(); } + +// This is very bad parsing, but we only need to get 2 values... and the format +// of the file doesn't seem to be standard? So this should be fine... +pub fn getUserIdRange(allocator: std.mem.Allocator, file_path: []const u8) !UidRange { + return platform_struct.getUserIdRange(allocator, file_path); +} diff --git a/src/main.zig b/src/main.zig index 5962aac..b69c669 100644 --- a/src/main.zig +++ b/src/main.zig @@ -25,7 +25,6 @@ const OldSave = @import("config/OldSave.zig"); const SavedUsers = @import("config/SavedUsers.zig"); const migrator = @import("config/migrator.zig"); const SharedError = @import("SharedError.zig"); -const UidRange = @import("UidRange.zig"); const StringList = std.ArrayListUnmanaged([]const u8); const Ini = ini.Ini; @@ -1151,7 +1150,7 @@ fn crawl(session: *Session, lang: Lang, path: []const u8, display_server: Displa } fn getAllUsernames(allocator: std.mem.Allocator, login_defs_path: []const u8) !StringList { - const uid_range = try getUserIdRange(allocator, login_defs_path); + const uid_range = try interop.getUserIdRange(allocator, login_defs_path); var usernames: StringList = .empty; var maybe_entry = interop.getNextUsernameEntry(); @@ -1171,45 +1170,6 @@ fn getAllUsernames(allocator: std.mem.Allocator, login_defs_path: []const u8) !S return usernames; } -// This is very bad parsing, but we only need to get 2 values... and the format -// of the file doesn't seem to be standard? So this should be fine... -fn getUserIdRange(allocator: std.mem.Allocator, login_defs_path: []const u8) !UidRange { - const login_defs_file = try std.fs.cwd().openFile(login_defs_path, .{}); - defer login_defs_file.close(); - - const login_defs_buffer = try login_defs_file.readToEndAlloc(allocator, std.math.maxInt(u16)); - defer allocator.free(login_defs_buffer); - - var iterator = std.mem.splitScalar(u8, login_defs_buffer, '\n'); - var uid_range = UidRange{}; - - while (iterator.next()) |line| { - const trimmed_line = std.mem.trim(u8, line, " \n\r\t"); - - if (std.mem.startsWith(u8, trimmed_line, "UID_MIN")) { - uid_range.uid_min = try parseValue(std.posix.uid_t, "UID_MIN", trimmed_line); - } else if (std.mem.startsWith(u8, trimmed_line, "UID_MAX")) { - uid_range.uid_max = try parseValue(std.posix.uid_t, "UID_MAX", trimmed_line); - } - } - - return uid_range; -} - -fn parseValue(comptime T: type, name: []const u8, buffer: []const u8) !T { - var iterator = std.mem.splitAny(u8, buffer, " \t"); - var maybe_value: ?T = null; - - while (iterator.next()) |slice| { - // Skip the slice if it's empty (whitespace) or is the name of the - // property (e.g. UID_MIN or UID_MAX) - if (slice.len == 0 or std.mem.eql(u8, slice, name)) continue; - maybe_value = std.fmt.parseInt(T, slice, 10) catch continue; - } - - return maybe_value orelse error.ValueNotFound; -} - fn adjustBrightness(allocator: std.mem.Allocator, cmd: []const u8) !void { var brightness = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", cmd }, allocator); brightness.stdout_behavior = .Ignore;