zig/lib/std / os/plan9.zig

const std = @import("../std.zig");
const builtin = @import("builtin");

fd_t

pub const fd_t = i32;

STDIN_FILENO

pub const STDIN_FILENO = 0;

STDOUT_FILENO

pub const STDOUT_FILENO = 1;

STDERR_FILENO

pub const STDERR_FILENO = 2;

PATH_MAX

pub const PATH_MAX = 1023;

syscall_bits

pub const syscall_bits = switch (builtin.cpu.arch) {
    .x86_64 => @import("plan9/x86_64.zig"),
    else => @compileError("more plan9 syscall implementations (needs more inline asm in stage2"),
};

E

plan9/errno.zig
pub const E = @import("plan9/errno.zig").E;

getErrno()

Get the errno from a syscall return value, or 0 for no error.

pub fn getErrno(r: usize) E {
    const signed_r = @as(isize, @bitCast(r));
    const int = if (signed_r > -4096 and signed_r < 0) -signed_r else 0;
    return @as(E, @enumFromInt(int));
}
// The max bytes that can be in the errstr buff

ERRMAX

pub const ERRMAX = 128;
var errstr_buf: [ERRMAX]u8 = undefined;

errstr()

Gets whatever the last errstr was

pub fn errstr() []const u8 {
    _ = syscall_bits.syscall2(.ERRSTR, @intFromPtr(&errstr_buf), ERRMAX);
    return std.mem.span(@as([*:0]u8, @ptrCast(&errstr_buf)));
}

Plink

pub const Plink = anyopaque;

Tos

pub const Tos = extern struct {
    prof: extern struct {
        pp: *Plink,
        next: *Plink,
        last: *Plink,
        first: *Plink,
        pid: u32,
        what: u32,
    },
    cyclefreq: u64,
    kcycles: i64,
    pcycles: i64,
    pid: u32,
    clock: u32,
    // top of stack is here
};

pub var tos: *Tos = undefined; // set in start.zig

getpid()

Per process profiling known to be 0(ptr) known to be 4(ptr) cycle clock frequency if there is one, 0 otherwise cycles spent in kernel cycles spent in process (kernel + user) might as well put the pid here

pub fn getpid() u32 {
    return tos.pid;
}

SIG

pub const SIG = struct {
    pub const HUP = 1;
    pub const INT = 2;
    pub const QUIT = 3;
    pub const ILL = 4;
    pub const ABRT = 5;
    pub const FPE = 6;
    pub const KILL = 7;
    pub const SEGV = 8;
    pub const PIPE = 9;
    pub const ALRM = 10;
    pub const TERM = 11;
    pub const USR1 = 12;
    pub const USR2 = 13;
    pub const BUS = 14;
    // The following symbols must be defined, but the signals needn't be supported
    pub const CHLD = 15;
    pub const CONT = 16;
    pub const STOP = 17;
    pub const TSTP = 18;
    pub const TTIN = 19;
    pub const TTOU = 20;
};

sigset_t

hangup interrupt quit illegal instruction (not reset when caught) used by abort floating point exception kill (cannot be caught or ignored) segmentation violation write on a pipe with no one to read it alarm clock software termination signal from kill user defined signal 1 user defined signal 2 bus error child process terminated or stopped continue if stopped stop interactive stop read from ctl tty by member of background write to ctl tty by member of background

pub const sigset_t = c_long;

empty_sigset

pub const empty_sigset = 0;

siginfo_t

pub const siginfo_t = c_long;
// TODO plan9 doesn't have sigaction_fn. Sigaction is not a union, but we incude it here to be compatible.

Sigaction

pub const Sigaction = extern struct {
    pub const handler_fn = *const fn (c_int) callconv(.C) void;
    pub const sigaction_fn = *const fn (c_int, *const siginfo_t, ?*const anyopaque) callconv(.C) void;

    handler: extern union {
        handler: ?handler_fn,
        sigaction: ?sigaction_fn,
    },
    mask: sigset_t,
    flags: c_int,
};

AT

pub const AT = struct {
    pub const FDCWD = -100; // we just make up a constant; FDCWD and openat don't actually exist in plan9
};
// TODO implement sigaction
// right now it is just a shim to allow using start.zig code

sigaction()

pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigaction) usize {
    _ = oact;
    _ = act;
    _ = sig;
    return 0;
}

SYS

pub const SYS = enum(usize) {
    SYSR1 = 0,
    _ERRSTR = 1,
    BIND = 2,
    CHDIR = 3,
    CLOSE = 4,
    DUP = 5,
    ALARM = 6,
    EXEC = 7,
    EXITS = 8,
    _FSESSION = 9,
    FAUTH = 10,
    _FSTAT = 11,
    SEGBRK = 12,
    _MOUNT = 13,
    OPEN = 14,
    _READ = 15,
    OSEEK = 16,
    SLEEP = 17,
    _STAT = 18,
    RFORK = 19,
    _WRITE = 20,
    PIPE = 21,
    CREATE = 22,
    FD2PATH = 23,
    BRK_ = 24,
    REMOVE = 25,
    _WSTAT = 26,
    _FWSTAT = 27,
    NOTIFY = 28,
    NOTED = 29,
    SEGATTACH = 30,
    SEGDETACH = 31,
    SEGFREE = 32,
    SEGFLUSH = 33,
    RENDEZVOUS = 34,
    UNMOUNT = 35,
    _WAIT = 36,
    SEMACQUIRE = 37,
    SEMRELEASE = 38,
    SEEK = 39,
    FVERSION = 40,
    ERRSTR = 41,
    STAT = 42,
    FSTAT = 43,
    WSTAT = 44,
    FWSTAT = 45,
    MOUNT = 46,
    AWAIT = 47,
    PREAD = 50,
    PWRITE = 51,
    TSEMACQUIRE = 52,
    _NSEC = 53,
};

