From 02c164882396f4f8b3dfff6ea90e8d19c8b869bf Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" <jeff@ocjtech.us> Date: Sat, 22 Mar 2025 12:03:01 -0500 Subject: [PATCH] first --- .gitignore | 4 ++ build.zig | 34 +++++++++++ build.zig.zon | 14 +++++ src/PipeReader.zig | 56 +++++++++++++++++ src/UnixSocketReader.zig | 129 +++++++++++++++++++++++++++++++++++++++ src/main.zig | 85 ++++++++++++++++++++++++++ 6 files changed, 322 insertions(+) create mode 100644 .gitignore create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 src/PipeReader.zig create mode 100644 src/UnixSocketReader.zig create mode 100644 src/main.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a4aa3da --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/zig-out +/.zig-cache +/result* + diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..bae698b --- /dev/null +++ b/build.zig @@ -0,0 +1,34 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // We will also create a module for our other entry point, 'main.zig'. + const exe_mod = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + const exe = b.addExecutable(.{ + .name = "zig_logging_wrapper", + .root_module = exe_mod, + }); + b.installArtifact(exe); + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + const exe_unit_tests = b.addTest(.{ + .root_module = exe_mod, + }); + + const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_exe_unit_tests.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..c80576f --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,14 @@ +.{ + .name = .zig_logging_wrapper, + + .version = "0.0.0", + .fingerprint = 0x259c6307f5768bcf, // Changing this has security and trust implications. + .minimum_zig_version = "0.14.0", + .dependencies = .{}, + + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/src/PipeReader.zig b/src/PipeReader.zig new file mode 100644 index 0000000..8c0ee0d --- /dev/null +++ b/src/PipeReader.zig @@ -0,0 +1,56 @@ +const PipeReader = @This(); + +const std = @import("std"); + +const log = std.log.scoped(.pipe_reader); + +spawn_failed: *bool, +exe: *std.process.Child, +pipe: enum { stdout, stderr }, +sem: std.Thread.Semaphore = .{}, +thread: std.Thread = undefined, + +pub fn spawn(self: *PipeReader) !void { + self.thread = try std.Thread.spawn(.{}, reader, .{self}); +} + +pub fn wait(self: *PipeReader) void { + self.sem.wait(); +} + +pub fn join(self: *PipeReader) void { + self.thread.join(); +} + +fn reader(self: *PipeReader) void { + const fd: std.fs.File = fd: { + defer self.sem.post(); + while (!self.spawn_failed.*) { + switch (self.pipe) { + .stdout => if (self.exe.stdout) |fd| break :fd fd, + .stderr => if (self.exe.stderr) |fd| break :fd fd, + } + std.time.sleep(10 * std.time.ns_per_ms); + } + self.sem.post(); + return; + }; + + const rdr = fd.reader(); + + while (true) { + var buf: [2048]u8 = undefined; + const line = rdr.readUntilDelimiter(&buf, '\n') catch |err| switch (err) { + error.EndOfStream => return, + error.NotOpenForReading => return, + else => { + log.err("{s}: {}", .{ @tagName(self.pipe), err }); + + return; + }, + }; + const ts = std.time.nanoTimestamp(); + if (line.len == 0) break; + log.info("{s}: {d} {s}", .{ @tagName(self.pipe), ts, line }); + } +} diff --git a/src/UnixSocketReader.zig b/src/UnixSocketReader.zig new file mode 100644 index 0000000..d4ed6e8 --- /dev/null +++ b/src/UnixSocketReader.zig @@ -0,0 +1,129 @@ +const UnixSocketReader = @This(); + +const std = @import("std"); + +const log = std.log.scoped(.socket_reader); + +sock: std.posix.socket_t = undefined, +thread: std.Thread = undefined, + +pub fn init(self: *UnixSocketReader) !void { + const path: []const u8 = "/tmp/wrapper"; + + var dir = try std.fs.openDirAbsolute(std.fs.path.dirname(path) orelse return error.BadPath, .{}); + defer dir.close(); + dir.deleteFile(std.fs.path.basename(path)) catch {}; + + var addr = std.mem.zeroes(std.posix.sockaddr.un); + addr.family = std.posix.AF.UNIX; + @memcpy(addr.path[0..path.len], path); + + self.sock = try std.posix.socket(std.posix.AF.UNIX, std.posix.SOCK.DGRAM, 0); + try std.posix.bind(self.sock, @ptrCast(&addr), @sizeOf(std.posix.sockaddr.un)); +} + +pub fn spawn(self: *UnixSocketReader) !void { + self.thread = try std.Thread.spawn(.{}, reader, .{self}); + self.thread.detach(); +} + +const Severity = enum(u8) { + EMERG = 0, + ALERT = 1, + CRIT = 2, + ERR = 3, + WARNING = 4, + NOTICE = 5, + INFO = 6, + DEBUG = 7, +}; + +const Facility = enum(u8) { + KERN = (0 << 3), + USER = (1 << 3), + MAIL = (2 << 3), + DAEMON = (3 << 3), + AUTH = (4 << 3), + SYSLOG = (5 << 3), + LPR = (6 << 3), + NEWS = (7 << 3), + UUCP = (8 << 3), + CRON = (9 << 3), + AUTHPRIV = (10 << 3), + FTP = (11 << 3), + NTP = (12 << 3), + AUDIT = (13 << 3), + ALERT = (14 << 3), + CLOCK = (15 << 3), + LOCAL0 = (16 << 3), + LOCAL1 = (17 << 3), + LOCAL2 = (18 << 3), + LOCAL3 = (19 << 3), + LOCAL4 = (20 << 3), + LOCAL5 = (21 << 3), + LOCAL6 = (22 << 3), + LOCAL7 = (23 << 3), +}; + +pub fn reader(self: *UnixSocketReader) void { + while (true) { + var buf: [4096]u8 = undefined; + var addr: std.posix.sockaddr = undefined; + var addrlen: std.posix.socklen_t = @sizeOf(std.posix.sockaddr); + const len = std.posix.recvfrom(self.sock, &buf, 0, &addr, &addrlen) catch |err| { + log.info("socket reader: {}", .{err}); + return; + }; + const line = buf[0..len]; + syslog: { + if (line.len < 3) { + log.warn("data too short to be syslog", .{}); + break :syslog; + } + const pri_start_char = std.mem.indexOfScalar(u8, line, '<') orelse { + log.warn("syslog: can't find '<'", .{}); + break :syslog; + }; + if (pri_start_char != 0) { + log.warn("syslog does not start with '<'", .{}); + break :syslog; + } + const pri_end_char = std.mem.indexOfScalarPos(u8, line, 1, '>') orelse { + log.warn("syslog: can't find '>'", .{}); + break :syslog; + }; + const pri = line[pri_start_char + 1 .. pri_end_char]; + if (pri.len < 1) { + log.warn("pri is too short", .{}); + break :syslog; + } + if (pri.len > 3) { + log.warn("pri is too long", .{}); + break :syslog; + } + for (pri) |ch| if (!std.ascii.isDigit(ch)) { + log.warn("invalid digit in pri", .{}); + break :syslog; + }; + const prival = std.fmt.parseUnsigned(u8, pri, 10) catch |err| { + log.warn("unable to convert pri to an integer: {}", .{err}); + break :syslog; + }; + if (prival > 191) { + log.warn("prival out of range", .{}); + break :syslog; + } + log.info("pri: \'{s}\'", .{pri}); + const severity = std.meta.intToEnum(Severity, prival & 0b00000111) catch { + log.warn("unable to convert severity", .{}); + break :syslog; + }; + const facility = std.meta.intToEnum(Facility, prival & 0b11111000) catch { + log.warn("unable to convert facility", .{}); + break :syslog; + }; + log.info("severity: {s} facility: {s}", .{ @tagName(severity), @tagName(facility) }); + } + log.info("{s}", .{line}); + } +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..eaf2cad --- /dev/null +++ b/src/main.zig @@ -0,0 +1,85 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +const log = std.log.scoped(.wrapper); + +const PipeReader = @import("PipeReader.zig"); +const SocketReader = @import("SocketReader.zig"); + +var debug_allocator: std.heap.DebugAllocator(.{}) = .init; + +pub fn main() !void { + const alloc, const is_debug = switch (builtin.mode) { + .Debug, .ReleaseSafe => .{ debug_allocator.allocator(), true }, + .ReleaseFast, .ReleaseSmall => .{ std.heap.smp_allocator, false }, + }; + defer if (is_debug) { + _ = debug_allocator.deinit(); + }; + + var socket: SocketReader = .{}; + try socket.init(); + try socket.spawn(); + + var exe = std.process.Child.init( + &.{ + "logger", + "--stderr", + "--unix=/tmp/wrapper", + "hello", + }, + alloc, + ); + + exe.stdin_behavior = .Ignore; + exe.stdout_behavior = .Pipe; + exe.stderr_behavior = .Pipe; + + var spawn_failed: bool = false; + + var stdout: PipeReader = .{ + .spawn_failed = &spawn_failed, + .exe = &exe, + .pipe = .stdout, + }; + try stdout.spawn(); + defer stdout.join(); + + var stderr: PipeReader = .{ + .spawn_failed = &spawn_failed, + .exe = &exe, + .pipe = .stderr, + }; + try stderr.spawn(); + defer stderr.join(); + + exe.spawn() catch |err| { + log.err("err: {}", .{err}); + spawn_failed = true; + return; + }; + + stdout.wait(); + stderr.wait(); + + const rc = try exe.wait(); + + switch (rc) { + .Exited => |code| { + if (code != 0) { + log.warn("exited with error code {d}", .{code}); + } + }, + .Signal => |signal| { + log.warn("terminated with signal {}", .{signal}); + }, + .Stopped => |signal| { + log.warn("stopped with signal {}", .{signal}); + }, + .Unknown => |code| { + log.warn("unknown error {}", .{code}); + }, + } + + log.info("finished!", .{}); +}