diff --git a/src/User.zig b/src/User.zig index f5a7ceb..a5a0bbd 100644 --- a/src/User.zig +++ b/src/User.zig @@ -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 { diff --git a/src/flags.zig b/src/flags.zig index 9aa57a0..797abfb 100644 --- a/src/flags.zig +++ b/src/flags.zig @@ -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; diff --git a/src/unix2db/main.zig b/src/unix2db/main.zig index c3b98a2..e33895d 100644 --- a/src/unix2db/main.zig +++ b/src/unix2db/main.zig @@ -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 ")); }