1use std::fs::File;
6use std::io::Result;
7use std::mem::size_of;
8use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
9
10pub struct FileMapState {
15 base: *const u8,
16 end: *const u8,
17 size: usize,
18 fd: RawFd,
19}
20
21unsafe impl Send for FileMapState {}
23unsafe impl Sync for FileMapState {}
24
25impl Default for FileMapState {
26 fn default() -> Self {
27 FileMapState {
28 fd: -1,
29 base: std::ptr::null(),
30 end: std::ptr::null(),
31 size: 0,
32 }
33 }
34}
35
36impl Drop for FileMapState {
37 fn drop(&mut self) {
38 if !self.base.is_null() {
39 unsafe { libc::munmap(self.base as *mut u8 as *mut libc::c_void, self.size) };
40 self.base = std::ptr::null();
41 self.end = std::ptr::null();
42 self.size = 0;
43 }
44 if self.fd >= 0 {
45 let _ = nix::unistd::close(self.fd);
46 self.fd = -1;
47 }
48 }
49}
50
51impl FileMapState {
52 pub fn new(file: File, offset: libc::off_t, size: usize, writable: bool) -> Result<Self> {
56 let prot = if writable {
57 libc::PROT_READ | libc::PROT_WRITE
58 } else {
59 libc::PROT_READ
60 };
61 let base = unsafe {
62 libc::mmap(
63 std::ptr::null_mut(),
64 size,
65 prot,
66 libc::MAP_NORESERVE | libc::MAP_SHARED,
67 file.as_raw_fd(),
68 offset,
69 )
70 } as *const u8;
71 if base as *mut core::ffi::c_void == libc::MAP_FAILED {
72 return Err(last_error!(
73 "failed to memory map file region into current process"
74 ));
75 } else if base.is_null() {
76 return Err(last_error!(
77 "failed to memory map file region into current process"
78 ));
79 }
80 let end = unsafe { base.add(size) };
82
83 Ok(Self {
84 fd: file.into_raw_fd(),
85 base,
86 end,
87 size,
88 })
89 }
90
91 pub fn size(&self) -> usize {
93 self.size
94 }
95
96 pub fn get_ref<T>(&self, offset: usize) -> Result<&T> {
98 let start = self.base.wrapping_add(offset);
99 let end = start.wrapping_add(size_of::<T>());
100
101 if start > end || start < self.base || end < self.base || end > self.end {
102 return Err(einval!("invalid mmap offset"));
103 }
104
105 Ok(unsafe { &*(start as *const T) })
106 }
107
108 pub fn get_mut<T>(&mut self, offset: usize) -> Result<&mut T> {
110 let start = self.base.wrapping_add(offset);
111 let end = start.wrapping_add(size_of::<T>());
112
113 if start > end || start < self.base || end < self.base || end > self.end {
114 return Err(einval!("invalid mmap offset"));
115 }
116
117 Ok(unsafe { &mut *(start as *const T as *mut T) })
118 }
119
120 pub fn get_slice<T>(&self, offset: usize, count: usize) -> Result<&[T]> {
122 let start = self.base.wrapping_add(offset);
123 if count.checked_mul(size_of::<T>()).is_none() {
124 bail_einval!("count 0x{count:x} to validate_slice() is too big");
125 }
126 let size = count * size_of::<T>();
127 if size.checked_add(start as usize).is_none() {
128 bail_einval!(
129 "invalid parameter to validate_slice(), offset 0x{offset:x}, count 0x{count:x}"
130 );
131 }
132 let end = start.wrapping_add(size);
133 if start > end || start < self.base || end < self.base || end > self.end {
134 bail_einval!(
135 "invalid range in validate_slice, base 0x{:p}, start 0x{start:p}, end 0x{end:p}",
136 self.base
137 );
138 }
139 Ok(unsafe { std::slice::from_raw_parts(start as *const T, count) })
140 }
141
142 pub fn get_slice_mut<T>(&mut self, offset: usize, count: usize) -> Result<&mut [T]> {
144 let start = self.base.wrapping_add(offset);
145 if count.checked_mul(size_of::<T>()).is_none() {
146 bail_einval!("count 0x{count:x} to validate_slice() is too big");
147 }
148 let size = count * size_of::<T>();
149 if size.checked_add(start as usize).is_none() {
150 bail_einval!(
151 "invalid parameter to validate_slice(), offset 0x{offset:x}, count 0x{count:x}"
152 );
153 }
154 let end = start.wrapping_add(size);
155 if start > end || start < self.base || end < self.base || end > self.end {
156 bail_einval!(
157 "invalid range in validate_slice, base 0x{:p}, start 0x{start:p}, end 0x{end:p}",
158 self.base
159 );
160 }
161 Ok(unsafe { std::slice::from_raw_parts_mut(start as *mut T, count) })
162 }
163
164 pub fn validate_range(&self, offset: usize, size: usize) -> Result<*const u8> {
166 let start = self.base.wrapping_add(offset);
167 let end = start.wrapping_add(size);
168
169 if start > end || start < self.base || end < self.base || end > self.end {
170 return Err(einval!("invalid range"));
171 }
172
173 Ok(start)
174 }
175
176 pub unsafe fn offset(&self, offset: usize) -> *const u8 {
181 self.base.wrapping_add(offset)
182 }
183
184 pub fn sync_data(&self) -> Result<()> {
186 let file = unsafe { File::from_raw_fd(self.fd) };
187 let result = file.sync_data();
188 std::mem::forget(file);
189 result
190 }
191}
192
193pub fn clone_file(fd: RawFd) -> Result<File> {
195 unsafe {
196 let fd = libc::dup(fd);
197 if fd < 0 {
198 return Err(last_error!("failed to dup bootstrap file fd"));
199 }
200 Ok(File::from_raw_fd(fd))
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use vmm_sys_util::tempfile::TempFile;
207
208 use super::*;
209 use std::fs::OpenOptions;
210 use std::path::PathBuf;
211
212 #[test]
213 fn create_file_map_object() {
214 let root_dir = &std::env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR");
215 let path = PathBuf::from(root_dir).join("../tests/texture/bootstrap/rafs-v5.boot");
216 let file = OpenOptions::new()
217 .read(true)
218 .write(false)
219 .open(path)
220 .unwrap();
221 let map = FileMapState::new(file, 0, 4096, false).unwrap();
222
223 let magic = map.get_ref::<u32>(0).unwrap();
224 assert_eq!(u32::from_le(*magic), 0x52414653);
225
226 map.get_ref::<u32>(4096).unwrap_err();
227 let _ = map.get_ref::<u32>(4092).unwrap();
228 let _ = map.get_ref::<u32>(0).unwrap();
229 map.validate_range(4096, 1).unwrap_err();
230 let _ = map.validate_range(4095, 1).unwrap();
231 let _ = map.validate_range(0, 1).unwrap();
232 drop(map);
233 }
234
235 #[test]
236 fn create_default_file_map_object() {
237 let map = FileMapState::default();
238 drop(map);
239 }
240
241 #[test]
242 fn test_file_map_error() {
243 let temp = TempFile::new().unwrap();
244 let file = OpenOptions::new()
245 .read(true)
246 .write(false)
247 .open(temp.as_path())
248 .unwrap();
249 assert!(FileMapState::new(file, 0, 4096, true).is_err());
250
251 let temp = TempFile::new().unwrap();
252 let file = OpenOptions::new()
253 .read(true)
254 .write(false)
255 .open(temp.as_path())
256 .unwrap();
257 let mut map = FileMapState::new(file, 0, 4096, false).unwrap();
258 assert!(map.get_slice::<usize>(0, usize::MAX).is_err());
259 assert!(map.get_slice::<usize>(usize::MAX, 1).is_err());
260 assert!(map.get_slice::<usize>(4096, 4096).is_err());
261 assert!(map.get_slice::<usize>(0, 128).is_ok());
262
263 assert!(map.get_slice_mut::<usize>(0, usize::MAX).is_err());
264 assert!(map.get_slice_mut::<usize>(usize::MAX, 1).is_err());
265 assert!(map.get_slice_mut::<usize>(4096, 4096).is_err());
266 assert!(map.get_slice_mut::<usize>(0, 128).is_ok());
267 }
268}