├── .github └── workflows │ └── rust.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── img ├── nt-list.svg └── windbg.png ├── nt-list ├── Cargo.toml └── src │ ├── lib.rs │ ├── list │ ├── base.rs │ ├── boxing.rs │ ├── mod.rs │ └── traits.rs │ ├── private.rs │ ├── single_list │ ├── base.rs │ ├── boxing.rs │ ├── mod.rs │ └── traits.rs │ └── traits.rs └── nt-list_macros ├── Cargo.toml └── src ├── helpers.rs └── lib.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Clippy 20 | run: cargo clippy --verbose 21 | - name: Build 22 | run: cargo build --verbose 23 | - name: Run tests 24 | run: cargo test --verbose 25 | 26 | miri: 27 | name: "Miri" 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v3 31 | - name: Install Miri 32 | run: | 33 | rustup toolchain install nightly --component miri 34 | rustup override set nightly 35 | cargo miri setup 36 | - name: Test with Miri 37 | run: cargo miri test --no-fail-fast 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | 8 | ## [0.3.0] - 2023-10-19 9 | - Upgraded dependencies, with major upgrades for `moveit` (to 0.6.x) and `syn` (to 2.x) 10 | - Upgraded to `resolver = "2"` in the top-level `Cargo.toml` 11 | 12 | ## [0.2.1] - 2023-10-19 13 | - Fixed Tree Borrows and Stacked Borrows violations when running `cargo miri` (#6, #7) 14 | 15 | ## [0.2.0] - 2022-10-27 16 | - Fixed double-drop when a Drop handler of an element panics during `clear` 17 | - Added an `Extend` implementation for `NtBoxingListHead` 18 | - Added a `FromIterator` implementation for `NtBoxingSingleListHead` 19 | - Added documentation which structures are only available behind the `alloc` feature (#1) 20 | - Fixed undefined behavior caused by incorrect usage of `MaybeUninit` (#2, #3) 21 | - Changed all type casts to convert between pointers directly without going via `usize` (#4) 22 | - Changed `NtListElement` to an `unsafe trait` to prevent possibly undefined behavior from safe code (#5) 23 | - Changed all code examples in the documentation to be buildable as doc-tests 24 | - Fixed warnings emitted by clippy of Rust 1.64.0 25 | 26 | ## [0.1.0] - 2022-07-28 27 | - Initial release 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "nt-list", 5 | "nt-list_macros", 6 | ] 7 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2022 Colin Finck 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # nt-list 4 | 5 | [![crates.io](https://img.shields.io/crates/v/nt-list)](https://crates.io/crates/nt-list) 6 | [![docs.rs](https://img.shields.io/docsrs/nt-list)](https://docs.rs/nt-list) 7 | ![license: MIT OR Apache-2.0](https://img.shields.io/crates/l/nt-list) 8 | 9 | *by Colin Finck <>* 10 | 11 | Provides compatible, type-safe, and idiomatic Rust implementations of the Windows NT Linked Lists, known as [`LIST_ENTRY`](https://docs.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-list_entry) and [`SINGLE_LIST_ENTRY`](https://docs.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-single_list_entry). 12 | 13 | ## In Action 14 | ![](img/windbg.png) 15 | Debugging a Rust application in WinDbg and using the [`!list`](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-list) extension to traverse a doubly linked list. 16 | A testament to the compatibility between `nt-list` and `LIST_ENTRY`. 17 | 18 | ## Details 19 | Singly and doubly linked lists of this format are fundamental data structures widely used in Windows itself and in drivers written for Windows. 20 | In the case of a doubly linked list, Windows defines a `LIST_ENTRY` structure with forward and backward pointers to other `LIST_ENTRY` structures. 21 | `LIST_ENTRY` is then embedded into your own element structure. 22 | Check the [relevant Microsoft documentation](https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/singly-and-doubly-linked-lists) for more details on linked lists in Windows. 23 | 24 | This design exhibits several properties that differ from textbook linked list implementations: 25 | 26 | * A single element can be part of multiple lists (by having multiple `LIST_ENTRY` fields). 27 | * YOU are responsible for pushing only elements of the same type to a list. 28 | Without any type safety, the C/C++ compiler cannot prevent you from adding differently typed elements to the same list. 29 | * Links point to the `LIST_ENTRY` field of an element and not to the element itself. 30 | YOU need to retrieve the corresponding element structure using `CONTAINING_RECORD`, and YOU are responsible for all parameters passed to that macro. 31 | 32 | The _nt-list_ crate introduces type safety for these lists, taking away some responsibility from the user and moving it to the compiler. 33 | Additionally, it offers an idiomatic Rust interface similar to that of [`LinkedList`](https://doc.rust-lang.org/std/collections/struct.LinkedList.html) and [`Vec`](https://doc.rust-lang.org/std/vec/struct.Vec.html). 34 | 35 | ## Example 36 | Creating a linked list with `nt-list` boils down to these three steps: 37 | 38 | 1. You define an empty enum to identify your list (for type safety when pushing elements), and derive either `NtList` (doubly linked list) or `NtSingleList` (singly linked list). 39 | 2. You define your element structure, declare an entry as `#[boxed]` if desired, and derive `NtListElement`. 40 | 3. You call `new` of the respective list implementation with the element structure and empty enum as type parameters. 41 | 42 | All of this taken together looks like: 43 | 44 | ```rust,no_run 45 | #[derive(NtSingleList)] 46 | enum MyList {} 47 | 48 | #[derive(Default, NtListElement)] 49 | #[repr(C)] 50 | struct MyElement { 51 | #[boxed] 52 | entry: NtSingleListEntry, 53 | value: i32, 54 | } 55 | 56 | fn test() { 57 | let mut list = NtBoxingSingleListHead::::new(); 58 | 59 | list.push_back(MyElement { 60 | value: 42, 61 | ..Default::default() 62 | }); 63 | 64 | for element in list.iter() { 65 | println!("{}", element.value); 66 | } 67 | } 68 | ``` 69 | 70 | Check the [module-level documentation of _list_](https://docs.rs/nt-list/latest/nt-list/list/) for a doubly linked list example. 71 | 72 | ## `no_std` support 73 | The crate is `no_std`-compatible and therefore usable from firmware-level code up to user-mode applications. 74 | 75 | To support heap allocations in `NtBoxingListHead` and `NtBoxingSingleListHead`, the crate depends on the `alloc` library. 76 | If you want to use the crate in a pure `no_std` environment without heap allocations, include it with `default-features = false` to disable the default `alloc` feature. 77 | 78 | ## License 79 | This crate is licensed under either of 80 | 81 | * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 82 | * [MIT license](http://opensource.org/licenses/MIT) 83 | 84 | at your option. 85 | 86 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 87 | -------------------------------------------------------------------------------- /img/nt-list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /img/windbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColinFinck/nt-list/a9bb625553e999393e54e8739b4fcddb7c53c378/img/windbg.png -------------------------------------------------------------------------------- /nt-list/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nt-list" 3 | version = "0.3.0" 4 | authors = ["Colin Finck "] 5 | description = "Compatible, type-safe, and idiomatic Rust implementations of Windows NT Linked Lists" 6 | homepage = "https://github.com/ColinFinck/nt-list" 7 | repository = "https://github.com/ColinFinck/nt-list" 8 | documentation = "https://docs.rs/nt-list" 9 | readme = "../README.md" 10 | edition = "2021" 11 | rust-version = "1.56" 12 | license = "MIT OR Apache-2.0" 13 | keywords = ["list_entry", "single_list_entry", "no_std", "nt", "windows"] 14 | categories = ["data-structures", "no-std", "os::windows-apis"] 15 | 16 | [dependencies] 17 | moveit = "0.6.0" 18 | nt-list_macros = { path = "../nt-list_macros", version = "0.3.0" } 19 | 20 | [features] 21 | default = ["alloc"] 22 | alloc = [] 23 | 24 | [package.metadata.docs.rs] 25 | all-features = true 26 | rustdoc-args = ["--cfg", "docsrs"] 27 | -------------------------------------------------------------------------------- /nt-list/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Colin Finck 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | // 4 | //! Provides compatible, type-safe, and idiomatic Rust implementations of the Windows NT Linked Lists, 5 | //! known as [`LIST_ENTRY`] and [`SINGLE_LIST_ENTRY`]. 6 | //! 7 | //! Singly and doubly linked lists of this format are fundamental data structures widely used in 8 | //! Windows itself and in drivers written for Windows. 9 | //! In the case of a doubly linked list, Windows defines a `LIST_ENTRY` structure with forward and backward 10 | //! pointers to other `LIST_ENTRY` structures. 11 | //! `LIST_ENTRY` is then embedded into your own element structure. 12 | //! Check the [relevant Microsoft documentation](https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/singly-and-doubly-linked-lists) 13 | //! for more details on linked lists in Windows. 14 | //! 15 | //! This design exhibits several properties that differ from textbook linked list implementations: 16 | //! 17 | //! * A single element can be part of multiple lists (by having multiple `LIST_ENTRY` fields). 18 | //! * YOU are responsible for pushing only elements of the same type to a list. 19 | //! Without any type safety, the C/C++ compiler cannot prevent you from adding differently typed 20 | //! elements to the same list. 21 | //! * Links point to the `LIST_ENTRY` field of an element and not to the element itself. 22 | //! YOU need to retrieve the corresponding element structure using `CONTAINING_RECORD`, and it's YOUR 23 | //! responsibility to use the correct parameters for that macro. 24 | //! 25 | //! The `nt-list` crate introduces type safety for these lists, taking away some responsibility from the user 26 | //! and moving it to the compiler. 27 | //! Additionally, it offers an idiomatic Rust interface similar to that of [`LinkedList`] and [`Vec`]. 28 | //! 29 | //! ## Example 30 | //! Creating a linked list with `nt-list` boils down to these three steps: 31 | //! 32 | //! 1. You define an empty enum to identify your list (for type safety when pushing elements), 33 | //! and derive either [`NtList`] (doubly linked list) or [`NtSingleList`] (singly linked list). 34 | //! 2. You define your element structure, declare an entry as `#[boxed]` if desired, 35 | //! and derive [`NtListElement`]. 36 | //! 3. You call `new` of the respective list implementation with the element structure 37 | //! and empty enum as type parameters. 38 | //! 39 | //! All of this taken together looks like: 40 | //! 41 | //! ``` 42 | //! # use nt_list::NtListElement; 43 | //! # use nt_list::single_list::{NtBoxingSingleListHead, NtSingleList, NtSingleListEntry}; 44 | //! # 45 | //! #[derive(NtSingleList)] 46 | //! enum MyList {} 47 | //! 48 | //! #[derive(Default, NtListElement)] 49 | //! #[repr(C)] 50 | //! struct MyElement { 51 | //! #[boxed] 52 | //! entry: NtSingleListEntry, 53 | //! value: i32, 54 | //! } 55 | //! 56 | //! fn test() { 57 | //! let mut list = NtBoxingSingleListHead::::new(); 58 | //! 59 | //! list.push_front(MyElement { 60 | //! value: 42, 61 | //! ..Default::default() 62 | //! }); 63 | //! 64 | //! for element in list.iter() { 65 | //! println!("{}", element.value); 66 | //! } 67 | //! } 68 | //! ``` 69 | //! 70 | //! Check the module-level documentation of [list] and [single_list] for more information on how to use 71 | //! `nt-list`. 72 | //! 73 | //! ## `no_std` support 74 | //! The crate is `no_std`-compatible and therefore usable from firmware-level code up to user-mode applications. 75 | //! 76 | //! To support heap allocations in `NtBoxingListHead` and `NtBoxingSingleListHead`, the crate depends on the 77 | //! `alloc` library. 78 | //! If you want to use the crate in a pure `no_std` environment without heap allocations, include it with 79 | //! `default-features = false` to disable the default `alloc` feature. 80 | //! 81 | //! [`LinkedList`]: alloc::collections::LinkedList 82 | //! [`LIST_ENTRY`]: https://docs.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-list_entry 83 | //! [`NtList`]: enum@crate::list::NtList 84 | //! [`NtSingleList`]: enum@crate::single_list::NtSingleList 85 | //! [`SINGLE_LIST_ENTRY`]: https://docs.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-single_list_entry 86 | //! [`Vec`]: alloc::vec::Vec 87 | 88 | #![no_std] 89 | #![cfg_attr(docsrs, feature(doc_cfg))] 90 | #![allow(clippy::missing_safety_doc)] 91 | #![warn(missing_docs)] 92 | 93 | #[cfg(feature = "alloc")] 94 | extern crate alloc; 95 | 96 | // Required for deriving our traits when testing. 97 | #[cfg(test)] 98 | extern crate self as nt_list; 99 | 100 | pub mod list; 101 | mod private; 102 | pub mod single_list; 103 | mod traits; 104 | 105 | pub use traits::*; 106 | -------------------------------------------------------------------------------- /nt-list/src/list/base.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Colin Finck 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | use core::iter::FusedIterator; 5 | use core::marker::PhantomPinned; 6 | use core::pin::Pin; 7 | use core::ptr; 8 | 9 | use moveit::{new, New}; 10 | 11 | use super::traits::NtList; 12 | use crate::traits::{NtListElement, NtTypedList}; 13 | 14 | /// A doubly linked list header compatible to [`LIST_ENTRY`] of the Windows NT API. 15 | /// 16 | /// This variant requires elements to be allocated beforehand on a stable address and be 17 | /// valid as long as the list is used. 18 | /// As the Rust compiler cannot guarantee the validity of them, almost all `NtListHead` 19 | /// functions are `unsafe`. 20 | /// You almost always want to use [`NtBoxingListHead`] over this. 21 | /// 22 | /// See the [module-level documentation](crate::list) for more details. 23 | /// 24 | /// This structure substitutes the `LIST_ENTRY` structure of the Windows NT API for the list header. 25 | /// 26 | /// [`LIST_ENTRY`]: https://docs.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-list_entry 27 | /// [`NtBoxingListHead`]: crate::list::NtBoxingListHead 28 | #[repr(C)] 29 | pub struct NtListHead, L: NtTypedList> { 30 | pub(crate) flink: *mut NtListEntry, 31 | pub(crate) blink: *mut NtListEntry, 32 | pub(crate) pin: PhantomPinned, 33 | } 34 | 35 | impl NtListHead 36 | where 37 | E: NtListElement, 38 | L: NtTypedList, 39 | { 40 | /// Creates a new doubly linked list. 41 | /// 42 | /// This function substitutes [`InitializeListHead`] of the Windows NT API. 43 | /// 44 | /// [`InitializeListHead`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-initializelisthead 45 | pub fn new() -> impl New { 46 | new::of(Self { 47 | flink: ptr::null_mut(), 48 | blink: ptr::null_mut(), 49 | pin: PhantomPinned, 50 | }) 51 | .with(|this| { 52 | let this = unsafe { this.get_unchecked_mut() }; 53 | this.flink = (this as *mut Self).cast(); 54 | this.blink = this.flink; 55 | }) 56 | } 57 | 58 | /// Moves all elements from `other` to the end of the list. 59 | /// 60 | /// This reuses all the nodes from `other` and moves them into `self`. 61 | /// After this operation, `other` becomes empty. 62 | /// 63 | /// This operation computes in *O*(*1*) time. 64 | pub unsafe fn append(mut self: Pin<&mut Self>, other: Pin<&mut Self>) { 65 | if other.as_ref().is_empty() { 66 | return; 67 | } 68 | 69 | // Append `other` to `self` by remounting the respective elements: 70 | // - The last element of `self` shall be followed by the first element of `other`. 71 | // - The first element of `other` shall be preceded by the last element of `self`. 72 | // - The last element of `other` shall be followed by the end marker of `self`. 73 | // - The last element of `self` shall be changed to the last element of `other`. 74 | (*self.blink).flink = other.flink; 75 | (*other.flink).blink = self.blink; 76 | (*other.blink).flink = self.as_mut().end_marker_mut(); 77 | self.get_unchecked_mut().blink = other.blink; 78 | 79 | // Clear `other` without touching any of its elements. 80 | other.clear(); 81 | } 82 | 83 | /// Provides a reference to the last element, or `None` if the list is empty. 84 | /// 85 | /// This operation computes in *O*(*1*) time. 86 | pub unsafe fn back(self: Pin<&Self>) -> Option<&E> { 87 | (!self.is_empty()).then(|| NtListEntry::containing_record(self.blink)) 88 | } 89 | 90 | /// Provides a mutable reference to the last element, or `None` if the list is empty. 91 | /// 92 | /// This operation computes in *O*(*1*) time. 93 | pub unsafe fn back_mut(self: Pin<&mut Self>) -> Option<&mut E> { 94 | (!self.as_ref().is_empty()).then(|| NtListEntry::containing_record_mut(self.blink)) 95 | } 96 | 97 | /// Removes all elements from the list. 98 | /// 99 | /// This operation computes in *O*(*1*) time, because it only resets the forward and 100 | /// backward links of the header. 101 | pub fn clear(mut self: Pin<&mut Self>) { 102 | let end_marker = self.as_mut().end_marker_mut(); 103 | let self_mut = unsafe { self.get_unchecked_mut() }; 104 | 105 | self_mut.flink = end_marker; 106 | self_mut.blink = end_marker; 107 | } 108 | 109 | /// Returns a const pointer to the "end marker element" (which is the address of our own `NtListHead`, but interpreted as a `NtListEntry` element address). 110 | pub(crate) fn end_marker(self: Pin<&Self>) -> *const NtListEntry { 111 | (self.get_ref() as *const Self).cast() 112 | } 113 | 114 | /// Returns a mutable pointer to the "end marker element" (which is the address of our own `NtListHead`, but interpreted as a `NtListEntry` element address). 115 | pub(crate) fn end_marker_mut(self: Pin<&mut Self>) -> *mut NtListEntry { 116 | (unsafe { self.get_unchecked_mut() } as *mut Self).cast() 117 | } 118 | 119 | /// Returns the [`NtListEntry`] for the given element. 120 | pub(crate) fn entry(element: &mut E) -> *mut NtListEntry { 121 | let element_ptr = element as *mut E; 122 | 123 | // This is the canonical implementation of `byte_add` 124 | let entry = unsafe { element_ptr.cast::().add(E::offset()).cast::() }; 125 | 126 | entry.cast() 127 | } 128 | 129 | /// Provides a reference to the first element, or `None` if the list is empty. 130 | /// 131 | /// This operation computes in *O*(*1*) time. 132 | pub unsafe fn front(self: Pin<&Self>) -> Option<&E> { 133 | (!self.is_empty()).then(|| NtListEntry::containing_record(self.flink)) 134 | } 135 | 136 | /// Provides a mutable reference to the first element, or `None` if the list is empty. 137 | /// 138 | /// This operation computes in *O*(*1*) time. 139 | pub unsafe fn front_mut(self: Pin<&mut Self>) -> Option<&mut E> { 140 | (!self.as_ref().is_empty()).then(|| NtListEntry::containing_record_mut(self.flink)) 141 | } 142 | 143 | /// Returns `true` if the list is empty. 144 | /// 145 | /// This function substitutes [`IsListEmpty`] of the Windows NT API. 146 | /// 147 | /// This operation computes in *O*(*1*) time. 148 | /// 149 | /// [`IsListEmpty`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-islistempty 150 | pub fn is_empty(self: Pin<&Self>) -> bool { 151 | self.flink as *const NtListEntry == (self.get_ref() as *const Self).cast() 152 | } 153 | 154 | /// Returns an iterator yielding references to each element of the list. 155 | pub unsafe fn iter(self: Pin<&Self>) -> Iter { 156 | let head = self; 157 | let flink = head.flink; 158 | let blink = head.blink; 159 | 160 | Iter { head, flink, blink } 161 | } 162 | 163 | /// Returns an iterator yielding mutable references to each element of the list. 164 | pub unsafe fn iter_mut(self: Pin<&mut Self>) -> IterMut { 165 | let head = self; 166 | let flink = head.flink; 167 | let blink = head.blink; 168 | 169 | IterMut { head, flink, blink } 170 | } 171 | 172 | /// Counts all elements and returns the length of the list. 173 | /// 174 | /// This operation computes in *O*(*n*) time. 175 | pub unsafe fn len(self: Pin<&Self>) -> usize { 176 | self.iter().count() 177 | } 178 | 179 | /// Removes the last element from the list and returns it, or `None` if the list is empty. 180 | /// 181 | /// This function substitutes [`RemoveTailList`] of the Windows NT API. 182 | /// 183 | /// This operation computes in *O*(*1*) time. 184 | /// 185 | /// [`RemoveTailList`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-removetaillist 186 | pub unsafe fn pop_back(self: Pin<&mut Self>) -> Option<&mut E> { 187 | (!self.as_ref().is_empty()).then(|| { 188 | let entry = self.blink; 189 | (*entry).remove(); 190 | NtListEntry::containing_record_mut(entry) 191 | }) 192 | } 193 | 194 | /// Removes the first element from the list and returns it, or `None` if the list is empty. 195 | /// 196 | /// This function substitutes [`RemoveHeadList`] of the Windows NT API. 197 | /// 198 | /// This operation computes in *O*(*1*) time. 199 | /// 200 | /// [`RemoveHeadList`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-removeheadlist 201 | pub unsafe fn pop_front(self: Pin<&mut Self>) -> Option<&mut E> { 202 | (!self.as_ref().is_empty()).then(|| { 203 | let entry = self.flink; 204 | (*entry).remove(); 205 | NtListEntry::containing_record_mut(entry) 206 | }) 207 | } 208 | 209 | /// Appends an element to the back of the list. 210 | /// 211 | /// This function substitutes [`InsertTailList`] of the Windows NT API. 212 | /// 213 | /// This operation computes in *O*(*1*) time. 214 | /// 215 | /// [`InsertTailList`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-inserttaillist 216 | pub unsafe fn push_back(mut self: Pin<&mut Self>, element: &mut E) { 217 | let entry = Self::entry(element); 218 | 219 | let old_blink = self.blink; 220 | (*entry).flink = self.as_mut().end_marker_mut(); 221 | (*entry).blink = old_blink; 222 | (*old_blink).flink = entry; 223 | self.get_unchecked_mut().blink = entry; 224 | } 225 | 226 | /// Appends an element to the front of the list. 227 | /// 228 | /// This function substitutes [`InsertHeadList`] of the Windows NT API. 229 | /// 230 | /// This operation computes in *O*(*1*) time. 231 | /// 232 | /// [`InsertHeadList`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-insertheadlist 233 | pub unsafe fn push_front(mut self: Pin<&mut Self>, element: &mut E) { 234 | let entry = Self::entry(element); 235 | 236 | let old_flink = self.flink; 237 | (*entry).flink = old_flink; 238 | (*entry).blink = self.as_mut().end_marker_mut(); 239 | (*old_flink).blink = entry; 240 | self.get_unchecked_mut().flink = entry; 241 | } 242 | 243 | /// Retains only the elements specified by the predicate, passing a mutable reference to it. 244 | /// 245 | /// In other words, remove all elements `e` for which `f(&mut e)` returns `false`. 246 | /// This method operates in place, visiting each element exactly once in the original order, 247 | /// and preserves the order of the retained elements. 248 | /// 249 | /// This function substitutes [`RemoveEntryList`] of the Windows NT API. 250 | /// 251 | /// This operation computes in *O*(*n*) time. 252 | /// 253 | /// [`RemoveEntryList`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-removeentrylist 254 | pub unsafe fn retain(self: Pin<&mut Self>, mut f: F) 255 | where 256 | F: FnMut(&mut E) -> bool, 257 | { 258 | for element in self.iter_mut() { 259 | if !f(element) { 260 | let entry = Self::entry(element); 261 | (*entry).remove(); 262 | } 263 | } 264 | } 265 | } 266 | 267 | /// Iterator over the elements of a doubly linked list. 268 | /// 269 | /// This iterator is returned from the [`NtListHead::iter`] and [`NtBoxingListHead::iter`] functions. 270 | /// 271 | /// [`NtBoxingListHead::iter`]: crate::list::NtBoxingListHead::iter 272 | pub struct Iter<'a, E: NtListElement, L: NtTypedList> { 273 | head: Pin<&'a NtListHead>, 274 | flink: *const NtListEntry, 275 | blink: *const NtListEntry, 276 | } 277 | 278 | impl<'a, E, L> Iter<'a, E, L> 279 | where 280 | E: NtListElement, 281 | L: NtTypedList, 282 | { 283 | fn terminate(&mut self) { 284 | self.flink = self.head.end_marker(); 285 | self.blink = self.flink; 286 | } 287 | } 288 | 289 | impl<'a, E, L> Iterator for Iter<'a, E, L> 290 | where 291 | E: NtListElement, 292 | L: NtTypedList, 293 | { 294 | type Item = &'a E; 295 | 296 | fn next(&mut self) -> Option<&'a E> { 297 | if self.flink == self.head.end_marker() { 298 | None 299 | } else { 300 | unsafe { 301 | let element_ptr = self.flink; 302 | 303 | if self.flink == self.blink { 304 | // We are crossing the other end of the iterator and must not iterate any further. 305 | self.terminate(); 306 | } else { 307 | self.flink = (*self.flink).flink; 308 | } 309 | 310 | Some(NtListEntry::containing_record(element_ptr)) 311 | } 312 | } 313 | } 314 | 315 | fn last(mut self) -> Option<&'a E> { 316 | self.next_back() 317 | } 318 | } 319 | 320 | impl<'a, E, L> DoubleEndedIterator for Iter<'a, E, L> 321 | where 322 | E: NtListElement, 323 | L: NtTypedList, 324 | { 325 | fn next_back(&mut self) -> Option<&'a E> { 326 | if self.blink == self.head.end_marker() { 327 | None 328 | } else { 329 | unsafe { 330 | let element_ptr = self.blink; 331 | 332 | if self.blink == self.flink { 333 | // We are crossing the other end of the iterator and must not iterate any further. 334 | self.terminate(); 335 | } else { 336 | self.blink = (*self.blink).blink; 337 | } 338 | 339 | Some(NtListEntry::containing_record(element_ptr)) 340 | } 341 | } 342 | } 343 | } 344 | 345 | impl<'a, E, L> FusedIterator for Iter<'a, E, L> 346 | where 347 | E: NtListElement, 348 | L: NtTypedList, 349 | { 350 | } 351 | 352 | /// Mutable iterator over the elements of a doubly linked list. 353 | /// 354 | /// This iterator is returned from the [`NtListHead::iter_mut`] and [`NtBoxingListHead::iter_mut`] functions. 355 | /// 356 | /// [`NtBoxingListHead::iter_mut`]: crate::list::NtBoxingListHead::iter_mut 357 | pub struct IterMut<'a, E: NtListElement, L: NtTypedList> { 358 | head: Pin<&'a mut NtListHead>, 359 | flink: *mut NtListEntry, 360 | blink: *mut NtListEntry, 361 | } 362 | 363 | impl<'a, E, L> IterMut<'a, E, L> 364 | where 365 | E: NtListElement, 366 | L: NtTypedList, 367 | { 368 | fn terminate(&mut self) { 369 | self.flink = self.head.as_mut().end_marker_mut(); 370 | self.blink = self.flink; 371 | } 372 | } 373 | 374 | impl<'a, E, L> Iterator for IterMut<'a, E, L> 375 | where 376 | E: NtListElement, 377 | L: NtTypedList, 378 | { 379 | type Item = &'a mut E; 380 | 381 | fn next(&mut self) -> Option<&'a mut E> { 382 | if self.flink == self.head.as_mut().end_marker_mut() { 383 | None 384 | } else { 385 | unsafe { 386 | let element_ptr = self.flink; 387 | 388 | if self.flink == self.blink { 389 | // We are crossing the other end of the iterator and must not iterate any further. 390 | self.terminate(); 391 | } else { 392 | self.flink = (*self.flink).flink; 393 | } 394 | 395 | Some(NtListEntry::containing_record_mut(element_ptr)) 396 | } 397 | } 398 | } 399 | 400 | fn last(mut self) -> Option<&'a mut E> { 401 | self.next_back() 402 | } 403 | } 404 | 405 | impl<'a, E, L> DoubleEndedIterator for IterMut<'a, E, L> 406 | where 407 | E: NtListElement, 408 | L: NtTypedList, 409 | { 410 | fn next_back(&mut self) -> Option<&'a mut E> { 411 | if self.blink == self.head.as_mut().end_marker_mut() { 412 | None 413 | } else { 414 | unsafe { 415 | let element_ptr = self.blink; 416 | 417 | if self.blink == self.flink { 418 | // We are crossing the other end of the iterator and must not iterate any further. 419 | self.terminate(); 420 | } else { 421 | self.blink = (*self.blink).blink; 422 | } 423 | 424 | Some(NtListEntry::containing_record_mut(element_ptr)) 425 | } 426 | } 427 | } 428 | } 429 | 430 | impl<'a, E, L> FusedIterator for IterMut<'a, E, L> 431 | where 432 | E: NtListElement, 433 | L: NtTypedList, 434 | { 435 | } 436 | 437 | /// This structure substitutes the `LIST_ENTRY` structure of the Windows NT API for actual list entries. 438 | #[derive(Debug)] 439 | #[repr(C)] 440 | pub struct NtListEntry, L: NtTypedList> { 441 | pub(crate) flink: *mut NtListEntry, 442 | pub(crate) blink: *mut NtListEntry, 443 | pin: PhantomPinned, 444 | } 445 | 446 | impl NtListEntry 447 | where 448 | E: NtListElement, 449 | L: NtTypedList, 450 | { 451 | /// Allows the creation of an `NtListEntry`, but leaves all fields uninitialized. 452 | /// 453 | /// Its fields are only initialized when an entry is pushed to a list. 454 | pub fn new() -> Self { 455 | Self { 456 | flink: ptr::null_mut(), 457 | blink: ptr::null_mut(), 458 | pin: PhantomPinned, 459 | } 460 | } 461 | 462 | pub(crate) unsafe fn containing_record<'a>(ptr: *const Self) -> &'a E { 463 | // This is the canonical implementation of `byte_sub` 464 | let element_ptr = unsafe { ptr.cast::().sub(E::offset()).cast::() }; 465 | 466 | unsafe { &*element_ptr.cast() } 467 | } 468 | 469 | pub(crate) unsafe fn containing_record_mut<'a>(ptr: *mut Self) -> &'a mut E { 470 | // This is the canonical implementation of `byte_sub` 471 | let element_ptr = unsafe { ptr.cast::().sub(E::offset()).cast::() }; 472 | 473 | unsafe { &mut *element_ptr.cast() } 474 | } 475 | 476 | pub(crate) unsafe fn remove(&mut self) { 477 | let old_flink = self.flink; 478 | let old_blink = self.blink; 479 | (*old_flink).blink = old_blink; 480 | (*old_blink).flink = old_flink; 481 | } 482 | } 483 | 484 | impl Default for NtListEntry 485 | where 486 | E: NtListElement, 487 | L: NtTypedList, 488 | { 489 | fn default() -> Self { 490 | Self::new() 491 | } 492 | } 493 | -------------------------------------------------------------------------------- /nt-list/src/list/boxing.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Colin Finck 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | use core::marker::PhantomPinned; 5 | use core::pin::Pin; 6 | use core::ptr; 7 | 8 | use alloc::boxed::Box; 9 | use moveit::{new, New}; 10 | 11 | use super::base::{Iter, IterMut, NtListEntry, NtListHead}; 12 | use super::traits::NtList; 13 | use crate::traits::{NtBoxedListElement, NtListElement, NtTypedList}; 14 | 15 | /// A variant of [`NtListHead`] that boxes every element on insertion. 16 | /// 17 | /// This guarantees ownership and therefore all `NtBoxingListHead` functions can be used without 18 | /// resorting to `unsafe`. 19 | /// If you can, use this implementation over [`NtListHead`]. 20 | /// 21 | /// You need to implement the [`NtBoxedListElement`] trait to designate a single list as the boxing one. 22 | /// This also establishes clear ownership when a single element is part of more than one list. 23 | /// 24 | /// See the [module-level documentation](crate::list) for more details. 25 | /// 26 | /// This structure substitutes the [`LIST_ENTRY`] structure of the Windows NT API for the list header. 27 | /// 28 | /// [`LIST_ENTRY`]: https://docs.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-list_entry 29 | #[repr(transparent)] 30 | #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] 31 | pub struct NtBoxingListHead< 32 | E: NtBoxedListElement + NtListElement, 33 | L: NtTypedList, 34 | >(NtListHead); 35 | 36 | impl NtBoxingListHead 37 | where 38 | E: NtBoxedListElement + NtListElement, 39 | L: NtTypedList, 40 | { 41 | /// Creates a new doubly linked list that owns all elements. 42 | /// 43 | /// This function substitutes [`InitializeListHead`] of the Windows NT API. 44 | /// 45 | /// [`InitializeListHead`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-initializelisthead 46 | pub fn new() -> impl New { 47 | new::of(Self(NtListHead { 48 | flink: ptr::null_mut(), 49 | blink: ptr::null_mut(), 50 | pin: PhantomPinned, 51 | })) 52 | .with(|this| { 53 | let this = unsafe { this.get_unchecked_mut() }; 54 | this.0.flink = (this as *mut Self).cast(); 55 | this.0.blink = this.0.flink; 56 | }) 57 | } 58 | 59 | /// Moves all elements from `other` to the end of the list. 60 | /// 61 | /// This reuses all the nodes from `other` and moves them into `self`. 62 | /// After this operation, `other` becomes empty. 63 | /// 64 | /// This operation computes in *O*(*1*) time. 65 | pub fn append(self: Pin<&mut Self>, other: Pin<&mut Self>) { 66 | unsafe { self.inner_mut().append(other.inner_mut()) } 67 | } 68 | 69 | /// Provides a reference to the last element, or `None` if the list is empty. 70 | /// 71 | /// This operation computes in *O*(*1*) time. 72 | pub fn back(self: Pin<&Self>) -> Option<&E> { 73 | unsafe { self.inner().back() } 74 | } 75 | 76 | /// Provides a mutable reference to the last element, or `None` if the list is empty. 77 | /// 78 | /// This operation computes in *O*(*1*) time. 79 | pub fn back_mut(self: Pin<&mut Self>) -> Option<&mut E> { 80 | unsafe { self.inner_mut().back_mut() } 81 | } 82 | 83 | /// Removes all elements from the list, deallocating their memory. 84 | /// 85 | /// Unlike [`NtListHead::clear`], this operation computes in *O*(*n*) time, because it 86 | /// needs to traverse all elements to deallocate them. 87 | pub fn clear(mut self: Pin<&mut Self>) { 88 | let end_marker = self.as_mut().inner_mut().end_marker_mut(); 89 | 90 | // Get the link to the first element before it's being reset. 91 | let mut current = self.0.flink; 92 | 93 | // Make the list appear empty before deallocating any element. 94 | // By doing this here and not at the very end, we guard against the following scenario: 95 | // 96 | // 1. We deallocate an element. 97 | // 2. The `Drop` handler of that element is called and panics. 98 | // 3. Consequently, the `Drop` handler of `NtBoxingListHead` is called and removes all elements. 99 | // 4. While removing elements, the just dropped element is dropped again. 100 | // 101 | // By clearing the list at the beginning, the `Drop` handler of `NtBoxingListHead` won't find any 102 | // elements, and thereby it won't drop any elements. 103 | self.inner_mut().clear(); 104 | 105 | // Traverse the list in the old-fashioned way and deallocate each element. 106 | while current != end_marker { 107 | unsafe { 108 | let element = NtListEntry::containing_record_mut(current); 109 | current = (*current).flink; 110 | drop(Box::from_raw(element)); 111 | } 112 | } 113 | } 114 | 115 | /// Provides a reference to the first element, or `None` if the list is empty. 116 | /// 117 | /// This operation computes in *O*(*1*) time. 118 | pub fn front(self: Pin<&Self>) -> Option<&E> { 119 | unsafe { self.inner().front() } 120 | } 121 | 122 | /// Provides a mutable reference to the first element, or `None` if the list is empty. 123 | /// 124 | /// This operation computes in *O*(*1*) time. 125 | pub fn front_mut(self: Pin<&mut Self>) -> Option<&mut E> { 126 | unsafe { self.inner_mut().front_mut() } 127 | } 128 | 129 | fn inner(self: Pin<&Self>) -> Pin<&NtListHead> { 130 | unsafe { Pin::new_unchecked(&self.get_ref().0) } 131 | } 132 | 133 | fn inner_mut(self: Pin<&mut Self>) -> Pin<&mut NtListHead> { 134 | unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) } 135 | } 136 | 137 | /// Returns `true` if the list is empty. 138 | /// 139 | /// This function substitutes [`IsListEmpty`] of the Windows NT API. 140 | /// 141 | /// This operation computes in *O*(*1*) time. 142 | /// 143 | /// [`IsListEmpty`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-islistempty 144 | pub fn is_empty(self: Pin<&Self>) -> bool { 145 | self.inner().is_empty() 146 | } 147 | 148 | /// Returns an iterator yielding references to each element of the list. 149 | pub fn iter(self: Pin<&Self>) -> Iter { 150 | unsafe { self.inner().iter() } 151 | } 152 | 153 | /// Returns an iterator yielding mutable references to each element of the list. 154 | pub fn iter_mut(self: Pin<&mut Self>) -> IterMut { 155 | unsafe { self.inner_mut().iter_mut() } 156 | } 157 | 158 | /// Counts all elements and returns the length of the list. 159 | /// 160 | /// This operation computes in *O*(*n*) time. 161 | pub fn len(self: Pin<&Self>) -> usize { 162 | unsafe { self.inner().len() } 163 | } 164 | 165 | /// Removes the last element from the list and returns it, or `None` if the list is empty. 166 | /// 167 | /// This function substitutes [`RemoveTailList`] of the Windows NT API. 168 | /// 169 | /// This operation computes in *O*(*1*) time. 170 | /// 171 | /// [`RemoveTailList`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-removetaillist 172 | pub fn pop_back(self: Pin<&mut Self>) -> Option> { 173 | unsafe { 174 | self.inner_mut() 175 | .pop_back() 176 | .map(|element| Box::from_raw(element)) 177 | } 178 | } 179 | 180 | /// Removes the first element from the list and returns it, or `None` if the list is empty. 181 | /// 182 | /// This function substitutes [`RemoveHeadList`] of the Windows NT API. 183 | /// 184 | /// This operation computes in *O*(*1*) time. 185 | /// 186 | /// [`RemoveHeadList`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-removeheadlist 187 | pub fn pop_front(self: Pin<&mut Self>) -> Option> { 188 | unsafe { 189 | self.inner_mut() 190 | .pop_front() 191 | .map(|element| Box::from_raw(element)) 192 | } 193 | } 194 | 195 | /// Appends an element to the back of the list. 196 | /// 197 | /// This function substitutes [`InsertTailList`] of the Windows NT API. 198 | /// 199 | /// This operation computes in *O*(*1*) time. 200 | /// 201 | /// [`InsertTailList`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-inserttaillist 202 | pub fn push_back(self: Pin<&mut Self>, element: E) { 203 | let boxed_element = Box::new(element); 204 | unsafe { self.inner_mut().push_back(Box::leak(boxed_element)) } 205 | } 206 | 207 | /// Appends an element to the front of the list. 208 | /// 209 | /// This function substitutes [`InsertHeadList`] of the Windows NT API. 210 | /// 211 | /// This operation computes in *O*(*1*) time. 212 | /// 213 | /// [`InsertHeadList`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-insertheadlist 214 | pub fn push_front(self: Pin<&mut Self>, element: E) { 215 | let boxed_element = Box::new(element); 216 | unsafe { self.inner_mut().push_front(Box::leak(boxed_element)) } 217 | } 218 | 219 | /// Retains only the elements specified by the predicate, passing a mutable reference to it. 220 | /// 221 | /// In other words, remove all elements `e` for which `f(&mut e)` returns `false`. 222 | /// This method operates in place, visiting each element exactly once in the original order, 223 | /// and preserves the order of the retained elements. 224 | /// 225 | /// This function substitutes [`RemoveEntryList`] of the Windows NT API. 226 | /// 227 | /// This operation computes in *O*(*n*) time. 228 | /// 229 | /// [`RemoveEntryList`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-removeentrylist 230 | pub fn retain(self: Pin<&mut Self>, mut f: F) 231 | where 232 | F: FnMut(&mut E) -> bool, 233 | { 234 | for element in self.iter_mut() { 235 | if !f(element) { 236 | let entry = NtListHead::entry(element); 237 | 238 | unsafe { 239 | (*entry).remove(); 240 | drop(Box::from_raw(element)); 241 | } 242 | } 243 | } 244 | } 245 | } 246 | 247 | impl Drop for NtBoxingListHead 248 | where 249 | E: NtBoxedListElement + NtListElement, 250 | L: NtTypedList, 251 | { 252 | fn drop(&mut self) { 253 | let pinned = unsafe { Pin::new_unchecked(self) }; 254 | 255 | for element in pinned.iter_mut() { 256 | // Reconstruct the `Box` we created in push_back/push_front and let it leave the scope 257 | // to call its Drop handler and deallocate the element gracefully. 258 | unsafe { 259 | drop(Box::from_raw(element)); 260 | } 261 | } 262 | } 263 | } 264 | 265 | impl Extend> for Pin<&mut NtBoxingListHead> 266 | where 267 | E: NtBoxedListElement + NtListElement, 268 | L: NtTypedList, 269 | { 270 | fn extend(&mut self, iter: T) 271 | where 272 | T: IntoIterator>, 273 | { 274 | let end_marker = self.as_mut().inner_mut().end_marker_mut(); 275 | let mut previous = self.as_ref().inner().blink; 276 | 277 | for element in iter.into_iter() { 278 | // We could use `NtBoxingListHead::push_back` here, but this manual implementation 279 | // is slightly optimized (doesn't modify list head's `blink` on every iteration). 280 | unsafe { 281 | let entry = NtListHead::entry(Box::leak(element)); 282 | 283 | (*entry).flink = end_marker; 284 | (*entry).blink = previous; 285 | (*previous).flink = entry; 286 | 287 | previous = entry; 288 | } 289 | } 290 | 291 | unsafe { 292 | self.as_mut().get_unchecked_mut().0.blink = previous; 293 | } 294 | } 295 | } 296 | 297 | impl Extend for Pin<&mut NtBoxingListHead> 298 | where 299 | E: NtBoxedListElement + NtListElement, 300 | L: NtTypedList, 301 | { 302 | fn extend(&mut self, iter: T) 303 | where 304 | T: IntoIterator, 305 | { 306 | self.extend(iter.into_iter().map(Box::new)) 307 | } 308 | } 309 | 310 | #[cfg(test)] 311 | mod tests { 312 | use super::*; 313 | use crate::list::NtListEntry; 314 | use alloc::vec::Vec; 315 | use moveit::moveit; 316 | 317 | #[derive(NtList)] 318 | enum MyList {} 319 | 320 | #[derive(Default, NtListElement)] 321 | #[repr(C)] 322 | struct MyElement { 323 | value: i32, 324 | #[boxed] 325 | entry: NtListEntry, 326 | } 327 | 328 | impl MyElement { 329 | fn new(value: i32) -> Self { 330 | Self { 331 | value, 332 | ..Default::default() 333 | } 334 | } 335 | } 336 | 337 | #[test] 338 | fn test_append() { 339 | // Append two lists of equal size. 340 | moveit! { 341 | let mut list1 = NtBoxingListHead::::new(); 342 | let mut list2 = NtBoxingListHead::::new(); 343 | } 344 | 345 | for i in 0..10 { 346 | list1.as_mut().push_back(MyElement::new(i)); 347 | list2.as_mut().push_back(MyElement::new(i)); 348 | } 349 | 350 | list1.as_mut().append(list2.as_mut()); 351 | 352 | assert_eq!(list1.as_ref().len(), 20); 353 | assert_eq!(list2.as_ref().len(), 0); 354 | 355 | for (i, element) in (0..10).chain(0..10).zip(list1.as_ref().iter()) { 356 | assert_eq!(i, element.value); 357 | } 358 | 359 | verify_all_links(list1.as_ref().inner()); 360 | 361 | // Append the final list to an empty list. 362 | moveit! { 363 | let mut list3 = NtBoxingListHead::::new(); 364 | } 365 | 366 | list3.as_mut().append(list1.as_mut()); 367 | 368 | assert_eq!(list3.as_ref().len(), 20); 369 | assert_eq!(list1.as_ref().len(), 0); 370 | 371 | verify_all_links(list3.as_ref().inner()); 372 | } 373 | 374 | #[test] 375 | fn test_clear_and_append() { 376 | // Append two lists of equal size. 377 | moveit! { 378 | let mut list1 = NtBoxingListHead::::new(); 379 | let mut list2 = NtBoxingListHead::::new(); 380 | } 381 | 382 | for i in 0..10 { 383 | list1.as_mut().push_back(MyElement::new(i)); 384 | list2.as_mut().push_back(MyElement::new(i)); 385 | } 386 | 387 | list1.as_mut().append(list2.as_mut()); 388 | 389 | assert_eq!(list1.as_ref().len(), 20); 390 | assert_eq!(list2.as_ref().len(), 0); 391 | 392 | for (i, element) in (0..10).chain(0..10).zip(list1.as_ref().iter()) { 393 | assert_eq!(i, element.value); 394 | } 395 | 396 | verify_all_links(list1.as_ref().inner()); 397 | 398 | // Add more elements to both lists 399 | list1.as_mut().push_back(MyElement::new(21)); 400 | list1.as_mut().push_front(MyElement::new(22)); 401 | 402 | list2.as_mut().push_back(MyElement::new(21)); 403 | list2.as_mut().push_front(MyElement::new(22)); 404 | 405 | // Append the final list to a cleared list. 406 | moveit! { 407 | let mut list3 = NtBoxingListHead::::new(); 408 | } 409 | 410 | list3.as_mut().clear(); 411 | list3.as_mut().append(list1.as_mut()); 412 | 413 | assert_eq!(list3.as_ref().len(), 22); 414 | assert_eq!(list1.as_ref().len(), 0); 415 | 416 | verify_all_links(list3.as_ref().inner()); 417 | } 418 | 419 | #[test] 420 | fn test_clear_and_push() { 421 | moveit! { 422 | let mut list = NtBoxingListHead::::new(); 423 | } 424 | 425 | list.as_mut().clear(); 426 | 427 | for i in 0..=3 { 428 | list.as_mut().push_back(MyElement::new(i)); 429 | } 430 | for i in 4..=6 { 431 | list.as_mut().push_front(MyElement::new(i)); 432 | } 433 | 434 | assert_eq!(list.as_ref().back().unwrap().value, 3); 435 | assert_eq!(list.as_mut().back_mut().unwrap().value, 3); 436 | assert_eq!(list.as_ref().front().unwrap().value, 6); 437 | assert_eq!(list.as_mut().front_mut().unwrap().value, 6); 438 | 439 | verify_all_links(list.as_ref().inner()); 440 | } 441 | 442 | #[test] 443 | fn test_back_and_front() { 444 | moveit! { 445 | let mut list = NtBoxingListHead::::new(); 446 | } 447 | 448 | for i in 0..=3 { 449 | list.as_mut().push_back(MyElement::new(i)); 450 | } 451 | 452 | assert_eq!(list.as_ref().back().unwrap().value, 3); 453 | assert_eq!(list.as_mut().back_mut().unwrap().value, 3); 454 | assert_eq!(list.as_ref().front().unwrap().value, 0); 455 | assert_eq!(list.as_mut().front_mut().unwrap().value, 0); 456 | } 457 | 458 | #[test] 459 | fn test_extend() { 460 | let integers = [0, 1, 2, 3, 4, 5]; 461 | 462 | moveit! { 463 | let mut list = NtBoxingListHead::::new(); 464 | } 465 | 466 | list.as_mut() 467 | .extend(integers.into_iter().map(MyElement::new)); 468 | 469 | for (i, element) in integers.into_iter().zip(list.as_ref().iter()) { 470 | assert_eq!(i, element.value); 471 | } 472 | 473 | verify_all_links(list.as_ref().inner()); 474 | } 475 | 476 | #[test] 477 | fn test_pop_back() { 478 | moveit! { 479 | let mut list = NtBoxingListHead::::new(); 480 | } 481 | 482 | for i in 0..10 { 483 | list.as_mut().push_back(MyElement::new(i)); 484 | } 485 | 486 | for i in (0..10).rev() { 487 | let element = list.as_mut().pop_back().unwrap(); 488 | assert_eq!(i, element.value); 489 | verify_all_links(list.as_ref().inner()); 490 | } 491 | 492 | assert!(list.as_ref().is_empty()); 493 | } 494 | 495 | #[test] 496 | fn test_pop_front() { 497 | moveit! { 498 | let mut list = NtBoxingListHead::::new(); 499 | } 500 | 501 | for i in 0..10 { 502 | list.as_mut().push_back(MyElement::new(i)); 503 | } 504 | 505 | for i in 0..10 { 506 | let element = list.as_mut().pop_front().unwrap(); 507 | assert_eq!(i, element.value); 508 | verify_all_links(list.as_ref().inner()); 509 | } 510 | 511 | assert!(list.as_ref().is_empty()); 512 | } 513 | 514 | #[test] 515 | fn test_push_back() { 516 | moveit! { 517 | let mut list = NtBoxingListHead::::new(); 518 | } 519 | 520 | for i in 0..10 { 521 | list.as_mut().push_back(MyElement::new(i)); 522 | } 523 | 524 | assert_eq!(list.as_ref().len(), 10); 525 | 526 | for (i, element) in (0..10).zip(list.as_ref().iter()) { 527 | assert_eq!(i, element.value); 528 | } 529 | 530 | verify_all_links(list.as_ref().inner()); 531 | } 532 | 533 | #[test] 534 | fn test_push_front() { 535 | moveit! { 536 | let mut list = NtBoxingListHead::::new(); 537 | } 538 | 539 | for i in 0..10 { 540 | list.as_mut().push_front(MyElement::new(i)); 541 | } 542 | 543 | assert_eq!(list.as_ref().len(), 10); 544 | 545 | for (i, element) in (0..10).rev().zip(list.as_ref().iter()) { 546 | assert_eq!(i, element.value); 547 | } 548 | 549 | verify_all_links(list.as_ref().inner()); 550 | } 551 | 552 | #[test] 553 | fn test_retain() { 554 | moveit! { 555 | let mut list = NtBoxingListHead::::new(); 556 | } 557 | 558 | for i in 0..10 { 559 | list.as_mut().push_back(MyElement::new(i)); 560 | } 561 | 562 | // Keep only the even elements. 563 | list.as_mut().retain(|element| element.value % 2 == 0); 564 | 565 | assert_eq!(list.as_ref().len(), 5); 566 | 567 | for (i, element) in (0..10).step_by(2).zip(list.as_ref().iter()) { 568 | assert_eq!(i, element.value); 569 | } 570 | 571 | verify_all_links(list.as_ref().inner()); 572 | 573 | // Keep only the first and last of the remaining elements. 574 | list.as_mut() 575 | .retain(|element| element.value == 0 || element.value == 8); 576 | 577 | let mut iter = list.as_ref().iter(); 578 | assert_eq!(iter.next().unwrap().value, 0); 579 | assert_eq!(iter.next().unwrap().value, 8); 580 | assert!(matches!(iter.next(), None)); 581 | } 582 | 583 | fn verify_all_links(head: Pin<&NtListHead>) 584 | where 585 | E: NtListElement, 586 | L: NtTypedList, 587 | { 588 | let mut current; 589 | let end = (head.get_ref() as *const _ as *mut NtListHead).cast(); 590 | 591 | // Traverse the list in forward direction and collect all entries. 592 | current = head.flink; 593 | let mut forward_entries = Vec::<*mut NtListEntry>::new(); 594 | 595 | while current != end { 596 | if !forward_entries.is_empty() { 597 | // Verify that the previous entry is referenced by this entry's `blink`. 598 | unsafe { 599 | assert_eq!(*forward_entries.last().unwrap(), (*current).blink); 600 | } 601 | } 602 | 603 | forward_entries.push(current); 604 | current = unsafe { (*current).flink }; 605 | } 606 | 607 | // Traverse the list in backward direction and collect all entries. 608 | current = head.blink; 609 | let mut backward_entries = 610 | Vec::<*mut NtListEntry>::with_capacity(forward_entries.len()); 611 | 612 | while current != end { 613 | if !backward_entries.is_empty() { 614 | // Verify that the previous entry is referenced by this entry's `flink`. 615 | unsafe { 616 | assert_eq!(*backward_entries.last().unwrap(), (*current).flink); 617 | } 618 | } 619 | 620 | backward_entries.push(current); 621 | current = unsafe { (*current).blink }; 622 | } 623 | 624 | // Verify that `backward_entries` is the exact reverse of `forward_entries`. 625 | assert_eq!(forward_entries.len(), backward_entries.len()); 626 | 627 | for (fe, be) in forward_entries.iter().zip(backward_entries.iter().rev()) { 628 | assert_eq!(fe, be); 629 | } 630 | } 631 | } 632 | -------------------------------------------------------------------------------- /nt-list/src/list/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Colin Finck 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | // 4 | //! A doubly linked list compatible to [`LIST_ENTRY`] of the Windows NT API. 5 | //! 6 | //! To make this list type-safe, `nt-list` first asks you to declare an empty enum, which then serves 7 | //! as the `L` type parameter to distinguish different lists. 8 | //! A list element can be part of multiple linked lists by having multiple entry fields in the element 9 | //! structure. 10 | //! You need to declare an empty enum for every entry field of every element structure. 11 | //! 12 | //! The empty enum is designated as a doubly linked list via: 13 | //! 14 | //! ``` 15 | //! # use nt_list::list::NtList; 16 | //! # 17 | //! #[derive(NtList)] 18 | //! enum MyList {} 19 | //! ``` 20 | //! 21 | //! Next you define your element structure, adding an [`NtListEntry`] field for each doubly linked 22 | //! list you want your element to be part of. 23 | //! A single [`NtListEntry`] field can be marked with `#[boxed]` to make that list own the elements 24 | //! and handle their memory allocation and deallocation: 25 | //! 26 | //! ``` 27 | //! # use nt_list::NtListElement; 28 | //! # use nt_list::list::{NtList, NtListEntry}; 29 | //! # 30 | //! # #[derive(NtList)] 31 | //! # enum MyList {} 32 | //! # 33 | //! #[derive(Default, NtListElement)] 34 | //! #[repr(C)] 35 | //! struct MyElement { 36 | //! #[boxed] 37 | //! entry: NtListEntry, 38 | //! value: i32, 39 | //! } 40 | //! ``` 41 | //! 42 | //! You can then manage that list using the safe [`NtBoxingListHead`] interface: 43 | //! 44 | //! ``` 45 | //! # use moveit::moveit; 46 | //! # use nt_list::NtListElement; 47 | //! # use nt_list::list::{NtBoxingListHead, NtList, NtListEntry}; 48 | //! # 49 | //! # #[derive(NtList)] 50 | //! # enum MyList {} 51 | //! # 52 | //! # #[derive(Default, NtListElement)] 53 | //! # #[repr(C)] 54 | //! # struct MyElement { 55 | //! # #[boxed] 56 | //! # entry: NtListEntry, 57 | //! # value: i32, 58 | //! # } 59 | //! # 60 | //! moveit! { 61 | //! let mut list = NtBoxingListHead::::new(); 62 | //! } 63 | //! 64 | //! list.as_mut().push_back(MyElement { 65 | //! value: 42, 66 | //! ..Default::default() 67 | //! }); 68 | //! assert!(!list.as_ref().is_empty()); 69 | //! ``` 70 | //! 71 | //! The last link of a `LIST_ENTRY` doubly linked list points back to the list header. 72 | //! This requires the address of the list header to be stable. 73 | //! Therefore, the list address is pinned on creation by using the [`moveit`] crate, and 74 | //! all doubly linked list functions require pinned references. 75 | //! 76 | //! For non-boxed entries, you can only use the [`NtListHead`] interface. 77 | //! It requires elements to be allocated beforehand on a stable address and be valid as long as 78 | //! the list is used. 79 | //! Without owning the elements, the Rust compiler cannot guarantee the validity of them. 80 | //! This is why almost all [`NtListHead`] functions are `unsafe`. 81 | //! Fortunately, [`NtListHead`] is usually only necessary when an element is part of multiple lists. 82 | //! 83 | //! [`LIST_ENTRY`]: https://docs.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-list_entry 84 | //! [`moveit`]: https://crates.io/crates/moveit 85 | 86 | mod base; 87 | #[cfg(feature = "alloc")] 88 | mod boxing; 89 | mod traits; 90 | 91 | pub use base::*; 92 | #[cfg(feature = "alloc")] 93 | pub use boxing::*; 94 | pub use traits::*; 95 | -------------------------------------------------------------------------------- /nt-list/src/list/traits.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Colin Finck 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | use crate::private::Sealed; 5 | use crate::traits::NtListType; 6 | 7 | /// Designates a list as an NT doubly linked list (`LIST_ENTRY` structure of the Windows NT API). 8 | /// 9 | /// You usually want to use `#[derive(NtList)]` to implement [`NtTypedList`] with type set to `NtList`. 10 | /// 11 | /// [`NtTypedList`]: crate::traits::NtTypedList 12 | pub enum NtList {} 13 | 14 | /// Doubly linked list type (`LIST_ENTRY` structure of the Windows NT API) 15 | impl NtListType for NtList {} 16 | impl Sealed for NtList {} 17 | 18 | /// Designates an empty enum as a doubly linked list. 19 | /// 20 | /// Technically, this macro implements [`NtTypedList`] with type set to [`enum@NtList`]. 21 | /// 22 | /// [`NtTypedList`]: crate::traits::NtTypedList 23 | pub use nt_list_macros::NtList; 24 | -------------------------------------------------------------------------------- /nt-list/src/private.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Colin Finck 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | // https://rust-lang.github.io/api-guidelines/future-proofing.html 5 | pub trait Sealed {} 6 | -------------------------------------------------------------------------------- /nt-list/src/single_list/base.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Colin Finck 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | use core::iter::FusedIterator; 5 | use core::marker::PhantomData; 6 | use core::ptr; 7 | 8 | use super::traits::NtSingleList; 9 | use crate::traits::{NtListElement, NtTypedList}; 10 | 11 | /// A singly linked list header compatible to [`SINGLE_LIST_ENTRY`] of the Windows NT API. 12 | /// 13 | /// This variant requires elements to be allocated beforehand on a stable address and be 14 | /// valid as long as the list is used. 15 | /// As the Rust compiler cannot guarantee the validity of them, almost all `NtSingleListHead` 16 | /// functions are `unsafe`. 17 | /// You almost always want to use [`NtBoxingSingleListHead`] over this. 18 | /// 19 | /// See the [module-level documentation](crate::single_list) for more details. 20 | /// 21 | /// This structure substitutes the `SINGLE_LIST_ENTRY` structure of the Windows NT API for the list header. 22 | /// 23 | /// [`NtBoxingSingleListHead`]: crate::single_list::NtBoxingSingleListHead 24 | /// [`SINGLE_LIST_ENTRY`]: https://docs.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-single_list_entry 25 | #[repr(C)] 26 | pub struct NtSingleListHead, L: NtTypedList> { 27 | pub(crate) next: *mut NtSingleListEntry, 28 | } 29 | 30 | impl NtSingleListHead 31 | where 32 | E: NtListElement, 33 | L: NtTypedList, 34 | { 35 | /// Creates a new singly linked list. 36 | pub fn new() -> Self { 37 | Self { 38 | next: ptr::null_mut(), 39 | } 40 | } 41 | 42 | /// Removes all elements from the list. 43 | /// 44 | /// This operation computes in *O*(*1*) time, because it only resets the forward link of the header. 45 | pub fn clear(&mut self) { 46 | self.next = ptr::null_mut(); 47 | } 48 | 49 | /// Returns the [`NtSingleListEntry`] for the given element. 50 | pub(crate) fn entry(element: &mut E) -> *mut NtSingleListEntry { 51 | let element_ptr = element as *mut E; 52 | 53 | // This is the canonical implementation of `byte_add` 54 | let entry = unsafe { element_ptr.cast::().add(E::offset()).cast::() }; 55 | 56 | entry.cast() 57 | } 58 | 59 | /// Provides a reference to the first element, or `None` if the list is empty. 60 | /// 61 | /// This operation computes in *O*(*1*) time. 62 | pub unsafe fn front(&self) -> Option<&E> { 63 | (!self.is_empty()).then(|| NtSingleListEntry::containing_record(self.next)) 64 | } 65 | 66 | /// Provides a mutable reference to the first element, or `None` if the list is empty. 67 | /// 68 | /// This operation computes in *O*(*1*) time. 69 | pub unsafe fn front_mut(&mut self) -> Option<&mut E> { 70 | (!self.is_empty()).then(|| NtSingleListEntry::containing_record_mut(self.next)) 71 | } 72 | 73 | /// Returns `true` if the list is empty. 74 | /// 75 | /// This operation computes in *O*(*1*) time. 76 | pub fn is_empty(&self) -> bool { 77 | self.next.is_null() 78 | } 79 | 80 | /// Returns an iterator yielding references to each element of the list. 81 | pub unsafe fn iter(&self) -> Iter { 82 | Iter { 83 | current: self.next, 84 | phantom: PhantomData, 85 | } 86 | } 87 | 88 | /// Returns an iterator yielding mutable references to each element of the list. 89 | pub unsafe fn iter_mut(&mut self) -> IterMut { 90 | IterMut { 91 | current: self.next, 92 | phantom: PhantomData, 93 | } 94 | } 95 | 96 | /// Counts all elements and returns the length of the list. 97 | /// 98 | /// This operation computes in *O*(*n*) time. 99 | pub unsafe fn len(&self) -> usize { 100 | self.iter().count() 101 | } 102 | 103 | /// Removes the first element from the list and returns it, or `None` if the list is empty. 104 | /// 105 | /// This function substitutes [`PopEntryList`] of the Windows NT API. 106 | /// 107 | /// This operation computes in *O*(*1*) time. 108 | /// 109 | /// [`PopEntryList`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-popentrylist 110 | pub unsafe fn pop_front(&mut self) -> Option<&mut E> { 111 | (!self.is_empty()).then(|| { 112 | let entry = self.next; 113 | self.next = (*entry).next; 114 | NtSingleListEntry::containing_record_mut(entry) 115 | }) 116 | } 117 | 118 | /// Appends an element to the front of the list. 119 | /// 120 | /// This function substitutes [`PushEntryList`] of the Windows NT API. 121 | /// 122 | /// This operation computes in *O*(*1*) time. 123 | /// 124 | /// [`PushEntryList`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-pushentrylist 125 | pub unsafe fn push_front(&mut self, element: &mut E) { 126 | let entry = Self::entry(element); 127 | 128 | (*entry).next = self.next; 129 | self.next = entry; 130 | } 131 | 132 | /// Retains only the elements specified by the predicate, passing a mutable reference to it. 133 | /// 134 | /// In other words, remove all elements `e` for which `f(&mut e)` returns `false`. 135 | /// This method operates in place, visiting each element exactly once in the original order, 136 | /// and preserves the order of the retained elements. 137 | /// 138 | /// This operation computes in *O*(*n*) time. 139 | pub unsafe fn retain(&mut self, mut f: F) 140 | where 141 | F: FnMut(&mut E) -> bool, 142 | { 143 | let mut previous = (self as *mut Self).cast(); 144 | let mut current = self.next; 145 | 146 | while !current.is_null() { 147 | let element = NtSingleListEntry::containing_record_mut(current); 148 | 149 | if f(element) { 150 | previous = current; 151 | } else { 152 | (*previous).next = (*current).next; 153 | } 154 | 155 | current = (*current).next; 156 | } 157 | } 158 | } 159 | 160 | impl Default for NtSingleListHead 161 | where 162 | E: NtListElement, 163 | L: NtTypedList, 164 | { 165 | fn default() -> Self { 166 | Self::new() 167 | } 168 | } 169 | 170 | /// Iterator over the elements of a singly linked list. 171 | /// 172 | /// This iterator is returned from the [`NtSingleListHead::iter`] and 173 | /// [`NtBoxingSingleListHead::iter`] functions. 174 | /// 175 | /// [`NtBoxingSingleListHead::iter`]: crate::single_list::NtBoxingSingleListHead::iter 176 | pub struct Iter<'a, E: NtListElement, L: NtTypedList> { 177 | current: *const NtSingleListEntry, 178 | phantom: PhantomData<&'a NtSingleListHead>, 179 | } 180 | 181 | impl<'a, E, L> Iterator for Iter<'a, E, L> 182 | where 183 | E: NtListElement, 184 | L: NtTypedList, 185 | { 186 | type Item = &'a E; 187 | 188 | fn next(&mut self) -> Option<&'a E> { 189 | if self.current.is_null() { 190 | None 191 | } else { 192 | unsafe { 193 | let element_ptr = self.current; 194 | self.current = (*self.current).next; 195 | Some(NtSingleListEntry::::containing_record(element_ptr)) 196 | } 197 | } 198 | } 199 | } 200 | 201 | impl<'a, E, L> FusedIterator for Iter<'a, E, L> 202 | where 203 | E: NtListElement, 204 | L: NtTypedList, 205 | { 206 | } 207 | 208 | /// Mutable iterator over the elements of a singly linked list. 209 | /// 210 | /// This iterator is returned from the [`NtSingleListHead::iter_mut`] and 211 | /// [`NtBoxingSingleListHead::iter_mut`] functions. 212 | /// 213 | /// [`NtBoxingSingleListHead::iter_mut`]: crate::single_list::NtBoxingSingleListHead::iter_mut 214 | pub struct IterMut<'a, E: NtListElement, L: NtTypedList> { 215 | current: *mut NtSingleListEntry, 216 | phantom: PhantomData<&'a mut NtSingleListHead>, 217 | } 218 | 219 | impl<'a, E, L> Iterator for IterMut<'a, E, L> 220 | where 221 | E: NtListElement, 222 | L: NtTypedList, 223 | { 224 | type Item = &'a mut E; 225 | 226 | fn next(&mut self) -> Option<&'a mut E> { 227 | if self.current.is_null() { 228 | None 229 | } else { 230 | unsafe { 231 | let element_ptr = self.current; 232 | self.current = (*self.current).next; 233 | Some(NtSingleListEntry::containing_record_mut(element_ptr)) 234 | } 235 | } 236 | } 237 | } 238 | 239 | impl<'a, E, L> FusedIterator for IterMut<'a, E, L> 240 | where 241 | E: NtListElement, 242 | L: NtTypedList, 243 | { 244 | } 245 | 246 | /// This structure substitutes the `SINGLE_LIST_ENTRY` structure of the Windows NT API for actual list entries. 247 | #[derive(Debug)] 248 | #[repr(C)] 249 | pub struct NtSingleListEntry, L: NtTypedList> { 250 | pub(crate) next: *mut NtSingleListEntry, 251 | } 252 | 253 | impl NtSingleListEntry 254 | where 255 | E: NtListElement, 256 | L: NtTypedList, 257 | { 258 | /// Allows the creation of an `NtSingleListEntry`, but leaves all fields uninitialized. 259 | /// 260 | /// Its fields are only initialized when an entry is pushed to a list. 261 | pub fn new() -> Self { 262 | Self { 263 | next: ptr::null_mut(), 264 | } 265 | } 266 | 267 | pub(crate) unsafe fn containing_record<'a>(ptr: *const Self) -> &'a E { 268 | // This is the canonical implementation of `byte_sub` 269 | let element_ptr = unsafe { ptr.cast::().sub(E::offset()).cast::() }; 270 | 271 | unsafe { &*element_ptr.cast() } 272 | } 273 | 274 | pub(crate) unsafe fn containing_record_mut<'a>(ptr: *mut Self) -> &'a mut E { 275 | // This is the canonical implementation of `byte_sub` 276 | let element_ptr = unsafe { ptr.cast::().sub(E::offset()).cast::() }; 277 | 278 | unsafe { &mut *element_ptr.cast() } 279 | } 280 | } 281 | 282 | impl Default for NtSingleListEntry 283 | where 284 | E: NtListElement, 285 | L: NtTypedList, 286 | { 287 | fn default() -> Self { 288 | Self::new() 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /nt-list/src/single_list/boxing.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Colin Finck 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | use core::ptr; 5 | 6 | use alloc::boxed::Box; 7 | 8 | use super::base::{Iter, IterMut, NtSingleListEntry, NtSingleListHead}; 9 | use super::traits::NtSingleList; 10 | use crate::traits::{NtBoxedListElement, NtListElement, NtTypedList}; 11 | 12 | /// A variant of [`NtSingleListHead`] that boxes every element on insertion. 13 | /// 14 | /// This guarantees ownership and therefore all `NtBoxingSingleListHead` functions can be used without 15 | /// resorting to `unsafe`. 16 | /// If you can, use this implementation over [`NtSingleListHead`]. 17 | /// 18 | /// You need to implement the [`NtBoxedListElement`] trait to designate a single list as the boxing one. 19 | /// This also establishes clear ownership when a single element is part of more than one list. 20 | /// 21 | /// See the [module-level documentation](crate::single_list) for more details. 22 | /// 23 | /// This structure substitutes the [`SINGLE_LIST_ENTRY`] structure of the Windows NT API for the list header. 24 | /// 25 | /// [`SINGLE_LIST_ENTRY`]: https://docs.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-single_list_entry 26 | #[repr(transparent)] 27 | #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] 28 | pub struct NtBoxingSingleListHead< 29 | E: NtBoxedListElement + NtListElement, 30 | L: NtTypedList, 31 | >(NtSingleListHead); 32 | 33 | impl NtBoxingSingleListHead 34 | where 35 | E: NtBoxedListElement + NtListElement, 36 | L: NtTypedList, 37 | { 38 | /// Creates a new singly linked list that owns all elements. 39 | pub fn new() -> Self { 40 | Self(NtSingleListHead::::new()) 41 | } 42 | 43 | /// Removes all elements from the list, deallocating their memory. 44 | /// 45 | /// Unlike [`NtSingleListHead::clear`], this operation computes in *O*(*n*) time, because it 46 | /// needs to traverse all elements to deallocate them. 47 | pub fn clear(&mut self) { 48 | // Get the link to the first element before it's being reset. 49 | let mut current = self.0.next; 50 | 51 | // Make the list appear empty before deallocating any element. 52 | // By doing this here and not at the very end, we guard against the following scenario: 53 | // 54 | // 1. We deallocate an element. 55 | // 2. The `Drop` handler of that element is called and panics. 56 | // 3. Consequently, the `Drop` handler of `NtBoxingSingleListHead` is called and removes all elements. 57 | // 4. While removing elements, the just dropped element is dropped again. 58 | // 59 | // By clearing the list at the beginning, the `Drop` handler of `NtBoxingSingleListHead` won't find any 60 | // elements, and thereby it won't drop any elements. 61 | self.0.clear(); 62 | 63 | // Traverse the list in the old-fashioned way and deallocate each element. 64 | while !current.is_null() { 65 | unsafe { 66 | let next = (*current).next; 67 | let element = NtSingleListEntry::::containing_record_mut(current); 68 | drop(Box::from_raw(element)); 69 | current = next; 70 | } 71 | } 72 | } 73 | 74 | /// Provides a reference to the first element, or `None` if the list is empty. 75 | /// 76 | /// This operation computes in *O*(*1*) time. 77 | pub fn front(&self) -> Option<&E> { 78 | unsafe { self.0.front() } 79 | } 80 | 81 | /// Provides a mutable reference to the first element, or `None` if the list is empty. 82 | /// 83 | /// This operation computes in *O*(*1*) time. 84 | pub fn front_mut(&mut self) -> Option<&mut E> { 85 | unsafe { self.0.front_mut() } 86 | } 87 | 88 | /// Returns `true` if the list is empty. 89 | /// 90 | /// This operation computes in *O*(*1*) time. 91 | pub fn is_empty(&self) -> bool { 92 | self.0.is_empty() 93 | } 94 | 95 | /// Returns an iterator yielding references to each element of the list. 96 | pub fn iter(&self) -> Iter { 97 | unsafe { self.0.iter() } 98 | } 99 | 100 | /// Returns an iterator yielding mutable references to each element of the list. 101 | pub fn iter_mut(&mut self) -> IterMut { 102 | unsafe { self.0.iter_mut() } 103 | } 104 | 105 | /// Counts all elements and returns the length of the list. 106 | /// 107 | /// This operation computes in *O*(*n*) time. 108 | pub fn len(&self) -> usize { 109 | unsafe { self.0.len() } 110 | } 111 | 112 | /// Removes the first element from the list and returns it, or `None` if the list is empty. 113 | /// 114 | /// This function substitutes [`PopEntryList`] of the Windows NT API. 115 | /// 116 | /// This operation computes in *O*(*1*) time. 117 | /// 118 | /// [`PopEntryList`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-popentrylist 119 | pub fn pop_front(&mut self) -> Option> { 120 | unsafe { self.0.pop_front().map(|element| Box::from_raw(element)) } 121 | } 122 | 123 | /// Appends an element to the front of the list. 124 | /// 125 | /// This function substitutes [`PushEntryList`] of the Windows NT API. 126 | /// 127 | /// This operation computes in *O*(*1*) time. 128 | /// 129 | /// [`PushEntryList`]: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-pushentrylist 130 | pub fn push_front(&mut self, element: E) { 131 | let boxed_element = Box::new(element); 132 | unsafe { self.0.push_front(Box::leak(boxed_element)) } 133 | } 134 | 135 | /// Retains only the elements specified by the predicate, passing a mutable reference to it. 136 | /// 137 | /// In other words, remove all elements `e` for which `f(&mut e)` returns `false`. 138 | /// This method operates in place, visiting each element exactly once in the original order, 139 | /// and preserves the order of the retained elements. 140 | /// 141 | /// This operation computes in *O*(*n*) time. 142 | pub fn retain(&mut self, mut f: F) 143 | where 144 | F: FnMut(&mut E) -> bool, 145 | { 146 | let mut previous = (self as *mut Self).cast(); 147 | let mut current = self.0.next; 148 | 149 | while !current.is_null() { 150 | unsafe { 151 | // Note: we can soundly store the next pointer ahead of time, 152 | // since the only methods that can modify the next pointer are 153 | // `NtSingleListEntry::{push,pop}_front`, and both of those 154 | // are unsafe. 155 | let next = (*current).next; 156 | let element = NtSingleListEntry::containing_record_mut(current); 157 | 158 | if f(element) { 159 | previous = current; 160 | current = next; 161 | } else { 162 | (*previous).next = next; 163 | current = next; 164 | drop(Box::from_raw(element)); 165 | } 166 | } 167 | } 168 | } 169 | } 170 | 171 | impl Default for NtBoxingSingleListHead 172 | where 173 | E: NtBoxedListElement + NtListElement, 174 | L: NtTypedList, 175 | { 176 | fn default() -> Self { 177 | Self::new() 178 | } 179 | } 180 | 181 | impl Drop for NtBoxingSingleListHead 182 | where 183 | E: NtBoxedListElement + NtListElement, 184 | L: NtTypedList, 185 | { 186 | fn drop(&mut self) { 187 | for element in self.iter_mut() { 188 | // Reconstruct the `Box` we created in push_front and let it leave the scope 189 | // to call its Drop handler and deallocate the element gracefully. 190 | unsafe { 191 | drop(Box::from_raw(element)); 192 | } 193 | } 194 | } 195 | } 196 | 197 | impl FromIterator> for NtBoxingSingleListHead 198 | where 199 | E: NtBoxedListElement + NtListElement, 200 | L: NtTypedList, 201 | { 202 | fn from_iter(iter: T) -> Self 203 | where 204 | T: IntoIterator>, 205 | { 206 | let mut list = NtBoxingSingleListHead::::new(); 207 | let mut previous = 208 | (&mut list.0 as *mut NtSingleListHead).cast::>(); 209 | 210 | for element in iter.into_iter() { 211 | // `NtBoxingSingleListHead` only comes with a `push_front` method, so we have to push 212 | // elements by hand and keep track of the last one. 213 | unsafe { 214 | let entry = NtSingleListHead::entry(Box::leak(element)); 215 | 216 | (*entry).next = ptr::null_mut(); 217 | (*previous).next = entry; 218 | 219 | previous = entry; 220 | } 221 | } 222 | 223 | list 224 | } 225 | } 226 | 227 | impl FromIterator for NtBoxingSingleListHead 228 | where 229 | E: NtBoxedListElement + NtListElement, 230 | L: NtTypedList, 231 | { 232 | fn from_iter(iter: T) -> Self 233 | where 234 | T: IntoIterator, 235 | { 236 | iter.into_iter().map(Box::new).collect() 237 | } 238 | } 239 | 240 | #[cfg(test)] 241 | mod tests { 242 | use super::*; 243 | use crate::single_list::NtSingleListEntry; 244 | 245 | #[derive(NtSingleList)] 246 | enum MyList {} 247 | 248 | #[derive(Default, NtListElement)] 249 | #[repr(C)] 250 | struct MyElement { 251 | value: i32, 252 | #[boxed] 253 | entry: NtSingleListEntry, 254 | } 255 | 256 | impl MyElement { 257 | fn new(value: i32) -> Self { 258 | Self { 259 | value, 260 | ..Default::default() 261 | } 262 | } 263 | } 264 | 265 | #[test] 266 | fn test_from_iter() { 267 | let integers = [0, 1, 2, 3, 4, 5]; 268 | let list = integers 269 | .into_iter() 270 | .map(MyElement::new) 271 | .collect::>(); 272 | 273 | for (i, element) in integers.into_iter().zip(list.iter()) { 274 | assert_eq!(i, element.value); 275 | } 276 | } 277 | 278 | #[test] 279 | fn test_front() { 280 | let mut list = NtBoxingSingleListHead::::new(); 281 | 282 | for i in 0..=3 { 283 | list.push_front(MyElement::new(i)); 284 | } 285 | 286 | assert_eq!(list.front().unwrap().value, 3); 287 | assert_eq!(list.front_mut().unwrap().value, 3); 288 | } 289 | 290 | #[test] 291 | fn test_pop_front() { 292 | let mut list = NtBoxingSingleListHead::::new(); 293 | 294 | for i in 0..10 { 295 | list.push_front(MyElement::new(i)); 296 | } 297 | 298 | for i in (0..10).rev() { 299 | let element = list.pop_front().unwrap(); 300 | assert_eq!(i, element.value); 301 | } 302 | 303 | assert!(list.is_empty()); 304 | } 305 | 306 | #[test] 307 | fn test_push_front() { 308 | let mut list = NtBoxingSingleListHead::::new(); 309 | 310 | for i in 0..10 { 311 | list.push_front(MyElement::new(i)); 312 | } 313 | 314 | assert_eq!(list.len(), 10); 315 | 316 | for (i, element) in (0..10).rev().zip(list.iter()) { 317 | assert_eq!(i, element.value); 318 | } 319 | } 320 | 321 | #[test] 322 | fn test_retain() { 323 | let mut list = NtBoxingSingleListHead::::new(); 324 | 325 | for i in 0..10 { 326 | list.push_front(MyElement::new(i)); 327 | } 328 | 329 | // Keep only the even elements. 330 | list.retain(|element| element.value % 2 == 0); 331 | 332 | assert_eq!(list.len(), 5); 333 | 334 | for (i, element) in (0..=8).rev().step_by(2).zip(list.iter()) { 335 | assert_eq!(i, element.value); 336 | } 337 | 338 | // Keep only the first and last of the remaining elements. 339 | list.retain(|element| element.value == 8 || element.value == 0); 340 | 341 | let mut iter = list.iter(); 342 | assert_eq!(iter.next().unwrap().value, 8); 343 | assert_eq!(iter.next().unwrap().value, 0); 344 | assert!(matches!(iter.next(), None)); 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /nt-list/src/single_list/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Colin Finck 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | // 4 | //! A singly linked list compatible to [`SINGLE_LIST_ENTRY`] of the Windows NT API. 5 | //! 6 | //! To make this list type-safe, `nt-list` first asks you to declare an empty enum, which then serves 7 | //! as the `L` type parameter to distinguish different lists. 8 | //! A list element can be part of multiple linked lists by having multiple entry fields in the element 9 | //! structure. 10 | //! You need to declare an empty enum for every entry field of every element structure. 11 | //! 12 | //! The empty enum is designated as a singly linked list via: 13 | //! 14 | //! ``` 15 | //! # use nt_list::single_list::NtSingleList; 16 | //! # 17 | //! #[derive(NtSingleList)] 18 | //! enum MyList {} 19 | //! ``` 20 | //! 21 | //! Next you define your element structure, adding an [`NtSingleListEntry`] field for each singly linked 22 | //! list you want your element to be part of. 23 | //! A single [`NtSingleListEntry`] field can be marked with `#[boxed]` to make that list own the elements 24 | //! and handle their memory allocation and deallocation: 25 | //! 26 | //! ``` 27 | //! # use nt_list::NtListElement; 28 | //! # use nt_list::single_list::{NtSingleList, NtSingleListEntry}; 29 | //! # 30 | //! # #[derive(NtSingleList)] 31 | //! # enum MyList {} 32 | //! # 33 | //! #[derive(Default, NtListElement)] 34 | //! #[repr(C)] 35 | //! struct MyElement { 36 | //! #[boxed] 37 | //! entry: NtSingleListEntry, 38 | //! value: i32, 39 | //! } 40 | //! ``` 41 | //! 42 | //! You can then manage that list using the safe [`NtBoxingSingleListHead`] interface: 43 | //! 44 | //! ``` 45 | //! # use nt_list::NtListElement; 46 | //! # use nt_list::single_list::{NtBoxingSingleListHead, NtSingleList, NtSingleListEntry}; 47 | //! # 48 | //! # #[derive(NtSingleList)] 49 | //! # enum MyList {} 50 | //! # 51 | //! # #[derive(Default, NtListElement)] 52 | //! # #[repr(C)] 53 | //! # struct MyElement { 54 | //! # #[boxed] 55 | //! # entry: NtSingleListEntry, 56 | //! # value: i32, 57 | //! # } 58 | //! # 59 | //! let mut list = NtBoxingSingleListHead::::new(); 60 | //! 61 | //! list.push_front(MyElement { 62 | //! value: 42, 63 | //! ..Default::default() 64 | //! }); 65 | //! assert!(!list.is_empty()); 66 | //! ``` 67 | //! 68 | //! For non-boxed entries, you can only use the [`NtSingleListHead`] interface. 69 | //! It requires elements to be allocated beforehand on a stable address and be valid as long as 70 | //! the list is used. 71 | //! Without owning the elements, the Rust compiler cannot guarantee the validity of them. 72 | //! This is why almost all [`NtSingleListHead`] functions are `unsafe`. 73 | //! Fortunately, [`NtSingleListHead`] is usually only necessary when an element is part of multiple lists. 74 | //! 75 | //! [`SINGLE_LIST_ENTRY`]: https://docs.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-single_list_entry 76 | 77 | mod base; 78 | #[cfg(feature = "alloc")] 79 | mod boxing; 80 | mod traits; 81 | 82 | pub use base::*; 83 | #[cfg(feature = "alloc")] 84 | pub use boxing::*; 85 | pub use traits::*; 86 | -------------------------------------------------------------------------------- /nt-list/src/single_list/traits.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Colin Finck 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | use crate::private::Sealed; 5 | use crate::traits::NtListType; 6 | 7 | /// Designates a list as an NT singly linked list (`SINGLE_LIST_ENTRY` structure of the Windows NT API). 8 | /// 9 | /// You usually want to use `#[derive(NtSingleList)]` to implement [`NtTypedList`] with type set to `NtSingleList`. 10 | /// 11 | /// [`NtTypedList`]: crate::traits::NtTypedList 12 | pub enum NtSingleList {} 13 | 14 | /// Singly linked list type (`SINGLE_LIST_ENTRY` structure of the Windows NT API) 15 | impl NtListType for NtSingleList {} 16 | impl Sealed for NtSingleList {} 17 | 18 | /// Designates an empty enum as a singly linked list. 19 | /// 20 | /// Technically, this macro implements [`NtTypedList`] with type set to [`enum@NtSingleList`]. 21 | /// 22 | /// [`NtTypedList`]: crate::traits::NtTypedList 23 | pub use nt_list_macros::NtSingleList; 24 | -------------------------------------------------------------------------------- /nt-list/src/traits.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Colin Finck 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | use crate::private::Sealed; 5 | 6 | /// The type (singly or doubly linked list) of an empty enum that implements [`NtTypedList`]. 7 | pub trait NtListType: Sealed {} 8 | 9 | /// Designates an empty enum as an NT list of a specific type (singly or doubly linked list). 10 | /// 11 | /// You are supposed to define an empty enum and implement this trait for every list entry field 12 | /// of every list element type in your program. 13 | /// 14 | /// This is required, because a single element may be part of multiple NT lists, and henceforth 15 | /// its element structure then contains multiple entry fields (e.g. [`NtListEntry`]). 16 | /// To make all list functions insert and remove elements via the correct entry fields, 17 | /// lists need to be uniquely identified, and this is what the empty enum types are for. 18 | /// 19 | /// The easiest way to implement this trait is to use `derive` with the appropriate list type 20 | /// ([`NtList`] or [`NtSingleList`]): 21 | /// 22 | /// ``` 23 | /// # use nt_list::list::NtList; 24 | /// # 25 | /// #[derive(NtList)] 26 | /// enum MyList {} 27 | /// ``` 28 | /// 29 | /// [`NtList`]: enum@crate::list::NtList 30 | /// [`NtListEntry`]: crate::list::NtListEntry 31 | /// [`NtSingleList`]: enum@crate::single_list::NtSingleList 32 | pub trait NtTypedList { 33 | /// Identifier of the list 34 | type T: NtListType; 35 | } 36 | 37 | /// Designates a structure as a list element with an entry field (e.g. [`NtListEntry`]) of a 38 | /// particular NT list. 39 | /// The entry field's position inside the list is given by implementing the `offset` method. 40 | /// The NT list is identified via the enum that implements [`NtTypedList`]. 41 | /// 42 | /// You can implement this trait multiple times for a structure if it is part of multiple 43 | /// lists (and therefore contains multiple entry fields). 44 | /// 45 | /// The easiest way to implement this trait for all entry fields of a structure is to use 46 | /// `derive` on the structure: 47 | /// 48 | /// ``` 49 | /// # use nt_list::NtListElement; 50 | /// # use nt_list::list::{NtList, NtListEntry}; 51 | /// # 52 | /// # #[derive(NtList)] 53 | /// # enum MyList {} 54 | /// # 55 | /// #[derive(NtListElement)] 56 | /// #[repr(C)] 57 | /// struct MyElement { 58 | /// entry: NtListEntry, 59 | /// value: i32, 60 | /// } 61 | /// ``` 62 | /// 63 | /// # Safety 64 | /// 65 | /// This trait is unsafe, because the compiler cannot verify that the `offset` method has been 66 | /// implemented correctly. 67 | /// Safe functions rely on the offset pointing to an actual [`NtListEntry`] or [`NtSingleListEntry`]. 68 | /// This trait must also only be implemented for structures marked with `#[repr(C)]`. 69 | /// 70 | /// It is therefore recommended to only derive this trait as described above and never implement 71 | /// it manually. 72 | /// 73 | /// [`NtListEntry`]: crate::list::NtListEntry 74 | /// [`NtSingleListEntry`]: crate::single_list::NtSingleListEntry 75 | pub unsafe trait NtListElement { 76 | /// Returns the byte offset to the entry field relative to the beginning of the 77 | /// element structure. 78 | fn offset() -> usize; 79 | } 80 | 81 | /// Implements the [`NtListElement`] and (optionally) [`NtBoxedListElement`] traits for the given 82 | /// element structure. 83 | /// 84 | /// Technically, this macro traverses the structure and looks for [`NtListEntry`] and [`NtSingleListEntry`] 85 | /// fields. 86 | /// For each entry, it takes its list type parameter `L` and implements [`NtListElement`] along with 87 | /// the `offset` trait function for it. 88 | /// 89 | /// If an entry is marked with the `#[boxed]` attribute, [`NtBoxedListElement`] is also implemented for 90 | /// the structure. 91 | /// 92 | /// [`NtListEntry`]: crate::list::NtListEntry 93 | /// [`NtSingleListEntry`]: crate::single_list::NtSingleListEntry 94 | pub use nt_list_macros::NtListElement; 95 | 96 | /// Enables [`NtBoxingListHead`] for a list element structure. 97 | /// 98 | /// While an element may be part of multiple lists, only one list may have ownership of the element 99 | /// and handle its memory allocation and deallocation. 100 | /// Therefore, `NtBoxedListElement` can only be implemented once per list element structure. 101 | /// 102 | /// The easiest way to implement this trait is to use the `#[boxed]` attribute for the appropriate 103 | /// entry field and use `derive` on the structure: 104 | /// 105 | /// ``` 106 | /// # use nt_list::NtListElement; 107 | /// # use nt_list::list::{NtList, NtListEntry}; 108 | /// # 109 | /// # #[derive(NtList)] 110 | /// # enum MyList {} 111 | /// # 112 | /// #[derive(NtListElement)] 113 | /// #[repr(C)] 114 | /// struct MyElement { 115 | /// #[boxed] 116 | /// entry: NtListEntry, 117 | /// value: i32, 118 | /// } 119 | /// ``` 120 | /// 121 | /// [`NtBoxingListHead`]: crate::list::NtBoxingListHead 122 | /// [`NtListEntry`]: crate::list::NtListEntry 123 | pub trait NtBoxedListElement { 124 | /// Identifier of the list 125 | type L: NtTypedList; 126 | } 127 | -------------------------------------------------------------------------------- /nt-list_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nt-list_macros" 3 | version = "0.3.0" 4 | authors = ["Colin Finck "] 5 | description = "Macros for nt-list" 6 | edition = "2021" 7 | rust-version = "1.56" 8 | license = "MIT OR Apache-2.0" 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [dependencies] 14 | proc-macro2 = "1.0.69" 15 | quote = "1.0.33" 16 | syn = { version = "2.0.38", features = ["derive", "parsing", "printing", "proc-macro"], default-features = false } 17 | -------------------------------------------------------------------------------- /nt-list_macros/src/helpers.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Colin Finck 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | use proc_macro2::TokenStream; 5 | use quote::quote; 6 | use syn::{ 7 | Data, DeriveInput, Error, Field, Fields, GenericArgument, Ident, PathArguments, Result, Type, 8 | TypePath, 9 | }; 10 | 11 | /// Helper function to derive the trait that designates an empty enum as a list. 12 | /// 13 | /// Example parameters for the doubly linked list: 14 | /// * trait_name: "NtList" 15 | /// * trait_path: quote! {::nt_list::list::traits::NtList} 16 | pub(crate) fn derive_list_enum_trait( 17 | input: DeriveInput, 18 | list_type_name: &str, 19 | list_type_path: TokenStream, 20 | ) -> Result { 21 | if let Data::Enum(e) = &input.data { 22 | if e.variants.is_empty() { 23 | let ident = &input.ident; 24 | 25 | return Ok(quote! { 26 | impl ::nt_list::NtTypedList for #ident { 27 | type T = #list_type_path; 28 | } 29 | }); 30 | } 31 | } 32 | 33 | Err(Error::new_spanned( 34 | input, 35 | format!("{} can only be derived for an empty enum", list_type_name), 36 | )) 37 | } 38 | 39 | /// Helper function to derive NtListElement. 40 | pub fn derive_list_struct_trait(input: DeriveInput) -> Result { 41 | let s = match &input.data { 42 | Data::Struct(s) => s, 43 | _ => { 44 | return Err(Error::new_spanned( 45 | input, 46 | "NtListElement can only be derived for structs", 47 | )) 48 | } 49 | }; 50 | 51 | let f = match &s.fields { 52 | Fields::Named(f) => f, 53 | _ => { 54 | return Err(Error::new_spanned( 55 | input, 56 | "NtListElement can only be derived for structs with named fields", 57 | )) 58 | } 59 | }; 60 | 61 | if !has_repr_c(&input) { 62 | return Err(Error::new_spanned( 63 | input, 64 | "NtListElement can only be derived for structs with #[repr(C)]", 65 | )); 66 | } 67 | 68 | let mut boxed_attrs = 0usize; 69 | let ident = &input.ident; 70 | 71 | let tokens = f.named.iter().filter_map(|field| { 72 | parse_element_field(field).map(|info| { 73 | let field_ident = info.ident; 74 | let list_ty = info.list_ty; 75 | boxed_attrs += info.is_boxed as usize; 76 | 77 | let mut boxed_impl = TokenStream::new(); 78 | if info.is_boxed { 79 | boxed_impl = quote! { 80 | impl ::nt_list::NtBoxedListElement for #ident { 81 | type L = #list_ty; 82 | } 83 | }; 84 | } 85 | 86 | quote! { 87 | unsafe impl ::nt_list::NtListElement<#list_ty> for #ident { 88 | fn offset() -> usize { 89 | let base = ::core::mem::MaybeUninit::<#ident>::uninit(); 90 | let base_ptr = base.as_ptr(); 91 | let field_ptr = unsafe { ::core::ptr::addr_of!((*base_ptr).#field_ident) }; 92 | field_ptr as usize - base_ptr as usize 93 | } 94 | } 95 | 96 | #boxed_impl 97 | } 98 | }) 99 | }); 100 | let output = quote! { 101 | #(#tokens)* 102 | }; 103 | 104 | if output.is_empty() { 105 | return Err(Error::new_spanned( 106 | input, 107 | "Found no NtListEntry/NtSingleListEntry fields", 108 | )); 109 | } 110 | 111 | if boxed_attrs > 1 { 112 | return Err(Error::new_spanned( 113 | input, 114 | "Only a single entry field may have a #[boxed] attribute", 115 | )); 116 | } 117 | 118 | Ok(output) 119 | } 120 | 121 | /// Returns whether the given input has a `#[repr(C)]` attribute. 122 | /// 123 | /// This also works when multiple `repr` attributes are used, or a single `repr` attribute has multiple entries. 124 | fn has_repr_c(input: &DeriveInput) -> bool { 125 | let mut repr_c = false; 126 | 127 | for attr in &input.attrs { 128 | if attr.path().is_ident("repr") { 129 | let _ = attr.parse_nested_meta(|meta| { 130 | if meta.path.is_ident("C") { 131 | repr_c = true; 132 | } 133 | 134 | Ok(()) 135 | }); 136 | } 137 | } 138 | 139 | repr_c 140 | } 141 | 142 | pub(crate) struct ElementFieldInfo<'a> { 143 | /// The "entry" in `entry: nt_list::list::base::NtListEntry` 144 | pub(crate) ident: &'a Ident, 145 | /// The "mytraits::MyList" in `entry: nt_list::list::base::NtListEntry` 146 | pub(crate) list_ty: &'a TypePath, 147 | /// Whether a `#[boxed]` attribute has been placed before the field. 148 | pub(crate) is_boxed: bool, 149 | } 150 | 151 | /// Checks if the given field is a list entry field of an element structure and returns some 152 | /// information about it. 153 | /// 154 | /// `field` can be the syntax tree of e.g. 155 | /// * `entry: NtListEntry` 156 | /// * `entry: nt_list::list::base::NtListEntry` 157 | pub(crate) fn parse_element_field(field: &Field) -> Option { 158 | const SUPPORTED_TYPES: &[&str] = &["NtListEntry", "NtSingleListEntry"]; 159 | 160 | let ident = &field.ident.as_ref()?; 161 | let is_boxed = field.attrs.iter().any(|attr| attr.path().is_ident("boxed")); 162 | 163 | // Get the last segment of the type path and check it against the type name. 164 | // This isn't 100% accurate, we may catch similarly named types that are not ours. 165 | // But a user who derives `NtListElement` for a structure shouldn't mix it with foreign `NtListEntry` types anyway... 166 | let ty_path = match &field.ty { 167 | Type::Path(ty_path) => ty_path, 168 | _ => return None, 169 | }; 170 | 171 | let segment = ty_path.path.segments.last()?; 172 | if !SUPPORTED_TYPES.iter().any(|x| segment.ident == x) { 173 | return None; 174 | } 175 | 176 | // Make our check more accurate by also checking that the `NtListEntry` type of this field has two type parameters. 177 | let ab_args = match &segment.arguments { 178 | PathArguments::AngleBracketed(ab_args) => ab_args, 179 | _ => return None, 180 | }; 181 | if ab_args.args.len() != 2 { 182 | return None; 183 | } 184 | 185 | // Now we can be reasonably sure that this is our `NtListEntry` type and the second type parameter is the one 186 | // we are looking for. 187 | let arg = ab_args.args.last()?; 188 | let ty = match &arg { 189 | GenericArgument::Type(ty) => ty, 190 | _ => return None, 191 | }; 192 | let list_ty = match &ty { 193 | Type::Path(list_ty) => list_ty, 194 | _ => return None, 195 | }; 196 | 197 | Some(ElementFieldInfo { 198 | ident, 199 | list_ty, 200 | is_boxed, 201 | }) 202 | } 203 | -------------------------------------------------------------------------------- /nt-list_macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Colin Finck 2 | // SPDX-License-Identifier: MIT OR Apache-2.0 3 | 4 | mod helpers; 5 | 6 | use proc_macro::TokenStream; 7 | use quote::quote; 8 | use syn::{parse_macro_input, DeriveInput}; 9 | 10 | #[proc_macro_derive(NtList)] 11 | pub fn derive_nt_list(input: TokenStream) -> TokenStream { 12 | let input = parse_macro_input!(input as DeriveInput); 13 | helpers::derive_list_enum_trait(input, "NtList", quote! {::nt_list::list::NtList}) 14 | .unwrap_or_else(|e| e.to_compile_error()) 15 | .into() 16 | } 17 | 18 | #[proc_macro_derive(NtListElement, attributes(boxed))] 19 | pub fn derive_nt_list_element(input: TokenStream) -> TokenStream { 20 | let input = parse_macro_input!(input as DeriveInput); 21 | helpers::derive_list_struct_trait(input) 22 | .unwrap_or_else(|e| e.to_compile_error()) 23 | .into() 24 | } 25 | 26 | #[proc_macro_derive(NtSingleList)] 27 | pub fn derive_nt_single_list(input: TokenStream) -> TokenStream { 28 | let input = parse_macro_input!(input as DeriveInput); 29 | helpers::derive_list_enum_trait( 30 | input, 31 | "NtSingleList", 32 | quote! {::nt_list::single_list::NtSingleList}, 33 | ) 34 | .unwrap_or_else(|e| e.to_compile_error()) 35 | .into() 36 | } 37 | --------------------------------------------------------------------------------