How do you handle libc errno?

I’m learning the basics of working with sockets so I was using std.os.linux for LibC-like wrappers to syscalls, and I noticed that it returns a usize, not an error union, which means I have to do the C-style error checking rather than being able to use try statements to bubble up errors. I don’t know how to do compile time shenanigans yet, so I just used a few vim macros to create this monstrosity of a mapping.

Long block of mapping code
const std = @import("std");
const builtin = @import("builtin");
const linux = std.os.linux;

/// Error set generated from std.os.linux.E
pub const LinuxError = error{
    /// Operation not permitted
    PERM,
    /// No such file or directory
    NOENT,
    /// No such process
    SRCH,
    /// Interrupted system call
    INTR,
    /// I/O error
    IO,
    /// No such device or address
    NXIO,
    /// Arg list too long
    @"2BIG",
    /// Exec format error
    NOEXEC,
    /// Bad file number
    BADF,
    /// No child processes
    CHILD,
    /// Try again
    /// Also means: WOULDBLOCK: operation would block
    AGAIN,
    /// Out of memory
    NOMEM,
    /// Permission denied
    ACCES,
    /// Bad address
    FAULT,
    /// Block device required
    NOTBLK,
    /// Device or resource busy
    BUSY,
    /// File exists
    EXIST,
    /// Cross-device link
    XDEV,
    /// No such device
    NODEV,
    /// Not a directory
    NOTDIR,
    /// Is a directory
    ISDIR,
    /// Invalid argument
    INVAL,
    /// File table overflow
    NFILE,
    /// Too many open files
    MFILE,
    /// Not a typewriter
    NOTTY,
    /// Text file busy
    TXTBSY,
    /// File too large
    FBIG,
    /// No space left on device
    NOSPC,
    /// Illegal seek
    SPIPE,
    /// Read-only file system
    ROFS,
    /// Too many links
    MLINK,
    /// Broken pipe
    PIPE,
    /// Math argument out of domain of func
    DOM,
    /// Math result not representable
    RANGE,
    /// Resource deadlock would occur
    DEADLK,
    /// File name too long
    NAMETOOLONG,
    /// No record locks available
    NOLCK,
    /// Function not implemented
    NOSYS,
    /// Directory not empty
    NOTEMPTY,
    /// Too many symbolic links encountered
    LOOP,
    /// No message of desired type
    NOMSG,
    /// Identifier removed
    IDRM,
    /// Channel number out of range
    CHRNG,
    /// Level 2 not synchronized
    L2NSYNC,
    /// Level 3 halted
    L3HLT,
    /// Level 3 reset
    L3RST,
    /// Link number out of range
    LNRNG,
    /// Protocol driver not attached
    UNATCH,
    /// No CSI structure available
    NOCSI,
    /// Level 2 halted
    L2HLT,
    /// Invalid exchange
    BADE,
    /// Invalid request descriptor
    BADR,
    /// Exchange full
    XFULL,
    /// No anode
    NOANO,
    /// Invalid request code
    BADRQC,
    /// Invalid slot
    BADSLT,
    /// Bad font file format
    BFONT,
    /// Device not a stream
    NOSTR,
    /// No data available
    NODATA,
    /// Timer expired
    TIME,
    /// Out of streams resources
    NOSR,
    /// Machine is not on the network
    NONET,
    /// Package not installed
    NOPKG,
    /// Object is remote
    REMOTE,
    /// Link has been severed
    NOLINK,
    /// Advertise error
    ADV,
    /// Srmount error
    SRMNT,
    /// Communication error on send
    COMM,
    /// Protocol error
    PROTO,
    /// Multihop attempted
    MULTIHOP,
    /// RFS specific error
    DOTDOT,
    /// Not a data message
    BADMSG,
    /// Value too large for defined data type
    OVERFLOW,
    /// Name not unique on network
    NOTUNIQ,
    /// File descriptor in bad state
    BADFD,
    /// Remote address changed
    REMCHG,
    /// Can not access a needed shared library
    LIBACC,
    /// Accessing a corrupted shared library
    LIBBAD,
    /// .lib section in a.out corrupted
    LIBSCN,
    /// Attempting to link in too many shared libraries
    LIBMAX,
    /// Cannot exec a shared library directly
    LIBEXEC,
    /// Illegal byte sequence
    ILSEQ,
    /// Interrupted system call should be restarted
    RESTART,
    /// Streams pipe error
    STRPIPE,
    /// Too many users
    USERS,
    /// Socket operation on non-socket
    NOTSOCK,
    /// Destination address required
    DESTADDRREQ,
    /// Message too long
    MSGSIZE,
    /// Protocol wrong type for socket
    PROTOTYPE,
    /// Protocol not available
    NOPROTOOPT,
    /// Protocol not supported
    PROTONOSUPPORT,
    /// Socket type not supported
    SOCKTNOSUPPORT,
    /// Operation not supported on transport endpoint
    /// This code also means `NOTSUP`.
    OPNOTSUPP,
    /// Protocol family not supported
    PFNOSUPPORT,
    /// Address family not supported by protocol
    AFNOSUPPORT,
    /// Address already in use
    ADDRINUSE,
    /// Cannot assign requested address
    ADDRNOTAVAIL,
    /// Network is down
    NETDOWN,
    /// Network is unreachable
    NETUNREACH,
    /// Network dropped connection because of reset
    NETRESET,
    /// Software caused connection abort
    CONNABORTED,
    /// Connection reset by peer
    CONNRESET,
    /// No buffer space available
    NOBUFS,
    /// Transport endpoint is already connected
    ISCONN,
    /// Transport endpoint is not connected
    NOTCONN,
    /// Cannot send after transport endpoint shutdown
    SHUTDOWN,
    /// Too many references: cannot splice
    TOOMANYREFS,
    /// Connection timed out
    TIMEDOUT,
    /// Connection refused
    CONNREFUSED,
    /// Host is down
    HOSTDOWN,
    /// No route to host
    HOSTUNREACH,
    /// Operation already in progress
    ALREADY,
    /// Operation now in progress
    INPROGRESS,
    /// Stale NFS file handle
    STALE,
    /// Structure needs cleaning
    UCLEAN,
    /// Not a XENIX named type file
    NOTNAM,
    /// No XENIX semaphores available
    NAVAIL,
    /// Is a named type file
    ISNAM,
    /// Remote I/O error
    REMOTEIO,
    /// Quota exceeded
    DQUOT,
    /// No medium found
    NOMEDIUM,
    /// Wrong medium type
    MEDIUMTYPE,
    /// Operation canceled
    CANCELED,
    /// Required key not available
    NOKEY,
    /// Key has expired
    KEYEXPIRED,
    /// Key has been revoked
    KEYREVOKED,
    /// Key was rejected by service
    KEYREJECTED,
    // for robust mutexes
    /// Owner died
    OWNERDEAD,
    /// State not recoverable
    NOTRECOVERABLE,
    /// Operation not possible due to RF-kill
    RFKILL,
    /// Memory page has hardware error
    HWPOISON,
    // nameserver query return codes
    /// DNS server returned answer with no data
    NSRNODATA,
    /// DNS server claims query was misformatted
    NSRFORMERR,
    /// DNS server returned general failure
    NSRSERVFAIL,
    /// Domain name not found
    NSRNOTFOUND,
    /// DNS server does not implement requested operation
    NSRNOTIMP,
    /// DNS server refused query
    NSRREFUSED,
    /// Misformatted DNS query
    NSRBADQUERY,
    /// Misformatted domain name
    NSRBADNAME,
    /// Unsupported address family
    NSRBADFAMILY,
    /// Misformatted DNS reply
    NSRBADRESP,
    /// Could not contact DNS servers
    NSRCONNREFUSED,
    /// Timeout while contacting DNS servers
    NSRTIMEOUT,
    /// End of file
    NSROF,
    /// Error reading file
    NSRFILE,
    /// Out of memory
    NSRNOMEM,
    /// Application terminated lookup
    NSRDESTRUCTION,
    /// Domain name is too long
    NSRQUERYDOMAINTOOLONG,
    /// Domain name is too long
    NSRCNAMELOOP,
};

