const std = @import("std"); const pad = @import("padding.zig"); const validate = @import("validate.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; } }; const GroupStored = struct { gid: u32, name: []const u8, members_offset: u32, }; const PackedGroup = struct { const alignmentBits = 3; const Inner = packed struct { gid: u32, members_offset: u32, groupname_len: u8, pub fn groupnameLen(self: *const Inner) usize { return self.groupname_len + 1; } }; inner: *const Inner, groupdata: []const u8, pub const Entry = struct { group: PackedGroup, next: ?[]const u8, }; pub fn fromBytes(bytes: []const u8) Entry { const inner = mem.bytesAsValue(Inner, bytes[0..@sizeOf(Inner)]); const endBlob = @sizeOf(Inner) + inner.groupnameLen(); const nextStart = pad.roundUp(usize, alignmentBits, endBlob); var next: ?[]const u8 = null; if (nextStart < bytes.len) next = bytes[nextStart..]; return Entry{ .group = PackedGroup{ .inner = inner, .groupdata = bytes[@sizeOf(Inner)..endBlob], }, .next = next, }; } const packErr = validate.InvalidRecord || Allocator.Error; 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) ?PackedGroup { if (it.section) |section| { const entry = 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) u32 { return self.inner.members_offset; } pub fn name(self: *const PackedGroup) []const u8 { return self.groupdata; } pub fn packTo( arr: *ArrayList(u8), group: GroupStored, ) packErr!void { const groupname_len = try validate.downCast(u5, group.name.len - 1); try validate.utf8(group.name); const inner = Inner{ .gid = group.gid, .members_offset = group.members_offset, .groupname_len = groupname_len, }; try arr.*.appendSlice(mem.asBytes(&inner)); try arr.*.appendSlice(group.name); try pad.arrayList(arr, alignmentBits); } }; 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); 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{ .{ .gid = 1000, .name = "sudo", .members_offset = 1, }, .{ .gid = std.math.maxInt(u32), .name = "Name" ** 8, // 32 .members_offset = std.math.maxInt(u32), }, }; for (groups) |group| { try PackedGroup.packTo(&buf, group); } var i: u29 = 0; var it = PackedGroup.iterator(buf.items); while (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")); }