Coverage Report

Created: 2026-08-05 07:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/memmap2-0.9.11/src/unix.rs
Line
Count
Source
1
use std::fs::File;
2
use std::io;
3
use std::mem::ManuallyDrop;
4
use std::os::unix::io::{FromRawFd, RawFd};
5
use std::sync::atomic::{AtomicUsize, Ordering};
6
7
#[cfg(any(
8
    all(target_os = "linux", not(target_arch = "mips")),
9
    target_os = "freebsd",
10
    target_os = "android"
11
))]
12
const MAP_STACK: libc::c_int = libc::MAP_STACK;
13
14
#[cfg(not(any(
15
    all(target_os = "linux", not(target_arch = "mips")),
16
    target_os = "freebsd",
17
    target_os = "android"
18
)))]
19
const MAP_STACK: libc::c_int = 0;
20
21
#[cfg(any(target_os = "linux", target_os = "android"))]
22
const MAP_POPULATE: libc::c_int = libc::MAP_POPULATE;
23
24
#[cfg(not(any(target_os = "linux", target_os = "android")))]
25
const MAP_POPULATE: libc::c_int = 0;
26
27
#[cfg(any(target_os = "linux", target_os = "android"))]
28
const MAP_HUGETLB: libc::c_int = libc::MAP_HUGETLB;
29
30
#[cfg(target_os = "linux")]
31
const MAP_HUGE_MASK: libc::c_int = libc::MAP_HUGE_MASK;
32
33
#[cfg(any(target_os = "linux", target_os = "android"))]
34
const MAP_HUGE_SHIFT: libc::c_int = libc::MAP_HUGE_SHIFT;
35
36
#[cfg(not(any(target_os = "linux", target_os = "android")))]
37
const MAP_HUGETLB: libc::c_int = 0;
38
39
#[cfg(not(target_os = "linux"))]
40
const MAP_HUGE_MASK: libc::c_int = 0;
41
42
#[cfg(not(any(target_os = "linux", target_os = "android")))]
43
const MAP_HUGE_SHIFT: libc::c_int = 0;
44
45
#[cfg(any(
46
    target_os = "linux",
47
    target_os = "android",
48
    target_vendor = "apple",
49
    target_os = "netbsd",
50
    target_os = "solaris",
51
    target_os = "illumos",
52
))]
53
const MAP_NORESERVE: libc::c_int = libc::MAP_NORESERVE;
54
55
#[cfg(not(any(
56
    target_os = "linux",
57
    target_os = "android",
58
    target_vendor = "apple",
59
    target_os = "netbsd",
60
    target_os = "solaris",
61
    target_os = "illumos",
62
)))]
63
const MAP_NORESERVE: libc::c_int = 0;
64
65
#[cfg(any(
66
    target_os = "android",
67
    all(target_os = "linux", not(target_env = "musl"))
68
))]
69
use libc::{mmap64 as mmap, off64_t as off_t};
70
71
#[cfg(not(any(
72
    target_os = "android",
73
    all(target_os = "linux", not(target_env = "musl"))
