fuse_backend_rs/passthrough/
inode_store.rs

1// Use of this source code is governed by a BSD-style license that can be
2// found in the LICENSE-BSD-3-Clause file.
3
4use std::collections::BTreeMap;
5use std::sync::Arc;
6
7use super::file_handle::FileHandle;
8use super::statx::StatExt;
9use super::{Inode, InodeData, InodeHandle};
10
11#[derive(Clone, Copy, Default, PartialOrd, Ord, PartialEq, Eq, Debug)]
12/// Identify an inode in `PassthroughFs` by `InodeId`.
13pub struct InodeId {
14    pub ino: libc::ino64_t,
15    pub dev: libc::dev_t,
16    pub mnt: u64,
17}
18
19impl InodeId {
20    #[inline]
21    pub(super) fn from_stat(st: &StatExt) -> Self {
22        InodeId {
23            ino: st.st.st_ino,
24            dev: st.st.st_dev,
25            mnt: st.mnt_id,
26        }
27    }
28}
29
30#[derive(Default)]
31pub struct InodeStore {
32    data: BTreeMap<Inode, Arc<InodeData>>,
33    by_id: BTreeMap<InodeId, Inode>,
34    by_handle: BTreeMap<Arc<FileHandle>, Inode>,
35}
36
37impl InodeStore {
38    /// Insert an inode into the manager
39    ///
40    /// The caller needs to ensure that no inode with the same key exists, otherwise the old inode
41    /// will get lost.
42    pub fn insert(&mut self, data: Arc<InodeData>) {
43        self.by_id.insert(data.id, data.inode);
44        if let InodeHandle::Handle(handle) = &data.handle {
45            self.by_handle
46                .insert(handle.file_handle().clone(), data.inode);
47        }
48        self.data.insert(data.inode, data);
49    }
50
51    /// Remove an inode from the manager, keeping the (key, ino) mapping if `remove_data_only` is true.
52    pub fn remove(&mut self, inode: &Inode, remove_data_only: bool) -> Option<Arc<InodeData>> {
53        let data = self.data.remove(inode);
54        if remove_data_only {
55            // Don't remove by_id and by_handle, we need use it to store inode
56            // record the mapping of inodes using these two structures to ensure
57            // that the same files always use the same inode
58            return data;
59        }
60
61        if let Some(data) = data.as_ref() {
62            if let InodeHandle::Handle(handle) = &data.handle {
63                self.by_handle.remove(handle.file_handle());
64            }
65            self.by_id.remove(&data.id);
66        }
67        data
68    }
69
70    pub fn clear(&mut self) {
71        self.data.clear();
72        self.by_handle.clear();
73        self.by_id.clear();
74    }
75
76    pub fn get(&self, inode: &Inode) -> Option<&Arc<InodeData>> {
77        self.data.get(inode)
78    }
79
80    pub fn get_by_id(&self, id: &InodeId) -> Option<&Arc<InodeData>> {
81        let inode = self.inode_by_id(id)?;
82        self.get(inode)
83    }
84
85    pub fn get_by_handle(&self, handle: &FileHandle) -> Option<&Arc<InodeData>> {
86        let inode = self.inode_by_handle(handle)?;
87        self.get(inode)
88    }
89
90    pub fn inode_by_id(&self, id: &InodeId) -> Option<&Inode> {
91        self.by_id.get(id)
92    }
93
94    pub fn inode_by_handle(&self, handle: &FileHandle) -> Option<&Inode> {
95        self.by_handle.get(handle)
96    }
97}
98
99#[cfg(test)]
100mod test {
101    use super::super::*;
102    use super::*;
103
104    use std::ffi::CStr;
105    use std::mem::MaybeUninit;
106    use std::os::unix::io::AsRawFd;
107    use std::sync::atomic::Ordering;
108    use vmm_sys_util::tempfile::TempFile;
109
110    impl PartialEq for InodeData {
111        fn eq(&self, other: &Self) -> bool {
112            if self.inode != other.inode
113                || self.id != other.id
114                || self.mode != other.mode
115                || self.refcount.load(Ordering::Relaxed) != other.refcount.load(Ordering::Relaxed)
116            {
117                return false;
118            }
119
120            match (&self.handle, &other.handle) {
121                (InodeHandle::File(f1), InodeHandle::File(f2)) => f1.as_raw_fd() == f2.as_raw_fd(),
122                (InodeHandle::Handle(h1), InodeHandle::Handle(h2)) => {
123                    h1.file_handle() == h2.file_handle()
124                }
125                _ => false,
126            }
127        }
128    }
129
130    fn stat_fd(fd: &impl AsRawFd) -> io::Result<libc::stat64> {
131        let mut st = MaybeUninit::<libc::stat64>::zeroed();
132        let null_path = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") };
133
134        // Safe because the kernel will only write data in `st` and we check the return value.
135        let res = unsafe {
136            libc::fstatat64(
137                fd.as_raw_fd(),
138                null_path.as_ptr(),
139                st.as_mut_ptr(),
140                libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW,
141            )
142        };
143        if res >= 0 {
144            // Safe because the kernel guarantees that the struct is now fully initialized.
145            Ok(unsafe { st.assume_init() })
146        } else {
147            Err(io::Error::last_os_error())
148        }
149    }
150
151    #[test]
152    fn test_inode_store() {
153        let mut m = InodeStore::default();
154        let tmpfile1 = TempFile::new().unwrap();
155        let tmpfile2 = TempFile::new().unwrap();
156
157        let inode1: Inode = 3;
158        let inode2: Inode = 4;
159        let inode_stat1 = StatExt {
160            st: stat_fd(tmpfile1.as_file()).unwrap(),
161            mnt_id: 0,
162        };
163        let inode_stat2 = StatExt {
164            st: stat_fd(tmpfile2.as_file()).unwrap(),
165            mnt_id: 0,
166        };
167        let id1 = InodeId::from_stat(&inode_stat1);
168        let id2 = InodeId::from_stat(&inode_stat2);
169        let file_or_handle1 = InodeHandle::File(tmpfile1.into_file());
170        let file_or_handle2 = InodeHandle::File(tmpfile2.into_file());
171        let data1 = InodeData::new(inode1, file_or_handle1, 2, id1, inode_stat1.st.st_mode);
172        let data2 = InodeData::new(inode2, file_or_handle2, 2, id2, inode_stat2.st.st_mode);
173        let data1 = Arc::new(data1);
174        let data2 = Arc::new(data2);
175
176        m.insert(data1.clone());
177
178        // get not present key, expect none
179        assert!(m.get(&1).is_none());
180
181        // get just inserted value by key, by id, by handle
182        assert!(m.get_by_id(&InodeId::default()).is_none());
183        assert!(m.get_by_handle(&FileHandle::default()).is_none());
184        assert_eq!(m.get(&inode1).unwrap(), &data1);
185        assert_eq!(m.get_by_id(&id1).unwrap(), &data1);
186
187        // insert another value, and check again
188        m.insert(data2.clone());
189        assert!(m.get(&1).is_none());
190        assert!(m.get_by_id(&InodeId::default()).is_none());
191        assert!(m.get_by_handle(&FileHandle::default()).is_none());
192        assert_eq!(m.get(&inode1).unwrap(), &data1);
193        assert_eq!(m.get_by_id(&id1).unwrap(), &data1);
194        assert_eq!(m.get(&inode2).unwrap(), &data2);
195        assert_eq!(m.get_by_id(&id2).unwrap(), &data2);
196
197        // remove non-present key
198        assert!(m.remove(&1, false).is_none());
199
200        // remove present key, return its value
201        assert_eq!(m.remove(&inode1, false).unwrap(), data1.clone());
202        assert!(m.get(&inode1).is_none());
203        assert!(m.get_by_id(&id1).is_none());
204        assert_eq!(m.get(&inode2).unwrap(), &data2);
205        assert_eq!(m.get_by_id(&id2).unwrap(), &data2);
206
207        // clear the map
208        m.clear();
209        assert!(m.get(&1).is_none());
210        assert!(m.get_by_id(&InodeId::default()).is_none());
211        assert!(m.get_by_handle(&FileHandle::default()).is_none());
212        assert!(m.get(&inode1).is_none());
213        assert!(m.get_by_id(&id1).is_none());
214        assert!(m.get(&inode2).is_none());
215        assert!(m.get_by_id(&id2).is_none());
216    }
217}