initial commit
This commit is contained in:
+204
@@ -0,0 +1,204 @@
|
||||
//! Thin typed wrappers over the Linux syscalls att_wm needs.
|
||||
//!
|
||||
//! Zig 0.16 moved most of `std.posix` behind the new `std.Io` interface, which
|
||||
//! is the wrong shape for a window manager: everything here is a raw fd driven
|
||||
//! by a single `poll()` loop, with no allocator and no async runtime. Going
|
||||
//! straight to `std.os.linux` is both simpler and closer to what the code
|
||||
//! actually does.
|
||||
|
||||
const std = @import("std");
|
||||
const linux = std.os.linux;
|
||||
|
||||
/// Must be the linux decoder, not `std.posix.errno`: with libc linked the
|
||||
/// latter expects a libc-style -1 return and reports every raw syscall error
|
||||
/// as success, which then overflows the casts below.
|
||||
const errno = linux.errno;
|
||||
|
||||
pub const fd_t = linux.fd_t;
|
||||
pub const pid_t = linux.pid_t;
|
||||
pub const E = linux.E;
|
||||
|
||||
pub const Error = error{
|
||||
Again,
|
||||
Interrupted,
|
||||
ConnectionReset,
|
||||
AddressInUse,
|
||||
NotFound,
|
||||
PermissionDenied,
|
||||
ConnectionRefused,
|
||||
BrokenPipe,
|
||||
NameTooLong,
|
||||
OutOfMemory,
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
fn check(rc: usize) Error!usize {
|
||||
return switch (errno(rc)) {
|
||||
.SUCCESS => rc,
|
||||
.AGAIN => error.Again,
|
||||
.INTR => error.Interrupted,
|
||||
.ADDRINUSE => error.AddressInUse,
|
||||
.NOENT => error.NotFound,
|
||||
.ACCES, .PERM => error.PermissionDenied,
|
||||
.CONNREFUSED => error.ConnectionRefused,
|
||||
.CONNRESET => error.ConnectionReset,
|
||||
.PIPE => error.BrokenPipe,
|
||||
.NAMETOOLONG => error.NameTooLong,
|
||||
.NOMEM => error.OutOfMemory,
|
||||
else => error.Unexpected,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn read(fd: fd_t, buf: []u8) Error!usize {
|
||||
if (buf.len == 0) return 0;
|
||||
while (true) {
|
||||
return check(linux.read(fd, buf.ptr, buf.len)) catch |err| switch (err) {
|
||||
error.Interrupted => continue,
|
||||
else => err,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(fd: fd_t, bytes: []const u8) Error!usize {
|
||||
if (bytes.len == 0) return 0;
|
||||
return check(linux.write(fd, bytes.ptr, bytes.len));
|
||||
}
|
||||
|
||||
/// Write everything, retrying short writes. Best effort: errors are swallowed
|
||||
/// because every caller is emitting diagnostics or usage text.
|
||||
pub fn writeAllBestEffort(fd: fd_t, bytes: []const u8) void {
|
||||
var off: usize = 0;
|
||||
while (off < bytes.len) {
|
||||
off += write(fd, bytes[off..]) catch return;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(fd: fd_t) void {
|
||||
_ = linux.close(fd);
|
||||
}
|
||||
|
||||
pub fn socket(domain: u32, socket_type: u32, protocol: u32) Error!fd_t {
|
||||
return @intCast(try check(linux.socket(domain, socket_type, protocol)));
|
||||
}
|
||||
|
||||
pub fn bind(fd: fd_t, addr: *const linux.sockaddr, len: linux.socklen_t) Error!void {
|
||||
_ = try check(linux.bind(fd, addr, len));
|
||||
}
|
||||
|
||||
pub fn listen(fd: fd_t, backlog: u31) Error!void {
|
||||
_ = try check(linux.listen(fd, backlog));
|
||||
}
|
||||
|
||||
pub fn accept4(fd: fd_t, flags: u32) Error!fd_t {
|
||||
return @intCast(try check(linux.accept4(fd, null, null, flags)));
|
||||
}
|
||||
|
||||
pub fn connect(fd: fd_t, addr: *const linux.sockaddr, len: linux.socklen_t) Error!void {
|
||||
_ = try check(linux.connect(fd, addr, len));
|
||||
}
|
||||
|
||||
pub fn ftruncate(fd: fd_t, length: u64) Error!void {
|
||||
_ = try check(linux.ftruncate(fd, @intCast(length)));
|
||||
}
|
||||
|
||||
/// Make a memfd immutable, so a compositor mapping it cannot have the bytes
|
||||
/// changed underneath it.
|
||||
pub fn addSeals(fd: fd_t, seals: usize) Error!void {
|
||||
_ = try check(linux.fcntl(fd, linux.F.ADD_SEALS, seals));
|
||||
}
|
||||
|
||||
pub fn timerfdCreate(flags: linux.TFD) Error!fd_t {
|
||||
return @intCast(try check(linux.timerfd_create(.MONOTONIC, flags)));
|
||||
}
|
||||
|
||||
pub fn timerfdSetTime(fd: fd_t, spec: *const linux.itimerspec) Error!void {
|
||||
_ = try check(linux.timerfd_settime(fd, .{}, spec, null));
|
||||
}
|
||||
|
||||
pub fn fork() Error!pid_t {
|
||||
return @intCast(try check(linux.fork()));
|
||||
}
|
||||
|
||||
pub fn setsid() void {
|
||||
_ = linux.setsid();
|
||||
}
|
||||
|
||||
pub fn exit(code: u8) noreturn {
|
||||
linux.exit(code);
|
||||
}
|
||||
|
||||
pub fn waitpid(pid: pid_t) void {
|
||||
var status: u32 = undefined;
|
||||
while (true) {
|
||||
const rc = linux.wait4(pid, &status, 0, null);
|
||||
switch (errno(rc)) {
|
||||
.INTR => continue,
|
||||
else => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a path. Best effort: the only caller is clearing a stale socket.
|
||||
pub fn unlink(path: []const u8) void {
|
||||
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
if (path.len >= buf.len) return;
|
||||
@memcpy(buf[0..path.len], path);
|
||||
buf[path.len] = 0;
|
||||
_ = linux.unlink(@ptrCast(&buf));
|
||||
}
|
||||
|
||||
/// Build a unix socket address. Paths must fit in sun_path with room for the
|
||||
/// terminating NUL.
|
||||
pub fn sockaddrUn(path: []const u8) Error!linux.sockaddr.un {
|
||||
var addr: linux.sockaddr.un = .{ .family = linux.AF.UNIX, .path = undefined };
|
||||
if (path.len >= addr.path.len) return error.NameTooLong;
|
||||
@memset(&addr.path, 0);
|
||||
@memcpy(addr.path[0..path.len], path);
|
||||
return addr;
|
||||
}
|
||||
|
||||
pub fn sockaddrUnLen(addr: *const linux.sockaddr.un) linux.socklen_t {
|
||||
_ = addr;
|
||||
return @sizeOf(linux.sockaddr.un);
|
||||
}
|
||||
|
||||
/// execvp: run `argv[0]`, searching PATH when it contains no slash.
|
||||
///
|
||||
/// Only ever called between fork() and exec in the child, so it must not
|
||||
/// allocate; the candidate path is assembled in a stack buffer.
|
||||
pub fn execvpe(
|
||||
argv: [*:null]const ?[*:0]const u8,
|
||||
envp: [*:null]const ?[*:0]const u8,
|
||||
path_env: ?[]const u8,
|
||||
) Error {
|
||||
const file = std.mem.span(argv[0].?);
|
||||
|
||||
if (std.mem.indexOfScalar(u8, file, '/') != null) {
|
||||
return execErr(linux.execve(argv[0].?, argv, envp));
|
||||
}
|
||||
|
||||
const search = path_env orelse "/usr/local/bin:/usr/bin:/bin";
|
||||
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
var last: Error = error.NotFound;
|
||||
|
||||
var it = std.mem.tokenizeScalar(u8, search, ':');
|
||||
while (it.next()) |dir| {
|
||||
if (dir.len + 1 + file.len + 1 > buf.len) continue;
|
||||
@memcpy(buf[0..dir.len], dir);
|
||||
buf[dir.len] = '/';
|
||||
@memcpy(buf[dir.len + 1 ..][0..file.len], file);
|
||||
buf[dir.len + 1 + file.len] = 0;
|
||||
const candidate: [*:0]const u8 = @ptrCast(&buf);
|
||||
|
||||
last = execErr(linux.execve(candidate, argv, envp));
|
||||
// ENOENT just means "not in this directory"; keep looking.
|
||||
if (last != error.NotFound) return last;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
/// execve only returns on failure, so its result is always an error.
|
||||
fn execErr(rc: usize) Error {
|
||||
_ = check(rc) catch |err| return err;
|
||||
return error.Unexpected;
|
||||
}
|
||||
Reference in New Issue
Block a user