74
)))]
75
use libc::{mmap, off_t};
76
77
pub struct MmapInner {
78
    ptr: *mut libc::c_void,
79
    len: usize,
80
}
81
82
impl MmapInner {
83
    /// Creates a new `MmapInner`.
84
    ///
85
    /// This is a thin wrapper around the `mmap` system call.
86
1.57k
    fn new(
87
1.57k
        len: usize,
88
1.57k
        prot: libc::c_int,
89
1.57k
        flags: libc::c_int,
90
1.57k
        file: RawFd,
91
1.57k
        offset: u64,
92
1.57k
    ) -> io::Result<MmapInner> {
93
1.57k
        let alignment = offset % page_size() as u64;
94
1.57k
        let aligned_offset = offset - alignment;
95
96
1.57k
        let (map_len, map_offset) = Self::adjust_mmap_params(len, alignment as usize)?;
97
98
        // SAFETY: creating a new memory map with a nullptr as address hint is always sound:
99
        // it does not modify any existing mapping or memory contents.
100
1.57k
        let ptr = unsafe {
101
1.57k
            mmap(
102
1.57k
                std::ptr::null_mut(),
103
1.57k
                map_len as libc::size_t,
104
1.57k
                prot,
105
1.57k
                flags,
106
1.57k
                file,
107
1.57k
                aligned_offset as off_t,
108
            )
109
        };
110
111
1.57k
        if ptr == libc::MAP_FAILED {
112
0
            Err(io::Error::last_os_error())
113
        } else {
114
            // SAFETY: The ptr and len have been checked,
115
            // and the offset has been calculated as required.
116
1.57k
            Ok(unsafe { Self::from_raw_parts(ptr, len, map_offset) })
117
        }
118
1.57k
    }
119
120
1.57k
    fn adjust_mmap_params(len: usize, alignment: usize) -> io::Result<(usize, usize)> {
121
        // Rust's slice cannot be larger than isize::MAX.
122
        // See https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html
123
        //
124
        // This is not a problem on 64-bit targets, but on 32-bit one
125
        // having a file or an anonymous mapping larger than 2GB is quite normal
126
        // and we have to prevent it.
127
        //
128
        // The code below is essentially the same as in Rust's std:
129
        // https://github.com/rust-lang/rust/blob/db78ab70a88a0a5e89031d7ee4eccec835dcdbde/library/alloc/src/raw_vec.rs#L495
130
1.57k
        if std::mem::size_of::<usize>() < 8 && len > isize::MAX as usize {
131
0
            return Err(io::Error::new(
132
0
                io::ErrorKind::InvalidData,
133
0
                "memory map length overflows isize",
134
0
            ));
135
1.57k
        }
136
137
1.57k
        let map_len = len + alignment;
138
1.57k
        let map_offset = alignment;
139
140
        // `libc::mmap` does not support zero-size mappings. POSIX defines:
141
        //
142
        // https://pubs.opengroup.org/onlinepubs/9699919799/functions/mmap.html
143
        // > If `len` is zero, `mmap()` shall fail and no mapping shall be established.
144
        //
145
        // So if we would create such a mapping, crate a one-byte mapping instead:
146
1.57k
        let map_len = map_len.max(1);
147
148
        // Note that in that case `MmapInner::len` is still set to zero,
149
        // and `Mmap` will still dereferences to an empty slice.
150
        //
151
        // If this mapping is backed by an empty file, we create a mapping larger than the file.
152
        // This is unusual but well-defined. On the same man page, POSIX further defines:
153
        //
154
        // > The `mmap()` function can be used to map a region of memory that is larger
155
        // > than the current size of the object.
156
        //
157
        // (The object here is the file.)
158
        //
159
        // > Memory access within the mapping but beyond the current end of the underlying
160
        // > objects may result in SIGBUS signals being sent to the process. The reason for this
161
        // > is that the size of the object can be manipulated by other processes and can change
162
        // > at any moment. The implementation should tell the application that a memory reference
163
        // > is outside the object where this can be detected; otherwise, written data may be lost
164
        // > and read data may not reflect actual data in the object.
165
        //
166
        // Because `MmapInner::len` is not incremented, this increment of `aligned_len`
167
        // will not allow accesses past the end of the file and will not cause SIGBUS.
168
        //
169
        // (SIGBUS is still possible by mapping a non-empty file and then truncating it
170
        // to a shorter size, but that is unrelated to this handling of empty files.)
171
1.57k
        Ok((map_len, map_offset))
172
1.57k
    }
173
174
    /// Get the current memory mapping as a `(ptr, map_len, offset)` tuple.
175
    ///
176
    /// Note that `map_len` is the length of the memory mapping itself and
177
    /// _not_ the one that would be passed to `from_raw_parts`.
178
1.57k
    fn as_mmap_params(&self) -> (*mut libc::c_void, usize, usize) {
179
1.57k
        let offset = self.ptr as usize % page_size();
180
1.57k
        let len = self.len + offset;
181
182
        // There are two possible memory layouts we could have, depending on
183
        // the length and offset passed when constructing this instance:
184
        //
185
        // 1. The "normal" memory layout looks like this:
186
        //
187
        //         |<------------------>|<---------------------->|
188
        //     mmap ptr    offset      ptr     public slice
189
        //
190
        //    That is, we have
191
        //    - The start of the page-aligned memory mapping returned by mmap,
192
        //      followed by,
193
        //    - Some number of bytes that are memory mapped but ignored since
194
        //      they are before the byte offset requested by the user, followed
195
        //      by,
196
        //    - The actual memory mapped slice requested by the user.
197
        //
198
        //    This maps cleanly to a (ptr, len, offset) tuple.
199
        //
200
        // 2. Then, we have the case where the user requested a zero-length
201
        //    memory mapping. mmap(2) does not support zero-length mappings so
202
        //    this crate works around that by actually making a mapping of
203
        //    length one. This means that we have
204
        //    - A length zero slice, followed by,
205
        //    - A single memory mapped byte
206
        //
207
        //    Note that this only happens if the offset within the page is also
208
        //    zero. Otherwise, we have a memory map of offset bytes and not a
209
        //    zero-length memory map.
210
        //
211
        //    This doesn't fit cleanly into a (ptr, len, offset) tuple. Instead,
212
        //    we fudge it slightly: a zero-length memory map turns into a
213
        //    mapping of length one and can't be told apart outside of this
214
        //    method without knowing the original length.
215
1.57k
        if len == 0 {
216
0
            (self.ptr, 1, 0)
217
        } else {
218
1.57k
            let offset = self.ptr as usize % page_size();
219
            // SAFETY: MmapInner guarantees that rounding `self.ptr` down to a page boundary gives the real address of the memory map.
220
            // This means that it points into the same allocation as `self.ptr`.
221
1.57k
            let ptr = unsafe { self.ptr.sub(offset) };
222
1.57k
            (ptr, len, offset)
223
        }
224
1.57k
    }
225
226
    /// Construct this `MmapInner` from its raw components
227
    ///
228
    /// # Safety
229
    ///
230
    /// - `ptr` must point to the start of memory mapping that can be freed
231
    ///   using `munmap(2)` (i.e. returned by `mmap(2)` or `mremap(2)`)
232
    /// - The memory mapping at `ptr` must have a length of `len + offset`.
233
    /// - If `len + offset == 0` then the memory mapping must be of length 1.
234
    /// - `offset` must be less than the current page size.
235
1.57k
    unsafe fn from_raw_parts(ptr: *mut libc::c_void, len: usize, offset: usize) -> Self {
236
1.57k
        debug_assert_eq!(ptr as usize % page_size(), 0, "ptr not page-aligned");
237
1.57k
        debug_assert!(offset < page_size(), "offset larger than page size");
238
239
1.57k
        Self {
240
1.57k
            ptr: unsafe { ptr.add(offset) },
241
1.57k
            len,
242
1.57k
        }
243
1.57k
    }
244
245
0
    pub fn map(
246
0
        len: usize,
247
0
        file: RawFd,
248
0
        offset: u64,
249
0
        populate: bool,
250
0
        no_reserve: bool,
251
0
    ) -> io::Result<MmapInner> {
252
0
        let populate = if populate { MAP_POPULATE } else { 0 };
253
0
        let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 };
254
0
        MmapInner::new(
255
0
            len,
256
            libc::PROT_READ,
257
0
            libc::MAP_SHARED | populate | no_reserve,
258
0
            file,
259
0
            offset,
260
        )
261
0
    }
