├── .github └── workflows │ └── test.yml ├── .gitmodules ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── example ├── open_write_read_close.py └── probe.py ├── pyproject.toml ├── setup.py ├── src └── liburing │ ├── __init__.pxd │ ├── __init__.py │ ├── _test.pxd │ ├── _test.pyx │ ├── common.pxd │ ├── common.pyx │ ├── error.pxd │ ├── error.pyx │ ├── file.pxd │ ├── file.pyx │ ├── futex.pxd │ ├── futex.pyx │ ├── helper.pxd │ ├── helper.pyx │ ├── include │ ├── liburing.h │ └── liburing │ │ ├── barrier.h │ │ ├── compat.h │ │ ├── io_uring.h │ │ ├── io_uring_version.h │ │ └── sanitize.h │ ├── lib │ ├── __init__.pxd │ ├── file.pxd │ ├── futex.pxd │ ├── io_uring.pxd │ ├── poll.pxd │ ├── socket.pxd │ ├── statx.pxd │ ├── type.pxd │ └── uring.pxd │ ├── os.pxd │ ├── os.pyx │ ├── other.pxd │ ├── other.pyx │ ├── poll.pxd │ ├── poll.pyx │ ├── probe.pxd │ ├── probe.py │ ├── probe.pyx │ ├── queue.pxd │ ├── queue.pyx │ ├── register.pxd │ ├── register.pyx │ ├── socket.pxd │ ├── socket.pyx │ ├── socket_extra.pxd │ ├── socket_extra.pyx │ ├── statx.pxd │ ├── statx.pyx │ ├── syscall.pxd │ ├── syscall.pyx │ ├── time.pxd │ ├── time.pyx │ ├── version.pxd │ ├── version.py │ ├── version.pyx │ ├── xattr.pxd │ └── xattr.pyx └── test ├── conftest.py ├── error_test.py ├── file ├── file_test.py ├── open_close_test.py └── open_how_test.py ├── futex_test.py ├── helper └── io_uring_put_sqe_test.py ├── iovec_test.py ├── lib ├── file_define_test.py ├── futex_define_test.py ├── io_uring_define_test.py ├── poll_define_test.py ├── socket_define_test.py ├── statx_define_test.py ├── type_define_test.py └── uring_define_test.py ├── os ├── splice_test.py └── unlink_test.py ├── probe_test.py ├── queue ├── get_cqe_test.py ├── init_exit_test.py ├── macro_test.py └── sqe_cqe_test.py ├── socket ├── cmd_sock_test.py ├── getaddinfo_test.py ├── getnameinfo_test.py ├── getsockname_test.py ├── socket_connect_test.py └── socket_extra_test.py ├── statx_test.py ├── time_test.py └── version_test.py /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Test 3 | 4 | on: [push, pull_request] # yamllint disable-line rule:truthy 5 | 6 | jobs: 7 | build: 8 | 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest] 13 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14-dev"] 14 | 15 | steps: 16 | - name: Checkout repository 17 | uses: actions/checkout@v4 18 | with: 19 | submodules: true 20 | # Setup Python 21 | - uses: actions/checkout@v4 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v5 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | # Runners 27 | - name: Install dependencies 28 | run: | 29 | python3 -m pip install --upgrade pip flake8 pytest 30 | python3 -m pip install --upgrade . 31 | 32 | # for debugging: 33 | # python3 -m pip -vvvv install --upgrade . 34 | # ls -alFR 35 | - name: Lint with flake8 36 | run: | 37 | # stop the build if there are Python syntax errors or undefined names 38 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 39 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 40 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 41 | - name: Test with PyTest 42 | run: | 43 | python3 -m pytest test 44 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libs/liburing"] 2 | path = libs/liburing 3 | url = https://github.com/axboe/liburing.git 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # C Liburing START >>> 2 | graft libs/liburing/src 3 | 4 | include libs/liburing/configure 5 | include libs/liburing/liburing-ffi.pc.in 6 | include libs/liburing/liburing.pc.in 7 | include libs/liburing/liburing.spec 8 | include libs/liburing/LICENSE 9 | include libs/liburing/Makefile 10 | include libs/liburing/Makefile.common 11 | include libs/liburing/Makefile.quiet 12 | # C Liburing END <<< 13 | 14 | graft example 15 | graft test 16 | 17 | exclude src/liburing/*.c 18 | global-exclude *.py[cod] # note: must run last to exclude properly 19 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | |test-status| |downloads| 2 | 3 | Liburing 4 | ======== 5 | 6 | Liburing is Python + Cython wrapper around `C Liburing`_, which is a helper to setup and tear-down io_uring instances. 7 | 8 | * Fast & scalable asynchronous I/O (storage, networking, ...) interface. 9 | * ``io_uring`` reduces number of syscalls overhead & context switches, thus improving speed. 10 | * ... 11 | 12 | Good(old) documentation `Lord of the io_uring`_ 13 | 14 | Check out `Shakti`_. It uses ``liburing`` and provides an easy to use Python ``async`` ``await`` Interface. 15 | 16 | 17 | Requires 18 | -------- 19 | 20 | - Linux 6.11+ 21 | - Python 3.9+ 22 | 23 | 24 | Includes (battery) 25 | ------------------ 26 | 27 | - C liburing 2.9+ 28 | 29 | 30 | Install, update & uninstall (Alpha) 31 | ----------------------------------- 32 | 33 | Use `pip`_ to install, upgrade & uninstall Python wrapper: 34 | 35 | .. code-block:: text 36 | 37 | python3 -m pip install liburing # install 38 | 39 | python3 -m pip install --upgrade liburing # upgrade 40 | 41 | python3 -m pip uninstall liburing # uninstall 42 | 43 | 44 | Install directly from GitHub: 45 | 46 | .. code-block:: text 47 | 48 | python3 -m pip install --upgrade git+https://github.com/YoSTEALTH/Liburing 49 | 50 | 51 | To find out all the class, functions and definitions: 52 | 53 | .. code-block:: python 54 | 55 | import liburing 56 | 57 | print(dir(liburing)) # to see all the importable names (this will not load all the modules) 58 | help(liburing) # to see all the help docs (this will load all the modules.) 59 | 60 | 61 | Find out which ``io_uring`` operations is supported by the kernel: 62 | 63 | .. code-block:: python 64 | 65 | # example/probe.py 66 | import liburing 67 | 68 | probe = liburing.probe() 69 | print(probe) 70 | 71 | 72 | Simple File Example 73 | ------------------- 74 | 75 | .. code-block:: python 76 | 77 | # example/open_write_read_close.py 78 | from liburing import O_CREAT, O_RDWR, AT_FDCWD, iovec, io_uring, io_uring_get_sqe, \ 79 | io_uring_prep_openat, io_uring_prep_write, io_uring_prep_read, \ 80 | io_uring_prep_close, io_uring_submit, io_uring_wait_cqe, \ 81 | io_uring_cqe_seen, io_uring_cqe, io_uring_queue_init, io_uring_queue_exit, \ 82 | io_uring_sqe_set_data64, trap_error 83 | 84 | 85 | def open(ring, cqe, path, flags, mode=0o660, dir_fd=AT_FDCWD): 86 | _path = path if isinstance(path, bytes) else str(path).encode() 87 | # if `path` is relative and `dir_fd` is `AT_FDCWD`, then `path` is relative 88 | # to current working directory. Also `_path` must be in bytes 89 | 90 | sqe = io_uring_get_sqe(ring) # sqe(submission queue entry) 91 | io_uring_prep_openat(sqe, _path, flags, mode, dir_fd) 92 | # set submit entry identifier as `1` which is returned back in `cqe.user_data` 93 | # so you can keep track of submit/completed entries. 94 | io_uring_sqe_set_data64(sqe, 1) 95 | return _submit_and_wait(ring, cqe) # returns fd 96 | 97 | 98 | def write(ring, cqe, fd, data, offset=0): 99 | iov = iovec(data) # or iovec([bytearray(data)]) 100 | sqe = io_uring_get_sqe(ring) 101 | io_uring_prep_write(sqe, fd, iov.iov_base, iov.iov_len, offset) 102 | io_uring_sqe_set_data64(sqe, 2) 103 | return _submit_and_wait(ring, cqe) # returns length(s) of bytes written 104 | 105 | 106 | def read(ring, cqe, fd, length, offset=0): 107 | iov = iovec(bytearray(length)) # or [bytearray(length)] 108 | sqe = io_uring_get_sqe(ring) 109 | io_uring_prep_read(sqe, fd, iov.iov_base, iov.iov_len, offset) 110 | io_uring_sqe_set_data64(sqe, 3) 111 | _submit_and_wait(ring, cqe) # get actual length of file read. 112 | return iov.iov_base 113 | 114 | 115 | def close(ring, cqe, fd): 116 | sqe = io_uring_get_sqe(ring) 117 | io_uring_prep_close(sqe, fd) 118 | io_uring_sqe_set_data64(sqe, 4) 119 | _submit_and_wait(ring, cqe) # no error means success! 120 | 121 | 122 | def _submit_and_wait(ring, cqe): 123 | io_uring_submit(ring) # submit entry 124 | io_uring_wait_cqe(ring, cqe) # wait for entry to finish 125 | result = trap_error(cqe.res) # auto raise appropriate exception if failed 126 | # note `cqe.res` returns results, if ``< 0`` its an error, if ``>= 0`` its the value 127 | 128 | # done with current entry so clear it from completion queue. 129 | io_uring_cqe_seen(ring, cqe) 130 | return result # type: int 131 | 132 | 133 | def main(): 134 | ring = io_uring() 135 | cqe = io_uring_cqe() # completion queue entry 136 | try: 137 | io_uring_queue_init(32, ring, 0) 138 | 139 | fd = open(ring, cqe, '/tmp/liburing-test-file.txt', O_CREAT | O_RDWR) 140 | print('fd:', fd) 141 | 142 | length = write(ring, cqe, fd, b'hello world') 143 | print('wrote:', length) 144 | 145 | content = read(ring, cqe, fd, length) 146 | print('read:', content) 147 | 148 | close(ring, cqe, fd) 149 | print('closed.') 150 | finally: 151 | io_uring_queue_exit(ring) 152 | 153 | 154 | if __name__ == '__main__': 155 | main() 156 | 157 | 158 | Note 159 | ---- 160 | - Try not to use ``from liburing import *`` this will load all the modules at once, unless that's what you want! 161 | 162 | 163 | Cython Note 164 | ----------- 165 | - You can ``cimport`` ``liburing`` directly into your project if you are planning on compiling your project as well. 166 | - There is also ``src/liburing/lib`` directory with raw ``.pxd`` header files. 167 | - All raw ``C`` wrapped function, enum, struct, defines starts with ``__``, not including anything that's ``ctypedef``. This is to prevent naming confusion between whats ``C`` and ``Cython`` side. 168 | - ``liburing`` must be included in both ``build-system.requires`` and ``project.dependencies`` in ``pyproject.toml`` to compile and use properly. 169 | - Check out `Shakti`_ to see how to include ``liburing`` using ``cython``. 170 | 171 | 172 | TODO 173 | ---- 174 | - Stable Release (currently still in alpha) 175 | - Linux 6.1 Backwards compatibility. 176 | 177 | 178 | License 179 | ------- 180 | Free, Public Domain (CC0). `Read more`_ 181 | 182 | .. _pip: https://pip.pypa.io/en/stable/getting-started/ 183 | .. _Read more: https://github.com/YoSTEALTH/Liburing/blob/master/LICENSE.txt 184 | .. _C Liburing: https://github.com/axboe/liburing 185 | .. _Lord of the io_uring: https://unixism.net/loti/ 186 | .. _Shakti: https://github.com/YoSTEALTH/Shakti 187 | .. |test-status| image:: https://github.com/YoSTEALTH/Liburing/actions/workflows/test.yml/badge.svg?branch=master 188 | :target: https://github.com/YoSTEALTH/Liburing/actions/workflows/test.yml 189 | :alt: Test status 190 | .. |downloads| image:: https://img.shields.io/pypi/dm/liburing 191 | :alt: PyPI - Downloads 192 | -------------------------------------------------------------------------------- /example/open_write_read_close.py: -------------------------------------------------------------------------------- 1 | from liburing import O_CREAT, O_RDWR, AT_FDCWD, iovec, io_uring, io_uring_get_sqe, \ 2 | io_uring_prep_openat, io_uring_prep_write, io_uring_prep_read, \ 3 | io_uring_prep_close, io_uring_submit, io_uring_wait_cqe, \ 4 | io_uring_cqe_seen, io_uring_cqe, io_uring_queue_init, io_uring_queue_exit, \ 5 | io_uring_sqe_set_data64, trap_error 6 | 7 | 8 | def open(ring, cqe, path, flags, mode=0o660, dir_fd=AT_FDCWD): 9 | _path = path if isinstance(path, bytes) else str(path).encode() 10 | # if `path` is relative and `dir_fd` is `AT_FDCWD`, then `path` is relative 11 | # to current working directory. Also `_path` must be in bytes 12 | 13 | sqe = io_uring_get_sqe(ring) # sqe(submission queue entry) 14 | io_uring_prep_openat(sqe, _path, flags, mode, dir_fd) 15 | # set submit entry identifier as `1` which is returned back in `cqe.user_data` 16 | # so you can keep track of submit/completed entries. 17 | io_uring_sqe_set_data64(sqe, 1) 18 | return _submit_and_wait(ring, cqe) # returns fd 19 | 20 | 21 | def write(ring, cqe, fd, data, offset=0): 22 | iov = iovec(data) # or iovec([bytearray(data)]) 23 | sqe = io_uring_get_sqe(ring) 24 | io_uring_prep_write(sqe, fd, iov.iov_base, iov.iov_len, offset) 25 | io_uring_sqe_set_data64(sqe, 2) 26 | return _submit_and_wait(ring, cqe) # returns length(s) of bytes written 27 | 28 | 29 | def read(ring, cqe, fd, length, offset=0): 30 | iov = iovec(bytearray(length)) # or [bytearray(length)] 31 | sqe = io_uring_get_sqe(ring) 32 | io_uring_prep_read(sqe, fd, iov.iov_base, iov.iov_len, offset) 33 | io_uring_sqe_set_data64(sqe, 3) 34 | _submit_and_wait(ring, cqe) # get actual length of file read. 35 | return iov.iov_base 36 | 37 | 38 | def close(ring, cqe, fd): 39 | sqe = io_uring_get_sqe(ring) 40 | io_uring_prep_close(sqe, fd) 41 | io_uring_sqe_set_data64(sqe, 4) 42 | _submit_and_wait(ring, cqe) # no error means success! 43 | 44 | 45 | def _submit_and_wait(ring, cqe): 46 | io_uring_submit(ring) # submit entry 47 | io_uring_wait_cqe(ring, cqe) # wait for entry to finish 48 | result = trap_error(cqe.res) # auto raise appropriate exception if failed 49 | # note `cqe.res` returns results, if ``< 0`` its an error, if ``>= 0`` its the value 50 | 51 | # done with current entry so clear it from completion queue. 52 | io_uring_cqe_seen(ring, cqe) 53 | return result # type: int 54 | 55 | 56 | def main(): 57 | ring = io_uring() 58 | cqe = io_uring_cqe() # completion queue entry 59 | try: 60 | io_uring_queue_init(32, ring, 0) 61 | 62 | fd = open(ring, cqe, '/tmp/liburing-test-file.txt', O_CREAT | O_RDWR) 63 | print('fd:', fd) 64 | 65 | length = write(ring, cqe, fd, b'hello world') 66 | print('wrote:', length) 67 | 68 | content = read(ring, cqe, fd, length) 69 | print('read:', content) 70 | 71 | close(ring, cqe, fd) 72 | print('closed.') 73 | finally: 74 | io_uring_queue_exit(ring) 75 | 76 | 77 | if __name__ == '__main__': 78 | main() 79 | -------------------------------------------------------------------------------- /example/probe.py: -------------------------------------------------------------------------------- 1 | import liburing 2 | 3 | probe = liburing.probe() 4 | print(probe) 5 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "setuptools.build_meta" 3 | requires = ["setuptools>=60", "wheel", "cython>=3"] 4 | 5 | [project] 6 | name = "liburing" 7 | dynamic = ["version"] 8 | authors = [{name="Ritesh"}] 9 | readme = {file="README.rst", content-type="text/x-rst"} 10 | license = {file="LICENSE.txt", content-type="text"} 11 | requires-python = ">=3.9" 12 | dependencies = ["dynamic-import"] 13 | description = "Liburing is Python + Cython wrapper around C Liburing, which is a helper to setup and tear-down io_uring instances." 14 | classifiers = ["Topic :: Software Development", 15 | "License :: Public Domain", 16 | "Intended Audience :: Developers", 17 | "Operating System :: POSIX :: Linux", 18 | "Programming Language :: Python :: 3.9", 19 | "Programming Language :: Python :: 3.10", 20 | "Programming Language :: Python :: 3.11", 21 | "Programming Language :: Python :: 3.12", 22 | "Programming Language :: Python :: 3.13", 23 | "Development Status :: 3 - Alpha"] 24 | # 1 - Planning 25 | # 2 - Pre-Alpha 26 | # 3 - Alpha 27 | # 4 - Beta 28 | # 5 - Production/Stable 29 | # 6 - Mature 30 | # 7 - Inactive 31 | 32 | [project.urls] 33 | Homepage = "https://github.com/YoSTEALTH/Liburing" 34 | Issues = "https://github.com/YoSTEALTH/Liburing/issues" 35 | 36 | [project.optional-dependencies] 37 | test = ["pytest"] 38 | 39 | [tool.setuptools.packages.find] 40 | where = ["src"] 41 | 42 | [tool.setuptools.dynamic] 43 | version = {attr="liburing.__version__"} 44 | 45 | [tool.setuptools.package-data] 46 | "*" = ["*.pyx", "*.pxd", "*.h"] 47 | 48 | # for debugging locally START >>> 49 | # [tool.pytest.ini_options] 50 | # pythonpath = ["src"] 51 | 52 | # [tool.coverage.run] 53 | # plugins = ["Cython.Coverage"] 54 | 55 | # [tool.coverage.report] 56 | # exclude_also = [ 57 | # "raise NotImplementedError", 58 | # "memory_error\\(self\\)", 59 | # "index_error\\(self, index, '.*'\\)" 60 | # ] 61 | 62 | [tool.cython-lint] 63 | max-line-length = 100 64 | ignore = ['E221', 'E222'] 65 | # for debugging locally END <<< 66 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from os import cpu_count 2 | from shutil import copy2, copytree 3 | from os.path import join 4 | from tempfile import TemporaryDirectory 5 | from subprocess import run as sub_process_run 6 | from setuptools import setup 7 | from setuptools.command.build_ext import build_ext 8 | from Cython.Build import cythonize 9 | from Cython.Compiler import Options 10 | from Cython.Distutils import Extension 11 | 12 | 13 | class BuildExt(build_ext): 14 | 15 | def initialize_options(self): 16 | super().initialize_options() 17 | self.parallel = threads 18 | 19 | def build_extensions(self): 20 | copytree('libs/liburing/src', libsrc) 21 | for src in ('libs/liburing/configure', 22 | 'libs/liburing/liburing-ffi.pc.in', 23 | 'libs/liburing/liburing.pc.in', 24 | 'libs/liburing/liburing.spec', 25 | 'libs/liburing/Makefile', 26 | 'libs/liburing/Makefile.common', 27 | 'libs/liburing/Makefile.quiet'): 28 | copy2(src, join(tmpdir, src)) 29 | 30 | # note: just runs `configure` & `make`, does not `install`. 31 | sub_process_run(['./configure'], cwd=lib, capture_output=True, check=True) 32 | sub_process_run(['make', f'--jobs={threads}'], cwd=lib, capture_output=True) 33 | 34 | # replace `include` placeholder files with actual content. 35 | copytree(libinc, 'src/liburing/include', dirs_exist_ok=True) 36 | # have to replace `include` in `build_lib` e.g.'build/lib.linux-x86_64-cpython-313' 37 | # as well since installer copies over the files before `BuildExt` is called. 38 | copytree(libinc, join(self.build_lib, 'liburing/include'), dirs_exist_ok=True) 39 | super().build_extensions() 40 | 41 | 42 | if __name__ == '__main__': 43 | # compiler options 44 | Options.annotate = False 45 | Options.fast_fail = True 46 | Options.docstrings = True 47 | Options.warning_errors = False 48 | 49 | with TemporaryDirectory() as tmpdir: 50 | lib = join(tmpdir, 'libs/liburing') 51 | libsrc = join(lib, 'src') 52 | libinc = join(libsrc, 'include') 53 | threads = cpu_count() 54 | extension = [Extension(name='liburing.*', # where the `.so` will be saved. 55 | sources=['src/liburing/*.pyx'], 56 | language='c', 57 | libraries=['uring-ffi'], 58 | library_dirs=[libsrc], 59 | include_dirs=[libinc], 60 | # optimize & remove debug symbols + data. 61 | extra_compile_args=['-O3', '-g0'])] 62 | # install 63 | setup(cmdclass={'build_ext': BuildExt}, 64 | ext_modules=cythonize(extension, 65 | nthreads=threads, 66 | compiler_directives={'language_level': 3, 67 | 'embedsignature': True, # show `__doc__` 68 | 'boundscheck': False, 69 | 'wraparound': False})) 70 | -------------------------------------------------------------------------------- /src/liburing/__init__.pxd: -------------------------------------------------------------------------------- 1 | # empty `__init__.pxd` file is needed for `cimport` to work 2 | -------------------------------------------------------------------------------- /src/liburing/__init__.py: -------------------------------------------------------------------------------- 1 | from dynamic_import import importer 2 | 3 | 4 | __version__ = '2025.2.19' 5 | 6 | 7 | importer(exclude_dir=['lib', 'include']) 8 | # - `importer()` helps this project manage all import needs. It auto scans for 9 | # `*.so` files and caches import names for dynamic loading `*.so` files as needed. 10 | # - `importer()` also makes all import names accessible at top level, regardless of 11 | # where `*.so` files are located. 12 | # - This helps managing the project much easy and moving files/function around 13 | # doesn't break the project. 14 | -------------------------------------------------------------------------------- /src/liburing/_test.pxd: -------------------------------------------------------------------------------- 1 | from .lib.uring cimport * 2 | from .queue cimport * 3 | -------------------------------------------------------------------------------- /src/liburing/_test.pyx: -------------------------------------------------------------------------------- 1 | cpdef bint _io_uring_cqe_shift(io_uring ring): 2 | ''' 3 | Note 4 | - This function is used only to test macro functions. 5 | ''' 6 | return __io_uring_cqe_shift(&ring.ptr) 7 | 8 | cpdef int _io_uring_cqe_index(io_uring ring, unsigned int ptr, unsigned int mask): 9 | ''' 10 | Note 11 | - This function is used only to test macro functions. 12 | ''' 13 | return __io_uring_cqe_index(&ring.ptr, ptr, mask) 14 | 15 | 16 | 17 | def test_cqe_get_index(): 18 | cdef: 19 | io_uring ring = io_uring() 20 | io_uring_cqe cqe = io_uring_cqe() 21 | 22 | io_uring_queue_init(2, ring) 23 | try: 24 | assert (0, 0) == cqe.get_index(0) 25 | assert (0, 0) == cqe.get_index(1) 26 | 27 | sqe = io_uring_get_sqe(ring) 28 | io_uring_prep_nop(sqe) 29 | sqe.user_data = 1 30 | 31 | sqe = io_uring_get_sqe(ring) 32 | io_uring_prep_nop(sqe) 33 | sqe.user_data = 2 34 | 35 | assert io_uring_submit_and_wait_timeout(ring, cqe, 2) == 2 36 | 37 | assert (0, 1) == cqe.get_index(0) 38 | assert (0, 2) == cqe.get_index(1) 39 | assert (0, 0) == cqe.get_index(2) # error/no cqe 40 | finally: 41 | io_uring_queue_exit(ring) 42 | -------------------------------------------------------------------------------- /src/liburing/common.pxd: -------------------------------------------------------------------------------- 1 | from posix.unistd cimport _SC_IOV_MAX 2 | from cpython.mem cimport PyMem_RawCalloc, PyMem_RawFree 3 | from .lib.uring cimport * 4 | from .error cimport memory_error, index_error 5 | from .queue cimport io_uring_sqe 6 | 7 | 8 | cdef class iovec: 9 | cdef: 10 | __iovec *ptr 11 | list ref # TODO: replace this with array() ? 12 | unsigned int len 13 | 14 | 15 | cpdef void io_uring_prep_close(io_uring_sqe sqe, int fd) noexcept nogil 16 | cpdef void io_uring_prep_close_direct(io_uring_sqe sqe, unsigned int file_index) noexcept nogil 17 | 18 | cpdef void io_uring_prep_provide_buffers(io_uring_sqe sqe, 19 | unsigned char[:] addr, 20 | int len, 21 | int nr, 22 | int bgid, 23 | int bid=?) noexcept nogil 24 | cpdef void io_uring_prep_remove_buffers(io_uring_sqe sqe, int nr, int bgid) noexcept nogil 25 | 26 | 27 | cpdef enum io_uring_msg_ring_flags: 28 | IORING_MSG_DATA = __IORING_MSG_DATA 29 | IORING_MSG_SEND_FD = __IORING_MSG_SEND_FD 30 | 31 | 32 | cpdef enum io_wq_type: 33 | IO_WQ_BOUND = __IO_WQ_BOUND 34 | IO_WQ_UNBOUND = __IO_WQ_UNBOUND 35 | 36 | cpdef enum io_uring_register_pbuf_ring_flags: 37 | IOU_PBUF_RING_MMAP = __IOU_PBUF_RING_MMAP 38 | IOU_PBUF_RING_INC = __IOU_PBUF_RING_INC 39 | 40 | cpdef enum io_uring_restriction_op: 41 | IORING_RESTRICTION_REGISTER_OP = __IORING_RESTRICTION_REGISTER_OP 42 | IORING_RESTRICTION_SQE_OP = __IORING_RESTRICTION_SQE_OP 43 | IORING_RESTRICTION_SQE_FLAGS_ALLOWED = __IORING_RESTRICTION_SQE_FLAGS_ALLOWED 44 | IORING_RESTRICTION_SQE_FLAGS_REQUIRED = __IORING_RESTRICTION_SQE_FLAGS_REQUIRED 45 | IORING_RESTRICTION_LAST = __IORING_RESTRICTION_LAST 46 | 47 | cpdef enum __common_define__: 48 | IORING_CQE_BUFFER_SHIFT = __IORING_CQE_BUFFER_SHIFT 49 | 50 | # openat, openat2, accept, ... 51 | IORING_FILE_INDEX_ALLOC = __IORING_FILE_INDEX_ALLOC 52 | 53 | # TODO: need to move bellow content to other files 54 | 55 | SC_IOV_MAX = _SC_IOV_MAX 56 | 57 | AT_FDCWD = __AT_FDCWD 58 | AT_SYMLINK_FOLLOW = __AT_SYMLINK_FOLLOW 59 | AT_SYMLINK_NOFOLLOW = __AT_SYMLINK_NOFOLLOW 60 | AT_REMOVEDIR = __AT_REMOVEDIR 61 | AT_NO_AUTOMOUNT = __AT_NO_AUTOMOUNT 62 | AT_EMPTY_PATH = __AT_EMPTY_PATH 63 | AT_RECURSIVE = __AT_RECURSIVE 64 | 65 | # fsync flags 66 | IORING_FSYNC_DATASYNC = __IORING_FSYNC_DATASYNC 67 | 68 | IORING_URING_CMD_FIXED = __IORING_URING_CMD_FIXED 69 | IORING_URING_CMD_MASK = __IORING_URING_CMD_MASK 70 | 71 | IORING_POLL_ADD_MULTI = __IORING_POLL_ADD_MULTI 72 | IORING_POLL_UPDATE_EVENTS = __IORING_POLL_UPDATE_EVENTS 73 | IORING_POLL_UPDATE_USER_DATA = __IORING_POLL_UPDATE_USER_DATA 74 | IORING_POLL_ADD_LEVEL = __IORING_POLL_ADD_LEVEL 75 | 76 | IORING_ASYNC_CANCEL_ALL = __IORING_ASYNC_CANCEL_ALL 77 | IORING_ASYNC_CANCEL_FD = __IORING_ASYNC_CANCEL_FD 78 | IORING_ASYNC_CANCEL_ANY = __IORING_ASYNC_CANCEL_ANY 79 | IORING_ASYNC_CANCEL_FD_FIXED = __IORING_ASYNC_CANCEL_FD_FIXED 80 | IORING_ASYNC_CANCEL_USERDATA = __IORING_ASYNC_CANCEL_USERDATA 81 | IORING_ASYNC_CANCEL_OP = __IORING_ASYNC_CANCEL_OP 82 | 83 | IORING_RECVSEND_POLL_FIRST = __IORING_RECVSEND_POLL_FIRST 84 | IORING_RECV_MULTISHOT = __IORING_RECV_MULTISHOT 85 | IORING_RECVSEND_FIXED_BUF = __IORING_RECVSEND_FIXED_BUF 86 | IORING_SEND_ZC_REPORT_USAGE = __IORING_SEND_ZC_REPORT_USAGE 87 | IORING_RECVSEND_BUNDLE = __IORING_RECVSEND_BUNDLE 88 | 89 | IORING_NOTIF_USAGE_ZC_COPIED = __IORING_NOTIF_USAGE_ZC_COPIED 90 | 91 | IORING_ACCEPT_MULTISHOT = __IORING_ACCEPT_MULTISHOT 92 | IORING_ACCEPT_DONTWAIT = __IORING_ACCEPT_DONTWAIT 93 | IORING_ACCEPT_POLL_FIRST = __IORING_ACCEPT_POLL_FIRST 94 | 95 | IORING_MSG_RING_CQE_SKIP = __IORING_MSG_RING_CQE_SKIP 96 | IORING_MSG_RING_FLAGS_PASS = __IORING_MSG_RING_FLAGS_PASS 97 | IORING_FIXED_FD_NO_CLOEXEC = __IORING_FIXED_FD_NO_CLOEXEC 98 | IORING_NOP_INJECT_RESULT = __IORING_NOP_INJECT_RESULT 99 | 100 | IORING_CQE_F_BUFFER = __IORING_CQE_F_BUFFER 101 | IORING_CQE_F_MORE = __IORING_CQE_F_MORE 102 | IORING_CQE_F_SOCK_NONEMPTY = __IORING_CQE_F_SOCK_NONEMPTY 103 | IORING_CQE_F_NOTIF = __IORING_CQE_F_NOTIF 104 | IORING_CQE_F_BUF_MORE = __IORING_CQE_F_BUF_MORE 105 | 106 | IORING_OFF_SQ_RING = __IORING_OFF_SQ_RING 107 | IORING_OFF_CQ_RING = __IORING_OFF_CQ_RING 108 | IORING_OFF_SQES = __IORING_OFF_SQES 109 | IORING_OFF_PBUF_RING = __IORING_OFF_PBUF_RING 110 | IORING_OFF_PBUF_SHIFT = __IORING_OFF_PBUF_SHIFT 111 | IORING_OFF_MMAP_MASK = __IORING_OFF_MMAP_MASK 112 | 113 | IORING_SQ_NEED_WAKEUP = __IORING_SQ_NEED_WAKEUP 114 | IORING_SQ_CQ_OVERFLOW = __IORING_SQ_CQ_OVERFLOW 115 | IORING_SQ_TASKRUN = __IORING_SQ_TASKRUN 116 | 117 | IORING_CQ_EVENTFD_DISABLED = __IORING_CQ_EVENTFD_DISABLED 118 | 119 | IORING_FEAT_SINGLE_MMAP = __IORING_FEAT_SINGLE_MMAP 120 | IORING_FEAT_NODROP = __IORING_FEAT_NODROP 121 | IORING_FEAT_SUBMIT_STABLE = __IORING_FEAT_SUBMIT_STABLE 122 | IORING_FEAT_RW_CUR_POS = __IORING_FEAT_RW_CUR_POS 123 | IORING_FEAT_CUR_PERSONALITY = __IORING_FEAT_CUR_PERSONALITY 124 | IORING_FEAT_FAST_POLL = __IORING_FEAT_FAST_POLL 125 | IORING_FEAT_POLL_32BITS = __IORING_FEAT_POLL_32BITS 126 | IORING_FEAT_SQPOLL_NONFIXED = __IORING_FEAT_SQPOLL_NONFIXED 127 | IORING_FEAT_EXT_ARG = __IORING_FEAT_EXT_ARG 128 | IORING_FEAT_NATIVE_WORKERS = __IORING_FEAT_NATIVE_WORKERS 129 | IORING_FEAT_RSRC_TAGS = __IORING_FEAT_RSRC_TAGS 130 | IORING_FEAT_CQE_SKIP = __IORING_FEAT_CQE_SKIP 131 | IORING_FEAT_LINKED_FILE = __IORING_FEAT_LINKED_FILE 132 | IORING_FEAT_REG_REG_RING = __IORING_FEAT_REG_REG_RING 133 | IORING_FEAT_RECVSEND_BUNDLE = __IORING_FEAT_RECVSEND_BUNDLE 134 | IORING_FEAT_MIN_TIMEOUT = __IORING_FEAT_MIN_TIMEOUT 135 | 136 | # initialise with user provided memory pointed by user_addr 137 | IORING_MEM_REGION_TYPE_USER = __IORING_MEM_REGION_TYPE_USER 138 | # expose the region as registered wait arguments 139 | IORING_MEM_REGION_REG_WAIT_ARG = __IORING_MEM_REGION_REG_WAIT_ARG 140 | 141 | IORING_REGISTER_SRC_REGISTERED = __IORING_REGISTER_SRC_REGISTERED 142 | IORING_REGISTER_DST_REPLACE = __IORING_REGISTER_DST_REPLACE 143 | 144 | IORING_RSRC_REGISTER_SPARSE = __IORING_RSRC_REGISTER_SPARSE 145 | 146 | IORING_REGISTER_FILES_SKIP = __IORING_REGISTER_FILES_SKIP 147 | IO_URING_OP_SUPPORTED = __IO_URING_OP_SUPPORTED 148 | 149 | IORING_REG_WAIT_TS = __IORING_REG_WAIT_TS 150 | -------------------------------------------------------------------------------- /src/liburing/common.pyx: -------------------------------------------------------------------------------- 1 | from cython cimport boundscheck # don't move 2 | from collections.abc import Iterable 3 | 4 | 5 | cdef class iovec: 6 | ''' Vector I/O data structure ''' 7 | def __cinit__(self, object buffers): 8 | ''' 9 | Type 10 | buffers: Union[bytes, bytearray, memoryview, List[...], Tuple[...]] 11 | return: None 12 | 13 | Example 14 | # read single 15 | # ----------- 16 | >>> iov_read = iovec(bytearray(11)) 17 | >>> io_uring_prep_readv(sqe, fd, iov_read, ...) 18 | 19 | # read multiple 20 | # ------------- 21 | >>> iov_read = iovec([bytearray(1), bytearray(2), bytearray(3)]) 22 | >>> io_uring_prep_readv(sqe, fd, iov_read, ...) 23 | 24 | # write single 25 | # ------------ 26 | >>> iov_write = iovec(b'hello world') 27 | >>> io_uring_prep_readv(sqe, fd, iov_write, ...) 28 | 29 | # write multiple 30 | # -------------- 31 | >>> iov_write = iovec([b'1', b'22', b'333']) 32 | >>> io_uring_prep_readv(sqe, fd, iov_write, ...) 33 | 34 | Note 35 | - Make sure to hold on to variable you are passing into `iovec` so it does not get 36 | garbage collected before you get the chance to use it! 37 | ''' 38 | cdef: 39 | str error 40 | unsigned int index, length 41 | const unsigned char[:] buffer 42 | 43 | if buffers: 44 | self.ref = [] # reference holder 45 | if isinstance(buffers, (bytes, bytearray, memoryview)): 46 | self.ref.append(buffers) 47 | elif isinstance(buffers, Iterable): 48 | self.ref.extend(buffers) 49 | else: 50 | raise TypeError(f'`{self.__class__.__name__}(buffers)` type not supported!') 51 | 52 | self.len = len(self.ref) 53 | 54 | if self.len > SC_IOV_MAX: 55 | error = f'`{self.__class__.__name__}()` - `buffers` length of {self.len!r} ' \ 56 | f'exceeds `SC_IOV_MAX` limit set by OS of {SC_IOV_MAX!r}' 57 | raise OverflowError(error) 58 | 59 | self.ptr = <__iovec*>PyMem_RawCalloc(self.len, sizeof(__iovec)) 60 | if self.ptr is NULL: 61 | memory_error(self) 62 | 63 | for index in range(self.len): 64 | buffer = self.ref[index] 65 | if not (length := len(self.ref[index])): 66 | raise ValueError(f'`{self.__class__.__name__}()` can not be length of `0`') 67 | self.ptr[index].iov_base = &buffer[0] # starting address 68 | self.ptr[index].iov_len = length 69 | 70 | def __dealloc__(self): 71 | if self.ptr is not NULL: 72 | PyMem_RawFree(self.ptr) 73 | self.ptr = NULL 74 | 75 | def __bool__(self): 76 | return self.ptr is not NULL 77 | 78 | def __len__(self): 79 | return self.len 80 | 81 | def __getitem__(self, unsigned int index): 82 | cdef iovec iov 83 | if index == 0: 84 | return self 85 | elif self.len and index < self.len: 86 | iov = iovec() 87 | iov.ptr = &self.ptr[index] 88 | if iov.ptr.iov_len: 89 | return iov 90 | index_error(self, index) 91 | 92 | @property 93 | def iov_base(self): 94 | return self.ref[0] 95 | 96 | @property 97 | def iov_len(self): 98 | return self.ptr.iov_len 99 | 100 | 101 | cpdef inline void io_uring_prep_close(io_uring_sqe sqe, int fd) noexcept nogil: 102 | __io_uring_prep_close(sqe.ptr, fd) 103 | 104 | cpdef inline void io_uring_prep_close_direct(io_uring_sqe sqe, 105 | unsigned int file_index) noexcept nogil: 106 | __io_uring_prep_close_direct(sqe.ptr, file_index) 107 | 108 | 109 | cpdef inline void io_uring_prep_provide_buffers(io_uring_sqe sqe, 110 | unsigned char[:] addr, # void *addr, 111 | int len, 112 | int nr, 113 | int bgid, 114 | int bid=0) noexcept nogil: 115 | __io_uring_prep_provide_buffers(sqe.ptr, &addr[0], len, nr, bgid, bid) 116 | 117 | cpdef inline void io_uring_prep_remove_buffers(io_uring_sqe sqe, int nr, int bgid) noexcept nogil: 118 | __io_uring_prep_remove_buffers(sqe.ptr, nr, bgid) 119 | -------------------------------------------------------------------------------- /src/liburing/error.pxd: -------------------------------------------------------------------------------- 1 | from libc.errno cimport errno 2 | from libc.string cimport strerror 3 | 4 | 5 | cpdef int trap_error(int no, str msg=?) except -1 nogil 6 | cdef void raise_error(int no=?, str msg=?) 7 | cpdef void memory_error(object cls, str msg=?) 8 | cpdef void index_error(object cls, unsigned int, str msg=?) 9 | -------------------------------------------------------------------------------- /src/liburing/error.pyx: -------------------------------------------------------------------------------- 1 | cpdef inline int trap_error(int no, str msg='') except -1 nogil: 2 | ''' Trap Error 3 | 4 | Type 5 | no int 6 | msg str 7 | return int 8 | 9 | Example 10 | >>> trap_error(1) 11 | 1 12 | 13 | >>> trap_error(-11) 14 | BlockingIOError: [Errno 11] Resource temporarily unavailable 15 | 16 | >>> trap_error(-11, 'some message') 17 | BlockingIOError: [Errno 11] some message 18 | 19 | >>> trap_error(-1) 20 | # dynamic error based on `errno` 21 | 22 | Note 23 | - any `no >= 0` is considered safe. 24 | - if `no=-1` it will check with `errno` first to see if proper error is set, 25 | and raise that as exception 26 | 27 | Special 28 | Thanks to "_habnabit" for coming up with this function name 29 | ''' 30 | if no >= 0: 31 | return no 32 | 33 | with gil: 34 | raise_error(no, msg) 35 | 36 | 37 | cdef inline void raise_error(int no=-1, str msg='') except *: 38 | ''' This function will only raise Error ''' 39 | no = -errno or no 40 | raise OSError(-no, msg or strerror(-no).decode()) 41 | 42 | 43 | cpdef inline void memory_error(object self, str msg='is out of memory!') except *: 44 | ''' Raises MemoryError ''' 45 | raise MemoryError(f'`{self.__class__.__name__}()` {msg}') 46 | 47 | 48 | cpdef inline void index_error(object self, unsigned int index, str msg='out of range!') except *: 49 | ''' Raises IndexError ''' 50 | raise IndexError(f'`{self.__class__.__name__}()[{index}]` {msg}') 51 | -------------------------------------------------------------------------------- /src/liburing/file.pxd: -------------------------------------------------------------------------------- 1 | from cpython.mem cimport PyMem_RawCalloc, PyMem_RawFree 2 | from cpython.array cimport array 3 | from .lib.uring cimport * 4 | from .error cimport trap_error, memory_error 5 | from .common cimport iovec 6 | from .queue cimport io_uring, io_uring_sqe 7 | 8 | 9 | cdef class open_how: 10 | cdef __open_how *ptr 11 | 12 | 13 | cpdef void io_uring_prep_tee(io_uring_sqe sqe, 14 | int fd_in, 15 | int fd_out, 16 | unsigned int nbytes, 17 | unsigned int splice_flags) noexcept nogil 18 | cpdef void io_uring_prep_readv(io_uring_sqe sqe, 19 | int fd, 20 | iovec iovecs, 21 | __u64 offset=?) noexcept nogil 22 | cpdef void io_uring_prep_readv2(io_uring_sqe sqe, 23 | int fd, 24 | iovec iovecs, 25 | __u64 offset=?, 26 | int flags=?) noexcept nogil 27 | cpdef void io_uring_prep_read_fixed(io_uring_sqe sqe, 28 | int fd, 29 | char *buf, 30 | unsigned int nbytes, 31 | __u64 offset, 32 | int buf_index) noexcept nogil 33 | cpdef void io_uring_prep_writev(io_uring_sqe sqe, 34 | int fd, 35 | iovec iovecs, 36 | unsigned int nr_vecs, 37 | __u64 offset) noexcept nogil 38 | cpdef void io_uring_prep_writev2(io_uring_sqe sqe, 39 | int fd, 40 | iovec iovecs, 41 | unsigned int nr_vecs, 42 | __u64 offset, 43 | int flags) noexcept nogil 44 | cpdef void io_uring_prep_write_fixed(io_uring_sqe sqe, 45 | int fd, 46 | char *buf, 47 | unsigned int nbytes, 48 | __u64 offset, 49 | int buf_index) noexcept nogil 50 | cpdef void io_uring_prep_fsync(io_uring_sqe sqe, 51 | int fd, 52 | unsigned int fsync_flags=?) noexcept nogil 53 | cpdef void io_uring_prep_sync_file_range(io_uring_sqe sqe, 54 | int fd, 55 | unsigned int len=?, 56 | __u64 offset=?, 57 | int flags=?) noexcept nogil 58 | cpdef void io_uring_prep_openat(io_uring_sqe sqe, 59 | const char *path, 60 | int flags=?, 61 | mode_t mode=?, 62 | int dfd=?) noexcept nogil 63 | cpdef void io_uring_prep_openat2(io_uring_sqe sqe, 64 | const char *path, 65 | open_how how, 66 | int dfd=?) noexcept nogil 67 | cpdef void io_uring_prep_openat_direct(io_uring_sqe sqe, 68 | const char *path, 69 | int flags=?, 70 | unsigned int file_index=?, 71 | mode_t mode=?, 72 | int dfd=?) noexcept nogil 73 | cpdef void io_uring_prep_openat2_direct(io_uring_sqe sqe, 74 | const char *path, 75 | open_how how, 76 | unsigned int file_index=?, 77 | int dfd=?) noexcept nogil 78 | cpdef void io_uring_prep_read(io_uring_sqe sqe, 79 | int fd, 80 | unsigned char[:] buf, # `void *buf` 81 | unsigned int nbytes, 82 | __u64 offset=?) noexcept nogil 83 | cpdef void io_uring_prep_read_multishot(io_uring_sqe sqe, 84 | int fd, 85 | unsigned int nbytes, 86 | __u64 offset, 87 | int buf_group) noexcept nogil 88 | cpdef void io_uring_prep_write(io_uring_sqe sqe, 89 | int fd, 90 | const unsigned char[:] buf, # `const void * buf` 91 | unsigned int nbytes, 92 | __u64 offset) noexcept nogil 93 | 94 | cpdef void io_uring_prep_files_update(io_uring_sqe sqe, list[int] fds, int offset=?) 95 | 96 | cpdef void io_uring_prep_ftruncate(io_uring_sqe sqe, int fd, loff_t len) noexcept nogil 97 | 98 | 99 | cpdef enum __file_define__: 100 | RESOLVE_NO_XDEV = __RESOLVE_NO_XDEV 101 | RESOLVE_NO_MAGICLINKS = __RESOLVE_NO_MAGICLINKS 102 | RESOLVE_NO_SYMLINKS = __RESOLVE_NO_SYMLINKS 103 | RESOLVE_BENEATH = __RESOLVE_BENEATH 104 | RESOLVE_IN_ROOT = __RESOLVE_IN_ROOT 105 | RESOLVE_CACHED = __RESOLVE_CACHED 106 | 107 | SYNC_FILE_RANGE_WAIT_BEFORE = __SYNC_FILE_RANGE_WAIT_BEFORE 108 | SYNC_FILE_RANGE_WRITE = __SYNC_FILE_RANGE_WRITE 109 | SYNC_FILE_RANGE_WAIT_AFTER = __SYNC_FILE_RANGE_WAIT_AFTER 110 | 111 | O_ACCMODE = __O_ACCMODE 112 | O_RDONLY = __O_RDONLY 113 | O_WRONLY = __O_WRONLY 114 | O_RDWR = __O_RDWR 115 | 116 | O_APPEND = __O_APPEND 117 | O_ASYNC = __O_ASYNC 118 | O_CLOEXEC = __O_CLOEXEC 119 | O_CREAT = __O_CREAT 120 | 121 | O_DIRECT = __O_DIRECT 122 | O_DIRECTORY = __O_DIRECTORY 123 | O_DSYNC = __O_DSYNC 124 | O_EXCL = __O_EXCL 125 | O_LARGEFILE = __O_LARGEFILE 126 | O_NOATIME = __O_NOATIME 127 | O_NOCTTY = __O_NOCTTY 128 | O_NOFOLLOW = __O_NOFOLLOW 129 | O_NONBLOCK = __O_NONBLOCK 130 | O_PATH = __O_PATH 131 | 132 | O_SYNC = __O_SYNC 133 | O_TMPFILE = __O_TMPFILE 134 | O_TRUNC = __O_TRUNC 135 | -------------------------------------------------------------------------------- /src/liburing/futex.pxd: -------------------------------------------------------------------------------- 1 | from posix.mman cimport PROT_READ, PROT_WRITE, MAP_ANONYMOUS, MAP_PRIVATE, MAP_SHARED, MAP_FAILED, \ 2 | mmap, munmap 3 | from cpython.mem cimport PyMem_RawMalloc, PyMem_RawCalloc, PyMem_RawFree 4 | from cpython.array cimport array 5 | from .lib.uring cimport * 6 | from .error cimport memory_error 7 | from .queue cimport io_uring_sqe 8 | 9 | 10 | cdef class futex_state: 11 | cdef: 12 | uint32_t *ptr 13 | readonly bint private 14 | __u8 len 15 | 16 | cdef class futex_waitv: 17 | cdef: 18 | __futex_waitv *ptr 19 | bint private 20 | __u8 len 21 | 22 | cpdef void io_uring_prep_futex_wake(io_uring_sqe sqe, 23 | futex_state futex, 24 | uint64_t val, 25 | uint64_t mask, 26 | uint32_t futex_flags) noexcept nogil 27 | cpdef void io_uring_prep_futex_wait(io_uring_sqe sqe, 28 | futex_state futex, 29 | uint64_t val, 30 | uint64_t mask, 31 | uint32_t futex_flags) noexcept nogil 32 | cpdef void io_uring_prep_futex_waitv(io_uring_sqe sqe, futex_waitv waiters) noexcept nogil 33 | 34 | 35 | cpdef enum __futex_define__: 36 | FUTEX_WAIT = __FUTEX_WAIT 37 | FUTEX_WAKE = __FUTEX_WAKE 38 | FUTEX_FD = __FUTEX_FD 39 | FUTEX_REQUEUE = __FUTEX_REQUEUE 40 | FUTEX_CMP_REQUEUE = __FUTEX_CMP_REQUEUE 41 | FUTEX_WAKE_OP = __FUTEX_WAKE_OP 42 | FUTEX_WAIT_BITSET = __FUTEX_WAIT_BITSET 43 | FUTEX_WAKE_BITSET = __FUTEX_WAKE_BITSET 44 | FUTEX_LOCK_PI = __FUTEX_LOCK_PI 45 | FUTEX_LOCK_PI2 = __FUTEX_LOCK_PI2 46 | FUTEX_TRYLOCK_PI = __FUTEX_TRYLOCK_PI 47 | FUTEX_UNLOCK_PI = __FUTEX_UNLOCK_PI 48 | FUTEX_CMP_REQUEUE_PI = __FUTEX_CMP_REQUEUE_PI 49 | FUTEX_WAIT_REQUEUE_PI = __FUTEX_WAIT_REQUEUE_PI 50 | 51 | FUTEX2_PRIVATE = __FUTEX2_PRIVATE 52 | 53 | FUTEX2_SIZE_U8 = __FUTEX2_SIZE_U8 54 | FUTEX2_SIZE_U16 = __FUTEX2_SIZE_U16 55 | FUTEX2_SIZE_U32 = __FUTEX2_SIZE_U32 56 | FUTEX2_SIZE_U64 = __FUTEX2_SIZE_U64 57 | FUTEX2_NUMA = __FUTEX2_NUMA 58 | 59 | FUTEX_WAITV_MAX = __FUTEX_WAITV_MAX 60 | FUTEX_BITSET_MATCH_ANY = __FUTEX_BITSET_MATCH_ANY 61 | -------------------------------------------------------------------------------- /src/liburing/futex.pyx: -------------------------------------------------------------------------------- 1 | cdef class futex_state: 2 | ''' Futex State - User Address Memory ''' 3 | def __cinit__(self, __u8 num=1, bint private=False): 4 | ''' 5 | Type 6 | num: int 7 | private: bool 8 | return: None 9 | 10 | Example 11 | >>> futex = futex_state() 12 | >>> futex.state 13 | 0 14 | >>> futex.private 15 | False 16 | 17 | >>> io_uring_prep_futex_wait(sqe, futex) 18 | >>> futex.state = 1 19 | >>> io_uring_prep_futex_wake(sqe, futex) 20 | 21 | >>> futex.state = 0 22 | >>> fwv = futex_waitv(futex) 23 | >>> io_uring_prep_futex_waitv(sqe, fwv) 24 | 25 | Note 26 | - By default `MAP_SHARED` is created. Set `futex_state(True)` to use `MAP_PRIVATE` 27 | and `FUTEX2_PRIVATE` 28 | ''' 29 | if not __FUTEX2_PRIVATE: 30 | raise EnvironmentError('Looks like your Linux version does not support `futex2` ' 31 | 'features. Please updrade to Linux `6.7`+ and try again.') 32 | if num > 1: 33 | raise NotImplementedError(f'`{self.__class__.__name__}()` `num > 1`') 34 | if num > __FUTEX_WAITV_MAX: 35 | raise ValueError(f'{self.__class__.__name__}(num={num}) > {__FUTEX_WAITV_MAX}') 36 | self.len = num 37 | self.private = private 38 | if num: 39 | self.ptr = mmap(NULL, sizeof(uint32_t) * num, PROT_READ | PROT_WRITE, 40 | MAP_ANONYMOUS | (MAP_PRIVATE if private else MAP_SHARED), 41 | -1, 0) 42 | if self.ptr == MAP_FAILED: 43 | memory_error(self, '- `mmap()` not created!') 44 | 45 | def __dealloc__(self): 46 | if self.len: 47 | munmap(self.ptr, sizeof(uint32_t) * self.len) 48 | self.ptr = NULL 49 | 50 | @property 51 | def state(self) -> uint32_t: 52 | if self.ptr is NULL: 53 | memory_error(self, 'memory not set!') 54 | return self.ptr[0] 55 | 56 | @state.setter 57 | def state(self, uint32_t value): 58 | if self.ptr is NULL: 59 | memory_error(self, 'memory not set!') 60 | self.ptr[0] = value 61 | 62 | def __repr__(self): 63 | if self.ptr is NULL: 64 | return f'{self.__class__.__name__}(state=NULL, private=NULL)' 65 | else: 66 | return f'{self.__class__.__name__}(state={self.ptr[0]!r}, private={self.private!r})' 67 | 68 | cdef class futex_waitv: 69 | ''' A Waiter For Vectorized Wait ''' 70 | def __cinit__(self, futex_state futex): 71 | ''' 72 | Type 73 | futex: futex_state 74 | return: None 75 | 76 | Flags 77 | FUTEX2_SIZE_U8 78 | FUTEX2_SIZE_U16 79 | FUTEX2_SIZE_U32 80 | FUTEX2_SIZE_U64 81 | FUTEX2_NUMA 82 | 83 | Example 84 | >>> futex = futex_state() 85 | >>> futex.state = 1 86 | 87 | >>> waiter = futex_waitv(futex) 88 | >>> waiter.val = 0 89 | >>> waiter.val 90 | 0 91 | 92 | >>> io_uring_prep_futex_waitv(sqe, waiter, ...) 93 | ''' 94 | if futex.len > 1: 95 | raise NotImplementedError(f'`{self.__class__.__name__}()` `waiters > 1`') 96 | 97 | if futex.len: 98 | self.ptr = <__futex_waitv*>PyMem_RawCalloc(futex.len, sizeof(__futex_waitv)) 99 | if self.ptr is NULL: 100 | memory_error(self) 101 | self.ptr.uaddr = <__u64>futex.ptr 102 | self.private = futex.private 103 | self.len = futex.len 104 | 105 | if self.private: 106 | self.ptr.flags = __FUTEX2_PRIVATE 107 | 108 | def __dealloc__(self): 109 | if self.ptr is not NULL: 110 | PyMem_RawFree(self.ptr) 111 | self.ptr = NULL 112 | 113 | @property 114 | def val(self): 115 | if self.ptr is not NULL: 116 | return self.ptr.val 117 | 118 | @val.setter 119 | def val(self, __u64 val): 120 | if self.ptr is not NULL: 121 | self.ptr.val = val 122 | 123 | @property 124 | def flags(self): 125 | if self.ptr is not NULL: 126 | return self.ptr.flags 127 | 128 | @flags.setter 129 | def flags(self, __u32 flags): 130 | if self.ptr is not NULL: 131 | self.ptr.flags |= flags 132 | 133 | 134 | cpdef inline void io_uring_prep_futex_wake(io_uring_sqe sqe, 135 | futex_state futex, 136 | uint64_t val, 137 | uint64_t mask, 138 | uint32_t futex_flags) noexcept nogil: 139 | __io_uring_prep_futex_wake(sqe.ptr, futex.ptr, val, mask, futex_flags, 0) 140 | 141 | cpdef inline void io_uring_prep_futex_wait(io_uring_sqe sqe, 142 | futex_state futex, 143 | uint64_t val, 144 | uint64_t mask, 145 | uint32_t futex_flags) noexcept nogil: 146 | __io_uring_prep_futex_wait(sqe.ptr, futex.ptr, val, mask, futex_flags, 0) 147 | 148 | cpdef inline void io_uring_prep_futex_waitv(io_uring_sqe sqe, futex_waitv waiters) noexcept nogil: 149 | __io_uring_prep_futex_waitv(sqe.ptr, waiters.ptr, waiters.len, 0) 150 | -------------------------------------------------------------------------------- /src/liburing/helper.pxd: -------------------------------------------------------------------------------- 1 | from libc.string cimport memcpy 2 | from .lib.uring cimport * 3 | from .queue cimport io_uring, io_uring_sqe 4 | 5 | 6 | cpdef bool io_uring_put_sqe(io_uring ring, io_uring_sqe sqe_s) noexcept nogil 7 | -------------------------------------------------------------------------------- /src/liburing/helper.pyx: -------------------------------------------------------------------------------- 1 | cpdef inline bool io_uring_put_sqe(io_uring ring, io_uring_sqe sqe_s) noexcept nogil: 2 | ''' Put `io_uring_sqe` into `io_uring` ring's memory. 3 | 4 | Type 5 | ring: io_uring 6 | sqe_s: io_uring_sqe 7 | return: bool 8 | 9 | Example 10 | # single 11 | >>> sqe = io_uring_sqe() 12 | >>> io_uring_prep_read(sqe, ...) 13 | 14 | # multiple 15 | >>> sqe = io_uring_sqe(2) 16 | >>> io_uring_prep_read(sqe[0], ...) 17 | >>> io_uring_prep_write(sqe[1], ...) 18 | 19 | # back-end single | multiple 20 | >>> if io_uring_put_sqe(self.ring, sqe): 21 | ... # do stuff 22 | 23 | # failed: `ring` was full, submit and try to again. 24 | >>> if io_uring_put_sqe(self.ring, sqe): 25 | ... io_uring_submit(self.ring) 26 | ... if io_uring_put_sqe(self.ring, sqe): 27 | ... # do stuff 28 | 29 | Note 30 | - Returns `False` if queue is full. Will need to `io_uring_submit()` and try again. 31 | - Returns `False` if `entries` < `len(sqe_s)` 32 | ''' 33 | cdef: 34 | __u16 i 35 | size_t size 36 | __io_uring_sqe* sqe 37 | 38 | if sqe_s.len == 0: 39 | return True 40 | # note: 41 | # - its ok to submit `0` sqe thus `True` 42 | # - this also accounts for `ptr` gotten from `io_uring_get_sqe` 43 | # as its `len == 0` thus not try to replace memory by mistake! 44 | else: 45 | size = sizeof(__io_uring_sqe) 46 | if sqe_s.len == 1: 47 | if (sqe := __io_uring_get_sqe(&ring.ptr)) is not NULL: 48 | memcpy(sqe, sqe_s.ptr, size) 49 | return True 50 | elif (ring.ptr.sq.ring_entries >= sqe_s.len 51 | and __io_uring_sq_space_left(&ring.ptr) >= sqe_s.len): 52 | for i in range(sqe_s.len): 53 | if (sqe := __io_uring_get_sqe(&ring.ptr)) is NULL: 54 | return False # this should ever trigger but just in case! 55 | memcpy(sqe, &sqe_s.ptr[i], size) 56 | else: 57 | return True 58 | else: 59 | return False 60 | -------------------------------------------------------------------------------- /src/liburing/include/liburing.h: -------------------------------------------------------------------------------- 1 | /* 2 | These are just empty `*.h` files, which will be replaced with actual 3 | content before compiling. 4 | 5 | This trick is used so `pip` will properly install and uninstall these files. 6 | 7 | Include header files are needed when external library uses `cimport` 8 | from `liburing` and that library needs header info to compile properly. 9 | */ 10 | -------------------------------------------------------------------------------- /src/liburing/include/liburing/barrier.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YoSTEALTH/Liburing/5898d15bfae088173cf50674876c5d1e3e5f1b70/src/liburing/include/liburing/barrier.h -------------------------------------------------------------------------------- /src/liburing/include/liburing/compat.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YoSTEALTH/Liburing/5898d15bfae088173cf50674876c5d1e3e5f1b70/src/liburing/include/liburing/compat.h -------------------------------------------------------------------------------- /src/liburing/include/liburing/io_uring.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YoSTEALTH/Liburing/5898d15bfae088173cf50674876c5d1e3e5f1b70/src/liburing/include/liburing/io_uring.h -------------------------------------------------------------------------------- /src/liburing/include/liburing/io_uring_version.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YoSTEALTH/Liburing/5898d15bfae088173cf50674876c5d1e3e5f1b70/src/liburing/include/liburing/io_uring_version.h -------------------------------------------------------------------------------- /src/liburing/include/liburing/sanitize.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YoSTEALTH/Liburing/5898d15bfae088173cf50674876c5d1e3e5f1b70/src/liburing/include/liburing/sanitize.h -------------------------------------------------------------------------------- /src/liburing/lib/__init__.pxd: -------------------------------------------------------------------------------- 1 | # External Cython Usage: 2 | # from liburing.lib.uring cimport * 3 | -------------------------------------------------------------------------------- /src/liburing/lib/file.pxd: -------------------------------------------------------------------------------- 1 | from .type cimport * 2 | 3 | 4 | cdef extern from '../include/liburing/compat.h' nogil: 5 | # Definition of RESOLVE_* constants 6 | struct __open_how 'open_how': 7 | __u64 flags 8 | __u64 mode 9 | __u64 resolve 10 | 11 | enum: 12 | # Block mount-point crossings (includes bind-mounts). 13 | __RESOLVE_NO_XDEV 'RESOLVE_NO_XDEV' 14 | # Block traversal through procfs-style "magic-links". 15 | __RESOLVE_NO_MAGICLINKS 'RESOLVE_NO_MAGICLINKS' 16 | # Block traversal through all symlinks (implies OEXT_NO_MAGICLINKS) 17 | __RESOLVE_NO_SYMLINKS 'RESOLVE_NO_SYMLINKS' 18 | # Block "lexical" trickery like "..", symlinks, and absolute paths which escape the dirfd. 19 | __RESOLVE_BENEATH 'RESOLVE_BENEATH' 20 | # Make all jumps to "/" and ".." be scoped inside the dirfd (similar to chroot(2)). 21 | __RESOLVE_IN_ROOT 'RESOLVE_IN_ROOT' 22 | # Only complete if resolution can be completed through cached lookup. 23 | # May return `-EAGAIN` if that's not possible. 24 | __RESOLVE_CACHED 'RESOLVE_CACHED' 25 | 26 | 27 | cdef extern from '' nogil: 28 | enum: 29 | # `sync_file_range` flags for `io_uring_prep_sync_file_range` 30 | # ----------------------------------------------------------- 31 | __SYNC_FILE_RANGE_WAIT_BEFORE 'SYNC_FILE_RANGE_WAIT_BEFORE' 32 | __SYNC_FILE_RANGE_WRITE 'SYNC_FILE_RANGE_WRITE' 33 | __SYNC_FILE_RANGE_WAIT_AFTER 'SYNC_FILE_RANGE_WAIT_AFTER' 34 | 35 | __O_ACCMODE 'O_ACCMODE' 36 | __O_RDONLY 'O_RDONLY' 37 | __O_WRONLY 'O_WRONLY' 38 | __O_RDWR 'O_RDWR' 39 | 40 | __O_APPEND 'O_APPEND' 41 | __O_ASYNC 'O_ASYNC' 42 | __O_CLOEXEC 'O_CLOEXEC' 43 | __O_CREAT 'O_CREAT' 44 | # ... 45 | __O_DIRECT 'O_DIRECT' 46 | __O_DIRECTORY 'O_DIRECTORY' 47 | __O_DSYNC 'O_DSYNC' 48 | __O_EXCL 'O_EXCL' 49 | __O_LARGEFILE 'O_LARGEFILE' 50 | __O_NOATIME 'O_NOATIME' 51 | __O_NOCTTY 'O_NOCTTY' 52 | __O_NOFOLLOW 'O_NOFOLLOW' 53 | __O_NONBLOCK 'O_NONBLOCK' # 'O_NDELAY' 54 | __O_PATH 'O_PATH' 55 | # ... 56 | __O_SYNC 'O_SYNC' 57 | __O_TMPFILE 'O_TMPFILE' # must be specified with `O_RDWR` | `O_WRONLY`. `O_EXCL` (optional) 58 | __O_TRUNC 'O_TRUNC' 59 | -------------------------------------------------------------------------------- /src/liburing/lib/futex.pxd: -------------------------------------------------------------------------------- 1 | from .type cimport * 2 | 3 | 4 | # note: Linux 6.7+ 5 | # `io_uring` uses `futex2`, not `futex` 6 | cdef extern from '' nogil: 7 | ''' #ifndef FUTEX2_PRIVATE 8 | #include "../include/liburing/compat.h" 9 | 10 | #define FUTEX2_PRIVATE 0 11 | 12 | #define FUTEX2_SIZE_U8 0 13 | #define FUTEX2_SIZE_U16 0 14 | #define FUTEX2_SIZE_U32 0 15 | #define FUTEX2_SIZE_U64 0 16 | #define FUTEX2_NUMA 0 17 | #endif 18 | ''' 19 | struct __futex_waitv 'futex_waitv': 20 | __u64 val # expected value at `uaddr` 21 | __u64 uaddr # user address to wait on 22 | __u32 flags # flags for this waiter 23 | __u32 __reserved # reserved member to preserve data alignment. Should be `0`. 24 | 25 | enum: 26 | # Catch Result(s) 27 | __FUTEX_WAIT 'FUTEX_WAIT' 28 | __FUTEX_WAKE 'FUTEX_WAKE' 29 | __FUTEX_FD 'FUTEX_FD' 30 | __FUTEX_REQUEUE 'FUTEX_REQUEUE' 31 | __FUTEX_CMP_REQUEUE 'FUTEX_CMP_REQUEUE' 32 | __FUTEX_WAKE_OP 'FUTEX_WAKE_OP' 33 | __FUTEX_WAIT_BITSET 'FUTEX_WAIT_BITSET' 34 | __FUTEX_WAKE_BITSET 'FUTEX_WAKE_BITSET' 35 | __FUTEX_LOCK_PI 'FUTEX_LOCK_PI' 36 | __FUTEX_LOCK_PI2 'FUTEX_LOCK_PI2' 37 | __FUTEX_TRYLOCK_PI 'FUTEX_TRYLOCK_PI' 38 | __FUTEX_UNLOCK_PI 'FUTEX_UNLOCK_PI' 39 | __FUTEX_CMP_REQUEUE_PI 'FUTEX_CMP_REQUEUE_PI' 40 | __FUTEX_WAIT_REQUEUE_PI 'FUTEX_WAIT_REQUEUE_PI' 41 | 42 | # a futex can be either private or shared. private is used for processes that shares the 43 | # same memory space and the virtual address of the futex will be the same for all processes. 44 | # this allows for optimizations in the kernel. to use private flag: 45 | # e.g `__FUTEX2_PRIVATE | __FUTEX_WAIT` 46 | __FUTEX2_PRIVATE 'FUTEX2_PRIVATE' 47 | 48 | # Flags for `futex2` syscalls. 49 | __FUTEX2_SIZE_U8 'FUTEX2_SIZE_U8' 50 | __FUTEX2_SIZE_U16 'FUTEX2_SIZE_U16' 51 | __FUTEX2_SIZE_U32 'FUTEX2_SIZE_U32' 52 | __FUTEX2_SIZE_U64 'FUTEX2_SIZE_U64' 53 | __FUTEX2_NUMA 'FUTEX2_NUMA' 54 | 55 | # max numbers of elements in a `futex_waitv` array 56 | __FUTEX_WAITV_MAX 'FUTEX_WAITV_MAX' 57 | 58 | # mask-bitset with all bits set for the `FUTEX_*_BITSET` OPs to request a match of any bit. 59 | __FUTEX_BITSET_MATCH_ANY 'FUTEX_BITSET_MATCH_ANY' 60 | -------------------------------------------------------------------------------- /src/liburing/lib/poll.pxd: -------------------------------------------------------------------------------- 1 | from .type cimport * 2 | 3 | 4 | cdef extern from '' nogil: 5 | ctypedef union epoll_data_t: 6 | void* ptr 7 | int fd 8 | uint32_t u32 9 | uint64_t u64 10 | 11 | struct __epoll_event 'epoll_event': 12 | uint32_t events # Epoll events 13 | epoll_data_t data # User data variable 14 | 15 | # TODO: need to add epoll flags 16 | -------------------------------------------------------------------------------- /src/liburing/lib/statx.pxd: -------------------------------------------------------------------------------- 1 | from .type cimport * 2 | 3 | 4 | cdef extern from '' nogil: 5 | # Timestamp structure for the timestamps in struct statx. 6 | struct __statx_timestamp 'statx_timestamp': 7 | __s64 tv_sec # `tv_sec` seconds before(-) or after(+) 00:00:00 1st Jan 1970 UTC. 8 | __u32 tv_nsec # `tv_nsec` nanoseconds (0..999,999,999) after the `tv_sec` time. 9 | __s32 __reserved 10 | 11 | # Structures for the extended file attribute retrieval system call `statx()`. 12 | struct __statx 'statx': 13 | # 0x00 14 | __u32 stx_mask # What results were written 15 | __u32 stx_blksize # Preferred general I/O size 16 | __u64 stx_attributes # Flags conveying information about the file 17 | # 0x10 18 | __u32 stx_nlink # Number of hard links 19 | __u32 stx_uid # User ID of owner 20 | __u32 stx_gid # Group ID of owner 21 | __u16 stx_mode # File type and mode 22 | # 0x20 23 | __u64 stx_ino # Inode number 24 | __u64 stx_size # Total size in bytes 25 | __u64 stx_blocks # Number of 512-byte blocks allocated 26 | __u64 stx_attributes_mask # Mask to show what's supported in `stx_attributes` 27 | # 0x40 - The following fields are file timestamps 28 | __statx_timestamp stx_atime # Last access time 29 | __statx_timestamp stx_btime # File creation time 30 | __statx_timestamp stx_ctime # Last attribute change time 31 | __statx_timestamp stx_mtime # Last data modification time 32 | # 0x80 33 | __u32 stx_rdev_major # Device ID of special file 34 | __u32 stx_rdev_minor 35 | __u32 stx_dev_major # ID of device containing file 36 | __u32 stx_dev_minor 37 | # 0x90 38 | __u64 stx_mnt_id 39 | # note: not supported 40 | # __u32 stx_dio_mem_align # Memory buffer alignment for direct I/O 41 | # __u32 stx_dio_offset_align # File offset alignment for direct I/O 42 | 43 | # Mask - flags to be set for `stx_mask` 44 | # - Query request/result mask for `statx()` and struct `statx::stx_mask`. 45 | # - These bits should be set in the "mask" argument of `statx()` to request 46 | # particular items when calling `statx()`. 47 | enum: 48 | __STATX_TYPE 'STATX_TYPE' # Want|got `stx_mode & S_IFMT` 49 | __STATX_MODE 'STATX_MODE' # Want|got `stx_mode & ~S_IFMT` 50 | __STATX_NLINK 'STATX_NLINK' # Want|got `stx_nlink` 51 | __STATX_UID 'STATX_UID' # Want|got `stx_uid` 52 | __STATX_GID 'STATX_GID' # Want|got `stx_gid` 53 | __STATX_ATIME 'STATX_ATIME' # Want|got `stx_atime` 54 | __STATX_MTIME 'STATX_MTIME' # Want|got `stx_mtime` 55 | __STATX_CTIME 'STATX_CTIME' # Want|got `stx_ctime` 56 | __STATX_INO 'STATX_INO' # Want|got `stx_ino` 57 | __STATX_SIZE 'STATX_SIZE' # Want|got `stx_size` 58 | __STATX_BLOCKS 'STATX_BLOCKS' # Want|got `stx_blocks` 59 | __STATX_BASIC_STATS 'STATX_BASIC_STATS' # [All of the above] 60 | __STATX_BTIME 'STATX_BTIME' # Want|got `stx_btime` 61 | __STATX_MNT_ID 'STATX_MNT_ID' # Got `stx_mnt_id` 62 | # note: not supported 63 | # __STATX_DIOALIGN 'STATX_DIOALIGN' # Want/got direct I/O alignment info 64 | 65 | # Attributes to be found in `stx_attributes` and masked in `stx_attributes_mask`. 66 | __STATX_ATTR_COMPRESSED 'STATX_ATTR_COMPRESSED' # [I] File is compressed by the fs 67 | __STATX_ATTR_IMMUTABLE 'STATX_ATTR_IMMUTABLE' # [I] File is marked immutable 68 | __STATX_ATTR_APPEND 'STATX_ATTR_APPEND' # [I] File is append-only 69 | __STATX_ATTR_NODUMP 'STATX_ATTR_NODUMP' # [I] File is not to be dumped 70 | __STATX_ATTR_ENCRYPTED 'STATX_ATTR_ENCRYPTED' # [I] File requires key to decrypt in fs 71 | __STATX_ATTR_AUTOMOUNT 'STATX_ATTR_AUTOMOUNT' # Dir: Automount trigger 72 | __STATX_ATTR_MOUNT_ROOT 'STATX_ATTR_MOUNT_ROOT' # Root of a mount 73 | __STATX_ATTR_VERITY 'STATX_ATTR_VERITY' # [I] Verity protected file 74 | __STATX_ATTR_DAX 'STATX_ATTR_DAX' # File is currently in DAX state 75 | # note: flags marked [I] correspond to the `FS_IOC_SETFLAGS` flags 76 | 77 | # Encoding of the file mode. 78 | __S_IFMT 'S_IFMT' # These bits determine file type. 79 | # File types. 80 | __S_IFSOCK 'S_IFSOCK' # socket 81 | __S_IFLNK 'S_IFLNK' # symbolic link 82 | __S_IFREG 'S_IFREG' # regular file 83 | __S_IFBLK 'S_IFBLK' # block device 84 | __S_IFDIR 'S_IFDIR' # directory 85 | __S_IFCHR 'S_IFCHR' # character device 86 | __S_IFIFO 'S_IFIFO' # FIFO 87 | 88 | __S_ISUID 'S_ISUID' 89 | __S_ISGID 'S_ISGID' 90 | __S_ISVTX 'S_ISVTX' 91 | 92 | __S_IRWXU 'S_IRWXU' 93 | __S_IRUSR 'S_IRUSR' 94 | __S_IWUSR 'S_IWUSR' 95 | __S_IXUSR 'S_IXUSR' 96 | 97 | __S_IRWXG 'S_IRWXG' 98 | __S_IRGRP 'S_IRGRP' 99 | __S_IWGRP 'S_IWGRP' 100 | __S_IXGRP 'S_IXGRP' 101 | 102 | __S_IRWXO 'S_IRWXO' 103 | __S_IROTH 'S_IROTH' 104 | __S_IWOTH 'S_IWOTH' 105 | __S_IXOTH 'S_IXOTH' 106 | 107 | # Macro 108 | int __S_ISLNK 'S_ISLNK'(mode_t m) 109 | int __S_ISREG 'S_ISREG'(mode_t m) 110 | int __S_ISDIR 'S_ISDIR'(mode_t m) 111 | int __S_ISCHR 'S_ISCHR'(mode_t m) 112 | int __S_ISBLK 'S_ISBLK'(mode_t m) 113 | int __S_ISFIFO 'S_ISFIFO'(mode_t m) 114 | int __S_ISSOCK 'S_ISSOCK'(mode_t m) 115 | 116 | 117 | cdef extern from * nogil: # '' 118 | enum: 119 | # Flags 120 | # __AT_STATX_SYNC_TYPE 'AT_STATX_SYNC_TYPE' # skipping: not documented 121 | __AT_STATX_SYNC_AS_STAT 'AT_STATX_SYNC_AS_STAT' 122 | __AT_STATX_FORCE_SYNC 'AT_STATX_FORCE_SYNC' 123 | __AT_STATX_DONT_SYNC 'AT_STATX_DONT_SYNC' 124 | -------------------------------------------------------------------------------- /src/liburing/lib/type.pxd: -------------------------------------------------------------------------------- 1 | ctypedef bint bool 2 | 3 | 4 | cdef extern from '' nogil: 5 | # NOTE: Does not matter that `ctypedef int` is given to all, 6 | # compiler will assign it correct type(tested!). 7 | 8 | # Custom Types 9 | ctypedef int off_t 10 | ctypedef int mode_t 11 | ctypedef int size_t 12 | ctypedef int ssize_t 13 | ctypedef int loff_t 14 | 15 | ctypedef int __s8 16 | ctypedef int __s16 17 | ctypedef int __s32 18 | ctypedef int __s64 19 | ctypedef unsigned int __u8 20 | ctypedef unsigned int __u16 21 | ctypedef unsigned int __u32 22 | ctypedef unsigned int __u64 23 | ctypedef unsigned int __aligned_u64 24 | ctypedef int int8_t 25 | ctypedef int int16_t 26 | ctypedef int int32_t 27 | ctypedef int int64_t 28 | ctypedef unsigned int uint8_t 29 | ctypedef unsigned int uint16_t 30 | ctypedef unsigned int uint32_t 31 | ctypedef unsigned int uint64_t 32 | ctypedef unsigned int uintptr_t 33 | 34 | # compat.h - this file is auto-created so its correct for these values to be here. 35 | # ========------------------------------------------------------------------------ 36 | ctypedef int __kernel_rwf_t 37 | 38 | 39 | cdef extern from '' nogil: 40 | ''' const int BITS = __CPU_SETSIZE / __NCPUBITS; 41 | ''' 42 | const int BITS 43 | ctypedef unsigned long __cpu_mask 44 | ctypedef struct cpu_set_t: 45 | __cpu_mask __bits[BITS] 46 | 47 | 48 | cdef extern from '' nogil: 49 | ctypedef int id_t 50 | ctypedef int idtype_t 51 | 52 | ctypedef int pid_t 53 | ctypedef int uid_t 54 | ctypedef int si_value 55 | 56 | union sigval: 57 | si_value value 58 | 59 | ctypedef struct siginfo_t: 60 | int si_signo # Signal number 61 | int si_code # Signal code 62 | pid_t si_pid # Sending process ID 63 | uid_t si_uid # Real user ID of sending process 64 | void* si_addr # Address of faulting instruction 65 | int si_status # Exit value or signal 66 | sigval si_value # Signal value 67 | 68 | 69 | cdef extern from '' nogil: 70 | enum: _SIGSET_NWORDS 71 | ctypedef struct __sigset_t: 72 | unsigned long int __val[_SIGSET_NWORDS] 73 | ctypedef __sigset_t sigset_t 74 | 75 | 76 | cdef extern from '' nogil: 77 | struct __iovec 'iovec': 78 | void* iov_base 79 | size_t iov_len 80 | 81 | 82 | cdef extern from '../include/liburing/compat.h' nogil: 83 | ctypedef int64_t __kernel_time64_t 84 | struct __kernel_timespec: 85 | __kernel_time64_t tv_sec # seconds 86 | long long tv_nsec # nanoseconds 87 | 88 | 89 | cdef extern from '' nogil: 90 | enum: 91 | # flags for `renameat2`. 92 | __RENAME_NOREPLACE 'RENAME_NOREPLACE' 93 | __RENAME_EXCHANGE 'RENAME_EXCHANGE' 94 | __RENAME_WHITEOUT 'RENAME_WHITEOUT' 95 | 96 | 97 | cdef extern from '' nogil: 98 | enum: 99 | # AT_* flags 100 | __AT_FDCWD 'AT_FDCWD' # Use current working directory. 101 | __AT_SYMLINK_FOLLOW 'AT_SYMLINK_FOLLOW' # Follow symbolic links. 102 | __AT_SYMLINK_NOFOLLOW 'AT_SYMLINK_NOFOLLOW' # Do not follow symbolic links. 103 | __AT_REMOVEDIR 'AT_REMOVEDIR' # Remove directory instead of unlinking file. 104 | __AT_NO_AUTOMOUNT 'AT_NO_AUTOMOUNT' # Suppress terminal automount traversal. 105 | __AT_EMPTY_PATH 'AT_EMPTY_PATH' # Allow empty relative pathname. 106 | __AT_RECURSIVE 'AT_RECURSIVE' # Apply to the entire subtree. 107 | 108 | # splice flags 109 | __SPLICE_F_MOVE 'SPLICE_F_MOVE' # Move pages instead of copying. 110 | __SPLICE_F_NONBLOCK 'SPLICE_F_NONBLOCK' # Don't block on the pipe splicing 111 | __SPLICE_F_MORE 'SPLICE_F_MORE' # Expect more data. 112 | __SPLICE_F_GIFT 'SPLICE_F_GIFT' # Pages passed in are a gift. 113 | 114 | # `fallocate` mode 115 | __FALLOC_FL_KEEP_SIZE 'FALLOC_FL_KEEP_SIZE' 116 | __FALLOC_FL_PUNCH_HOLE 'FALLOC_FL_PUNCH_HOLE' 117 | __FALLOC_FL_NO_HIDE_STALE 'FALLOC_FL_NO_HIDE_STALE' 118 | __FALLOC_FL_COLLAPSE_RANGE 'FALLOC_FL_COLLAPSE_RANGE' 119 | __FALLOC_FL_ZERO_RANGE 'FALLOC_FL_ZERO_RANGE' 120 | __FALLOC_FL_INSERT_RANGE 'FALLOC_FL_INSERT_RANGE' 121 | __FALLOC_FL_UNSHARE_RANGE 'FALLOC_FL_UNSHARE_RANGE' 122 | -------------------------------------------------------------------------------- /src/liburing/os.pxd: -------------------------------------------------------------------------------- 1 | from .queue cimport * 2 | 3 | 4 | cpdef void io_uring_prep_mkdir(io_uring_sqe sqe, const char *path, mode_t mode=?) noexcept nogil 5 | cpdef void io_uring_prep_mkdirat(io_uring_sqe sqe, 6 | const char *path, 7 | mode_t mode=?, 8 | int dfd=?) noexcept nogil 9 | 10 | cpdef void io_uring_prep_rename(io_uring_sqe sqe, 11 | const char *oldpath, 12 | const char *newpath) noexcept nogil 13 | cpdef void io_uring_prep_renameat(io_uring_sqe sqe, 14 | const char *oldpath, 15 | const char *newpath, 16 | int olddfd=?, 17 | int newdfd=?, 18 | unsigned int flags=?) noexcept nogil 19 | 20 | cpdef void io_uring_prep_symlinkat(io_uring_sqe sqe, 21 | const char *target, 22 | const char *linkpath, 23 | int newdirfd=?) noexcept nogil 24 | cpdef void io_uring_prep_symlink(io_uring_sqe sqe, 25 | const char *target, 26 | const char *linkpath) noexcept nogil 27 | 28 | cpdef void io_uring_prep_link(io_uring_sqe sqe, 29 | const char *oldpath, 30 | const char *newpath, 31 | int flags=?) noexcept nogil 32 | cpdef void io_uring_prep_linkat(io_uring_sqe sqe, 33 | const char *oldpath, 34 | const char *newpath, 35 | int olddfd=?, 36 | int newdfd=?, 37 | int flags=?) noexcept nogil 38 | cpdef void io_uring_prep_unlink(io_uring_sqe sqe, 39 | const char *path, 40 | int flags=?) noexcept nogil 41 | cpdef void io_uring_prep_unlinkat(io_uring_sqe sqe, 42 | const char *path, 43 | int flags=?, 44 | int dfd=?) noexcept nogil 45 | 46 | cpdef void io_uring_prep_fallocate(io_uring_sqe sqe, 47 | int fd, 48 | __u64 len, 49 | __u64 offset=?, 50 | int mode=?) noexcept nogil 51 | 52 | cpdef void io_uring_prep_splice(io_uring_sqe sqe, 53 | int fd_in, 54 | int64_t off_in, 55 | int fd_out, 56 | int64_t off_out, 57 | unsigned int nbytes, 58 | unsigned int splice_flags) noexcept nogil 59 | 60 | 61 | cpdef enum __os_define__: 62 | SPLICE_F_FD_IN_FIXED = __SPLICE_F_FD_IN_FIXED 63 | 64 | # flags for `renameat2`. 65 | RENAME_NOREPLACE = __RENAME_NOREPLACE 66 | RENAME_EXCHANGE = __RENAME_EXCHANGE 67 | RENAME_WHITEOUT = __RENAME_WHITEOUT 68 | 69 | # `fallocate` mode 70 | FALLOC_FL_KEEP_SIZE = __FALLOC_FL_KEEP_SIZE 71 | FALLOC_FL_PUNCH_HOLE = __FALLOC_FL_PUNCH_HOLE 72 | FALLOC_FL_NO_HIDE_STALE = __FALLOC_FL_NO_HIDE_STALE 73 | FALLOC_FL_COLLAPSE_RANGE = __FALLOC_FL_COLLAPSE_RANGE 74 | FALLOC_FL_ZERO_RANGE = __FALLOC_FL_ZERO_RANGE 75 | FALLOC_FL_INSERT_RANGE = __FALLOC_FL_INSERT_RANGE 76 | FALLOC_FL_UNSHARE_RANGE = __FALLOC_FL_UNSHARE_RANGE 77 | 78 | # splice flags 79 | SPLICE_F_MOVE = __SPLICE_F_MOVE 80 | SPLICE_F_NONBLOCK = __SPLICE_F_NONBLOCK 81 | SPLICE_F_MORE = __SPLICE_F_MORE 82 | SPLICE_F_GIFT = __SPLICE_F_GIFT 83 | -------------------------------------------------------------------------------- /src/liburing/os.pyx: -------------------------------------------------------------------------------- 1 | cpdef inline void io_uring_prep_mkdir(io_uring_sqe sqe, 2 | const char *path, 3 | mode_t mode=0o777) noexcept nogil: 4 | __io_uring_prep_mkdir(sqe.ptr, path, mode) 5 | 6 | cpdef inline void io_uring_prep_mkdirat(io_uring_sqe sqe, 7 | const char *path, 8 | mode_t mode=0o777, 9 | int dfd=__AT_FDCWD) noexcept nogil: 10 | __io_uring_prep_mkdirat(sqe.ptr, dfd, path, mode) 11 | 12 | 13 | cpdef inline void io_uring_prep_rename(io_uring_sqe sqe, 14 | const char *oldpath, 15 | const char *newpath) noexcept nogil: 16 | __io_uring_prep_rename(sqe.ptr, oldpath, newpath) 17 | 18 | cpdef inline void io_uring_prep_renameat(io_uring_sqe sqe, 19 | const char *oldpath, 20 | const char *newpath, 21 | int olddfd=__AT_FDCWD, 22 | int newdfd=__AT_FDCWD, 23 | unsigned int flags=0) noexcept nogil: 24 | ''' 25 | Flags 26 | RENAME_NOREPLACE 27 | RENAME_EXCHANGE 28 | RENAME_WHITEOUT 29 | ''' 30 | __io_uring_prep_renameat(sqe.ptr, olddfd, oldpath, newdfd, newpath, flags) 31 | 32 | 33 | cpdef inline void io_uring_prep_symlinkat(io_uring_sqe sqe, 34 | const char *target, 35 | const char *linkpath, 36 | int newdirfd=__AT_FDCWD) noexcept nogil: 37 | __io_uring_prep_symlinkat(sqe.ptr, target, newdirfd, linkpath) 38 | 39 | cpdef inline void io_uring_prep_symlink(io_uring_sqe sqe, 40 | const char *target, 41 | const char *linkpath) noexcept nogil: 42 | __io_uring_prep_symlink(sqe.ptr, target, linkpath) 43 | 44 | 45 | cpdef inline void io_uring_prep_link(io_uring_sqe sqe, 46 | const char *oldpath, 47 | const char *newpath, 48 | int flags=0) noexcept nogil: 49 | __io_uring_prep_link(sqe.ptr, oldpath, newpath, flags) 50 | 51 | cpdef inline void io_uring_prep_linkat(io_uring_sqe sqe, 52 | const char *oldpath, 53 | const char *newpath, 54 | int olddfd=__AT_FDCWD, 55 | int newdfd=__AT_FDCWD, 56 | int flags=0) noexcept nogil: 57 | __io_uring_prep_linkat(sqe.ptr, olddfd, oldpath, newdfd, newpath, flags) 58 | 59 | cpdef inline void io_uring_prep_unlink(io_uring_sqe sqe, 60 | const char *path, 61 | int flags=0) noexcept nogil: 62 | ''' Flags: AT_REMOVEDIR ''' 63 | __io_uring_prep_unlink(sqe.ptr, path, flags) 64 | 65 | cpdef inline void io_uring_prep_unlinkat(io_uring_sqe sqe, 66 | const char *path, 67 | int flags=0, 68 | int dfd=__AT_FDCWD) noexcept nogil: 69 | ''' Flags: AT_REMOVEDIR ''' 70 | __io_uring_prep_unlinkat(sqe.ptr, dfd, path, flags) 71 | 72 | 73 | cpdef inline void io_uring_prep_fallocate(io_uring_sqe sqe, 74 | int fd, 75 | __u64 len, 76 | __u64 offset=0, 77 | int mode=0) noexcept nogil: 78 | ''' 79 | Mode 80 | FALLOC_FL_KEEP_SIZE # default is extend size 81 | FALLOC_FL_PUNCH_HOLE # de-allocates range 82 | FALLOC_FL_NO_HIDE_STALE # reserved codepoint 83 | FALLOC_FL_COLLAPSE_RANGE 84 | FALLOC_FL_ZERO_RANGE 85 | FALLOC_FL_INSERT_RANGE 86 | FALLOC_FL_UNSHARE_RANGE 87 | ''' 88 | __io_uring_prep_fallocate(sqe.ptr, fd, mode, offset, len) 89 | 90 | 91 | cpdef inline void io_uring_prep_splice(io_uring_sqe sqe, 92 | int fd_in, 93 | int64_t off_in, 94 | int fd_out, 95 | int64_t off_out, 96 | unsigned int nbytes, 97 | unsigned int splice_flags) noexcept nogil: 98 | ''' 99 | Note 100 | `io_uring_prep_splice()` - Either `fd_in` or `fd_out` must be a pipe. 101 | 102 | - If `fd_in` refers to a pipe, `off_in` is ignored and must be set to `-1`. 103 | 104 | - If `fd_in` does not refer to a pipe and `off_in` is `-1`, then `nbytes` are read 105 | from `fd_in` starting from the file offset, which is incremented by the 106 | number of bytes read. 107 | 108 | - If `fd_in` does not refer to a pipe and `off_in` is not `-1`, then the starting 109 | offset of `fd_in` will be `off_in`. 110 | 111 | This splice operation can be used to implement sendfile by splicing to an 112 | intermediate pipe first, then splice to the final destination. 113 | In fact, the implementation of sendfile in kernel uses splice internally. 114 | 115 | NOTE that even if `fd_in` or `fd_out` refers to a pipe, the splice operation 116 | can still fail with `EINVAL` if one of the `fd` doesn't explicitly support splice 117 | operation, e.g. reading from terminal is unsupported from kernel 5.7 to 5.11. 118 | Check issue #291 for more information. 119 | ''' 120 | __io_uring_prep_splice(sqe.ptr, fd_in, off_in, fd_out, off_out, nbytes, splice_flags) 121 | -------------------------------------------------------------------------------- /src/liburing/other.pxd: -------------------------------------------------------------------------------- 1 | from .lib.uring cimport * 2 | from .error cimport trap_error, memory_error 3 | from .queue cimport io_uring, io_uring_params, io_uring_sqe 4 | 5 | 6 | # TODO: class/function here have not been organized, yet! 7 | 8 | 9 | cdef class io_uring_buf_ring: 10 | cdef __io_uring_buf_ring * ptr 11 | # TODO: io_uring_free_buf_ring on exit. 12 | 13 | 14 | cpdef io_uring_buf_ring io_uring_setup_buf_ring(io_uring ring, 15 | unsigned int nentries, 16 | int bgid, 17 | unsigned int flags, 18 | int ret) 19 | cpdef int io_uring_free_buf_ring(io_uring ring, 20 | io_uring_buf_ring br, 21 | unsigned int nentries, 22 | int bgid) 23 | 24 | cpdef void io_uring_prep_fadvise(io_uring_sqe sqe, 25 | int fd, 26 | __u64 offset, 27 | off_t len, 28 | int advice) noexcept nogil 29 | # TODO: 30 | # cpdef void io_uring_prep_madvise(io_uring_sqe sqe, 31 | # void *addr, 32 | # off_t length, 33 | # int advice) noexcept nogil 34 | 35 | cpdef void io_uring_prep_msg_ring(io_uring_sqe sqe, 36 | int fd, 37 | unsigned int len, 38 | __u64 data, 39 | unsigned int flags=?) noexcept nogil 40 | cpdef void io_uring_prep_msg_ring_fd(io_uring_sqe sqe, 41 | int fd, 42 | int source_fd, 43 | int target_fd, 44 | __u64 data, 45 | unsigned int flags=?) noexcept nogil 46 | cpdef void io_uring_prep_msg_ring_fd_alloc(io_uring_sqe sqe, 47 | int fd, 48 | int source_fd, 49 | __u64 data, 50 | unsigned int flags=?) noexcept nogil 51 | cpdef void io_uring_prep_msg_ring_cqe_flags(io_uring_sqe sqe, 52 | int fd, 53 | unsigned int len, 54 | __u64 data, 55 | unsigned int cqe_flags, 56 | unsigned int flags=?) noexcept nogil 57 | 58 | cpdef ssize_t io_uring_mlock_size(unsigned int entries, unsigned int flags) nogil 59 | cpdef ssize_t io_uring_mlock_size_params(unsigned int entries, io_uring_params p) nogil 60 | -------------------------------------------------------------------------------- /src/liburing/other.pyx: -------------------------------------------------------------------------------- 1 | # TODO: class/function here have not been organized, yet! 2 | 3 | 4 | cdef class io_uring_buf_ring: 5 | pass 6 | 7 | # Mapped buffer ring alloc/register + unregister/free helpers 8 | cpdef io_uring_buf_ring io_uring_setup_buf_ring(io_uring ring, 9 | unsigned int nentries, 10 | int bgid, 11 | unsigned int flags, 12 | int ret): 13 | cdef io_uring_buf_ring buf = io_uring_buf_ring() 14 | buf.ptr = __io_uring_setup_buf_ring(&ring.ptr, nentries, bgid, flags, &ret) 15 | memory_error(buf) 16 | return buf 17 | 18 | cpdef int io_uring_free_buf_ring(io_uring ring, 19 | io_uring_buf_ring br, 20 | unsigned int nentries, 21 | int bgid): 22 | cdef int r = __io_uring_free_buf_ring(&ring.ptr, br.ptr, nentries, bgid) 23 | trap_error(r) 24 | br.ptr = NULL 25 | 26 | cpdef inline void io_uring_prep_fadvise(io_uring_sqe sqe, 27 | int fd, 28 | __u64 offset, 29 | off_t len, 30 | int advice) noexcept nogil: 31 | __io_uring_prep_fadvise(sqe.ptr, fd, offset, len, advice) 32 | 33 | # TODO: 34 | # cpdef inline void io_uring_prep_madvise(io_uring_sqe sqe, 35 | # void *addr, 36 | # off_t length, 37 | # int advice) noexcept nogil: 38 | # __io_uring_prep_madvise(sqe.ptr, &addr, length, advice) 39 | 40 | 41 | cpdef inline void io_uring_prep_msg_ring(io_uring_sqe sqe, 42 | int fd, 43 | unsigned int len, 44 | __u64 data, 45 | unsigned int flags=0) noexcept nogil: 46 | __io_uring_prep_msg_ring(sqe.ptr, fd, len, data, flags) 47 | 48 | cpdef inline void io_uring_prep_msg_ring_fd(io_uring_sqe sqe, 49 | int fd, 50 | int source_fd, 51 | int target_fd, 52 | __u64 data, 53 | unsigned int flags=0) noexcept nogil: 54 | __io_uring_prep_msg_ring_fd(sqe.ptr, fd, source_fd, target_fd, data, flags) 55 | 56 | cpdef inline void io_uring_prep_msg_ring_fd_alloc(io_uring_sqe sqe, 57 | int fd, 58 | int source_fd, 59 | __u64 data, 60 | unsigned int flags=0) noexcept nogil: 61 | __io_uring_prep_msg_ring_fd_alloc(sqe.ptr, fd, source_fd, data, flags) 62 | 63 | cpdef inline void io_uring_prep_msg_ring_cqe_flags(io_uring_sqe sqe, 64 | int fd, 65 | unsigned int len, 66 | __u64 data, 67 | unsigned int cqe_flags, 68 | unsigned int flags=0) noexcept nogil: 69 | __io_uring_prep_msg_ring_cqe_flags(sqe.ptr, fd, len, data, flags, cqe_flags) 70 | 71 | 72 | cpdef ssize_t io_uring_mlock_size(unsigned int entries, unsigned int flags) nogil: 73 | return __io_uring_mlock_size(entries, flags) 74 | 75 | cpdef ssize_t io_uring_mlock_size_params(unsigned int entries, io_uring_params p) nogil: 76 | return __io_uring_mlock_size_params(entries, p.ptr) 77 | -------------------------------------------------------------------------------- /src/liburing/poll.pxd: -------------------------------------------------------------------------------- 1 | from .queue cimport * 2 | 3 | 4 | cdef class epoll_event: 5 | cdef __epoll_event* ptr 6 | 7 | 8 | cpdef void io_uring_prep_poll_add(io_uring_sqe sqe, 9 | int fd, 10 | unsigned int poll_mask) noexcept nogil 11 | 12 | cpdef void io_uring_prep_poll_update(io_uring_sqe sqe, 13 | __u64 old_user_data, 14 | __u64 new_user_data, 15 | unsigned int poll_mask, 16 | unsigned int flags) noexcept nogil 17 | 18 | cpdef void io_uring_prep_poll_remove(io_uring_sqe sqe, __u64 user_data) noexcept nogil 19 | 20 | cpdef void io_uring_prep_poll_multishot(io_uring_sqe sqe, 21 | int fd, 22 | unsigned int poll_mask) noexcept nogil 23 | 24 | 25 | cpdef void io_uring_prep_epoll_ctl(io_uring_sqe sqe, 26 | int epfd, 27 | int fd, 28 | int op, 29 | epoll_event ev) noexcept nogil 30 | -------------------------------------------------------------------------------- /src/liburing/poll.pyx: -------------------------------------------------------------------------------- 1 | cdef class epoll_event: 2 | ''' Note: if you want this feature create an Issue/PR. ''' 3 | 4 | 5 | cpdef inline void io_uring_prep_poll_add(io_uring_sqe sqe, 6 | int fd, 7 | unsigned int poll_mask) noexcept nogil: 8 | __io_uring_prep_poll_add(sqe.ptr, fd, poll_mask) 9 | 10 | cpdef inline void io_uring_prep_poll_update(io_uring_sqe sqe, 11 | __u64 old_user_data, 12 | __u64 new_user_data, 13 | unsigned int poll_mask, 14 | unsigned int flags) noexcept nogil: 15 | __io_uring_prep_poll_update(sqe.ptr, old_user_data, new_user_data, poll_mask, flags) 16 | 17 | cpdef inline void io_uring_prep_poll_remove(io_uring_sqe sqe, 18 | __u64 user_data) noexcept nogil: 19 | __io_uring_prep_poll_remove(sqe.ptr, user_data) 20 | 21 | cpdef inline void io_uring_prep_poll_multishot(io_uring_sqe sqe, 22 | int fd, 23 | unsigned int poll_mask) noexcept nogil: 24 | __io_uring_prep_poll_multishot(sqe.ptr, fd, poll_mask) 25 | 26 | 27 | cpdef inline void io_uring_prep_epoll_ctl(io_uring_sqe sqe, 28 | int epfd, 29 | int fd, 30 | int op, 31 | epoll_event ev) noexcept nogil: 32 | __io_uring_prep_epoll_ctl(sqe.ptr, epfd, fd, op, ev.ptr) 33 | -------------------------------------------------------------------------------- /src/liburing/probe.pxd: -------------------------------------------------------------------------------- 1 | from cpython.mem cimport PyMem_RawCalloc, PyMem_RawFree 2 | from .lib.uring cimport * 3 | from .error cimport trap_error, memory_error 4 | from .queue cimport io_uring 5 | 6 | 7 | cdef class io_uring_probe: 8 | cdef: 9 | __io_uring_probe* ptr 10 | unsigned int len 11 | 12 | cpdef io_uring_probe io_uring_get_probe_ring(io_uring ring) 13 | cpdef io_uring_probe io_uring_get_probe() 14 | cpdef void io_uring_free_probe(io_uring_probe probe) 15 | cpdef bool io_uring_opcode_supported(io_uring_probe p, int op) 16 | cpdef int io_uring_register_probe(io_uring ring, io_uring_probe p, unsigned int nr) 17 | -------------------------------------------------------------------------------- /src/liburing/probe.py: -------------------------------------------------------------------------------- 1 | from liburing import io_uring_op, io_uring_get_probe, io_uring_opcode_supported, io_uring_free_probe 2 | 3 | 4 | def probe() -> dict: 5 | ''' Probe your system to find out which `io_uring` operations are available. 6 | 7 | Example 8 | >>> probe() 9 | {'IORING_OP_NOP': True, ...} 10 | 11 | # or 12 | 13 | >>> for op, supported in probe().items(): 14 | ... op, supported 15 | IORING_OP_NOP True 16 | ... 17 | ''' 18 | r = {} 19 | p = io_uring_get_probe() 20 | try: 21 | for i in io_uring_op: 22 | if i.name != 'IORING_OP_LAST': 23 | r[i.name] = io_uring_opcode_supported(p, i) 24 | finally: 25 | io_uring_free_probe(p) 26 | return r 27 | -------------------------------------------------------------------------------- /src/liburing/probe.pyx: -------------------------------------------------------------------------------- 1 | cdef class io_uring_probe: 2 | 3 | def __cinit__(self, unsigned int num=0): 4 | if num: 5 | self.ptr = <__io_uring_probe*>PyMem_RawCalloc( 6 | num, sizeof(__io_uring_probe) + num * sizeof(__io_uring_probe_op) 7 | ) 8 | if self.ptr is NULL: 9 | memory_error(self) 10 | self.len = num 11 | 12 | def __dealloc__(self): 13 | if self.len and self.ptr is not NULL: 14 | PyMem_RawFree(self.ptr) 15 | self.ptr = NULL 16 | 17 | @property 18 | def last_op(self): 19 | if self.ptr is not NULL: 20 | return self.ptr.last_op 21 | memory_error(self) 22 | 23 | @property 24 | def ops_len(self): 25 | if self.ptr is not NULL: 26 | return self.ptr.ops_len 27 | memory_error(self) 28 | 29 | cpdef io_uring_probe io_uring_get_probe_ring(io_uring ring): 30 | cdef io_uring_probe probe = io_uring_probe() 31 | probe.ptr = __io_uring_get_probe_ring(&ring.ptr) 32 | return probe 33 | 34 | cpdef io_uring_probe io_uring_get_probe(): 35 | cdef io_uring_probe probe = io_uring_probe() 36 | probe.ptr = __io_uring_get_probe() 37 | return probe 38 | 39 | cpdef void io_uring_free_probe(io_uring_probe probe): 40 | __io_uring_free_probe(probe.ptr) 41 | probe.ptr = NULL 42 | 43 | cpdef inline bool io_uring_opcode_supported(io_uring_probe p, int op): 44 | return __io_uring_opcode_supported(p.ptr, op) 45 | 46 | cpdef int io_uring_register_probe(io_uring ring, io_uring_probe p, unsigned int nr): 47 | return trap_error(__io_uring_register_probe(&ring.ptr, p.ptr, nr)) 48 | -------------------------------------------------------------------------------- /src/liburing/queue.pxd: -------------------------------------------------------------------------------- 1 | from cpython.mem cimport PyMem_RawCalloc, PyMem_RawFree 2 | from cpython.ref cimport Py_INCREF, Py_DECREF 3 | from .lib.uring cimport * 4 | from .error cimport trap_error, memory_error, index_error 5 | from .time cimport timespec 6 | 7 | 8 | cdef class io_uring: 9 | cdef: 10 | __io_uring ptr 11 | bint active 12 | 13 | 14 | cdef class io_uring_sqe: 15 | cdef: 16 | __io_uring_sqe* ptr 17 | __u16 len 18 | list[object] ref 19 | 20 | 21 | cdef class io_uring_cqe: 22 | cdef: 23 | __io_uring_cqe* ptr 24 | 25 | inline tuple[__s32, __u64] get_index(self, unsigned int index) noexcept nogil 26 | 27 | cdef class io_uring_reg_wait: 28 | cdef __io_uring_reg_wait* ptr 29 | 30 | 31 | cdef class io_uring_params: 32 | cdef __io_uring_params* ptr 33 | 34 | 35 | cdef class io_uring_buf_ring: 36 | cdef __io_uring_buf_ring* ptr 37 | 38 | 39 | cdef class siginfo: 40 | cdef siginfo_t* ptr 41 | 42 | 43 | cdef class sigset: 44 | cdef sigset_t* ptr 45 | 46 | 47 | cpdef int io_uring_queue_init_mem(unsigned int entries, 48 | io_uring ring, 49 | io_uring_params p, 50 | unsigned char[:] buf, 51 | size_t buf_size) 52 | cpdef int io_uring_queue_init_params(unsigned int entries, 53 | io_uring ring, 54 | io_uring_params p) nogil 55 | cpdef int io_uring_queue_init(unsigned int entries, 56 | io_uring ring, 57 | unsigned int flags=?) nogil 58 | cpdef int io_uring_queue_mmap(int fd, 59 | io_uring_params p, 60 | io_uring ring) nogil 61 | cpdef int io_uring_ring_dontfork(io_uring ring) nogil 62 | cpdef int io_uring_queue_exit(io_uring ring) nogil 63 | cpdef unsigned int io_uring_peek_batch_cqe(io_uring ring, 64 | io_uring_cqe cqes, 65 | unsigned int count) nogil 66 | cpdef int io_uring_wait_cqes(io_uring ring, 67 | io_uring_cqe cqe_ptr, 68 | unsigned int wait_nr, 69 | timespec ts=?, 70 | sigset sigmask=?) nogil 71 | cpdef int io_uring_wait_cqes_min_timeout(io_uring ring, 72 | io_uring_cqe cqe_ptr, 73 | unsigned int wait_nr, 74 | timespec ts, 75 | unsigned int min_ts_usec, 76 | sigset sigmask=?) nogil 77 | cpdef int io_uring_wait_cqe_timeout(io_uring ring, io_uring_cqe cqe_ptr, timespec ts) nogil 78 | cpdef int io_uring_submit(io_uring ring) nogil 79 | cpdef int io_uring_submit_and_wait(io_uring ring, unsigned int wait_nr) nogil 80 | cpdef int io_uring_submit_and_wait_timeout(io_uring ring, 81 | io_uring_cqe cqe_ptr, 82 | unsigned int wait_nr, 83 | timespec ts=?, 84 | sigset sigmask=?) nogil 85 | cpdef int io_uring_submit_and_wait_min_timeout(io_uring ring, 86 | io_uring_cqe cqe_ptr, 87 | unsigned int wait_nr, 88 | timespec ts, 89 | unsigned int min_wait, 90 | sigset sigmask=?) nogil 91 | cpdef int io_uring_submit_and_wait_reg(io_uring ring, io_uring_cqe cqe_ptr, unsigned int wait_nr, int reg_index) nogil 92 | cpdef int io_uring_register_wait_reg(io_uring ring, io_uring_reg_wait reg, int nr) nogil 93 | cpdef int io_uring_resize_rings(io_uring ring, io_uring_params p) nogil 94 | cpdef int io_uring_clone_buffers_offset(io_uring dst, 95 | io_uring src, 96 | unsigned int dst_off, 97 | unsigned int src_off, 98 | unsigned int nr, 99 | unsigned int flags) nogil 100 | cpdef int io_uring_clone_buffers(io_uring dst, io_uring src) nogil 101 | 102 | cpdef int io_uring_enable_rings(io_uring ring) nogil 103 | cpdef int io_uring_close_ring_fd(io_uring ring) nogil 104 | 105 | cpdef int io_uring_get_events(io_uring ring) nogil 106 | cpdef int io_uring_submit_and_get_events(io_uring ring) nogil 107 | 108 | cpdef int io_uring_buf_ring_head(io_uring ring, int buf_group, uint16_t head) nogil 109 | 110 | cpdef void io_uring_cq_advance(io_uring ring, 111 | unsigned int nr) noexcept nogil 112 | cpdef void io_uring_cqe_seen(io_uring ring, 113 | io_uring_cqe nr) noexcept nogil 114 | 115 | # Command prep helpers 116 | # -------------------- 117 | cpdef void io_uring_sqe_set_data(io_uring_sqe sqe, 118 | object obj) 119 | cpdef object io_uring_cqe_get_data(io_uring_cqe cqe) 120 | cpdef void io_uring_sqe_set_data64(io_uring_sqe sqe, 121 | __u64 data) noexcept nogil 122 | cpdef __u64 io_uring_cqe_get_data64(io_uring_cqe cqe) noexcept nogil 123 | cpdef void io_uring_sqe_set_flags(io_uring_sqe sqe, 124 | unsigned int flags) noexcept nogil 125 | 126 | cpdef void io_uring_prep_nop(io_uring_sqe sqe) noexcept nogil 127 | cpdef void io_uring_prep_cancel64(io_uring_sqe sqe, 128 | __u64 user_data, 129 | int flags) noexcept nogil 130 | cpdef void io_uring_prep_cancel(io_uring_sqe sqe, 131 | object user_data, 132 | int flags) noexcept 133 | cpdef void io_uring_prep_cancel_fd(io_uring_sqe sqe, 134 | int fd, 135 | unsigned int flags) noexcept nogil 136 | 137 | cpdef void io_uring_prep_waitid(io_uring_sqe sqe, 138 | idtype_t idtype, 139 | id_t id, 140 | siginfo infop, 141 | int options, 142 | unsigned int flags) noexcept nogil 143 | cpdef void io_uring_prep_fixed_fd_install(io_uring_sqe sqe, 144 | int fd, 145 | unsigned int flags) noexcept nogil 146 | 147 | cpdef unsigned int io_uring_sq_ready(io_uring ring) noexcept nogil 148 | cpdef unsigned int io_uring_sq_space_left(io_uring ring) noexcept nogil 149 | cpdef int io_uring_sqring_wait(io_uring ring) noexcept nogil 150 | cpdef unsigned int io_uring_cq_ready(io_uring ring) noexcept nogil 151 | cpdef bool io_uring_cq_has_overflow(io_uring ring) noexcept nogil 152 | cpdef bool io_uring_cq_eventfd_enabled(io_uring ring) noexcept nogil 153 | 154 | cpdef int io_uring_cq_eventfd_toggle(io_uring ring, 155 | bool enabled) noexcept nogil 156 | cpdef int io_uring_wait_cqe_nr(io_uring ring, 157 | io_uring_cqe cqe_ptr, 158 | unsigned int wait_nr) noexcept nogil 159 | cpdef int io_uring_peek_cqe(io_uring ring, 160 | io_uring_cqe cqe_ptr) noexcept nogil 161 | cpdef int io_uring_wait_cqe(io_uring ring, 162 | io_uring_cqe cqe_ptr) noexcept nogil 163 | 164 | cpdef int io_uring_buf_ring_mask(__u32 ring_entries) noexcept nogil 165 | cpdef void io_uring_buf_ring_init(io_uring_buf_ring br) noexcept nogil 166 | cpdef void io_uring_buf_ring_add(io_uring_buf_ring br, 167 | unsigned char[:] addr, 168 | unsigned int len, 169 | unsigned short bid, 170 | int mask, 171 | int buf_offset) noexcept nogil 172 | cpdef void io_uring_buf_ring_advance(io_uring_buf_ring br, 173 | int count) noexcept nogil 174 | cpdef void io_uring_buf_ring_cq_advance(io_uring ring, 175 | io_uring_buf_ring br, 176 | int count) noexcept nogil 177 | cpdef int io_uring_buf_ring_available(io_uring ring, 178 | io_uring_buf_ring br, 179 | unsigned short bgid) noexcept nogil 180 | 181 | cpdef io_uring_sqe io_uring_get_sqe(io_uring ring) 182 | 183 | cpdef unsigned int io_uring_for_each_cqe(io_uring ring, io_uring_cqe cqe) noexcept nogil 184 | 185 | 186 | cpdef enum __queue_define__: 187 | LIBURING_UDATA_TIMEOUT = __LIBURING_UDATA_TIMEOUT 188 | # sqe.flags 189 | IOSQE_FIXED_FILE = __IOSQE_FIXED_FILE 190 | IOSQE_IO_DRAIN = __IOSQE_IO_DRAIN 191 | IOSQE_IO_LINK = __IOSQE_IO_LINK 192 | IOSQE_IO_HARDLINK = __IOSQE_IO_HARDLINK 193 | IOSQE_ASYNC = __IOSQE_ASYNC 194 | IOSQE_BUFFER_SELECT = __IOSQE_BUFFER_SELECT 195 | IOSQE_CQE_SKIP_SUCCESS = __IOSQE_CQE_SKIP_SUCCESS 196 | -------------------------------------------------------------------------------- /src/liburing/register.pxd: -------------------------------------------------------------------------------- 1 | from cpython.array cimport array 2 | from .lib.uring cimport * 3 | from .error cimport trap_error 4 | from .common cimport iovec 5 | from .queue cimport io_uring 6 | 7 | 8 | cdef class io_uring_restriction: 9 | cdef __io_uring_restriction * ptr 10 | 11 | cdef class io_uring_buf_reg: 12 | cdef __io_uring_buf_reg * ptr 13 | 14 | cdef class io_uring_sync_cancel_reg: 15 | cdef __io_uring_sync_cancel_reg * ptr 16 | 17 | cdef class io_uring_napi: 18 | cdef __io_uring_napi * ptr 19 | 20 | cdef class io_uring_mem_region_reg: 21 | cdef __io_uring_mem_region_reg * ptr 22 | 23 | 24 | cpdef int io_uring_register_buffers(io_uring ring, 25 | iovec iovecs, 26 | unsigned int nr_iovecs) nogil 27 | cpdef int io_uring_register_buffers_tags(io_uring ring, 28 | iovec iovecs, 29 | const __u64 tags, 30 | unsigned int nr) nogil 31 | cpdef int io_uring_register_buffers_sparse(io_uring ring, 32 | unsigned int nr) nogil 33 | cpdef int io_uring_register_buffers_update_tag(io_uring ring, 34 | unsigned int off, 35 | iovec iovecs, 36 | const __u64 tags, 37 | unsigned int nr) nogil 38 | cpdef int io_uring_unregister_buffers(io_uring ring) nogil 39 | 40 | cpdef int io_uring_register_eventfd(io_uring ring, int fd) nogil 41 | cpdef int io_uring_register_eventfd_async(io_uring ring, int fd) nogil 42 | cpdef int io_uring_unregister_eventfd(io_uring ring) nogil 43 | 44 | cpdef int io_uring_register_personality(io_uring ring) nogil 45 | cpdef int io_uring_unregister_personality(io_uring ring, int id) nogil 46 | cpdef int io_uring_register_restrictions(io_uring ring, 47 | io_uring_restriction res, 48 | unsigned int nr_res) nogil 49 | 50 | cpdef int io_uring_register_iowq_aff(io_uring ring, 51 | size_t cpusz, 52 | const cpu_set_t mask) nogil 53 | cpdef int io_uring_unregister_iowq_aff(io_uring ring) nogil 54 | cpdef int io_uring_register_iowq_max_workers(io_uring ring, unsigned int values) nogil 55 | cpdef int io_uring_register_ring_fd(io_uring ring) nogil 56 | cpdef int io_uring_unregister_ring_fd(io_uring ring) nogil 57 | 58 | cpdef int io_uring_register_buf_ring(io_uring ring, 59 | io_uring_buf_reg reg, 60 | unsigned int flags) nogil 61 | cpdef int io_uring_unregister_buf_ring(io_uring ring, 62 | int bgid) nogil 63 | cpdef int io_uring_register_sync_cancel(io_uring ring, 64 | io_uring_sync_cancel_reg reg) nogil 65 | 66 | cpdef int io_uring_register_file_alloc_range(io_uring ring, 67 | unsigned int off, 68 | unsigned int len) 69 | 70 | cpdef int io_uring_register_napi(io_uring ring, io_uring_napi napi) nogil 71 | cpdef int io_uring_unregister_napi(io_uring ring, io_uring_napi napi) nogil 72 | 73 | 74 | cpdef int io_uring_register_files(io_uring ring, list[int] fds) 75 | cpdef int io_uring_register_files_tags(io_uring ring, 76 | int files, 77 | __u64 tags, 78 | unsigned int nr) nogil 79 | cpdef int io_uring_register_files_sparse(io_uring ring, 80 | unsigned int nr) nogil 81 | cpdef int io_uring_register_files_update_tag(io_uring ring, 82 | unsigned int off, 83 | int files, 84 | __u64 tags, 85 | unsigned int nr_files) nogil 86 | cpdef int io_uring_unregister_files(io_uring ring) nogil 87 | cpdef int io_uring_register_files_update(io_uring ring, 88 | unsigned int off, 89 | int files, 90 | unsigned int nr_files) nogil 91 | 92 | cpdef int io_uring_register_region(io_uring ring, io_uring_mem_region_reg reg) nogil 93 | -------------------------------------------------------------------------------- /src/liburing/register.pyx: -------------------------------------------------------------------------------- 1 | cpdef int io_uring_register_buffers(io_uring ring, 2 | iovec iovecs, 3 | unsigned int nr_iovecs) nogil: 4 | return trap_error(__io_uring_register_buffers(&ring.ptr, iovecs.ptr, nr_iovecs)) 5 | 6 | cpdef int io_uring_register_buffers_tags(io_uring ring, 7 | iovec iovecs, 8 | const __u64 tags, 9 | unsigned int nr) nogil: 10 | return trap_error(__io_uring_register_buffers_tags(&ring.ptr, iovecs.ptr, &tags, nr)) 11 | 12 | cpdef int io_uring_register_buffers_sparse(io_uring ring, 13 | unsigned int nr) nogil: 14 | return trap_error(__io_uring_register_buffers_sparse(&ring.ptr, nr)) 15 | 16 | cpdef int io_uring_register_buffers_update_tag(io_uring ring, 17 | unsigned int off, 18 | iovec iovecs, 19 | const __u64 tags, 20 | unsigned int nr) nogil: 21 | return trap_error(__io_uring_register_buffers_update_tag(&ring.ptr, off, iovecs.ptr, &tags, nr)) 22 | 23 | cpdef int io_uring_unregister_buffers(io_uring ring) nogil: 24 | return trap_error(__io_uring_unregister_buffers(&ring.ptr)) 25 | 26 | 27 | cpdef int io_uring_register_eventfd(io_uring ring, int fd) nogil: 28 | return trap_error(__io_uring_register_eventfd(&ring.ptr, fd)) 29 | 30 | cpdef int io_uring_register_eventfd_async(io_uring ring, int fd) nogil: 31 | return trap_error(__io_uring_register_eventfd_async(&ring.ptr, fd)) 32 | 33 | cpdef int io_uring_unregister_eventfd(io_uring ring) nogil: 34 | return trap_error(__io_uring_unregister_eventfd(&ring.ptr)) 35 | 36 | cpdef int io_uring_register_personality(io_uring ring) nogil: 37 | return trap_error(__io_uring_register_personality(&ring.ptr)) 38 | 39 | cpdef int io_uring_unregister_personality(io_uring ring, int id) nogil: 40 | return trap_error(__io_uring_unregister_personality(&ring.ptr, id)) 41 | 42 | cpdef int io_uring_register_restrictions(io_uring ring, 43 | io_uring_restriction res, 44 | unsigned int nr_res) nogil: 45 | return trap_error(__io_uring_register_restrictions(&ring.ptr, res.ptr, nr_res)) 46 | 47 | 48 | cpdef int io_uring_register_iowq_aff(io_uring ring, 49 | size_t cpusz, 50 | const cpu_set_t mask) nogil: 51 | return trap_error(__io_uring_register_iowq_aff(&ring.ptr, cpusz, &mask)) 52 | 53 | cpdef int io_uring_unregister_iowq_aff(io_uring ring) nogil: 54 | return trap_error(__io_uring_unregister_iowq_aff(&ring.ptr)) 55 | 56 | cpdef int io_uring_register_iowq_max_workers(io_uring ring, unsigned int values) nogil: 57 | return trap_error(__io_uring_register_iowq_max_workers(&ring.ptr, &values)) 58 | 59 | cpdef int io_uring_register_ring_fd(io_uring ring) nogil: 60 | return trap_error(__io_uring_register_ring_fd(&ring.ptr)) 61 | 62 | cpdef int io_uring_unregister_ring_fd(io_uring ring) nogil: 63 | return trap_error(__io_uring_unregister_ring_fd(&ring.ptr)) 64 | 65 | cpdef int io_uring_register_buf_ring(io_uring ring, 66 | io_uring_buf_reg reg, 67 | unsigned int flags) nogil: 68 | return trap_error(__io_uring_register_buf_ring(&ring.ptr, reg.ptr, flags)) 69 | 70 | cpdef int io_uring_unregister_buf_ring(io_uring ring, 71 | int bgid) nogil: 72 | return trap_error(__io_uring_unregister_buf_ring(&ring.ptr, bgid)) 73 | 74 | cpdef int io_uring_register_sync_cancel(io_uring ring, 75 | io_uring_sync_cancel_reg reg) nogil: 76 | return trap_error(__io_uring_register_sync_cancel(&ring.ptr, reg.ptr)) 77 | 78 | 79 | cpdef int io_uring_register_file_alloc_range(io_uring ring, 80 | unsigned int off, 81 | unsigned int len): 82 | return trap_error(__io_uring_register_file_alloc_range(&ring.ptr, off, len)) 83 | 84 | 85 | cpdef int io_uring_register_napi(io_uring ring, io_uring_napi napi) nogil: 86 | return trap_error(__io_uring_register_napi(&ring.ptr, napi.ptr)) 87 | 88 | cpdef int io_uring_unregister_napi(io_uring ring, io_uring_napi napi) nogil: 89 | return trap_error(__io_uring_unregister_napi(&ring.ptr, napi.ptr)) 90 | 91 | 92 | cpdef int io_uring_register_files(io_uring ring, list[int] fds): 93 | ''' Register File Descriptor 94 | 95 | Example 96 | >>> fds = [1, 2, 3] 97 | >>> io_uring_register_files(ring, fds) 98 | ... 99 | >>> io_uring_unregister_files(ring) 100 | 101 | Note 102 | "Registered files have less overhead per operation than normal files. 103 | This is due to the kernel grabbing a reference count on a file when an 104 | operation begins, and dropping it when it's done. When the process file 105 | table is shared, for example if the process has ever created any 106 | threads, then this cost goes up even more. Using registered files 107 | reduces the overhead of file reference management across requests that 108 | operate on a file." 109 | ''' 110 | cdef array[int] _fds = array('i', fds) 111 | return trap_error(__io_uring_register_files(&ring.ptr, _fds.data.as_ints, len(_fds))) 112 | 113 | cpdef int io_uring_register_files_tags(io_uring ring, 114 | int files, 115 | __u64 tags, 116 | unsigned int nr) nogil: 117 | return trap_error(__io_uring_register_files_tags(&ring.ptr, &files, &tags, nr)) 118 | 119 | cpdef int io_uring_register_files_sparse(io_uring ring, 120 | unsigned int nr) nogil: 121 | return trap_error(__io_uring_register_files_sparse(&ring.ptr, nr)) 122 | 123 | cpdef int io_uring_register_files_update_tag(io_uring ring, 124 | unsigned int off, 125 | int files, 126 | __u64 tags, 127 | unsigned int nr_files) nogil: 128 | return trap_error(__io_uring_register_files_update_tag(&ring.ptr, off, &files, &tags, nr_files)) 129 | 130 | cpdef int io_uring_unregister_files(io_uring ring) nogil: 131 | ''' Unregister All File Descriptor(s) ''' 132 | return trap_error(__io_uring_unregister_files(&ring.ptr)) 133 | 134 | cpdef int io_uring_register_files_update(io_uring ring, 135 | unsigned int off, 136 | int files, 137 | unsigned int nr_files) nogil: 138 | return trap_error(__io_uring_register_files_update(&ring.ptr, off, &files, nr_files)) 139 | 140 | cpdef int io_uring_register_region(io_uring ring, io_uring_mem_region_reg reg) nogil: 141 | return trap_error(__io_uring_register_region(&ring.ptr, reg.ptr)) 142 | -------------------------------------------------------------------------------- /src/liburing/socket_extra.pxd: -------------------------------------------------------------------------------- 1 | from libc.string cimport memset 2 | from .lib.socket cimport * 3 | from .socket cimport * 4 | from .error cimport trap_error 5 | 6 | 7 | cdef class getaddrinfo: 8 | cdef __addrinfo* ptr 9 | 10 | 11 | cpdef int getpeername(int sockfd, sockaddr addr) nogil 12 | cpdef tuple getsockname(int sockfd, sockaddr addr) 13 | 14 | cpdef tuple getnameinfo(sockaddr addr, int flags=?) 15 | cpdef bint isIP(sa_family_t family, char* value) noexcept nogil 16 | 17 | cpdef enum __extra_define__: 18 | # getaddrinfo/getnameinfo start >>> 19 | AI_PASSIVE = __AI_PASSIVE 20 | AI_CANONNAME = __AI_CANONNAME 21 | AI_NUMERICHOST = __AI_NUMERICHOST 22 | AI_V4MAPPED = __AI_V4MAPPED 23 | AI_ALL = __AI_ALL 24 | AI_ADDRCONFIG = __AI_ADDRCONFIG 25 | AI_IDN = __AI_IDN 26 | AI_CANONIDN = __AI_CANONIDN 27 | AI_NUMERICSERV = __AI_NUMERICSERV 28 | 29 | NI_NUMERICHOST = __NI_NUMERICHOST 30 | NI_NUMERICSERV = __NI_NUMERICSERV 31 | NI_NOFQDN = __NI_NOFQDN 32 | NI_NAMEREQD = __NI_NAMEREQD 33 | NI_DGRAM = __NI_DGRAM 34 | NI_IDN = __NI_IDN 35 | # getaddrinfo/getnameinfo end <<< 36 | -------------------------------------------------------------------------------- /src/liburing/socket_extra.pyx: -------------------------------------------------------------------------------- 1 | cdef class getaddrinfo: 2 | def __cinit__(self, const char* host, char* port_service, 3 | int family=0, int type=0, int proto=0, int flags=0): 4 | ''' 5 | Example 6 | >>> for af_, sock_, proto, canon, addr in getaddrinfo(b'127.0.0.1', b'12345'): 7 | ... ... 8 | ... io_uring_prep_socket(sqe, af_, sock_) 9 | ... ... 10 | ... io_uring_prep_connect(sqe, sockfd, addr) 11 | ... ... 12 | ... break # if connected successfully, break, else try next. 13 | ''' 14 | # TODO: `port_service` should handle both `int` & `bytes` types. 15 | cdef: 16 | int no 17 | __addrinfo hints 18 | 19 | memset(&hints, 0, sizeof(__addrinfo)) 20 | hints.ai_flags = flags 21 | hints.ai_family = family 22 | hints.ai_socktype = type 23 | hints.ai_protocol = proto 24 | if no := __getaddrinfo(host, port_service, &hints, &self.ptr): 25 | trap_error(no, __gai_strerror(no).decode()) 26 | 27 | def __dealloc__(self): 28 | if self.ptr is not NULL: 29 | __freeaddrinfo(self.ptr) 30 | self.ptr = NULL 31 | 32 | def __iter__(self): 33 | cdef: 34 | __addrinfo* p = self.ptr 35 | sockaddr addr 36 | while p.ai_next is not NULL: 37 | addr = sockaddr() 38 | addr.ptr = p.ai_addr 39 | addr.family = p.ai_family 40 | addr.sizeof = p.ai_addrlen 41 | yield ( 42 | p.ai_family, 43 | p.ai_socktype, 44 | p.ai_protocol, 45 | p.ai_canonname or b'', 46 | addr 47 | ) 48 | p = p.ai_next 49 | 50 | 51 | cpdef int getpeername(int sockfd, sockaddr addr) nogil: 52 | ''' TODO ''' 53 | return trap_error(__getpeername(sockfd, <__sockaddr*>addr.ptr, &addr.sizeof)) 54 | 55 | cpdef tuple getsockname(int sockfd, sockaddr addr): 56 | ''' 57 | Example 58 | >>> addr = sockaddr(b'127.0.0.1', 0) 59 | >>> bind(sockfd, addr) 60 | >>> getsockname(sockfd, addr) 61 | b'127.0.0.1', 6744 # random port 62 | 63 | Note 64 | - if port is `0` & `bind` is used, socket will get assigned random port by OS. 65 | ''' 66 | cdef: 67 | __sockaddr_in ptr4 68 | __sockaddr_in6 ptr6 69 | socklen_t size 70 | uint16_t port 71 | char* ip 72 | char ip4_char[__INET_ADDRSTRLEN] 73 | char ip6_char[__INET6_ADDRSTRLEN] 74 | 75 | if addr.family == __AF_INET: 76 | ip = ip4_char 77 | size = sizeof(__sockaddr_in) 78 | memset(&ptr4, 0, size) 79 | trap_error(__getsockname(sockfd, <__sockaddr*>&ptr4, &size)) 80 | __inet_ntop(addr.family, &ptr4.sin_addr, ip, __INET_ADDRSTRLEN) 81 | port = __htons(ptr4.sin_port) 82 | 83 | elif addr.family == __AF_INET6: 84 | ip = ip6_char 85 | size = sizeof(__sockaddr_in6) 86 | memset(&ptr6, 0, size) 87 | trap_error(__getsockname(sockfd, <__sockaddr*>&ptr6, &size)) 88 | __inet_ntop(addr.family, &ptr6.sin6_addr, ip, __INET6_ADDRSTRLEN) 89 | port = __htons(ptr6.sin6_port) 90 | else: 91 | raise TypeError('getsockname() - received `addr.family` type not supported!') 92 | return (ip, port) 93 | 94 | cpdef tuple getnameinfo(sockaddr addr, int flags=0): 95 | ''' 96 | Example 97 | >>> addr = sockaddr(liburing.AF_INET, b'0.0.0.0', 12345) 98 | >>> getnameinfo(addr, liburing.NI_NUMERICHOST | liburing.NI_NUMERICSERV) 99 | (b'0.0.0.0', b'12345') 100 | 101 | Flags 102 | NI_NUMERICHOST # Don't try to look up hostname. 103 | NI_NUMERICSERV # Don't convert port number to name. 104 | NI_NOFQDN # Only return nodename portion. 105 | NI_NAMEREQD # Don't return numeric addresses. 106 | NI_DGRAM # Look up UDP service rather than TCP. 107 | NI_IDN # Convert name from IDN format. 108 | 109 | Note 110 | - return type will depend on content being returned. 111 | ''' 112 | cdef: 113 | char host[__NI_MAXHOST] 114 | char service[__NI_MAXSERV] 115 | 116 | trap_error(__getnameinfo(<__sockaddr*>addr.ptr, addr.sizeof, 117 | host, sizeof(host), service, sizeof(service), flags)) 118 | return (host, int(service) if service.isdigit() else service) 119 | 120 | 121 | cpdef bint isIP(sa_family_t family, char* value) noexcept nogil: 122 | ''' 123 | Example 124 | >>> isIP(AF_INET, b'0.0.0.0') 125 | True 126 | >>> isIP(AF_INET6, b'::1') 127 | True 128 | >>> isIP(AF_INET6, b'domain.ext') 129 | False 130 | >>> isIP(AF_INET, b'domain.ext') 131 | False 132 | >>> isIP(AF_UNIX, b'/path/socket') 133 | False 134 | ''' 135 | cdef: 136 | __sockaddr_in ptr_in 137 | __sockaddr_in6 ptr_in6 138 | 139 | if family == __AF_INET: 140 | return __inet_pton(family, value, &ptr_in.sin_addr) 141 | elif family == __AF_INET6: 142 | return __inet_pton(family, value, &ptr_in6.sin6_addr) 143 | else: 144 | return False 145 | -------------------------------------------------------------------------------- /src/liburing/statx.pxd: -------------------------------------------------------------------------------- 1 | from cpython.mem cimport PyMem_RawCalloc, PyMem_RawFree 2 | from .error cimport memory_error 3 | from .queue cimport * 4 | 5 | 6 | cdef class statx: 7 | cdef __statx* ptr 8 | 9 | 10 | cpdef void io_uring_prep_statx(io_uring_sqe sqe, 11 | statx statxbuf, 12 | const char *path, 13 | int flags=?, 14 | unsigned int mask=?, 15 | int dfd=?) noexcept nogil 16 | 17 | 18 | cpdef enum __statx_define__: 19 | # defines 20 | STATX_TYPE = __STATX_TYPE 21 | STATX_MODE = __STATX_MODE 22 | STATX_NLINK = __STATX_NLINK 23 | STATX_UID = __STATX_UID 24 | STATX_GID = __STATX_GID 25 | STATX_ATIME = __STATX_ATIME 26 | STATX_MTIME = __STATX_MTIME 27 | STATX_CTIME = __STATX_CTIME 28 | STATX_INO = __STATX_INO 29 | STATX_SIZE = __STATX_SIZE 30 | STATX_BLOCKS = __STATX_BLOCKS 31 | STATX_BASIC_STATS = __STATX_BASIC_STATS 32 | STATX_BTIME = __STATX_BTIME 33 | STATX_MNT_ID = __STATX_MNT_ID 34 | # note: not supported 35 | # STATX_DIOALIGN = __STATX_DIOALIGN 36 | 37 | STATX_ATTR_COMPRESSED = __STATX_ATTR_COMPRESSED 38 | STATX_ATTR_IMMUTABLE = __STATX_ATTR_IMMUTABLE 39 | STATX_ATTR_APPEND = __STATX_ATTR_APPEND 40 | STATX_ATTR_NODUMP = __STATX_ATTR_NODUMP 41 | STATX_ATTR_ENCRYPTED = __STATX_ATTR_ENCRYPTED 42 | STATX_ATTR_AUTOMOUNT = __STATX_ATTR_AUTOMOUNT 43 | STATX_ATTR_MOUNT_ROOT = __STATX_ATTR_MOUNT_ROOT 44 | STATX_ATTR_VERITY = __STATX_ATTR_VERITY 45 | STATX_ATTR_DAX = __STATX_ATTR_DAX 46 | 47 | S_IFMT = __S_IFMT 48 | 49 | S_IFSOCK = __S_IFSOCK 50 | S_IFLNK = __S_IFLNK 51 | S_IFREG = __S_IFREG 52 | S_IFBLK = __S_IFBLK 53 | S_IFDIR = __S_IFDIR 54 | S_IFCHR = __S_IFCHR 55 | S_IFIFO = __S_IFIFO 56 | 57 | S_ISUID = __S_ISUID 58 | S_ISGID = __S_ISGID 59 | S_ISVTX = __S_ISVTX 60 | 61 | S_IRWXU = __S_IRWXU 62 | S_IRUSR = __S_IRUSR 63 | S_IWUSR = __S_IWUSR 64 | S_IXUSR = __S_IXUSR 65 | 66 | S_IRWXG = __S_IRWXG 67 | S_IRGRP = __S_IRGRP 68 | S_IWGRP = __S_IWGRP 69 | S_IXGRP = __S_IXGRP 70 | 71 | S_IRWXO = __S_IRWXO 72 | S_IROTH = __S_IROTH 73 | S_IWOTH = __S_IWOTH 74 | S_IXOTH = __S_IXOTH 75 | 76 | # AT_STATX_SYNC_TYPE = __AT_STATX_SYNC_TYPE # skipping: not documented 77 | AT_STATX_SYNC_AS_STAT = __AT_STATX_SYNC_AS_STAT 78 | AT_STATX_FORCE_SYNC = __AT_STATX_FORCE_SYNC 79 | AT_STATX_DONT_SYNC = __AT_STATX_DONT_SYNC 80 | -------------------------------------------------------------------------------- /src/liburing/statx.pyx: -------------------------------------------------------------------------------- 1 | cdef class statx: 2 | ''' Structures for the extended file attribute retrieval system call `statx()`. 3 | 4 | Example 5 | >>> ring = io_uring() 6 | ... ... 7 | >>> stat = statx() 8 | >>> path = __file__.encode() 9 | >>> if sqe := io_uring_get_sqe(ring) 10 | ... io_uring_prep_statx(sqe, stat, path) 11 | ... ... 12 | >>> stat.isfile 13 | True 14 | >>> stat.size 15 | 123 16 | 17 | Note 18 | - For more information visit: 19 | https://man7.org/linux/man-pages/man2/statx.2.html 20 | https://man7.org/linux/man-pages/man7/inode.7.html 21 | ''' 22 | def __cinit__(self): 23 | self.ptr = <__statx*>PyMem_RawCalloc(1, sizeof(__statx)) 24 | if self.ptr is NULL: 25 | memory_error(self) 26 | 27 | def __dealloc__(self): 28 | if self.ptr is not NULL: 29 | PyMem_RawFree(self.ptr) 30 | self.ptr = NULL 31 | 32 | @property 33 | def stx_mask(self): 34 | return self.ptr.stx_mask 35 | 36 | @property 37 | def stx_blksize(self): 38 | return self.ptr.stx_blksize 39 | 40 | @property 41 | def stx_attributes(self): 42 | return self.ptr.stx_attributes 43 | 44 | @property 45 | def stx_nlink(self): 46 | return self.ptr.stx_nlink 47 | 48 | @property 49 | def stx_uid(self): 50 | return self.ptr.stx_uid 51 | 52 | @property 53 | def stx_gid(self): 54 | return self.ptr.stx_gid 55 | 56 | @property 57 | def stx_mode(self): 58 | return self.ptr.stx_mode 59 | 60 | @property 61 | def stx_ino(self): 62 | return self.ptr.stx_ino 63 | 64 | @property 65 | def stx_size(self): 66 | return self.ptr.stx_size 67 | 68 | @property 69 | def stx_blocks(self): 70 | return self.ptr.stx_blocks 71 | 72 | @property 73 | def stx_attributes_mask(self): 74 | return self.ptr.stx_attributes_mask 75 | 76 | # Timestamps 77 | # ---------- 78 | @property 79 | def stx_atime(self) -> float: 80 | ''' The file's last access timestamp. ''' 81 | return self.ptr.stx_atime.tv_sec + (self.ptr.stx_atime.tv_nsec * 0.000_000_001) 82 | 83 | @property 84 | def stx_btime(self) -> float: 85 | ''' The file's creation timestamp. ''' 86 | return self.ptr.stx_btime.tv_sec + (self.ptr.stx_btime.tv_nsec * 0.000_000_001) 87 | 88 | @property 89 | def stx_ctime(self) -> float: 90 | ''' The file's last status change timestamp. ''' 91 | return self.ptr.stx_ctime.tv_sec + (self.ptr.stx_ctime.tv_nsec * 0.000_000_001) 92 | 93 | @property 94 | def stx_mtime(self) -> float: 95 | ''' The file's last modification timestamp. ''' 96 | return self.ptr.stx_mtime.tv_sec + (self.ptr.stx_mtime.tv_nsec * 0.000_000_001) 97 | 98 | # ID 99 | # -- 100 | @property 101 | def stx_rdev_major(self): 102 | return self.ptr.stx_rdev_major 103 | 104 | @property 105 | def stx_rdev_minor(self): 106 | return self.ptr.stx_rdev_minor 107 | 108 | @property 109 | def stx_dev_major(self): 110 | return self.ptr.stx_dev_major 111 | 112 | @property 113 | def stx_dev_minor(self): 114 | return self.ptr.stx_dev_minor 115 | 116 | @property 117 | def stx_mnt_id(self): 118 | return self.ptr.stx_mnt_id 119 | 120 | # note: not supported 121 | # @property 122 | # def stx_dio_mem_align(self): 123 | # return self.ptr.stx_dio_mem_align 124 | 125 | # @property 126 | # def stx_dio_offset_align(self): 127 | # return self.ptr.stx_dio_offset_align 128 | 129 | # Inode 130 | # ----- 131 | @property 132 | def islink(self) -> bool: 133 | ''' Return True if mode is from a symbolic link. ''' 134 | return __S_ISLNK(self.ptr.stx_mode) == 1 135 | 136 | @property 137 | def isfile(self) -> bool: 138 | ''' Return True if mode is from a regular file. ''' 139 | return __S_ISREG(self.ptr.stx_mode) == 1 140 | 141 | @property 142 | def isreg(self) -> bool: 143 | ''' Return True if mode is from a regular file. ''' 144 | return __S_ISREG(self.ptr.stx_mode) == 1 145 | 146 | @property 147 | def isdir(self) -> bool: 148 | ''' Return True if mode is from a directory. ''' 149 | return __S_ISDIR(self.ptr.stx_mode) == 1 150 | 151 | @property 152 | def ischr(self) -> bool: 153 | ''' Return True if mode is from a character special device file. ''' 154 | return __S_ISCHR(self.ptr.stx_mode) == 1 155 | 156 | @property 157 | def isblk(self) -> bool: 158 | ''' Return True if mode is from a block special device file. ''' 159 | return __S_ISBLK(self.ptr.stx_mode) == 1 160 | 161 | @property 162 | def isfifo(self) -> bool: 163 | ''' Return True if mode is from a FIFO (named pipe). ''' 164 | return __S_ISFIFO(self.ptr.stx_mode) == 1 165 | 166 | @property 167 | def issock(self) -> bool: 168 | ''' Return True if mode is from a socket. ''' 169 | return __S_ISSOCK(self.ptr.stx_mode) == 1 170 | 171 | 172 | cpdef inline void io_uring_prep_statx(io_uring_sqe sqe, 173 | statx statxbuf, 174 | const char* path, 175 | int flags=0, 176 | unsigned int mask=0, 177 | int dfd=__AT_FDCWD) noexcept nogil: 178 | ''' 179 | Type 180 | sqe: io_uring_sqe 181 | statxbuf: statx 182 | path: bytes 183 | flags: int 184 | mask: int 185 | dfd: int 186 | return: None 187 | 188 | Example 189 | >>> stat = statx() 190 | >>> path = __file__.encode() # note: also keeps reference alive 191 | >>> if sqe := io_uring_get_sqe() 192 | ... io_uring_prep_statx(sqe, AT_FDCWD, path, 0, 0, stat) 193 | ... ... 194 | >>> stat.isfile 195 | True 196 | >>> stat.size 197 | 123 198 | 199 | Flag 200 | AT_EMPTY_PATH 201 | AT_NO_AUTOMOUNT 202 | AT_SYMLINK_NOFOLLOW # Do not follow symbolic links. 203 | AT_STATX_SYNC_AS_STAT 204 | AT_STATX_FORCE_SYNC 205 | AT_STATX_DONT_SYNC 206 | 207 | Mask 208 | STATX_TYPE # Want|got `stx_mode & S_IFMT` 209 | STATX_MODE # Want|got `stx_mode & ~S_IFMT` 210 | STATX_NLINK # Want|got `stx_nlink` 211 | STATX_UID # Want|got `stx_uid` 212 | STATX_GID # Want|got `stx_gid` 213 | STATX_ATIME # Want|got `stx_atime` 214 | STATX_MTIME # Want|got `stx_mtime` 215 | STATX_CTIME # Want|got `stx_ctime` 216 | STATX_INO # Want|got `stx_ino` 217 | STATX_SIZE # Want|got `stx_size` 218 | STATX_BLOCKS # Want|got `stx_blocks` 219 | STATX_BASIC_STATS # [All of the above] 220 | STATX_BTIME # Want|got `stx_btime` 221 | STATX_MNT_ID # Got `stx_mnt_id` 222 | # note: not supported 223 | # STATX_DIOALIGN # Want/got direct I/O alignment info 224 | 225 | Note 226 | - Keep reference to `path` or else it will raise `FileNotFoundError` 227 | ''' 228 | __io_uring_prep_statx(sqe.ptr, dfd, path, flags, mask, statxbuf.ptr) 229 | -------------------------------------------------------------------------------- /src/liburing/syscall.pxd: -------------------------------------------------------------------------------- 1 | from .lib.uring cimport * 2 | from .error cimport trap_error 3 | from .queue cimport sigset, io_uring_params 4 | 5 | 6 | # `io_uring` syscalls. 7 | cpdef int io_uring_enter(unsigned int fd, 8 | unsigned int to_submit, 9 | unsigned int min_complete, 10 | unsigned int flags, 11 | sigset sig) nogil 12 | cpdef int io_uring_enter2(unsigned int fd, 13 | unsigned int to_submit, 14 | unsigned int min_complete, 15 | unsigned int flags, 16 | sigset sig, 17 | size_t sz) nogil 18 | cpdef int io_uring_setup(unsigned int entries, io_uring_params p) nogil 19 | cpdef int io_uring_register(unsigned int fd, 20 | unsigned int opcode, 21 | const unsigned char[:] arg, 22 | unsigned int nr_args) nogil 23 | 24 | 25 | cpdef enum io_uring_op: 26 | IORING_OP_NOP = __IORING_OP_NOP 27 | IORING_OP_READV = __IORING_OP_READV 28 | IORING_OP_WRITEV = __IORING_OP_WRITEV 29 | IORING_OP_FSYNC = __IORING_OP_FSYNC 30 | IORING_OP_READ_FIXED = __IORING_OP_READ_FIXED 31 | IORING_OP_WRITE_FIXED = __IORING_OP_WRITE_FIXED 32 | IORING_OP_POLL_ADD = __IORING_OP_POLL_ADD 33 | IORING_OP_POLL_REMOVE = __IORING_OP_POLL_REMOVE 34 | IORING_OP_SYNC_FILE_RANGE = __IORING_OP_SYNC_FILE_RANGE 35 | IORING_OP_SENDMSG = __IORING_OP_SENDMSG 36 | IORING_OP_RECVMSG = __IORING_OP_RECVMSG 37 | IORING_OP_TIMEOUT = __IORING_OP_TIMEOUT 38 | IORING_OP_TIMEOUT_REMOVE = __IORING_OP_TIMEOUT_REMOVE 39 | IORING_OP_ACCEPT = __IORING_OP_ACCEPT 40 | IORING_OP_ASYNC_CANCEL = __IORING_OP_ASYNC_CANCEL 41 | IORING_OP_LINK_TIMEOUT = __IORING_OP_LINK_TIMEOUT 42 | IORING_OP_CONNECT = __IORING_OP_CONNECT 43 | IORING_OP_FALLOCATE = __IORING_OP_FALLOCATE 44 | IORING_OP_OPENAT = __IORING_OP_OPENAT 45 | IORING_OP_CLOSE = __IORING_OP_CLOSE 46 | IORING_OP_FILES_UPDATE = __IORING_OP_FILES_UPDATE 47 | IORING_OP_STATX = __IORING_OP_STATX 48 | IORING_OP_READ = __IORING_OP_READ 49 | IORING_OP_WRITE = __IORING_OP_WRITE 50 | IORING_OP_FADVISE = __IORING_OP_FADVISE 51 | IORING_OP_MADVISE = __IORING_OP_MADVISE 52 | IORING_OP_SEND = __IORING_OP_SEND 53 | IORING_OP_RECV = __IORING_OP_RECV 54 | IORING_OP_OPENAT2 = __IORING_OP_OPENAT2 55 | IORING_OP_EPOLL_CTL = __IORING_OP_EPOLL_CTL 56 | IORING_OP_SPLICE = __IORING_OP_SPLICE 57 | IORING_OP_PROVIDE_BUFFERS = __IORING_OP_PROVIDE_BUFFERS 58 | IORING_OP_REMOVE_BUFFERS = __IORING_OP_REMOVE_BUFFERS 59 | IORING_OP_TEE = __IORING_OP_TEE 60 | IORING_OP_SHUTDOWN = __IORING_OP_SHUTDOWN 61 | IORING_OP_RENAMEAT = __IORING_OP_RENAMEAT 62 | IORING_OP_UNLINKAT = __IORING_OP_UNLINKAT 63 | IORING_OP_MKDIRAT = __IORING_OP_MKDIRAT 64 | IORING_OP_SYMLINKAT = __IORING_OP_SYMLINKAT 65 | IORING_OP_LINKAT = __IORING_OP_LINKAT 66 | IORING_OP_MSG_RING = __IORING_OP_MSG_RING 67 | IORING_OP_FSETXATTR = __IORING_OP_FSETXATTR 68 | IORING_OP_SETXATTR = __IORING_OP_SETXATTR 69 | IORING_OP_FGETXATTR = __IORING_OP_FGETXATTR 70 | IORING_OP_GETXATTR = __IORING_OP_GETXATTR 71 | IORING_OP_SOCKET = __IORING_OP_SOCKET 72 | IORING_OP_URING_CMD = __IORING_OP_URING_CMD 73 | IORING_OP_SEND_ZC = __IORING_OP_SEND_ZC 74 | IORING_OP_SENDMSG_ZC = __IORING_OP_SENDMSG_ZC 75 | IORING_OP_READ_MULTISHOT = __IORING_OP_READ_MULTISHOT 76 | IORING_OP_WAITID = __IORING_OP_WAITID 77 | IORING_OP_FUTEX_WAIT = __IORING_OP_FUTEX_WAIT 78 | IORING_OP_FUTEX_WAKE = __IORING_OP_FUTEX_WAKE 79 | IORING_OP_FUTEX_WAITV = __IORING_OP_FUTEX_WAITV 80 | IORING_OP_FIXED_FD_INSTALL = __IORING_OP_FIXED_FD_INSTALL 81 | IORING_OP_FTRUNCATE = __IORING_OP_FTRUNCATE 82 | IORING_OP_BIND = __IORING_OP_BIND 83 | IORING_OP_LISTEN = __IORING_OP_LISTEN 84 | # this goes last, obviously 85 | IORING_OP_LAST = __IORING_OP_LAST 86 | 87 | # `io_uring` register, used by `io_uring_register()`. 88 | cpdef enum io_uring_register_op: 89 | IORING_REGISTER_BUFFERS = __IORING_REGISTER_BUFFERS 90 | IORING_UNREGISTER_BUFFERS = __IORING_UNREGISTER_BUFFERS 91 | IORING_REGISTER_FILES = __IORING_REGISTER_FILES 92 | IORING_UNREGISTER_FILES = __IORING_UNREGISTER_FILES 93 | IORING_REGISTER_EVENTFD = __IORING_REGISTER_EVENTFD 94 | IORING_UNREGISTER_EVENTFD = __IORING_UNREGISTER_EVENTFD 95 | IORING_REGISTER_FILES_UPDATE = __IORING_REGISTER_FILES_UPDATE 96 | IORING_REGISTER_EVENTFD_ASYNC = __IORING_REGISTER_EVENTFD_ASYNC 97 | IORING_REGISTER_PROBE = __IORING_REGISTER_PROBE 98 | IORING_REGISTER_PERSONALITY = __IORING_REGISTER_PERSONALITY 99 | IORING_UNREGISTER_PERSONALITY = __IORING_UNREGISTER_PERSONALITY 100 | IORING_REGISTER_RESTRICTIONS = __IORING_REGISTER_RESTRICTIONS 101 | IORING_REGISTER_ENABLE_RINGS = __IORING_REGISTER_ENABLE_RINGS 102 | IORING_REGISTER_FILES2 = __IORING_REGISTER_FILES2 103 | IORING_REGISTER_FILES_UPDATE2 = __IORING_REGISTER_FILES_UPDATE2 104 | IORING_REGISTER_BUFFERS2 = __IORING_REGISTER_BUFFERS2 105 | IORING_REGISTER_BUFFERS_UPDATE = __IORING_REGISTER_BUFFERS_UPDATE 106 | IORING_REGISTER_IOWQ_AFF = __IORING_REGISTER_IOWQ_AFF 107 | IORING_UNREGISTER_IOWQ_AFF = __IORING_UNREGISTER_IOWQ_AFF 108 | IORING_REGISTER_IOWQ_MAX_WORKERS = __IORING_REGISTER_IOWQ_MAX_WORKERS 109 | IORING_REGISTER_RING_FDS = __IORING_REGISTER_RING_FDS 110 | IORING_UNREGISTER_RING_FDS = __IORING_UNREGISTER_RING_FDS 111 | IORING_REGISTER_PBUF_RING = __IORING_REGISTER_PBUF_RING 112 | IORING_UNREGISTER_PBUF_RING = __IORING_UNREGISTER_PBUF_RING 113 | IORING_REGISTER_SYNC_CANCEL = __IORING_REGISTER_SYNC_CANCEL 114 | IORING_REGISTER_FILE_ALLOC_RANGE = __IORING_REGISTER_FILE_ALLOC_RANGE 115 | IORING_REGISTER_PBUF_STATUS = __IORING_REGISTER_PBUF_STATUS 116 | IORING_REGISTER_NAPI = __IORING_REGISTER_NAPI 117 | IORING_UNREGISTER_NAPI = __IORING_UNREGISTER_NAPI 118 | IORING_REGISTER_CLOCK = __IORING_REGISTER_CLOCK 119 | IORING_REGISTER_CLONE_BUFFERS = __IORING_REGISTER_CLONE_BUFFERS 120 | IORING_REGISTER_RESIZE_RINGS = __IORING_REGISTER_RESIZE_RINGS 121 | IORING_REGISTER_MEM_REGION = __IORING_REGISTER_MEM_REGION 122 | IORING_REGISTER_LAST = __IORING_REGISTER_LAST 123 | IORING_REGISTER_USE_REGISTERED_RING = __IORING_REGISTER_USE_REGISTERED_RING 124 | 125 | 126 | cpdef enum __syscall_define__: 127 | # io_uring_enter(2) flags 128 | IORING_ENTER_GETEVENTS = __IORING_ENTER_GETEVENTS 129 | IORING_ENTER_SQ_WAKEUP = __IORING_ENTER_SQ_WAKEUP 130 | IORING_ENTER_SQ_WAIT = __IORING_ENTER_SQ_WAIT 131 | IORING_ENTER_EXT_ARG = __IORING_ENTER_EXT_ARG 132 | IORING_ENTER_REGISTERED_RING = __IORING_ENTER_REGISTERED_RING 133 | IORING_ENTER_ABS_TIMER = __IORING_ENTER_ABS_TIMER 134 | IORING_ENTER_EXT_ARG_REG = __IORING_ENTER_EXT_ARG_REG 135 | 136 | # `io_uring_setup()` flags 137 | IORING_SETUP_IOPOLL = __IORING_SETUP_IOPOLL 138 | IORING_SETUP_SQPOLL = __IORING_SETUP_SQPOLL 139 | IORING_SETUP_SQ_AFF = __IORING_SETUP_SQ_AFF 140 | IORING_SETUP_CQSIZE = __IORING_SETUP_CQSIZE 141 | IORING_SETUP_CLAMP = __IORING_SETUP_CLAMP 142 | IORING_SETUP_ATTACH_WQ = __IORING_SETUP_ATTACH_WQ 143 | IORING_SETUP_R_DISABLED = __IORING_SETUP_R_DISABLED 144 | IORING_SETUP_SUBMIT_ALL = __IORING_SETUP_SUBMIT_ALL 145 | IORING_SETUP_COOP_TASKRUN = __IORING_SETUP_COOP_TASKRUN 146 | IORING_SETUP_TASKRUN_FLAG = __IORING_SETUP_TASKRUN_FLAG 147 | IORING_SETUP_SQE128 = __IORING_SETUP_SQE128 148 | IORING_SETUP_CQE32 = __IORING_SETUP_CQE32 149 | IORING_SETUP_SINGLE_ISSUER = __IORING_SETUP_SINGLE_ISSUER 150 | IORING_SETUP_DEFER_TASKRUN = __IORING_SETUP_DEFER_TASKRUN 151 | IORING_SETUP_NO_MMAP = __IORING_SETUP_NO_MMAP 152 | IORING_SETUP_REGISTERED_FD_ONLY = __IORING_SETUP_REGISTERED_FD_ONLY 153 | IORING_SETUP_NO_SQARRAY = __IORING_SETUP_NO_SQARRAY 154 | IORING_SETUP_HYBRID_IOPOLL = __IORING_SETUP_HYBRID_IOPOLL 155 | -------------------------------------------------------------------------------- /src/liburing/syscall.pyx: -------------------------------------------------------------------------------- 1 | # Raw access to `io_uring` syscalls. 2 | cpdef int io_uring_enter(unsigned int fd, 3 | unsigned int to_submit, 4 | unsigned int min_complete, 5 | unsigned int flags, 6 | sigset sig) nogil: 7 | return trap_error(__io_uring_enter(fd, to_submit, min_complete, flags, sig.ptr)) 8 | 9 | cpdef int io_uring_enter2(unsigned int fd, 10 | unsigned int to_submit, 11 | unsigned int min_complete, 12 | unsigned int flags, 13 | sigset sig, 14 | size_t sz) nogil: 15 | return trap_error(__io_uring_enter2(fd, to_submit, min_complete, flags, sig.ptr, sz)) 16 | 17 | cpdef int io_uring_setup(unsigned int entries, io_uring_params p) nogil: 18 | return trap_error(__io_uring_setup(entries, p.ptr)) 19 | 20 | cpdef int io_uring_register(unsigned int fd, 21 | unsigned int opcode, 22 | const unsigned char[:] arg, # const void * arg, 23 | unsigned int nr_args) nogil: 24 | return trap_error(__io_uring_register(fd, opcode, &arg[0], nr_args)) 25 | -------------------------------------------------------------------------------- /src/liburing/time.pxd: -------------------------------------------------------------------------------- 1 | from cpython.mem cimport PyMem_RawCalloc, PyMem_RawFree 2 | from .lib.uring cimport * 3 | from .error cimport memory_error, index_error 4 | from .queue cimport io_uring_sqe 5 | 6 | 7 | cdef class timespec: 8 | cdef __kernel_timespec* ptr 9 | 10 | 11 | cpdef void io_uring_prep_timeout(io_uring_sqe sqe, 12 | timespec ts, 13 | unsigned int count, 14 | unsigned int flags) noexcept nogil 15 | cpdef void io_uring_prep_timeout_remove(io_uring_sqe sqe, 16 | __u64 user_data, 17 | unsigned int flags) noexcept nogil 18 | cpdef void io_uring_prep_timeout_update(io_uring_sqe sqe, 19 | timespec ts, 20 | __u64 user_data, 21 | unsigned int flags) noexcept nogil 22 | cpdef void io_uring_prep_link_timeout(io_uring_sqe sqe, 23 | timespec ts, 24 | unsigned int flags) noexcept nogil 25 | 26 | 27 | cpdef enum __time_define__: 28 | IORING_TIMEOUT_ABS = __IORING_TIMEOUT_ABS 29 | IORING_TIMEOUT_UPDATE = __IORING_TIMEOUT_UPDATE 30 | IORING_TIMEOUT_BOOTTIME = __IORING_TIMEOUT_BOOTTIME 31 | IORING_TIMEOUT_REALTIME = __IORING_TIMEOUT_REALTIME 32 | IORING_LINK_TIMEOUT_UPDATE = __IORING_LINK_TIMEOUT_UPDATE 33 | IORING_TIMEOUT_ETIME_SUCCESS = __IORING_TIMEOUT_ETIME_SUCCESS 34 | IORING_TIMEOUT_MULTISHOT = __IORING_TIMEOUT_MULTISHOT 35 | IORING_TIMEOUT_CLOCK_MASK = __IORING_TIMEOUT_CLOCK_MASK 36 | IORING_TIMEOUT_UPDATE_MASK = __IORING_TIMEOUT_UPDATE_MASK 37 | -------------------------------------------------------------------------------- /src/liburing/time.pyx: -------------------------------------------------------------------------------- 1 | cdef class timespec: 2 | ''' Kernel Timespec 3 | 4 | Example 5 | >>> ts = timespec(1) # int 6 | >>> ts = timespec(1.5) # float 7 | >>> io_uring_prep_timeout(sqe, ts, ...) 8 | 9 | # manually set raw value 10 | >>> ts = timespec() 11 | >>> ts.tv_sec = 1 # second 12 | >>> ts.tv_nsec = 500000000 # nanosecond 13 | >>> io_uring_prep_timeout(sqe, ts, ...) 14 | ''' 15 | def __cinit__(self, double second=0): 16 | self.ptr = <__kernel_timespec*>PyMem_RawCalloc(1, sizeof(__kernel_timespec)) 17 | if self.ptr is NULL: 18 | memory_error(self) 19 | if second: 20 | # note: converting from `double` is the reason for casting 21 | self.ptr.tv_sec = (second / 1) 22 | self.ptr.tv_nsec = (((second % 1) * 1_000_000_000) / 1) 23 | 24 | def __dealloc__(self): 25 | if self.ptr is not NULL: 26 | PyMem_RawFree(self.ptr) 27 | self.ptr = NULL 28 | 29 | @property 30 | def tv_sec(self): 31 | if self.ptr is NULL: 32 | memory_error(self) 33 | return self.ptr.tv_sec 34 | 35 | @tv_sec.setter 36 | def tv_sec(self, int64_t second): 37 | if self.ptr is NULL: 38 | memory_error(self) 39 | self.ptr.tv_sec = second 40 | 41 | @property 42 | def tv_nsec(self): 43 | if self.ptr is NULL: 44 | memory_error(self) 45 | return self.ptr.tv_nsec 46 | 47 | @tv_nsec.setter 48 | def tv_nsec(self, long long nanosecond): 49 | if self.ptr is NULL: 50 | memory_error(self) 51 | self.ptr.tv_nsec = nanosecond 52 | 53 | 54 | cpdef inline void io_uring_prep_timeout(io_uring_sqe sqe, 55 | timespec ts, 56 | unsigned int count, 57 | unsigned int flags) noexcept nogil: 58 | __io_uring_prep_timeout(sqe.ptr, ts.ptr, count, flags) 59 | 60 | cpdef inline void io_uring_prep_timeout_remove(io_uring_sqe sqe, 61 | __u64 user_data, 62 | unsigned int flags) noexcept nogil: 63 | __io_uring_prep_timeout_remove(sqe.ptr, user_data, flags) 64 | 65 | cpdef inline void io_uring_prep_timeout_update(io_uring_sqe sqe, 66 | timespec ts, 67 | __u64 user_data, 68 | unsigned int flags) noexcept nogil: 69 | __io_uring_prep_timeout_update(sqe.ptr, ts.ptr, user_data, flags) 70 | 71 | cpdef inline void io_uring_prep_link_timeout(io_uring_sqe sqe, 72 | timespec ts, 73 | unsigned int flags) noexcept nogil: 74 | __io_uring_prep_link_timeout(sqe.ptr, ts.ptr, flags) 75 | -------------------------------------------------------------------------------- /src/liburing/version.pxd: -------------------------------------------------------------------------------- 1 | from .lib.uring cimport * 2 | 3 | 4 | # liburing version 5 | cpdef __u8 liburing_version_major() 6 | cpdef __u8 liburing_version_minor() 7 | cpdef bool liburing_version_check(__u8 major, __u8 minor) 8 | # note: changed C function name from `io_uring_*` to `liburing_*`, as this is 9 | # `liburing` library version. `io_uring` is the backend installed into linux. 10 | -------------------------------------------------------------------------------- /src/liburing/version.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | 3 | 4 | LINUX_VERSION_MAJOR = 0 5 | LINUX_VERSION_MINOR = 0 6 | 7 | 8 | def _set_linux_version(): 9 | global LINUX_VERSION_MAJOR, LINUX_VERSION_MINOR 10 | if not LINUX_VERSION_MAJOR: 11 | with open('/proc/version', 'rb') as file: 12 | data = file.read() 13 | major, minor, *_ = data.split()[2].split(b'.', 2) 14 | LINUX_VERSION_MAJOR = int(major) 15 | LINUX_VERSION_MINOR = int(minor) 16 | 17 | 18 | _set_linux_version() # init 19 | 20 | 21 | @lru_cache 22 | def linux_version_check(version): 23 | ''' Linux Version Check. 24 | 25 | Type 26 | version: str | int | float 27 | return: bool 28 | 29 | Example 30 | # assuming your linux is 6.7 31 | >>> linux_version_check(5) 32 | False 33 | >>> linux_version_check('6.6') 34 | False 35 | >>> linux_version_check(6.7) 36 | False 37 | >>> linux_version_check(6.8) 38 | True 39 | >>> linux_version_check(7.0) 40 | True 41 | ''' 42 | major, minor = map(int, str(float(version)).split('.')) # '6.7' -> 6 7 43 | return (major > LINUX_VERSION_MAJOR) or ((major == LINUX_VERSION_MAJOR) and 44 | (minor > LINUX_VERSION_MINOR)) 45 | -------------------------------------------------------------------------------- /src/liburing/version.pyx: -------------------------------------------------------------------------------- 1 | # liburing version 2 | cpdef __u8 liburing_version_major(): 3 | ''' Note: `io_uring_major_version` has been renamed to `liburing_version_major` ''' 4 | return __io_uring_major_version() 5 | 6 | cpdef __u8 liburing_version_minor(): 7 | ''' Note: `io_uring_minor_version` has been renamed to `liburing_version_minor` ''' 8 | return __io_uring_minor_version() 9 | 10 | cpdef bool liburing_version_check(__u8 major, __u8 minor): 11 | ''' Note: `io_uring_check_version` has been renamed to `liburing_version_check` ''' 12 | return __io_uring_check_version(major, minor) 13 | -------------------------------------------------------------------------------- /src/liburing/xattr.pxd: -------------------------------------------------------------------------------- 1 | from .lib.uring cimport __io_uring_prep_setxattr, __io_uring_prep_getxattr, \ 2 | __io_uring_prep_fsetxattr, __io_uring_prep_fgetxattr 3 | from .queue cimport io_uring_sqe 4 | 5 | 6 | cpdef void io_uring_prep_setxattr(io_uring_sqe sqe, 7 | const char *name, 8 | const char *value, 9 | const char *path, 10 | unsigned int len, 11 | int flags=?) noexcept nogil 12 | cpdef void io_uring_prep_getxattr(io_uring_sqe sqe, 13 | const char *name, 14 | char *value, 15 | const char *path, 16 | unsigned int len) noexcept nogil 17 | cpdef void io_uring_prep_fsetxattr(io_uring_sqe sqe, 18 | int fd, 19 | const char *name, 20 | const char *value, 21 | unsigned int len, 22 | int flags=?) noexcept nogil 23 | cpdef void io_uring_prep_fgetxattr(io_uring_sqe sqe, 24 | int fd, 25 | const char *name, 26 | char *value, 27 | unsigned int len) noexcept nogil 28 | -------------------------------------------------------------------------------- /src/liburing/xattr.pyx: -------------------------------------------------------------------------------- 1 | cpdef inline void io_uring_prep_setxattr(io_uring_sqe sqe, 2 | const char *name, 3 | const char *value, 4 | const char *path, 5 | unsigned int len, 6 | int flags=0) noexcept nogil: 7 | __io_uring_prep_setxattr(sqe.ptr, name, value, path, flags, len) 8 | 9 | cpdef inline void io_uring_prep_getxattr(io_uring_sqe sqe, 10 | const char *name, 11 | char *value, 12 | const char *path, 13 | unsigned int len) noexcept nogil: 14 | __io_uring_prep_getxattr(sqe.ptr, name, value, path, len) 15 | 16 | cpdef inline void io_uring_prep_fsetxattr(io_uring_sqe sqe, 17 | int fd, 18 | const char *name, 19 | const char *value, 20 | unsigned int len, 21 | int flags=0) noexcept nogil: 22 | __io_uring_prep_fsetxattr(sqe.ptr, fd, name, value, flags, len) 23 | 24 | cpdef inline void io_uring_prep_fgetxattr(io_uring_sqe sqe, 25 | int fd, 26 | const char *name, 27 | char *value, 28 | unsigned int len) noexcept nogil: 29 | __io_uring_prep_fgetxattr(sqe.ptr, fd, name, value, len) 30 | -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import os.path 3 | import pytest 4 | import pathlib 5 | import getpass 6 | import tempfile 7 | import liburing 8 | 9 | 10 | @pytest.fixture 11 | def tmp_dir(): 12 | ''' Temporary directory to store test data. 13 | 14 | Example 15 | >>> test_my_function(tmp_dir) 16 | ... # create directory 17 | ... # ---------------- 18 | ... my_dir = tmp_dir / 'my_dir' 19 | ... my_dir.mkdir() 20 | ... 21 | ... # create file 22 | ... # ----------- 23 | ... my_file = tmp_dir / 'my_file.ext' 24 | ... my_file.write_text('Hello World!!!') 25 | 26 | Note 27 | - unlike pytest's `tmpdir`, `tmp_path`, ... `tmp_dir` generated 28 | files & directories are not deleted after 3 runs. 29 | ''' 30 | path = f'{tempfile.gettempdir()}/pytest-of-{getpass.getuser()}-holder' 31 | if not os.path.exists(path): 32 | os.mkdir(path) 33 | return pathlib.Path(tempfile.mkdtemp(dir=path)) 34 | 35 | 36 | # liburing start >>> 37 | @pytest.fixture 38 | def ring(): 39 | ring = liburing.io_uring() 40 | try: 41 | liburing.io_uring_queue_init(1024, ring) 42 | yield ring 43 | finally: 44 | liburing.io_uring_queue_exit(ring) 45 | 46 | 47 | @pytest.fixture 48 | def cqe(): 49 | return liburing.io_uring_cqe() 50 | # liburing end <<< 51 | 52 | 53 | # linux version start >>> 54 | LINUX_VERSION = f'{liburing.LINUX_VERSION_MAJOR}.{liburing.LINUX_VERSION_MINOR}' 55 | 56 | 57 | @pytest.fixture(autouse=True) 58 | def skip_by_platform(request): 59 | ''' 60 | Example 61 | >>> @pytest.mark.skip_linux(6.7) 62 | >>> def test_function(): 63 | ... 64 | test.py::test_function SKIPPED (Linux `6.7 < 6.8`) 65 | 66 | >>> @pytest.mark.skip_linux(6.7, 'custom message') 67 | >>> def test_function(): 68 | ... 69 | test.py::test_function SKIPPED (custom message) 70 | 71 | >>> @pytest.mark.skip_linux('6.7', '') 72 | >>> def test_function(): 73 | ... 74 | test.py::test_function SKIPPED 75 | ''' 76 | if r := request.node.get_closest_marker('skip_linux'): 77 | if liburing.linux_version_check(version := r.args[0]): 78 | msg = r.args[1] if len(r.args) > 1 else f'Kernel `{LINUX_VERSION} < {version}`' 79 | pytest.skip(msg) 80 | 81 | 82 | def pytest_configure(config): 83 | config.addinivalue_line( 84 | "markers", 85 | "skip_linux(version:str|float|int, message:str): skipping linux version not supported.") 86 | # linux version end <<< 87 | -------------------------------------------------------------------------------- /test/error_test.py: -------------------------------------------------------------------------------- 1 | from re import escape 2 | from pytest import raises 3 | from liburing import trap_error, memory_error, index_error 4 | 5 | 6 | class MyClass: 7 | pass 8 | 9 | 10 | def test_trap_error(): 11 | assert trap_error(0) == 0 12 | assert trap_error(1) == 1 13 | 14 | with raises(BlockingIOError): 15 | trap_error(-11) 16 | 17 | with raises(OSError): 18 | trap_error(-1) 19 | 20 | msg = escape('testing `msg` returns') 21 | with raises(BlockingIOError, match=msg): 22 | trap_error(-11, 'testing `msg` returns') 23 | 24 | 25 | def test_memory_error(): 26 | error = escape('`MyClass()` is out of memory!') 27 | with raises(MemoryError, match=error): 28 | memory_error(MyClass()) 29 | 30 | error = escape('`MyClass()` message') 31 | with raises(MemoryError, match=error): 32 | memory_error(MyClass(), 'message') 33 | 34 | 35 | def test_index_error(): 36 | error = escape('`MyClass()[1]` out of range!') 37 | with raises(IndexError, match=error): 38 | index_error(MyClass(), 1) 39 | 40 | error = escape('`MyClass()[1]` message!') 41 | with raises(IndexError, match=error): 42 | index_error(MyClass(), 1, 'message!') 43 | 44 | with raises(OverflowError): 45 | index_error(MyClass(), -1) 46 | -------------------------------------------------------------------------------- /test/file/file_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | import liburing 4 | 5 | 6 | def test_file_registration(tmp_dir, ring): 7 | fds = [] 8 | fds.append(os.open(tmp_dir / '1.txt', liburing.O_CREAT)) 9 | fds.append(os.open(tmp_dir / '2.txt', liburing.O_CREAT)) 10 | try: 11 | assert liburing.io_uring_register_files(ring, fds) == 0 12 | assert liburing.io_uring_unregister_files(ring) == 0 13 | finally: 14 | for fd in fds: 15 | os.close(fd) 16 | 17 | 18 | def test_files_write_read_mix(tmp_dir, ring, cqe): 19 | path = tmp_dir / '1.txt' 20 | fd = os.open(path, liburing.O_RDWR | liburing.O_CREAT, 0o660) 21 | 22 | # prepare for writing two separate writes and reads. 23 | one = bytearray(b'hello') 24 | two = bytearray(b'world') 25 | vec_one = liburing.iovec(one) 26 | vec_two = liburing.iovec(two) 27 | 28 | try: 29 | # write "hello" 30 | sqe = liburing.io_uring_get_sqe(ring) # get sqe (submission queue entry) to fill 31 | liburing.io_uring_prep_write(sqe, fd, vec_one[0].iov_base, vec_one[0].iov_len, 0) 32 | sqe.user_data = 1 33 | 34 | # write "world" 35 | sqe = liburing.io_uring_get_sqe(ring) 36 | liburing.io_uring_prep_writev(sqe, fd, vec_two, len(vec_two), 5) 37 | sqe.user_data = 2 38 | 39 | # submit both writes 40 | assert liburing.io_uring_submit(ring) == 2 41 | 42 | # wait for ``2`` entry to complete using single syscall 43 | assert liburing.io_uring_wait_cqes(ring, cqe, 2) == 0 44 | assert cqe.res == 5 45 | assert cqe.user_data == 1 46 | liburing.io_uring_cqe_seen(ring, cqe) 47 | 48 | # re-uses the same resources from above?! 49 | assert liburing.io_uring_wait_cqes(ring, cqe, 1) == 0 50 | assert cqe.res == 5 51 | assert cqe.user_data == 2 52 | liburing.io_uring_cqe_seen(ring, cqe) 53 | 54 | # Using same ``vec*`` swap so read can be confirmed. 55 | 56 | # read "world" 57 | sqe = liburing.io_uring_get_sqe(ring) 58 | liburing.io_uring_prep_readv(sqe, fd, vec_one, 5) 59 | sqe.user_data = 3 60 | 61 | assert liburing.io_uring_submit(ring) == 1 62 | assert liburing.io_uring_wait_cqe(ring, cqe) == 0 63 | assert cqe.res == 5 64 | assert cqe.user_data == 3 65 | liburing.io_uring_cq_advance(ring, 1) 66 | 67 | # read "hello" 68 | sqe = liburing.io_uring_get_sqe(ring) 69 | liburing.io_uring_prep_read(sqe, fd, vec_two[0].iov_base, vec_two[0].iov_len, 0) 70 | sqe.user_data = 4 71 | 72 | assert liburing.io_uring_submit(ring) == 1 73 | assert liburing.io_uring_wait_cqe(ring, cqe) == 0 74 | assert cqe.res == 5 75 | assert cqe.user_data == 4 76 | liburing.io_uring_cq_advance(ring, 1) 77 | 78 | # use same as write buffer to read but switch values so the change is detected 79 | assert one == b'world' 80 | assert two == b'hello' 81 | finally: 82 | os.close(fd) 83 | 84 | 85 | def test_rwf_nowait_flag(ring, cqe): 86 | path1 = './test_rwf_nowait_flag.txt' 87 | path2 = './test_rwf_nowait_flag_empty_file.txt' 88 | # note: `RWF_NOWAIT` will raise `OSError: [Errno 95] Operation not supported` 89 | # if the file is not in disk or file is located in ram this includes `/tmp` 90 | _onwait_flag(ring, cqe, path1) 91 | _onwait_flag_empty_file(ring, cqe, path2, os.RWF_NOWAIT) 92 | _onwait_flag_empty_file(ring, cqe, path2, 0) # no flag 93 | 94 | # one of the ways to tell if `RWF_NOWAIT` flag is working is to catch its error 95 | with pytest.raises(OSError): 96 | path = '/dev/shm/test_rwf_nowait_flag.txt' 97 | _onwait_flag(ring, cqe, path) 98 | 99 | 100 | def _onwait_flag(ring, cqe, path): 101 | fd = os.open(path, liburing.O_RDWR | liburing.O_CREAT | liburing.O_NONBLOCK, 0o660) 102 | one = bytearray(6) 103 | two = bytearray(5) 104 | vec = liburing.iovec([one, two]) 105 | try: 106 | # write 107 | # ----- 108 | os.write(fd, b'hello world') 109 | os.fsync(fd) 110 | 111 | # read 112 | # ---- 113 | sqe = liburing.io_uring_get_sqe(ring) 114 | liburing.io_uring_prep_readv2(sqe, fd, vec, 0, os.RWF_NOWAIT) 115 | sqe.user_data = 1 116 | 117 | assert liburing.io_uring_submit(ring) == 1 118 | 119 | while True: 120 | try: 121 | liburing.io_uring_peek_cqe(ring, cqe) 122 | except BlockingIOError: 123 | pass # print('test_rwf_nowait_flag BlockingIOError', flush=True) 124 | else: 125 | liburing.trap_error(cqe.res) 126 | assert cqe.res == 6 + 5 127 | assert cqe.user_data == 1 128 | assert one == b'hello ' 129 | assert two == b'world' 130 | liburing.io_uring_cqe_seen(ring, cqe) 131 | break 132 | finally: 133 | os.close(fd) 134 | os.unlink(path) 135 | 136 | 137 | def _onwait_flag_empty_file(ring, cqe, path, flag): 138 | fd = os.open(path, liburing.O_RDWR | liburing.O_CREAT | liburing.O_NONBLOCK, 0o660) 139 | read = bytearray(5) 140 | iov = liburing.iovec(read) 141 | try: 142 | # read empty file 143 | # --------------- 144 | sqe = liburing.io_uring_get_sqe(ring) 145 | liburing.io_uring_prep_readv2(sqe, fd, iov, 0, flag) 146 | sqe.user_data = 1 147 | 148 | assert liburing.io_uring_submit(ring) == 1 149 | liburing.io_uring_peek_cqe(ring, cqe) 150 | assert cqe.res == 0 151 | liburing.io_uring_cqe_seen(ring, cqe) 152 | assert iov.iov_base == bytearray(b'\x00\x00\x00\x00\x00') 153 | assert read == bytearray(b'\x00\x00\x00\x00\x00') 154 | finally: 155 | os.close(fd) 156 | os.unlink(path) 157 | -------------------------------------------------------------------------------- /test/file/open_close_test.py: -------------------------------------------------------------------------------- 1 | import liburing 2 | 3 | 4 | def test_openat_close(ring, cqe): 5 | flags = liburing.O_TMPFILE | liburing.O_WRONLY 6 | # open 7 | sqe = liburing.io_uring_get_sqe(ring) 8 | liburing.io_uring_prep_openat(sqe, b'.', flags) 9 | sqe.user_data = 123 10 | # submit 11 | liburing.io_uring_submit(ring) 12 | liburing.io_uring_wait_cqe(ring, cqe) 13 | fd = liburing.trap_error(cqe.res) 14 | assert cqe.user_data == 123 15 | liburing.io_uring_cqe_seen(ring, cqe) 16 | # close 17 | sqe = liburing.io_uring_get_sqe(ring) 18 | liburing.io_uring_prep_close(sqe, fd) 19 | sqe.user_data = 321 20 | # submit 21 | liburing.io_uring_submit(ring) 22 | liburing.io_uring_wait_cqe(ring, cqe) 23 | assert liburing.trap_error(cqe.res) == 0 24 | assert cqe.user_data == 321 25 | 26 | 27 | def test_openat2_close(ring, cqe): 28 | how = liburing.open_how(liburing.O_TMPFILE | liburing.O_WRONLY, 0o777, liburing.RESOLVE_IN_ROOT) 29 | # open 30 | sqe = liburing.io_uring_get_sqe(ring) 31 | liburing.io_uring_prep_openat2(sqe, b'.', how) 32 | sqe.user_data = 123 33 | # submit 34 | liburing.io_uring_submit(ring) 35 | liburing.io_uring_wait_cqe(ring, cqe) 36 | fd = liburing.trap_error(cqe.res) 37 | assert cqe.user_data == 123 38 | liburing.io_uring_cqe_seen(ring, cqe) 39 | # close 40 | sqe = liburing.io_uring_get_sqe(ring) 41 | liburing.io_uring_prep_close(sqe, fd) 42 | sqe.user_data = 321 43 | # submit 44 | liburing.io_uring_submit(ring) 45 | liburing.io_uring_wait_cqe(ring, cqe) 46 | assert liburing.trap_error(cqe.res) == 0 47 | assert cqe.user_data == 321 48 | 49 | 50 | def test_openat_close_direct(ring, cqe): 51 | index = 0 52 | flags = liburing.O_TMPFILE | liburing.O_WRONLY 53 | # register 54 | liburing.io_uring_register_files(ring, [index, 1, 2, 3]) 55 | # open 56 | sqe = liburing.io_uring_get_sqe(ring) 57 | liburing.io_uring_prep_openat_direct(sqe, b'.', flags, index) 58 | sqe.user_data = 123 59 | # submit 60 | liburing.io_uring_submit(ring) 61 | liburing.io_uring_wait_cqe(ring, cqe) 62 | result = liburing.trap_error(cqe.res) 63 | assert result == 0 64 | assert cqe.user_data == 123 65 | liburing.io_uring_cqe_seen(ring, cqe) 66 | # close 67 | sqe = liburing.io_uring_get_sqe(ring) 68 | liburing.io_uring_prep_close_direct(sqe, index) 69 | sqe.user_data = 321 70 | # submit 71 | liburing.io_uring_submit(ring) 72 | liburing.io_uring_wait_cqe(ring, cqe) 73 | assert liburing.trap_error(cqe.res) == 0 74 | assert cqe.user_data == 321 75 | # unregister 76 | liburing.io_uring_unregister_files(ring) 77 | 78 | 79 | def test_openat2_close_direct(ring, cqe): 80 | how = liburing.open_how(liburing.O_TMPFILE | liburing.O_WRONLY) 81 | index = 3 82 | # register 83 | liburing.io_uring_register_files(ring, [0, 1, 2, index]) 84 | # open 85 | sqe = liburing.io_uring_get_sqe(ring) 86 | liburing.io_uring_prep_openat2_direct(sqe, b'.', how, index) 87 | sqe.user_data = 123 88 | # submit 89 | liburing.io_uring_submit(ring) 90 | liburing.io_uring_wait_cqe(ring, cqe) 91 | result = liburing.trap_error(cqe.res) 92 | assert result == 0 93 | assert cqe.user_data == 123 94 | liburing.io_uring_cqe_seen(ring, cqe) 95 | # close 96 | sqe = liburing.io_uring_get_sqe(ring) 97 | liburing.io_uring_prep_close_direct(sqe, index) 98 | sqe.user_data = 321 99 | # submit 100 | liburing.io_uring_submit(ring) 101 | liburing.io_uring_wait_cqe(ring, cqe) 102 | assert liburing.trap_error(cqe.res) == 0 103 | assert cqe.user_data == 321 104 | # unregister 105 | liburing.io_uring_unregister_files(ring) 106 | 107 | 108 | def test_openat2_close_direct_auto_file_index_alloc(ring, cqe): 109 | how = liburing.open_how(liburing.O_TMPFILE | liburing.O_RDWR) 110 | find_index = 3 111 | # register 112 | liburing.io_uring_register_files(ring, [0, 1, 2, -1]) 113 | # open 114 | sqe = liburing.io_uring_get_sqe(ring) 115 | liburing.io_uring_prep_openat2_direct(sqe, b'.', how) # `file_index=IORING_FILE_INDEX_ALLOC` 116 | sqe.user_data = 123 117 | # submit 118 | assert liburing.io_uring_submit(ring) == 1 119 | assert liburing.io_uring_wait_cqe(ring, cqe) == 0 120 | assert liburing.trap_error(cqe.res) == find_index 121 | assert cqe.user_data == 123 122 | liburing.io_uring_cqe_seen(ring, cqe) 123 | # close 124 | sqe = liburing.io_uring_get_sqe(ring) 125 | liburing.io_uring_prep_close_direct(sqe, 0) 126 | sqe.user_data = 321 127 | # submit 128 | assert liburing.io_uring_submit(ring) == 1 129 | assert liburing.io_uring_wait_cqe(ring, cqe) == 0 130 | assert liburing.trap_error(cqe.res) == 0 131 | assert cqe.user_data == 321 132 | liburing.io_uring_cqe_seen(ring, cqe) 133 | # unregister 134 | liburing.io_uring_unregister_files(ring) 135 | -------------------------------------------------------------------------------- /test/file/open_how_test.py: -------------------------------------------------------------------------------- 1 | from liburing import O_RDWR, RESOLVE_CACHED, open_how 2 | 3 | 4 | def test_open_how(): 5 | how = open_how(O_RDWR, 0o777, RESOLVE_CACHED) 6 | assert how.flags == O_RDWR 7 | assert how.mode == 0o777 8 | assert how.resolve == RESOLVE_CACHED 9 | 10 | how = open_how() 11 | how.flags = O_RDWR 12 | how.mode = 0o777 13 | how.resolve = RESOLVE_CACHED 14 | assert how.flags == O_RDWR 15 | assert how.mode == 0o777 16 | assert how.resolve == RESOLVE_CACHED 17 | 18 | assert str(open_how(1, 2, 3)) == 'open_how(flags=1, mode=2, resolve=3)' 19 | -------------------------------------------------------------------------------- /test/futex_test.py: -------------------------------------------------------------------------------- 1 | # import re 2 | import errno 3 | import pytest 4 | import liburing 5 | 6 | 7 | def test_futex_6_7(): 8 | # this should run if linux `< 6.7` 9 | if liburing.linux_version_check(6.7): 10 | with pytest.raises(EnvironmentError): 11 | liburing.futex_state() 12 | 13 | 14 | @pytest.mark.skip_linux(6.7) 15 | def test_futex_state(): 16 | # shared 17 | futex = liburing.futex_state() 18 | assert futex.state == 0 19 | assert futex.private is False 20 | futex.state = 123 21 | assert futex.state == 123 22 | 23 | assert repr(futex) == 'futex_state(state=123, private=False)' 24 | 25 | with pytest.raises(AttributeError): 26 | futex.private = True 27 | 28 | with pytest.raises(OverflowError): 29 | futex.state = -1 30 | 31 | with pytest.raises(OverflowError): 32 | liburing.futex_state(-1) 33 | 34 | with pytest.raises(NotImplementedError): 35 | liburing.futex_state(2) 36 | 37 | # TODO: enable this when `futex_state(num)` can handle `> 1` 38 | # max_1 = liburing.FUTEX_WAITV_MAX + 1 39 | # msg = re.escape(f'futex_state({max_1}) > {liburing.FUTEX_WAITV_MAX}') 40 | # with pytest.raises(ValueError, match=msg): 41 | # liburing.futex_state(max_1) 42 | 43 | # private 44 | futex = liburing.futex_state(1, True) 45 | assert futex.private is True 46 | assert repr(futex) == 'futex_state(state=0, private=True)' 47 | 48 | # NULL 49 | futex = liburing.futex_state(0) 50 | assert futex.private is False 51 | with pytest.raises(MemoryError): 52 | assert futex.state is None 53 | with pytest.raises(MemoryError): 54 | futex.state = 123 55 | 56 | assert repr(futex) == 'futex_state(state=NULL, private=NULL)' 57 | 58 | 59 | @pytest.mark.skip_linux('6.7') 60 | def test_futex_waitv_class(): 61 | with pytest.raises(TypeError): 62 | liburing.futex_waitv() 63 | 64 | futex = liburing.futex_state() 65 | fw = liburing.futex_waitv(futex) 66 | assert fw.val == 0 67 | assert fw.flags == 0 68 | fw.val = 1 69 | fw.flags = 2 70 | assert fw.val == 1 71 | assert fw.flags == 2 72 | 73 | futex = liburing.futex_state(1, True) 74 | fw = liburing.futex_waitv(futex) 75 | assert fw.val == 0 76 | assert fw.flags == liburing.FUTEX2_PRIVATE 77 | fw.flags = 123 78 | assert fw.flags == liburing.FUTEX2_PRIVATE | 123 79 | 80 | 81 | @pytest.mark.skip_linux('6.7') 82 | def test_multi_wake(ring, cqe): 83 | val = 0 84 | mask = liburing.FUTEX_BITSET_MATCH_ANY 85 | futex = liburing.futex_state() 86 | futex_flags = liburing.FUTEX2_SIZE_U32 87 | 88 | # Submit two futex waits 89 | sqe = liburing.io_uring_get_sqe(ring) 90 | liburing.io_uring_prep_futex_wait(sqe, futex, val, mask, futex_flags) 91 | sqe.user_data = 1 92 | 93 | sqe = liburing.io_uring_get_sqe(ring) 94 | liburing.io_uring_prep_futex_wait(sqe, futex, val, mask, futex_flags) 95 | sqe.user_data = 2 96 | 97 | assert liburing.io_uring_submit(ring) == 2 98 | 99 | # Now submit wake for just one futex 100 | futex.state = 1 101 | sqe = liburing.io_uring_get_sqe(ring) 102 | liburing.io_uring_prep_futex_wake(sqe, futex, 2, mask, futex_flags) 103 | sqe.user_data = 100 104 | 105 | assert liburing.io_uring_submit(ring) == 1 106 | 107 | # We expect to find completions for the both futex waits, and the futex wake. 108 | for i in range(3): 109 | assert liburing.io_uring_wait_cqe(ring, cqe) == 0 110 | assert cqe.res >= 0 111 | liburing.io_uring_cqe_seen(ring, cqe) 112 | assert liburing.io_uring_peek_cqe(ring, cqe) == -errno.EAGAIN 113 | 114 | 115 | @pytest.mark.skip_linux(6.7) 116 | def test_multi_wake_waitv(ring, cqe): 117 | futex = liburing.futex_state() 118 | f0 = liburing.futex_waitv(futex) 119 | f0.flags = liburing.FUTEX2_SIZE_U32 120 | 121 | # Submit two futex waits 122 | sqe = liburing.io_uring_get_sqe(ring) 123 | liburing.io_uring_prep_futex_waitv(sqe, f0) 124 | sqe.user_data = 1 125 | 126 | sqe = liburing.io_uring_get_sqe(ring) 127 | liburing.io_uring_prep_futex_waitv(sqe, f0) 128 | sqe.user_data = 2 129 | 130 | assert liburing.io_uring_submit(ring) == 2 131 | 132 | # Now submit wake for just one futex 133 | futex.state = 1 134 | sqe = liburing.io_uring_get_sqe(ring) 135 | liburing.io_uring_prep_futex_wake(sqe, futex, 2, 136 | liburing.FUTEX_BITSET_MATCH_ANY, liburing.FUTEX2_SIZE_U32) 137 | sqe.user_data = 100 138 | 139 | assert liburing.io_uring_submit(ring) == 1 140 | 141 | # We expect to find completions for the both futex waits, and the futex wake. 142 | for i in range(3): 143 | assert liburing.io_uring_wait_cqe(ring, cqe) == 0 144 | assert cqe.res >= 0 145 | liburing.io_uring_cqe_seen(ring, cqe) 146 | assert liburing.io_uring_peek_cqe(ring, cqe) == -errno.EAGAIN 147 | 148 | 149 | @pytest.mark.skip_linux(6.7) 150 | def test_futex_wake_zero(ring, cqe): 151 | val = 0 152 | mask = liburing.FUTEX_BITSET_MATCH_ANY 153 | futex = liburing.futex_state() 154 | futex_flags = liburing.FUTEX2_SIZE_U32 155 | 156 | sqe = liburing.io_uring_get_sqe(ring) 157 | liburing.io_uring_prep_futex_wait(sqe, futex, val, mask, futex_flags) 158 | sqe.user_data = 1 159 | assert liburing.io_uring_submit(ring) == 1 160 | 161 | sqe = liburing.io_uring_get_sqe(ring) 162 | liburing.io_uring_prep_futex_wake(sqe, futex, val, mask, futex_flags) 163 | sqe.user_data = 2 164 | assert liburing.io_uring_submit(ring) == 1 165 | 166 | assert liburing.io_uring_wait_cqe(ring, cqe) == 0 167 | 168 | # should get zero res and it should be the wake 169 | assert cqe.res == 0 and cqe.user_data == 2 170 | liburing.io_uring_cqe_seen(ring, cqe) 171 | 172 | # should not have the wait complete 173 | assert liburing.io_uring_peek_cqe(ring, cqe) == -errno.EAGAIN 174 | -------------------------------------------------------------------------------- /test/helper/io_uring_put_sqe_test.py: -------------------------------------------------------------------------------- 1 | from liburing import io_uring_put_sqe, io_uring, io_uring_cqe, io_uring_submit, \ 2 | io_uring_sqe, io_uring_queue_init, io_uring_queue_exit, \ 3 | io_uring_wait_cqes, io_uring_cq_advance, io_uring_prep_nop 4 | 5 | 6 | def test_io_uring_put_sqe(): 7 | assert _loop(1, 2) is False # entries < sqe 8 | assert _loop(2, 2) is True 9 | assert _loop(8, 2) is True 10 | assert _loop(1, 0) is True # sqe = 0, its ok to submit 0 entries 11 | assert _loop(1024, 1024) is True 12 | 13 | # note: entries is rounded up to the nearest power of `2` 14 | assert _loop(3, 4) is True 15 | assert _loop(3, 5) is False 16 | assert _loop(5, 8) is True 17 | 18 | 19 | def _loop(entries, num): 20 | ring = io_uring() 21 | cqe = io_uring_cqe() 22 | try: 23 | assert io_uring_queue_init(entries, ring) == 0 24 | sqe = io_uring_sqe(num) 25 | for i in range(num): 26 | io_uring_prep_nop(sqe[i]) 27 | sqe[i].user_data = i+1 # user_data can not be `0` 28 | if io_uring_put_sqe(ring, sqe): 29 | io_uring_submit(ring) 30 | else: 31 | return False 32 | if num: 33 | assert io_uring_wait_cqes(ring, cqe, num) == 0 34 | for i in range(num): 35 | assert cqe[i].user_data == i+1 36 | io_uring_cq_advance(ring, num) 37 | return True 38 | finally: 39 | io_uring_queue_exit(ring) 40 | -------------------------------------------------------------------------------- /test/iovec_test.py: -------------------------------------------------------------------------------- 1 | import re 2 | import pytest 3 | from liburing import SC_IOV_MAX, iovec 4 | 5 | 6 | def test_iovec(): 7 | assert len(iovec(bytes(1))) == 1 8 | assert len(iovec(bytearray(2))) == 1 9 | assert len(iovec(memoryview(bytearray(3)))) == 1 10 | 11 | # read 12 | read_bytes = [bytes(1), bytes(2), bytes(3)] 13 | read_bytearray = [bytearray(2), bytearray(1)] 14 | read_memoryview = [memoryview(bytearray(3))] 15 | 16 | assert len(iovec(read_bytes)) == 3 17 | assert len(iovec(read_bytearray)) == 2 18 | assert len(iovec(read_memoryview)) == 1 19 | 20 | assert iovec(read_bytes).iov_len == 1 21 | assert iovec(read_bytearray).iov_len == 2 22 | assert iovec(read_memoryview).iov_len == 3 23 | 24 | # empty for internal use 25 | iov = iovec(None) 26 | assert len(iov) == 0 27 | assert bool(iov) is False 28 | 29 | # write 30 | write_bytes = [b'a'] 31 | write_bytearray = [bytearray(b'bb')] 32 | write_memoryview = [memoryview(bytearray(b'ccc'))] 33 | 34 | assert iovec(write_bytes).iov_len == 1 35 | assert iovec(write_bytearray).iov_len == 2 36 | assert iovec(write_memoryview).iov_len == 3 37 | assert iovec(write_bytes).iov_base == b'a' 38 | assert iovec(write_bytearray).iov_base == b'bb' 39 | assert iovec(write_memoryview).iov_base == b'ccc' 40 | 41 | iov_max = SC_IOV_MAX + 1 42 | buffers = [bytes(1)] * iov_max 43 | error = re.escape(f"`iovec()` - `buffers` length of {iov_max} exceeds `SC_IOV_MAX` limit set by OS of {SC_IOV_MAX}") 44 | with pytest.raises(OverflowError, match=error): 45 | iovec(buffers) 46 | 47 | with pytest.raises(ValueError, match=re.escape('`iovec()` can not be length of `0`')): 48 | iovec([b'']) 49 | -------------------------------------------------------------------------------- /test/lib/file_define_test.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YoSTEALTH/Liburing/5898d15bfae088173cf50674876c5d1e3e5f1b70/test/lib/file_define_test.py -------------------------------------------------------------------------------- /test/lib/futex_define_test.py: -------------------------------------------------------------------------------- 1 | import liburing 2 | 3 | 4 | def test_futex_define(): 5 | assert liburing.FUTEX_WAIT == 0 6 | assert liburing.FUTEX_WAKE == 1 7 | assert liburing.FUTEX_FD == 2 8 | assert liburing.FUTEX_REQUEUE == 3 9 | assert liburing.FUTEX_CMP_REQUEUE == 4 10 | assert liburing.FUTEX_WAKE_OP == 5 11 | assert liburing.FUTEX_LOCK_PI == 6 12 | assert liburing.FUTEX_UNLOCK_PI == 7 13 | assert liburing.FUTEX_TRYLOCK_PI == 8 14 | assert liburing.FUTEX_WAIT_BITSET == 9 15 | assert liburing.FUTEX_WAKE_BITSET == 10 16 | assert liburing.FUTEX_WAIT_REQUEUE_PI == 11 17 | assert liburing.FUTEX_CMP_REQUEUE_PI == 12 18 | assert liburing.FUTEX_LOCK_PI2 == 13 19 | 20 | if liburing.linux_version_check(6.7): 21 | assert liburing.FUTEX2_PRIVATE == 0 22 | assert liburing.FUTEX2_SIZE_U8 == 0 23 | assert liburing.FUTEX2_SIZE_U16 == 0 24 | assert liburing.FUTEX2_SIZE_U32 == 0 25 | assert liburing.FUTEX2_SIZE_U64 == 0 26 | assert liburing.FUTEX2_NUMA == 0 27 | 28 | assert liburing.FUTEX2_PRIVATE | liburing.FUTEX_WAIT == 0 29 | assert liburing.FUTEX2_PRIVATE | liburing.FUTEX_WAKE == 1 30 | else: 31 | assert liburing.FUTEX2_PRIVATE == 128 32 | assert liburing.FUTEX2_SIZE_U8 == 0x00 33 | assert liburing.FUTEX2_SIZE_U16 == 0x01 34 | assert liburing.FUTEX2_SIZE_U32 == 0x02 35 | assert liburing.FUTEX2_SIZE_U64 == 0x03 36 | assert liburing.FUTEX2_NUMA == 0x04 37 | 38 | assert liburing.FUTEX2_PRIVATE | liburing.FUTEX_WAIT == 128 39 | assert liburing.FUTEX2_PRIVATE | liburing.FUTEX_WAKE == 129 40 | 41 | assert liburing.FUTEX_WAITV_MAX == 128 42 | assert liburing.FUTEX_BITSET_MATCH_ANY == 0xffffffff 43 | -------------------------------------------------------------------------------- /test/lib/poll_define_test.py: -------------------------------------------------------------------------------- 1 | # TODO: there is nothing to test yet since epoll flags haven't been defined. 2 | -------------------------------------------------------------------------------- /test/lib/socket_define_test.py: -------------------------------------------------------------------------------- 1 | import liburing 2 | 3 | 4 | def test_socket_define(): 5 | assert liburing.AF_UNIX == 1 6 | assert liburing.AF_INET == 2 7 | assert liburing.AF_INET6 == 10 8 | 9 | assert liburing.SOCK_STREAM == 1 10 | assert liburing.SOCK_DGRAM == 2 11 | assert liburing.SOCK_RAW == 3 12 | assert liburing.SOCK_RDM == 4 13 | assert liburing.SOCK_SEQPACKET == 5 14 | assert liburing.SOCK_DCCP == 6 15 | assert liburing.SOCK_PACKET == 10 16 | assert liburing.SOCK_CLOEXEC == 0o2000000 17 | assert liburing.SOCK_NONBLOCK == 0o4000 18 | 19 | assert liburing.SHUT_RD == 0 20 | assert liburing.SHUT_WR == 1 21 | assert liburing.SHUT_RDWR == 2 22 | 23 | assert liburing.SOCKET_URING_OP_SIOCINQ == 0 24 | assert liburing.SOCKET_URING_OP_SIOCOUTQ == 1 25 | assert liburing.SOCKET_URING_OP_GETSOCKOPT == 2 26 | assert liburing.SOCKET_URING_OP_SETSOCKOPT == 3 27 | 28 | # setsockopt & getsockopt start >>> 29 | assert liburing.SOL_SOCKET == 1 30 | assert liburing.SO_DEBUG == 1 31 | assert liburing.SO_REUSEADDR == 2 32 | assert liburing.SO_TYPE == 3 33 | assert liburing.SO_ERROR == 4 34 | assert liburing.SO_DONTROUTE == 5 35 | assert liburing.SO_BROADCAST == 6 36 | assert liburing.SO_SNDBUF == 7 37 | assert liburing.SO_RCVBUF == 8 38 | assert liburing.SO_SNDBUFFORCE == 32 39 | assert liburing.SO_RCVBUFFORCE == 33 40 | assert liburing.SO_KEEPALIVE == 9 41 | assert liburing.SO_OOBINLINE == 10 42 | assert liburing.SO_NO_CHECK == 11 43 | assert liburing.SO_PRIORITY == 12 44 | assert liburing.SO_LINGER == 13 45 | assert liburing.SO_BSDCOMPAT == 14 46 | assert liburing.SO_REUSEPORT == 15 47 | assert liburing.SO_PASSCRED == 16 48 | assert liburing.SO_PEERCRED == 17 49 | assert liburing.SO_RCVLOWAT == 18 50 | assert liburing.SO_SNDLOWAT == 19 51 | assert liburing.SO_BINDTODEVICE == 25 52 | 53 | # Socket filtering 54 | assert liburing.SO_ATTACH_FILTER == 26 55 | assert liburing.SO_DETACH_FILTER == 27 56 | assert liburing.SO_GET_FILTER == liburing.SO_ATTACH_FILTER 57 | assert liburing.SO_PEERNAME == 28 58 | assert liburing.SO_ACCEPTCONN == 30 59 | assert liburing.SO_PEERSEC == 31 60 | assert liburing.SO_PASSSEC == 34 61 | assert liburing.SO_MARK == 36 62 | assert liburing.SO_PROTOCOL == 38 63 | assert liburing.SO_DOMAIN == 39 64 | assert liburing.SO_RXQ_OVFL == 40 65 | assert liburing.SO_WIFI_STATUS == 41 66 | assert liburing.SCM_WIFI_STATUS == liburing.SO_WIFI_STATUS 67 | assert liburing.SO_PEEK_OFF == 42 68 | # setsockopt & getsockopt end <<< 69 | 70 | assert liburing.IPPROTO_IP == 0 71 | assert liburing.IPPROTO_ICMP == 1 72 | assert liburing.IPPROTO_IGMP == 2 73 | assert liburing.IPPROTO_IPIP == 4 74 | assert liburing.IPPROTO_TCP == 6 75 | assert liburing.IPPROTO_EGP == 8 76 | assert liburing.IPPROTO_PUP == 12 77 | assert liburing.IPPROTO_UDP == 17 78 | assert liburing.IPPROTO_IDP == 22 79 | assert liburing.IPPROTO_TP == 29 80 | assert liburing.IPPROTO_DCCP == 33 81 | assert liburing.IPPROTO_IPV6 == 41 82 | assert liburing.IPPROTO_RSVP == 46 83 | assert liburing.IPPROTO_GRE == 47 84 | assert liburing.IPPROTO_ESP == 50 85 | assert liburing.IPPROTO_AH == 51 86 | assert liburing.IPPROTO_MTP == 92 87 | assert liburing.IPPROTO_BEETPH == 94 88 | assert liburing.IPPROTO_ENCAP == 98 89 | assert liburing.IPPROTO_PIM == 103 90 | assert liburing.IPPROTO_COMP == 108 91 | # note: not supported 92 | # assert liburing.IPPROTO_L2TP == 115 93 | assert liburing.IPPROTO_SCTP == 132 94 | assert liburing.IPPROTO_UDPLITE == 136 95 | assert liburing.IPPROTO_MPLS == 137 96 | assert liburing.IPPROTO_ETHERNET == 143 97 | assert liburing.IPPROTO_RAW == 255 98 | assert liburing.IPPROTO_MPTCP == 262 99 | 100 | 101 | def test_socket_extra_define(): 102 | # getaddrinfo/getnameinfo start >>> 103 | assert liburing.AI_PASSIVE == 0x0001 104 | assert liburing.AI_CANONNAME == 0x0002 105 | assert liburing.AI_NUMERICHOST == 0x0004 106 | assert liburing.AI_V4MAPPED == 0x0008 107 | assert liburing.AI_ALL == 0x0010 108 | assert liburing.AI_ADDRCONFIG == 0x0020 109 | assert liburing.AI_IDN == 0x0040 110 | assert liburing.AI_CANONIDN == 0x0080 111 | assert liburing.AI_NUMERICSERV == 0x0400 112 | 113 | assert liburing.NI_NUMERICHOST == 1 114 | assert liburing.NI_NUMERICSERV == 2 115 | assert liburing.NI_NOFQDN == 4 116 | assert liburing.NI_NAMEREQD == 8 117 | assert liburing.NI_DGRAM == 16 118 | assert liburing.NI_IDN == 32 119 | # getaddrinfo/getnameinfo end <<< 120 | -------------------------------------------------------------------------------- /test/lib/statx_define_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import liburing 3 | 4 | 5 | @pytest.mark.skip_linux('5.6') 6 | def test_statx_define(): 7 | assert liburing.STATX_TYPE == 0x00000001 8 | assert liburing.STATX_MODE == 0x00000002 9 | assert liburing.STATX_NLINK == 0x00000004 10 | assert liburing.STATX_UID == 0x00000008 11 | assert liburing.STATX_GID == 0x00000010 12 | assert liburing.STATX_ATIME == 0x00000020 13 | assert liburing.STATX_MTIME == 0x00000040 14 | assert liburing.STATX_CTIME == 0x00000080 15 | assert liburing.STATX_INO == 0x00000100 16 | assert liburing.STATX_SIZE == 0x00000200 17 | assert liburing.STATX_BLOCKS == 0x00000400 18 | assert liburing.STATX_BASIC_STATS == 0x000007ff 19 | assert liburing.STATX_BTIME == 0x00000800 20 | assert liburing.STATX_MNT_ID == 0x00001000 21 | # note: not supported 22 | # assert liburing.STATX_DIOALIGN == 0x00002000 23 | 24 | assert liburing.STATX_ATTR_COMPRESSED == 0x00000004 25 | assert liburing.STATX_ATTR_IMMUTABLE == 0x00000010 26 | assert liburing.STATX_ATTR_APPEND == 0x00000020 27 | assert liburing.STATX_ATTR_NODUMP == 0x00000040 28 | assert liburing.STATX_ATTR_ENCRYPTED == 0x00000800 29 | assert liburing.STATX_ATTR_AUTOMOUNT == 0x00001000 30 | assert liburing.STATX_ATTR_MOUNT_ROOT == 0x00002000 31 | assert liburing.STATX_ATTR_VERITY == 0x00100000 32 | assert liburing.STATX_ATTR_DAX == 0x00200000 33 | 34 | assert liburing.S_IFMT == 0o170000 35 | 36 | assert liburing.S_IFSOCK == 0o140000 37 | assert liburing.S_IFLNK == 0o120000 38 | assert liburing.S_IFREG == 0o100000 39 | assert liburing.S_IFBLK == 0o60000 40 | assert liburing.S_IFDIR == 0o40000 41 | assert liburing.S_IFCHR == 0o20000 42 | assert liburing.S_IFIFO == 0o10000 43 | 44 | assert liburing.S_ISUID == 0o4000 45 | assert liburing.S_ISGID == 0o2000 46 | assert liburing.S_ISVTX == 0o1000 47 | 48 | assert liburing.S_IRWXU == 0o700 49 | assert liburing.S_IRUSR == 0o400 50 | assert liburing.S_IWUSR == 0o200 51 | assert liburing.S_IXUSR == 0o100 52 | 53 | assert liburing.S_IRWXG == 0o70 54 | assert liburing.S_IRGRP == 0o40 55 | assert liburing.S_IWGRP == 0o20 56 | assert liburing.S_IXGRP == 0o10 57 | 58 | assert liburing.S_IRWXO == 0o7 59 | assert liburing.S_IROTH == 0o4 60 | assert liburing.S_IWOTH == 0o2 61 | assert liburing.S_IXOTH == 0o1 62 | 63 | # assert liburing.AT_STATX_SYNC_TYPE == 0x6000 # skipping: not documented 64 | assert liburing.AT_STATX_SYNC_AS_STAT == 0x0000 65 | assert liburing.AT_STATX_FORCE_SYNC == 0x2000 66 | assert liburing.AT_STATX_DONT_SYNC == 0x4000 67 | -------------------------------------------------------------------------------- /test/lib/type_define_test.py: -------------------------------------------------------------------------------- 1 | import liburing 2 | 3 | 4 | def test_type_define(): 5 | # flags for `renameat2`. 6 | assert liburing.RENAME_NOREPLACE == 1 << 0 7 | assert liburing.RENAME_EXCHANGE == 1 << 1 8 | assert liburing.RENAME_WHITEOUT == 1 << 2 9 | 10 | # AT_* flags 11 | assert liburing.AT_FDCWD == -100 12 | assert liburing.AT_SYMLINK_FOLLOW == 0x400 13 | assert liburing.AT_SYMLINK_NOFOLLOW == 0x100 14 | assert liburing.AT_REMOVEDIR == 0x200 15 | assert liburing.AT_NO_AUTOMOUNT == 0x800 16 | assert liburing.AT_EMPTY_PATH == 0x1000 17 | assert liburing.AT_RECURSIVE == 0x8000 18 | 19 | # splice flags 20 | assert liburing.SPLICE_F_MOVE == 1 21 | assert liburing.SPLICE_F_NONBLOCK == 2 22 | assert liburing.SPLICE_F_MORE == 4 23 | assert liburing.SPLICE_F_GIFT == 8 24 | 25 | # `fallocate` mode 26 | assert liburing.FALLOC_FL_KEEP_SIZE == 0x01 27 | assert liburing.FALLOC_FL_PUNCH_HOLE == 0x02 28 | assert liburing.FALLOC_FL_NO_HIDE_STALE == 0x04 29 | assert liburing.FALLOC_FL_COLLAPSE_RANGE == 0x08 30 | assert liburing.FALLOC_FL_ZERO_RANGE == 0x10 31 | assert liburing.FALLOC_FL_INSERT_RANGE == 0x20 32 | assert liburing.FALLOC_FL_UNSHARE_RANGE == 0x40 33 | -------------------------------------------------------------------------------- /test/lib/uring_define_test.py: -------------------------------------------------------------------------------- 1 | import liburing 2 | 3 | 4 | def test_uring_defines(): 5 | assert liburing.LIBURING_UDATA_TIMEOUT == 18446744073709551615 6 | -------------------------------------------------------------------------------- /test/os/splice_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import os.path 3 | import pytest 4 | import liburing 5 | 6 | 7 | @pytest.mark.skip_linux(5.7) 8 | def test_clone_file_using_splice(tmp_dir, ring, cqe): 9 | flags = liburing.SPLICE_F_MOVE | liburing.SPLICE_F_MORE 10 | data = b'hello world' 11 | buf_len = len(data) 12 | ts = liburing.timespec(3) 13 | 14 | fd_in = os.open(os.path.join(tmp_dir, 'splice-1.txt'), os.O_RDWR | os.O_CREAT, 0o660) 15 | os.write(fd_in, data) 16 | fd_out = os.open(os.path.join(tmp_dir, 'splice-2.txt'), os.O_RDWR | os.O_CREAT, 0o660) 17 | r, w = os.pipe() 18 | 19 | try: 20 | # read from file "splice-1.txt" 21 | sqe = liburing.io_uring_get_sqe(ring) 22 | liburing.io_uring_prep_splice(sqe, fd_in, 0, w, -1, buf_len, flags) 23 | sqe.user_data = 1 24 | 25 | # chain top and bottom sqe 26 | sqe.flags |= liburing.IOSQE_IO_LINK 27 | 28 | # write to file "splice-2.txt" 29 | sqe = liburing.io_uring_get_sqe(ring) 30 | liburing.io_uring_prep_splice(sqe, r, -1, fd_out, 0, buf_len, flags) 31 | sqe.user_data = 2 32 | 33 | assert liburing.io_uring_submit_and_wait_timeout(ring, cqe, 2, ts) == 2 34 | assert cqe.res == 11 35 | assert cqe.user_data == 1 36 | assert cqe[1].res == 11 37 | assert cqe[1].user_data == 2 38 | liburing.io_uring_cq_advance(ring, 2) 39 | 40 | assert os.read(fd_out, buf_len) == data 41 | 42 | finally: 43 | os.close(r) 44 | os.close(w) 45 | os.close(fd_in) 46 | os.close(fd_out) 47 | -------------------------------------------------------------------------------- /test/os/unlink_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import os.path 3 | import pytest 4 | import liburing 5 | 6 | 7 | @pytest.mark.skip_linux(5.11) 8 | def test_unlink(tmp_dir, ring, cqe): 9 | file_path = tmp_dir / 'file1.txt' 10 | file_path.write_text('test1') 11 | file_path = str(file_path).encode() 12 | 13 | dir_path = tmp_dir / 'directory1' 14 | dir_path.mkdir() 15 | dir_path = str(dir_path).encode() 16 | 17 | assert os.path.isfile(file_path) 18 | assert os.path.isdir(dir_path) 19 | 20 | sqe = liburing.io_uring_get_sqe(ring) 21 | liburing.io_uring_prep_unlink(sqe, file_path) 22 | sqe.flags = liburing.IOSQE_IO_LINK | liburing.IOSQE_ASYNC 23 | sqe.user_data = 1 24 | 25 | sqe = liburing.io_uring_get_sqe(ring) 26 | liburing.io_uring_prep_unlink(sqe, dir_path, liburing.AT_REMOVEDIR) 27 | sqe.user_data = 2 28 | 29 | assert liburing.io_uring_submit_and_wait_timeout(ring, cqe, 2) == 2 30 | 31 | for i in range(2): 32 | assert liburing.trap_error(cqe[i].res) == 0 33 | assert cqe[i].user_data == i+1 34 | liburing.io_uring_cq_advance(ring, 2) 35 | 36 | assert not os.path.exists(file_path) # file should not exist 37 | assert not os.path.exists(dir_path) # dir should not exist 38 | 39 | 40 | @pytest.mark.skip_linux(5.11) 41 | def test_unlinkat(tmp_dir, ring, cqe): 42 | file_path = tmp_dir / 'file2.txt' 43 | file_path.write_text('test2') 44 | file_path = str(file_path).encode() 45 | 46 | dir_path = tmp_dir / 'directory2' 47 | dir_path.mkdir() 48 | dir_path = str(dir_path).encode() 49 | 50 | assert os.path.isfile(file_path) 51 | assert os.path.isdir(dir_path) 52 | 53 | sqe = liburing.io_uring_get_sqe(ring) 54 | liburing.io_uring_prep_unlinkat(sqe, file_path) 55 | sqe.flags = liburing.IOSQE_IO_LINK | liburing.IOSQE_ASYNC 56 | sqe.user_data = 1 57 | 58 | sqe = liburing.io_uring_get_sqe(ring) 59 | liburing.io_uring_prep_unlinkat(sqe, dir_path, liburing.AT_REMOVEDIR) 60 | sqe.user_data = 2 61 | 62 | assert liburing.io_uring_submit_and_wait_timeout(ring, cqe, 2) == 2 63 | 64 | for i in range(2): 65 | assert liburing.trap_error(cqe[i].res) == 0 66 | assert cqe[i].user_data == i+1 67 | liburing.io_uring_cq_advance(ring, 2) 68 | 69 | assert not os.path.exists(file_path) # file should not exist 70 | assert not os.path.exists(dir_path) # dir should not exist 71 | 72 | 73 | @pytest.mark.skip_linux(5.11) 74 | def test_unlinkat_error(tmp_dir, ring, cqe): 75 | file_path = tmp_dir / 'file3.txt' 76 | file_path.write_text('test3') 77 | file_path = str(file_path).encode() 78 | 79 | dir_path = tmp_dir / 'directory3' 80 | dir_path.mkdir() 81 | dir_path = str(dir_path).encode() 82 | 83 | assert os.path.isfile(file_path) 84 | assert os.path.isdir(dir_path) 85 | 86 | sqe = liburing.io_uring_get_sqe(ring) 87 | with pytest.raises(TypeError): 88 | liburing.io_uring_prep_unlinkat(sqe, 'string_path') 89 | liburing.io_uring_prep_unlinkat(sqe, file_path, liburing.AT_REMOVEDIR) # not wrong flag 90 | sqe.user_data = 1 91 | 92 | assert liburing.io_uring_submit(ring) == 1 93 | assert liburing.io_uring_wait_cqe(ring, cqe) == 0 94 | 95 | with pytest.raises(NotADirectoryError): 96 | assert liburing.trap_error(cqe.res) == 0 97 | assert cqe.user_data == 1 98 | liburing.io_uring_cqe_seen(ring, cqe) 99 | 100 | sqe = liburing.io_uring_get_sqe(ring) 101 | liburing.io_uring_prep_unlinkat(sqe, dir_path) # not using flag to remove dir 102 | sqe.user_data = 1 103 | 104 | assert liburing.io_uring_submit(ring) == 1 105 | assert liburing.io_uring_wait_cqe(ring, cqe) == 0 106 | 107 | with pytest.raises(IsADirectoryError): 108 | assert liburing.trap_error(cqe.res) == 0 109 | assert cqe.user_data == 1 110 | 111 | liburing.io_uring_cqe_seen(ring, cqe) 112 | assert os.path.exists(file_path) # dir should exist 113 | assert os.path.exists(dir_path) # dir should exist 114 | -------------------------------------------------------------------------------- /test/probe_test.py: -------------------------------------------------------------------------------- 1 | from re import escape 2 | from errno import EINVAL 3 | from pytest import raises 4 | from liburing import probe, io_uring_get_probe, io_uring_get_probe_ring, io_uring_free_probe, \ 5 | io_uring_register_probe, io_uring_probe 6 | 7 | 8 | def test_probe(): 9 | op = probe() 10 | assert op['IORING_OP_NOP'] is True 11 | assert op.get('IORING_OP_LAST', None) is None 12 | 13 | p = io_uring_get_probe() 14 | io_uring_free_probe(p) 15 | with raises(MemoryError, match=escape('`io_uring_probe()` is out of memory!')): 16 | p.ops_len 17 | 18 | 19 | def test_probe_ring(ring): 20 | p = io_uring_get_probe_ring(ring) 21 | assert p.ops_len > 31 22 | assert p.last_op > 30 23 | assert p.last_op == p.ops_len - 1 24 | io_uring_free_probe(p) 25 | 26 | 27 | def test_probe_register(ring): 28 | p = io_uring_probe(1) 29 | with raises(OSError) as e: 30 | io_uring_register_probe(ring, p, 256) 31 | assert e.value.errno == EINVAL 32 | # free is doen by the `__dealloc__` since `num` is set 33 | 34 | p = io_uring_probe(4) 35 | assert io_uring_register_probe(ring, p, 4) == 0 36 | # free is doen by the `__dealloc__` since `num` is set 37 | -------------------------------------------------------------------------------- /test/queue/get_cqe_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import liburing 3 | 4 | 5 | def test_for_each(ring, cqe): 6 | loop = 300 7 | for i in range(loop): 8 | sqe = liburing.io_uring_get_sqe(ring) 9 | sqe.user_data = i+1 10 | 11 | assert (counter := liburing.io_uring_submit(ring)) == loop 12 | liburing.io_uring_wait_cqe_nr(ring, cqe, counter) # wait for all to be ready 13 | 14 | # >>> difference START 15 | cq_ready = 0 16 | for i in range(liburing.io_uring_for_each_cqe(ring, cqe)): 17 | assert cqe[i].user_data == i+1 18 | cq_ready += 1 19 | # <<< difference END 20 | 21 | assert cq_ready == loop 22 | liburing.io_uring_cq_advance(ring, cq_ready) # free seen entries 23 | 24 | 25 | @pytest.mark.skip('BUG') 26 | def test_peek_batch(ring, cqe): 27 | loop = 300 28 | for i in range(loop): 29 | sqe = liburing.io_uring_get_sqe(ring) 30 | sqe.user_data = i+1 31 | 32 | assert (counter := liburing.io_uring_submit(ring)) == loop 33 | liburing.io_uring_wait_cqe_nr(ring, cqe, counter) # wait for all to be ready 34 | 35 | # >>> difference START 36 | cq_ready = liburing.io_uring_peek_batch_cqe(ring, cqe, counter) 37 | for i in range(cq_ready): 38 | assert cqe[i].user_data == i+1 39 | # <<< difference END 40 | 41 | assert cq_ready == loop 42 | liburing.io_uring_cq_advance(ring, cq_ready) # free seen entries 43 | -------------------------------------------------------------------------------- /test/queue/init_exit_test.py: -------------------------------------------------------------------------------- 1 | import errno 2 | import pytest 3 | import liburing 4 | 5 | 6 | def test_io_uring_init_exit(): 7 | ring = liburing.io_uring() 8 | assert ring.flags is None 9 | assert ring.ring_fd is None 10 | assert ring.features is None 11 | assert ring.enter_ring_fd is None 12 | assert ring.int_flags is None 13 | 14 | assert liburing.io_uring_queue_init(8, ring, 0) == 0 15 | assert ring.ring_fd > 0 16 | assert ring.enter_ring_fd > 0 17 | with pytest.raises(RuntimeError) as e: # Trying to init again 18 | liburing.io_uring_queue_init(4, ring, 0) 19 | assert str(ring).startswith('io_uring(flags=') 20 | assert liburing.io_uring_queue_exit(ring) == 0 21 | assert str(ring).startswith(' 0 16 | assert cqe.user_data == 1 17 | liburing.io_uring_cqe_seen(ring, cqe) 18 | 19 | # python socket 20 | s = socket.socket(fileno=sockfd) 21 | assert s.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) == 0 22 | 23 | sqe = liburing.io_uring_get_sqe(ring) # will reuse 24 | with pytest.raises(ValueError): 25 | val = array.array('b', [0]) 26 | liburing.io_uring_prep_setsockopt(sqe, 27 | sockfd, liburing.SOL_SOCKET, liburing.SO_REUSEADDR, val) 28 | with pytest.raises(ValueError): 29 | val = array.array('b', [0]) 30 | liburing.io_uring_prep_getsockopt(sqe, 31 | sockfd, liburing.SOL_SOCKET, liburing.SO_REUSEADDR, val) 32 | # set 33 | val_set = array.array('i', [1]) 34 | liburing.io_uring_prep_setsockopt(sqe, 35 | sockfd, liburing.SOL_SOCKET, liburing.SO_REUSEADDR, val_set) 36 | sqe.user_data = 2 37 | assert liburing.io_uring_submit_and_wait_timeout(ring, cqe, 1, ts) == 1 38 | assert liburing.trap_error(cqe.res) == 0 39 | assert cqe.user_data == 2 40 | liburing.io_uring_cqe_seen(ring, cqe) 41 | assert val_set[0] == 1 42 | 43 | # get 44 | val_get = array.array('i', [0]) 45 | sqe = liburing.io_uring_get_sqe(ring) 46 | liburing.io_uring_prep_getsockopt(sqe, 47 | sockfd, liburing.SOL_SOCKET, liburing.SO_REUSEADDR, val_get) 48 | sqe.user_data = 3 49 | assert liburing.io_uring_submit_and_wait_timeout(ring, cqe, 1, ts) == 1 50 | assert liburing.trap_error(cqe.res) >= 2 # return `sizeof` int 51 | assert cqe.user_data == 3 52 | liburing.io_uring_cqe_seen(ring, cqe) 53 | assert val_get[0] == 1 54 | assert s.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) == 1 55 | 56 | # close socket 57 | sqe = liburing.io_uring_get_sqe(ring) 58 | liburing.io_uring_prep_close(sqe, sockfd) 59 | sqe.user_data = 4 60 | assert liburing.io_uring_submit_and_wait_timeout(ring, cqe, 1, ts) == 1 61 | assert liburing.trap_error(cqe.res) == 0 62 | assert cqe.user_data == 4 63 | liburing.io_uring_cqe_seen(ring, cqe) 64 | 65 | 66 | @pytest.mark.skip_linux('6.7') 67 | def test_cmd_sock(ring, cqe): 68 | ts = liburing.timespec(3) 69 | # socket 70 | sqe = liburing.io_uring_get_sqe(ring) 71 | liburing.io_uring_prep_socket(sqe, liburing.AF_INET, liburing.SOL_SOCKET) 72 | sqe.user_data = 1 73 | assert liburing.io_uring_submit_and_wait_timeout(ring, cqe, 1, ts) == 1 74 | assert (sockfd := liburing.trap_error(cqe.res)) > 0 75 | assert cqe.user_data == 1 76 | liburing.io_uring_cqe_seen(ring, cqe) 77 | 78 | sqe = liburing.io_uring_get_sqe(ring) 79 | with pytest.raises(ValueError): 80 | val = array.array('b', [0]) 81 | liburing.io_uring_prep_cmd_sock(sqe, liburing.SOCKET_URING_OP_SETSOCKOPT, 82 | sockfd, liburing.SOL_SOCKET, liburing.SO_REUSEADDR, val) 83 | # set 84 | val_set = array.array('i', [1]) 85 | liburing.io_uring_prep_cmd_sock(sqe, liburing.SOCKET_URING_OP_SETSOCKOPT, 86 | sockfd, liburing.SOL_SOCKET, liburing.SO_REUSEADDR, val_set) 87 | sqe.user_data = 2 88 | assert liburing.io_uring_submit_and_wait_timeout(ring, cqe, 1, ts) == 1 89 | assert liburing.trap_error(cqe.res) == 0 90 | assert cqe.user_data == 2 91 | liburing.io_uring_cqe_seen(ring, cqe) 92 | assert val_set[0] == 1 93 | 94 | # get 95 | val_get = array.array('i', [0]) 96 | sqe = liburing.io_uring_get_sqe(ring) 97 | liburing.io_uring_prep_cmd_sock(sqe, liburing.SOCKET_URING_OP_GETSOCKOPT, 98 | sockfd, liburing.SOL_SOCKET, liburing.SO_REUSEADDR, val_get) 99 | sqe.user_data = 3 100 | assert liburing.io_uring_submit_and_wait_timeout(ring, cqe, 1, ts) == 1 101 | assert liburing.trap_error(cqe.res) >= 2 # return `sizeof` int 102 | assert cqe.user_data == 3 103 | liburing.io_uring_cqe_seen(ring, cqe) 104 | assert val_get[0] == 1 105 | 106 | # close socket 107 | sqe = liburing.io_uring_get_sqe(ring) 108 | liburing.io_uring_prep_close(sqe, sockfd) 109 | sqe.user_data = 4 110 | assert liburing.io_uring_submit_and_wait_timeout(ring, cqe, 1, ts) == 1 111 | assert liburing.trap_error(cqe.res) == 0 112 | assert cqe.user_data == 4 113 | liburing.io_uring_cqe_seen(ring, cqe) 114 | -------------------------------------------------------------------------------- /test/socket/getaddinfo_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import liburing 3 | 4 | 5 | def test_getaddrinfo(): 6 | assert len(list(liburing.getaddrinfo(b'127.0.0.1', b'12345'))) == 2 7 | assert len(list(liburing.getaddrinfo(b'python.org', b'80'))) > 12 < 33 8 | for af_, sock_, proto, canon, addr in liburing.getaddrinfo(b'127.0.0.1', b'12345'): 9 | assert af_ == liburing.AF_INET 10 | assert sock_ == liburing.SOCK_STREAM 11 | assert proto == liburing.IPPROTO_TCP 12 | assert canon == b'' 13 | assert type(addr) is liburing.sockaddr 14 | assert addr.family == liburing.AF_INET 15 | break 16 | 17 | msg = 'Servname not supported for ai_socktype' 18 | with pytest.raises(OSError, match=msg): 19 | liburing.getaddrinfo(b'127.0.0.1', b'123abc45') 20 | -------------------------------------------------------------------------------- /test/socket/getnameinfo_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import liburing 3 | 4 | 5 | def test_getnameinfo(ring, cqe): 6 | addr = liburing.sockaddr(liburing.AF_INET, b'0.0.0.0', 12345) 7 | host, port = liburing.getnameinfo(addr, liburing.NI_NUMERICHOST | liburing.NI_NUMERICSERV) 8 | assert host == b'0.0.0.0' 9 | assert port == 12345 10 | 11 | addr = liburing.sockaddr(liburing.AF_INET6, b'::', 12345) 12 | host, port = liburing.getnameinfo(addr, liburing.NI_NUMERICHOST | liburing.NI_NUMERICSERV) 13 | assert host == b'::' 14 | assert port == 12345 15 | 16 | with pytest.raises(ValueError): 17 | addr = liburing.sockaddr(liburing.AF_INET, b'::', 12345) 18 | with pytest.raises(ValueError): 19 | addr = liburing.sockaddr(liburing.AF_INET6, b'0.0.0.0', 12345) 20 | -------------------------------------------------------------------------------- /test/socket/getsockname_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import liburing 3 | 4 | 5 | @pytest.mark.skip_linux('6.11') 6 | def test_getsockname(ring, cqe): 7 | with pytest.raises(TypeError): 8 | addr = liburing.sockaddr(liburing.AF_UNIX, b'./path') 9 | liburing.getsockname(-1, addr) 10 | 11 | ts = liburing.timespec(3) 12 | for i in range(2): 13 | # socket 14 | sqe = liburing.io_uring_get_sqe(ring) 15 | if i: 16 | addr = liburing.sockaddr(liburing.AF_INET6, b'::1', 0) 17 | liburing.io_uring_prep_socket(sqe, liburing.AF_INET6, liburing.SOCK_STREAM) 18 | else: 19 | addr = liburing.sockaddr(liburing.AF_INET, b'127.0.0.1', 0) 20 | liburing.io_uring_prep_socket(sqe, liburing.AF_INET, liburing.SOCK_STREAM) 21 | sqe.user_data = i+1 22 | assert liburing.io_uring_submit_and_wait_timeout(ring, cqe, 1, ts) == 1 23 | sockfd = liburing.trap_error(cqe.res) 24 | assert cqe.user_data == i+1 25 | liburing.io_uring_cqe_seen(ring, cqe) 26 | 27 | # bind 28 | sqe = liburing.io_uring_get_sqe(ring) 29 | liburing.io_uring_prep_bind(sqe, sockfd, addr) 30 | sqe.user_data = i+1 31 | assert liburing.io_uring_submit_and_wait_timeout(ring, cqe, 1, ts) == 1 32 | liburing.trap_error(cqe.res) 33 | assert cqe.user_data == i+1 34 | liburing.io_uring_cqe_seen(ring, cqe) 35 | 36 | ip, port = liburing.getsockname(sockfd, addr) 37 | if i: 38 | assert ip == b'::1' 39 | assert port >= 1000 40 | else: 41 | assert ip == b'127.0.0.1' 42 | assert port >= 1000 43 | 44 | # close 45 | sqe = liburing.io_uring_get_sqe(ring) 46 | liburing.io_uring_prep_close(sqe, sockfd) 47 | sqe.user_data = i+1 48 | assert liburing.io_uring_submit_and_wait_timeout(ring, cqe, 1, ts) == 1 49 | liburing.trap_error(cqe.res) 50 | assert cqe.user_data == i+1 51 | liburing.io_uring_cqe_seen(ring, cqe) 52 | -------------------------------------------------------------------------------- /test/socket/socket_connect_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import liburing 3 | 4 | 5 | def test_sockaddr_class(): 6 | with pytest.raises(NotImplementedError): 7 | liburing.sockaddr(123) 8 | # TODO: ? 9 | # with pytest.raises(ValueError): 10 | # liburing.sockaddr(123, b'hello world hello world') # length over 14 11 | 12 | # AF_UNIX 13 | with pytest.raises(ValueError): 14 | liburing.sockaddr(liburing.AF_UNIX, b'') 15 | with pytest.raises(ValueError): 16 | liburing.sockaddr(liburing.AF_UNIX, b' '*109) 17 | assert liburing.sockaddr(liburing.AF_UNIX, b'./path')._test == { 18 | 'sun_family': 1, 'sun_path': b'./path'} 19 | 20 | # AF_INET 21 | with pytest.raises(ValueError): 22 | liburing.sockaddr(liburing.AF_INET) 23 | with pytest.raises(ValueError): 24 | liburing.sockaddr(liburing.AF_INET, b'', 123) 25 | with pytest.raises(ValueError): 26 | liburing.sockaddr(liburing.AF_INET, b'./path') 27 | with pytest.raises(ValueError): 28 | liburing.sockaddr(liburing.AF_INET, b'bad. ad.dre.ss', 123) 29 | with pytest.raises(ValueError): 30 | liburing.sockaddr(liburing.AF_INET, b'::1', 123) # ipv6 in ipv4 31 | assert liburing.sockaddr(liburing.AF_INET, b'0.0.0.0', 123)._test == { 32 | 'sin_family': 2, 'sin_port': 31488, 'sin_addr': {'s_addr': 0}} 33 | assert liburing.sockaddr(liburing.AF_INET, b'127.0.0.1', 80)._test == { 34 | 'sin_family': 2, 'sin_port': 20480, 'sin_addr': {'s_addr': 16777343}} 35 | 36 | # AF_INET6 37 | with pytest.raises(ValueError): 38 | liburing.sockaddr(liburing.AF_INET6) 39 | with pytest.raises(ValueError): 40 | liburing.sockaddr(liburing.AF_INET6, b'', 123) 41 | with pytest.raises(ValueError): 42 | liburing.sockaddr(liburing.AF_INET6, b'./path') 43 | with pytest.raises(ValueError): 44 | liburing.sockaddr(liburing.AF_INET6, b'123.123.123.123', 123, 234) # IPv4 in IPv6 45 | assert liburing.sockaddr(liburing.AF_INET6, b'::', 123, 321)._test == { 46 | 'sin6_family': 10, 'sin6_port': 31488, 'sin6_flowinfo': 0, 47 | 'sin6_addr': {'s6_addr': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}, 48 | 'sin6_scope_id': 321} 49 | assert liburing.sockaddr(liburing.AF_INET6, b'::1', 65535)._test == { 50 | 'sin6_family': 10, 'sin6_port': 65535, 'sin6_flowinfo': 0, 51 | 'sin6_addr': {'s6_addr': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]}, 52 | 'sin6_scope_id': 0} 53 | 54 | 55 | -------------------------------------------------------------------------------- /test/socket/socket_extra_test.py: -------------------------------------------------------------------------------- 1 | import liburing 2 | 3 | 4 | def test_isIP(): 5 | assert liburing.isIP(liburing.AF_INET, b'0.0.0.0') is True 6 | assert liburing.isIP(liburing.AF_INET6, b'::1') is True 7 | assert liburing.isIP(liburing.AF_INET6, b'domain.ext') is False 8 | assert liburing.isIP(liburing.AF_INET, b'domain.ext') is False 9 | assert liburing.isIP(liburing.AF_UNIX, b'/path/socket') is False 10 | -------------------------------------------------------------------------------- /test/statx_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | import liburing 4 | 5 | 6 | @pytest.mark.skip_linux('5.6') 7 | def test_io_uring_prep_statx(tmp_dir, ring, cqe): 8 | file_path = tmp_dir / 'statx_test.txt' 9 | file_path.write_text('hello world') 10 | file_path = str(file_path).encode() 11 | 12 | statx = liburing.statx() 13 | sqe = liburing.io_uring_get_sqe(ring) 14 | liburing.io_uring_prep_statx(sqe, statx, file_path, liburing.AT_STATX_FORCE_SYNC, 15 | liburing.STATX_BASIC_STATS | liburing.STATX_BTIME) 16 | sqe.user_data = 1 17 | 18 | # end time 19 | assert liburing.io_uring_submit_and_wait_timeout(ring, cqe, 1) == 1 20 | assert cqe.res == 0 21 | assert cqe.user_data == 1 22 | liburing.io_uring_cqe_seen(ring, cqe) 23 | 24 | assert statx.islink is False 25 | assert statx.isfile is True 26 | assert statx.isreg is True 27 | assert statx.isdir is False 28 | assert statx.ischr is False 29 | assert statx.isblk is False 30 | assert statx.isfifo is False 31 | assert statx.issock is False 32 | 33 | assert statx.stx_size == 11 34 | 35 | os_stat = os.lstat(file_path) 36 | assert statx.stx_btime == statx.stx_atime == os_stat.st_atime 37 | assert statx.stx_ctime == os_stat.st_ctime 38 | assert statx.stx_mtime == os_stat.st_mtime 39 | -------------------------------------------------------------------------------- /test/time_test.py: -------------------------------------------------------------------------------- 1 | import liburing 2 | 3 | 4 | def test_timespec(): 5 | ts = liburing.timespec(1) 6 | assert ts.tv_sec == 1 7 | assert ts.tv_nsec == 0 8 | 9 | ts = liburing.timespec(1.5) 10 | assert ts.tv_sec == 1 11 | assert ts.tv_nsec == 500000000 12 | 13 | ts = liburing.timespec() 14 | assert ts.tv_sec == 0 15 | assert ts.tv_nsec == 0 16 | ts.tv_sec = 1 17 | ts.tv_nsec = 500000000 18 | assert ts.tv_sec == 1 19 | assert ts.tv_nsec == 500000000 20 | -------------------------------------------------------------------------------- /test/version_test.py: -------------------------------------------------------------------------------- 1 | from liburing import LINUX_VERSION_MAJOR, LINUX_VERSION_MINOR, linux_version_check, \ 2 | liburing_version_major, liburing_version_minor, liburing_version_check 3 | 4 | 5 | def test_set_linux_version_define(): 6 | with open('/proc/version', 'rb') as file: 7 | major, minor, *_ = file.read().split()[2].split(b'.', 2) 8 | assert LINUX_VERSION_MAJOR == int(major) 9 | assert LINUX_VERSION_MINOR == int(minor) 10 | 11 | 12 | def test_linux_version(): 13 | major = LINUX_VERSION_MAJOR 14 | minor = LINUX_VERSION_MINOR 15 | assert linux_version_check(f'{major-1 or 1}') is False 16 | assert linux_version_check(f'{major-1 or 1}.{minor or 1}') is False 17 | assert linux_version_check(f'{major}.{minor}') is False # current linux version 18 | assert linux_version_check(major+1) is True 19 | assert linux_version_check(float(f'{major+1}.{minor or 1}')) is True 20 | 21 | 22 | def test_liburing_version(): 23 | assert liburing_version_major() >= 2 and liburing_version_minor() >= 4 24 | # checks if liburing version is == or > than installed version. 25 | assert liburing_version_check(10, 10) is True 26 | --------------------------------------------------------------------------------