1
Fork 0
turbonss/src/sections.zig

790 lines
26 KiB
Zig
Raw Normal View History

2022-02-28 10:31:14 +02:00
const std = @import("std");
2022-03-02 11:05:20 +02:00
const fmt = std.fmt;
2022-03-07 06:09:20 +02:00
const mem = std.mem;
2022-03-02 11:05:20 +02:00
const math = std.math;
2022-02-28 10:31:14 +02:00
const sort = std.sort;
const assert = std.debug.assert;
2022-03-02 11:05:20 +02:00
const unicode = std.unicode;
2022-02-28 10:31:14 +02:00
const Allocator = std.mem.Allocator;
2022-03-02 06:02:04 +02:00
const ArrayListUnmanaged = std.ArrayListUnmanaged;
2022-03-02 06:50:15 +02:00
const ArrayList = std.ArrayList;
2022-03-02 11:05:20 +02:00
const MultiArrayList = std.MultiArrayList;
2022-02-28 10:31:14 +02:00
const StringHashMap = std.StringHashMap;
const AutoHashMap = std.AutoHashMap;
const BufSet = std.BufSet;
const pad = @import("padding.zig");
2022-03-02 06:50:15 +02:00
const compress = @import("compress.zig");
2022-03-18 06:52:21 +02:00
const PackedUser = @import("user.zig").PackedUser;
const User = @import("user.zig").User;
const Group = @import("group.zig").Group;
const GroupStored = @import("group.zig").GroupStored;
const PackedGroup = @import("group.zig").PackedGroup;
const ShellSections = @import("shell.zig").ShellWriter.ShellSections;
const ShellReader = @import("shell.zig").ShellReader;
const ShellWriter = @import("shell.zig").ShellWriter;
2022-03-18 18:48:33 +02:00
const Header = @import("header.zig").Header;
2022-03-18 06:52:21 +02:00
const max_shells = @import("shell.zig").max_shells;
2022-03-18 18:48:33 +02:00
const section_length_bits = @import("header.zig").section_length_bits;
const section_length = @import("header.zig").section_length;
2022-03-02 11:05:20 +02:00
const cmph = @import("cmph.zig");
const bdz = @import("bdz.zig");
2022-02-28 10:31:14 +02:00
const Corpus = struct {
2022-03-01 05:47:44 +02:00
arena: std.heap.ArenaAllocator,
2022-02-28 10:31:14 +02:00
// sorted by name, by unicode codepoint
users: MultiArrayList(User),
2022-02-28 10:31:14 +02:00
// sorted by gid
groups: MultiArrayList(Group),
2022-03-02 11:05:20 +02:00
2022-03-08 20:48:33 +02:00
name2user: StringHashMap(u32),
name2group: StringHashMap(u32),
2022-03-09 05:42:59 +02:00
group2users: []const []const u32,
2022-03-09 05:54:00 +02:00
user2groups: []const []const u32,
2022-02-28 10:31:14 +02:00
pub fn init(
2022-03-01 05:47:44 +02:00
baseAllocator: Allocator,
2022-02-28 10:31:14 +02:00
usersConst: []const User,
groupsConst: []const Group,
) error{ OutOfMemory, InvalidUtf8, Duplicate, NotFound }!Corpus {
2022-03-01 05:47:44 +02:00
var arena = std.heap.ArenaAllocator.init(baseAllocator);
var allocator = arena.allocator();
2022-03-05 05:33:31 +02:00
errdefer arena.deinit();
2022-03-01 05:47:44 +02:00
var users_arr = try allocator.alloc(User, usersConst.len);
var groups_arr = try allocator.alloc(Group, groupsConst.len);
2022-03-02 06:18:19 +02:00
for (usersConst) |*user, i|
users_arr[i] = try user.clone(allocator);
2022-03-02 06:18:19 +02:00
for (groupsConst) |*group, i|
groups_arr[i] = try group.clone(allocator);
2022-02-28 10:31:14 +02:00
sort.sort(User, users_arr, {}, cmpUser);
sort.sort(Group, groups_arr, {}, cmpGroup);
2022-02-28 10:31:14 +02:00
var users = MultiArrayList(User){};
try users.ensureTotalCapacity(allocator, users_arr.len);
for (users_arr) |user|
users.appendAssumeCapacity(user);
var groups = MultiArrayList(Group){};
try groups.ensureTotalCapacity(allocator, groups_arr.len);
for (groups_arr) |group|
groups.appendAssumeCapacity(group);
2022-03-02 11:05:20 +02:00
2022-03-08 20:48:33 +02:00
var name2user = StringHashMap(u32).init(allocator);
var name2group = StringHashMap(u32).init(allocator);
for (users.items(.name)) |name, i| {
2022-03-09 06:02:55 +02:00
var res1 = try name2user.getOrPut(name);
2022-03-02 06:18:19 +02:00
if (res1.found_existing)
2022-02-28 10:31:14 +02:00
return error.Duplicate;
2022-03-08 20:48:33 +02:00
res1.value_ptr.* = @intCast(u32, i);
2022-02-28 10:31:14 +02:00
}
for (groups.items(.name)) |name, i| {
2022-03-09 06:02:55 +02:00
var res1 = try name2group.getOrPut(name);
2022-03-02 06:18:19 +02:00
if (res1.found_existing)
2022-02-28 10:31:14 +02:00
return error.Duplicate;
2022-03-08 20:48:33 +02:00
res1.value_ptr.* = @intCast(u32, i);
2022-02-28 10:31:14 +02:00
}
2022-03-09 05:42:59 +02:00
var group2users = try allocator.alloc([]u32, groups.len);
2022-03-02 11:05:20 +02:00
// uses baseAllocator, because it will be freed before
// returning from this function. This keeps the arena clean.
2022-03-09 05:54:00 +02:00
var user2groups = try baseAllocator.alloc(ArrayListUnmanaged(u32), users.len);
defer baseAllocator.free(user2groups);
mem.set(ArrayListUnmanaged(u32), user2groups, ArrayListUnmanaged(u32){});
2022-03-04 10:37:07 +02:00
for (groups.items(.members)) |group_members, i| {
2022-03-09 06:02:55 +02:00
var members = try allocator.alloc(u32, group_members.count());
2022-03-02 11:05:20 +02:00
members.len = 0;
2022-02-28 10:31:14 +02:00
2022-03-09 06:02:55 +02:00
var it = group_members.iterator();
2022-02-28 10:31:14 +02:00
while (it.next()) |memberName| {
2022-03-09 05:54:00 +02:00
if (name2user.get(memberName.*)) |user_idx| {
2022-03-02 11:05:20 +02:00
members.len += 1;
2022-03-09 05:54:00 +02:00
members[members.len - 1] = user_idx;
try user2groups[user_idx].append(allocator, @intCast(u32, i));
2022-03-09 07:06:39 +02:00
} else return error.NotFound;
2022-02-28 10:31:14 +02:00
}
2022-03-09 05:42:59 +02:00
group2users[i] = members;
2022-02-28 10:31:14 +02:00
}
2022-03-09 07:06:39 +02:00
for (group2users) |*groupusers|
2022-03-09 05:54:00 +02:00
sort.sort(u32, groupusers.*, {}, comptime sort.asc(u32));
2022-03-02 06:02:04 +02:00
2022-03-09 05:54:00 +02:00
var user2groups_final = try allocator.alloc([]const u32, users.len);
user2groups_final.len = users.len;
for (user2groups) |*usergroups, i| {
sort.sort(u32, usergroups.items, {}, comptime sort.asc(u32));
user2groups_final[i] = usergroups.toOwnedSlice(allocator);
}
2022-02-28 10:31:14 +02:00
return Corpus{
2022-03-01 05:47:44 +02:00
.arena = arena,
2022-02-28 10:31:14 +02:00
.users = users,
.groups = groups,
.name2user = name2user,
.name2group = name2group,
2022-03-09 05:42:59 +02:00
.group2users = group2users,
2022-03-09 05:54:00 +02:00
.user2groups = user2groups_final,
2022-02-28 10:31:14 +02:00
};
}
2022-03-01 05:47:44 +02:00
pub fn deinit(self: *Corpus) void {
self.arena.deinit();
self.* = undefined;
}
2022-02-28 10:31:14 +02:00
};
pub fn shellSections(
allocator: Allocator,
corpus: *const Corpus,
) error{ OutOfMemory, Overflow }!ShellSections {
2022-03-18 06:52:21 +02:00
var popcon = ShellWriter.init(allocator);
for (corpus.users.items(.shell)) |shell|
try popcon.put(shell);
2022-03-18 06:52:21 +02:00
return popcon.toOwnedSections(max_shells);
}
2022-03-03 18:05:46 +02:00
pub const UserGids = struct {
2022-03-07 06:09:20 +02:00
// user index -> offset in blob
2022-03-08 20:44:32 +02:00
idx2offset: []const u64,
// compressed user gids blob. A blob contains N <= users.len items,
// an item is:
// len: varint
// gid: [varint]varint,
// ... and the gid list is delta-compressed.
2022-03-07 06:09:20 +02:00
blob: []const u8,
pub fn deinit(self: *UserGids, allocator: Allocator) void {
2022-03-07 06:09:20 +02:00
allocator.free(self.idx2offset);
allocator.free(self.blob);
self.* = undefined;
2022-03-03 18:05:46 +02:00
}
};
2022-03-02 06:50:15 +02:00
pub fn userGids(
allocator: Allocator,
corpus: *const Corpus,
) error{ OutOfMemory, Overflow }!UserGids {
var blob = ArrayList(u8).init(allocator);
2022-03-05 05:33:31 +02:00
errdefer blob.deinit();
2022-03-08 20:44:32 +02:00
var idx2offset = try allocator.alloc(u64, corpus.users.len);
2022-03-07 06:09:20 +02:00
errdefer allocator.free(idx2offset);
2022-03-05 05:33:31 +02:00
2022-03-06 13:11:06 +02:00
// zero'th entry is empty, so groupless users can refer to it.
try compress.appendUvarint(&blob, 0);
2022-03-05 05:33:31 +02:00
var scratch = try allocator.alloc(u32, 256);
defer allocator.free(scratch);
2022-03-09 06:02:55 +02:00
for (corpus.user2groups) |usergroups, user_idx| {
2022-03-09 05:54:00 +02:00
if (usergroups.len == 0) {
2022-03-07 06:09:20 +02:00
idx2offset[user_idx] = 0;
2022-03-09 05:54:00 +02:00
continue;
2022-03-06 13:11:06 +02:00
}
2022-03-09 05:54:00 +02:00
idx2offset[user_idx] = blob.items.len;
scratch = try allocator.realloc(scratch, usergroups.len);
scratch.len = usergroups.len;
const corpusGids = corpus.groups.items(.gid);
2022-03-09 05:54:00 +02:00
for (usergroups) |group_idx, i|
scratch[i] = corpusGids[group_idx];
2022-03-09 05:54:00 +02:00
compress.deltaCompress(u32, scratch) catch |err| switch (err) {
error.NotSorted => unreachable,
};
try compress.appendUvarint(&blob, usergroups.len);
for (scratch) |gid|
try compress.appendUvarint(&blob, gid);
2022-03-04 10:37:07 +02:00
}
return UserGids{
2022-03-07 06:09:20 +02:00
.idx2offset = idx2offset,
.blob = blob.toOwnedSlice(),
};
}
2022-03-07 06:09:20 +02:00
pub const UsersSection = struct {
// user index -> offset in blob
idx2offset: []const u32,
blob: []const u8,
pub fn deinit(self: *UsersSection, allocator: Allocator) void {
allocator.free(self.idx2offset);
allocator.free(self.blob);
self.* = undefined;
}
};
2022-03-05 06:08:01 +02:00
pub fn usersSection(
allocator: Allocator,
corpus: *const Corpus,
2022-03-05 10:19:42 +02:00
gids: *const UserGids,
shells: *const ShellSections,
2022-03-07 06:09:20 +02:00
) error{ OutOfMemory, Overflow, InvalidRecord }!UsersSection {
var idx2offset = try allocator.alloc(u32, corpus.users.len);
errdefer allocator.free(idx2offset);
// as of writing each user takes 12 bytes + blobs + padding, padded to
2022-03-06 13:11:06 +02:00
// 8 bytes. 24 is an optimistic lower bound for an average record size.
2022-03-07 06:09:20 +02:00
var blob = try ArrayList(u8).initCapacity(allocator, 24 * corpus.users.len);
errdefer blob.deinit();
var i: usize = 0;
while (i < corpus.users.len) : (i += 1) {
// TODO: this is inefficient by calling `.slice()` on every iteration
const user = corpus.users.get(i);
const user_offset = try math.cast(u35, blob.items.len);
assert(user_offset & 7 == 0);
idx2offset[i] = @truncate(u32, user_offset >> 3);
2022-03-18 06:52:21 +02:00
try PackedUser.packTo(
2022-03-07 06:09:20 +02:00
&blob,
2022-03-06 13:11:06 +02:00
user,
2022-03-09 06:06:37 +02:00
gids.idx2offset[i],
shells.shell2idx,
2022-03-06 13:11:06 +02:00
);
2022-03-18 06:52:21 +02:00
try pad.arrayList(&blob, PackedUser.alignment_bits);
2022-03-06 13:11:06 +02:00
}
2022-03-07 06:09:20 +02:00
return UsersSection{
.idx2offset = idx2offset,
.blob = blob.toOwnedSlice(),
};
2022-03-05 06:08:01 +02:00
}
2022-03-07 06:09:20 +02:00
pub const GroupMembers = struct {
// group index to it's offset in blob
2022-03-09 07:04:33 +02:00
idx2offset: []const u64,
2022-03-09 07:06:39 +02:00
// members are delta-varint encoded byte-offsets to the user struct
2022-03-07 06:09:20 +02:00
blob: []const u8,
pub fn deinit(self: *GroupMembers, allocator: Allocator) void {
allocator.free(self.idx2offset);
allocator.free(self.blob);
self.* = undefined;
}
};
pub fn groupMembers(
allocator: Allocator,
corpus: *const Corpus,
2022-03-07 06:09:20 +02:00
user2offset: []const u32,
) error{OutOfMemory}!GroupMembers {
2022-03-09 07:04:33 +02:00
var idx2offset = try allocator.alloc(u64, corpus.groups.len);
2022-03-07 06:09:20 +02:00
errdefer allocator.free(idx2offset);
var blob = ArrayList(u8).init(allocator);
errdefer blob.deinit();
// zero'th entry is empty, so empty groups can refer to it
try compress.appendUvarint(&blob, 0);
2022-03-09 07:04:33 +02:00
2022-03-09 07:06:39 +02:00
var scratch = try ArrayList(u32).initCapacity(allocator, 1024);
defer scratch.deinit();
2022-03-09 07:04:33 +02:00
for (corpus.group2users) |members, group_idx| {
if (members.len == 0) {
idx2offset[group_idx] = 0;
2022-03-07 06:09:20 +02:00
continue;
2022-03-02 06:50:15 +02:00
}
2022-03-07 06:09:20 +02:00
2022-03-09 07:06:39 +02:00
idx2offset[group_idx] = blob.items.len;
try scratch.ensureTotalCapacity(members.len);
scratch.items.len = members.len;
for (members) |user_idx, i|
scratch.items[i] = user2offset[user_idx];
2022-03-09 07:04:33 +02:00
2022-03-09 07:06:39 +02:00
compress.deltaCompress(u32, scratch.items) catch |err| switch (err) {
2022-03-09 07:04:33 +02:00
error.NotSorted => unreachable,
};
try compress.appendUvarint(&blob, members.len);
2022-03-09 07:06:39 +02:00
for (scratch.items) |elem|
try compress.appendUvarint(&blob, elem);
2022-03-02 06:50:15 +02:00
}
2022-03-07 06:09:20 +02:00
return GroupMembers{
.idx2offset = idx2offset,
.blob = blob.toOwnedSlice(),
};
}
2022-03-02 06:50:15 +02:00
pub const GroupsSection = struct {
// group index -> offset in blob
idx2offset: []const u32,
blob: []const u8,
pub fn deinit(self: *GroupsSection, allocator: Allocator) void {
allocator.free(self.idx2offset);
allocator.free(self.blob);
self.* = undefined;
}
};
pub fn groupsSection(
allocator: Allocator,
corpus: *const Corpus,
members_offset: []const u64,
) error{ OutOfMemory, Overflow, InvalidRecord }!GroupsSection {
var idx2offset = try allocator.alloc(u32, corpus.groups.len);
errdefer allocator.free(idx2offset);
var blob = try ArrayList(u8).initCapacity(allocator, 8 * corpus.groups.len);
errdefer blob.deinit();
var i: usize = 0;
while (i < corpus.groups.len) : (i += 1) {
// TODO: this is inefficient; it's calling `.slice()` on every iteration
const group = corpus.groups.get(i);
const group_offset = try math.cast(u32, blob.items.len);
assert(group_offset & 7 == 0);
2022-03-16 06:57:35 +02:00
idx2offset[i] = @truncate(u32, group_offset >> 3);
2022-03-18 06:52:21 +02:00
const group_stored = GroupStored{
.gid = group.gid,
.name = group.name,
.members_offset = members_offset[i],
};
2022-03-18 06:52:21 +02:00
try PackedGroup.packTo(&blob, group_stored);
try pad.arrayList(&blob, PackedGroup.alignment_bits);
}
return GroupsSection{
.idx2offset = idx2offset,
.blob = blob.toOwnedSlice(),
};
}
2022-03-18 07:17:52 +02:00
// creates a bdz index using packed_mphf.
// hash = bdz_search(packed_mphf, keys[i]);
// result[hash] = idx2offset[i];
2022-03-15 07:47:52 +02:00
pub fn bdzIdx(
comptime T: type,
allocator: Allocator,
packed_mphf: []const u8,
keys: []const T,
2022-03-18 07:17:52 +02:00
idx2offset: []const u32,
2022-03-15 07:47:52 +02:00
) error{OutOfMemory}![]const u32 {
const search_fn = comptime blk: {
switch (T) {
u32 => break :blk bdz.search_u32,
[]const u8 => break :blk bdz.search,
else => unreachable,
}
};
assert(keys.len <= math.maxInt(u32));
2022-03-15 07:47:52 +02:00
var result = try allocator.alloc(u32, keys.len);
2022-03-16 06:57:35 +02:00
for (keys) |key, i|
2022-03-18 07:17:52 +02:00
result[search_fn(packed_mphf, key)] = idx2offset[i];
2022-03-15 07:47:52 +02:00
return result;
}
2022-02-28 10:31:14 +02:00
// cmpUser compares two users for sorting. By username's utf8 codepoints, ascending.
fn cmpUser(_: void, a: User, b: User) bool {
var utf8_a = (unicode.Utf8View.init(a.name) catch unreachable).iterator();
var utf8_b = (unicode.Utf8View.init(b.name) catch unreachable).iterator();
while (utf8_a.nextCodepoint()) |codepoint_a| {
if (utf8_b.nextCodepoint()) |codepoint_b| {
2022-03-02 06:18:19 +02:00
if (codepoint_a == codepoint_b) {
continue;
2022-03-09 07:06:39 +02:00
} else return codepoint_a < codepoint_b;
2022-02-28 10:31:14 +02:00
}
2022-03-02 06:18:19 +02:00
// a is a prefix of b. It is thus shorter.
return false;
2022-02-28 10:31:14 +02:00
}
// b is a prefix of a
return true;
}
fn cmpGroup(_: void, a: Group, b: Group) bool {
return a.gid < b.gid;
}
2022-03-18 18:48:33 +02:00
// nblocks returns how many blocks a particular slice will take.
fn nblocks(arr: []const u8) u32 {
const upper = pad.roundUp(u38, section_length_bits, @intCast(u38, arr.len));
assert(upper & (section_length - 1) == 0);
return @truncate(u32, upper >> 6);
}
2022-03-07 06:09:20 +02:00
pub const AllSections = struct {
allocator: Allocator,
bdz_gid: []const u8,
bdz_groupname: []const u8,
bdz_uid: []const u8,
bdz_username: []const u8,
users: UsersSection,
shell_sections: ShellSections,
2022-03-13 14:22:49 +02:00
shell_reader: ShellReader,
2022-03-07 06:09:20 +02:00
user_gids: UserGids,
2022-03-09 07:04:33 +02:00
group_members: GroupMembers,
groups: GroupsSection,
2022-03-15 06:35:48 +02:00
idx_gid2group: []const u32,
idx_groupname2group: []const u32,
idx_uid2user: []const u32,
idx_name2user: []const u32,
2022-03-18 18:48:33 +02:00
header: Header,
2022-03-07 06:09:20 +02:00
pub fn init(
allocator: Allocator,
corpus: *const Corpus,
) error{ Overflow, OutOfMemory, InvalidRecord }!AllSections {
2022-03-16 06:57:35 +02:00
const gids = corpus.groups.items(.gid);
const gnames = corpus.groups.items(.name);
const uids = corpus.users.items(.uid);
const unames = corpus.users.items(.name);
2022-03-13 14:22:49 +02:00
2022-03-16 06:57:35 +02:00
var bdz_gid = try cmph.packU32(allocator, gids);
errdefer allocator.free(bdz_gid);
var bdz_groupname = try cmph.packStr(allocator, gnames);
errdefer allocator.free(bdz_groupname);
var bdz_uid = try cmph.packU32(allocator, uids);
errdefer allocator.free(bdz_uid);
const bdz_username = try cmph.packStr(allocator, unames);
errdefer allocator.free(bdz_username);
var shell_sections = try shellSections(allocator, corpus);
errdefer shell_sections.deinit();
var user_gids = try userGids(allocator, corpus);
errdefer user_gids.deinit(allocator);
var users = try usersSection(allocator, corpus, &user_gids, &shell_sections);
errdefer users.deinit(allocator);
var group_members = try groupMembers(allocator, corpus, users.idx2offset);
errdefer group_members.deinit(allocator);
var groups = try groupsSection(allocator, corpus, group_members.idx2offset);
errdefer groups.deinit(allocator);
2022-03-18 07:17:52 +02:00
var idx_gid2group = try bdzIdx(u32, allocator, bdz_gid, gids, groups.idx2offset);
2022-03-16 06:57:35 +02:00
errdefer allocator.free(idx_gid2group);
2022-03-18 07:17:52 +02:00
var idx_groupname2group = try bdzIdx([]const u8, allocator, bdz_groupname, gnames, groups.idx2offset);
2022-03-16 06:57:35 +02:00
errdefer allocator.free(idx_groupname2group);
2022-03-18 07:17:52 +02:00
var idx_uid2user = try bdzIdx(u32, allocator, bdz_uid, uids, users.idx2offset);
2022-03-16 06:57:35 +02:00
errdefer allocator.free(idx_uid2user);
2022-03-18 07:17:52 +02:00
var idx_name2user = try bdzIdx([]const u8, allocator, bdz_username, unames, users.idx2offset);
2022-03-16 06:57:35 +02:00
errdefer allocator.free(idx_name2user);
2022-03-15 07:47:52 +02:00
2022-03-18 18:48:33 +02:00
const header = Header{
.nblocks_shell_blob = undefined,
.num_shells = undefined,
.num_groups = undefined,
.num_users = undefined,
.nblocks_bdz_gid = undefined,
.nblocks_bdz_groupname = undefined,
.nblocks_bdz_uid = undefined,
.nblocks_bdz_username = undefined,
.nblocks_groups = undefined,
.nblocks_users = undefined,
.nblocks_groupmembers = undefined,
.nblocks_additional_gids = undefined,
};
2022-03-07 06:09:20 +02:00
return AllSections{
.allocator = allocator,
.bdz_gid = bdz_gid,
.bdz_groupname = bdz_groupname,
.bdz_uid = bdz_uid,
.bdz_username = bdz_username,
.shell_sections = shell_sections,
2022-03-18 06:52:21 +02:00
.shell_reader = ShellReader.init(
2022-03-16 06:57:35 +02:00
mem.sliceAsBytes(shell_sections.index.constSlice()),
mem.sliceAsBytes(shell_sections.blob.constSlice()),
),
2022-03-07 06:09:20 +02:00
.user_gids = user_gids,
.users = users,
2022-03-09 07:06:39 +02:00
.group_members = group_members,
.groups = groups,
2022-03-15 07:47:52 +02:00
.idx_gid2group = idx_gid2group,
.idx_groupname2group = idx_groupname2group,
.idx_uid2user = idx_uid2user,
.idx_name2user = idx_name2user,
2022-03-18 18:48:33 +02:00
.header = header,
2022-03-07 06:09:20 +02:00
};
}
pub fn deinit(self: *AllSections) void {
self.allocator.free(self.bdz_gid);
self.allocator.free(self.bdz_groupname);
self.allocator.free(self.bdz_uid);
self.allocator.free(self.bdz_username);
self.shell_sections.deinit();
self.user_gids.deinit(self.allocator);
self.users.deinit(self.allocator);
2022-03-09 07:06:39 +02:00
self.group_members.deinit(self.allocator);
self.groups.deinit(self.allocator);
2022-03-15 07:47:52 +02:00
self.allocator.free(self.idx_gid2group);
self.allocator.free(self.idx_groupname2group);
self.allocator.free(self.idx_uid2user);
self.allocator.free(self.idx_name2user);
2022-03-07 06:09:20 +02:00
self.* = undefined;
}
};
2022-03-02 06:02:04 +02:00
2022-02-28 10:31:14 +02:00
const testing = std.testing;
2022-03-18 06:52:21 +02:00
const someMembers = @import("group.zig").someMembers;
2022-02-28 10:31:14 +02:00
2022-03-02 11:05:20 +02:00
fn testCorpus(allocator: Allocator) !Corpus {
2022-03-01 15:25:38 +02:00
const users = [_]User{ User{
2022-03-18 07:34:28 +02:00
.uid = 0,
.gid = 0,
.name = "root",
.gecos = "",
.home = "/root",
.shell = "/bin/bash",
}, User{
2022-03-02 11:05:20 +02:00
.uid = 128,
.gid = 128,
2022-02-28 10:31:14 +02:00
.name = "vidmantas",
.gecos = "Vidmantas Kaminskas",
.home = "/home/vidmantas",
.shell = "/bin/bash",
}, User{
2022-03-18 07:34:28 +02:00
.uid = 1000,
2022-03-02 11:05:20 +02:00
.gid = math.maxInt(u32),
2022-02-28 10:31:14 +02:00
.name = "Name" ** 8,
.gecos = "Gecos" ** 51,
.home = "Home" ** 16,
.shell = "She.LllL" ** 8,
}, User{
2022-03-18 07:34:28 +02:00
.uid = 100000,
2022-02-28 10:31:14 +02:00
.gid = 1002,
.name = "svc-bar",
.gecos = "",
.home = "/",
.shell = "/",
2022-03-04 10:37:07 +02:00
}, User{
.uid = 65534,
.gid = 65534,
.name = "nobody",
.gecos = "nobody",
.home = "/nonexistent",
.shell = "/usr/sbin/nologin",
2022-02-28 10:31:14 +02:00
} };
2022-03-18 07:34:28 +02:00
var members0 = try someMembers(
allocator,
&[_][]const u8{"root"},
);
defer members0.deinit();
2022-03-18 06:52:21 +02:00
var members1 = try someMembers(
2022-03-01 11:01:45 +02:00
allocator,
&[_][]const u8{"vidmantas"},
);
2022-03-01 05:47:44 +02:00
defer members1.deinit();
2022-03-18 06:52:21 +02:00
var members2 = try someMembers(
2022-03-01 11:01:45 +02:00
allocator,
2022-03-01 15:25:38 +02:00
&[_][]const u8{ "svc-bar", "vidmantas" },
2022-03-01 11:01:45 +02:00
);
2022-03-01 05:47:44 +02:00
defer members2.deinit();
2022-03-18 06:52:21 +02:00
var members3 = try someMembers(
2022-03-01 11:01:45 +02:00
allocator,
2022-03-18 07:34:28 +02:00
&[_][]const u8{ "svc-bar", "Name" ** 8, "vidmantas", "root" },
2022-03-01 11:01:45 +02:00
);
2022-03-01 05:47:44 +02:00
defer members3.deinit();
2022-03-16 06:59:12 +02:00
const groups = [_]Group{
2022-03-18 07:34:28 +02:00
Group{ .gid = 0, .name = "root", .members = members0 },
2022-03-16 06:59:12 +02:00
Group{ .gid = 128, .name = "vidmantas", .members = members1 },
Group{ .gid = 9999, .name = "all", .members = members3 },
2022-03-18 07:34:28 +02:00
Group{ .gid = 100000, .name = "service-account", .members = members2 },
2022-03-16 06:59:12 +02:00
};
2022-02-28 10:31:14 +02:00
2022-03-02 11:05:20 +02:00
return try Corpus.init(allocator, users[0..], groups[0..]);
}
test "test corpus" {
var corpus = try testCorpus(testing.allocator);
2022-03-01 05:47:44 +02:00
defer corpus.deinit();
2022-03-02 11:05:20 +02:00
2022-03-07 06:09:20 +02:00
const name_name = 0;
const nobody = 1;
2022-03-18 07:34:28 +02:00
const root = 2;
const svc_bar = 3;
const vidmantas = 4;
2022-03-07 06:09:20 +02:00
const usernames = corpus.users.items(.name);
try testing.expectEqualStrings(usernames[name_name], "Name" ** 8);
try testing.expectEqualStrings(usernames[nobody], "nobody");
2022-03-18 07:34:28 +02:00
try testing.expectEqualStrings(usernames[root], "root");
try testing.expectEqualStrings(usernames[svc_bar], "svc-bar");
try testing.expectEqualStrings(usernames[vidmantas], "vidmantas");
2022-03-07 06:09:20 +02:00
2022-03-18 07:34:28 +02:00
const g_root = 0;
2022-03-07 06:09:20 +02:00
const g_vidmantas = 1;
const g_all = 2;
2022-03-18 07:34:28 +02:00
const g_service_account = 3;
2022-03-07 06:09:20 +02:00
const groupnames = corpus.groups.items(.name);
2022-03-18 07:34:28 +02:00
try testing.expectEqualStrings(groupnames[g_root], "root");
try testing.expectEqualStrings(groupnames[g_service_account], "service-account");
try testing.expectEqualStrings(groupnames[g_vidmantas], "vidmantas");
try testing.expectEqualStrings(groupnames[g_all], "all");
2022-03-01 11:01:45 +02:00
2022-03-01 15:25:38 +02:00
try testing.expectEqual(corpus.name2user.get("404"), null);
2022-03-07 06:09:20 +02:00
try testing.expectEqual(corpus.name2user.get("vidmantas").?, vidmantas);
2022-03-01 15:25:38 +02:00
try testing.expectEqual(corpus.name2group.get("404"), null);
2022-03-07 06:09:20 +02:00
try testing.expectEqual(corpus.name2group.get("vidmantas").?, g_vidmantas);
2022-03-01 15:25:38 +02:00
2022-03-09 05:42:59 +02:00
const membersOfAll = corpus.group2users[g_all];
2022-03-07 06:09:20 +02:00
try testing.expectEqual(membersOfAll[0], name_name);
2022-03-18 07:34:28 +02:00
try testing.expectEqual(membersOfAll[1], root);
try testing.expectEqual(membersOfAll[2], svc_bar);
try testing.expectEqual(membersOfAll[3], vidmantas);
2022-03-01 15:25:38 +02:00
2022-03-09 05:54:00 +02:00
const groupsOfVidmantas = corpus.user2groups[vidmantas];
2022-03-18 07:34:28 +02:00
try testing.expectEqual(groupsOfVidmantas[0], g_vidmantas);
try testing.expectEqual(groupsOfVidmantas[1], g_all);
try testing.expectEqual(groupsOfVidmantas[2], g_service_account);
2022-02-28 10:31:14 +02:00
}
2022-03-15 06:26:48 +02:00
test "test groups, group members and users" {
2022-03-03 18:05:46 +02:00
const allocator = testing.allocator;
var corpus = try testCorpus(allocator);
defer corpus.deinit();
2022-03-09 07:06:39 +02:00
var sections = try AllSections.init(allocator, &corpus);
defer sections.deinit();
const blob = sections.group_members.blob;
2022-03-15 06:26:48 +02:00
var i: usize = 0;
while (i < corpus.groups.len) : (i += 1) {
const offset = sections.group_members.idx2offset[i];
2022-03-09 07:06:39 +02:00
var vit = try compress.VarintSliceIterator(blob[offset..]);
var it = compress.DeltaDecompressionIterator(&vit);
2022-03-15 06:26:48 +02:00
for (corpus.group2users[i]) |user_idx| {
2022-03-09 07:06:39 +02:00
const got_user_offset = (try it.next()).?;
const want_user_offset = sections.users.idx2offset[user_idx];
try testing.expectEqual(got_user_offset, want_user_offset);
}
try testing.expectEqual(it.next(), null);
2022-03-09 07:06:39 +02:00
}
2022-03-18 06:52:21 +02:00
var it = PackedUser.iterator(sections.users.blob, sections.shell_reader);
2022-03-15 06:26:48 +02:00
i = 0;
while (i < corpus.users.len) : (i += 1) {
const got = (try it.next()).?;
const user = corpus.users.get(i);
try testing.expectEqual(user.uid, got.uid());
try testing.expectEqual(user.gid, got.gid());
try testing.expectEqualStrings(user.name, got.name());
try testing.expectEqualStrings(user.gecos, got.gecos());
try testing.expectEqualStrings(user.home, got.home());
try testing.expectEqualStrings(user.shell, got.shell(sections.shell_reader));
2022-03-15 06:26:48 +02:00
}
2022-03-05 10:19:42 +02:00
}
test "userGids" {
const allocator = testing.allocator;
var corpus = try testCorpus(allocator);
defer corpus.deinit();
var user_gids = try userGids(allocator, &corpus);
defer user_gids.deinit(allocator);
2022-03-06 09:57:44 +02:00
2022-03-09 07:06:39 +02:00
var user_idx: usize = 0;
while (user_idx < corpus.users.len) : (user_idx += 1) {
const groups = corpus.user2groups[user_idx];
const offset = user_gids.idx2offset[user_idx];
2022-03-09 05:54:00 +02:00
if (groups.len == 0) {
2022-03-07 06:09:20 +02:00
try testing.expect(offset == 0);
2022-03-06 09:57:44 +02:00
continue;
}
2022-03-07 06:09:20 +02:00
var vit = try compress.VarintSliceIterator(user_gids.blob[offset..]);
2022-03-06 09:57:44 +02:00
var it = compress.DeltaDecompressionIterator(&vit);
2022-03-09 05:54:00 +02:00
try testing.expectEqual(it.remaining(), groups.len);
2022-03-08 20:44:32 +02:00
var i: u64 = 0;
const corpusGids = corpus.groups.items(.gid);
2022-03-06 09:57:44 +02:00
while (try it.next()) |gid| : (i += 1) {
try testing.expectEqual(gid, corpusGids[groups[i]]);
2022-03-06 09:57:44 +02:00
}
try testing.expectEqual(i, groups.len);
2022-03-06 09:57:44 +02:00
}
2022-03-03 18:05:46 +02:00
}
2022-03-02 11:05:20 +02:00
test "pack gids" {
const allocator = testing.allocator;
var corpus = try testCorpus(allocator);
defer corpus.deinit();
const cmph_gid = try cmph.packU32(allocator, corpus.groups.items(.gid));
2022-03-02 11:05:20 +02:00
defer allocator.free(cmph_gid);
const k1 = bdz.search_u32(cmph_gid, 0);
const k2 = bdz.search_u32(cmph_gid, 128);
const k3 = bdz.search_u32(cmph_gid, 9999);
2022-03-18 07:34:28 +02:00
const k4 = bdz.search_u32(cmph_gid, 100000);
var hashes = &[_]u32{ k1, k2, k3, k4 };
2022-03-02 11:05:20 +02:00
sort.sort(u32, hashes, {}, comptime sort.asc(u32));
for (hashes) |hash, i|
try testing.expectEqual(i, hash);
}
2022-02-28 10:31:14 +02:00
fn testUser(name: []const u8) User {
var result = std.mem.zeroes(User);
result.name = name;
return result;
}
test "users compare function" {
const a = testUser("a");
const b = testUser("b");
const bb = testUser("bb");
try testing.expect(cmpUser({}, a, b));
try testing.expect(!cmpUser({}, b, a));
try testing.expect(cmpUser({}, a, bb));
try testing.expect(!cmpUser({}, bb, a));
try testing.expect(cmpUser({}, b, bb));
try testing.expect(!cmpUser({}, bb, b));
}
2022-03-15 07:47:52 +02:00
2022-03-18 07:17:52 +02:00
const hash_offsets = &[_]u32{ 0, 10, 20, 30 };
2022-03-16 07:08:57 +02:00
fn expectUsedHashes(allocator: Allocator, arr: []const u32) !void {
2022-03-18 07:17:52 +02:00
var used = AutoHashMap(u32, void).init(allocator);
defer used.deinit();
2022-03-16 07:08:57 +02:00
for (arr) |elem|
2022-03-18 07:17:52 +02:00
try used.putNoClobber(elem, {});
for (hash_offsets) |item|
try testing.expect(used.get(item) != null);
2022-03-16 07:08:57 +02:00
}
2022-03-15 07:47:52 +02:00
2022-03-16 07:08:57 +02:00
test "bdzIdx on u32" {
const keys = [_]u32{ 42, 1, 2, 3 };
const mphf = try cmph.packU32(testing.allocator, keys[0..]);
defer testing.allocator.free(mphf);
2022-03-18 07:17:52 +02:00
var result = try bdzIdx(u32, testing.allocator, mphf, keys[0..], hash_offsets);
2022-03-16 07:08:57 +02:00
defer testing.allocator.free(result);
try expectUsedHashes(testing.allocator, result);
}
test "bdzIdx on str" {
const keys = [_][]const u8{ "42", "1", "2", "3" };
const mphf = try cmph.packStr(testing.allocator, keys[0..]);
defer testing.allocator.free(mphf);
2022-03-18 07:17:52 +02:00
var result = try bdzIdx([]const u8, testing.allocator, mphf, keys[0..], hash_offsets);
2022-03-16 07:08:57 +02:00
defer testing.allocator.free(result);
try expectUsedHashes(testing.allocator, result);
2022-03-15 07:47:52 +02:00
}
2022-03-18 18:48:33 +02:00
test "nblocks" {
const T = struct { want: u32, arr: []const u8 };
const tests = &[_]T{
.{ .want = 0, .arr = &[_]u8{} },
.{ .want = 1, .arr = &[_]u8{ 1, 2, 42 } },
.{ .want = 1, .arr = &[_]u8{1} ** 63 },
.{ .want = 1, .arr = &[_]u8{1} ** 64 },
.{ .want = 2, .arr = &[_]u8{1} ** 65 },
};
for (tests) |tt| {
try testing.expectEqual(tt.want, nblocks(tt.arr));
}
}