Example #1
0
def test_openat2_resolve_cache(tmpdir):
    ring = io_uring()
    cqes = io_uring_cqes()
    try:
        assert io_uring_queue_init(2, ring, 0) == 0

        how = open_how(O_CREAT, 0o600, RESOLVE_CACHED)
        lookup_path = join(tmpdir, 'lookup-file.txt').encode()

        for _ in range(2, 0, -1):  # countdown
            try:
                sqe = io_uring_get_sqe(ring)
                io_uring_prep_openat2(sqe, AT_FDCWD, lookup_path, how)
                fd = submit_wait_result(ring, cqes)
            except BlockingIOError:
                if how[0].resolve & RESOLVE_CACHED:
                    how[0].resolve = how[0].resolve & ~RESOLVE_CACHED
                # note:
                #   must retry without `RESOLVE_CACHED` since
                #   file path was not in kernel's lookup cache.
            else:
                assert fd > 0

                # close `fd`
                sqe = io_uring_get_sqe(ring)
                io_uring_prep_close(sqe, fd)
                assert submit_wait_result(ring, cqes) == 0
                break
        else:
            assert False  # failed to create file
    finally:
        io_uring_queue_exit(ring)
Example #2
0
def exists(ring, cqes, path):
    try:
        stat = statx()
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_statx(sqe, AT_FDCWD, path, 0, 0, stat)
        submit_wait_result(ring, cqes)
    except OSError:
        return False
    else:
        return True
Example #3
0
def test_file_o_direct():
    # note:
    #   - `O_DIRECT` does not work if the file path is in memory, like "/dev/shm" or "/tmp"
    #   - currently only `MAP_PRIVATE` works with `io_uring_register_buffers`, `MAP_SHARED` will be fixed soon!

    ring = io_uring()
    cqes = io_uring_cqes()

    if skip_os('5.14'):  # was a bug < 5.14
        read = mmap(-1, PAGESIZE, flags=MAP_PRIVATE)
        write = mmap(-1, PAGESIZE, flags=MAP_PRIVATE)
    else:
        read = mmap(-1, PAGESIZE)  # MAP_SHARED
        write = mmap(-1, PAGESIZE)

    write[0:11] = b'hello world'
    iov = iovec(write, read)
    try:
        assert io_uring_queue_init(2, ring, 0) == 0
        assert io_uring_register_buffers(ring, iov, len(iov)) == 0

        # open - create local testing file.
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_openat(sqe, AT_FDCWD, b'.',
                             O_TMPFILE | O_RDWR | O_DIRECT, 0o700)
        fd = submit_wait_result(ring, cqes)

        # write
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_write_fixed(sqe, fd, iov[0].iov_base, iov[0].iov_len, 0,
                                  0)
        assert submit_wait_result(ring, cqes) == PAGESIZE

        # read
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_read_fixed(sqe, fd, iov[1].iov_base, iov[1].iov_len, 0,
                                 1)
        assert submit_wait_result(ring, cqes) == PAGESIZE

        # confirm
        assert read[:20] == write[:20]

        # close
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_close(sqe, fd)
        assert submit_wait_result(ring, cqes) == 0

    finally:
        read.close()
        write.close()
        io_uring_queue_exit(ring)
