safer_ring/registry/mod.rs
1//! File descriptor and buffer registration for performance optimization.
2//!
3//! This module provides safe registration of file descriptors and buffers with io_uring
4//! for improved performance. Registration allows the kernel to avoid repeated lookups
5//! and validations for frequently used resources.
6//!
7//! # Safety Guarantees
8//!
9//! - Registered resources cannot be used after unregistration (compile-time enforced)
10//! - Resources cannot be unregistered while operations are using them
11//! - Buffer addresses remain stable throughout registration lifetime
12//! - File descriptors are validated before registration
13//!
14//! # Example
15//!
16//! ```rust,no_run
17//! # use safer_ring::{Registry, Ring};
18//! # use std::pin::Pin;
19//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
20//! let mut registry = Registry::new();
21//!
22//! // Register a file descriptor
23//! let registered_fd = registry.register_fd(0)?;
24//!
25//! // Register a buffer
26//! let buffer = Pin::new(Box::new([0u8; 1024]));
27//! let registered_buffer = registry.register_buffer(buffer)?;
28//!
29//! println!("Registered FD index: {}", registered_fd.index());
30//! println!("Registered buffer size: {}", registered_buffer.size());
31//! # Ok(())
32//! # }
33//! ```
34
35use std::collections::HashSet;
36use std::marker::PhantomData;
37use std::os::unix::io::RawFd;
38use std::pin::Pin;
39
40use crate::error::{Result, SaferRingError};
41use crate::ownership::OwnedBuffer;
42
43#[cfg(target_os = "linux")]
44#[cfg(test)]
45mod tests;
46
47/// Registry for managing registered file descriptors and buffers.
48///
49/// The registry provides safe management of io_uring registered resources,
50/// ensuring that resources cannot be used after unregistration and that
51/// unregistration cannot occur while resources are in use.
52///
53/// # Lifetime Management
54///
55/// The registry is tied to a ring lifetime to ensure that registered resources
56/// cannot outlive the ring that registered them. This prevents use-after-free
57/// bugs when the ring is dropped.
58///
59/// # Thread Safety
60///
61/// The registry is not thread-safe by design. Each ring should have its own
62/// registry, and operations should be performed from a single thread or
63/// properly synchronized externally.
64pub struct Registry<'ring> {
65 /// Registered file descriptors with their original fd values
66 registered_fds: Vec<Option<(RawFd, RegisteredFdInner)>>,
67 /// Registered buffers with their pinned memory
68 registered_buffers: Vec<Option<Pin<Box<[u8]>>>>,
69 /// Fixed files for optimized access by index
70 fixed_files: Vec<Option<RawFd>>,
71 /// Pre-registered buffer slots for kernel buffer selection
72 registered_buffer_slots: Vec<Option<OwnedBuffer>>,
73 /// Set of file descriptor indices currently in use by operations
74 fds_in_use: HashSet<u32>,
75 /// Set of buffer indices currently in use by operations
76 buffers_in_use: HashSet<u32>,
77 /// Set of fixed file indices currently in use by operations
78 fixed_files_in_use: HashSet<u32>,
79 /// Set of registered buffer slot indices currently in use
80 buffer_slots_in_use: HashSet<u32>,
81 /// Whether the registry has been registered with io_uring
82 #[allow(dead_code)]
83 is_registered: bool,
84 /// Phantom data for lifetime tracking
85 _phantom: PhantomData<&'ring ()>,
86}
87
88/// Internal data for a registered file descriptor.
89#[derive(Debug, Clone)]
90struct RegisteredFdInner {
91 /// Original file descriptor value
92 #[allow(dead_code)]
93 fd: RawFd,
94 /// Whether this fd is currently in use
95 #[allow(dead_code)]
96 in_use: bool,
97}
98
99/// A registered file descriptor with safe handle and lifetime tracking.
100///
101/// This handle prevents the file descriptor from being unregistered while
102/// it's still in use by operations. The handle is tied to the registry
103/// lifetime to prevent use after the registry is dropped.
104#[derive(Debug)]
105pub struct RegisteredFd {
106 /// Index in the registration table
107 index: u32,
108 /// Original file descriptor for validation
109 fd: RawFd,
110}
111
112/// A registered buffer with safe handle and lifetime tracking.
113///
114/// This handle prevents the buffer from being unregistered while it's
115/// still in use by operations. The handle maintains information about
116/// the buffer size for validation purposes.
117#[derive(Debug)]
118pub struct RegisteredBuffer {
119 /// Index in the registration table
120 index: u32,
121 /// Size of the registered buffer
122 size: usize,
123}
124
125impl<'ring> Default for Registry<'ring> {
126 fn default() -> Self {
127 Self::new()
128 }
129}
130
131impl<'ring> Registry<'ring> {
132 /// Create a new empty registry.
133 ///
134 /// The registry starts empty and can have file descriptors and buffers
135 /// registered with it. Registration with io_uring happens when the
136 /// first resource is registered.
137 pub fn new() -> Self {
138 Self {
139 registered_fds: Vec::new(),
140 registered_buffers: Vec::new(),
141 fixed_files: Vec::new(),
142 registered_buffer_slots: Vec::new(),
143 fds_in_use: HashSet::new(),
144 buffers_in_use: HashSet::new(),
145 fixed_files_in_use: HashSet::new(),
146 buffer_slots_in_use: HashSet::new(),
147 is_registered: false,
148 _phantom: PhantomData,
149 }
150 }
151
152 /// Register a file descriptor for optimized access.
153 ///
154 /// Registers the file descriptor with io_uring for improved performance
155 /// on subsequent operations. The returned handle can be used to reference
156 /// the registered file descriptor in operations.
157 ///
158 /// # Arguments
159 ///
160 /// * `fd` - The file descriptor to register
161 ///
162 /// # Returns
163 ///
164 /// Returns a RegisteredFd handle that can be used in operations.
165 ///
166 /// # Errors
167 ///
168 /// - Returns `SaferRingError::Io` if the file descriptor is invalid
169 /// - Returns `SaferRingError::IoUring` if io_uring registration fails
170 ///
171 /// # Example
172 ///
173 /// ```rust,no_run
174 /// # use safer_ring::Registry;
175 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
176 /// let mut registry = Registry::new();
177 /// let registered_fd = registry.register_fd(0)?; // stdin
178 /// # Ok(())
179 /// # }
180 /// ```
181 pub fn register_fd(&mut self, fd: RawFd) -> Result<RegisteredFd> {
182 // Validate file descriptor
183 if fd < 0 {
184 return Err(SaferRingError::Io(std::io::Error::new(
185 std::io::ErrorKind::InvalidInput,
186 "File descriptor must be non-negative",
187 )));
188 }
189
190 // Find an empty slot or add a new one
191 let index =
192 if let Some(empty_index) = self.registered_fds.iter().position(|slot| slot.is_none()) {
193 empty_index as u32
194 } else {
195 let index = self.registered_fds.len() as u32;
196 self.registered_fds.push(None);
197 index
198 };
199
200 let inner = RegisteredFdInner { fd, in_use: false };
201
202 // Store the registration
203 self.registered_fds[index as usize] = Some((fd, inner));
204
205 // TODO: Actually register with io_uring when integrated with Ring
206 // For now, we just track the registration locally
207
208 Ok(RegisteredFd { index, fd })
209 }
210
211 /// Register a buffer for optimized access.
212 ///
213 /// Registers the buffer with io_uring for improved performance on
214 /// subsequent operations. The buffer must be pinned to ensure its
215 /// address remains stable throughout the registration lifetime.
216 ///
217 /// # Arguments
218 ///
219 /// * `buffer` - The pinned buffer to register
220 ///
221 /// # Returns
222 ///
223 /// Returns a RegisteredBuffer handle that can be used in operations.
224 ///
225 /// # Errors
226 ///
227 /// - Returns `SaferRingError::Io` if the buffer is empty
228 /// - Returns `SaferRingError::IoUring` if io_uring registration fails
229 ///
230 /// # Example
231 ///
232 /// ```rust,no_run
233 /// # use safer_ring::Registry;
234 /// # use std::pin::Pin;
235 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
236 /// let mut registry = Registry::new();
237 /// let buffer = Pin::new(Box::new([0u8; 1024]));
238 /// let registered_buffer = registry.register_buffer(buffer)?;
239 /// # Ok(())
240 /// # }
241 /// ```
242 pub fn register_buffer(&mut self, buffer: Pin<Box<[u8]>>) -> Result<RegisteredBuffer> {
243 if buffer.is_empty() {
244 return Err(SaferRingError::Io(std::io::Error::new(
245 std::io::ErrorKind::InvalidInput,
246 "Buffer cannot be empty",
247 )));
248 }
249
250 let size = buffer.len();
251
252 // Find an empty slot or add a new one
253 let index = if let Some(empty_index) = self
254 .registered_buffers
255 .iter()
256 .position(|slot| slot.is_none())
257 {
258 empty_index as u32
259 } else {
260 let index = self.registered_buffers.len() as u32;
261 self.registered_buffers.push(None);
262 index
263 };
264
265 // Store the buffer
266 self.registered_buffers[index as usize] = Some(buffer);
267
268 // TODO: Actually register with io_uring when integrated with Ring
269 // For now, we just track the registration locally
270
271 Ok(RegisteredBuffer { index, size })
272 }
273
274 /// Unregister a file descriptor.
275 ///
276 /// Removes the file descriptor from the registry. This operation will
277 /// fail if the file descriptor is currently in use by any operations.
278 ///
279 /// # Arguments
280 ///
281 /// * `registered_fd` - The registered file descriptor to unregister
282 ///
283 /// # Errors
284 ///
285 /// - Returns `SaferRingError::NotRegistered` if the fd is not registered
286 /// - Returns `SaferRingError::BufferInFlight` if the fd is in use
287 ///
288 /// # Safety
289 ///
290 /// This function consumes the RegisteredFd handle, preventing further
291 /// use after unregistration.
292 pub fn unregister_fd(&mut self, registered_fd: RegisteredFd) -> Result<()> {
293 let index = registered_fd.index as usize;
294
295 // Check if the fd exists and matches
296 if index >= self.registered_fds.len() {
297 return Err(SaferRingError::NotRegistered);
298 }
299
300 let slot = &mut self.registered_fds[index];
301 let Some((stored_fd, _)) = slot.as_ref() else {
302 return Err(SaferRingError::NotRegistered);
303 };
304
305 if *stored_fd != registered_fd.fd {
306 return Err(SaferRingError::NotRegistered);
307 }
308
309 // Check if the fd is in use
310 if self.fds_in_use.contains(®istered_fd.index) {
311 return Err(SaferRingError::BufferInFlight);
312 }
313
314 // Remove the registration
315 *slot = None;
316
317 // TODO: Actually unregister with io_uring when integrated with Ring
318
319 Ok(())
320 }
321
322 /// Unregister a buffer.
323 ///
324 /// Removes the buffer from the registry and returns ownership of the
325 /// pinned buffer. This operation will fail if the buffer is currently
326 /// in use by any operations.
327 ///
328 /// # Arguments
329 ///
330 /// * `registered_buffer` - The registered buffer to unregister
331 ///
332 /// # Returns
333 ///
334 /// Returns the original pinned buffer on successful unregistration.
335 ///
336 /// # Errors
337 ///
338 /// - Returns `SaferRingError::NotRegistered` if the buffer is not registered
339 /// - Returns `SaferRingError::BufferInFlight` if the buffer is in use
340 ///
341 /// # Safety
342 ///
343 /// This function consumes the RegisteredBuffer handle, preventing further
344 /// use after unregistration.
345 pub fn unregister_buffer(
346 &mut self,
347 registered_buffer: RegisteredBuffer,
348 ) -> Result<Pin<Box<[u8]>>> {
349 let index = registered_buffer.index as usize;
350
351 // Check if the buffer exists
352 if index >= self.registered_buffers.len() {
353 return Err(SaferRingError::NotRegistered);
354 }
355
356 let slot = &mut self.registered_buffers[index];
357 let Some(buffer) = slot.as_ref() else {
358 return Err(SaferRingError::NotRegistered);
359 };
360
361 // Validate the buffer size matches
362 if buffer.len() != registered_buffer.size {
363 return Err(SaferRingError::NotRegistered);
364 }
365
366 // Check if the buffer is in use
367 if self.buffers_in_use.contains(®istered_buffer.index) {
368 return Err(SaferRingError::BufferInFlight);
369 }
370
371 // Remove and return the buffer
372 let buffer = slot.take().unwrap();
373
374 // TODO: Actually unregister with io_uring when integrated with Ring
375
376 Ok(buffer)
377 }
378
379 /// Mark a file descriptor as in use by an operation.
380 ///
381 /// This is called internally when an operation is submitted using a
382 /// registered file descriptor. It prevents the fd from being unregistered
383 /// while the operation is in flight.
384 ///
385 /// # Arguments
386 ///
387 /// * `registered_fd` - The registered file descriptor being used
388 ///
389 /// # Errors
390 ///
391 /// Returns `SaferRingError::NotRegistered` if the fd is not registered.
392 #[allow(dead_code)]
393 pub(crate) fn mark_fd_in_use(&mut self, registered_fd: &RegisteredFd) -> Result<()> {
394 let index = registered_fd.index as usize;
395
396 if index >= self.registered_fds.len() || self.registered_fds[index].is_none() {
397 return Err(SaferRingError::NotRegistered);
398 }
399
400 self.fds_in_use.insert(registered_fd.index);
401 Ok(())
402 }
403
404 /// Mark a file descriptor as no longer in use.
405 ///
406 /// This is called internally when an operation using a registered file
407 /// descriptor completes. It allows the fd to be unregistered again.
408 ///
409 /// # Arguments
410 ///
411 /// * `registered_fd` - The registered file descriptor no longer in use
412 #[allow(dead_code)]
413 pub(crate) fn mark_fd_not_in_use(&mut self, registered_fd: &RegisteredFd) {
414 self.fds_in_use.remove(®istered_fd.index);
415 }
416
417 /// Mark a buffer as in use by an operation.
418 ///
419 /// This is called internally when an operation is submitted using a
420 /// registered buffer. It prevents the buffer from being unregistered
421 /// while the operation is in flight.
422 ///
423 /// # Arguments
424 ///
425 /// * `registered_buffer` - The registered buffer being used
426 ///
427 /// # Errors
428 ///
429 /// Returns `SaferRingError::NotRegistered` if the buffer is not registered.
430 #[allow(dead_code)]
431 pub(crate) fn mark_buffer_in_use(
432 &mut self,
433 registered_buffer: &RegisteredBuffer,
434 ) -> Result<()> {
435 let index = registered_buffer.index as usize;
436
437 if index >= self.registered_buffers.len() || self.registered_buffers[index].is_none() {
438 return Err(SaferRingError::NotRegistered);
439 }
440
441 self.buffers_in_use.insert(registered_buffer.index);
442 Ok(())
443 }
444
445 /// Mark a buffer as no longer in use.
446 ///
447 /// This is called internally when an operation using a registered buffer
448 /// completes. It allows the buffer to be unregistered again.
449 ///
450 /// # Arguments
451 ///
452 /// * `registered_buffer` - The registered buffer no longer in use
453 #[allow(dead_code)]
454 pub(crate) fn mark_buffer_not_in_use(&mut self, registered_buffer: &RegisteredBuffer) {
455 self.buffers_in_use.remove(®istered_buffer.index);
456 }
457
458 /// Get the number of registered file descriptors.
459 ///
460 /// Returns the total number of file descriptors currently registered,
461 /// including those that may be in use by operations.
462 pub fn fd_count(&self) -> usize {
463 self.registered_fds
464 .iter()
465 .filter(|slot| slot.is_some())
466 .count()
467 }
468
469 /// Get the number of registered buffers.
470 ///
471 /// Returns the total number of buffers currently registered,
472 /// including those that may be in use by operations.
473 pub fn buffer_count(&self) -> usize {
474 self.registered_buffers
475 .iter()
476 .filter(|slot| slot.is_some())
477 .count()
478 }
479
480 /// Get the number of file descriptors currently in use.
481 ///
482 /// Returns the number of registered file descriptors that are
483 /// currently being used by in-flight operations.
484 pub fn fds_in_use_count(&self) -> usize {
485 self.fds_in_use.len()
486 }
487
488 /// Get the number of buffers currently in use.
489 ///
490 /// Returns the number of registered buffers that are currently
491 /// being used by in-flight operations.
492 pub fn buffers_in_use_count(&self) -> usize {
493 self.buffers_in_use.len()
494 }
495
496 /// Check if a file descriptor is registered.
497 ///
498 /// # Arguments
499 ///
500 /// * `registered_fd` - The registered file descriptor to check
501 ///
502 /// # Returns
503 ///
504 /// Returns true if the file descriptor is currently registered.
505 pub fn is_fd_registered(&self, registered_fd: &RegisteredFd) -> bool {
506 let index = registered_fd.index as usize;
507 index < self.registered_fds.len()
508 && self.registered_fds[index]
509 .as_ref()
510 .map(|(fd, _)| *fd == registered_fd.fd)
511 .unwrap_or(false)
512 }
513
514 /// Check if a buffer is registered.
515 ///
516 /// # Arguments
517 ///
518 /// * `registered_buffer` - The registered buffer to check
519 ///
520 /// # Returns
521 ///
522 /// Returns true if the buffer is currently registered.
523 pub fn is_buffer_registered(&self, registered_buffer: &RegisteredBuffer) -> bool {
524 let index = registered_buffer.index as usize;
525 index < self.registered_buffers.len()
526 && self.registered_buffers[index]
527 .as_ref()
528 .map(|buffer| buffer.len() == registered_buffer.size)
529 .unwrap_or(false)
530 }
531
532 /// Get the raw file descriptor value for a registered fd.
533 ///
534 /// This is used internally by operations to get the actual fd value
535 /// for system calls.
536 ///
537 /// # Arguments
538 ///
539 /// * `registered_fd` - The registered file descriptor
540 ///
541 /// # Returns
542 ///
543 /// Returns the raw file descriptor value if registered.
544 ///
545 /// # Errors
546 ///
547 /// Returns `SaferRingError::NotRegistered` if the fd is not registered.
548 #[allow(dead_code)]
549 pub(crate) fn get_raw_fd(&self, registered_fd: &RegisteredFd) -> Result<RawFd> {
550 let index = registered_fd.index as usize;
551
552 if index >= self.registered_fds.len() {
553 return Err(SaferRingError::NotRegistered);
554 }
555
556 match &self.registered_fds[index] {
557 Some((fd, _)) if *fd == registered_fd.fd => Ok(*fd),
558 _ => Err(SaferRingError::NotRegistered),
559 }
560 }
561
562 /// Get a reference to a registered buffer.
563 ///
564 /// This is used internally by operations to access the buffer data
565 /// for I/O operations.
566 ///
567 /// # Arguments
568 ///
569 /// * `registered_buffer` - The registered buffer
570 ///
571 /// # Returns
572 ///
573 /// Returns a reference to the pinned buffer if registered.
574 ///
575 /// # Errors
576 ///
577 /// Returns `SaferRingError::NotRegistered` if the buffer is not registered.
578 #[allow(dead_code)]
579 pub(crate) fn get_buffer(
580 &self,
581 registered_buffer: &RegisteredBuffer,
582 ) -> Result<&Pin<Box<[u8]>>> {
583 let index = registered_buffer.index as usize;
584
585 if index >= self.registered_buffers.len() {
586 return Err(SaferRingError::NotRegistered);
587 }
588
589 match &self.registered_buffers[index] {
590 Some(buffer) if buffer.len() == registered_buffer.size => Ok(buffer),
591 _ => Err(SaferRingError::NotRegistered),
592 }
593 }
594
595 // Fixed Files API - Performance optimization for frequently used files
596
597 /// Register multiple files for optimized fixed-file operations.
598 ///
599 /// Fixed files are pre-registered with io_uring and accessed by index
600 /// instead of file descriptor, providing better performance for frequently
601 /// used files. This is particularly useful for applications that work with
602 /// a known set of files repeatedly.
603 ///
604 /// # Arguments
605 ///
606 /// * `fds` - Vector of file descriptors to register as fixed files
607 ///
608 /// # Returns
609 ///
610 /// Returns a vector of FixedFile handles in the same order as input.
611 ///
612 /// # Example
613 ///
614 /// ```rust,no_run
615 /// # use safer_ring::Registry;
616 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
617 /// let mut registry = Registry::new();
618 /// let fixed_files = registry.register_fixed_files(vec![0, 1, 2])?; // stdin, stdout, stderr
619 /// # Ok(())
620 /// # }
621 /// ```
622 pub fn register_fixed_files(&mut self, fds: Vec<RawFd>) -> Result<Vec<FixedFile>> {
623 if fds.is_empty() {
624 return Ok(Vec::new());
625 }
626
627 // Validate all file descriptors first
628 for &fd in &fds {
629 if fd < 0 {
630 return Err(SaferRingError::Io(std::io::Error::new(
631 std::io::ErrorKind::InvalidInput,
632 format!("Invalid file descriptor: {fd}"),
633 )));
634 }
635 }
636
637 // Clear existing fixed files if any
638 self.fixed_files.clear();
639 self.fixed_files_in_use.clear();
640
641 let mut fixed_files = Vec::new();
642
643 for (index, &fd) in fds.iter().enumerate() {
644 self.fixed_files.push(Some(fd));
645 fixed_files.push(FixedFile {
646 index: index as u32,
647 fd,
648 });
649 }
650
651 // TODO: Actually register with io_uring when integrated with Ring
652 // For now, we just track the registration locally
653
654 Ok(fixed_files)
655 }
656
657 /// Unregister all fixed files.
658 ///
659 /// This operation will fail if any fixed file is currently in use.
660 ///
661 /// # Errors
662 ///
663 /// Returns `SaferRingError::BufferInFlight` if any fixed file is in use.
664 pub fn unregister_fixed_files(&mut self) -> Result<()> {
665 if !self.fixed_files_in_use.is_empty() {
666 return Err(SaferRingError::BufferInFlight);
667 }
668
669 self.fixed_files.clear();
670 // TODO: Actually unregister with io_uring when integrated with Ring
671
672 Ok(())
673 }
674
675 /// Get the number of fixed files registered.
676 pub fn fixed_file_count(&self) -> usize {
677 self.fixed_files
678 .iter()
679 .filter(|slot| slot.is_some())
680 .count()
681 }
682
683 /// Get the number of fixed files currently in use.
684 pub fn fixed_files_in_use_count(&self) -> usize {
685 self.fixed_files_in_use.len()
686 }
687
688 // Registered Buffer Slots API - For kernel buffer selection optimization
689
690 /// Register multiple buffer slots for kernel buffer selection.
691 ///
692 /// Registered buffer slots are pre-allocated buffers that the kernel
693 /// can select from during I/O operations. This provides the best performance
694 /// for high-throughput applications by eliminating the need to specify
695 /// buffer addresses in each operation.
696 ///
697 /// # Arguments
698 ///
699 /// * `buffers` - Vector of owned buffers to register as selectable slots
700 ///
701 /// # Returns
702 ///
703 /// Returns a vector of RegisteredBufferSlot handles.
704 ///
705 /// # Example
706 ///
707 /// ```rust,no_run
708 /// # use safer_ring::{Registry, OwnedBuffer};
709 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
710 /// let mut registry = Registry::new();
711 /// let buffers = vec![
712 /// OwnedBuffer::new(4096),
713 /// OwnedBuffer::new(4096),
714 /// OwnedBuffer::new(4096),
715 /// ];
716 /// let buffer_slots = registry.register_buffer_slots(buffers)?;
717 /// # Ok(())
718 /// # }
719 /// ```
720 pub fn register_buffer_slots(
721 &mut self,
722 buffers: Vec<OwnedBuffer>,
723 ) -> Result<Vec<RegisteredBufferSlot>> {
724 if buffers.is_empty() {
725 return Ok(Vec::new());
726 }
727
728 // Clear existing buffer slots if any
729 self.registered_buffer_slots.clear();
730 self.buffer_slots_in_use.clear();
731
732 let mut buffer_slots = Vec::new();
733
734 for (index, buffer) in buffers.into_iter().enumerate() {
735 let size = buffer.size();
736 self.registered_buffer_slots.push(Some(buffer));
737 buffer_slots.push(RegisteredBufferSlot {
738 index: index as u32,
739 size,
740 in_use: false,
741 });
742 }
743
744 // TODO: Actually register with io_uring when integrated with Ring
745 // For now, we just track the registration locally
746
747 Ok(buffer_slots)
748 }
749
750 /// Unregister all buffer slots and return ownership of the buffers.
751 ///
752 /// This operation will fail if any buffer slot is currently in use.
753 ///
754 /// # Returns
755 ///
756 /// Returns the original owned buffers on successful unregistration.
757 ///
758 /// # Errors
759 ///
760 /// Returns `SaferRingError::BufferInFlight` if any buffer slot is in use.
761 pub fn unregister_buffer_slots(&mut self) -> Result<Vec<OwnedBuffer>> {
762 if !self.buffer_slots_in_use.is_empty() {
763 return Err(SaferRingError::BufferInFlight);
764 }
765
766 let buffers = self.registered_buffer_slots.drain(..).flatten().collect();
767
768 // TODO: Actually unregister with io_uring when integrated with Ring
769
770 Ok(buffers)
771 }
772
773 /// Get the number of buffer slots registered.
774 pub fn buffer_slot_count(&self) -> usize {
775 self.registered_buffer_slots
776 .iter()
777 .filter(|slot| slot.is_some())
778 .count()
779 }
780
781 /// Get the number of buffer slots currently in use.
782 pub fn buffer_slots_in_use_count(&self) -> usize {
783 self.buffer_slots_in_use.len()
784 }
785
786 // Internal methods for tracking usage of fixed files and buffer slots
787
788 /// Mark a fixed file as in use by an operation.
789 #[allow(dead_code)]
790 pub(crate) fn mark_fixed_file_in_use(&mut self, fixed_file: &FixedFile) -> Result<()> {
791 let index = fixed_file.index as usize;
792
793 if index >= self.fixed_files.len() || self.fixed_files[index].is_none() {
794 return Err(SaferRingError::NotRegistered);
795 }
796
797 self.fixed_files_in_use.insert(fixed_file.index);
798 Ok(())
799 }
800
801 /// Mark a fixed file as no longer in use.
802 #[allow(dead_code)]
803 pub(crate) fn mark_fixed_file_not_in_use(&mut self, fixed_file: &FixedFile) {
804 self.fixed_files_in_use.remove(&fixed_file.index);
805 }
806
807 /// Mark a buffer slot as in use by an operation.
808 #[allow(dead_code)]
809 pub(crate) fn mark_buffer_slot_in_use(
810 &mut self,
811 buffer_slot: &RegisteredBufferSlot,
812 ) -> Result<()> {
813 let index = buffer_slot.index as usize;
814
815 if index >= self.registered_buffer_slots.len()
816 || self.registered_buffer_slots[index].is_none()
817 {
818 return Err(SaferRingError::NotRegistered);
819 }
820
821 self.buffer_slots_in_use.insert(buffer_slot.index);
822 Ok(())
823 }
824
825 /// Mark a buffer slot as no longer in use.
826 #[allow(dead_code)]
827 pub(crate) fn mark_buffer_slot_not_in_use(&mut self, buffer_slot: &RegisteredBufferSlot) {
828 self.buffer_slots_in_use.remove(&buffer_slot.index);
829 }
830
831 /// Get the raw file descriptor for a fixed file.
832 #[allow(dead_code)]
833 pub(crate) fn get_fixed_file_fd(&self, fixed_file: &FixedFile) -> Result<RawFd> {
834 let index = fixed_file.index as usize;
835
836 if index >= self.fixed_files.len() {
837 return Err(SaferRingError::NotRegistered);
838 }
839
840 match &self.fixed_files[index] {
841 Some(fd) if *fd == fixed_file.fd => Ok(*fd),
842 _ => Err(SaferRingError::NotRegistered),
843 }
844 }
845
846 /// Get a reference to a buffer in a registered slot.
847 #[allow(dead_code)]
848 pub(crate) fn get_buffer_slot(
849 &self,
850 buffer_slot: &RegisteredBufferSlot,
851 ) -> Result<&OwnedBuffer> {
852 let index = buffer_slot.index as usize;
853
854 if index >= self.registered_buffer_slots.len() {
855 return Err(SaferRingError::NotRegistered);
856 }
857
858 match &self.registered_buffer_slots[index] {
859 Some(buffer) if buffer.size() == buffer_slot.size => Ok(buffer),
860 _ => Err(SaferRingError::NotRegistered),
861 }
862 }
863}
864
865impl<'ring> Drop for Registry<'ring> {
866 /// Ensure no resources are in use when the registry is dropped.
867 ///
868 /// This prevents resource leaks and ensures that all operations have
869 /// completed before the registry is destroyed.
870 fn drop(&mut self) {
871 let total_in_use = self.fds_in_use.len()
872 + self.buffers_in_use.len()
873 + self.fixed_files_in_use.len()
874 + self.buffer_slots_in_use.len();
875
876 if total_in_use > 0 {
877 panic!(
878 "Registry dropped with resources in use: {} fds, {} buffers, {} fixed_files, {} buffer_slots",
879 self.fds_in_use.len(),
880 self.buffers_in_use.len(),
881 self.fixed_files_in_use.len(),
882 self.buffer_slots_in_use.len()
883 );
884 }
885 }
886}
887
888impl RegisteredFd {
889 /// Get the index of this registered file descriptor.
890 ///
891 /// The index is used internally by io_uring to reference the registered
892 /// file descriptor efficiently.
893 pub fn index(&self) -> u32 {
894 self.index
895 }
896
897 /// Get the raw file descriptor value.
898 ///
899 /// This returns the original file descriptor that was registered.
900 /// Note that this should generally not be used directly - use the
901 /// registered handle in operations instead.
902 pub fn raw_fd(&self) -> RawFd {
903 self.fd
904 }
905}
906
907impl RegisteredBuffer {
908 /// Get the index of this registered buffer.
909 ///
910 /// The index is used internally by io_uring to reference the registered
911 /// buffer efficiently.
912 pub fn index(&self) -> u32 {
913 self.index
914 }
915
916 /// Get the size of this registered buffer.
917 ///
918 /// Returns the size in bytes of the registered buffer.
919 pub fn size(&self) -> usize {
920 self.size
921 }
922}
923
924/// Fixed file descriptor with optimized kernel access.
925///
926/// Fixed files are pre-registered with the kernel and accessed by index
927/// instead of file descriptor, providing better performance for frequently
928/// used files.
929#[derive(Debug, Clone, PartialEq, Eq)]
930pub struct FixedFile {
931 /// Index in the fixed file table
932 index: u32,
933 /// Original file descriptor for validation
934 fd: RawFd,
935}
936
937/// Registered buffer slot with optimized kernel access.
938///
939/// Registered buffers are pre-allocated and registered with the kernel,
940/// allowing operations to reference buffers by index instead of pointer
941/// for better performance.
942#[derive(Debug)]
943pub struct RegisteredBufferSlot {
944 /// Index in the registered buffer table
945 index: u32,
946 /// Size of the buffer slot
947 size: usize,
948 /// Whether this slot is currently in use
949 in_use: bool,
950}
951
952impl FixedFile {
953 /// Get the index of this fixed file.
954 pub fn index(&self) -> u32 {
955 self.index
956 }
957
958 /// Get the original file descriptor.
959 pub fn raw_fd(&self) -> RawFd {
960 self.fd
961 }
962}
963
964impl RegisteredBufferSlot {
965 /// Get the index of this buffer slot.
966 pub fn index(&self) -> u32 {
967 self.index
968 }
969
970 /// Get the size of this buffer slot.
971 pub fn size(&self) -> usize {
972 self.size
973 }
974
975 /// Check if this slot is currently in use.
976 pub fn is_in_use(&self) -> bool {
977 self.in_use
978 }
979}
980
981// RegisteredFd and RegisteredBuffer are now Send/Sync for better ergonomics
982// The safety is maintained through the registry's lifetime management