2022-02-28 10:31:14 +02:00
|
|
|
const std = @import("std");
|
|
|
|
const unicode = std.unicode;
|
|
|
|
const sort = std.sort;
|
|
|
|
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-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-02-28 10:31:14 +02:00
|
|
|
const userImport = @import("user.zig");
|
|
|
|
const groupImport = @import("group.zig");
|
|
|
|
const User = userImport.User;
|
|
|
|
const Group = groupImport.Group;
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
|
|
// pointing to `users` and `groups` slices above.
|
|
|
|
name2user: StringHashMap(*const User),
|
|
|
|
uid2user: AutoHashMap(u32, *const User),
|
|
|
|
name2group: StringHashMap(*const Group),
|
|
|
|
gid2group: AutoHashMap(u32, *const Group),
|
2022-03-02 06:02:04 +02:00
|
|
|
groupname2users: StringHashMap(ArrayListUnmanaged(*const User)),
|
|
|
|
username2groups: StringHashMap(ArrayListUnmanaged(*const Group)),
|
2022-02-28 10:31:14 +02:00
|
|
|
|
|
|
|
pub const initErr = Allocator.Error ||
|
|
|
|
error{InvalidUtf8} ||
|
|
|
|
error{Duplicate} ||
|
|
|
|
error{NotFound};
|
|
|
|
|
|
|
|
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,
|
|
|
|
) initErr!Corpus {
|
2022-03-01 05:47:44 +02:00
|
|
|
var arena = std.heap.ArenaAllocator.init(baseAllocator);
|
|
|
|
var allocator = arena.allocator();
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
var name2user = StringHashMap(*const User).init(allocator);
|
|
|
|
var uid2user = AutoHashMap(u32, *const User).init(allocator);
|
|
|
|
var name2group = StringHashMap(*const Group).init(allocator);
|
|
|
|
var gid2group = AutoHashMap(u32, *const Group).init(allocator);
|
2022-03-01 15:25:38 +02:00
|
|
|
for (users) |*user| {
|
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-01 15:25:38 +02:00
|
|
|
res1.value_ptr.* = user;
|
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
|
|
|
|
|
|
|
res2.value_ptr.* = user;
|
2022-02-28 10:31:14 +02:00
|
|
|
}
|
|
|
|
|
2022-03-01 15:25:38 +02:00
|
|
|
for (groups) |*group| {
|
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-01 15:25:38 +02:00
|
|
|
res1.value_ptr.* = group;
|
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-01 15:25:38 +02:00
|
|
|
res2.value_ptr.* = group;
|
2022-02-28 10:31:14 +02:00
|
|
|
}
|
|
|
|
|
2022-03-02 06:02:04 +02:00
|
|
|
var groupname2users = StringHashMap(ArrayListUnmanaged(*const User)).init(allocator);
|
|
|
|
var username2groups = StringHashMap(ArrayListUnmanaged(*const Group)).init(allocator);
|
2022-03-01 15:25:38 +02:00
|
|
|
for (groups) |*group| {
|
2022-03-02 06:02:04 +02:00
|
|
|
var members = try ArrayListUnmanaged(*const User).initCapacity(
|
2022-03-01 11:01:45 +02:00
|
|
|
allocator,
|
|
|
|
group.members.count(),
|
|
|
|
);
|
2022-02-28 10:31:14 +02:00
|
|
|
|
|
|
|
var it = group.members.iterator();
|
|
|
|
while (it.next()) |memberName| {
|
|
|
|
if (name2user.get(memberName.*)) |user| {
|
|
|
|
members.appendAssumeCapacity(user);
|
|
|
|
} 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-02 06:02:04 +02:00
|
|
|
groupsOfMember.value_ptr.* = ArrayListUnmanaged(*const Group){};
|
|
|
|
try groupsOfMember.value_ptr.*.append(allocator, group);
|
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 06:02:04 +02:00
|
|
|
{
|
|
|
|
var it = groupname2users.valueIterator();
|
2022-03-02 06:18:19 +02:00
|
|
|
while (it.next()) |groupUsers|
|
2022-03-02 06:02:04 +02:00
|
|
|
sort.sort(*const User, groupUsers.items, {}, cmpUserPtr);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
var it = username2groups.valueIterator();
|
2022-03-02 06:18:19 +02:00
|
|
|
while (it.next()) |userGroups|
|
2022-03-02 06:02:04 +02:00
|
|
|
sort.sort(*const Group, userGroups.items, {}, cmpGroupPtr);
|
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
.uid2user = uid2user,
|
|
|
|
.name2group = name2group,
|
|
|
|
.gid2group = gid2group,
|
|
|
|
.groupname2users = groupname2users,
|
|
|
|
.username2groups = username2groups,
|
|
|
|
};
|
|
|
|
}
|
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-02 06:50:15 +02:00
|
|
|
pub const Sections = struct {
|
|
|
|
allocator: Allocator,
|
|
|
|
corpus: *const Corpus,
|
|
|
|
|
|
|
|
pub fn init(allocator: Allocator, corpus: *const Corpus) Sections {
|
|
|
|
return Sections{
|
|
|
|
.allocator = allocator,
|
|
|
|
.corpus = corpus,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub const GroupMembers = struct {
|
|
|
|
offsets: []const usize,
|
|
|
|
bytes: []const u8,
|
|
|
|
};
|
|
|
|
|
|
|
|
const groupMembersErr = error{Overflow} || Allocator.Error;
|
|
|
|
|
|
|
|
pub fn groupMembers(self: *const Sections) groupMembersErr!GroupMembers {
|
|
|
|
var buf: [compress.maxVarintLen64]u8 = undefined;
|
|
|
|
var offsets = ArrayListUnmanaged(usize).initCapacity(
|
|
|
|
self.allocator,
|
|
|
|
self.corpus.groups.len,
|
|
|
|
);
|
|
|
|
var bytes = ArrayList(u8).init(self.allocator);
|
|
|
|
var offset: usize = 0;
|
|
|
|
for (self.corpus.groups) |group, i| {
|
|
|
|
offsets[i] = offset;
|
|
|
|
const users = self.corpus.groupname2users.get(group.name).?;
|
|
|
|
const len = try compress.putVarint(&buf, users.len);
|
|
|
|
offset += len;
|
|
|
|
try bytes.appendSlice(buf[0..len]);
|
|
|
|
for (users) |user| {
|
|
|
|
// TODO: offset into the User's record
|
|
|
|
_ = user;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-03-02 06:02:04 +02:00
|
|
|
fn cmpUserPtr(context: void, a: *const User, b: *const User) bool {
|
|
|
|
return cmpUser(context, a.*, b.*);
|
|
|
|
}
|
|
|
|
|
2022-02-28 10:31:14 +02:00
|
|
|
fn cmpGroup(_: void, a: Group, b: Group) bool {
|
|
|
|
return a.gid < b.gid;
|
|
|
|
}
|
|
|
|
|
2022-03-02 06:02:04 +02:00
|
|
|
fn cmpGroupPtr(context: void, a: *const Group, b: *const Group) bool {
|
|
|
|
return cmpGroup(context, a.*, b.*);
|
|
|
|
}
|
|
|
|
|
2022-02-28 10:31:14 +02:00
|
|
|
const testing = std.testing;
|
|
|
|
|
|
|
|
test "test corpus" {
|
|
|
|
const allocator = testing.allocator;
|
|
|
|
|
2022-03-01 15:25:38 +02:00
|
|
|
const users = [_]User{ User{
|
2022-02-28 10:31:14 +02:00
|
|
|
.uid = 1000,
|
|
|
|
.gid = 1000,
|
|
|
|
.name = "vidmantas",
|
|
|
|
.gecos = "Vidmantas Kaminskas",
|
|
|
|
.home = "/home/vidmantas",
|
|
|
|
.shell = "/bin/bash",
|
|
|
|
}, User{
|
|
|
|
.uid = 0,
|
|
|
|
.gid = std.math.maxInt(u32),
|
|
|
|
.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-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-02-28 10:31:14 +02:00
|
|
|
.gid = 1000,
|
|
|
|
.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{
|
|
|
|
.gid = 1234,
|
|
|
|
.name = "service-account",
|
|
|
|
.members = members2,
|
2022-02-28 10:31:14 +02:00
|
|
|
} };
|
|
|
|
|
2022-03-01 05:47:44 +02:00
|
|
|
var corpus = try Corpus.init(allocator, users[0..], groups[0..]);
|
|
|
|
defer corpus.deinit();
|
|
|
|
try testing.expectEqualStrings(corpus.users[0].name, "Name" ** 8);
|
|
|
|
try testing.expectEqualStrings(corpus.users[1].name, "svc-bar");
|
|
|
|
try testing.expectEqualStrings(corpus.users[2].name, "vidmantas");
|
2022-03-01 11:01:45 +02:00
|
|
|
|
2022-03-01 15:25:38 +02:00
|
|
|
try testing.expectEqual(corpus.name2user.get("404"), null);
|
|
|
|
try testing.expectEqual(corpus.name2user.get("vidmantas").?.uid, 1000);
|
|
|
|
try testing.expectEqual(corpus.uid2user.get(42), null);
|
|
|
|
try testing.expectEqual(corpus.uid2user.get(1000).?.gid, 1000);
|
|
|
|
try testing.expectEqual(corpus.name2group.get("404"), null);
|
|
|
|
try testing.expectEqual(corpus.name2group.get("vidmantas").?.gid, 1000);
|
|
|
|
try testing.expectEqual(corpus.gid2group.get(42), null);
|
|
|
|
try testing.expectEqual(corpus.gid2group.get(1000).?.gid, 1000);
|
|
|
|
|
2022-03-02 06:02:04 +02:00
|
|
|
const membersOfAll = corpus.groupname2users.get("all").?.items;
|
|
|
|
try testing.expectEqualStrings(membersOfAll[0].name, "Name" ** 8);
|
|
|
|
try testing.expectEqualStrings(membersOfAll[1].name, "svc-bar");
|
|
|
|
try testing.expectEqualStrings(membersOfAll[2].name, "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
|
|
|
|
|
|
|
const groupsOfVidmantas = corpus.username2groups.get("vidmantas").?.items;
|
|
|
|
try testing.expectEqual(groupsOfVidmantas[0].gid, 1000);
|
|
|
|
try testing.expectEqual(groupsOfVidmantas[1].gid, 1234);
|
|
|
|
try testing.expectEqual(groupsOfVidmantas[2].gid, 9999);
|
2022-03-02 06:02:45 +02:00
|
|
|
try testing.expectEqual(corpus.username2groups.get("404"), null);
|
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));
|
|
|
|
}
|