Getting errno from usize

Minimal Example:

const epollfd = os.epoll_create1(0);
    if (epollfd < 0) {
        @panic("Could not get epoll fd\n");
    } else {
        std.debug.print(
            "Epoll created: {any}\n",
            .{epollfd},
        );
    }

Here function like os.epoll_create1(0) returns usize. In case of errors, you get back large unsigned integers, sometimes the max for usize. How to get the errno in such cases?

Trying to convert usizeisize does not work due to size mismatch.

Have you looked at @errorFromInt and its inverse function?

I may just be an idiot here, but that’s the first thing that came to mind.

If not that, have you tried @intCast?

That function is making a raw syscall, if you want a function that returns a Zig error use std.posix.epoll_create1. If you want to see how that is implemented you can read the source code and see that it passes the usize to the errno helper function.

2 Likes

Don’t use this. This builtin converts an integer to the global zig error set, which has nothing to do with errno.

@zeroN1 , use std.posix.errno, here is an example:

rval = std.posix.errno(std.os.linux.ioctl(socket, std.os.linux.SIOCSIFFLAGS, @intFromPtr(&ifr)));
        switch (rval) {
            .SUCCESS => {},
            else => {
                return error.NicError;
            },
}

Welcome to ziggit!

4 Likes

Thanks. Just tried this and working nicely:

pub fn get_errno(
    sock: os.fd_t,
) posix.E {
    var fr: [1024]u8 = undefined;
    const e = os.ioctl(
        sock,
        os.SIOCSIFFLAGS,
        @intFromPtr(&fr),
    );
    return posix.errno(e);
}

As @permutationlock pointed out, the function from posix module do return better error messages. Something to work on for revision but this looks fine for now.

Just a quick question: what does the &fr pointer do exactly? From the man page I got:

The third argument is an untyped pointer to memory.  It’s traditionally char *argp (from the days before void * was valid C), and will be so named for this discussion.

Did not make too much sense to me. (I am a newb to low level stuff so still catching on)

The example I provided has nothing to do with epoll, it was just an example of using std.posix.errno. You should not be using ioctl. Could you describe more of what you are trying to accomplish?

const std = @import("std");
const net = std.net;
const os = std.os.linux;
const posix = std.posix;

pub fn get_errno(
    sock: os.fd_t,
) posix.E {
    var fr: [1024]u8 = undefined;
    const e = os.ioctl(
        sock,
        os.SIOCSIFFLAGS,
        @intFromPtr(&fr),
    );
    return posix.errno(e);
}

