diff --git a/src/user.zig b/src/user.zig index fcaa4f4..39a7b1b 100644 --- a/src/user.zig +++ b/src/user.zig @@ -4,10 +4,11 @@ const pad = @import("padding.zig"); const assert = std.debug.assert; const mem = std.mem; const Allocator = mem.Allocator; +const ArrayList = std.ArrayList; -// ShellIndexFn is a function prototype that, given a shell's index (in -// global shell section), will return a shell string. Matches ShelReader.get. -const ShellIndexFn = fn (u6) []const u8; +// Idx2ShellProto is a function prototype that, given a shell's index (in +// global shell section), will return a shell string. Matches ShellReader.get. +const Idx2ShellProto = fn (u6) []const u8; // User is a convenient public struct for record construction and // serialization. Iterator can help retrieve these records. @@ -28,19 +29,19 @@ fn packedUser(immutable: bool) type { const Self = @This(); const AlignmentBits = 3; - const shellIndexFn = fn ([]const u8) ?u6; + const shell2idxProto = fn ([]const u8) ?u6; const InnerSize = @divExact(@bitSizeOf(Inner), 8); const Inner = packed struct { uid: u32, gid: u32, - additional_gids_offset: u29, shell_here: bool, shell_len_or_idx: u6, home_len: u6, name_is_a_suffix: bool, name_len: u5, gecos_len: u8, + additional_gids_offset: u29, fn homeLen(self: *const Inner) usize { return @as(u32, self.home_len) + 1; @@ -107,7 +108,7 @@ fn packedUser(immutable: bool) type { }; pub fn fromBytes(bytes: Bytes) Entry { - const inner = std.mem.bytesAsValue( + const inner = mem.bytesAsValue( Inner, // Should use InnerSize instead of sizeOf, see // https://github.com/ziglang/zig/issues/10958 @@ -154,7 +155,7 @@ fn packedUser(immutable: bool) type { pub fn packTo( arr: *ArrayList(u8), user: User, - idxFn: shellIndexFn, + idxFn: shell2idxProto, ) packErr!void { // function arguments are consts. We need to mutate the underlying // slice, so passing it via pointer instead. @@ -219,6 +220,10 @@ fn packedUser(immutable: bool) type { return self.inner.gid; } + pub fn additionalGidsOffset(self: Self) u29 { + return self.inner.additional_gids_offset; + } + pub fn home(self: Self) []const u8 { return self.userdata[0..self.inner.homeLen()]; } @@ -235,7 +240,7 @@ fn packedUser(immutable: bool) type { return self.userdata[gecos_pos .. gecos_pos + gecos_len]; } - pub fn shell(self: Self, idxFn: ShellIndexFn) []const u8 { + pub fn shell(self: Self, idxFn: Idx2ShellProto) []const u8 { if (self.inner.shell_here) { const shell_pos = self.inner.maybeShellStart(); const shell_len = self.inner.shellLen(); @@ -244,9 +249,17 @@ fn packedUser(immutable: bool) type { return idxFn(self.inner.shell_len_or_idx); } + pub fn setAdditionalGidsOffset(self: Self, new: u29) void { + // TODO(motiejus) how to not declare function for const PackedUser at all? + if (immutable) { + @compileError("this function is available only for mutable PackedUsers"); + } + self.inner.additional_gids_offset = new; + } + pub const Iterator = struct { section: ?Bytes, - shellIndex: ShellIndexFn, + shellIndex: Idx2ShellProto, pub fn next(it: *Iterator) ?Self { if (it.section) |section| { @@ -258,7 +271,7 @@ fn packedUser(immutable: bool) type { } }; - pub fn iterator(section: Bytes, idxFn: ShellIndexFn) Iterator { + pub fn iterator(section: Bytes, idxFn: Idx2ShellProto) Iterator { return Iterator{ .section = section, .shellIndex = idxFn, @@ -268,7 +281,6 @@ fn packedUser(immutable: bool) type { } const testing = std.testing; -const ArrayList = std.ArrayList; test "PackedUser internal and external alignment" { // External padding (AlignmentBits) must be higher or equal to @@ -277,7 +289,7 @@ test "PackedUser internal and external alignment" { // cannot be converted from/to [@bitSizeOf(PackedUser)/8]u8; // asBytes/bytesAsValue use @sizeOf, which is larger. Now we are putting no // more than 1, but it probably could be higher. - try testing.expect(@sizeOf(PackedUserMut.Inner) * 8 - @bitSizeOf(PackedUserMut.Inner) <= 8); + try testing.expectEqual(8, @sizeOf(PackedUserConst.Inner) * 8 - @bitSizeOf(PackedUserConst.Inner)); } fn testShellIndex(shell: []const u8) ?u6 { @@ -331,21 +343,47 @@ test "construct PackedUser section" { .shell = "/", } }; for (users) |user| { - try buf.ensureUnusedCapacity(PackedUserMut.maxSize()); - try PackedUserMut.packTo(&buf, user, testShellIndex); + try PackedUserConst.packTo(&buf, user, testShellIndex); } - var it = PackedUserMut.iterator(buf.items, testShell); - var i: u32 = 0; - while (it.next()) |user| : (i += 1) { - try testing.expectEqual(users[i].uid, user.uid()); - try testing.expectEqual(users[i].gid, user.gid()); - try testing.expectEqualStrings(users[i].name, user.name()); - try testing.expectEqualStrings(users[i].gecos, user.gecos()); - try testing.expectEqualStrings(users[i].home, user.home()); - try testing.expectEqualStrings(users[i].shell, user.shell(testShell)); + var i: u29 = 0; + { + var it = PackedUserConst.iterator(buf.items, testShell); + i = 0; + while (it.next()) |user| : (i += 1) { + try testing.expectEqual(users[i].uid, user.uid()); + try testing.expectEqual(users[i].gid, user.gid()); + try testing.expectEqual( + @as(u29, std.math.maxInt(u29)), + user.additionalGidsOffset(), + ); + try testing.expectEqualStrings(users[i].name, user.name()); + try testing.expectEqualStrings(users[i].gecos, user.gecos()); + try testing.expectEqualStrings(users[i].home, user.home()); + try testing.expectEqualStrings(users[i].shell, user.shell(testShell)); + } + try testing.expectEqual(users.len, i); + } + + { + var it = PackedUserMut.iterator(buf.items, testShell); + i = 0; + while (it.next()) |user| : (i += 1) { + user.setAdditionalGidsOffset(i); + } + try testing.expectEqual(users.len, i); + } + + { + var it = PackedUserConst.iterator(buf.items, testShell); + i = 0; + while (it.next()) |user| : (i += 1) { + try testing.expectEqual(users[i].gid, user.gid()); + try testing.expectEqual(i, user.additionalGidsOffset()); + try testing.expectEqualStrings(users[i].name, user.name()); + } + try testing.expectEqual(users.len, i); } - try testing.expectEqual(users.len, i); } test "PackedUser.maxSize()" {