Skip to content

Commit 467d04d

Browse files
committed
feat(sys): reimplement ngx_queue_t macros
Bindgen does not support functional macros or inline functions, so we have to maintain a copy of the implementation of any such item we need to use. There are no safe wrappers, because all the operations here are unsafe and unsound, unless used on the heap- or pool-allocated objects with certain ownership constraints.
1 parent 9d0cee9 commit 467d04d

File tree

2 files changed

+261
-0
lines changed

2 files changed

+261
-0
lines changed

nginx-sys/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#![warn(missing_docs)]
33
#![no_std]
44

5+
mod queue;
6+
57
use core::fmt;
68
use core::mem::offset_of;
79
use core::ptr::copy_nonoverlapping;
@@ -22,6 +24,8 @@ mod bindings {
2224
#[doc(no_inline)]
2325
pub use bindings::*;
2426

27+
pub use queue::*;
28+
2529
/// The offset of the `main_conf` field in the `ngx_http_conf_ctx_t` struct.
2630
///
2731
/// This is used to access the main configuration context for an HTTP module.

nginx-sys/src/queue.rs

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
use core::ptr;
2+
3+
use crate::bindings::ngx_queue_t;
4+
5+
/// Get a reference to the beginning of a queue node data structure,
6+
/// considering the queue field offset in it.
7+
///
8+
/// # Safety
9+
///
10+
/// `$q` must be a valid pointer to the field `$link` in the struct `$type`
11+
#[macro_export]
12+
macro_rules! ngx_queue_data {
13+
($q:expr, $type:ident, $link:ident) => {
14+
$q.byte_sub(::core::mem::offset_of!($type, $link)).cast::<$type>()
15+
};
16+
}
17+
18+
/// Initializes the queue head before use.
19+
///
20+
/// # Safety
21+
///
22+
/// `q` must be a valid pointer to [ngx_queue_t].
23+
#[inline]
24+
pub unsafe fn ngx_queue_init(q: *mut ngx_queue_t) {
25+
(*q).prev = q;
26+
(*q).next = q;
27+
}
28+
29+
/// Returns `true` if the queue contains no elements.
30+
///
31+
/// # Safety
32+
///
33+
/// `q` must be a valid pointer to [ngx_queue_t], initialized with [ngx_queue_init].
34+
#[inline]
35+
pub unsafe fn ngx_queue_empty(q: *const ngx_queue_t) -> bool {
36+
q == (*q).prev
37+
}
38+
39+
/// Inserts a new node after the current.
40+
///
41+
/// # Safety
42+
///
43+
/// Both `q` and `x` must be valid pointers to [ngx_queue_t]
44+
#[inline]
45+
pub unsafe fn ngx_queue_insert_after(q: *mut ngx_queue_t, x: *mut ngx_queue_t) {
46+
(*x).next = (*q).next;
47+
(*(*x).next).prev = x;
48+
(*x).prev = q;
49+
(*q).next = x;
50+
}
51+
52+
/// Inserts a new node before the current.
53+
///
54+
/// # Safety
55+
///
56+
/// Both `q` and `x` must be valid pointers to [ngx_queue_t].
57+
#[inline]
58+
pub unsafe fn ngx_queue_insert_before(q: *mut ngx_queue_t, x: *mut ngx_queue_t) {
59+
(*x).prev = (*q).prev;
60+
(*(*x).prev).next = x;
61+
(*x).next = q;
62+
(*q).prev = x;
63+
}
64+
65+
/// Removes a node from the queue.
66+
///
67+
/// # Safety
68+
///
69+
/// `q` must be a valid pointer to an [ngx_queue_t] node.
70+
#[inline]
71+
pub unsafe fn ngx_queue_remove(q: *mut ngx_queue_t) {
72+
(*(*q).next).prev = (*q).prev;
73+
(*(*q).prev).next = (*q).next;
74+
(*q).prev = ptr::null_mut();
75+
(*q).next = ptr::null_mut();
76+
}
77+
78+
/// Splits a queue at a node, returning the queue tail in a separate queue.
79+
///
80+
/// # Safety
81+
///
82+
/// `h` must be a valid pointer to a head queue node.
83+
/// `q` must be a node in the queue `h`.
84+
/// `n` must be a valid pointer to [ngx_queue_t].
85+
#[inline]
86+
pub unsafe fn ngx_queue_split(h: *mut ngx_queue_t, q: *mut ngx_queue_t, n: *mut ngx_queue_t) {
87+
(*n).prev = (*h).prev;
88+
(*(*n).prev).next = n;
89+
(*n).next = q;
90+
(*h).prev = (*q).prev;
91+
(*(*h).prev).next = h;
92+
(*q).prev = n;
93+
}
94+
95+
/// Adds a second queue to the first queue.
96+
///
97+
/// # Safety
98+
///
99+
/// Both `h` and `n` must be valid pointers to queue heads, initialized with [ngx_queue_init].
100+
/// `n` will be left in invalid state, pointing to the subrange of `h` without back references.
101+
#[inline]
102+
pub unsafe fn ngx_queue_add(h: *mut ngx_queue_t, n: *mut ngx_queue_t) {
103+
(*(*h).prev).next = (*n).next;
104+
(*(*n).next).prev = (*h).prev;
105+
(*h).prev = (*n).prev;
106+
(*(*h).prev).next = h;
107+
}
108+
109+
impl ngx_queue_t {
110+
/// Returns `true` if the queue contains no elements.
111+
pub fn is_empty(&self) -> bool {
112+
unsafe { ngx_queue_empty(self) }
113+
}
114+
}
115+
116+
impl Default for ngx_queue_t {
117+
fn default() -> ngx_queue_t {
118+
ngx_queue_t {
119+
prev: ptr::null_mut(),
120+
next: ptr::null_mut(),
121+
}
122+
}
123+
}
124+
125+
#[cfg(test)]
126+
mod tests {
127+
extern crate alloc;
128+
use alloc::boxed::Box;
129+
130+
use super::*;
131+
132+
struct TestData {
133+
value: usize,
134+
queue: ngx_queue_t,
135+
}
136+
137+
impl TestData {
138+
pub fn new(value: usize) -> *mut Self {
139+
// We should be using `ngx_pool_t` here, but that is not possible without linking to
140+
// the nginx
141+
let mut x = Box::new(Self {
142+
value,
143+
queue: Default::default(),
144+
});
145+
unsafe { ngx_queue_init(ptr::addr_of_mut!(x.queue)) };
146+
Box::into_raw(x)
147+
}
148+
149+
pub unsafe fn free(x: *mut Self) {
150+
let _ = Box::from_raw(x);
151+
}
152+
}
153+
154+
impl Drop for TestData {
155+
fn drop(&mut self) {
156+
if !self.queue.next.is_null() && !self.queue.is_empty() {
157+
unsafe { ngx_queue_remove(ptr::addr_of_mut!(self.queue)) };
158+
}
159+
}
160+
}
161+
162+
struct Iter {
163+
h: *mut ngx_queue_t,
164+
q: *mut ngx_queue_t,
165+
next: fn(*mut ngx_queue_t) -> *mut ngx_queue_t,
166+
}
167+
168+
impl Iter {
169+
pub fn new(h: *mut ngx_queue_t) -> Self {
170+
let next = |x: *mut ngx_queue_t| unsafe { (*x).next };
171+
Self { h, q: next(h), next }
172+
}
173+
174+
pub fn new_reverse(h: *mut ngx_queue_t) -> Self {
175+
let next = |x: *mut ngx_queue_t| unsafe { (*x).prev };
176+
Self { h, q: next(h), next }
177+
}
178+
}
179+
180+
impl Iterator for Iter {
181+
type Item = *mut ngx_queue_t;
182+
183+
fn next(&mut self) -> Option<Self::Item> {
184+
if self.h == self.q {
185+
return None;
186+
}
187+
188+
let item = self.q;
189+
self.q = (self.next)(self.q);
190+
Some(item)
191+
}
192+
}
193+
194+
#[test]
195+
fn test_queue() {
196+
fn value(q: *mut ngx_queue_t) -> usize {
197+
unsafe { (*ngx_queue_data!(q, TestData, queue)).value }
198+
}
199+
200+
// Check forward and reverse iteration
201+
fn cmp(h: *mut ngx_queue_t, other: &[usize]) -> bool {
202+
Iter::new(h).map(value).eq(other.iter().cloned())
203+
&& Iter::new_reverse(h).map(value).eq(other.iter().rev().cloned())
204+
}
205+
206+
// Note how this test does not use references or borrows to avoid triggering UBs
207+
// detectable by Miri. This does not mean that the code is safe or sound.
208+
unsafe {
209+
// Initialize and fill the queue
210+
211+
let mut h1 = ngx_queue_t::default();
212+
ngx_queue_init(ptr::addr_of_mut!(h1));
213+
214+
let mut h2 = ngx_queue_t::default();
215+
ngx_queue_init(ptr::addr_of_mut!(h2));
216+
217+
for i in 1..=5 {
218+
let elem = TestData::new(i);
219+
ngx_queue_insert_before(ptr::addr_of_mut!(h1), ptr::addr_of_mut!((*elem).queue));
220+
221+
let elem = TestData::new(i);
222+
ngx_queue_insert_after(ptr::addr_of_mut!(h2), ptr::addr_of_mut!((*elem).queue));
223+
}
224+
225+
// Iterate and test the values
226+
227+
assert!(cmp(ptr::addr_of_mut!(h1), &[1, 2, 3, 4, 5]));
228+
assert!(cmp(ptr::addr_of_mut!(h2), &[5, 4, 3, 2, 1]));
229+
230+
// Move nodes from h2 to h1
231+
232+
// h2 still points to the subrange of h1 after this operation
233+
ngx_queue_add(ptr::addr_of_mut!(h1), ptr::addr_of_mut!(h2));
234+
235+
assert!(cmp(ptr::addr_of_mut!(h1), &[1, 2, 3, 4, 5, 5, 4, 3, 2, 1]));
236+
237+
ngx_queue_split(ptr::addr_of_mut!(h1), (*h2.next).next, ptr::addr_of_mut!(h2));
238+
239+
assert!(cmp(ptr::addr_of_mut!(h1), &[1, 2, 3, 4, 5, 5]));
240+
assert!(cmp(ptr::addr_of_mut!(h2), &[4, 3, 2, 1]));
241+
242+
// Cleanup
243+
244+
for q in Iter::new(ptr::addr_of_mut!(h1)) {
245+
let td = ngx_queue_data!(q, TestData, queue);
246+
TestData::free(td);
247+
}
248+
assert!(h1.is_empty());
249+
250+
for q in Iter::new(ptr::addr_of_mut!(h2)) {
251+
let td = ngx_queue_data!(q, TestData, queue);
252+
TestData::free(td);
253+
}
254+
assert!(h2.is_empty());
255+
};
256+
}
257+
}

0 commit comments

Comments
 (0)