pub fn start_poll() !void {
    var addr = net.Address.initIp4([4]u8{ 127, 0, 0, 1 }, 19690);
    const listen_sock = try posix.socket(
        addr.any.family,
        posix.SOCK.STREAM,
        posix.IPPROTO.TCP,
    );

    const len: posix.socklen_t = addr.getOsSockLen();
    const lfd = os.bind(listen_sock, &addr.any, len);
    std.debug.print(
        "LFD: {any}\n",
        .{lfd},
    );

    const listfd = os.listen(listen_sock, 5);
    std.debug.print(
        "ListFD: {any}\n",
        .{listfd},
    );

    const epollfd = os.epoll_create1(0);

    const rval = get_errno(listen_sock);
    switch (rval) {
        .SUCCESS => std.debug.print(
            "Successfully created epoll",
            .{},
        ),
        else => std.debug.print(
            "Some error: {any}\n",
            .{rval},
        ),
    }

    if (epollfd < 0) {
        @panic("Could not get epoll fd\n");
    } else {
        std.debug.print(
            "Epoll created: {any}\n",
            .{epollfd},
        );
    }

    var events: [10]os.epoll_event = undefined;
    var ev: os.epoll_event = undefined;
    var conn_sock: usize = undefined;

    ev.events |= os.EPOLL.IN;
    ev.data.fd = listen_sock;

    var epolladd = os.epoll_ctl(
        @intCast(epollfd),
        os.EPOLL.CTL_ADD,
        listen_sock,
        &ev,
    );

    if (epolladd < 0) {
        @panic("Could not add listen_sock to epoll");
    } else {
        std.debug.print(
            "Epoll added: {any}\n",
            .{epolladd},
        );
    }

    var msgbuff: [4096]u8 = undefined;

    while (true) {
        const nfds = os.epoll_wait(
            @intCast(epollfd),
            &events,
            10,
            -1,
        );

        if (nfds < 0) {
            @panic("Could not wait on tasks.\n");
        } else {
            std.debug.print(
                "Got fds after wait: {any}\n",
                .{nfds},
            );
        }

        for (0..nfds) |n| {
            if (events[n].data.fd == listen_sock) {
                // this the socket for the client used for getting and sending data

                // this line is important; the len ptr must be set correctly
                var client_addr_len: posix.socklen_t = addr.getOsSockLen();
                conn_sock = os.accept(listen_sock, &addr.any, &client_addr_len);

                if (conn_sock < 0) {
                    @panic("Could not accept on listening socket");
                } else {
                    std.debug.print(
                        "Connection accepted: {any}\n",
                        .{conn_sock},
                    );
                }

                const fd_64: isize = @bitCast(conn_sock);
                const fd_32: i32 = @intCast(fd_64);
                const fd_t: os.fd_t = @as(os.fd_t, fd_32);
                _ = os.fcntl(fd_t, os.F.SETFL, os.SOCK.NONBLOCK);
                ev.events = os.EPOLL.IN | os.EPOLL.ET;
                ev.data.fd = fd_t;

                epolladd = os.epoll_ctl(@intCast(epollfd), os.EPOLL.CTL_ADD, fd_t, &ev);

                if (epolladd < 0) {
                    @panic("Could not add connection socket");
                } else {
                    // do something here
                    std.debug.print(
                        "Connect sock added to epoll: {any}\n",
                        .{epolladd},
                    );

                    const buff: []const u8 = "Hi there, thanks for connection to our server!\n";
                    const bytes_written = os.write(
                        fd_t,
                        @ptrCast(&buff[0]),
                        buff.len,
                    );

                    std.debug.print("Wrote {} bytes to socket\n", .{bytes_written});
                }

                continue;
            }

            if (events[n].events & os.EPOLL.IN != 0) {
                @memset(&msgbuff, 0);
                const bytes_read = os.read(
                    events[n].data.fd,
                    @ptrCast(&msgbuff[0]),
                    msgbuff.len,
                );
                std.debug.print("Got data. Total bytes received: {d}\nMessage: {sc}\n", .{ bytes_read, msgbuff[0..bytes_read] });
            }
        }
    }
}

So here’s the full script. I am basically trying to learn how non-blocking I/O works. I read somewhere that Node.js event loop uses libuv which I think use epoll and other platform specific syscall for this. I was trying to create a minimal example.

Most of the epoll related code is from man pages on epoll. Hope that explains the usecase.

You’ll need to separate the essential part of @kj4tmp’s example from the incidental parts.

I’d start by reading the documentation for std.posix.errno.

Welcome to Ziggit!

1 Like

Thanks everyone for helping me out with this. It took me a while to understand: I don’t need errno if I simply stick to the functions from posix module. It returns all the errors nicely (as named enums). Here’s the re-written version of the script (leaving it here for reference):

const std = @import("std");
const posix = std.posix;
const os = std.os.linux;
const io = std.io;
const mem = std.mem;
const net = std.net;
const time = std.time;

var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();

fn debug_print(text: []const u8) void {
    const head = "[Debug] ";
    var buff: []u8 = allocator.alloc(u8, text.len + head.*.len) catch {
        return;
    };
    defer allocator.free(buff);

    @memcpy(
        buff[0..head.*.len],
        head[0..head.*.len],
    );
    @memcpy(
        buff[head.*.len .. head.*.len + text.len],
        text[0..],
    );

    _ = io.getStdOut().writer().write(buff) catch |e| {
        std.debug.print(
            "Error in debug print: {any}\n",
            .{e},
        );
    };
}