/// Converts Libc errno to Zig error values, courtesy of vim macros running
/// at 1 frame per minute.
pub fn errnoToError(errno: linux.E) LinuxError {
    if (builtin.os.tag != .linux) {
        @compileError("Idk how to work with anything but linux.");
    }

    switch (errno) {
        .PERM => LinuxError.PERM,
        .NOENT => LinuxError.NOENT,
        .SRCH => LinuxError.SRCH,
        .INTR => LinuxError.INTR,
        .IO => LinuxError.IO,
        .NXIO => LinuxError.NXIO,
        .@"2BIG" => LinuxError.@"2BIG",
        .NOEXEC => LinuxError.NOEXEC,
        .BADF => LinuxError.BADF,
        .CHILD => LinuxError.CHILD,
        .AGAIN => LinuxError.AGAIN,
        .NOMEM => LinuxError.NOMEM,
        .ACCES => LinuxError.ACCES,
        .FAULT => LinuxError.FAULT,
        .NOTBLK => LinuxError.NOTBLK,
        .BUSY => LinuxError.BUSY,
        .EXIST => LinuxError.EXIST,
        .XDEV => LinuxError.XDEV,
        .NODEV => LinuxError.NODEV,
        .NOTDIR => LinuxError.NOTDIR,
        .ISDIR => LinuxError.ISDIR,
        .INVAL => LinuxError.INVAL,
        .NFILE => LinuxError.NFILE,
        .MFILE => LinuxError.MFILE,
        .NOTTY => LinuxError.NOTTY,
        .TXTBSY => LinuxError.TXTBSY,
        .FBIG => LinuxError.FBIG,
        .NOSPC => LinuxError.NOSPC,
        .SPIPE => LinuxError.SPIPE,
        .ROFS => LinuxError.ROFS,
        .MLINK => LinuxError.MLINK,
        .PIPE => LinuxError.PIPE,
        .DOM => LinuxError.DOM,
        .RANGE => LinuxError.RANGE,
        .DEADLK => LinuxError.DEADLK,
        .NAMETOOLONG => LinuxError.NAMETOOLONG,
        .NOLCK => LinuxError.NOLCK,
        .NOSYS => LinuxError.NOSYS,
        .NOTEMPTY => LinuxError.NOTEMPTY,
        .LOOP => LinuxError.LOOP,
        .NOMSG => LinuxError.NOMSG,
        .IDRM => LinuxError.IDRM,
        .CHRNG => LinuxError.CHRNG,
        .L2NSYNC => LinuxError.L2NSYNC,
        .L3HLT => LinuxError.L3HLT,
        .L3RST => LinuxError.L3RST,
        .LNRNG => LinuxError.LNRNG,
        .UNATCH => LinuxError.UNATCH,
        .NOCSI => LinuxError.NOCSI,
        .L2HLT => LinuxError.L2HLT,
        .BADE => LinuxError.BADE,
        .BADR => LinuxError.BADR,
        .XFULL => LinuxError.XFULL,
        .NOANO => LinuxError.NOANO,
        .BADRQC => LinuxError.BADRQC,
        .BADSLT => LinuxError.BADSLT,
        .BFONT => LinuxError.BFONT,
        .NOSTR => LinuxError.NOSTR,
        .NODATA => LinuxError.NODATA,
        .TIME => LinuxError.TIME,
        .NOSR => LinuxError.NOSR,
        .NONET => LinuxError.NONET,
        .NOPKG => LinuxError.NOPKG,
        .REMOTE => LinuxError.REMOTE,
        .NOLINK => LinuxError.NOLINK,
        .ADV => LinuxError.ADV,
        .SRMNT => LinuxError.SRMNT,
        .COMM => LinuxError.COMM,
        .PROTO => LinuxError.PROTO,
        .MULTIHOP => LinuxError.MULTIHOP,
        .DOTDOT => LinuxError.DOTDOT,
        .BADMSG => LinuxError.BADMSG,
        .OVERFLOW => LinuxError.OVERFLOW,
        .NOTUNIQ => LinuxError.NOTUNIQ,
        .BADFD => LinuxError.BADFD,
        .REMCHG => LinuxError.REMCHG,
        .LIBACC => LinuxError.LIBACC,
        .LIBBAD => LinuxError.LIBBAD,
        .LIBSCN => LinuxError.LIBSCN,
        .LIBMAX => LinuxError.LIBMAX,
        .LIBEXEC => LinuxError.LIBEXEC,
        .ILSEQ => LinuxError.ILSEQ,
        .RESTART => LinuxError.RESTART,
        .STRPIPE => LinuxError.STRPIPE,
        .USERS => LinuxError.USERS,
        .NOTSOCK => LinuxError.NOTSOCK,
        .DESTADDRREQ => LinuxError.DESTADDRREQ,
        .MSGSIZE => LinuxError.MSGSIZE,
        .PROTOTYPE => LinuxError.PROTOTYPE,
        .NOPROTOOPT => LinuxError.NOPROTOOPT,
        .PROTONOSUPPORT => LinuxError.PROTONOSUPPORT,
        .SOCKTNOSUPPORT => LinuxError.SOCKTNOSUPPORT,
        .OPNOTSUPP => LinuxError.OPNOTSUPP,
        .PFNOSUPPORT => LinuxError.PFNOSUPPORT,
        .AFNOSUPPORT => LinuxError.AFNOSUPPORT,
        .ADDRINUSE => LinuxError.ADDRINUSE,
        .ADDRNOTAVAIL => LinuxError.ADDRNOTAVAIL,
        .NETDOWN => LinuxError.NETDOWN,
        .NETUNREACH => LinuxError.NETUNREACH,
        .NETRESET => LinuxError.NETRESET,
        .CONNABORTED => LinuxError.CONNABORTED,
        .CONNRESET => LinuxError.CONNRESET,
        .NOBUFS => LinuxError.NOBUFS,
        .ISCONN => LinuxError.ISCONN,
        .NOTCONN => LinuxError.NOTCONN,
        .SHUTDOWN => LinuxError.SHUTDOWN,
        .TOOMANYREFS => LinuxError.TOOMANYREFS,
        .TIMEDOUT => LinuxError.TIMEDOUT,
        .CONNREFUSED => LinuxError.CONNREFUSED,
        .HOSTDOWN => LinuxError.HOSTDOWN,
        .HOSTUNREACH => LinuxError.HOSTUNREACH,
        .ALREADY => LinuxError.ALREADY,
        .INPROGRESS => LinuxError.INPROGRESS,
        .STALE => LinuxError.STALE,
        .UCLEAN => LinuxError.UCLEAN,
        .NOTNAM => LinuxError.NOTNAM,
        .NAVAIL => LinuxError.NAVAIL,
        .ISNAM => LinuxError.ISNAM,
        .REMOTEIO => LinuxError.REMOTEIO,
        .DQUOT => LinuxError.DQUOT,
        .NOMEDIUM => LinuxError.NOMEDIUM,
        .MEDIUMTYPE => LinuxError.MEDIUMTYPE,
        .CANCELED => LinuxError.CANCELED,
        .NOKEY => LinuxError.NOKEY,
        .KEYEXPIRED => LinuxError.KEYEXPIRED,
        .KEYREVOKED => LinuxError.KEYREVOKED,
        .KEYREJECTED => LinuxError.KEYREJECTED,
        .OWNERDEAD => LinuxError.OWNERDEAD,
        .NOTRECOVERABLE => LinuxError.NOTRECOVERABLE,
        .RFKILL => LinuxError.RFKILL,
        .HWPOISON => LinuxError.HWPOISON,
        .NSRNODATA => LinuxError.NSRNODATA,
        .NSRFORMERR => LinuxError.NSRFORMERR,
        .NSRSERVFAIL => LinuxError.NSRSERVFAIL,
        .NSRNOTFOUND => LinuxError.NSRNOTFOUND,
        .NSRNOTIMP => LinuxError.NSRNOTIMP,
        .NSRREFUSED => LinuxError.NSRREFUSED,
        .NSRBADQUERY => LinuxError.NSRBADQUERY,
        .NSRBADNAME => LinuxError.NSRBADNAME,
        .NSRBADFAMILY => LinuxError.NSRBADFAMILY,
        .NSRBADRESP => LinuxError.NSRBADRESP,
        .NSRCONNREFUSED => LinuxError.NSRCONNREFUSED,
        .NSRTIMEOUT => LinuxError.NSRTIMEOUT,
        .NSROF => LinuxError.NSROF,
        .NSRFILE => LinuxError.NSRFILE,
        .NSRNOMEM => LinuxError.NSRNOMEM,
        .NSRDESTRUCTION => LinuxError.NSRDESTRUCTION,
        .NSRQUERYDOMAINTOOLONG => LinuxError.NSRQUERYDOMAINTOOLONG,
        .NSRCNAMELOOP => LinuxError.NSRCNAMELOOP,
    }
}

