const std = @import("std"); const os = std.os; const mem = std.mem; const fmt = std.fmt; const meta = std.meta; const maxInt = std.math.maxInt; const Allocator = mem.Allocator; const ArrayList = std.ArrayList; const Group = @This(); gid: u32, name: []const u8, members: []align(1) const []const u8, // storage of name, members and members strings. for no particular reason. // Everything (name, members, member strings) could be allocated separately // (one extreme) and only once (another extreme). For some reason I chose the // right extreme, but it doesn't have to be this way. _buf: []const u8, pub fn init( allocator: Allocator, gid: u32, name: []const u8, members: []align(1) const []const u8, ) error{OutOfMemory}!Group { const buf_len = @sizeOf([]const u8) * members.len + name.len + blk: { var sum: usize = 0; for (members) |member| sum += member.len; break :blk @intCast(u32, sum); }; var buf = try allocator.alloc(u8, buf_len); errdefer allocator.free(buf); var ptr_end = @sizeOf([]const u8) * members.len; var members_ptr = mem.bytesAsSlice([]const u8, buf[0..ptr_end]); var offset: usize = ptr_end; for (members) |member, i| { mem.copy(u8, buf[offset..], member); members_ptr[i] = buf[offset .. offset + member.len]; offset += member.len; } mem.copy(u8, buf[offset..], name); return Group{ .gid = gid, .name = buf[offset .. offset + name.len], .members = members_ptr, ._buf = buf, }; } // This could be made more efficient, but clone() is never in the hot path. pub fn clone(self: *const Group, allocator: Allocator) error{OutOfMemory}!Group { return init(allocator, self.gid, self.name, self.members); } pub fn deinit(self: *Group, allocator: Allocator) void { allocator.free(self._buf); self.* = undefined; } pub const FromReaderError = error{ InvalidRecord, OutOfMemory } || os.ReadError; pub fn fromReader(allocator: Allocator, reader: anytype) FromReaderError![]Group { var groups = ArrayList(Group).init(allocator); errdefer { for (groups.items) |*group| group.deinit(allocator); groups.deinit(); } var member_ptrs = ArrayList([]const u8).init(allocator); defer member_ptrs.deinit(); var line = ArrayList(u8).init(allocator); defer line.deinit(); while (true) { // TODO: catch and interpret different errors const max = std.math.maxInt(u32); reader.readUntilDelimiterArrayList(&line, '\n', max) catch |err| switch (err) { error.EndOfStream => break, error.StreamTooLong => unreachable, else => |e| return e, }; var it = mem.split(u8, line.items, ":"); const name = it.next() orelse return error.InvalidRecord; _ = it.next() orelse return error.InvalidRecord; // password const gids = it.next() orelse return error.InvalidRecord; const members_commas = it.next() orelse return error.InvalidRecord; if (it.next() != null) return error.InvalidRecord; const gid = fmt.parseInt(u32, gids, 10) catch return error.InvalidRecord; var members_it = mem.split(u8, members_commas, ","); while (members_it.next()) |member| try member_ptrs.append(member); try groups.append(try init(allocator, gid, name, member_ptrs.items)); member_ptrs.shrinkRetainingCapacity(0); } return groups.toOwnedSlice(); } pub fn writeTo(self: *const Group, writer: anytype) os.WriteError!void { try writer.print("{s}:x:{d}:", .{ self.name, self.gid }); if (self.members.len == 0) return; try writer.writeAll(self.members[0]); for (self.members[1..]) |member| try writer.print(",{s}", .{member}); try writer.writeByte('\n'); } // suggested buffer size in bytes if all strings were zero-terminated // (for CGroup). pub fn strlenZ(self: *const Group) usize { var count: usize = 0; for (self.members) |member| count += member.len + 1; count += ptr_size * (self.members.len + 1); count += self.name.len + 1; return count; } // Name type should be 0-terminated, members should be null-terminated, but // sentinel is not part of the type (but is always there). Adding sentinel // crashes zig compiler in pre-0.10. https://github.com/ziglang/zig/issues/7517 pub const CGroup = extern struct { gid: u32, // should be [*:0]const u8 name: [*]const u8, // Should be [*:null]align(1) const ?[*:0]const u8 members: [*]align(1) const ?[*:0]const u8, }; // size of the pointer to a single member. pub const ptr_size = @sizeOf(meta.Child(meta.fieldInfo(CGroup, .members).field_type)); const testing = std.testing; test "Group.clone" { // TODO: how to do this on stack? var member1 = mem.sliceTo(try testing.allocator.dupeZ(u8, "member1"), 0); defer testing.allocator.free(member1); var group = try init(testing.allocator, 1, "foo", &[_][]const u8{ member1, "member2" }); defer group.deinit(testing.allocator); var cloned = try group.clone(testing.allocator); defer cloned.deinit(testing.allocator); group.gid = 2; group.name = "bar"; member1[0] = 'x'; try testing.expectEqual(cloned.gid, 1); try testing.expectEqualSlices(u8, cloned.name, "foo"); try testing.expectEqualSlices(u8, cloned.members[0], "member1"); }