1
Fork 0
turbonss/src/turbo-getent.zig

230 lines
6.7 KiB
Zig

const std = @import("std");
const io = std.io;
const os = std.os;
const mem = std.mem;
const fmt = std.fmt;
const ArrayList = std.ArrayList;
const flags = @import("flags.zig");
const DB = @import("DB.zig");
const File = @import("File.zig");
const PackedUser = @import("PackedUser.zig");
const User = @import("User.zig");
const Mode = enum { group, passwd };
const usage =
\\usage: turbo-getent [OPTION]... group|passwd [key...]
\\
\\ -h Print this help message and exit
\\ --db=PATH Path to the database (default: /etc/turbonss/db.turbo)
\\
\\turbo-getent resolves group or passwd (a.k.a. user) entries from the
\\database file. If one or more key arguments are provided, then only
\\entries that match the supplied keys will be displayed. If no key is
\\provided, all entries will be displayed.
\\
\\Exit codes:
\\
\\ 0 command completed successfully
\\ 1 invalid arguments or db could not be opened
\\ 2 one or more supplied key could not be found in the database
\\ 3 output error
\\
;
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 stderr = io.getStdErr().writer();
const stdout = io.getStdOut().writer();
const return_code = execute(stdout, stderr, argv[1..]);
os.exit(return_code);
}
fn execute(
stdout: anytype,
stderr: anytype,
argv: []const [*:0]const u8,
) u8 {
const myflags = flags.parse(argv, &[_]flags.Flag{
.{ .name = "-h", .kind = .boolean },
.{ .name = "--db", .kind = .arg },
}) catch {
stderr.writeAll(usage) catch {};
return 1;
};
if (myflags.boolFlag("-h")) {
stdout.writeAll(usage) catch return 1;
return 0;
}
if (myflags.args.len == 0) {
stderr.writeAll(usage) catch {};
return 1;
}
const arg0 = mem.span(myflags.args[0]);
const mode: Mode = blk: {
if (mem.eql(u8, arg0, "passwd")) {
break :blk .passwd;
} else if (mem.eql(u8, arg0, "group")) {
break :blk .group;
} else {
stderr.print("bad argument {s}: expected passwd or group\n", .{
myflags.args[0],
}) catch return 3;
return 1;
}
};
const db_file = myflags.argFlag("--db") orelse "/etc/turbonss/db.turbo";
var file = File.open(db_file) catch |err| {
stderr.print(
"ERROR {s}: file '{s}' is corrupted or cannot be read\n",
.{ @errorName(err), db_file },
) catch return 3;
return 1;
};
defer file.close();
return switch (mode) {
.passwd => passwd(stdout, &file.db, myflags.args[1..]),
.group => group(stdout, &file.db, myflags.args[1..]),
};
}
fn passwd(stdout: anytype, db: *const DB, keys: []const [*:0]const u8) u8 {
if (keys.len == 0)
return passwd_all(stdout, db);
var some_notfound = false;
const shell_reader = db.shellReader();
for (keys) |key| {
const keyZ = mem.span(key);
const maybe_packed_user = if (fmt.parseUnsigned(u32, keyZ, 10)) |uid|
db.getUserByUid(uid)
else |_|
db.getUserByName(keyZ);
const packed_user = maybe_packed_user orelse {
some_notfound = true;
continue;
};
const line = packed_user.toUser(shell_reader).toLine();
stdout.writeAll(line.constSlice()) catch return 3;
}
return if (some_notfound) 2 else 0;
}
fn passwd_all(stdout: anytype, db: *const DB) u8 {
const shell_reader = db.shellReader();
var it = PackedUser.iterator(db.users, db.header.num_users, shell_reader);
while (it.next()) |packed_user| {
const line = packed_user.toUser(db.shellReader()).toLine();
stdout.writeAll(line.constSlice()) catch return 3;
}
return 0;
}
fn group(stdout: anytype, db: *const DB, keys: []const [*:0]const u8) u8 {
if (keys.len == 0)
return group_all(stdout, db);
var some_notfound = false;
for (keys) |key| {
const keyZ = mem.span(key);
const maybe_packed_group = if (fmt.parseUnsigned(u32, keyZ, 10)) |gid|
db.getGroupByGid(gid)
else |_|
db.getGroupByName(keyZ);
const g = maybe_packed_group orelse {
some_notfound = true;
continue;
};
// not converting to Group to avoid memory allocations.
stdout.print("{s}:x:{d}:", .{ g.name(), g.gid() }) catch return 3;
}
return if (some_notfound) 2 else 0;
}
fn group_all(stdout: anytype, db: *const DB) u8 {
_ = stdout;
_ = db;
return 0;
}
const testing = std.testing;
test "passwd" {
var tf = try File.TestDB.init(testing.allocator);
defer tf.deinit();
var stdout = ArrayList(u8).init(testing.allocator);
defer stdout.deinit();
var stderr = ArrayList(u8).init(testing.allocator);
defer stderr.deinit();
{
const args = &[_][*:0]const u8{
"--db",
tf.path,
"passwd",
"root",
"doesnotexist",
"vidmantas",
"0",
"1",
};
const got = execute(stdout.writer(), stderr.writer(), args);
try testing.expectEqual(got, 2);
const want_root = "root:x:0:0::/root:/bin/bash\n";
try testing.expectEqualStrings(stdout.items[0..want_root.len], want_root);
const want_vidmantas = "vidmantas:x:128:128:Vidmantas Kaminskas:/home/vidmantas:/bin/bash\n";
var offset: usize = want_root.len + want_vidmantas.len;
try testing.expectEqualStrings(stdout.items[want_root.len..offset], want_vidmantas);
try testing.expectEqualStrings(stdout.items[offset..], want_root);
}
}
test "passwd_all" {
var tf = try File.TestDB.init(testing.allocator);
defer tf.deinit();
var stdout = ArrayList(u8).init(testing.allocator);
defer stdout.deinit();
var stderr = ArrayList(u8).init(testing.allocator);
defer stderr.deinit();
const args = &[_][*:0]const u8{
"--db",
tf.path,
"passwd",
};
var buf: [User.max_line_len]u8 = undefined;
const got = execute(stdout.writer(), stderr.writer(), args);
try testing.expectEqual(got, 0);
const want_names = &[_][]const u8{
"Name" ** 8,
"nobody",
"root",
"svc-bar",
"vidmantas",
};
var i: usize = 0;
const reader = io.fixedBufferStream(stdout.items).reader();
while (try reader.readUntilDelimiterOrEof(buf[0..], '\n')) |line| {
var name = mem.split(u8, line, ":");
try testing.expectEqualStrings(want_names[i], name.next().?);
i += 1;
}
}