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;
|
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-03 18:05:46 +02:00
|
|
|
const shellImport = @import("shell.zig");
|
2022-02-28 10:31:14 +02:00
|
|
|
const userImport = @import("user.zig");
|
|
|
|
const groupImport = @import("group.zig");
|
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 User = userImport.User;
|
|
|
|
const Group = groupImport.Group;
|
2022-03-05 06:08:01 +02:00
|
|
|
const ShellSections = shellImport.ShellWriter.ShellSections;
|
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: []User,
|
|
|
|
// sorted by gid
|
|
|
|
groups: []Group,
|
|
|
|
|
2022-03-02 11:05:20 +02:00
|
|
|
// convenience users and groups by column
|
|
|
|
usersMulti: MultiArrayList(User),
|
|
|
|
groupsMulti: MultiArrayList(Group),
|
|
|
|
|
2022-02-28 10:31:14 +02:00
|
|
|
// pointing to `users` and `groups` slices above.
|
2022-03-07 06:09:20 +02:00
|
|
|
name2user: StringHashMap(usize),
|
|
|
|
uid2user: AutoHashMap(u32, usize),
|
|
|
|
name2group: StringHashMap(usize),
|
|
|
|
gid2group: AutoHashMap(u32, usize),
|
|
|
|
groupname2users: StringHashMap([]usize),
|
|
|
|
username2groups: StringHashMap([]usize),
|
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,
|
2022-03-06 18:01:52 +02:00
|
|
|
) 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
|
|
|
|
2022-02-28 10:31:14 +02:00
|
|
|
var users = try allocator.alloc(User, usersConst.len);
|
|
|
|
var groups = try allocator.alloc(Group, groupsConst.len);
|
2022-03-02 06:18:19 +02:00
|
|
|
for (usersConst) |*user, i|
|
|
|
|
users[i] = try user.clone(allocator);
|
|
|
|
for (groupsConst) |*group, i|
|
|
|
|
groups[i] = try group.clone(allocator);
|
2022-02-28 10:31:14 +02:00
|
|
|
|
|
|
|
sort.sort(User, users, {}, cmpUser);
|
|
|
|
sort.sort(Group, groups, {}, cmpGroup);
|
|
|
|
|
2022-03-02 11:05:20 +02:00
|
|
|
var usersMulti = MultiArrayList(User){};
|
|
|
|
try usersMulti.ensureTotalCapacity(allocator, users.len);
|
|
|
|
for (users) |user|
|
|
|
|
usersMulti.appendAssumeCapacity(user);
|
|
|
|
var groupsMulti = MultiArrayList(Group){};
|
|
|
|
try groupsMulti.ensureTotalCapacity(allocator, groups.len);
|
|
|
|
for (groups) |group|
|
|
|
|
groupsMulti.appendAssumeCapacity(group);
|
|
|
|
|
2022-03-07 06:09:20 +02:00
|
|
|
var name2user = StringHashMap(usize).init(allocator);
|
|
|
|
var uid2user = AutoHashMap(u32, usize).init(allocator);
|
|
|
|
var name2group = StringHashMap(usize).init(allocator);
|
|
|
|
var gid2group = AutoHashMap(u32, usize).init(allocator);
|
|
|
|
for (users) |*user, i| {
|
2022-02-28 10:31:14 +02:00
|
|
|
var res1 = try name2user.getOrPut(user.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-07 06:09:20 +02:00
|
|
|
res1.value_ptr.* = i;
|
2022-02-28 10:31:14 +02:00
|
|
|
|
|
|
|
var res2 = try uid2user.getOrPut(user.uid);
|
2022-03-02 06:18:19 +02:00
|
|
|
if (res2.found_existing)
|
2022-02-28 10:31:14 +02:00
|
|
|
return error.Duplicate;
|
2022-03-01 15:25:38 +02:00
|
|
|
|
2022-03-07 06:09:20 +02:00
|
|
|
res2.value_ptr.* = i;
|
2022-02-28 10:31:14 +02:00
|
|
|
}
|
|
|
|
|
2022-03-07 06:09:20 +02:00
|
|
|
for (groups) |*group, i| {
|
2022-02-28 10:31:14 +02:00
|
|
|
var res1 = try name2group.getOrPut(group.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-07 06:09:20 +02:00
|
|
|
res1.value_ptr.* = i;
|
2022-02-28 10:31:14 +02:00
|
|
|
var res2 = try gid2group.getOrPut(group.gid);
|
2022-03-02 06:18:19 +02:00
|
|
|
if (res2.found_existing)
|
2022-02-28 10:31:14 +02:00
|
|
|
return error.Duplicate;
|
2022-03-07 06:09:20 +02:00
|
|
|
res2.value_ptr.* = i;
|
2022-02-28 10:31:14 +02:00
|
|
|
}
|
|
|
|
|
2022-03-07 06:09:20 +02:00
|
|
|
var groupname2users = StringHashMap([]usize).init(allocator);
|
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-02 07:15:08 +02:00
|
|
|
var username2groups = StringHashMap(
|
2022-03-07 06:09:20 +02:00
|
|
|
ArrayListUnmanaged(usize),
|
2022-03-02 07:15:08 +02:00
|
|
|
).init(baseAllocator);
|
2022-03-05 05:33:31 +02:00
|
|
|
defer username2groups.deinit();
|
2022-03-04 10:37:07 +02:00
|
|
|
|
2022-03-07 06:09:20 +02:00
|
|
|
for (groups) |*group, i| {
|
|
|
|
var members = try allocator.alloc(usize, group.members.count());
|
2022-03-02 11:05:20 +02:00
|
|
|
members.len = 0;
|
2022-02-28 10:31:14 +02:00
|
|
|
|
|
|
|
var it = group.members.iterator();
|
|
|
|
while (it.next()) |memberName| {
|
2022-03-07 06:09:20 +02:00
|
|
|
if (name2user.get(memberName.*)) |idx| {
|
2022-03-02 11:05:20 +02:00
|
|
|
members.len += 1;
|
2022-03-07 06:09:20 +02:00
|
|
|
members[members.len - 1] = idx;
|
2022-02-28 10:31:14 +02:00
|
|
|
} else {
|
|
|
|
return error.NotFound;
|
|
|
|
}
|
|
|
|
|
2022-03-01 11:01:45 +02:00
|
|
|
var groupsOfMember = try username2groups.getOrPut(memberName.*);
|
2022-03-02 06:18:19 +02:00
|
|
|
if (!groupsOfMember.found_existing)
|
2022-03-07 06:09:20 +02:00
|
|
|
groupsOfMember.value_ptr.* = ArrayListUnmanaged(usize){};
|
|
|
|
try groupsOfMember.value_ptr.*.append(allocator, i);
|
2022-02-28 10:31:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var result = try groupname2users.getOrPut(group.name);
|
2022-03-02 06:18:19 +02:00
|
|
|
if (result.found_existing)
|
2022-02-28 10:31:14 +02:00
|
|
|
return error.Duplicate;
|
|
|
|
result.value_ptr.* = members;
|
|
|
|
}
|
|
|
|
|
2022-03-02 11:05:20 +02:00
|
|
|
var it1 = groupname2users.valueIterator();
|
|
|
|
while (it1.next()) |groupUsers| {
|
2022-03-07 06:09:20 +02:00
|
|
|
sort.sort(usize, groupUsers.*, {}, comptime sort.asc(usize));
|
2022-03-02 06:02:04 +02:00
|
|
|
}
|
|
|
|
|
2022-03-02 11:05:20 +02:00
|
|
|
var it2 = username2groups.valueIterator();
|
|
|
|
while (it2.next()) |userGroups|
|
2022-03-07 06:09:20 +02:00
|
|
|
sort.sort(usize, userGroups.items, {}, comptime sort.asc(usize));
|
2022-03-02 07:15:08 +02:00
|
|
|
|
2022-03-07 06:09:20 +02:00
|
|
|
var username2groups_final = StringHashMap([]usize).init(allocator);
|
2022-03-04 10:37:07 +02:00
|
|
|
var it = username2groups.iterator();
|
|
|
|
while (it.next()) |elem| {
|
|
|
|
const username = elem.key_ptr.*;
|
|
|
|
const usergroups = elem.value_ptr.*.toOwnedSlice(allocator);
|
|
|
|
try username2groups_final.put(username, usergroups);
|
2022-03-02 07:15:08 +02:00
|
|
|
}
|
|
|
|
|
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,
|
2022-03-02 11:05:20 +02:00
|
|
|
.usersMulti = usersMulti,
|
|
|
|
.groupsMulti = groupsMulti,
|
2022-02-28 10:31:14 +02:00
|
|
|
.name2user = name2user,
|
|
|
|
.uid2user = uid2user,
|
|
|
|
.name2group = name2group,
|
|
|
|
.gid2group = gid2group,
|
2022-03-02 11:05:20 +02:00
|
|
|
.groupname2users = groupname2users,
|
2022-03-02 07:15:08 +02:00
|
|
|
.username2groups = username2groups_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
|
|
|
};
|
|
|
|
|
2022-03-04 11:01:19 +02:00
|
|
|
pub fn bdzGid(allocator: Allocator, corpus: *const Corpus) cmph.Error![]const u8 {
|
|
|
|
return try cmph.pack_u32(allocator, corpus.groupsMulti.items(.gid));
|
|
|
|
}
|
2022-03-02 06:50:15 +02:00
|
|
|
|
2022-03-04 11:01:19 +02:00
|
|
|
pub fn bdzGroupname(allocator: Allocator, corpus: *const Corpus) cmph.Error![]const u8 {
|
|
|
|
return try cmph.pack_str(allocator, corpus.groupsMulti.items(.name));
|
|
|
|
}
|
2022-03-02 06:50:15 +02:00
|
|
|
|
2022-03-04 11:01:19 +02:00
|
|
|
pub fn bdzUid(allocator: Allocator, corpus: *const Corpus) cmph.Error![]const u8 {
|
|
|
|
return try cmph.pack_u32(allocator, corpus.usersMulti.items(.uid));
|
|
|
|
}
|
2022-03-02 06:50:15 +02:00
|
|
|
|
2022-03-04 11:01:19 +02:00
|
|
|
pub fn bdzUsername(allocator: Allocator, corpus: *const Corpus) cmph.Error![]const u8 {
|
|
|
|
return try cmph.pack_str(allocator, corpus.usersMulti.items(.name));
|
|
|
|
}
|
2022-03-03 18:05:46 +02:00
|
|
|
|
2022-03-04 11:01:19 +02:00
|
|
|
// TODO(motiejus) there are a few problems:
|
|
|
|
// - memory management for shell sections is a mess. Make it easier by ...
|
|
|
|
// - shell module should accept a list of shells and spit out two slices
|
|
|
|
// (allocated with a given allocator). There is too much dancing around
|
|
|
|
// here.
|
|
|
|
pub fn shellSections(
|
|
|
|
allocator: Allocator,
|
|
|
|
corpus: *const Corpus,
|
2022-03-06 18:01:52 +02:00
|
|
|
) error{ OutOfMemory, Overflow }!ShellSections {
|
2022-03-04 11:01:19 +02:00
|
|
|
var popcon = shellImport.ShellWriter.init(allocator);
|
|
|
|
for (corpus.usersMulti.items(.shell)) |shell| {
|
|
|
|
try popcon.put(shell);
|
2022-03-03 18:05:46 +02:00
|
|
|
}
|
2022-03-05 06:08:01 +02:00
|
|
|
return popcon.toOwnedSections(shellImport.max_shells);
|
2022-03-04 11:01:19 +02:00
|
|
|
}
|
2022-03-03 18:05:46 +02:00
|
|
|
|
2022-03-04 11:01:19 +02:00
|
|
|
pub const UserGids = struct {
|
2022-03-07 06:09:20 +02:00
|
|
|
// user index -> offset in blob
|
|
|
|
idx2offset: []const u32,
|
2022-03-04 11:01:19 +02:00
|
|
|
// 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,
|
2022-03-04 11:01:19 +02:00
|
|
|
|
|
|
|
pub fn deinit(self: *UserGids, allocator: Allocator) void {
|
2022-03-07 06:09:20 +02:00
|
|
|
allocator.free(self.idx2offset);
|
2022-03-04 11:01:19 +02:00
|
|
|
allocator.free(self.blob);
|
|
|
|
self.* = undefined;
|
2022-03-03 18:05:46 +02:00
|
|
|
}
|
2022-03-04 11:01:19 +02:00
|
|
|
};
|
2022-03-02 06:50:15 +02:00
|
|
|
|
2022-03-06 17:55:29 +02:00
|
|
|
const userGidsPaddingBits = 3;
|
|
|
|
|
2022-03-06 18:01:52 +02:00
|
|
|
pub fn userGids(
|
|
|
|
allocator: Allocator,
|
|
|
|
corpus: *const Corpus,
|
|
|
|
) error{ OutOfMemory, Overflow }!UserGids {
|
2022-03-04 11:01:19 +02:00
|
|
|
var blob = ArrayList(u8).init(allocator);
|
2022-03-05 05:33:31 +02:00
|
|
|
errdefer blob.deinit();
|
2022-03-07 06:09:20 +02:00
|
|
|
var idx2offset = try allocator.alloc(u32, corpus.users.len);
|
|
|
|
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-06 17:55:29 +02:00
|
|
|
try pad.arrayList(&blob, userGidsPaddingBits);
|
2022-03-06 13:11:06 +02:00
|
|
|
|
2022-03-05 05:33:31 +02:00
|
|
|
var scratch = try allocator.alloc(u32, 256);
|
|
|
|
defer allocator.free(scratch);
|
2022-03-07 06:09:20 +02:00
|
|
|
for (corpus.users) |user, user_idx| {
|
2022-03-06 13:11:06 +02:00
|
|
|
if (corpus.username2groups.get(user.name)) |usergroups| {
|
2022-03-07 06:09:20 +02:00
|
|
|
const userOffset = try math.cast(u32, blob.items.len);
|
|
|
|
std.debug.assert(userOffset & 7 == 0);
|
|
|
|
idx2offset[user_idx] = userOffset;
|
2022-03-06 13:11:06 +02:00
|
|
|
scratch = try allocator.realloc(scratch, usergroups.len);
|
|
|
|
scratch.len = usergroups.len;
|
2022-03-07 06:09:20 +02:00
|
|
|
for (usergroups) |group_idx, i|
|
|
|
|
scratch[i] = corpus.groups[group_idx].gid;
|
2022-03-06 13:11:06 +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-06 17:55:29 +02:00
|
|
|
try pad.arrayList(&blob, userGidsPaddingBits);
|
2022-03-06 13:11:06 +02:00
|
|
|
} else {
|
2022-03-07 06:09:20 +02:00
|
|
|
idx2offset[user_idx] = 0;
|
2022-03-06 13:11:06 +02:00
|
|
|
}
|
2022-03-04 10:37:07 +02:00
|
|
|
}
|
|
|
|
|
2022-03-04 11:01:19 +02:00
|
|
|
return UserGids{
|
2022-03-07 06:09:20 +02:00
|
|
|
.idx2offset = idx2offset,
|
2022-03-04 11:01:19 +02:00
|
|
|
.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);
|
2022-03-05 10:19:42 +02:00
|
|
|
// as of writing each user takes 15 bytes + strings + 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();
|
|
|
|
for (corpus.users) |user, i| {
|
|
|
|
const userOffset = try math.cast(u32, blob.items.len);
|
|
|
|
const gidOffset = gids.idx2offset[i];
|
|
|
|
std.debug.assert(userOffset & 7 == 0);
|
|
|
|
std.debug.assert(gidOffset & 7 == 0);
|
|
|
|
idx2offset[i] = userOffset;
|
2022-03-06 17:55:29 +02:00
|
|
|
try userImport.PackedUserHash.packTo(
|
2022-03-07 06:09:20 +02:00
|
|
|
&blob,
|
2022-03-06 13:11:06 +02:00
|
|
|
user,
|
2022-03-07 06:09:20 +02:00
|
|
|
@truncate(u29, @shrExact(gidOffset, 3)),
|
2022-03-06 17:55:29 +02:00
|
|
|
shells.indices,
|
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
|
|
|
|
idx2offset: []const u32,
|
|
|
|
blob: []const u8,
|
|
|
|
|
|
|
|
pub fn deinit(self: *GroupMembers, allocator: Allocator) void {
|
|
|
|
allocator.free(self.idx2offset);
|
|
|
|
allocator.free(self.blob);
|
|
|
|
self.* = undefined;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-03-06 18:01:52 +02:00
|
|
|
pub fn groupMembers(
|
|
|
|
allocator: Allocator,
|
|
|
|
corpus: *const Corpus,
|
2022-03-07 06:09:20 +02:00
|
|
|
user2offset: []const u32,
|
|
|
|
) error{OutOfMemory}!GroupMembers {
|
|
|
|
var idx2offset = try allocator.alloc(u32, corpus.groups.len);
|
|
|
|
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-04 11:01:19 +02:00
|
|
|
for (corpus.groups) |group, i| {
|
|
|
|
const users = corpus.groupname2users.get(group.name).?;
|
2022-03-07 06:09:20 +02:00
|
|
|
if (users.len == 0) {
|
|
|
|
idx2offset[i] = 0;
|
|
|
|
continue;
|
2022-03-02 06:50:15 +02:00
|
|
|
}
|
2022-03-07 06:09:20 +02:00
|
|
|
|
|
|
|
idx2offset[i] = blob.len;
|
|
|
|
compress.appendUvarint(&blob, users.len);
|
|
|
|
for (users) |userIdx|
|
|
|
|
compress.appendUvarint(&blob, user2offset[userIdx]);
|
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-04 11:01:19 +02:00
|
|
|
}
|
2022-03-02 06:50:15 +02:00
|
|
|
|
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;
|
|
|
|
} else {
|
2022-02-28 10:31:14 +02:00
|
|
|
return codepoint_a < codepoint_b;
|
|
|
|
}
|
|
|
|
}
|
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-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,
|
|
|
|
shell_index: []const u8,
|
|
|
|
shell_blob: []const u8,
|
|
|
|
|
|
|
|
user_gids: UserGids,
|
|
|
|
user_gids_b: []const u8,
|
|
|
|
|
|
|
|
pub fn init(
|
|
|
|
allocator: Allocator,
|
|
|
|
corpus: *const Corpus,
|
|
|
|
) error{ Overflow, OutOfMemory, InvalidRecord }!AllSections {
|
|
|
|
const bdz_gid = try bdzGid(allocator, corpus);
|
|
|
|
const bdz_groupname = try bdzGroupname(allocator, corpus);
|
|
|
|
const bdz_uid = try bdzUid(allocator, corpus);
|
|
|
|
const bdz_username = try bdzUsername(allocator, corpus);
|
|
|
|
const shell_sections = try shellSections(allocator, corpus);
|
|
|
|
const shell_index = shell_sections.index;
|
|
|
|
const shell_blob = shell_sections.blob;
|
|
|
|
const user_gids = try userGids(allocator, corpus);
|
|
|
|
const users = try usersSection(
|
|
|
|
allocator,
|
|
|
|
corpus,
|
|
|
|
&user_gids,
|
|
|
|
&shell_sections,
|
|
|
|
);
|
|
|
|
return AllSections{
|
|
|
|
.allocator = allocator,
|
|
|
|
.bdz_gid = bdz_gid,
|
|
|
|
.bdz_groupname = bdz_groupname,
|
|
|
|
.bdz_uid = bdz_uid,
|
|
|
|
.bdz_username = bdz_username,
|
|
|
|
.shell_sections = shell_sections,
|
|
|
|
.shell_index = mem.sliceAsBytes(shell_index.constSlice()),
|
|
|
|
.shell_blob = mem.sliceAsBytes(shell_blob.constSlice()),
|
|
|
|
.user_gids = user_gids,
|
|
|
|
.user_gids_b = user_gids.blob,
|
|
|
|
.users = users,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
self.* = undefined;
|
|
|
|
}
|
|
|
|
};
|
2022-03-02 06:02:04 +02:00
|
|
|
|
2022-02-28 10:31:14 +02:00
|
|
|
const testing = std.testing;
|
|
|
|
|
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-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{
|
|
|
|
.uid = 0,
|
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{
|
|
|
|
.uid = 1002,
|
|
|
|
.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-01 11:01:45 +02:00
|
|
|
var members1 = try groupImport.someMembers(
|
|
|
|
allocator,
|
|
|
|
&[_][]const u8{"vidmantas"},
|
|
|
|
);
|
2022-03-01 05:47:44 +02:00
|
|
|
defer members1.deinit();
|
|
|
|
|
2022-03-01 11:01:45 +02:00
|
|
|
var members2 = try groupImport.someMembers(
|
|
|
|
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-01 11:01:45 +02:00
|
|
|
var members3 = try groupImport.someMembers(
|
|
|
|
allocator,
|
|
|
|
&[_][]const u8{ "svc-bar", "Name" ** 8, "vidmantas" },
|
|
|
|
);
|
2022-03-01 05:47:44 +02:00
|
|
|
defer members3.deinit();
|
|
|
|
|
2022-03-01 15:25:38 +02:00
|
|
|
const groups = [_]Group{ Group{
|
2022-03-02 11:05:20 +02:00
|
|
|
.gid = 128,
|
2022-02-28 10:31:14 +02:00
|
|
|
.name = "vidmantas",
|
2022-03-01 05:47:44 +02:00
|
|
|
.members = members1,
|
2022-02-28 10:31:14 +02:00
|
|
|
}, Group{
|
|
|
|
.gid = 9999,
|
2022-03-01 11:01:45 +02:00
|
|
|
.name = "all",
|
2022-03-01 05:47:44 +02:00
|
|
|
.members = members3,
|
2022-03-01 15:25:38 +02:00
|
|
|
}, Group{
|
2022-03-02 11:05:20 +02:00
|
|
|
.gid = 0,
|
2022-03-01 15:25:38 +02:00
|
|
|
.name = "service-account",
|
|
|
|
.members = members2,
|
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;
|
|
|
|
const svc_bar = 2;
|
|
|
|
const vidmantas = 3;
|
|
|
|
|
|
|
|
try testing.expectEqualStrings(corpus.users[name_name].name, "Name" ** 8);
|
|
|
|
try testing.expectEqualStrings(corpus.users[nobody].name, "nobody");
|
|
|
|
try testing.expectEqualStrings(corpus.users[svc_bar].name, "svc-bar");
|
|
|
|
try testing.expectEqualStrings(corpus.users[vidmantas].name, "vidmantas");
|
|
|
|
|
|
|
|
const g_service_account = 0;
|
|
|
|
const g_vidmantas = 1;
|
|
|
|
const g_all = 2;
|
|
|
|
|
|
|
|
try testing.expectEqualStrings(corpus.groups[g_service_account].name, "service-account");
|
|
|
|
try testing.expectEqualStrings(corpus.groups[g_vidmantas].name, "vidmantas");
|
|
|
|
try testing.expectEqualStrings(corpus.groups[g_all].name, "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.uid2user.get(42), null);
|
2022-03-07 06:09:20 +02:00
|
|
|
try testing.expectEqual(corpus.uid2user.get(128).?, 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
|
|
|
try testing.expectEqual(corpus.gid2group.get(42), null);
|
2022-03-07 06:09:20 +02:00
|
|
|
try testing.expectEqual(corpus.gid2group.get(128).?, g_vidmantas);
|
2022-03-01 15:25:38 +02:00
|
|
|
|
2022-03-02 07:15:08 +02:00
|
|
|
const membersOfAll = corpus.groupname2users.get("all").?;
|
2022-03-07 06:09:20 +02:00
|
|
|
try testing.expectEqual(membersOfAll[0], name_name);
|
|
|
|
try testing.expectEqual(membersOfAll[1], svc_bar);
|
|
|
|
try testing.expectEqual(membersOfAll[2], vidmantas);
|
2022-03-02 06:02:45 +02:00
|
|
|
try testing.expectEqual(corpus.groupname2users.get("404"), null);
|
2022-03-01 15:25:38 +02:00
|
|
|
|
2022-03-02 07:15:08 +02:00
|
|
|
const groupsOfVidmantas = corpus.username2groups.get("vidmantas").?;
|
2022-03-07 06:09:20 +02:00
|
|
|
try testing.expectEqual(groupsOfVidmantas[0], g_service_account);
|
|
|
|
try testing.expectEqual(groupsOfVidmantas[1], g_vidmantas);
|
|
|
|
try testing.expectEqual(groupsOfVidmantas[2], g_all);
|
2022-03-04 10:37:07 +02:00
|
|
|
try testing.expectEqual(corpus.username2groups.get("nobody"), null);
|
|
|
|
try testing.expectEqual(corpus.username2groups.get("doesnotexist"), null);
|
2022-02-28 10:31:14 +02:00
|
|
|
}
|
|
|
|
|
2022-03-03 18:05:46 +02:00
|
|
|
test "test sections" {
|
|
|
|
const allocator = testing.allocator;
|
|
|
|
var corpus = try testCorpus(allocator);
|
|
|
|
defer corpus.deinit();
|
|
|
|
|
2022-03-07 06:09:20 +02:00
|
|
|
var all = try AllSections.init(allocator, &corpus);
|
|
|
|
defer all.deinit();
|
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-07 06:09:20 +02:00
|
|
|
for (corpus.users) |user, userIdx| {
|
2022-03-06 09:57:44 +02:00
|
|
|
const groups = corpus.username2groups.get(user.name);
|
2022-03-07 06:09:20 +02:00
|
|
|
const offset = user_gids.idx2offset[userIdx];
|
2022-03-06 09:57:44 +02:00
|
|
|
if (groups == null) {
|
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);
|
|
|
|
try testing.expectEqual(it.remaining(), groups.?.len);
|
|
|
|
var i: usize = 0;
|
|
|
|
while (try it.next()) |gid| : (i += 1) {
|
2022-03-07 06:09:20 +02:00
|
|
|
try testing.expectEqual(gid, corpus.groups[groups.?[i]].gid);
|
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.pack_u32(allocator, corpus.groupsMulti.items(.gid));
|
|
|
|
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);
|
|
|
|
var hashes = &[_]u32{ k1, k2, k3 };
|
|
|
|
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));
|
|
|
|
}
|