This commit is contained in:
Jeffrey C. Ollie 2024-01-13 22:02:57 -06:00
commit 92a83ea883
Signed by: jeff
GPG key ID: 6F86035A6D97044E
6 changed files with 305 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/zig-cache
/zig-out

24
build.zig Normal file
View file

@ -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);
}

10
build.zig.zon Normal file
View file

@ -0,0 +1,10 @@
.{
.name = "journalz",
.version = "0.0.0",
.dependencies = .{},
.paths = .{
"",
},
}

47
src/cmsghdr.zig Normal file
View file

@ -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));
}

24
src/main.zig Normal file
View file

@ -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());
}

198
src/root.zig Normal file
View file

@ -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();
}