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_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_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 submit_open_entry(self, fpath, flags, mode, dir_fd): sqe = self._get_sqe() path = os.path.abspath(fpath).encode() liburing.io_uring_prep_openat(sqe, dir_fd, path, flags, mode) return self._submit(RequestType.OPEN, sqe, path)