write()

pub fn write(fd: i32, buf: [*]const u8, count: usize) usize {
    return syscall_bits.syscall4(.PWRITE, @bitCast(@as(isize, fd)), @intFromPtr(buf), count, @bitCast(@as(isize, -1)));
}

pwrite()

pub fn pwrite(fd: i32, buf: [*]const u8, count: usize, offset: isize) usize {
    return syscall_bits.syscall4(.PWRITE, @bitCast(@as(isize, fd)), @intFromPtr(buf), count, @bitCast(offset));
}

read()

pub fn read(fd: i32, buf: [*]const u8, count: usize) usize {
    return syscall_bits.syscall4(.PREAD, @bitCast(@as(isize, fd)), @intFromPtr(buf), count, @bitCast(@as(isize, -1)));
}

pread()

pub fn pread(fd: i32, buf: [*]const u8, count: usize, offset: isize) usize {
    return syscall_bits.syscall4(.PREAD, @bitCast(@as(isize, fd)), @intFromPtr(buf), count, @bitCast(offset));
}

open()

pub fn open(path: [*:0]const u8, flags: u32) usize {
    return syscall_bits.syscall2(.OPEN, @intFromPtr(path), @bitCast(@as(isize, flags)));
}

openat()

pub fn openat(dirfd: i32, path: [*:0]const u8, flags: u32, _: mode_t) usize {
    // we skip perms because only create supports perms
    if (dirfd == AT.FDCWD) { // openat(AT_FDCWD, ...) == open(...)
        return open(path, flags);
    }
    var dir_path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
    var total_path_buf: [std.fs.MAX_PATH_BYTES + 1]u8 = undefined;
    const rc = fd2path(dirfd, &dir_path_buf, std.fs.MAX_PATH_BYTES);
    if (rc != 0) return rc;
    var fba = std.heap.FixedBufferAllocator.init(&total_path_buf);
    var alloc = fba.allocator();
    const dir_path = std.mem.span(@as([*:0]u8, @ptrCast(&dir_path_buf)));
    const total_path = std.fs.path.join(alloc, &.{ dir_path, std.mem.span(path) }) catch unreachable; // the allocation shouldn't fail because it should not exceed MAX_PATH_BYTES
    fba.reset();
    const total_path_z = alloc.dupeZ(u8, total_path) catch unreachable; // should not exceed MAX_PATH_BYTES + 1
    return open(total_path_z.ptr, flags);
}

fd2path()

pub fn fd2path(fd: i32, buf: [*]u8, nbuf: usize) usize {
    return syscall_bits.syscall3(.FD2PATH, @bitCast(@as(isize, fd)), @intFromPtr(buf), nbuf);
}

create()

pub fn create(path: [*:0]const u8, omode: mode_t, perms: usize) usize {
    return syscall_bits.syscall3(.CREATE, @intFromPtr(path), @bitCast(@as(isize, omode)), perms);
}

exit()

pub fn exit(status: u8) noreturn {
    if (status == 0) {
        exits(null);
    } else {
        // TODO plan9 does not have exit codes. You either exit with 0 or a string
        const arr: [1:0]u8 = .{status};
        exits(&arr);
    }
}

exits()

pub fn exits(status: ?[*:0]const u8) noreturn {
    _ = syscall_bits.syscall1(.EXITS, if (status) |s| @intFromPtr(s) else 0);
    unreachable;
}

close()

pub fn close(fd: i32) usize {
    return syscall_bits.syscall1(.CLOSE, @bitCast(@as(isize, fd)));
}

mode_t

pub const mode_t = i32;

O

pub const O = struct {
    pub const READ = 0; // open for read
    pub const RDONLY = 0;
    pub const WRITE = 1; // write
    pub const WRONLY = 1;
    pub const RDWR = 2; // read and write
    pub const EXEC = 3; // execute, == read but check execute permission
    pub const TRUNC = 16; // or'ed in (except for exec), truncate file first
    pub const CEXEC = 32; // or'ed in (per file descriptor), close on exec
    pub const RCLOSE = 64; // or'ed in, remove on close
    pub const EXCL = 0x1000; // or'ed in, exclusive create
};

ExecData

pub const ExecData = struct {
    pub extern const etext: anyopaque;
    pub extern const edata: anyopaque;
    pub extern const end: anyopaque;
};

brk_()

Brk sets the system's idea of the lowest bss location not used by the program (called the break) to addr rounded up to the next multiple of 8 bytes. Locations not less than addr and below the stack pointer may cause a memory violation if accessed. -9front brk(2)

pub fn brk_(addr: usize) i32 {
    return @intCast(syscall_bits.syscall1(.BRK_, addr));
}
var bloc: usize = 0;
var bloc_max: usize = 0;

sbrk()

pub fn sbrk(n: usize) usize {
    if (bloc == 0) {
        // we are at the start
        bloc = @intFromPtr(&ExecData.end);
        bloc_max = @intFromPtr(&ExecData.end);
    }
    var bl = std.mem.alignForward(usize, bloc, std.mem.page_size);
    const n_aligned = std.mem.alignForward(usize, n, std.mem.page_size);
    if (bl + n_aligned > bloc_max) {
        // we need to allocate
        if (brk_(bl + n_aligned) < 0) return 0;
        bloc_max = bl + n_aligned;
    }
    bloc = bloc + n_aligned;
    return bl;
}