ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
33.67k stars 2.47k forks source link

sigsetsize argument to epoll_pwait is incorrect #12715

Open gpanders opened 2 years ago

gpanders commented 2 years ago

Zig Version

0.10.0-dev.3840+2b92c5a23

Steps to Reproduce

Call std.os.linux.epoll_pwait with a non-null sigmask argument.

Complete working example:

const std = @import("std");

pub fn main() anyerror!void {
    var sock = std.net.StreamServer.init(.{});
    defer sock.deinit();

    try sock.listen(try std.net.Address.resolveIp("0.0.0.0", 5151));

    const epfd = try std.os.epoll_create1(0);
    defer std.os.close(epfd);

    var sigmask = std.os.empty_sigset;
    try std.os.epoll_ctl(epfd, std.os.linux.EPOLL.CTL_ADD, sock.sockfd.?, &std.os.linux.epoll_event{
        .events = std.os.linux.EPOLL.IN,
        .data = .{ .fd = sock.sockfd.? },
    });

    var events: [1]std.os.linux.epoll_event = undefined;
    const rc = std.os.linux.epoll_pwait(epfd, &events, 1, -1, &sigmask);
    switch (std.os.errno(rc)) {
        .SUCCESS => std.debug.print("epoll_pwait succeeded\n", .{}),
        .INVAL => std.debug.print("EINVAL\n", .{}),
        else => unreachable,
    }
}

Compile with zig build-exe and run.

Expected Behavior

It should print epoll_pwait succeeded.

Actual Behavior

It prints EINVAL.

Zig's implementation of epoll_pwait is here: https://github.com/ziglang/zig/blob/36f4f32fad3e88a84b6a10d78df31a4ed2c24465/lib/std/os/linux.zig#L1436-L1446

It passes @sizeOf(sigset_t) as the final argument of the syscall, which matches what the man page for epoll_pwait says:

C library/kernel differences The raw epoll_pwait() and epoll_pwait2() system calls have a sixth argument, size_t sigsetsize, which specifies the size in bytes of the sigmask argument. The glibc epoll_pwait() wrapper function specifies this argument as a fixed value (equal to sizeof(sigset_t)).

Also according to the man page, the only occasion that EINVAL should be returned is when epfd is not an epoll file descriptor or maxevents is less than or equal to zero. But neither of those cases are true in this example. But if you check the Linux kernel sources, you find there is one more case where EINVAL can be returned, and that is if the sigsetsize argument does not match the size of the sigset_t data structure in the kernel:

if (sigsetsize != sizeof(sigset_t))
        return -EINVAL;

The sigset_t data structure defined in the kernel is 8 bytes (for x86-64):

typedef struct {
    unsigned long sig[_NSIG_WORDS];
} sigset_t;

I also checked the glibc sources to see what they are actually doing. They use a macro __NSIG_BYTES defined as:

#define __NSIG_BYTES (__NSIG_WORDS * (ULONG_WIDTH / UCHAR_WIDTH))

which, when you resolve all of the macros, is also 8 bytes for x86-64.

The sigset_t that Zig is passing to @sizeOf is 128 bytes. This size mismatch causes the EINVAL error. It looks like Zig needs to define a separate constant for this sigsetsize argument rather than using @sizeOf(sigset_t) (as the epoll_pwait man page would have you believe).

billzez commented 1 year ago

I've hit this bug as well. Like op says, sigset_t is 128 bytes and should be 8 bytes. Also, the kernel defines NSIG as 64, but os/linux.zig has it as 65.

It appears both of these incorrect values came from the musl source code.