Example #4
0
def test_register_fd_close():
    ring = io_uring()
    cqes = io_uring_cqes()
    write = bytearray(b'hello world')
    read = bytearray(11)
    iov = iovec(write, read)
    try:
        assert io_uring_queue_init(2, ring, 0) == 0

        # open - create local testing file.
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_openat(sqe, AT_FDCWD, b'.', O_TMPFILE | O_RDWR, 0o700)
        fd = submit_wait_result(ring, cqes)

        # register `fds`
        fds = files(fd)  # `int[fd]`
        index = 0
        assert io_uring_register_files(ring, fds, len(fds)) == 0

        # close - right away after registering fd
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_close(sqe, fd)
        assert submit_wait_result(ring, cqes) == 0

        # write - using registered file index and closed fd
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_write(sqe, index, iov[0].iov_base, iov[0].iov_len, 0)
        sqe.flags |= IOSQE_FIXED_FILE
        assert submit_wait_result(ring, cqes) == 11

        # read - using registered file index and closed fd
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_read(sqe, index, iov[1].iov_base, iov[1].iov_len, 0)
        sqe.flags |= IOSQE_FIXED_FILE
        assert submit_wait_result(ring, cqes) == 11

        # confirm
        assert write == read

        # unregister - index
        fds = files(-1)
        assert io_uring_register_files_update(ring, index, fds, 1) == 1

        # re-read - should not be able to since index was unregistered
        with raises(OSError):  # [Errno 9] Bad file descriptor
            sqe = io_uring_get_sqe(ring)
            io_uring_prep_read(sqe, index, iov[1].iov_base, iov[1].iov_len, 0)
            sqe.flags |= IOSQE_FIXED_FILE
            assert submit_wait_result(ring, cqes) == 11
    finally:
        io_uring_queue_exit(ring)
Example #5
0
def test_register_file_update():
    ring = io_uring()
    cqes = io_uring_cqes()
    write = bytearray(b'hello world')
    read = bytearray(11)
    iov = iovec(write, read)
    fds = files(-1, -1, -1, -1)  # prep 4 fd spots
    index = 2  # only use index ``2`` of the `fds`
    try:
        assert io_uring_queue_init(2, ring, 0) == 0

        # register 4 `fds`
        assert io_uring_register_files(ring, fds, len(fds)) == 0

        # open - create local testing file.
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_openat(sqe, AT_FDCWD, b'.', O_TMPFILE | O_RDWR, 0o700)
        fd = submit_wait_result(ring, cqes)
        fds[index] = fd

        fd2 = files(fd)  # `int[fd]`
        # register - update only the index ``2`` value
        assert io_uring_register_files_update(ring, index, fd2, 1) == 1

        # close
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_close(sqe, fds[index])
        assert submit_wait_result(ring, cqes) == 0

        # write - using registered file index vs fd
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_write(sqe, index, iov[0].iov_base, iov[0].iov_len, 0)
        sqe.flags |= IOSQE_FIXED_FILE
        assert submit_wait_result(ring, cqes) == 11

        # read - using registered file index vs fd
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_read(sqe, index, iov[1].iov_base, iov[1].iov_len, 0)
        sqe.flags |= IOSQE_FIXED_FILE
        assert submit_wait_result(ring, cqes) == 11

        # confirm
        assert write == read

        # unregister
        assert io_uring_unregister_files(ring) == 0
    finally:
        io_uring_queue_exit(ring)
Example #6
0
def test_statx(tmpdir):
    ring = io_uring()
    cqes = io_uring_cqes()
    file_path = join(tmpdir, 'statx_test.txt').encode()
    bad_path = join(tmpdir, 'file-that-does-not-exist').encode()

    # create sample file
    fd = open(file_path, O_CREAT, 0o700)
    close(fd)

    try:
        assert io_uring_queue_init(2, ring, 0) == 0

        stat = statx()
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_statx(sqe, AT_FDCWD, file_path, AT_STATX_FORCE_SYNC, STATX_MODE, stat)
        sqe.user_data = 1
        assert submit_wait_result(ring, cqes) == 0
        stat[0]
        assert S_IMODE(stat[0].stx_mode) == 448 == 0o700
        assert oct(S_IMODE(stat[0].stx_mode)) == oct(0o700)

        # statx version of file, dir, ... exists or not
        assert exists(ring, cqes, file_path)
        assert not exists(ring, cqes, bad_path)

    finally:
        io_uring_queue_exit(ring)
