Skip to main content

rustpython_host_env/
mmap.rs

1#![allow(
2    clippy::not_unsafe_ptr_arg_deref,
3    reason = "These helpers are thin wrappers around raw Windows mapping APIs."
4)]
5
6use std::io;
7
8#[cfg(unix)]
9pub use libc::{
10    MADV_DONTNEED, MADV_NORMAL, MADV_RANDOM, MADV_SEQUENTIAL, MADV_WILLNEED, PROT_EXEC, PROT_READ,
11    PROT_WRITE,
12};
13
14#[cfg(unix)]
15bitflags::bitflags! {
16    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
17    pub struct MapFlags: libc::c_int {
18        const MAP_SHARED = libc::MAP_SHARED;
19        const MAP_PRIVATE = libc::MAP_PRIVATE;
20        const MAP_ANON = libc::MAP_ANON;
21        const MAP_ANONYMOUS = libc::MAP_ANONYMOUS;
22
23        #[cfg(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"))]
24        const MAP_DENYWRITE = libc::MAP_DENYWRITE;
25        #[cfg(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"))]
26        const MAP_EXECUTABLE = libc::MAP_EXECUTABLE;
27        #[cfg(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"))]
28        const MAP_POPULATE = libc::MAP_POPULATE;
29
30        #[cfg(any(target_os = "linux", target_os = "openbsd", target_os = "netbsd"))]
31        const MAP_STACK = libc::MAP_STACK;
32
33        #[cfg(target_vendor = "apple")]
34        const MAP_NORESERVE = libc::MAP_NORESERVE;
35        #[cfg(target_vendor = "apple")]
36        const MAP_NOEXTEND = libc::MAP_NOEXTEND;
37        #[cfg(target_vendor = "apple")]
38        const MAP_HASSEMAPHORE = libc::MAP_HASSEMAPHORE;
39        #[cfg(target_vendor = "apple")]
40        const MAP_NOCACHE = libc::MAP_NOCACHE;
41        #[cfg(target_vendor = "apple")]
42        const MAP_JIT = libc::MAP_JIT;
43        // libc does not name these `sys/mman.h` flags.
44        #[cfg(target_vendor = "apple")]
45        const MAP_RESILIENT_CODESIGN = 0x2000;
46        #[cfg(target_vendor = "apple")]
47        const MAP_RESILIENT_MEDIA = 0x4000;
48        #[cfg(target_vendor = "apple")]
49        const MAP_32BIT = 0x8000;
50        #[cfg(target_vendor = "apple")]
51        const MAP_TRANSLATED_ALLOW_EXECUTE = 0x20000;
52        #[cfg(target_vendor = "apple")]
53        const MAP_UNIX03 = 0x40000;
54        #[cfg(target_vendor = "apple")]
55        const MAP_TPRO = 0x80000;
56    }
57}
58
59#[cfg(unix)]
60macro_rules! map_flag_consts {
61    ($($name:ident),+ $(,)?) => {
62        $(pub const $name: libc::c_int = MapFlags::$name.bits();)+
63    };
64}
65
66#[cfg(unix)]
67map_flag_consts! {
68    MAP_SHARED, MAP_PRIVATE, MAP_ANON, MAP_ANONYMOUS,
69}
70
71#[cfg(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"))]
72map_flag_consts! {
73    MAP_DENYWRITE, MAP_EXECUTABLE, MAP_POPULATE,
74}
75
76#[cfg(any(target_os = "linux", target_os = "openbsd", target_os = "netbsd"))]
77map_flag_consts! {
78    MAP_STACK,
79}
80
81#[cfg(target_vendor = "apple")]
82map_flag_consts! {
83    MAP_HASSEMAPHORE, MAP_JIT, MAP_NOCACHE, MAP_NOEXTEND, MAP_NORESERVE,
84    MAP_RESILIENT_CODESIGN, MAP_RESILIENT_MEDIA, MAP_32BIT,
85    MAP_TRANSLATED_ALLOW_EXECUTE, MAP_UNIX03, MAP_TPRO,
86}
87
88#[cfg(target_os = "macos")]
89pub use libc::{MADV_FREE_REUSABLE, MADV_FREE_REUSE};
90
91#[cfg(any(
92    target_os = "android",
93    target_os = "dragonfly",
94    target_os = "fuchsia",
95    target_os = "freebsd",
96    target_os = "linux",
97    target_os = "netbsd",
98    target_os = "openbsd",
99    target_vendor = "apple"
100))]
101pub use libc::MADV_FREE;
102
103#[cfg(target_os = "linux")]
104pub use libc::{
105    MADV_DODUMP, MADV_DOFORK, MADV_DONTDUMP, MADV_DONTFORK, MADV_HUGEPAGE, MADV_HWPOISON,
106    MADV_MERGEABLE, MADV_NOHUGEPAGE, MADV_REMOVE, MADV_UNMERGEABLE,
107};
108
109#[cfg(any(
110    target_os = "android",
111    all(
112        target_os = "linux",
113        any(
114            target_arch = "aarch64",
115            target_arch = "arm",
116            target_arch = "powerpc",
117            target_arch = "powerpc64",
118            target_arch = "s390x",
119            target_arch = "x86",
120            target_arch = "x86_64",
121            target_arch = "sparc64"
122        )
123    )
124))]
125pub use libc::MADV_SOFT_OFFLINE;
126
127#[cfg(target_os = "freebsd")]
128pub use libc::{MADV_AUTOSYNC, MADV_CORE, MADV_NOCORE, MADV_NOSYNC, MADV_PROTECT};
129
130pub use libc::EOVERFLOW;
131
132#[cfg(windows)]
133use crate::windows::{CheckWin32Bool, HandleToOwned};
134#[cfg(unix)]
135use crate::{crt_fd, fileutils, posix};
136use memmap2::{Mmap, MmapMut, MmapOptions};
137#[cfg(windows)]
138use std::os::windows::io::{AsRawHandle, IntoRawHandle};
139#[cfg(windows)]
140use windows_sys::Win32::{
141    Foundation::{
142        CloseHandle, DUPLICATE_SAME_ACCESS, DuplicateHandle, GetLastError, HANDLE,
143        INVALID_HANDLE_VALUE,
144    },
145    Storage::FileSystem::{FILE_BEGIN, GetFileSize, SetEndOfFile, SetFilePointerEx},
146    System::{
147        Memory::{
148            CreateFileMappingW, FILE_MAP_COPY, FILE_MAP_READ, FILE_MAP_WRITE, FlushViewOfFile,
149            MEMORY_MAPPED_VIEW_ADDRESS, MapViewOfFile, PAGE_READONLY, PAGE_READWRITE,
150            PAGE_WRITECOPY, UnmapViewOfFile,
151        },
152        Threading::GetCurrentProcess,
153    },
154};
155
156#[cfg(windows)]
157pub type Handle = HANDLE;
158#[cfg(windows)]
159pub const INVALID_HANDLE: Handle = INVALID_HANDLE_VALUE;
160
161#[derive(Clone, Copy, Debug, Eq, PartialEq)]
162pub enum AccessMode {
163    Default = 0,
164    Read = 1,
165    Write = 2,
166    Copy = 3,
167}
168
169#[cfg(windows)]
170#[derive(Debug)]
171pub struct NamedMmap {
172    map_handle: Handle,
173    view_ptr: *mut u8,
174    len: usize,
175}
176
177#[derive(Debug)]
178pub enum MappedFile {
179    Read(Mmap),
180    Write(MmapMut),
181}
182
183impl MappedFile {
184    pub fn as_slice(&self) -> &[u8] {
185        match self {
186            Self::Read(mmap) => &mmap[..],
187            Self::Write(mmap) => &mmap[..],
188        }
189    }
190
191    pub fn as_mut_slice(&mut self) -> &mut [u8] {
192        match self {
193            Self::Read(_) => panic!("mmap can't modify a readonly memory map."),
194            Self::Write(mmap) => &mut mmap[..],
195        }
196    }
197
198    pub fn as_ptr(&self) -> *const u8 {
199        match self {
200            Self::Read(mmap) => mmap.as_ptr(),
201            Self::Write(mmap) => mmap.as_ptr(),
202        }
203    }
204
205    pub fn flush_range(&self, offset: usize, size: usize) -> io::Result<()> {
206        match self {
207            Self::Read(_) => Ok(()),
208            Self::Write(mmap) => mmap.flush_range(offset, size),
209        }
210    }
211
212    #[cfg(all(unix, not(target_os = "redox")))]
213    pub fn madvise_range(&self, start: usize, length: usize, advice: i32) -> io::Result<()> {
214        let ptr = unsafe { self.as_ptr().add(start) };
215        posix::madvise(ptr as usize, length, advice)
216    }
217}
218
219#[cfg(windows)]
220unsafe impl Send for NamedMmap {}
221#[cfg(windows)]
222unsafe impl Sync for NamedMmap {}
223
224#[cfg(windows)]
225impl NamedMmap {
226    pub fn as_slice(&self) -> &[u8] {
227        unsafe { core::slice::from_raw_parts(self.view_ptr, self.len) }
228    }
229
230    pub fn as_mut_slice(&mut self) -> &mut [u8] {
231        unsafe { core::slice::from_raw_parts_mut(self.view_ptr, self.len) }
232    }
233
234    pub fn ptr_at(&self, offset: usize) -> *const core::ffi::c_void {
235        unsafe { self.view_ptr.add(offset) as *const _ }
236    }
237
238    pub fn flush_range(&self, offset: usize, size: usize) -> io::Result<()> {
239        flush_view(self.ptr_at(offset), size)
240    }
241}
242
243#[cfg(windows)]
244impl Drop for NamedMmap {
245    fn drop(&mut self) {
246        unsafe {
247            if !self.view_ptr.is_null() {
248                UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS {
249                    Value: self.view_ptr as *mut _,
250                });
251            }
252            if !self.map_handle.is_null() {
253                CloseHandle(self.map_handle);
254            }
255        }
256    }
257}
258
259#[cfg(windows)]
260pub fn duplicate_handle(handle: Handle) -> io::Result<Handle> {
261    let mut new_handle: Handle = INVALID_HANDLE;
262    unsafe {
263        DuplicateHandle(
264            GetCurrentProcess(),
265            handle,
266            GetCurrentProcess(),
267            &mut new_handle,
268            0,
269            0,
270            DUPLICATE_SAME_ACCESS,
271        )
272    }
273    .check_win32_bool()?;
274    Ok(new_handle)
275}
276
277#[cfg(windows)]
278pub fn get_file_len(handle: Handle) -> io::Result<i64> {
279    let mut high: u32 = 0;
280    let low = unsafe { GetFileSize(handle, &mut high) };
281    if low == u32::MAX {
282        let err = io::Error::last_os_error();
283        if err.raw_os_error() != Some(0) {
284            return Err(err);
285        }
286    }
287    Ok(((high as i64) << 32) | (low as i64))
288}
289
290#[cfg(unix)]
291pub fn file_len(fd: crt_fd::Borrowed<'_>) -> io::Result<i64> {
292    #[allow(clippy::useless_conversion, reason = "needed for 32-bit platforms")]
293    Ok(fileutils::fstat(fd)?.st_size.into())
294}
295
296#[cfg(unix)]
297pub fn prepare_file_mapping(fd: crt_fd::Borrowed<'_>) {
298    #[cfg(target_os = "macos")]
299    {
300        let _ = posix::full_fsync(fd.into());
301    }
302    #[cfg(not(target_os = "macos"))]
303    {
304        let _ = fd;
305    }
306}
307
308#[cfg(windows)]
309pub fn is_invalid_handle_value(handle: isize) -> bool {
310    handle == INVALID_HANDLE as isize
311}
312
313#[cfg(windows)]
314pub fn extend_file(handle: Handle, size: i64) -> io::Result<()> {
315    unsafe { SetFilePointerEx(handle, size, core::ptr::null_mut(), FILE_BEGIN) }
316        .check_win32_bool()?;
317    unsafe { SetEndOfFile(handle) }.check_win32_bool()
318}
319
320#[cfg(unix)]
321pub fn close_descriptor(fd: i32) {
322    if fd >= 0 {
323        let _ = crt_fd::close(unsafe { crt_fd::Owned::from_raw(fd) });
324    }
325}
326
327#[cfg(windows)]
328pub fn close_handle(handle: Handle) {
329    unsafe { CloseHandle(handle) };
330}
331
332#[cfg(windows)]
333pub fn flush_view(ptr: *const core::ffi::c_void, size: usize) -> io::Result<()> {
334    unsafe { FlushViewOfFile(ptr, size) }.check_win32_bool()
335}
336
337#[cfg(windows)]
338pub fn last_error() -> u32 {
339    unsafe { GetLastError() }
340}
341
342#[cfg(windows)]
343pub fn create_named_mapping(
344    file_handle: Handle,
345    tag: &str,
346    access: AccessMode,
347    offset: i64,
348    map_size: usize,
349) -> io::Result<NamedMmap> {
350    let (fl_protect, desired_access) = match access {
351        AccessMode::Default | AccessMode::Write => (PAGE_READWRITE, FILE_MAP_WRITE),
352        AccessMode::Read => (PAGE_READONLY, FILE_MAP_READ),
353        AccessMode::Copy => (PAGE_WRITECOPY, FILE_MAP_COPY),
354    };
355
356    let total_size = (offset as u64)
357        .checked_add(map_size as u64)
358        .ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))?;
359    let size_hi = (total_size >> 32) as u32;
360    let size_lo = total_size as u32;
361    let tag_wide: Vec<u16> = tag.encode_utf16().chain(core::iter::once(0)).collect();
362
363    let map_handle = unsafe {
364        CreateFileMappingW(
365            file_handle,
366            core::ptr::null(),
367            fl_protect,
368            size_hi,
369            size_lo,
370            tag_wide.as_ptr(),
371        )
372    }
373    .into_owned()
374    .ok_or_else(io::Error::last_os_error)?;
375
376    let off_hi = (offset as u64 >> 32) as u32;
377    let off_lo = offset as u32;
378    let view = unsafe {
379        MapViewOfFile(
380            map_handle.as_raw_handle() as Handle,
381            desired_access,
382            off_hi,
383            off_lo,
384            map_size,
385        )
386    };
387    if view.Value.is_null() {
388        // `map_handle` is closed automatically when dropped on this error path.
389        return Err(io::Error::last_os_error());
390    }
391
392    Ok(NamedMmap {
393        map_handle: map_handle.into_raw_handle() as Handle,
394        view_ptr: view.Value as *mut u8,
395        len: map_size,
396    })
397}
398
399#[cfg(unix)]
400pub fn map_anon(size: usize) -> io::Result<MappedFile> {
401    let mut mmap_opt = MmapOptions::new();
402    mmap_opt.len(size).map_anon().map(MappedFile::Write)
403}
404
405#[cfg(windows)]
406pub fn map_anon(size: usize) -> io::Result<MappedFile> {
407    let mut mmap_opt = MmapOptions::new();
408    mmap_opt.len(size).map_anon().map(MappedFile::Write)
409}
410
411#[cfg(unix)]
412pub fn map_file(
413    fd: crt_fd::Borrowed<'_>,
414    offset: i64,
415    size: usize,
416    access: AccessMode,
417) -> io::Result<(crt_fd::Owned, MappedFile)> {
418    let new_fd: crt_fd::Owned = posix::dup_noninheritable(fd.into())?.into();
419    let mut mmap_opt = MmapOptions::new();
420    let mmap_opt = mmap_opt.offset(offset as u64).len(size);
421
422    let mapped = match access {
423        AccessMode::Default | AccessMode::Write => {
424            unsafe { mmap_opt.map_mut(&new_fd) }.map(MappedFile::Write)?
425        }
426        AccessMode::Read => unsafe { mmap_opt.map(&new_fd) }.map(MappedFile::Read)?,
427        AccessMode::Copy => unsafe { mmap_opt.map_copy(&new_fd) }.map(MappedFile::Write)?,
428    };
429
430    Ok((new_fd, mapped))
431}
432
433#[cfg(all(unix, not(target_os = "redox")))]
434pub fn validate_advice(advice: i32) -> bool {
435    match advice {
436        libc::MADV_NORMAL
437        | libc::MADV_RANDOM
438        | libc::MADV_SEQUENTIAL
439        | libc::MADV_WILLNEED
440        | libc::MADV_DONTNEED => true,
441        #[cfg(any(
442            target_os = "linux",
443            target_os = "macos",
444            target_os = "ios",
445            target_os = "freebsd"
446        ))]
447        libc::MADV_FREE => true,
448        #[cfg(target_os = "linux")]
449        libc::MADV_DONTFORK
450        | libc::MADV_DOFORK
451        | libc::MADV_MERGEABLE
452        | libc::MADV_UNMERGEABLE
453        | libc::MADV_HUGEPAGE
454        | libc::MADV_NOHUGEPAGE
455        | libc::MADV_REMOVE
456        | libc::MADV_DONTDUMP
457        | libc::MADV_DODUMP
458        | libc::MADV_HWPOISON => true,
459        #[cfg(target_os = "freebsd")]
460        libc::MADV_NOSYNC
461        | libc::MADV_AUTOSYNC
462        | libc::MADV_NOCORE
463        | libc::MADV_CORE
464        | libc::MADV_PROTECT => true,
465        _ => false,
466    }
467}
468
469#[cfg(windows)]
470pub fn map_handle(
471    handle: Handle,
472    offset: i64,
473    size: usize,
474    access: AccessMode,
475) -> io::Result<MappedFile> {
476    use std::{
477        fs::File,
478        os::windows::io::{FromRawHandle, RawHandle},
479    };
480
481    let file = unsafe { File::from_raw_handle(handle as RawHandle) };
482    let mut mmap_opt = MmapOptions::new();
483    let mmap_opt = mmap_opt.offset(offset as u64).len(size);
484
485    let result = match access {
486        AccessMode::Default | AccessMode::Write => {
487            unsafe { mmap_opt.map_mut(&file) }.map(MappedFile::Write)
488        }
489        AccessMode::Read => unsafe { mmap_opt.map(&file) }.map(MappedFile::Read),
490        AccessMode::Copy => unsafe { mmap_opt.map_copy(&file) }.map(MappedFile::Write),
491    };
492
493    core::mem::forget(file);
494    result
495}