diff --git a/src/DB.zig b/src/DB.zig index 2138790..bc254e7 100644 --- a/src/DB.zig +++ b/src/DB.zig @@ -388,15 +388,18 @@ fn pushStr(str: []const u8, buf: []u8, offset: *usize) [*:0]const u8 { return buf[start .. start + str.len :0].ptr; } -pub fn writeUser(self: *const DB, user: PackedUser, buf: []u8) error{BufferTooSmall}!CUser { - const shell_reader = ShellReader{ +pub fn shellReader(self: *const DB) ShellReader { + return ShellReader{ .index = self.shell_index, .blob = self.shell_blob, }; +} + +pub fn writeUser(self: *const DB, user: PackedUser, buf: []u8) error{BufferTooSmall}!CUser { const name = user.name(); const gecos = user.gecos(); const home = user.home(); - const shell = user.shell(shell_reader); + const shell = user.shell(self.shellReader()); const strlen = name.len + 1 + @@ -421,7 +424,7 @@ pub fn writeUser(self: *const DB, user: PackedUser, buf: []u8) error{BufferTooSm }; } -pub fn getUser(self: *const DB, name: []const u8) ?PackedUser { +pub fn getUserByName(self: *const DB, name: []const u8) ?PackedUser { const idx = bdz.search(self.bdz_username, name); // bdz may return a hash that's bigger than the number of users if (idx >= self.header.num_users) return null; @@ -434,18 +437,23 @@ pub fn getUser(self: *const DB, name: []const u8) ?PackedUser { // get a CUser entry by name. pub fn getpwnam(self: *const DB, name: []const u8, buf: []u8) error{BufferTooSmall}!?CUser { - const user = self.getUser(name) orelse return null; + const user = self.getUserByName(name) orelse return null; return try self.writeUser(user, buf); } -// get a CUser entry by uid. -pub fn getpwuid(self: *const DB, uid: u32, buf: []u8) error{BufferTooSmall}!?CUser { +pub fn getUserByUid(self: *const DB, uid: u32) ?PackedUser { const idx = bdz.search_u32(self.bdz_uid, uid); if (idx >= self.header.num_users) return null; const offset = self.idx_uid2user[idx]; const nbits = PackedUser.alignment_bits; const user = PackedUser.fromBytes(self.users[offset << nbits ..]).user; if (uid != user.uid()) return null; + return user; +} + +// get a CUser entry by uid. +pub fn getpwuid(self: *const DB, uid: u32, buf: []u8) error{BufferTooSmall}!?CUser { + const user = self.getUserByUid(uid) orelse return null; return try self.writeUser(user, buf); } diff --git a/src/PackedUser.zig b/src/PackedUser.zig index 5d3aa18..a3cb046 100644 --- a/src/PackedUser.zig +++ b/src/PackedUser.zig @@ -205,6 +205,18 @@ pub fn shell(self: PackedUser, shell_reader: ShellReader) []const u8 { return shell_reader.get(self.inner.shell_len_or_idx); } +// returns a User representation of the PackedUser. +pub fn toUser(self: *const PackedUser, shell_reader: ShellReader) User { + return User{ + .uid = self.uid(), + .gid = self.gid(), + .name = self.name(), + .gecos = self.gecos(), + .home = self.home(), + .shell = self.shell(shell_reader), + }; +} + pub const max_str_len = math.maxInt(fieldInfo(Inner, .shell_len_or_idx).field_type) + 1 + math.maxInt(fieldInfo(Inner, .home_len).field_type) + 1 + diff --git a/src/libnss.zig b/src/libnss.zig index 238510d..90670df 100644 --- a/src/libnss.zig +++ b/src/libnss.zig @@ -266,13 +266,8 @@ fn setpwent(state: *State) void { state.getpwent_iterator_mu.lock(); defer state.getpwent_iterator_mu.unlock(); - state.getpwent_iterator = PackedUser.iterator( - state.file.db.users, - ShellReader{ - .index = state.file.db.shell_index, - .blob = state.file.db.shell_blob, - }, - ); + const db = state.file.db; + state.getpwent_iterator = PackedUser.iterator(db.users, db.shellReader()); } export fn _nss_turbo_endpwent() void { @@ -425,7 +420,7 @@ fn initgroups_dyn( errnop: *c_int, ) c.enum_nss_status { const db = state.file.db; - const user = db.getUser(mem.sliceTo(user_name, 0)) orelse { + const user = db.getUserByName(mem.sliceTo(user_name, 0)) orelse { errnop.* = @enumToInt(os.E.NOENT); return c.NSS_STATUS_NOTFOUND; }; diff --git a/src/test_all.zig b/src/test_all.zig index a8e7dd0..28bd44e 100644 --- a/src/test_all.zig +++ b/src/test_all.zig @@ -16,6 +16,7 @@ test "turbonss test suite" { _ = @import("validate.zig"); // main + _ = @import("turbo-getent.zig"); _ = @import("turbo-unix2db.zig"); _ = @import("turbo-analyze.zig"); } diff --git a/src/turbo-analyze.zig b/src/turbo-analyze.zig index 0f01694..b527e4d 100644 --- a/src/turbo-analyze.zig +++ b/src/turbo-analyze.zig @@ -12,7 +12,6 @@ const Allocator = std.mem.Allocator; const BoundedArray = std.BoundedArray; const flags = @import("flags.zig"); - const DB = @import("DB.zig"); const File = @import("File.zig"); const PackedUser = @import("PackedUser.zig"); diff --git a/src/turbo-getent.zig b/src/turbo-getent.zig index b3da3f4..4cc8319 100644 --- a/src/turbo-getent.zig +++ b/src/turbo-getent.zig @@ -1,9 +1,16 @@ const std = @import("std"); const io = std.io; const os = std.os; +const mem = std.mem; const fmt = std.fmt; +const ArrayList = std.ArrayList; const flags = @import("flags.zig"); +const DB = @import("DB.zig"); +const File = @import("File.zig"); +const PackedUser = @import("PackedUser.zig"); + +const Mode = enum { group, passwd }; const usage = \\usage: turbo-getent [OPTION]... group|passwd [key...] @@ -16,6 +23,13 @@ const usage = \\entries that match the supplied keys will be displayed. If no key is \\provided, all entries will be displayed. \\ + \\Exit codes: + \\ + \\ 0 command completed successfully + \\ 1 invalid arguments or db could not be opened + \\ 2 one or more supplied key could not be found in the database + \\ 3 output error + \\ ; pub fn main() !void { @@ -36,6 +50,7 @@ fn execute( ) u8 { const myflags = flags.parse(argv, &[_]flags.Flag{ .{ .name = "-h", .kind = .boolean }, + .{ .name = "--db", .kind = .arg }, }) catch { stderr.writeAll(usage) catch {}; return 1; @@ -46,5 +61,110 @@ fn execute( return 0; } + if (myflags.args.len == 0) { + stderr.writeAll(usage) catch {}; + return 1; + } + + const arg0 = mem.span(myflags.args[0]); + const mode: Mode = blk: { + if (mem.eql(u8, arg0, "passwd")) { + break :blk .passwd; + } else if (mem.eql(u8, arg0, "group")) { + break :blk .group; + } else { + stderr.print("bad argument {s}: expected passwd or group\n", .{ + myflags.args[0], + }) catch return 3; + return 1; + } + }; + + const db_file = myflags.argFlag("--db") orelse "/etc/turbonss/db.turbo"; + var file = File.open(db_file) catch |err| { + stderr.print( + "ERROR {s}: file '{s}' is corrupted or cannot be read\n", + .{ @errorName(err), db_file }, + ) catch return 3; + return 1; + }; + defer file.close(); + + return switch (mode) { + .passwd => passwd(stdout, &file.db, myflags.args[1..]), + .group => group(stdout, &file.db, myflags.args[1..]), + }; +} + +fn passwd(stdout: anytype, db: *const DB, keys: []const [*:0]const u8) u8 { + var some_notfound = false; + + if (keys.len == 0) + return passwd_all(stdout, db); + + const shell_reader = db.shellReader(); + + for (keys) |key| { + const keyZ = mem.span(key); + const maybe_packed_user = if (fmt.parseUnsigned(u32, keyZ, 10)) |uid| + db.getUserByUid(uid) + else |_| + db.getUserByName(keyZ); + + const packed_user = maybe_packed_user orelse { + some_notfound = true; + continue; + }; + + const line = packed_user.toUser(shell_reader).toLine(); + stdout.writeAll(line.constSlice()) catch return 3; + } + + return if (some_notfound) 2 else 0; +} + +fn passwd_all(stdout: anytype, db: *const DB) u8 { + var it = PackedUser.iterator(db.users, db.shellReader()); + while (it.next()) |packed_user| { + const line = packed_user.toUser(db.shellReader()).toLine(); + stdout.writeAll(line.constSlice()) catch return 3; + } + return 0; +} + +const testing = std.testing; + +test "passwd and passwd_all" { + var tf = try File.TestDB.init(testing.allocator); + defer tf.deinit(); + var stdout = ArrayList(u8).init(testing.allocator); + defer stdout.deinit(); + var stderr = ArrayList(u8).init(testing.allocator); + defer stderr.deinit(); + + const args = &[_][*:0]const u8{ + "--db", + tf.path, + "passwd", + "root", + "doesnotexist", + "vidmantas", + "0", + "1", + }; + const got = execute(stdout.writer(), stderr.writer(), args); + try testing.expectEqual(got, 2); + const want_root = "root:x:0:0::/root:/bin/bash\n"; + try testing.expectEqualStrings(stdout.items[0..want_root.len], want_root); + const want_vidmantas = "vidmantas:x:128:128:Vidmantas Kaminskas:/home/vidmantas:/bin/bash\n"; + var offset: usize = want_root.len + want_vidmantas.len; + try testing.expectEqualStrings(stdout.items[want_root.len..offset], want_vidmantas); + try testing.expectEqualStrings(stdout.items[offset..], want_root); +} + +fn group(stdout: anytype, db: *const DB, keys: []const [*:0]const u8) u8 { + _ = db; + _ = stdout; + _ = keys; return 0; }