ngx/core/
pool.rs

1use core::alloc::Layout;
2use core::ffi::c_void;
3use core::mem;
4use core::ptr::{self, NonNull};
5
6use nginx_sys::{
7    ngx_buf_t, ngx_create_temp_buf, ngx_palloc, ngx_pcalloc, ngx_pfree, ngx_pmemalign, ngx_pnalloc,
8    ngx_pool_cleanup_add, ngx_pool_t, NGX_ALIGNMENT,
9};
10
11use crate::allocator::{dangling_for_layout, AllocError, Allocator};
12use crate::core::buffer::{Buffer, MemoryBuffer, TemporaryBuffer};
13
14/// Non-owning wrapper for an [`ngx_pool_t`] pointer, providing methods for working with memory pools.
15///
16/// See <https://nginx.org/en/docs/dev/development_guide.html#pool>
17#[derive(Clone, Debug)]
18#[repr(transparent)]
19pub struct Pool(NonNull<ngx_pool_t>);
20
21unsafe impl Allocator for Pool {
22    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
23        // SAFETY:
24        // * This wrapper should be constructed with a valid pointer to ngx_pool_t.
25        // * The Pool type is !Send, thus we expect exclusive access for this call.
26        // * Pointers are considered mutable unless obtained from an immutable reference.
27        let ptr = if layout.size() == 0 {
28            // We can guarantee alignment <= NGX_ALIGNMENT for allocations of size 0 made with
29            // ngx_palloc_small. Any other cases are implementation-defined, and we can't tell which
30            // one will be used internally.
31            return Ok(NonNull::slice_from_raw_parts(
32                dangling_for_layout(&layout),
33                layout.size(),
34            ));
35        } else if layout.align() == 1 {
36            unsafe { ngx_pnalloc(self.0.as_ptr(), layout.size()) }
37        } else if layout.align() <= NGX_ALIGNMENT {
38            unsafe { ngx_palloc(self.0.as_ptr(), layout.size()) }
39        } else if cfg!(any(
40            ngx_feature = "have_posix_memalign",
41            ngx_feature = "have_memalign"
42        )) {
43            // ngx_pmemalign is always defined, but does not guarantee the requested alignment
44            // unless memalign/posix_memalign exists.
45            unsafe { ngx_pmemalign(self.0.as_ptr(), layout.size(), layout.align()) }
46        } else {
47            return Err(AllocError);
48        };
49
50        // Verify the alignment of the result
51        debug_assert_eq!(ptr.align_offset(layout.align()), 0);
52
53        let ptr = NonNull::new(ptr.cast()).ok_or(AllocError)?;
54        Ok(NonNull::slice_from_raw_parts(ptr, layout.size()))
55    }
56
57    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
58        // ngx_pfree is noop for small allocations unless NGX_DEBUG_PALLOC is set.
59        //
60        // Note: there should be no cleanup handlers for the allocations made using this API.
61        // Violating that could result in the following issues:
62        //  - use-after-free on large allocation
63        //  - multiple cleanup handlers attached to a dangling ptr (these are not unique)
64        if layout.size() > 0 // 0 is dangling ptr
65            && (layout.size() > self.as_ref().max || layout.align() > NGX_ALIGNMENT)
66        {
67            ngx_pfree(self.0.as_ptr(), ptr.as_ptr().cast());
68        }
69    }
70}
71
72impl AsRef<ngx_pool_t> for Pool {
73    #[inline]
74    fn as_ref(&self) -> &ngx_pool_t {
75        // SAFETY: this wrapper should be constructed with a valid pointer to ngx_pool_t
76        unsafe { self.0.as_ref() }
77    }
78}
79
80impl AsMut<ngx_pool_t> for Pool {
81    #[inline]
82    fn as_mut(&mut self) -> &mut ngx_pool_t {
83        // SAFETY: this wrapper should be constructed with a valid pointer to ngx_pool_t
84        unsafe { self.0.as_mut() }
85    }
86}
87
88impl Pool {
89    /// Creates a new `Pool` from an `ngx_pool_t` pointer.
90    ///
91    /// # Safety
92    /// The caller must ensure that a valid `ngx_pool_t` pointer is provided, pointing to valid
93    /// memory and non-null. A null argument will cause an assertion failure and panic.
94    pub unsafe fn from_ngx_pool(pool: *mut ngx_pool_t) -> Pool {
95        debug_assert!(!pool.is_null());
96        debug_assert!(pool.is_aligned());
97        Pool(NonNull::new_unchecked(pool))
98    }
99
100    /// Creates a buffer of the specified size in the memory pool.
101    ///
102    /// Returns `Some(TemporaryBuffer)` if the buffer is successfully created, or `None` if
103    /// allocation fails.
104    pub fn create_buffer(&mut self, size: usize) -> Option<TemporaryBuffer> {
105        let buf = unsafe { ngx_create_temp_buf(self.as_mut(), size) };
106        if buf.is_null() {
107            return None;
108        }
109
110        Some(TemporaryBuffer::from_ngx_buf(buf))
111    }
112
113    /// Creates a buffer from a string in the memory pool.
114    ///
115    /// Returns `Some(TemporaryBuffer)` if the buffer is successfully created, or `None` if
116    /// allocation fails.
117    pub fn create_buffer_from_str(&mut self, str: &str) -> Option<TemporaryBuffer> {
118        let mut buffer = self.create_buffer(str.len())?;
119        unsafe {
120            let buf = buffer.as_ngx_buf_mut();
121            ptr::copy_nonoverlapping(str.as_ptr(), (*buf).pos, str.len());
122            (*buf).last = (*buf).pos.add(str.len());
123        }
124        Some(buffer)
125    }
126
127    /// Creates a buffer from a static string in the memory pool.
128    ///
129    /// Returns `Some(MemoryBuffer)` if the buffer is successfully created, or `None` if allocation
130    /// fails.
131    pub fn create_buffer_from_static_str(&mut self, str: &'static str) -> Option<MemoryBuffer> {
132        let buf = self.calloc_type::<ngx_buf_t>();
133        if buf.is_null() {
134            return None;
135        }
136
137        // We cast away const, but buffers with the memory flag are read-only
138        let start = str.as_ptr() as *mut u8;
139        let end = unsafe { start.add(str.len()) };
140
141        unsafe {
142            (*buf).start = start;
143            (*buf).pos = start;
144            (*buf).last = end;
145            (*buf).end = end;
146            (*buf).set_memory(1);
147        }
148
149        Some(MemoryBuffer::from_ngx_buf(buf))
150    }
151
152    /// Adds a cleanup handler for a value in the memory pool.
153    ///
154    /// Returns `Ok(())` if the cleanup handler is successfully added, or `Err(())` if the cleanup
155    /// handler cannot be added.
156    ///
157    /// # Safety
158    /// This function is marked as unsafe because it involves raw pointer manipulation.
159    unsafe fn add_cleanup_for_value<T>(&mut self, value: *mut T) -> Result<(), ()> {
160        let cln = ngx_pool_cleanup_add(self.0.as_ptr(), 0);
161        if cln.is_null() {
162            return Err(());
163        }
164        (*cln).handler = Some(cleanup_type::<T>);
165        (*cln).data = value as *mut c_void;
166
167        Ok(())
168    }
169
170    /// Allocates memory from the pool of the specified size.
171    /// The resulting pointer is aligned to a platform word size.
172    ///
173    /// Returns a raw pointer to the allocated memory.
174    pub fn alloc(&mut self, size: usize) -> *mut c_void {
175        unsafe { ngx_palloc(self.0.as_ptr(), size) }
176    }
177
178    /// Allocates memory for a type from the pool.
179    /// The resulting pointer is aligned to a platform word size.
180    ///
181    /// Returns a typed pointer to the allocated memory.
182    pub fn alloc_type<T: Copy>(&mut self) -> *mut T {
183        self.alloc(mem::size_of::<T>()) as *mut T
184    }
185
186    /// Allocates zeroed memory from the pool of the specified size.
187    /// The resulting pointer is aligned to a platform word size.
188    ///
189    /// Returns a raw pointer to the allocated memory.
190    pub fn calloc(&mut self, size: usize) -> *mut c_void {
191        unsafe { ngx_pcalloc(self.0.as_ptr(), size) }
192    }
193
194    /// Allocates zeroed memory for a type from the pool.
195    /// The resulting pointer is aligned to a platform word size.
196    ///
197    /// Returns a typed pointer to the allocated memory.
198    pub fn calloc_type<T: Copy>(&mut self) -> *mut T {
199        self.calloc(mem::size_of::<T>()) as *mut T
200    }
201
202    /// Allocates unaligned memory from the pool of the specified size.
203    ///
204    /// Returns a raw pointer to the allocated memory.
205    pub fn alloc_unaligned(&mut self, size: usize) -> *mut c_void {
206        unsafe { ngx_pnalloc(self.0.as_ptr(), size) }
207    }
208
209    /// Allocates unaligned memory for a type from the pool.
210    ///
211    /// Returns a typed pointer to the allocated memory.
212    pub fn alloc_type_unaligned<T: Copy>(&mut self) -> *mut T {
213        self.alloc_unaligned(mem::size_of::<T>()) as *mut T
214    }
215
216    /// Allocates memory for a value of a specified type and adds a cleanup handler to the memory
217    /// pool.
218    ///
219    /// Returns a typed pointer to the allocated memory if successful, or a null pointer if
220    /// allocation or cleanup handler addition fails.
221    pub fn allocate<T>(&mut self, value: T) -> *mut T {
222        unsafe {
223            let p = self.alloc(mem::size_of::<T>()) as *mut T;
224            ptr::write(p, value);
225            if self.add_cleanup_for_value(p).is_err() {
226                ptr::drop_in_place(p);
227                return ptr::null_mut();
228            };
229            p
230        }
231    }
232}
233
234/// Cleanup handler for a specific type `T`.
235///
236/// This function is called when cleaning up a value of type `T` in an FFI context.
237///
238/// # Safety
239/// This function is marked as unsafe due to the raw pointer manipulation and the assumption that
240/// `data` is a valid pointer to `T`.
241///
242/// # Arguments
243///
244/// * `data` - A raw pointer to the value of type `T` to be cleaned up.
245unsafe extern "C" fn cleanup_type<T>(data: *mut c_void) {
246    ptr::drop_in_place(data as *mut T);
247}