262
263
0
    pub fn map_exec(
264
0
        len: usize,
265
0
        file: RawFd,
266
0
        offset: u64,
267
0
        populate: bool,
268
0
        no_reserve: bool,
269
0
    ) -> io::Result<MmapInner> {
270
0
        let populate = if populate { MAP_POPULATE } else { 0 };
271
0
        let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 };
272
0
        MmapInner::new(
273
0
            len,
274
0
            libc::PROT_READ | libc::PROT_EXEC,
275
0
            libc::MAP_SHARED | populate | no_reserve,
276
0
            file,
277
0
            offset,
278
        )
279
0
    }
280
281
0
    pub fn map_mut(
282
0
        len: usize,
283
0
        file: RawFd,
284
0
        offset: u64,
285
0
        populate: bool,
286
0
        no_reserve: bool,
287
0
    ) -> io::Result<MmapInner> {
288
0
        let populate = if populate { MAP_POPULATE } else { 0 };
289
0
        let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 };
290
0
        MmapInner::new(
291
0
            len,
292
0
            libc::PROT_READ | libc::PROT_WRITE,
293
0
            libc::MAP_SHARED | populate | no_reserve,
294
0
            file,
295
0
            offset,
296
        )
297
0
    }
298
299
0
    pub fn map_copy(
300
0
        len: usize,
301
0
        file: RawFd,
302
0
        offset: u64,
303
0
        populate: bool,
304
0
        no_reserve: bool,
305
0
    ) -> io::Result<MmapInner> {
306
0
        let populate = if populate { MAP_POPULATE } else { 0 };
307
0
        let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 };
