diff --git a/lib/DB.zig b/lib/DB.zig index 9793108..d27c97c 100644 --- a/lib/DB.zig +++ b/lib/DB.zig @@ -9,7 +9,6 @@ const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; const AutoHashMap = std.AutoHashMap; const BoundedArray = std.BoundedArray; -const ArenaAllocator = std.heap.ArenaAllocator; const Corpus = @import("Corpus.zig"); const pad = @import("padding.zig"); @@ -149,6 +148,14 @@ pub fn fromCorpus( }; } +pub fn getgrBufsize(self: *const DB) usize { + return self.header.getgr_bufsize; +} + +pub fn getpwBufsize(self: *const DB) usize { + return self.header.getpw_bufsize; +} + pub fn deinit(self: *DB, allocator: Allocator) void { allocator.destroy(self.header); allocator.free(self.bdz_gid); @@ -336,7 +343,7 @@ fn getUser(self: *const DB, user: PackedUser, buf: *[]u8) error{OutOfMemory}!CUs } // get a CUser entry by name. -fn getpwnam(self: *const DB, name: []const u8, buf: *[]u8) error{OutOfMemory}!?CUser { +pub fn getpwnam(self: *const DB, name: []const u8, buf: *[]u8) error{OutOfMemory}!?CUser { const idx = bdz.search(self.bdz_username, name); // bdz may return a hash that's bigger than the number of users if (idx >= self.header.num_users) return null; @@ -348,7 +355,7 @@ fn getpwnam(self: *const DB, name: []const u8, buf: *[]u8) error{OutOfMemory}!?C } // get a CUser entry by uid. -fn getpwuid(self: *const DB, uid: u32, buf: *[]u8) error{OutOfMemory}!?CUser { +pub fn getpwuid(self: *const DB, uid: u32, buf: *[]u8) error{OutOfMemory}!?CUser { const idx = bdz.search_u32(self.bdz_uid, uid); if (idx >= self.header.num_users) return null; const offset = self.idx_uid2user[idx]; @@ -644,7 +651,7 @@ test "getgrnam/getgrgid" { defer corpus.deinit(); var db = try DB.fromCorpus(testing.allocator, &corpus); defer db.deinit(testing.allocator); - var buf = try testing.allocator.alloc(u8, db.header.getgr_bufsize); + var buf = try testing.allocator.alloc(u8, db.getgrBufsize()); defer testing.allocator.free(buf); { @@ -677,7 +684,7 @@ test "getpwnam/getpwuid" { defer corpus.deinit(); var db = try DB.fromCorpus(testing.allocator, &corpus); defer db.deinit(testing.allocator); - var buf = try testing.allocator.alloc(u8, db.header.getpw_bufsize); + var buf = try testing.allocator.alloc(u8, db.getpwBufsize()); defer testing.allocator.free(buf); { diff --git a/lib/File.zig b/lib/File.zig index 35d2765..a3ac21d 100644 --- a/lib/File.zig +++ b/lib/File.zig @@ -1,6 +1,9 @@ const std = @import("std"); const os = std.os; +const fs = std.fs; +const Allocator = std.mem.Allocator; +const Corpus = @import("Corpus.zig"); const DB = @import("DB.zig"); const InvalidHeader = @import("header.zig").Invalid; @@ -13,12 +16,10 @@ pub const Error = os.OpenError || os.FStatError || os.MMapError || InvalidHeader pub fn open(fname: []const u8) Error!File { const fd = try os.open(fname, os.O.RDONLY, 0); var fd_open = true; - errdefer { - if (fd_open) os.close(fd); - } + errdefer if (fd_open) os.close(fd); const st = try os.fstat(fd); - const size = @intCast(u64, st.size); + const size = @intCast(usize, st.size); const ptr = try os.mmap(null, size, os.PROT.READ, os.MAP.SHARED, fd, 0); errdefer os.munmap(ptr); @@ -33,3 +34,38 @@ pub fn close(self: *File) void { os.munmap(self.ptr); self.* = undefined; } + +const testing = std.testing; + +pub const TestFile = struct { + dir: testing.TmpDir, + path: [:0]const u8, + + pub fn init(allocator: Allocator) !TestFile { + var corpus = try Corpus.testCorpus(allocator); + defer corpus.deinit(); + + var db = try DB.fromCorpus(allocator, &corpus); + defer db.deinit(allocator); + + var tmp = testing.tmpDir(.{}); + errdefer tmp.cleanup(); + + const mode = os.O.RDWR | os.O.CREAT | os.O.EXCL; + const fd = try os.openat(tmp.dir.fd, "db.turbo", mode, 0o666); + defer os.close(fd); + + _ = try os.writev(fd, db.iov().constSlice()); + const dir_path = try tmp.getFullPath(allocator); + defer allocator.free(dir_path); + + const full = &[_][]const u8{ dir_path, "db.turbo\x00" }; + var result = try fs.path.join(allocator, full); + return TestFile{ .dir = tmp, .path = result[0 .. result.len - 1 :0] }; + } + + pub fn deinit(self: *TestFile, allocator: Allocator) void { + self.dir.cleanup(); + allocator.free(self.path); + } +}; diff --git a/lib/libnss.zig b/lib/libnss.zig index 53d25b9..4cb01d1 100644 --- a/lib/libnss.zig +++ b/lib/libnss.zig @@ -2,6 +2,7 @@ const std = @import("std"); const os = std.os; const fmt = std.fmt; const mem = std.mem; +const process = std.process; const DB = @import("DB.zig"); const File = @import("File.zig"); @@ -11,10 +12,16 @@ const CUser = @import("User.zig").CUser; const PackedUser = @import("PackedUser.zig"); const c = @cImport({ - @cInclude("stdlib.h"); @cInclude("nss.h"); }); +const TURBONSS_DB = "DB"; +const TURBONSS_VERBOSE = "TURBONSS_VERBOSE"; +const TURBONSS_OMIT_MEMBERS = "TURBONSS_OMIT_MEMBERS"; + +extern fn putenv(name: [*:0]const u8, value: [*:0]const u8, overwrite: c_int) c_int; +extern fn getenv(name: [*:0]const u8) ?[*:0]const u8; + export const turbonss_default_path: [:0]const u8 = "/etc/turbonss/db.turbo"; // State is a type of the global variable holding the process state: @@ -24,7 +31,29 @@ const State = struct { getpwent_iterator: ?PackedUser.Iterator, getgrent_iterator: ?PackedGroup.Iterator, omit_members: bool, - err_msg: [1024]u8, + verbose: bool = false, + + fn debug(self: *const State, comptime format: []const u8, args: anytype) void { + self.log(.debug, format, args); + } + fn info(self: *const State, comptime format: []const u8, args: anytype) void { + self.log(.info, format, args); + } + fn err(self: *const State, comptime format: []const u8, args: anytype) void { + self.log(.err, format, args); + } + fn log( + self: *const State, + comptime level: std.log.Level, + comptime format: []const u8, + args: anytype, + ) void { + if (!self.verbose) return; + const stderr = std.io.getStdErr().writer(); + std.debug.getStderrMutex().lock(); + defer std.debug.getStderrMutex().unlock(); + stderr.print("[" ++ level.asText() ++ "] " ++ format ++ "\n", args) catch return; + } }; // state is initialized on library startup. @@ -32,14 +61,29 @@ var state: State = undefined; // constructor export fn _turbo_init() void { - const fname = os.getenvZ("TURBONSS_DB") orelse turbonss_default_path[0..]; + //if (os.getenvZ(TURBONSS_VERBOSE)) |env| + // state.verbose = mem.eql(u8, env, "1"); + + if (getenv(TURBONSS_VERBOSE)) |env| { + const envZ = mem.sliceTo(env, 0); + state.verbose = mem.eql(u8, envZ, "1"); + } + + //std.debug.print("TURBONSS_VERBOSE: {s}\n", .{os.getenv(TURBONSS_VERBOSE)}); + std.debug.print("TURBONSS_VERBOSE: {s}\n", .{getenv(TURBONSS_VERBOSE)}); + std.debug.print("verbose: {any}\n", .{state.verbose}); + + const fname = os.getenvZ(TURBONSS_DB) orelse turbonss_default_path[0..]; + state.debug("opening {s}", .{fname}); state.file = File.open(fname) catch |err| { - _ = fmt.bufPrint(&state.err_msg, "open {s}: {s}", .{ fname, @errorName(err) }) catch return; + state.err("open {s}: {s}", .{ fname, @errorName(err) }); return; }; + state.debug("turbonss database opened", .{}); - const omit_members_env = os.getenvZ("TURBONSS_OMIT_MEMBERS") orelse "auto"; + const omit_members_env = os.getenvZ(TURBONSS_OMIT_MEMBERS) orelse "auto"; state.omit_members = shouldOmitMembers(omit_members_env, os.argv); + state.debug("omitting members from getgr* calls: {any}\n", .{state.omit_members}); return; } @@ -65,10 +109,43 @@ export fn _nss_turbo_getpwuid_r( len: usize, errnop: *c_int, ) c.enum_nss_status { - _ = uid; - _ = res; - _ = buf; - _ = len; - _ = errnop; - return 0; + if (state.file == null) { + errnop.* = @enumToInt(os.E.AGAIN); + return c.NSS_STATUS_UNAVAIL; + } + const cuser = state.file.?.db.getpwuid(uid, &buf[0..len]) catch |err| switch (err) { + error.OutOfMemory => { + errnop.* = @enumToInt(os.E.RANGE); + return c.NSS_STATUS_TRYAGAIN; + }, + }; + + if (cuser == null) { + errnop.* = @enumToInt(os.E.NOENT); + return c.NSS_STATUS_NOTFOUND; + } + res.* = cuser.?; + + return c.NSS_STATUS_SUCCESS; +} + +const testing = std.testing; + +test "nss_turbo_getpwuid_r" { + var tf = try File.TestFile.init(testing.allocator); + defer tf.deinit(testing.allocator); + + var env = try process.getEnvMap(testing.allocator); + defer env.deinit(); + + try testing.expectEqual(putenv(TURBONSS_VERBOSE, "1", 1), 0); + try testing.expectEqual(putenv(TURBONSS_DB, tf.path, 1), 0); + _turbo_init(); + try testing.expect(state.file != null); + + var buf = try testing.allocator.alloc(u8, state.file.?.db.getpwBufsize()); + var user: CUser = undefined; + var errno: c_int = undefined; + const ret = _nss_turbo_getpwuid_r(128, &user, buf.ptr, buf.len, &errno); + try testing.expectEqual(ret, c.NSS_STATUS_SUCCESS); }