Example #7
0
def test_unlink(tmpdir):
    dir_path = join(tmpdir, 'directory').encode()
    mkdir(dir_path)  # create directory

    file_path = join(tmpdir, 'file.txt').encode()
    with open(file_path, 'x'):  # create file
        pass

    flags = 0
    ring = io_uring()
    cqes = io_uring_cqes()
    try:
        assert io_uring_queue_init(2, ring, 0) == 0

        sqes = get_sqes(ring, 2)
        io_uring_prep_unlinkat(sqes[0], AT_FDCWD, file_path, flags)
        sqes[0].flags |= IOSQE_IO_LINK
        flags |= AT_REMOVEDIR
        io_uring_prep_unlinkat(sqes[1], AT_FDCWD, dir_path, flags)

        assert submit_wait_result(ring, cqes, 2) == [0, 0]
        assert not exists(file_path)  # file should not exist
        assert not exists(dir_path)  # dir should not exist

    finally:
        io_uring_queue_exit(ring)
Example #8
0
def test_register_file():
    ring = io_uring()
    cqes = io_uring_cqes()
    write = bytearray(b'hello world')
    read = bytearray(11)
    iov = iovec(write, read)
    try:
        assert io_uring_queue_init(2, ring, 0) == 0

        # open - create local testing file.
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_openat(sqe, AT_FDCWD, b'.', O_TMPFILE | O_RDWR, 0o700)
        fd = submit_wait_result(ring, cqes)

        # register `fds`
        fds = files(fd)  # `int[fd]`
        index = 0
        assert io_uring_register_files(ring, fds, len(fds)) == 0

        # close
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_close(sqe, fd)
        assert submit_wait_result(ring, cqes) == 0

        # write - using registered file index vs fd
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_write(sqe, index, iov[0].iov_base, iov[0].iov_len, 0)
        sqe.flags |= IOSQE_FIXED_FILE
        assert submit_wait_result(ring, cqes) == 11

        # read - using registered file index vs fd
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_read(sqe, index, iov[1].iov_base, iov[1].iov_len, 0)
        sqe.flags |= IOSQE_FIXED_FILE
        assert submit_wait_result(ring, cqes) == 11

        # confirm
        assert write == read
    finally:
        # note: let exit also unregister fds
        io_uring_queue_exit(ring)
Example #9
0
def test_openat2(tmpdir):
    ring = io_uring()
    cqes = io_uring_cqes()
    # prep
    buffers = (bytearray(b'hello world'), bytearray(11))
    iov = iovec(*buffers)
    file_path = join(tmpdir, 'openat2_test.txt').encode()
    try:
        assert io_uring_queue_init(2, ring, 0) == 0
        assert io_uring_register_buffers(ring, iov, len(iov)) == 0

        # open
        how = open_how(O_CREAT | O_RDWR, 0o700)
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_openat2(sqe, AT_FDCWD, file_path, how)
        fd = submit_wait_result(ring, cqes)

        # write
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_write(sqe, fd, iov[0].iov_base, iov[0].iov_len, 0)
        assert submit_wait_result(ring, cqes) == 11

        # read
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_read(sqe, fd, iov[1].iov_base, iov[1].iov_len, 0)
        assert submit_wait_result(ring, cqes) == 11

        # confirm
        assert buffers[0] == buffers[1]

        # close
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_close(sqe, fd)
        submit_wait_result(ring, cqes)

        if skip_os('5.12'):
            skip('RESOLVE_CACHED 5.12+ Linux required')
        else:
            # second open for resolve test
            how = open_how(O_RDWR, 0, RESOLVE_CACHED)
            sqe = io_uring_get_sqe(ring)
            io_uring_prep_openat2(sqe, AT_FDCWD, file_path, how)
            fd2 = submit_wait_result(ring, cqes)

            assert fd2 > 0

            # close `fd2`
            sqe = io_uring_get_sqe(ring)
            io_uring_prep_close(sqe, fd2)
            assert submit_wait_result(ring, cqes) == 0
    finally:
        io_uring_queue_exit(ring)
