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(&registered_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(&registered_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(&registered_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(&registered_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