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)
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
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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
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)
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