axboe / liburing

Library providing helpers for the Linux kernel io_uring support
MIT License
2.86k stars 402 forks source link

io_uring openat non-existing file, cqe->res returns -2, outside of specification #1195

Closed Oipo closed 2 months ago

Oipo commented 2 months ago

According to the openat(2) man page, the openat command returns -1 and only -1 on error. When a non-existing file is being tried to open using the regular openat command, -1 is returned. Using io_uring openat, the cqe->res value is -2, which to my understanding is not allowed.

liburing version: 2.6 kernel version: 6.9.5

Repro file:

#include <iostream>
#include <fcntl.h>
#include <liburing.h>
#include <memory>

int main() {
    int ret = openat(AT_FDCWD, "NonExistingFile.txt", O_RDONLY, 0);
    std::cout << "normal openat() returned " << ret << std::endl;

    auto _eventQueue = std::make_unique<io_uring>();
    io_uring_params p{};
    p.flags = IORING_SETUP_DEFER_TASKRUN | IORING_SETUP_COOP_TASKRUN | IORING_SETUP_SINGLE_ISSUER;
    p.sq_thread_idle = 50;
    ret = io_uring_queue_init_params(1024, _eventQueue.get(), &p);
    if(ret < 0) {
        throw std::system_error(-ret, std::generic_category(), "io_uring_queue_init_params() failed");
    }
    ret = io_uring_register_ring_fd(_eventQueue.get());
    if(ret < 0) {
        throw std::system_error(-ret, std::generic_category(), "io_uring_register_ring_fd() failed");
    }

    auto *sqe = io_uring_get_sqe(_eventQueue.get());
    io_uring_prep_openat(sqe, AT_FDCWD, "NonExistingfile.txt", O_RDONLY, 0);
    io_uring_submit(_eventQueue.get());

    io_uring_cqe *cqe{};
    __kernel_timespec ts{};
    unsigned int head{};
    ts.tv_nsec = 1'000'000;
    bool handled{};
    while(!handled) {
        do {
            ret = io_uring_wait_cqes(_eventQueue.get(), &cqe, 1, &ts, nullptr);
            if (ret < 0 && ret != -ETIME) {
                throw std::system_error(-ret, std::generic_category(), "io_uring_wait_cqes() failed");
            }
        } while(ret == ETIME);

        io_uring_for_each_cqe(_eventQueue.get(), head, cqe) {
            if (cqe->res == -2) {
                std::cout << "io_uring openat returned -2" << std::endl;
            } else {
                std::cout << "io_uring openat returned " << cqe->res << std::endl;
            }
            handled = true;
        }
    }

    io_uring_queue_exit(_eventQueue.get());

    return 0;
}

Execution result on my PC:

$ g++ -D_GNU_SOURCE -include ../external/liburing/config-host.h -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -g -O3 -Wall -Wextra -fno-stack-protector openat_test.cpp ../external/liburing/src/liburing.a -I ../external/liburing/src/include/ && ./a.out 
normal openat() returned -1
io_uring openat returned -2

config-host.h contents:

/*
 * Automatically generated by configure - do not modify
 * Configured with: * './configure'
 */
#define CONFIG_NOLIBC
#define CONFIG_HAVE_KERNEL_RWF_T
#define CONFIG_HAVE_KERNEL_TIMESPEC
#define CONFIG_HAVE_OPEN_HOW
#define CONFIG_HAVE_STATX
#define CONFIG_HAVE_GLIBC_STATX
#define CONFIG_HAVE_CXX
#define CONFIG_HAVE_UCONTEXT
#define CONFIG_HAVE_STRINGOP_OVERFLOW
#define CONFIG_HAVE_ARRAY_BOUNDS
#define CONFIG_HAVE_NVME_URING
#define CONFIG_HAVE_FANOTIFY
#define CONFIG_HAVE_FUTEXV
ammarfaizi2 commented 2 months ago

io_uring is not wrong based on your report.

The syscall instruction itself actually returns a negative error code. However, the libc syscall wrappers, in their wisdom, return a -1 on error and stash the error code in the errno global variable (declared in <errno.h>).

On the other hand, io_uring, which doesn't use errno, correctly returns -2 (-ENOENT: No such file or directory) in your case.

It's the expected result.

You can interpret the io_uring return code using strerror (make it positive when you pass it). For example:

    if (cqe->res < 0) {
        printf("Error: (%d): %s\n", cqe->res, strerror(-cqe->res));
    }

Note the - sign in strerror(-cqe->res).

Oipo commented 2 months ago

Oh, that explains it. :facepalm:

Thanks for letting me know!