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}