const std = @import("std"); 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; const flags = @import("../flags.zig"); const User = @import("../User.zig"); const Group = @import("../Group.zig"); const Corpus = @import("../Corpus.zig"); const DB = @import("../DB.zig"); const ErrCtx = @import("../ErrCtx.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 stderr = try io.getStdErr(); const stdout = try io.getStdOut(); const return_code = execute(gpa, stdout, stderr, argv[1..]); os.exit(return_code); } fn execute( allocator: Allocator, stdout: anytype, 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 }, .{ .name = "--output", .kind = .arg }, }) catch { stderr.writeAll(usage) catch {}; return 1; }; if (result.boolFlag("-h")) { stdout.writeAll(usage) catch return 1; return 0; } if (result.args.len != 0) { stderr.print("ERROR: unknown option '{s}'\n", .{result.args[0]}) catch {}; stderr.writeAll(usage) catch {}; return 1; } const passwdFname = result.argFlag("--passwd") orelse "./passwd"; const groupFname = result.argFlag("--group") orelse "./group"; const outFile = result.argFlag("--output") orelse "./db.turbo"; // to catch an error set file.OpenError, wait for // https://github.com/ziglang/zig/issues/2473 var errc = ErrCtx{}; var passwdFile = fs.cwd().openFile(passwdFname, .{ .mode = .read_only }) catch |err| return fail(errc.wrapf("open {s}", .{passwdFname}), stderr, err); defer passwdFile.close(); var groupFile = fs.cwd().openFile(groupFname, .{ .mode = .read_only }) catch |err| return fail(errc.wrapf("open {s}", .{groupFname}), stderr, err); defer groupFile.close(); var users = User.fromReader(allocator, &errc, passwdFile.reader()) catch |err| return fail(errc.wrap("read users"), stderr, err); defer for (users) |*user| user.deinit(allocator); defer allocator.free(users); var groups = Group.fromReader(allocator, groupFile.reader()) catch |err| return fail(errc.wrap("read groups"), stderr, err); defer for (groups) |*group| group.deinit(allocator); defer allocator.free(groups); var corpus = Corpus.init(allocator, users, groups) catch |err| return fail(errc.wrap("init corpus"), stderr, err); defer corpus.deinit(); var db = DB.fromCorpus(allocator, &corpus) catch |err| return fail(errc.wrap("construct db from corpus"), stderr, err); defer db.deinit(allocator); const fd = os.open(outFile, os.O.WRONLY | os.O.TRUNC | os.O.CREAT, 0644) catch |err| return fail(errc.wrapf("open for writing {s}", .{outFile}), stderr, err); errdefer os.close(fd); const len = os.writev(fd, db.iov().constSlice()) catch |err| return fail(errc.wrapf("writev to {s}", .{outFile}), stderr, err); os.fsync(fd) catch |err| return fail(errc.wrapf("fsync {s}", .{outFile}), stderr, err); os.close(fd); stderr.print("total {d} bytes. groups={d} users={d}\n", .{ len, users.len, groups.len, }) catch return 1; return 0; } fn fail(errc: *ErrCtx, stderr: anytype, err: anytype) u8 { stderr.print("ERROR {s}", .{@errorName(err)}) catch {}; var it = errc.rev(); while (it.next()) |msg| stderr.print(": {s}", .{msg}) catch {}; stderr.print("\n", .{}) catch {}; return 1; } const testing = std.testing; test "invalid argument" { const allocator = testing.allocator; const args = &[_][*:0]const u8{"--invalid-argument"}; var stderr = ArrayList(u8).init(allocator); defer stderr.deinit(); var stderrw = stderr.writer(); var stdout = ArrayList(u8).init(allocator); defer stdout.deinit(); var stdoutw = stdout.writer(); const exit_code = execute(allocator, stdoutw, stderrw, args[0..]); try testing.expectEqual(@as(u8, 1), exit_code); try testing.expect(mem.startsWith( u8, stderr.items, "ERROR: unknown option '--invalid-argument'", )); } test "smoke test" { const allocator = testing.allocator; var stderr = ArrayList(u8).init(allocator); defer stderr.deinit(); var corpus = try Corpus.testCorpus(allocator); defer corpus.deinit(); var tmp = testing.tmpDir(.{}); errdefer tmp.cleanup(); const tmp_path = blk: { const relative_path = try fs.path.join(allocator, &[_][]const u8{ "zig-cache", "tmp", tmp.sub_path[0..], }); const real_path = try fs.realpathAlloc(allocator, relative_path); allocator.free(relative_path); break :blk real_path; }; defer allocator.free(tmp_path); const passwdPath = try fs.path.joinZ(allocator, &[_][]const u8{ tmp_path, "passwd" }); defer allocator.free(passwdPath); const groupPath = try fs.path.joinZ(allocator, &[_][]const u8{ tmp_path, "group" }); defer allocator.free(groupPath); const outPath = try fs.path.joinZ(allocator, &[_][]const u8{ tmp_path, "tmp.turbo" }); defer allocator.free(outPath); const passwd_fd = try os.open(passwdPath, os.O.CREAT | os.O.WRONLY, 0o644); const group_fd = try os.open(groupPath, os.O.CREAT | os.O.WRONLY, 0o644); var i: usize = 0; while (i < corpus.users.len) : (i += 1) { const user = corpus.users.get(i); const line = user.toLine(); _ = try os.write(passwd_fd, line.constSlice()); } os.close(passwd_fd); var group_writer = (fs.File{ .handle = group_fd }).writer(); i = 0; while (i < corpus.groups.len) : (i += 1) try corpus.groups.get(i).writeTo(group_writer); os.close(group_fd); const args = &[_][*:0]const u8{ "--passwd", passwdPath, "--group", groupPath, "--output", outPath, }; if (true) return error.SkipZigTest; const exit_code = try execute(allocator, stderr.writer(), args); try testing.expectEqual(@as(u8, 0), exit_code); }