turbonss/src/unix2db.zig

231 lines
7.7 KiB
Zig
Raw Normal View History

2022-05-25 09:42:42 +03:00
const std = @import("std");
2022-06-05 23:56:45 +03:00
const fs = std.fs;
const io = std.io;
const mem = std.mem;
const os = std.os;
2022-07-04 23:15:16 +03:00
const heap = std.heap;
2022-06-05 23:56:45 +03:00
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
2022-07-03 09:44:09 +03:00
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");
2022-06-05 23:56:45 +03:00
const usage =
2022-07-05 06:02:54 +03:00
\\usage: turbo-unix2db [options]
2022-06-05 23:56:45 +03:00
\\
\\ -h Print this help message and exit
2022-07-06 12:35:17 +03:00
\\ --passwd Path to passwd file (default: passwd)
\\ --group Path to group file (default: group)
\\ --output Path to output file (default: db.turbo)
2022-06-05 23:56:45 +03:00
\\
;
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;
2022-07-04 23:15:16 +03:00
const gpa = heap.raw_c_allocator;
2022-06-05 23:56:45 +03:00
2022-07-03 06:39:33 +03:00
const stderr = io.getStdErr().writer();
const stdout = io.getStdOut().writer();
2022-06-05 23:56:45 +03:00
2022-07-04 23:15:16 +03:00
const return_code = execute(gpa, stdout, stderr, argv[1..]);
2022-06-05 23:56:45 +03:00
os.exit(return_code);
}
fn execute(
allocator: Allocator,
2022-06-22 20:59:58 +03:00
stdout: anytype,
2022-06-05 23:56:45 +03:00
stderr: anytype,
argv: []const [*:0]const u8,
2022-06-22 16:23:02 +03:00
) u8 {
2022-06-05 23:56:45 +03:00
const result = flags.parse(argv, &[_]flags.Flag{
.{ .name = "-h", .kind = .boolean },
.{ .name = "--passwd", .kind = .arg },
.{ .name = "--group", .kind = .arg },
2022-06-07 05:59:00 +03:00
.{ .name = "--output", .kind = .arg },
2022-06-05 23:56:45 +03:00
}) catch {
2022-06-22 16:23:02 +03:00
stderr.writeAll(usage) catch {};
2022-06-05 23:56:45 +03:00
return 1;
};
2022-05-25 09:42:42 +03:00
2022-06-05 23:56:45 +03:00
if (result.boolFlag("-h")) {
2022-06-22 20:59:58 +03:00
stdout.writeAll(usage) catch return 1;
2022-06-05 23:56:45 +03:00
return 0;
}
if (result.args.len != 0) {
2022-06-22 16:23:02 +03:00
stderr.print("ERROR: unknown option '{s}'\n", .{result.args[0]}) catch {};
stderr.writeAll(usage) catch {};
2022-06-05 23:56:45 +03:00
return 1;
}
2022-07-06 12:35:17 +03:00
const passwd_fname = result.argFlag("--passwd") orelse "passwd";
const group_fname = result.argFlag("--group") orelse "group";
const out_fname = result.argFlag("--output") orelse "db.turbo";
2022-06-05 23:56:45 +03:00
2022-06-22 21:06:20 +03:00
// to catch an error set file.OpenError, wait for
2022-06-22 16:23:02 +03:00
// https://github.com/ziglang/zig/issues/2473
2022-06-22 20:59:58 +03:00
var errc = ErrCtx{};
2022-07-05 06:02:54 +03:00
var passwd_file = fs.cwd().openFile(passwd_fname, .{ .mode = .read_only }) catch |err|
return fail(errc.wrapf("open '{s}'", .{passwd_fname}), stderr, err);
defer passwd_file.close();
2022-06-22 21:06:20 +03:00
2022-07-05 06:02:54 +03:00
var group_file = fs.cwd().openFile(group_fname, .{ .mode = .read_only }) catch |err|
return fail(errc.wrapf("open '{s}'", .{group_fname}), stderr, err);
defer group_file.close();
2022-06-05 23:56:45 +03:00
2022-07-05 06:02:54 +03:00
var passwdReader = io.bufferedReader(passwd_file.reader()).reader();
2022-07-04 22:49:26 +03:00
var users = User.fromReader(allocator, &errc, passwdReader) catch |err|
2022-06-22 21:06:20 +03:00
return fail(errc.wrap("read users"), stderr, err);
2022-07-02 21:19:07 +03:00
defer {
for (users) |*user| user.deinit(allocator);
allocator.free(users);
}
2022-06-15 12:56:19 +03:00
2022-07-05 06:02:54 +03:00
var groupReader = io.bufferedReader(group_file.reader()).reader();
2022-07-04 22:49:26 +03:00
var groups = Group.fromReader(allocator, groupReader) catch |err|
2022-06-22 21:06:20 +03:00
return fail(errc.wrap("read groups"), stderr, err);
2022-07-02 21:19:07 +03:00
defer {
for (groups) |*group| group.deinit(allocator);
allocator.free(groups);
}
2022-06-05 23:56:45 +03:00
2022-07-03 09:44:09 +03:00
var corpus = Corpus.init(allocator, users, groups, &errc) catch |err|
2022-06-22 20:59:58 +03:00
return fail(errc.wrap("init corpus"), stderr, err);
2022-06-07 05:59:00 +03:00
defer corpus.deinit();
2022-07-04 06:09:03 +03:00
var db = DB.fromCorpus(allocator, &corpus, &errc) catch |err|
return fail(errc.wrap("construct DB from corpus"), stderr, err);
2022-06-07 05:59:00 +03:00
defer db.deinit(allocator);
2022-07-06 11:30:50 +03:00
const fd = os.open(out_fname, os.O.WRONLY | os.O.TRUNC | os.O.CREAT, 0o644) catch |err|
2022-07-05 06:02:54 +03:00
return fail(errc.wrapf("open for writing {s}", .{out_fname}), stderr, err);
2022-06-07 05:59:00 +03:00
errdefer os.close(fd);
2022-06-22 20:59:58 +03:00
const len = os.writev(fd, db.iov().constSlice()) catch |err|
2022-07-05 06:02:54 +03:00
return fail(errc.wrapf("writev to {s}", .{out_fname}), stderr, err);
2022-06-22 20:59:58 +03:00
os.fsync(fd) catch |err|
2022-07-05 06:02:54 +03:00
return fail(errc.wrapf("fsync {s}", .{out_fname}), stderr, err);
2022-06-07 05:59:00 +03:00
os.close(fd);
2022-06-22 20:59:58 +03:00
stderr.print("total {d} bytes. groups={d} users={d}\n", .{
2022-06-07 05:59:00 +03:00
len,
users.len,
groups.len,
2022-06-22 20:59:58 +03:00
}) catch return 1;
2022-06-05 23:56:45 +03:00
return 0;
}
2022-05-25 09:42:42 +03:00
2022-06-22 20:59:58 +03:00
fn fail(errc: *ErrCtx, stderr: anytype, err: anytype) u8 {
2022-07-04 05:44:42 +03:00
const err_chain = errc.unwrap().constSlice();
stderr.print("ERROR {s}: {s}\n", .{ @errorName(err), err_chain }) catch {};
2022-06-22 20:59:58 +03:00
return 1;
}
2022-05-25 09:42:42 +03:00
const testing = std.testing;
2022-06-05 23:56:45 +03:00
test "invalid argument" {
const allocator = testing.allocator;
const args = &[_][*:0]const u8{"--invalid-argument"};
var stderr = ArrayList(u8).init(allocator);
defer stderr.deinit();
2022-06-22 20:59:58 +03:00
var stdout = ArrayList(u8).init(allocator);
defer stdout.deinit();
2022-06-05 23:56:45 +03:00
2022-06-22 21:16:08 +03:00
const exit_code = execute(allocator, stdout.writer(), stderr.writer(), args[0..]);
2022-06-05 23:56:45 +03:00
try testing.expectEqual(@as(u8, 1), exit_code);
2022-06-07 05:59:00 +03:00
try testing.expect(mem.startsWith(
u8,
stderr.items,
"ERROR: unknown option '--invalid-argument'",
));
2022-05-25 09:42:42 +03:00
}
2022-06-07 05:59:00 +03:00
2022-06-22 21:16:08 +03:00
test "trivial error: missing passwd file" {
const allocator = testing.allocator;
const args = &[_][*:0]const u8{};
var stderr = ArrayList(u8).init(allocator);
defer stderr.deinit();
var stdout = ArrayList(u8).init(allocator);
defer stdout.deinit();
const exit_code = execute(allocator, stdout.writer(), stderr.writer(), args[0..]);
try testing.expectEqual(@as(u8, 1), exit_code);
2022-07-06 13:19:15 +03:00
try testing.expectEqualStrings(stderr.items, "ERROR FileNotFound: open 'passwd'\n");
2022-06-22 21:16:08 +03:00
}
test "fail" {
var errc = ErrCtx{};
2022-06-28 16:41:10 +03:00
var buf = ArrayList(u8).init(testing.allocator);
defer buf.deinit();
var wr = buf.writer();
const exit_code = fail(errc.wrapf("invalid user 'foo'", .{}), wr, error.NotSure);
2022-06-22 21:16:08 +03:00
try testing.expectEqual(exit_code, 1);
2022-06-28 16:41:10 +03:00
try testing.expectEqualStrings(buf.items, "ERROR NotSure: invalid user 'foo'\n");
2022-06-22 21:16:08 +03:00
}
2022-06-07 06:30:18 +03:00
test "smoke test" {
const allocator = testing.allocator;
var stderr = ArrayList(u8).init(allocator);
defer stderr.deinit();
2022-06-28 16:41:10 +03:00
var stdout = ArrayList(u8).init(allocator);
defer stdout.deinit();
2022-06-07 13:14:50 +03:00
var corpus = try Corpus.testCorpus(allocator);
defer corpus.deinit();
var tmp = testing.tmpDir(.{});
2022-06-12 13:27:12 +03:00
errdefer tmp.cleanup();
2022-06-07 13:14:50 +03:00
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);
2022-07-02 16:24:47 +03:00
const line = user.toLine().constSlice();
_ = try os.write(passwd_fd, line);
2022-06-07 13:14:50 +03:00
}
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,
};
2022-06-22 16:23:02 +03:00
2022-06-28 16:41:10 +03:00
const exit_code = execute(allocator, stdout.writer(), stderr.writer(), args);
2022-07-02 21:19:07 +03:00
try testing.expectEqualStrings("total 1664 bytes. groups=5 users=4\n", stderr.items);
2022-06-07 13:14:50 +03:00
try testing.expectEqual(@as(u8, 0), exit_code);
2022-06-07 06:30:18 +03:00
}