diff --git a/src/user.zig b/src/user.zig index fcdb04c..def7929 100644 --- a/src/user.zig +++ b/src/user.zig @@ -4,50 +4,11 @@ const pad = @import("padding.zig"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; -const cast = std.math.cast; +const math = std.math; +const mem = std.mem; -const PackedUserSize = @divExact(@bitSizeOf(PackedUser), 8); -pub const PackedUser = 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, - - // blobLength returns the length of the blob storing string values. - pub fn blobLength(self: *const PackedUser) usize { - var result: usize = self.realHomeLen(); - if (!self.name_is_a_suffix) { - result += self.realNameLen(); - } - result += self.realGecosLen(); - if (self.shell_here) { - result += self.realShellLen(); - } - - return result; - } - - pub fn realHomeLen(self: *const PackedUser) usize { - return @as(u32, self.home_len) + 1; - } - - pub fn realNameLen(self: *const PackedUser) usize { - return @as(u32, self.name_len) + 1; - } - - pub fn realShellLen(self: *const PackedUser) usize { - return @as(u32, self.shell_len_or_idx) + 1; - } - - pub fn realGecosLen(self: *const PackedUser) usize { - return self.gecos_len; - } -}; +// TODO(motiejus) move to the struct where it's used. +const shellIndexProto = fn (u6) []const u8; const PackedUserAlignmentBits = 3; pub const User = struct { @@ -59,28 +20,94 @@ pub const User = struct { shell: []const u8, }; -// UserWriter accepts a naive User struct and returns a PackedUser -pub const UserWriter = struct { - // shellIndexFnType is a signature for a function that accepts a shell - // string and returns it's index in the global shell section. Passing a - // function makes tests easier, and removes the Shell dependency of this - // module. +pub const PackedUser = struct { + // TODO: use @bitSizeOf(Inner)/8: + //const InnerSize = @divExact(@bitSizeOf(Inner), 8); const shellIndexFnType = fn ([]const u8) ?u6; - appendTo: *ArrayList(u8), - shellIndexFn: shellIndexFnType, + const InnerSize = @sizeOf(Inner); + 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, - pub fn init( - appendTo: *ArrayList(u8), - shellIndexFn: shellIndexFnType, - ) UserWriter { - return UserWriter{ - .appendTo = appendTo, - .shellIndexFn = shellIndexFn, + pub fn homeLen(self: *const PackedUser) 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 { + const name_len = self.nameLen(); + if (self.name_is_a_suffix) { + return self.inner.homeLen() - name_len; + } else { + return self.homeLen(); + } + } + + pub fn gecosPos(self: *const Inner) usize { + if (self.name_is_a_suffix) { + return self.homeLen(); + } else { + return self.homeLen() + self.nameLen(); + } + } + + pub fn maybeShellPos(self: *const Inner) usize { + assert(self.shell_here); + return self.gecosPos() + self.gecosLen(); + } + }; + + inner: Inner, + userdata: []const u8, + + pub fn fromBytes(bytes: []const u8) PackedUser { + const inner = std.mem.bytesAsValue( + PackedUser, + bytes[0..@sizeOf(Inner)][0..@sizeOf(Inner)], + ); + + const startBlob = InnerSize; + const endBlob = startBlob + inner.blobLength(); + return PackedUser{ + .inner = inner, + .userdata = bytes[startBlob..endBlob], }; } - pub fn downCast(comptime T: type, n: u64) error{InvalidRecord}!T { + fn downCast(comptime T: type, n: u64) error{InvalidRecord}!T { return std.math.cast(T, n) catch |err| switch (err) { error.Overflow => { return error.InvalidRecord; @@ -88,23 +115,19 @@ pub const UserWriter = struct { }; } - pub fn validateUtf8(s: []const u8) error{InvalidRecord}!void { + fn validateUtf8(s: []const u8) error{InvalidRecord}!void { if (!std.unicode.utf8ValidateSlice(s)) { return error.InvalidRecord; } } - // FIXME(motiejus) record valiation should return a separate type. For User - // case, it should be length-bound slices and utf8-codepoints instead of - // strings. - // - // zig does not have error contexts - // (https://github.com/ziglang/zig/issues/2647) and length-limited slices. - // (It does have bounded_array, but that preallocates the maximum length, - // which is not great for User records). So I am using those excuses to - // do the validation here. I may move it once I learn the language better. - const appendUserErr = error{InvalidRecord} || Allocator.Error; - pub fn appendUser(self: *UserWriter, user: User) appendUserErr!void { + // 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(buf1: *[]u8, user: User, shellIndexFn: shellIndexFnType) error{InvalidRecord}!void { + std.debug.print("\nbuf1.len: {d}\n", .{buf1.*.len}); + var buf = buf1.*; + const bufStart = @ptrToInt(&buf[0]); + 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); @@ -115,39 +138,79 @@ pub const UserWriter = struct { try validateUtf8(user.shell); try validateUtf8(user.gecos); - var puser = PackedUser{ + const inner = Inner{ .uid = user.uid, .gid = user.gid, - .additional_gids_offset = 1 << 29 - 1, - .shell_here = self.shellIndexFn(user.shell) == null, - .shell_len_or_idx = self.shellIndexFn(user.shell) orelse shell_len, + .additional_gids_offset = std.math.maxInt(u29), + .shell_here = shellIndexFn(user.shell) == null, + .shell_len_or_idx = shellIndexFn(user.shell) orelse shell_len, .home_len = home_len, .name_is_a_suffix = std.mem.endsWith(u8, user.home, user.name), .name_len = name_len, .gecos_len = gecos_len, }; + const innerBytes = mem.asBytes(&inner); - try self.appendTo.appendSlice(std.mem.asBytes(&puser)); - try self.appendTo.appendSlice(user.home); - if (!puser.name_is_a_suffix) { - try self.appendTo.appendSlice(user.name); + mem.copy(u8, buf, innerBytes); + buf = buf[innerBytes.len..]; + mem.copy(u8, buf, user.home); + buf = buf[user.home.len..]; + + if (!inner.name_is_a_suffix) { + mem.copy(u8, buf, user.name); + buf = buf[user.name.len..]; } - try self.appendTo.appendSlice(user.gecos); - if (puser.shell_here) { - try self.appendTo.appendSlice(user.shell); + mem.copy(u8, buf, user.gecos); + buf = buf[user.gecos.len..]; + if (inner.shell_here) { + mem.copy(u8, buf, user.shell); + buf = buf[user.shell.len..]; } - try self.appendTo.appendNTimes(0, pad.roundUpPadding( - u64, - PackedUserAlignmentBits, - self.appendTo.items.len, - )); + _ = bufStart; + //const bufLen = @ptrToInt(&buf[0]) - bufStart; + //const padding = pad.roundUpPadding(u64, PackedUserAlignmentBits, bufLen); + //mem.set(u8, buf[0..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); + } + + pub fn home(self: *const PackedUser) []const u8 { + return self.userdata[0..self.inner.homeLen()]; + } + + pub fn name(self: *const PackedUser) []const u8 { + const name_pos = self.inner.namePos(); + 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_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_len = self.inner.shellLen(); + return self.userdata[shell_pos .. shell_pos + shell_len]; + } + return shellIndex(self.inner.shell_len_or_idx); } }; pub const UserReader = struct { - const shellIndexProto = fn (u6) []const u8; - section: []const u8, shellIndex: shellIndexProto, @@ -182,25 +245,25 @@ pub const UserReader = struct { const endBlob = startBlob + u.blobLength(); const section = self.section[startBlob..endBlob]; - const home = section[0..u.realHomeLen()]; + 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.realHomeLen() - u.realNameLen(); - name = section[name_start..u.realHomeLen()]; - pos = u.realHomeLen(); + const name_start = u.homeLen() - u.nameLen(); + name = section[name_start..u.homeLen()]; + pos = u.homeLen(); } else { - const name_start = u.realHomeLen(); - name = section[name_start .. name_start + u.realNameLen()]; - pos = name_start + u.realNameLen(); + const name_start = u.homeLen(); + name = section[name_start .. name_start + u.nameLen()]; + pos = name_start + u.nameLen(); } - const gecos = section[pos .. pos + u.realGecosLen()]; - pos += u.realGecosLen(); + const gecos = section[pos .. pos + u.gecosLen()]; + pos += u.gecosLen(); var shell: []const u8 = undefined; if (u.shell_here) { - shell = section[pos .. pos + u.realShellLen()]; + shell = section[pos .. pos + u.shellLen()]; } else { shell = self.shellIndex(u.shell_len_or_idx); } @@ -272,7 +335,6 @@ test "construct PackedUser section" { var buf = ArrayList(u8).init(testing.allocator); defer buf.deinit(); - var writer = UserWriter.init(&buf, testShellIndex); const users = [_]User{ User{ .uid = 1000, .gid = 1000, @@ -296,19 +358,23 @@ test "construct PackedUser section" { .shell = "s" ** 64, } }; for (users) |user| { - try writer.appendUser(user); + std.debug.print("\nmaxSize: {d}\n", .{PackedUser.maxSize()}); + try buf.ensureUnusedCapacity(PackedUser.maxSize()); + buf.items[0] = 1; + std.debug.print("\nbuf.items.len: {d}\n", .{buf.items.len}); + try PackedUser.packTo(&buf.items, user, testShellIndex); } - var rd = UserReader.init(buf.items, testShell); + //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 = 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); + //} }