diff --git a/src/user.zig b/src/user.zig index da41488..eb2af0c 100644 --- a/src/user.zig +++ b/src/user.zig @@ -35,46 +35,28 @@ pub const PackedUser = struct { name_len: u5, gecos_len: u8, - pub fn homeLen(self: *const PackedUser) usize { + fn homeLen(self: *const Inner) usize { return @as(u32, self.home_len) + 1; } - pub fn nameLen(self: *const PackedUser) usize { - return @as(u32, self.name_len) + 1; - } - - pub fn gecosLen(self: *const PackedUser) usize { - return self.gecos_len; - } - - pub fn shellLen(self: *const PackedUser) usize { - return @as(u32, self.shell_len_or_idx) + 1; - } - - // blobLength returns the length of the blob storing string values. - pub fn blobLength(self: *const Inner) usize { - var result: usize = self.homeLen(); - if (!self.name_is_a_suffix) { - result += self.nameLen(); - } - result += self.gecosLen(); - if (self.shell_here) { - result += self.shellLen(); - } - - return result; - } - - pub fn namePos(self: *const Inner) usize { + fn nameStart(self: *const Inner) usize { const name_len = self.nameLen(); if (self.name_is_a_suffix) { - return self.inner.homeLen() - name_len; + return self.homeLen() - name_len; } else { return self.homeLen(); } } - pub fn gecosPos(self: *const Inner) usize { + fn nameLen(self: *const Inner) usize { + return @as(u32, self.name_len) + 1; + } + + fn gecosLen(self: *const Inner) usize { + return self.gecos_len; + } + + fn gecosStart(self: *const Inner) usize { if (self.name_is_a_suffix) { return self.homeLen(); } else { @@ -82,27 +64,83 @@ pub const PackedUser = struct { } } - pub fn maybeShellPos(self: *const Inner) usize { + fn maybeShellStart(self: *const Inner) usize { assert(self.shell_here); - return self.gecosPos() + self.gecosLen(); + return self.gecosStart() + self.gecosLen(); + } + + fn shellLen(self: *const Inner) usize { + return @as(u32, self.shell_len_or_idx) + 1; + } + + // blobLength returns the length of the blob storing string values. + fn blobLength(self: *const Inner) usize { + var result: usize = self.homeLen() + self.gecosLen(); + if (!self.name_is_a_suffix) { + result += self.nameLen(); + } + if (self.shell_here) { + result += self.shellLen(); + } + + return result; } }; - inner: Inner, + inner: *const Inner, userdata: []const u8, - pub fn fromBytes(bytes: []const u8) PackedUser { + pub const Entry = struct { + user: PackedUser, + next: ?[]const u8, + }; + + pub fn fromBytes(bytes: []const u8) Entry { const inner = std.mem.bytesAsValue( - PackedUser, + Inner, // https://github.com/ziglang/zig/issues/10958 - bytes[0..@sizeOf(Inner)][0..@sizeOf(Inner)], + bytes[0..@sizeOf(Inner)], ); const startBlob = InnerSize; const endBlob = startBlob + inner.blobLength(); - return PackedUser{ - .inner = inner, - .userdata = bytes[startBlob..endBlob], + + const nextStart = pad.roundUp(usize, PackedUserAlignmentBits, endBlob); + var next: ?[]const u8 = null; + if (nextStart < bytes.len) { + next = bytes[nextStart..]; + } + + std.debug.print("\n", .{}); + std.debug.print("startBlob: {d}\n", .{startBlob}); + std.debug.print("endBlob: {d}\n", .{endBlob}); + std.debug.print("nextStart: {d}\n", .{nextStart}); + std.debug.print("len(userdata): {d}\n", .{bytes[startBlob..endBlob].len}); + std.debug.print("uid: {d}\n", .{inner.uid}); + std.debug.print("gid: {d}\n", .{inner.gid}); + std.debug.print("additional_gids_offset: {d}\n", .{inner.additional_gids_offset}); + std.debug.print("shell_here: {d}\n", .{inner.shell_here}); + std.debug.print("shell_len_or_idx: {d}\n", .{inner.shell_len_or_idx}); + std.debug.print("home_len: {d}\n", .{inner.home_len}); + std.debug.print("name_is_a_suffix: {d}\n", .{inner.name_is_a_suffix}); + std.debug.print("name_len: {d}\n", .{inner.name_len}); + std.debug.print("gecos_len: {d}\n", .{inner.gecos_len}); + std.debug.print("userdata: {s}\n", .{bytes[startBlob..endBlob]}); + std.debug.print("userdata[0]: {c}\n", .{bytes[startBlob]}); + std.debug.print("userdata[1]: {c}\n", .{bytes[startBlob + 1]}); + std.debug.print("userdata[2]: {c}\n", .{bytes[startBlob + 2]}); + std.debug.print("userdata[3]: {c}\n", .{bytes[startBlob + 3]}); + std.debug.print("userdata[4]: {c}\n", .{bytes[startBlob + 4]}); + std.debug.print("userdata[5]: {c}\n", .{bytes[startBlob + 5]}); + std.debug.print("userdata[6]: {c}\n", .{bytes[startBlob + 6]}); + std.debug.print("userdata[7]: {c}\n", .{bytes[startBlob + 7]}); + + return Entry{ + .user = PackedUser{ + .inner = inner, + .userdata = bytes[startBlob..endBlob], + }, + .next = next, }; } @@ -122,12 +160,10 @@ pub const PackedUser = struct { // packTo packs the User record and copies it to the given byte slice. The // slice must have at least maxRecordSize() bytes available. - pub fn packTo(bufPtr: *[]u8, user: User, shellIndexFn: shellIndexFnType) error{InvalidRecord}!void { + // The slice is passed as a pointer, so it can be mutated. + pub fn packTo(buf: *[]u8, user: User, shellIndexFn: shellIndexFnType) error{InvalidRecord}!void { // function arguments are consts. We need to mutate the underlying // slice, so passing it via pointer instead. - var buf = bufPtr.*; - const bufStart = @ptrToInt(buf.ptr); - const home_len = try downCast(u6, user.home.len - 1); const name_len = try downCast(u5, user.name.len - 1); const shell_len = try downCast(u6, user.shell.len - 1); @@ -150,43 +186,56 @@ pub const PackedUser = struct { .gecos_len = gecos_len, }; const innerBytes = mem.asBytes(&inner); - buf.len += innerBytes.len + + var pos: usize = buf.*.len; + buf.*.len += InnerSize + user.home.len + user.gecos.len; - mem.copy(u8, buf[0..innerBytes.len], innerBytes); - buf = buf[innerBytes.len..]; - mem.copy(u8, buf[0..user.home.len], user.home); - buf = buf[user.home.len..]; + // innerBytes.len is longer than InnerSize. We want to copy + // only the InnerSize-number of bytes. + mem.copy(u8, buf.*[pos .. pos + InnerSize], innerBytes[0..InnerSize]); + pos += InnerSize; + mem.copy(u8, buf.*[pos .. pos + user.home.len], user.home); + pos += user.home.len; if (!inner.name_is_a_suffix) { - buf.len += user.name.len; - mem.copy(u8, buf[0..user.name.len], user.name); - buf = buf[user.name.len..]; + buf.*.len += user.name.len; + mem.copy(u8, buf.*[pos .. pos + user.name.len], user.name); + pos += user.name.len; } - mem.copy(u8, buf, user.gecos); - buf = buf[user.gecos.len..]; + mem.copy(u8, buf.*[pos .. pos + user.gecos.len], user.gecos); + pos += user.gecos.len; + if (inner.shell_here) { - buf.len += user.shell.len; - mem.copy(u8, buf[0..user.shell.len], user.shell); - buf = buf[user.shell.len..]; + buf.*.len += user.shell.len; + mem.copy(u8, buf.*[pos .. pos + user.shell.len], user.shell); + pos += user.shell.len; } - const bufLen = @ptrToInt(buf.ptr) - bufStart; - const padding = pad.roundUpPadding(u64, PackedUserAlignmentBits, bufLen); - buf.len += padding; - mem.set(u8, buf[0..padding], 0); + const padding = pad.roundUpPadding(u64, PackedUserAlignmentBits, pos); + buf.*.len += padding; + mem.set(u8, buf.*[pos .. pos + padding], 0); } // maxSize is the maximum number of records a PackedUser can take // (struct + userdata). pub fn maxSize() usize { - const unpadded = InnerSize + - std.math.maxInt(u6) + // home - std.math.maxInt(u5) + // name - std.math.maxInt(u6) + // shell - std.math.maxInt(u8); // gecos - return pad.roundUp(u64, PackedUserAlignmentBits, unpadded); + comptime { + const unpadded = InnerSize + + std.math.maxInt(u6) + // home + std.math.maxInt(u5) + // name + std.math.maxInt(u6) + // shell + std.math.maxInt(u8); // gecos + return pad.roundUp(u64, PackedUserAlignmentBits, unpadded); + } + } + + pub fn uid(self: *const PackedUser) u32 { + return self.inner.uid; + } + + pub fn gid(self: *const PackedUser) u32 { + return self.inner.gid; } pub fn home(self: *const PackedUser) []const u8 { @@ -194,20 +243,20 @@ pub const PackedUser = struct { } pub fn name(self: *const PackedUser) []const u8 { - const name_pos = self.inner.namePos(); + const name_pos = self.inner.nameStart(); const name_len = self.inner.nameLen(); return self.userdata[name_pos .. name_pos + name_len]; } pub fn gecos(self: *const PackedUser) []const u8 { - const gecos_pos = self.inner.gecosPos(); + const gecos_pos = self.inner.gecosStart(); const gecos_len = self.inner.gecosLen(); return self.userdata[gecos_pos .. gecos_pos + gecos_len]; } pub fn shell(self: *const PackedUser, shellIndex: shellIndexProto) []const u8 { if (self.inner.shell_here) { - const shell_pos = self.inner.maybeShellPos(); + const shell_pos = self.inner.maybeShellStart(); const shell_len = self.inner.shellLen(); return self.userdata[shell_pos .. shell_pos + shell_len]; } @@ -215,95 +264,24 @@ pub const PackedUser = struct { } }; -pub const UserReader = struct { - section: []const u8, +pub fn userIterator(section: []const u8, shellIndex: shellIndexProto) Iterator { + return Iterator{ + .section = section, + .shellIndex = shellIndex, + }; +} + +pub const Iterator = struct { + section: ?[]const u8, shellIndex: shellIndexProto, - pub const PackedEntry = struct { - packed_user: *PackedUser, - section: []const u8, - }; - - pub fn init(section: []u8, shellIndex: shellIndexProto) UserReader { - return UserReader{ - .section = section, - .shellIndex = shellIndex, - }; - } - - pub const Entry = struct { - user: User, - nextOffset: usize, - }; - - // atOffset returns a ?User in a given offset of the User section. Also, - // the offset to the next user. - pub fn atOffset(self: *UserReader, index: usize) ?Entry { - if (index == self.section.len) return null; - assert(index < self.section.len); - const endUser = index + @sizeOf(PackedUser); - var u = std.mem.bytesAsValue( - PackedUser, - self.section[index..endUser][0..@sizeOf(PackedUser)], - ); - const startBlob = endUser; - const endBlob = startBlob + u.blobLength(); - const section = self.section[startBlob..endBlob]; - - const home = section[0..u.homeLen()]; - var name: []const u8 = undefined; - var pos: usize = undefined; - - if (u.name_is_a_suffix) { - const name_start = u.homeLen() - u.nameLen(); - name = section[name_start..u.homeLen()]; - pos = u.homeLen(); - } else { - const name_start = u.homeLen(); - name = section[name_start .. name_start + u.nameLen()]; - pos = name_start + u.nameLen(); + pub fn next(it: *Iterator) ?PackedUser { + if (it.section) |section| { + const entry = PackedUser.fromBytes(section); + it.section = entry.next; + return entry.user; } - const gecos = section[pos .. pos + u.gecosLen()]; - pos += u.gecosLen(); - - var shell: []const u8 = undefined; - if (u.shell_here) { - shell = section[pos .. pos + u.shellLen()]; - } else { - shell = self.shellIndex(u.shell_len_or_idx); - } - - return Entry{ - .user = User{ - .uid = u.uid, - .gid = u.gid, - .name = name, - .gecos = gecos, - .home = home, - .shell = shell, - }, - .nextOffset = pad.roundUp(usize, PackedUserAlignmentBits, endBlob), - }; - } - - pub const Iterator = struct { - ur: *UserReader, - offset: usize = 0, - - pub fn next(it: *Iterator) ?User { - if (it.ur.atOffset(it.offset)) |result| { - it.offset = result.nextOffset; - return result.user; - } - return null; - } - }; - - pub fn iterator(self: *UserReader) Iterator { - return Iterator{ - .ur = self, - .offset = 0, - }; + return null; } }; @@ -340,6 +318,7 @@ test "construct PackedUser section" { var buf = ArrayList(u8).init(testing.allocator); defer buf.deinit(); + std.debug.print("\n", .{}); const users = [_]User{ User{ .uid = 1000, .gid = 1000, @@ -356,27 +335,33 @@ test "construct PackedUser section" { .shell = "/usr/bin/nologin", }, User{ .uid = 0, - .gid = 4294967295, - .name = "n" ** 32, - .gecos = "g" ** 255, - .home = "h" ** 64, - .shell = "s" ** 64, + .gid = math.maxInt(u32), + .name = "Name" ** 8, + .gecos = "Gecos" ** 51, + .home = "Home" ** 16, + .shell = "She.l..l" ** 8, + }, User{ + .uid = 1002, + .gid = 1002, + .name = "svc-bar", + .gecos = "", + .home = "/", + .shell = "/", } }; for (users) |user| { try buf.ensureUnusedCapacity(PackedUser.maxSize()); try PackedUser.packTo(&buf.items, user, testShellIndex); } - //var rd = UserReader.init(buf.items, testShell); - - //var it = rd.iterator(); - //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); - //} + var it = userIterator(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)); + } + try testing.expectEqual(users.len, i); }