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"); const CGroup = @import("Group.zig").CGroup; const PackedGroup = @import("PackedGroup.zig"); const CUser = @import("User.zig").CUser; const PackedUser = @import("PackedUser.zig"); const c = @cImport({ @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: // the DB handle and all the iterators. const State = struct { file: ?File, getpwent_iterator: ?PackedUser.Iterator, getgrent_iterator: ?PackedGroup.Iterator, omit_members: bool, 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. var state: State = undefined; // constructor export fn _turbo_init() void { //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)}); const fname = os.getenvZ(TURBONSS_DB) orelse turbonss_default_path[0..]; state.debug("opening {s}", .{fname}); state.file = File.open(fname) catch |err| { 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"; state.omit_members = shouldOmitMembers(omit_members_env, os.argv); state.debug("omitting members from getgr* calls: {any}\n", .{state.omit_members}); return; } fn shouldOmitMembers(env: []const u8, argv: [][*:0]u8) bool { if (mem.eql(u8, env, "1")) return true; if (mem.eql(u8, env, "0")) return false; if (argv.len == 0) return false; return mem.eql(u8, mem.sliceTo(argv[0], 0), "id"); } // destructor export fn _turbo_fini() void { if (state.file) |*fooo| fooo.close(); } export fn _nss_turbo_getpwuid_r( uid: c_uint, res: *CUser, buf: [*]u8, len: usize, errnop: *c_int, ) c.enum_nss_status { 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(); if (true) return error.SkipZigTest; 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); }