Example #10
0
def test_clone_file_using_splice(tmpdir):
    fd_in = os.open(join(tmpdir, '1.txt'), os.O_RDWR | os.O_CREAT, 0o660)
    fd_out = os.open(join(tmpdir, '2.txt'), os.O_RDWR | os.O_CREAT, 0o660)
    flags = SPLICE_F_MOVE | SPLICE_F_MORE
    data = b'hello world'
    BUF_SIZE = len(data)
    os.write(fd_in, data)
    r, w = os.pipe()

    ring = io_uring()
    cqes = io_uring_cqes(2)

    try:
        # initialization
        assert io_uring_queue_init(2, ring, 0) == 0

        # read from file "1.txt"
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_splice(sqe, fd_in, 0, w, -1, BUF_SIZE, flags)
        sqe.user_data = 1

        # chain top and bottom sqe
        sqe.flags |= IOSQE_IO_LINK

        # write to file "2.txt"
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_splice(sqe, r, -1, fd_out, 0, BUF_SIZE, flags)
        sqe.user_data = 2

        assert submit_wait_result(ring, cqes, 2) == [BUF_SIZE, BUF_SIZE]
        assert os.read(fd_out, BUF_SIZE) == data

    finally:
        os.close(fd_in)
        os.close(fd_out)
        io_uring_queue_exit(ring)
Example #11
0
def test_rename_file(tmpdir):
    src_file_path = join(tmpdir, 'src_file.txt').encode()
    dst_file_path = join(tmpdir, 'dst_file.txt').encode()

    # create src file
    with open(src_file_path, 'x'):
        pass

    ring = io_uring()
    cqes = io_uring_cqes()
    try:
        assert io_uring_queue_init(2, ring, 0) == 0

        assert exists(src_file_path)
        assert not exists(dst_file_path)

        sqe = io_uring_get_sqe(ring)
        io_uring_prep_renameat(sqe, AT_FDCWD, src_file_path, AT_FDCWD, dst_file_path, 0)
        assert submit_wait_result(ring, cqes) == 0

        assert not exists(src_file_path)  # old file should not exist
        assert exists(dst_file_path)      # renamed file should exist
    finally:
        io_uring_queue_exit(ring)
Example #12
0
def test_multiple_register_fd_mix(tmpdir):
    ring = io_uring()
    cqes = io_uring_cqes()
    loop = randint(2, 5)
    data = b'hello world'
    write = [bytearray(data) for _ in range(loop)]
    iov = iovec(*write)
    fds = files(-1 for _ in range(loop))  # prep fds
    try:
        assert io_uring_queue_init(2, ring, 0) == 0
        # initialize register
        assert io_uring_register_files(ring, fds, len(fds)) == 0

        for index in range(loop):  # index = 0, 1, ...
            path = join(tmpdir, f'register-fd-mix-{index}.txt').encode()
            # open
            sqe = io_uring_get_sqe(ring)
            io_uring_prep_openat(sqe, AT_FDCWD, path, O_CREAT | O_RDWR, 0o700)
            fd = submit_wait_result(ring, cqes)

            # register update
            if index == 1:  # skip registering middle one!
                fd1 = fd
            else:
                fds = files(fd)
                assert io_uring_register_files_update(ring, index, fds, 1) == 1

                # close registered fd only
                sqe = io_uring_get_sqe(ring)
                io_uring_prep_close(sqe, fd)
                assert submit_wait_result(ring, cqes) == 0

        # write
        for index in range(loop):
            sqe = io_uring_get_sqe(ring)
            if index == 1:  # write using fd
                io_uring_prep_write(sqe, fd1, iov[index].iov_base, iov[index].iov_len, 0)
            else:  # write using registered file index
                io_uring_prep_write(sqe, index, iov[index].iov_base, iov[index].iov_len, 0)
                sqe.flags |= IOSQE_FIXED_FILE
            assert submit_wait_result(ring, cqes) == 11

        # read
        for index in range(loop):
            path = join(tmpdir, f'register-fd-mix-{index}.txt').encode()
            stat = statx()
            sqe = io_uring_get_sqe(ring)
            io_uring_prep_statx(sqe, AT_FDCWD, path, 0, 0, stat)
            assert submit_wait_result(ring, cqes) == 0
            length = stat[0].stx_size  # get written file size
            # note: even though size is known, get it using statx

            read = bytearray(length)
            buffer = from_buffer(read)

            sqe = io_uring_get_sqe(ring)
            if index == 1:
                io_uring_prep_read(sqe, fd1, buffer, length, 0)
            else:  # read using registered file index
                io_uring_prep_read(sqe, index, buffer, length, 0)
                sqe.flags |= IOSQE_FIXED_FILE
            assert submit_wait_result(ring, cqes) == 11
            assert read == data

        # close
        for index in range(loop):
            if index == 1:
                sqe = io_uring_get_sqe(ring)
                io_uring_prep_close(sqe, fd1)
                assert submit_wait_result(ring, cqes) == 0
            else:  # unregister file index
                fds = files(-1)
                assert io_uring_register_files_update(ring, index, fds, 1) == 1
    finally:
        io_uring_queue_exit(ring)
