205 lines
6.1 KiB
Zig
205 lines
6.1 KiB
Zig
//! 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;
|
|
}
|