unix2db: first stab at flag parsing

This commit is contained in:
Motiejus Jakštys 2022-06-05 23:56:45 +03:00
parent 20bfa60f26
commit e238db9b60
3 changed files with 111 additions and 35 deletions

View File

@ -45,27 +45,6 @@ pub fn clone(
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 {
@ -79,8 +58,8 @@ fn fromLine(allocator: Allocator, line: []const u8) error{ InvalidRecord, OutOfM
// 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;
const uid = fmt.parseInt(u32, uids, 10) catch return error.InvalidRecord;
const gid = fmt.parseInt(u32, gids, 10) catch return error.InvalidRecord;
try validate.utf8(name);
try validate.utf8(gecos);
@ -105,6 +84,28 @@ fn strlen(self: *const User) usize {
self.shell.len;
}
const max_line_len = fmt.count(line_fmt, .{
max_user.name,
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;
}
// 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 {
@ -119,13 +120,13 @@ pub fn deinit(self: *User, allocator: Allocator) void {
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;
var buf: [max_line_len]u8 = undefined;
// TODO: catch and interpret error
while (try reader.readUntilDelimiterOrEof(buf[0..], '\n')) |line| {
const user = try fromLine(allocator, line);
try users.append(user);
}
_ = users;
return users.toOwnedSlice();
}
pub const CUser = extern struct {

View File

@ -35,7 +35,7 @@ pub fn ParseResult(comptime flags: []const Flag) type {
};
/// Remaining args after the recognized flags
args: [][*:0]const u8,
args: []const [*:0]const u8,
/// Data obtained from parsed flags
flag_data: [flags.len]FlagData = blk: {
// Init all flags to false/null
@ -73,7 +73,7 @@ pub fn ParseResult(comptime flags: []const Flag) type {
};
}
pub fn parse(args: [][*:0]const u8, comptime flags: []const Flag) !ParseResult(flags) {
pub fn parse(args: []const [*:0]const u8, comptime flags: []const Flag) !ParseResult(flags) {
var ret: ParseResult(flags) = .{ .args = undefined };
var arg_idx: usize = 0;

View File

@ -1,10 +1,85 @@
const std = @import("std");
const flags = @import("../flags.zig");
const fs = std.fs;
const io = std.io;
const mem = std.mem;
const os = std.os;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const GeneralPurposeAllocator = std.heap.GeneralPurposeAllocator;
pub fn main() !void {}
const flags = @import("../flags.zig");
const User = @import("../User.zig");
const usage =
\\usage: turbonss-unix2db [options]
\\
\\ -h Print this help message and exit
\\ --passwd Path to passwd file (default: ./passwd)
\\ --group Path to group file (default: ./group)
\\
;
pub fn main() !void {
// This line is here because of https://github.com/ziglang/zig/issues/7807
const argv: []const [*:0]const u8 = os.argv;
const gpa = GeneralPurposeAllocator(.{});
const return_code = execute(gpa, argv[1..]) catch |err| {
try io.getStdErr().writeAll("uncaught error: {s}\n", @intToError(err));
os.exit(1);
};
os.exit(return_code);
}
fn execute(
allocator: Allocator,
stderr: anytype,
argv: []const [*:0]const u8,
) !u8 {
const result = flags.parse(argv, &[_]flags.Flag{
.{ .name = "-h", .kind = .boolean },
.{ .name = "--passwd", .kind = .arg },
.{ .name = "--group", .kind = .arg },
}) catch {
try stderr.writeAll(usage);
return 1;
};
if (result.boolFlag("-h")) {
try io.getStdOut().writeAll(usage);
return 0;
}
if (result.args.len != 0) {
try stderr.print("ERROR: unknown option '{s}'\n", .{result.args[0]});
try stderr.writeAll(usage);
return 1;
}
const passwd = result.argFlag("--passwd") orelse "./passwd";
const group = result.argFlag("--group") orelse "./group";
var passwdFile = try fs.cwd().openFile(passwd, .{ .mode = .read_only });
defer passwdFile.close();
var groupFile = try fs.cwd().openFile(group, .{ .mode = .read_only });
defer groupFile.close();
var users = try User.fromReader(allocator, passwdFile.reader());
try stderr.print("read {d} users\n", .{users.len});
return 0;
}
const testing = std.testing;
test "stub" {
_ = flags;
test "invalid argument" {
const allocator = testing.allocator;
const args = &[_][*:0]const u8{"--invalid-argument"};
var stderr = ArrayList(u8).init(allocator);
defer stderr.deinit();
const exit_code = try execute(allocator, stderr.writer(), args[0..]);
try testing.expectEqual(@as(u8, 1), exit_code);
try testing.expect(mem.startsWith(u8, stderr.items, "ERROR: unknown "));
}