1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
// Axel '0vercl0k' Souchet - July 18 2023
//! This implements logic that allows to memory map a file on both
//! Unix and Windows (cf `memory_map_file` / `unmap_memory_mapped_file`).
use std::fmt::{self, Debug};
use std::fs::File;
use std::io::{self, Cursor, Read, Seek};
use std::path::Path;
pub trait Reader: Read + Seek {}
impl<T> Reader for T where T: Read + Seek {}
/// A memory mapped file reader is basically a slice of bytes over the memory
/// mapping and a cursor to be able to access the region.
pub struct MappedFileReader<'map> {
mapped_file: &'map [u8],
cursor: Cursor<&'map [u8]>,
}
impl Debug for MappedFileReader<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MappedFileReader").finish()
}
}
impl MappedFileReader<'_> {
/// Create a new [`MappedFileReader`] from a path using a memory map.
pub fn new(path: impl AsRef<Path>) -> io::Result<Self> {
// Open the file..
let file = File::open(path)?;
// ..and memory map it using the underlying OS-provided APIs.
let mapped_file = memory_map_file(&file)?;
Ok(Self {
mapped_file,
cursor: Cursor::new(mapped_file),
})
}
}
impl Read for MappedFileReader<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.cursor.read(buf)
}
}
impl Seek for MappedFileReader<'_> {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
self.cursor.seek(pos)
}
}
/// Drop the [`MappedFileReader`]. In the case we memory mapped the file, we
/// need to drop the mapping using OS-provided APIs.
impl Drop for MappedFileReader<'_> {
fn drop(&mut self) {
unmap_memory_mapped_file(self.mapped_file).expect("failed to unmap");
}
}
cfg_select! {
windows => {
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
// Memory mapping on Windows using `CreateFileMappingA` / `MapViewOfFile`.
mod windows {
use std::fs::File;
use std::os::windows::prelude::AsRawHandle;
use std::os::windows::raw::HANDLE;
use std::{io, ptr, slice};
const PAGE_READONLY: DWORD = 2;
const FILE_MAP_READ: DWORD = 4;
type DWORD = u32;
type BOOL = u32;
type SIZE_T = usize;
type LPCSTR = *mut u8;
type LPVOID = *const u8;
unsafe extern "system" {
/// Creates or opens a named or unnamed file mapping object for a
/// specified file.
///
/// <https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createfilemappinga>
fn CreateFileMappingA(
h: HANDLE,
file_mapping_attrs: *const u8,
protect: DWORD,
max_size_high: DWORD,
max_size_low: DWORD,
name: LPCSTR,
) -> HANDLE;
/// Maps a view of a file mapping into the address space of a calling
/// process.
///
/// <https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile>
fn MapViewOfFile(
file_mapping_object: HANDLE,
desired_access: DWORD,
file_offset_high: DWORD,
file_offset_low: DWORD,
number_of_bytes_to_map: SIZE_T,
) -> LPVOID;
/// Closes an open object handle.
///
/// <https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle>
fn CloseHandle(h: HANDLE) -> BOOL;
/// Unmaps a mapped view of a file from the calling process's address
/// space.
///
/// <https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-unmapviewoffile>
fn UnmapViewOfFile(base_address: LPVOID) -> BOOL;
}
/// Memory map a file into memory.
pub fn memory_map_file<'map>(file: &File) -> Result<&'map [u8], io::Error> {
// Grab the underlying HANDLE.
let file_handle = file.as_raw_handle();
// Create the mapping.
let mapping_handle = unsafe {
CreateFileMappingA(
file_handle,
ptr::null_mut(),
PAGE_READONLY,
0,
0,
ptr::null_mut(),
)
};
// If the mapping is NULL, it failed so let's bail.
if mapping_handle.is_null() {
return Err(io::Error::last_os_error());
}
// Grab the size of the underlying file, this will be the size of the
// view.
let size = file.metadata()?.len().try_into().unwrap();
// Map the view in the address space.
let base = unsafe { MapViewOfFile(mapping_handle, FILE_MAP_READ, 0, 0, size) };
// If the base address is NULL, it failed so let's bail.
if base.is_null() {
// Don't forget to close the HANDLE we created for the mapping.
unsafe {
CloseHandle(mapping_handle);
}
return Err(io::Error::last_os_error());
}
// Now we materialized a view in the address space, we can get rid of
// the mapping handle.
unsafe {
CloseHandle(mapping_handle);
}
// Make sure the size is not bigger than what [`slice::from_raw_parts`] wants.
assert!(size <= isize::MAX.try_into().unwrap(), "slice is too large");
// Create the slice over the mapping.
// SAFETY: This is safe because:
// - It is a byte slice, so we don't need to care about the alignment.
// - The base is not NULL as we've verified that it is the case above.
// - The underlying is owned by the type and the lifetime.
// - We asked the OS to map `size` bytes, so we have a guarantee that there's
// `size` consecutive bytes.
// - We never give a mutable reference to this slice, so it can't get mutated.
// - The total len of the slice is guaranteed to be smaller than
// [`isize::MAX`].
// - The underlying mapping, the type and the slice have the same lifetime
// which guarantees that we can't access the underlying once it has been
// unmapped (use-after-unmap).
Ok(unsafe { slice::from_raw_parts(base, size) })
}
/// Unmap the memory mapped file.
pub fn unmap_memory_mapped_file(data: &[u8]) -> Result<(), io::Error> {
match unsafe { UnmapViewOfFile(data.as_ptr()) } {
0 => Err(io::Error::last_os_error()),
_ => Ok(()),
}
}
}
use windows::memory_map_file;
use windows::unmap_memory_mapped_file;
}
unix => {
mod unix {
// Memory mapping on Unix using the mmap syscall.
use std::os::fd::AsRawFd;
use std::fs::File;
use std::{io, ptr, slice};
const PROT_READ: i32 = 1;
const MAP_SHARED: i32 = 1;
const MAP_FAILED: *const u8 = usize::MAX as _;
unsafe extern "system" {
fn mmap(
addr: *const u8,
length: usize,
prot: i32,
flags: i32,
fd: i32,
offset: i32,
) -> *const u8;
fn munmap(addr: *const u8, length: usize) -> i32;
}
pub fn memory_map_file<'map>(file: &File) -> Result<&'map [u8], io::Error> {
// Grab the underlying file descriptor.
let file_fd = file.as_raw_fd();
// Grab the size of the underlying file. This will be the size of the
// memory mapped region.
let size = file.metadata()?.len().try_into().unwrap();
// Mmap the file.
let ret = unsafe { mmap(ptr::null_mut(), size, PROT_READ, MAP_SHARED, file_fd, 0) };
// If the system call failed, bail.
if ret == MAP_FAILED {
return Err(io::Error::last_os_error());
}
// Make sure the size is not bigger than what [`slice::from_raw_parts`] wants.
assert!(size <= isize::MAX.try_into().unwrap(), "slice is too large");
// Create the slice over the mapping.
// SAFETY: This is safe because:
// - It is a byte slice, so we don't need to care about the alignment.
// - The base is not NULL as we've verified that it is the case above.
// - The underlying is owned by the type and the lifetime.
// - We asked the OS to map `size` bytes, so we have a guarantee that there's
// `size` consecutive bytes.
// - We never give a mutable reference to this slice, so it can't get mutated.
// - The total len of the slice is guaranteed to be smaller than
// [`isize::MAX`].
// - The underlying mapping, the type and the slice have the same lifetime
// which guarantees that we can't access the underlying once it has been
// unmapped (use-after-unmap).
Ok(unsafe { slice::from_raw_parts(ret, size) })
}
// Unmap a memory mapped file.
pub fn unmap_memory_mapped_file(data: &[u8]) -> Result<(), io::Error> {
match unsafe { munmap(data.as_ptr(), data.len()) } {
0 => Ok(()),
_ => Err(io::Error::last_os_error()),
}
}
}
use unix::memory_map_file;
use unix::unmap_memory_mapped_file;
}
_ => { panic!("Your system hasn't been implemented; if you do it, send a PR!"); }
}