pub fn err(rv: usize) LinuxError!usize {
    if (rv >= 0) {
        return rv;
    }

    return errnoToError(linux.errno(rv));
}

Now I get to write code like this when using syscalls

const std = @import("std");
const linux = std.os.linux;
const e = @import("linuxerrors.zig");

pub fn createTcpServer(host_ip: usize, port: usize) e.LinuxError!void {
    _ = host_ip;
    _ = port;

    const sock_fd = try e.err(linux.socket(
        linux.AF.INET,
        linux.SOCK.STREAM,
        linux.IPPROTO.TCP,
    ));
}

My two questions are:

  1. Is there a better way to do this? maybe this mapping exists somewhere in the standard library that I can use or there’s some comptime magic to convert std.os.linux.E to an error set that is LSP-friendly?
  2. How do I actually report the errors? If I want to do it in C I do perror("thing I was trynna do"), but since I’m trying to not link against libc (for fun, no particular reason), I was wondering if I could use comptime magic to access to docstring above the error variant, convert it to a string and report the error? Otherwise I could use more vim macros to just create the mapping.
1 Like
  1. You could take a look at how the standard library does it. Here is the Threaded way of opening a posix socket: https://codeberg.org/ziglang/zig/src/commit/a85cb728775375825afe4ebd62c60ae0b361d1e9/lib/std/Io/Threaded.zig#L12396