308
0
        MmapInner::new(
309
0
            len,
310
0
            libc::PROT_READ | libc::PROT_WRITE,
311
0
            libc::MAP_PRIVATE | populate | no_reserve,
312
0
            file,
313
0
            offset,
314
        )
315
0
    }
316
317
0
    pub fn map_copy_read_only(
318
0
        len: usize,
319
0
        file: RawFd,
320
0
        offset: u64,
321
0
        populate: bool,
322
0
        no_reserve: bool,
323
0
    ) -> io::Result<MmapInner> {
324
0
        let populate = if populate { MAP_POPULATE } else { 0 };
325
0
        let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 };
326
0
        MmapInner::new(
327
0
            len,
328
            libc::PROT_READ,
329
0
            libc::MAP_PRIVATE | populate | no_reserve,
330
0
            file,
331
0
            offset,
332
        )
333
0
    }
334
335
    /// Open an anonymous memory map.
336
1.57k
    pub fn map_anon(
337
1.57k
        len: usize,
338
1.57k
        stack: bool,
339
1.57k
        populate: bool,
340
1.57k
        huge: Option<u8>,
341
1.57k
        no_reserve: bool,
342
1.57k
    ) -> io::Result<MmapInner> {
343
1.57k
        let stack = if stack { MAP_STACK } else { 0 };
344
1.57k
        let populate = if populate { MAP_POPULATE } else { 0 };
345
1.57k
        let hugetlb = if huge.is_some() { MAP_HUGETLB } else { 0 };
346
1.57k
        let hugetlb_size = huge.map_or(0, |mask| {
347
0
            (u64::from(mask) & (MAP_HUGE_MASK as u64)) << MAP_HUGE_SHIFT
348
0
        }) as i32;
349
1.57k
        let no_reserve = if no_reserve { MAP_NORESERVE } else { 0 };
350
1.57k
        MmapInner::new(
351
1.57k
            len,
352
1.57k
            libc::PROT_READ | libc::PROT_WRITE,
353
1.57k
            libc::MAP_PRIVATE
354
1.57k
                | libc::MAP_ANON
355
1.57k
                | stack
356
1.57k
                | populate
357
1.57k
                | hugetlb
358
1.57k
                | hugetlb_size
359
1.57k
                | no_reserve,
360
            -1,
361
            0,
362
        )
363
1.57k
    }
