nydus_utils/
filemap.rs

1// Copyright (C) 2022 Alibaba Cloud. All rights reserved.
2//
3// SPDX-License-Identifier: Apache-2.0
4
5use std::fs::File;
6use std::io::Result;
7use std::mem::size_of;
8use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
9
10/// Struct to manage memory range mapped from file objects.
11///
12/// It maps a region from a file into current process by using libc::mmap().
13/// Then it provides safe interfaces to access the memory mapped region.
14pub struct FileMapState {
15    base: *const u8,
16    end: *const u8,
17    size: usize,
18    fd: RawFd,
19}
20
21// Safe to Send/Sync because the underlying data structures are readonly
22unsafe 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    /// Memory map a region of the file object into current process.
53    ///
54    /// It takes ownership of the file object and will close it when the returned object is dropped.
55    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        // Safe because the mmap area should covered the range [start, end)
81        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    /// Get size of mapped region.
92    pub fn size(&self) -> usize {
93        self.size
94    }
95
96    /// Cast a subregion of the mapped area to an object reference.
97    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    /// Cast a subregion of the mapped area to an mutable object reference.
109    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    /// Get an immutable slice of 'T' at 'offset' with 'count' entries.
121    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    /// Get a mutable slice of 'T' at 'offset' with 'count' entries.
143    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    /// Check whether the range [offset, offset + size) is valid and return the start address.
165    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    /// Add `offset` to the base pointer.
177    ///
178    /// # Safety
179    /// The caller should ensure that `offset` is within range.
180    pub unsafe fn offset(&self, offset: usize) -> *const u8 {
181        self.base.wrapping_add(offset)
182    }
183
184    /// Sync mapped file data into disk.
185    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
193/// Duplicate a file object by `libc::dup()`.
194pub 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}