Motiejus Jakštys
64bd5df633
This way we can use buffers in both Zig and C, without the performance or size penalty.
163 lines
4.7 KiB
Zig
163 lines
4.7 KiB
Zig
const std = @import("std");
|
|
const mem = std.mem;
|
|
const fmt = std.fmt;
|
|
const maxInt = std.math.maxInt;
|
|
const Allocator = mem.Allocator;
|
|
const ArrayList = std.ArrayList;
|
|
const BoundedArray = std.BoundedArray;
|
|
|
|
const User = @This();
|
|
const PackedUser = @import("PackedUser.zig");
|
|
const validate = @import("validate.zig");
|
|
|
|
uid: u32,
|
|
gid: u32,
|
|
name: []const u8,
|
|
gecos: []const u8,
|
|
home: []const u8,
|
|
shell: []const u8,
|
|
|
|
// deep-clones a User record with a given Allocator.
|
|
pub fn clone(
|
|
self: *const User,
|
|
allocator: Allocator,
|
|
) error{OutOfMemory}!User {
|
|
const stringdata = try allocator.alloc(u8, self.strlen());
|
|
const gecos_start = self.name.len;
|
|
const home_start = gecos_start + self.gecos.len;
|
|
const shell_start = home_start + self.home.len;
|
|
mem.copy(u8, stringdata[0..self.name.len], self.name);
|
|
mem.copy(u8, stringdata[gecos_start..], self.gecos);
|
|
mem.copy(u8, stringdata[home_start..], self.home);
|
|
mem.copy(u8, stringdata[shell_start..], self.shell);
|
|
|
|
return User{
|
|
.uid = self.uid,
|
|
.gid = self.gid,
|
|
.name = stringdata[0..self.name.len],
|
|
.gecos = stringdata[gecos_start .. gecos_start + self.gecos.len],
|
|
.home = stringdata[home_start .. home_start + self.home.len],
|
|
.shell = stringdata[shell_start .. shell_start + self.shell.len],
|
|
};
|
|
}
|
|
|
|
const line_fmt = "{s}:x:{d}:{d}:{s}:{s}:{s}\n";
|
|
|
|
const max_line_len = fmt.count(line_fmt, .{
|
|
max_user.uid,
|
|
max_user.gid,
|
|
max_user.gecos,
|
|
max_user.home,
|
|
max_user.shell,
|
|
});
|
|
|
|
// toPasswdLine formats the user in /etc/passwd format, including the EOL
|
|
fn toLine(self: *const User) BoundedArray(u8, max_line_len) {
|
|
var result = BoundedArray(u8, max_line_len);
|
|
fmt.bufPrint(result.slice(), line_fmt, .{
|
|
self.uid,
|
|
self.gid,
|
|
self.gecos,
|
|
self.home,
|
|
self.shell,
|
|
});
|
|
return result;
|
|
}
|
|
|
|
// fromLine accepts a line of /etc/passwd (with or without the EOL) and makes a
|
|
// User.
|
|
fn fromLine(allocator: Allocator, line: []const u8) error{ InvalidRecord, OutOfMemory }!User {
|
|
var it = mem.split(u8, line, ":");
|
|
const uids = it.next() orelse return error.InvalidRecord;
|
|
const gids = it.next() orelse return error.InvalidRecord;
|
|
const name = it.next() orelse return error.InvalidRecord;
|
|
const gecos = it.next() orelse return error.InvalidRecord;
|
|
const home = it.next() orelse return error.InvalidRecord;
|
|
const shell = it.next() orelse return error.InvalidRecord;
|
|
// the line must be exhaustive.
|
|
if (it.next() != null) return error.InvalidRecord;
|
|
|
|
const uid = fmt.parseInt(u32, uids) orelse return error.InvalidRecord;
|
|
const gid = fmt.parseInt(u32, gids) orelse return error.InvalidRecord;
|
|
|
|
try validate.utf8(name);
|
|
try validate.utf8(gecos);
|
|
try validate.utf8(home);
|
|
try validate.utf8(shell);
|
|
|
|
const user = User{
|
|
.uid = uid,
|
|
.gid = gid,
|
|
.name = name,
|
|
.gecos = gecos,
|
|
.home = home,
|
|
.shell = shell,
|
|
};
|
|
return try user.clone(allocator);
|
|
}
|
|
|
|
fn strlen(self: *const User) usize {
|
|
return self.name.len +
|
|
self.gecos.len +
|
|
self.home.len +
|
|
self.shell.len;
|
|
}
|
|
|
|
// length of all string-data fields, assuming they are zero-terminated.
|
|
// Does not include password, since that's always static 'x\00'.
|
|
pub fn strlenZ(self: *const User) usize {
|
|
return self.strlen() + 4; // '\0' of name, gecos, home and shell
|
|
}
|
|
|
|
pub fn deinit(self: *User, allocator: Allocator) void {
|
|
const slice = self.home.ptr[0..self.strlen()];
|
|
allocator.free(slice);
|
|
self.* = undefined;
|
|
}
|
|
|
|
pub fn fromReader(allocator: Allocator, reader: anytype) ![]User {
|
|
var users = ArrayList(User).init(allocator);
|
|
var scratch = ArrayList(u8).init(allocator);
|
|
defer scratch.deinit();
|
|
while (true) {
|
|
const line = reader.readUntilDelimiterArrayList(&scratch, '\n', maxInt(usize));
|
|
_ = line;
|
|
}
|
|
_ = users;
|
|
}
|
|
|
|
const testing = std.testing;
|
|
|
|
pub const max_user = User{
|
|
.uid = maxInt(u32),
|
|
.gid = maxInt(u32),
|
|
.name = "Name" ** 8,
|
|
.gecos = "realname" ** 255 ++ "realnam",
|
|
.home = "Home" ** 16,
|
|
.shell = "She.LllL" ** 32,
|
|
};
|
|
|
|
test "max_user and max_str_len are consistent" {
|
|
const total_len = max_user.name.len +
|
|
max_user.gecos.len +
|
|
max_user.home.len +
|
|
max_user.shell.len;
|
|
try testing.expectEqual(total_len, PackedUser.max_str_len);
|
|
}
|
|
|
|
test "User.clone" {
|
|
var allocator = testing.allocator;
|
|
const user = User{
|
|
.uid = 1000,
|
|
.gid = 1000,
|
|
.name = "vidmantas",
|
|
.gecos = "Vidmantas Kaminskas",
|
|
.home = "/home/vidmantas",
|
|
.shell = "/bin/bash",
|
|
};
|
|
var user2 = try user.clone(allocator);
|
|
defer user2.deinit(allocator);
|
|
|
|
try testing.expectEqualStrings(user.shell, "/bin/bash");
|
|
}
|