Example #13
0
def open_file(ring, cqes, file_path, cred_id):
    sqe = io_uring_get_sqe(ring)
    io_uring_prep_openat(sqe, AT_FDCWD, file_path, O_RDONLY, 0)
    if cred_id is not None:
        sqe.personality = cred_id
    return submit_wait_result(ring, cqes)  # fd
Example #14
0
def test_personality(tmpdir):
    # note: `sqe.personality` has limited use-case like its restricts to opening `fd`
    #       but not for read, write, close, ...

    ring = io_uring()
    cqes = io_uring_cqes()
    file_path = join(tmpdir, 'test_personality_root_file.txt').encode()
    try:
        assert io_uring_queue_init(2, ring, 0) == 0

        # create & open file only root can access!
        sqe = io_uring_get_sqe(ring)
        io_uring_prep_openat(sqe, AT_FDCWD, file_path, O_CREAT, 0o700)
        fd = submit_wait_result(ring, cqes)
        close_file(ring, cqes, fd)

        root_cred_id = io_uring_register_personality(ring)
        assert root_cred_id == 1

        # normal "user"
        setegid(1000)
        seteuid(1000)

        user_cred_id = io_uring_register_personality(ring)
        assert user_cred_id == 2

        # try to open root file as normal user.
        with raises(PermissionError):
            open_file(ring, cqes, file_path, None)

        # bad personality
        with raises(OSError):  # Invalid argument
            open_file(ring, cqes, file_path, 2)

        # what about default `0` for personality
        with raises(PermissionError):
            open_file(ring, cqes, file_path, 0)

        # try again to open file with root credential
        fd = open_file(ring, cqes, file_path, root_cred_id)
        close_file(ring, cqes, fd)

        # "root" again
        setegid(0)
        seteuid(0)

        # try again to open file with root credential
        fd = open_file(ring, cqes, file_path, root_cred_id)
        close_file(ring, cqes, fd)

        # try again to open file with no credential
        fd = open_file(ring, cqes, file_path, None)
        close_file(ring, cqes, fd)

        # try again to open file with "user" credential
        with raises(PermissionError):
            open_file(ring, cqes, file_path, user_cred_id)

        # "user" again
        setegid(1000)
        seteuid(1000)

        # try to unregister "root" personality
        assert io_uring_unregister_personality(ring, root_cred_id) == 0

        # try to unregister "user" personality
        assert io_uring_unregister_personality(ring, user_cred_id) == 0

        # try to open file with unregistered old "root" credential
        with raises(OSError):  # Invalid argument
            open_file(ring, cqes, file_path, root_cred_id)

    finally:
        # make sure its "root" again to allow proper cleanup
        seteuid(0)
        setegid(0)

        if exists(file_path):
            unlink(file_path)

        io_uring_queue_exit(ring)
Example #15
0
def close_file(ring, cqes, fd):
    sqe = io_uring_get_sqe(ring)
    io_uring_prep_close(sqe, fd)
    assert submit_wait_result(ring, cqes) == 0