Skip to content

Commit 4a4887f

Browse files
eryugeybergwolf
authored andcommitted
passthroughfs: add unit test for inode store
Signed-off-by: Eryu Guan <[email protected]>
1 parent 3c20438 commit 4a4887f

File tree

3 files changed

+142
-0
lines changed

3 files changed

+142
-0
lines changed

src/passthrough/file_handle.rs

+9
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,15 @@ pub struct FileHandle {
148148
handle: CFileHandle,
149149
}
150150

151+
impl Default for FileHandle {
152+
fn default() -> Self {
153+
Self {
154+
mnt_id: 0,
155+
handle: CFileHandle::new(0),
156+
}
157+
}
158+
}
159+
151160
extern "C" {
152161
fn name_to_handle_at(
153162
dirfd: libc::c_int,

src/passthrough/inode_store.rs

+131
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,134 @@ impl InodeStore {
6565
self.by_handle.get(handle)
6666
}
6767
}
68+
69+
#[cfg(test)]
70+
mod test {
71+
use super::super::*;
72+
use super::*;
73+
74+
use std::ffi::CStr;
75+
use std::os::unix::io::{AsRawFd, RawFd};
76+
use std::sync::atomic::Ordering;
77+
use vmm_sys_util::tempfile::TempFile;
78+
79+
impl PartialEq for InodeData {
80+
fn eq(&self, other: &Self) -> bool {
81+
if self.inode != other.inode
82+
|| self.altkey != other.altkey
83+
|| self.mode != other.mode
84+
|| self.refcount.load(Ordering::Relaxed) != other.refcount.load(Ordering::Relaxed)
85+
{
86+
return false;
87+
}
88+
89+
match (&self.file_or_handle, &other.file_or_handle) {
90+
(FileOrHandle::File(f1), FileOrHandle::File(f2)) => {
91+
f1.as_raw_fd() == f2.as_raw_fd()
92+
}
93+
(FileOrHandle::Handle(h1), FileOrHandle::Handle(h2)) => h1 == h2,
94+
_ => false,
95+
}
96+
}
97+
}
98+
99+
fn stat_fd(fd: RawFd) -> io::Result<libc::stat64> {
100+
let mut st = MaybeUninit::<libc::stat64>::zeroed();
101+
let null_path = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") };
102+
103+
// Safe because the kernel will only write data in `st` and we check the return value.
104+
let res = unsafe {
105+
libc::fstatat64(
106+
fd,
107+
null_path.as_ptr(),
108+
st.as_mut_ptr(),
109+
libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW,
110+
)
111+
};
112+
if res >= 0 {
113+
// Safe because the kernel guarantees that the struct is now fully initialized.
114+
Ok(unsafe { st.assume_init() })
115+
} else {
116+
Err(io::Error::last_os_error())
117+
}
118+
}
119+
120+
#[test]
121+
fn test_inode_store() {
122+
let mut m = InodeStore::default();
123+
let tmpfile1 = TempFile::new().unwrap();
124+
let tmpfile2 = TempFile::new().unwrap();
125+
126+
let inode1: Inode = 3;
127+
let inode2: Inode = 4;
128+
let inode_stat1 = InodeStat {
129+
stat: stat_fd(tmpfile1.as_file().as_raw_fd()).unwrap(),
130+
mnt_id: 0,
131+
};
132+
let inode_stat2 = InodeStat {
133+
stat: stat_fd(tmpfile2.as_file().as_raw_fd()).unwrap(),
134+
mnt_id: 0,
135+
};
136+
let ids1 = InodeAltKey::ids_from_stat(&inode_stat1);
137+
let ids2 = InodeAltKey::ids_from_stat(&inode_stat2);
138+
let file_or_handle1 = FileOrHandle::File(tmpfile1.into_file());
139+
let file_or_handle2 = FileOrHandle::File(tmpfile2.into_file());
140+
let data1 = InodeData::new(
141+
inode1,
142+
file_or_handle1,
143+
2,
144+
ids1,
145+
inode_stat1.get_stat().st_mode,
146+
);
147+
let data2 = InodeData::new(
148+
inode2,
149+
file_or_handle2,
150+
2,
151+
ids2,
152+
inode_stat2.get_stat().st_mode,
153+
);
154+
let data1 = Arc::new(data1);
155+
let data2 = Arc::new(data2);
156+
157+
m.insert(data1.clone());
158+
159+
// get not present key, expect none
160+
assert!(m.get(&1).is_none());
161+
162+
// get just inserted value by key, by id, by handle
163+
assert!(m.get_by_ids(&InodeAltKey::default()).is_none());
164+
assert!(m.get_by_handle(&FileHandle::default()).is_none());
165+
assert_eq!(m.get(&inode1).unwrap(), &data1);
166+
assert_eq!(m.get_by_ids(&ids1).unwrap(), &data1);
167+
168+
// insert another value, and check again
169+
m.insert(data2.clone());
170+
assert!(m.get(&1).is_none());
171+
assert!(m.get_by_ids(&InodeAltKey::default()).is_none());
172+
assert!(m.get_by_handle(&FileHandle::default()).is_none());
173+
assert_eq!(m.get(&inode1).unwrap(), &data1);
174+
assert_eq!(m.get_by_ids(&ids1).unwrap(), &data1);
175+
assert_eq!(m.get(&inode2).unwrap(), &data2);
176+
assert_eq!(m.get_by_ids(&ids2).unwrap(), &data2);
177+
178+
// remove non-present key
179+
assert!(m.remove(&1).is_none());
180+
181+
// remove present key, return its value
182+
assert_eq!(m.remove(&inode1).unwrap(), data1.clone());
183+
assert!(m.get(&inode1).is_none());
184+
assert!(m.get_by_ids(&ids1).is_none());
185+
assert_eq!(m.get(&inode2).unwrap(), &data2);
186+
assert_eq!(m.get_by_ids(&ids2).unwrap(), &data2);
187+
188+
// clear the map
189+
m.clear();
190+
assert!(m.get(&1).is_none());
191+
assert!(m.get_by_ids(&InodeAltKey::default()).is_none());
192+
assert!(m.get_by_handle(&FileHandle::default()).is_none());
193+
assert!(m.get(&inode1).is_none());
194+
assert!(m.get_by_ids(&ids1).is_none());
195+
assert!(m.get(&inode2).is_none());
196+
assert!(m.get_by_ids(&ids2).is_none());
197+
}
198+
}

src/passthrough/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ impl InodeAltKey {
8888
}
8989
}
9090

91+
#[derive(Debug)]
9192
enum FileOrHandle {
9293
File(File),
9394
Handle(Arc<FileHandle>),
@@ -127,6 +128,7 @@ impl AsRawFd for InodeFile<'_> {
127128
}
128129

129130
/// Represents an inode in `PassthroughFs`.
131+
#[derive(Debug)]
130132
pub struct InodeData {
131133
inode: Inode,
132134
// Most of these aren't actually files but ¯\_(ツ)_/¯.

0 commit comments

Comments
 (0)