364
365
0
    pub fn flush(&self, offset: usize, len: usize) -> io::Result<()> {
366
0
        if offset > self.len || len > self.len - offset {
367
0
            return Err(io::ErrorKind::InvalidInput.into());
368
0
        }
369
0
        let alignment = (self.ptr as usize + offset) % page_size();
370
0
        let offset = offset as isize - alignment as isize;
371
0
        let len = len + alignment;
372
0
        let result =
373
            // SAFETY: We've checked that offset and len fall within the mapped region.
374
0
            unsafe { libc::msync(self.ptr.offset(offset), len as libc::size_t, libc::MS_SYNC) };
375
0
        if result == 0 {
376
0
            Ok(())
377
        } else {
378
0
            Err(io::Error::last_os_error())
379
        }
380
0
    }
381
382
0
    pub fn flush_async(&self, offset: usize, len: usize) -> io::Result<()> {
383
0
        if offset > self.len || len > self.len - offset {
384
0
            return Err(io::ErrorKind::InvalidInput.into());
385
0
        }
386
0
        let alignment = (self.ptr as usize + offset) % page_size();
387
0
        let offset = offset as isize - alignment as isize;
388
0
        let len = len + alignment;
389
0
        let result =
390
            // SAFETY: We've checked that offset and len fall within the mapped region.
391
0
            unsafe { libc::msync(self.ptr.offset(offset), len as libc::size_t, libc::MS_ASYNC) };
392
0
        if result == 0 {
393
0
            Ok(())
394
        } else {
395
0
            Err(io::Error::last_os_error())
396
        }
397
0
    }
398
399
1.57k
    fn mprotect(&mut self, prot: libc::c_int) -> io::Result<()> {
400
1.57k
        let alignment = self.ptr as usize % page_size();
401
        // SAFETY: rounding self.ptr down to the previous page boundary gives the pointer of the actual memory map.
402
1.57k
        let ptr = unsafe { self.ptr.sub(alignment) };
403
1.57k
        let len = self.len + alignment;
404
1.57k
        let len = len.max(1);
405
406
        // SAFETY: the contract of MmapInner guarantees ptr and len are valid.
407
1.57k
        if unsafe { libc::mprotect(ptr, len, prot) } == 0 {
408
1.57k
            Ok(())
409
        } else {
410
0
            Err(io::Error::last_os_error())
411
        }
412
1.57k
    }
413
414
1.57k
    pub fn make_read_only(&mut self) -> io::Result<()> {
415
1.57k
        self.mprotect(libc::PROT_READ)
416
1.57k
    }
417
418
0
    pub fn make_exec(&mut self) -> io::Result<()> {
419
0
        self.mprotect(libc::PROT_READ | libc::PROT_EXEC)
420
0
    }
421
422
0
    pub fn make_mut(&mut self) -> io::Result<()> {
423
0
        self.mprotect(libc::PROT_READ | libc::PROT_WRITE)
424
0
    }
425
426
    #[inline]
427
20.7k
    pub fn ptr(&self) -> *const u8 {
428
20.7k
        self.ptr as *const u8
429
20.7k
    }
430
431
    #[inline]
432
1.57k
    pub fn mut_ptr(&mut self) -> *mut u8 {
433
1.57k
        self.ptr.cast()
434
1.57k
    }
435
436
    #[inline]
437
22.2k
    pub fn len(&self) -> usize {
438
22.2k
        self.len
439
22.2k
    }
440
441
    /// Perform an `madvise()`.
442
    ///
443
    /// # Safety
444
    ///
445
    /// Some `advise` values can be unsound depending on the situation.
446
    /// It is up to the caller to only perform sound madvise() calls on the memory range.