fn handle_connection(
    sock: posix.fd_t,
    ev: *os.epoll_event,
    addr: net.Address,
    epollfd: usize,
) !*posix.socket_t {
    var client_addr_len = addr.getOsSockLen();
    var conn_sock = posix.accept(
        sock,
        @constCast(&addr.any),
        &client_addr_len,
        posix.SOCK.NONBLOCK,
    ) catch |accept_error| {
        switch (accept_error) {
            error.WouldBlock => {
                debug_print("Would block :(\n");
                return @constCast(&@as(i32, -4));
            },
            else => {
                debug_print("Some error while handling connection\n");
                std.debug.print(
                    "Connection error: {any}\n",
                    .{accept_error},
                );
                return @constCast(&@as(i32, -1));
            },
        }
    };

    _ = posix.fcntl(conn_sock, posix.F.SETFL, posix.SOCK.NONBLOCK) catch |fcntl_error| {
        switch (fcntl_error) {
            error.DeadLock => {
                debug_print("Threads in deadlocked :(\n");
            },
            else => {
                debug_print("Some error while making socket non-block\n");
                std.debug.print(
                    "Connection error: {any}\n",
                    .{fcntl_error},
                );
            },
        }
        return fcntl_error;
    };
    ev.*.events = os.EPOLL.IN | os.EPOLL.ET;
    ev.*.data.fd = conn_sock;

    _ = posix.epoll_ctl(
        @intCast(epollfd),
        os.EPOLL.CTL_ADD,
        conn_sock,
        ev,
    ) catch |epolladd_error| {
        switch (epolladd_error) {
            error.FileDescriptorIncompatibleWithEpoll => {
                debug_print("Bad FD for epoll.\n");
            },
            else => {
                std.debug.print(
                    "Some other error: {any}\n",
                    .{epolladd_error},
                );
            },
        }
        return epolladd_error;
    };

    // send the intro text
    const buff: []const u8 = "Hi there, welcome to our server!\n";
    const bytes_written = try posix.write(
        conn_sock,
        buff,
    );
    std.debug.print(
        "New connection: {any}\n",
        .{conn_sock},
    );

    _ = bytes_written;

    return &conn_sock;
}

pub fn start_poll() !void {
    defer _ = gpa.deinit();
    debug_print("Starting event loop\n");

    var addr = net.Address.initIp4(
        [4]u8{ 127, 0, 0, 1 },
        19690,
    );
    const listen_sock = try posix.socket(
        addr.any.family,
        posix.SOCK.STREAM,
        posix.IPPROTO.TCP,
    );

    const len = addr.getOsSockLen();
    posix.bind(
        listen_sock,
        &addr.any,
        len,
    ) catch |bind_error| {
        switch (bind_error) {
            error.AccessDenied, error.AddressInUse, error.AlreadyBound => {
                debug_print("Access denied or addres not free\n");
            },
            else => {
                debug_print("Some other error\n");
            },
        }
        return bind_error;
    };

    debug_print("Listening socket bound\n");

    posix.listen(listen_sock, 2) catch |listen_error| {
        switch (listen_error) {
            else => {
                std.debug.print(
                    "Error while listening to listen_sock: {any}\n",
                    .{listen_error},
                );
            },
        }
        return listen_error;
    };

    const epollfd = posix.epoll_create1(0) catch |epoll_error| {
        return epoll_error;
    };
    debug_print("Epoll initialized\n");

    var events: [10]os.epoll_event = undefined;
    var ev: os.epoll_event = undefined;

    ev.events |= posix.POLL.IN;
    ev.data.fd = listen_sock;

    _ = posix.epoll_ctl(
        epollfd,
        @intCast(os.EPOLL.CTL_ADD),
        listen_sock,
        &ev,
    ) catch |epolladd_error| {
        return epolladd_error;
    };

    debug_print("Listening sock added to epoll\n");

    while (true) {
        const nfds = posix.epoll_wait(
            @intCast(epollfd),
            &events,
            -1,
        );

        for (0..nfds) |n| {
            if (events[n].data.fd == listen_sock) {
                const conn = try handle_connection(
                    listen_sock,
                    &events[n],
                    addr,
                    @intCast(epollfd),
                );

                _ = conn;
            }

            if (events[n].events & os.EPOLL.IN != 0) {
                debug_print("Got message; TODO: handle it\n");
            }
        }
    }
}

2 Likes