From 92a83ea883b0307e7396fd96be82d2111400d7c2 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sat, 13 Jan 2024 22:02:57 -0600 Subject: [PATCH] first --- .gitignore | 2 + build.zig | 24 ++++++ build.zig.zon | 10 +++ src/cmsghdr.zig | 47 ++++++++++++ src/main.zig | 24 ++++++ src/root.zig | 198 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 305 insertions(+) create mode 100644 .gitignore create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 src/cmsghdr.zig create mode 100644 src/main.zig create mode 100644 src/root.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93c1b5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/zig-cache +/zig-out diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..db50e95 --- /dev/null +++ b/build.zig @@ -0,0 +1,24 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + + const optimize = b.standardOptimizeOption(.{}); + + _ = b.addModule("journalz", .{ + .root_source_file = .{ .path = "src/root.zig" }, + .target = target, + .optimize = optimize, + }); + + const lib_unit_tests = b.addTest(.{ + .root_source_file = .{ .path = "src/root.zig" }, + .target = target, + .optimize = optimize, + }); + + const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); + + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_lib_unit_tests.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..2c55665 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,10 @@ +.{ + .name = "journalz", + .version = "0.0.0", + + .dependencies = .{}, + + .paths = .{ + "", + }, +} diff --git a/src/cmsghdr.zig b/src/cmsghdr.zig new file mode 100644 index 0000000..1c09569 --- /dev/null +++ b/src/cmsghdr.zig @@ -0,0 +1,47 @@ +const std = @import("std"); + +// "Borrowed" from https://github.com/tupleapp/tuple-launch/blob/master/cmsghdr.zig + +pub fn cmsghdr(comptime T: type) type { + const Header = extern struct { + len: usize, + level: c_int, + type: c_int, + }; + + const data_align = @sizeOf(usize); + const data_offset = std.mem.alignForward(usize, @sizeOf(Header), data_align); + + return extern struct { + const Self = @This(); + + bytes: [data_offset + @sizeOf(T)]u8 align(@alignOf(Header)), + + pub fn init(args: struct { + level: c_int, + type: c_int, + data: T, + }) Self { + var self: Self = undefined; + self.headerPtr().* = .{ + .len = data_offset + @sizeOf(T), + .level = args.level, + .type = args.type, + }; + self.dataPtr().* = args.data; + return self; + } + + pub fn headerPtr(self: *Self) *Header { + return @as(*Header, @ptrCast(self)); + } + + pub fn dataPtr(self: *Self) *T { + return @as(*T, @alignCast(@ptrCast(self.bytes[data_offset..]))); + } + }; +} + +test { + std.testing.refAllDecls(cmsghdr([3]std.os.fd_t)); +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..c8a3f67 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,24 @@ +const std = @import("std"); + +pub fn main() !void { + // Prints to stderr (it's a shortcut based on `std.io.getStdErr()`) + std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); + + // stdout is for the actual output of your application, for example if you + // are implementing gzip, then only the compressed bytes should be sent to + // stdout, not any debugging messages. + const stdout_file = std.io.getStdOut().writer(); + var bw = std.io.bufferedWriter(stdout_file); + const stdout = bw.writer(); + + try stdout.print("Run `zig build test` to run the tests.\n", .{}); + + try bw.flush(); // don't forget to flush! +} + +test "simple test" { + var list = std.ArrayList(i32).init(std.testing.allocator); + defer list.deinit(); // try commenting this out and see if zig detects the memory leak! + try list.append(42); + try std.testing.expectEqual(@as(i32, 42), list.pop()); +} diff --git a/src/root.zig b/src/root.zig new file mode 100644 index 0000000..96e6a46 --- /dev/null +++ b/src/root.zig @@ -0,0 +1,198 @@ +const std = @import("std"); +const cmsghdr = @import("cmsghdr.zig"); + +pub const Facility = enum(u5) { + KERN = 0, + USER = 1, + MAIL = 2, + DAEMON = 3, + AUTH = 4, + SYSLOG = 5, + LPR = 6, + NEWS = 7, + UUCP = 8, + CRON = 9, + AUTHPRIV = 10, + FTP = 11, + LOCAL0 = 16, + LOCAL1 = 17, + LOCAL2 = 18, + LOCAL3 = 19, + LOCAL4 = 20, + LOCAL5 = 21, + LOCAL6 = 22, + LOCAL7 = 23, +}; + +pub const Priority = enum(u3) { + EMERG = 0, + ALERT = 1, + CRIT = 2, + ERR = 3, + WARNING = 4, + NOTICE = 5, + INFO = 6, + DEBUG = 7, +}; + +const JOURNAL_SOCKET: []const u8 = "/run/systemd/journal/socket"; + +const SCM_RIGHTS = 1; + +const F_ADD_SEALS = 1033; + +const F_SEAL_SEAL = 0x0001; +const F_SEAL_SHRINK = 0x0002; +const F_SEAL_GROW = 0x0004; +const F_SEAL_WRITE = 0x0008; +const F_SEAL_FUTURE_WRITE = 0x0010; +const F_SEAL_EXEC = 0x0020; + +const MFD_NOEXEC_SEAL = 0x0008; + +pub const Logger = struct { + const Self = @This(); + + identifier: ?[]const u8, + socket: std.os.socket_t, + path: std.os.sockaddr.un = undefined, + + pub fn init(identifier: ?[]const u8) !Logger { + var logger = Logger{ + .identifier = identifier, + .socket = try std.os.socket(std.os.AF.UNIX, std.os.SOCK.DGRAM | std.os.SOCK.CLOEXEC, 0), + }; + errdefer std.os.close(logger.socket); + + logger.path.family = std.os.AF.UNIX; + @memset(logger.path.path[0..logger.path.path.len], 0); + @memcpy(logger.path.path[0..JOURNAL_SOCKET.len], JOURNAL_SOCKET); + + return logger; + } + + pub fn deinit(self: Self) void { + std.os.close(self.socket); + } + + const Message = struct { + logger: Logger, + priority: Priority, + memfd: std.os.fd_t, + + fn _writeString(self: Message, str: []const u8) !void { + const len = try std.os.write(self.memfd, str); + if (len != str.len) return error.ShortWrite; + } + + fn _writeU64(self: Message, native: u64) !void { + const le = std.mem.nativeToLittle(u64, native); + const msg = std.mem.asBytes(&le); + try self._writeString(msg); + } + + pub fn string(self: Message, key: []const u8, value: []const u8) !void { + try self._writeString(key); + try self._writeString("\n"); + try self._writeU64(value.len); + try self._writeString(value); + try self._writeString("\n"); + } + + pub fn int(self: Message, key: []const u8, value: anytype) !void { + if (@typeInfo(@TypeOf(value)) != .Int) @compileError("type " ++ @typeName(@TypeOf(value)) ++ " is not an integer type"); + + var buf: [32]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buf); + const writer = fbs.writer(); + try std.fmt.formatInt(value, 10, .lower, .{}, writer); + try self.string(key, fbs.getWritten()); + } + + pub fn identifier(self: Message, value: []const u8) !void { + try self.string("SYSLOG_IDENTIFIER", value); + } + + pub fn facility(self: Message, value: Facility) !void { + try self.int("SYSLOG_FACILITY", @intFromEnum(value)); + } + + pub fn src(self: Message, loc: std.builtin.SourceLocation) !void { + try self.string("CODE_FILE", loc.file); + try self.string("CODE_FUNC", loc.fn_name); + try self.int("CODE_LINE", loc.line); + } + + pub fn send(self: *const Message) !void { + _ = try std.os.fcntl( + self.memfd, + F_ADD_SEALS, + F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_EXEC, + ); + + var cmsg = cmsghdr.cmsghdr(std.os.fd_t).init( + .{ + .level = std.os.SOL.SOCKET, + .type = SCM_RIGHTS, + .data = self.memfd, + }, + ); + + var msghdr = std.os.msghdr_const{ + .name = @ptrCast(&self.logger.path), + .namelen = @sizeOf(@TypeOf(self.logger.path)), + .iov = undefined, + .iovlen = 0, + .control = &cmsg, + .controllen = @sizeOf(@TypeOf(cmsg)), + .flags = 0, + }; + + const result = try std.os.sendmsg( + self.logger.socket, + &msghdr, + 0, + ); + + std.debug.assert(result == 0); + + std.os.close(self.memfd); + } + + pub fn cancel(self: Message) void { + std.os.close(self.memfd); + } + }; + + fn message(self: Logger, priority: Priority) !Message { + const memfd = try std.os.memfd_create( + "zig", + std.os.MFD.CLOEXEC | std.os.MFD.ALLOW_SEALING | MFD_NOEXEC_SEAL, + ); + + const msg = Message{ + .logger = self, + .priority = priority, + .memfd = memfd, + }; + + if (self.identifier) |i| try msg.identifier(i); + try msg.int("PRIORITY", @intFromEnum(priority)); + + return msg; + } +}; + +test "test" { + const logger = try Logger.init("library test"); + defer logger.deinit(); + + const message = try logger.message(.INFO); + errdefer message.cancel(); + + try message.facility(.LOCAL0); + try message.string("MESSAGE", "hello"); + try message.src(@src()); + + try message.send(); +}