wasmer_sys_utils/memory/fd_memory/
fd_mmap.rs1use std::{
5 io::{self, Read, Write},
6 ptr, slice,
7};
8
9#[derive(Debug)]
12pub struct FdMmap {
13 ptr: usize,
18 len: usize,
19 fd: FdGuard,
21}
22
23#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub struct FdGuard(pub i32);
25
26impl Default for FdGuard {
27 fn default() -> Self {
28 Self(-1)
29 }
30}
31
32impl Clone for FdGuard {
33 fn clone(&self) -> Self {
34 unsafe { Self(libc::dup(self.0)) }
35 }
36}
37
38impl Drop for FdGuard {
39 fn drop(&mut self) {
40 if self.0 >= 0 {
41 unsafe {
42 libc::close(self.0);
43 }
44 self.0 = -1;
45 }
46 }
47}
48
49impl FdMmap {
50 pub fn new() -> Self {
52 let empty = Vec::<u8>::new();
56 Self {
57 ptr: empty.as_ptr() as usize,
58 len: 0,
59 fd: FdGuard::default(),
60 }
61 }
62
63 pub fn accessible_reserved(
67 accessible_size: usize,
68 mapping_size: usize,
69 ) -> Result<Self, String> {
70 let page_size = region::page::size();
71 assert!(accessible_size <= mapping_size);
72 assert_eq!(mapping_size & (page_size - 1), 0);
73 assert_eq!(accessible_size & (page_size - 1), 0);
74
75 if mapping_size == 0 {
78 return Ok(Self::new());
79 }
80
81 let fd = unsafe {
83 let file = libc::tmpfile();
84 if file.is_null() {
85 return Err(format!(
86 "failed to create temporary file - {}",
87 io::Error::last_os_error()
88 ));
89 }
90 FdGuard(libc::fileno(file))
91 };
92
93 unsafe {
95 if libc::ftruncate(fd.0, mapping_size as libc::off_t) < 0 {
96 return Err("could not truncate tmpfile".to_string());
97 }
98 }
99
100 Ok(if accessible_size == mapping_size {
101 let ptr = unsafe {
103 libc::mmap(
104 ptr::null_mut(),
105 mapping_size,
106 libc::PROT_READ | libc::PROT_WRITE,
107 libc::MAP_FILE | libc::MAP_SHARED,
108 fd.0,
109 0,
110 )
111 };
112 if ptr as isize == -1_isize {
113 return Err(io::Error::last_os_error().to_string());
114 }
115
116 Self {
117 ptr: ptr as usize,
118 len: mapping_size,
119 fd,
120 }
121 } else {
122 let ptr = unsafe {
124 libc::mmap(
125 ptr::null_mut(),
126 mapping_size,
127 libc::PROT_NONE,
128 libc::MAP_FILE | libc::MAP_SHARED,
129 fd.0,
130 0,
131 )
132 };
133 if ptr as isize == -1_isize {
134 return Err(io::Error::last_os_error().to_string());
135 }
136
137 let mut result = Self {
138 ptr: ptr as usize,
139 len: mapping_size,
140 fd,
141 };
142
143 if accessible_size != 0 {
144 result.make_accessible(0, accessible_size)?;
146 }
147
148 result
149 })
150 }
151
152 pub fn make_accessible(&mut self, start: usize, len: usize) -> Result<(), String> {
156 let page_size = region::page::size();
157 assert_eq!(start & (page_size - 1), 0);
158 assert_eq!(len & (page_size - 1), 0);
159 assert!(len < self.len);
160 assert!(start < self.len - len);
161
162 let ptr = self.ptr as *const u8;
164 unsafe { region::protect(ptr.add(start), len, region::Protection::READ_WRITE) }
165 .map_err(|e| e.to_string())
166 }
167
168 pub fn as_slice(&self) -> &[u8] {
170 unsafe { slice::from_raw_parts(self.ptr as *const u8, self.len) }
171 }
172
173 pub fn as_mut_slice(&mut self) -> &mut [u8] {
175 unsafe { slice::from_raw_parts_mut(self.ptr as *mut u8, self.len) }
176 }
177
178 pub fn as_mut_ptr(&mut self) -> *mut u8 {
185 self.ptr as *mut u8
186 }
187
188 pub fn len(&self) -> usize {
190 self.len
191 }
192
193 pub fn duplicate(&mut self, hint_used: Option<usize>) -> Result<Self, String> {
200 use std::os::unix::prelude::FromRawFd;
203 if self.len == 0 {
204 return Ok(Self::new());
205 }
206
207 unsafe {
209 libc::fsync(self.fd.0);
210 }
211
212 let fd = unsafe {
214 let file = libc::tmpfile();
215 if file.is_null() {
216 return Err(format!(
217 "failed to create temporary file - {}",
218 io::Error::last_os_error()
219 ));
220 }
221 FdGuard(libc::fileno(file))
222 };
223
224 unsafe {
226 if libc::ioctl(fd.0, 0x94, 9, self.fd.0) != 0
227 {
229 #[cfg(feature = "tracing")]
230 trace!("memory copy started");
231
232 let len = match hint_used {
234 Some(a) => a,
235 None => self.len,
236 };
237
238 let mut source = std::fs::File::from_raw_fd(self.fd.0);
241 let mut out = std::fs::File::from_raw_fd(fd.0);
242 copy_file_range(&mut source, 0, &mut out, 0, len)
243 .map_err(|err| format!("Could not copy memory: {err}"))?;
244
245 #[cfg(feature = "tracing")]
246 trace!("memory copy finished (size={})", len);
247 }
248 }
249
250 let flags = libc::MAP_FILE | libc::MAP_SHARED;
252
253 let ptr = unsafe {
255 libc::mmap(
256 ptr::null_mut(),
257 self.len,
258 libc::PROT_READ | libc::PROT_WRITE,
259 flags,
260 fd.0,
261 0,
262 )
263 };
264 if ptr as isize == -1_isize {
265 return Err(io::Error::last_os_error().to_string());
266 }
267
268 Ok(Self {
269 ptr: ptr as usize,
270 len: self.len,
271 fd,
272 })
273 }
274}
275
276impl Drop for FdMmap {
277 fn drop(&mut self) {
278 if self.len != 0 {
279 let r = unsafe { libc::munmap(self.ptr as *mut libc::c_void, self.len) };
280 assert_eq!(r, 0, "munmap failed: {}", io::Error::last_os_error());
281 }
282 }
283}
284
285#[cfg(target_family = "unix")]
290fn copy_file_range(
291 source: &mut std::fs::File,
292 source_offset: u64,
293 out: &mut std::fs::File,
294 out_offset: u64,
295 len: usize,
296) -> Result<(), std::io::Error> {
297 use std::io::{Seek, SeekFrom};
298
299 let source_original_pos = source.stream_position()?;
300 source.seek(SeekFrom::Start(source_offset))?;
301
302 let out_original_pos = out.stream_position()?;
305 out.seek(SeekFrom::Start(out_offset))?;
306
307 let mut buffer = vec![0u8; 4096];
312
313 let mut to_read = len;
314 while to_read > 0 {
315 let chunk_size = std::cmp::min(to_read, buffer.len());
316 let read = source.read(&mut buffer[0..chunk_size])?;
317 out.write_all(&buffer[0..read])?;
318 to_read -= read;
319 }
320
321 out.flush()?;
323
324 source.seek(SeekFrom::Start(source_original_pos))?;
326 out.flush()?;
327 out.sync_data()?;
328 out.seek(SeekFrom::Start(out_original_pos))?;
329
330 Ok(())
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 #[cfg(target_family = "unix")]
338 #[test]
339 fn test_copy_file_range() -> Result<(), std::io::Error> {
340 use std::{fs::OpenOptions, io::Seek};
344
345 let dir = std::env::temp_dir().join("wasmer/copy_file_range");
346 if dir.is_dir() {
347 std::fs::remove_dir_all(&dir).unwrap()
348 }
349 std::fs::create_dir_all(&dir).unwrap();
350
351 let pa = dir.join("a");
352 let pb = dir.join("b");
353
354 let data: Vec<u8> = (0..100).collect();
355 let mut a = OpenOptions::new()
356 .read(true)
357 .write(true)
358 .create_new(true)
359 .open(pa)
360 .unwrap();
361 a.write_all(&data).unwrap();
362
363 let datb: Vec<u8> = (100..200).collect();
364 let mut b = OpenOptions::new()
365 .read(true)
366 .write(true)
367 .create_new(true)
368 .open(pb)
369 .unwrap();
370 b.write_all(&datb).unwrap();
371
372 a.seek(io::SeekFrom::Start(30)).unwrap();
373 b.seek(io::SeekFrom::Start(99)).unwrap();
374 copy_file_range(&mut a, 10, &mut b, 40, 15).unwrap();
375
376 assert_eq!(a.stream_position().unwrap(), 30);
377 assert_eq!(b.stream_position().unwrap(), 99);
378
379 b.seek(io::SeekFrom::Start(0)).unwrap();
380 let mut out = Vec::new();
381 let len = b.read_to_end(&mut out).unwrap();
382 assert_eq!(len, 100);
383 assert_eq!(out[0..40], datb[0..40]);
384 assert_eq!(out[40..55], data[10..25]);
385 assert_eq!(out[55..100], datb[55..100]);
386
387 Ok(())
390 }
391}