447
0
    pub unsafe fn advise(&self, advice: libc::c_int, offset: usize, len: usize) -> io::Result<()> {
448
0
        if offset > self.len || len > self.len {
449
0
            return Err(std::io::ErrorKind::InvalidInput.into());
450
0
        }
451
0
        let alignment = (self.ptr as usize + offset) % page_size();
452
0
        let offset = offset as isize - alignment as isize;
453
0
        let len = len + alignment;
454
455
        // SAFETY: We've checked that offset is within the mapped region.
456
0
        let ptr = unsafe { self.ptr.offset(offset) };
457
458
        // The AIX signature of 'madvise()' differs from the POSIX
459
        // specification, which expects 'void *' as the type of the
460
        // 'addr' argument, whereas AIX uses 'caddr_t' (i.e., 'char *').
461
        #[cfg(target_os = "aix")]
462
        let ptr = self.ptr.offset(offset).cast();
463
464
        // SAFETY: ptr and len are valid. The burden of giving a safe `advice` value is on the caller.
465
0
        if unsafe { libc::madvise(ptr, len, advice) } != 0 {
466
0
            Err(io::Error::last_os_error())
467
        } else {
468
0
            Ok(())
469
        }
470
0
    }
471
472
    #[cfg(target_os = "linux")]
473
0
    pub fn remap(&mut self, new_len: usize, options: crate::RemapOptions) -> io::Result<()> {
474
0
        let (old_ptr, old_len, offset) = self.as_mmap_params();
475
0
        let (map_len, offset) = Self::adjust_mmap_params(new_len, offset)?;
476
477
        // SAFETY: we hold a mutable reference to self, so we can adjust the location and size of the mapping.
478
0
        let new_ptr = unsafe { libc::mremap(old_ptr, old_len, map_len, options.into_flags()) };
479
480
0
        if new_ptr == libc::MAP_FAILED {
481
0
            Err(io::Error::last_os_error())
482
        } else {
483
            // SAFETY: The pointer and length passed to `from_raw_parts` have just been obtained from a real map, so they must be valid.
484
0
            let new_map = unsafe { Self::from_raw_parts(new_ptr, new_len, offset) };
485
            // We explicitly don't drop self since the pointer within is no longer valid.
486
            // Instead, swap the new map into `self` and forget the old one.
487
0
            let old_map = std::mem::replace(self, new_map);
488
0
            std::mem::forget(old_map);
489
0
            Ok(())
490
        }
491
0
    }
492
493
0
    pub fn lock(&self) -> io::Result<()> {
494
        unsafe {
495
0
            if libc::mlock(self.ptr, self.len) != 0 {
496
0
                Err(io::Error::last_os_error())
497
            } else {
498
0
                Ok(())
499
            }
500
        }
501
0
    }
502
503
0
    pub fn unlock(&self) -> io::Result<()> {
504
        unsafe {
505
0
            if libc::munlock(self.ptr, self.len) != 0 {
506
0
                Err(io::Error::last_os_error())
507
            } else {
508
0
                Ok(())
509
            }
510
        }
511
0
    }
512
}
513
514
impl Drop for MmapInner {
515
1.57k
    fn drop(&mut self) {
516
1.57k
        let (ptr, len, _) = self.as_mmap_params();
517
518
        // Any errors during unmapping/closing are ignored as the only way
519
        // to report them would be through panicking which is highly discouraged
520
        // in Drop impls, c.f. https://github.com/rust-lang/lang-team/issues/97
521
1.57k
        unsafe { libc::munmap(ptr, len as libc::size_t) };
522
1.57k
    }
523
}
524
525
unsafe impl Sync for MmapInner {}
526
unsafe impl Send for MmapInner {}
527
528
9.45k
fn page_size() -> usize {
529
    static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
530
531
9.45k
    match PAGE_SIZE.load(Ordering::Relaxed) {
532
        0 => {
533
1
            let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize };
534
535
1
            PAGE_SIZE.store(page_size, Ordering::Relaxed);
536
537
1
            page_size
538
        }
539
9.44k
        page_size => page_size,
540
    }
541
9.45k
}
542
543
0
pub fn file_len(file: RawFd) -> io::Result<u64> {
544
    // SAFETY: We must not close the passed-in fd by dropping the File we create,
545
    // we ensure this by immediately wrapping it in a ManuallyDrop.
546
    unsafe {
547
0
        let file = ManuallyDrop::new(File::from_raw_fd(file));
548
0
        Ok(file.metadata()?.len())
549
    }
550
0
}