const std = @import("std"); const pad = @import("padding.zig"); const validate = @import("validate.zig"); const compress = @import("compress.zig"); const InvalidRecord = validate.InvalidRecord; const mem = std.mem; const Allocator = mem.Allocator; const ArrayList = std.ArrayList; const BufSet = std.BufSet; pub const Group = struct { gid: u32, name: []const u8, members: BufSet, pub fn clone(self: *const Group, allocator: Allocator) Allocator.Error!Group { var name = try allocator.dupe(u8, self.name); return Group{ .gid = self.gid, .name = name, .members = try self.members.cloneWithAllocator(allocator), }; } pub fn deinit(self: *Group, allocator: Allocator) void { allocator.free(self.name); self.members.deinit(); self.* = undefined; } }; pub const GroupStored = struct { gid: u32, name: []const u8, members_offset: u64, }; pub const PackedGroup = struct { pub const alignment_bits = 3; const Inner = packed struct { gid: u32, padding: u3 = 0, groupname_len: u5, pub fn groupnameLen(self: *const Inner) usize { return @as(usize, self.groupname_len) + 1; } }; inner: *const Inner, groupdata: []const u8, members_offset: u64, pub const Entry = struct { group: PackedGroup, next: ?[]const u8, }; pub fn fromBytes(bytes: []const u8) error{Overflow}!Entry { const inner = mem.bytesAsValue(Inner, bytes[0..@sizeOf(Inner)]); const start_blob = @sizeOf(Inner); const end_strings = @sizeOf(Inner) + inner.groupnameLen(); const members_offset = try compress.uvarint(bytes[end_strings..]); const end_blob = end_strings + members_offset.bytes_read; const next_start = pad.roundUp(usize, alignment_bits, end_blob); var next: ?[]const u8 = null; if (next_start < bytes.len) next = bytes[next_start..]; return Entry{ .group = PackedGroup{ .inner = inner, .groupdata = bytes[start_blob..end_strings], .members_offset = members_offset.value, }, .next = next, }; } fn validateUtf8(s: []const u8) InvalidRecord!void { if (!std.unicode.utf8ValidateSlice(s)) return error.InvalidRecord; } pub const Iterator = struct { section: ?[]const u8, pub fn next(it: *Iterator) error{Overflow}!?PackedGroup { if (it.section) |section| { const entry = try fromBytes(section); it.section = entry.next; return entry.group; } return null; } }; pub fn iterator(section: []const u8) Iterator { return Iterator{ .section = section }; } pub fn gid(self: *const PackedGroup) u32 { return self.inner.gid; } pub fn membersOffset(self: *const PackedGroup) u64 { return self.members_offset; } pub fn name(self: *const PackedGroup) []const u8 { return self.groupdata; } const packErr = validate.InvalidRecord || Allocator.Error || error{Overflow}; pub fn packTo( arr: *ArrayList(u8), group: GroupStored, ) packErr!void { std.debug.assert(arr.items.len & 7 == 0); try validate.utf8(group.name); const len = try validate.downCast(u5, group.name.len - 1); const inner = Inner{ .gid = group.gid, .groupname_len = len }; try arr.*.appendSlice(mem.asBytes(&inner)); try arr.*.appendSlice(group.name); try compress.appendUvarint(arr, group.members_offset); } }; const testing = std.testing; // someMembers constructs a bufset from an allocator and a list of strings. pub fn someMembers( allocator: Allocator, members: []const []const u8, ) Allocator.Error!BufSet { var bufset = BufSet.init(allocator); errdefer bufset.deinit(); for (members) |member| try bufset.insert(member); return bufset; } test "PackedGroup alignment" { try testing.expectEqual(@sizeOf(PackedGroup) * 8, @bitSizeOf(PackedGroup)); } test "construct PackedGroups" { var buf = ArrayList(u8).init(testing.allocator); defer buf.deinit(); const groups = [_]GroupStored{ GroupStored{ .gid = 1000, .name = "sudo", .members_offset = 1, }, GroupStored{ .gid = std.math.maxInt(u32), .name = "Name" ** 8, // 32 .members_offset = std.math.maxInt(u64), }, }; for (groups) |group| { try PackedGroup.packTo(&buf, group); try pad.arrayList(&buf, PackedGroup.alignment_bits); } var i: u29 = 0; var it = PackedGroup.iterator(buf.items); while (try it.next()) |group| : (i += 1) { try testing.expectEqual(groups[i].gid, group.gid()); try testing.expectEqualStrings(groups[i].name, group.name()); try testing.expectEqual(groups[i].members_offset, group.membersOffset()); } try testing.expectEqual(groups.len, i); } test "Group.clone" { var allocator = testing.allocator; var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); var members = BufSet.init(allocator); try members.insert("member1"); try members.insert("member2"); defer members.deinit(); var cloned = try members.cloneWithAllocator(arena.allocator()); cloned.remove("member1"); try cloned.insert("member4"); try testing.expect(members.contains("member1")); try testing.expect(!members.contains("member4")); try testing.expect(!cloned.contains("member1")); try testing.expect(cloned.contains("member4")); }