You’ll notice that they are doing the mapping to the errors directly at the call, allows for more clear error messages instead of just the generic Linux ones.

There doesn’t seem to be a standard way for what you are looking to do. I would even push back here. Not every syscall uses every errno. So having a generic LinuxError means you are creating more work for yourself at the catch sites. Instead of dealing with just the errors for that specific syscall, you now have to worry about all possible linux errors.

  1. Error reporting can be done in many ways. You can use std.debug.panic("thing I was trynna do");, you could std.log.err("message here", .{}); as well and keep going. Kinda depends on the behavior you want.
1 Like
  1. The code linked does the mapping as shown below, but it looks like they’re also handling all values of the errno enum possible by just catching the invalid ones by the else branch, so isn’t it just easier if this was an error set rather than an enum because then I can try and bubble it up. The other option would be having a mapping between syscalls and error subsets as specified by the man pages for each syscall but I couldn’t find that anywhere.

        const socket_fd = while (true) {
            const rc = posix.system.socket(family, flags, protocol);
            switch (posix.errno(rc)) {
                .SUCCESS => {
                    syscall.finish();
                    const fd: posix.fd_t = @intCast(rc);
                    errdefer closeFd(fd);
                    if (socket_flags_unsupported) try setCloexec(fd);
                    break fd;
                },
                .INTR => {
                    try syscall.checkCancel();
                    continue;
                },
                .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported),
                .INVAL => return syscall.fail(error.ProtocolUnsupportedBySystem),
                .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded),
                .NFILE => return syscall.fail(error.SystemFdQuotaExceeded),
                .NOBUFS => return syscall.fail(error.SystemResources),
                .NOMEM => return syscall.fail(error.SystemResources),
                .PROTONOSUPPORT => return syscall.fail(error.ProtocolUnsupportedByAddressFamily),
                .PROTOTYPE => return syscall.fail(error.SocketModeUnsupported),
                else => |err| return syscall.unexpectedErrno(err),
            }
        };
      
    

    Also, I tried looking for the function openSocketPosix in Io.Threaded but didn’t find that on my installation (using 0.16.0)

  2. I was looking for something similar to what perror does, where it takes the errno value (so this function will take LinuxError), and give the printable error message. So perror(LinuxError.NSRNOMEM) should give "Out of memory" which is the docstring for that error variant. Then I can do std.debug.panic("createsocket: {s}", .{perror(myerror)}) or something like that. Can I access the docstring for the error variant via something like @typeInfo? I’m open to better methods of reporting too, this is just whatever I thought of that looks similar to perror.

Yeah, that’s a common idiom. Ignore the values of linux.E that you know shouldn’t happen with that syscall and have a helpful error message on those extreme cases where that does happen. To some degree, they are codifying the expected errors with that switch.

It’s not a public function, so it is not accessible in userland. To use it, you will likely have to copy the code and modify it.

I think the closest you will get is:

std.debug.panic("createsocket: {t}",  .{myerror});

That will print the error name in the message. If you want a full message, then a switch on error will probably be the best bet.