1 | // (C) Copyright Craig Henderson 2002 'boost/memmap.hpp' from sandbox |
2 | // (C) Copyright Jonathan Turkanis 2004. |
3 | // (C) Copyright Jonathan Graehl 2004. |
4 | // (C) Copyright Jorge Lodos 2008. |
5 | // Distributed under the Boost Software License, Version 1.0. (See accompanying |
6 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.) |
7 | |
8 | // Define BOOST_IOSTREAMS_SOURCE so that <boost/iostreams/detail/config.hpp> |
9 | // knows that we are building the library (possibly exporting code), rather |
10 | // than using it (possibly importing code). |
11 | #define BOOST_IOSTREAMS_SOURCE |
12 | |
13 | #include <cassert> |
14 | #include <stdexcept> |
15 | #include <boost/iostreams/detail/config/rtl.hpp> |
16 | #include <boost/iostreams/detail/config/windows_posix.hpp> |
17 | #include <boost/iostreams/detail/file_handle.hpp> |
18 | #include <boost/iostreams/detail/system_failure.hpp> |
19 | #include <boost/iostreams/device/mapped_file.hpp> |
20 | #include <boost/throw_exception.hpp> |
21 | #include <boost/numeric/conversion/cast.hpp> |
22 | |
23 | #ifdef BOOST_IOSTREAMS_WINDOWS |
24 | # define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers |
25 | # include <windows.h> |
26 | #else |
27 | # include <errno.h> |
28 | # include <fcntl.h> |
29 | # include <sys/mman.h> // mmap, munmap. |
30 | # include <sys/stat.h> |
31 | # include <sys/types.h> // struct stat. |
32 | # include <unistd.h> // sysconf. |
33 | #endif |
34 | |
35 | namespace boost { namespace iostreams { |
36 | |
37 | namespace detail { |
38 | |
39 | // Class containing the platform-sepecific implementation |
40 | // Invariant: The members params_, data_, size_, handle_ (and mapped_handle_ |
41 | // on Windows) either |
42 | // - all have default values (or INVALID_HANDLE_VALUE for |
43 | // Windows handles), or |
44 | // - all have values reflecting a successful mapping. |
45 | // In the first case, error_ may be true, reflecting a recent unsuccessful |
46 | // open or close attempt; in the second case, error_ is always false. |
47 | class mapped_file_impl { |
48 | public: |
49 | typedef mapped_file_source::size_type size_type; |
50 | typedef mapped_file_source::param_type param_type; |
51 | typedef mapped_file_source::mapmode mapmode; |
52 | BOOST_STATIC_CONSTANT( |
53 | size_type, max_length = mapped_file_source::max_length); |
54 | mapped_file_impl(); |
55 | ~mapped_file_impl(); |
56 | void open(param_type p); |
57 | bool is_open() const { return data_ != 0; } |
58 | void close(); |
59 | bool error() const { return error_; } |
60 | mapmode flags() const { return params_.flags; } |
61 | std::size_t size() const { return static_cast<std::size_t>(size_); } |
62 | char* data() const { return data_; } |
63 | void resize(stream_offset new_size); |
64 | static int alignment(); |
65 | private: |
66 | void open_file(param_type p); |
67 | void try_map_file(param_type p); |
68 | void map_file(param_type& p); |
69 | bool unmap_file(); |
70 | void clear(bool error); |
71 | void cleanup_and_throw(const char* msg); |
72 | param_type params_; |
73 | char* data_; |
74 | stream_offset size_; |
75 | file_handle handle_; |
76 | #ifdef BOOST_IOSTREAMS_WINDOWS |
77 | file_handle mapped_handle_; |
78 | #endif |
79 | bool error_; |
80 | }; |
81 | |
82 | mapped_file_impl::mapped_file_impl() { clear(error: false); } |
83 | |
84 | mapped_file_impl::~mapped_file_impl() |
85 | { try { close(); } catch (...) { } } |
86 | |
87 | void mapped_file_impl::open(param_type p) |
88 | { |
89 | if (is_open()) |
90 | boost::throw_exception(BOOST_IOSTREAMS_FAILURE("file already open" )); |
91 | p.normalize(); |
92 | open_file(p); |
93 | map_file(p); // May modify p.hint |
94 | params_ = p; |
95 | } |
96 | |
97 | void mapped_file_impl::close() |
98 | { |
99 | if (data_ == 0) |
100 | return; |
101 | bool error = false; |
102 | error = !unmap_file() || error; |
103 | error = |
104 | #ifdef BOOST_IOSTREAMS_WINDOWS |
105 | !::CloseHandle(handle_) |
106 | #else |
107 | ::close(fd: handle_) != 0 |
108 | #endif |
109 | || error; |
110 | clear(error); |
111 | if (error) |
112 | throw_system_failure(msg: "failed closing mapped file" ); |
113 | } |
114 | |
115 | void mapped_file_impl::resize(stream_offset new_size) |
116 | { |
117 | if (!is_open()) |
118 | boost::throw_exception(BOOST_IOSTREAMS_FAILURE("file is closed" )); |
119 | if (flags() & mapped_file::priv) |
120 | boost::throw_exception( |
121 | BOOST_IOSTREAMS_FAILURE("can't resize private mapped file" ) |
122 | ); |
123 | if (!(flags() & mapped_file::readwrite)) |
124 | boost::throw_exception( |
125 | BOOST_IOSTREAMS_FAILURE("can't resize readonly mapped file" ) |
126 | ); |
127 | if (params_.offset >= new_size) |
128 | boost::throw_exception( |
129 | BOOST_IOSTREAMS_FAILURE("can't resize below mapped offset" ) |
130 | ); |
131 | if (!unmap_file()) |
132 | cleanup_and_throw(msg: "failed unmapping file" ); |
133 | #ifdef BOOST_IOSTREAMS_WINDOWS |
134 | stream_offset offset = ::SetFilePointer(handle_, 0, NULL, FILE_CURRENT); |
135 | if (offset == INVALID_SET_FILE_POINTER && ::GetLastError() != NO_ERROR) |
136 | cleanup_and_throw("failed querying file pointer" ); |
137 | LONG sizehigh = (new_size >> (sizeof(LONG) * 8)); |
138 | LONG sizelow = (new_size & 0xffffffff); |
139 | DWORD result = ::SetFilePointer(handle_, sizelow, &sizehigh, FILE_BEGIN); |
140 | if ((result == INVALID_SET_FILE_POINTER && ::GetLastError() != NO_ERROR) |
141 | || !::SetEndOfFile(handle_)) |
142 | cleanup_and_throw("failed resizing mapped file" ); |
143 | sizehigh = (offset >> (sizeof(LONG) * 8)); |
144 | sizelow = (offset & 0xffffffff); |
145 | ::SetFilePointer(handle_, sizelow, &sizehigh, FILE_BEGIN); |
146 | #else |
147 | if (BOOST_IOSTREAMS_FD_TRUNCATE(fd: handle_, length: new_size) == -1) |
148 | cleanup_and_throw(msg: "failed resizing mapped file" ); |
149 | #endif |
150 | size_ = new_size; |
151 | param_type p(params_); |
152 | map_file(p); // May modify p.hint |
153 | params_ = p; |
154 | } |
155 | |
156 | int mapped_file_impl::alignment() |
157 | { |
158 | #ifdef BOOST_IOSTREAMS_WINDOWS |
159 | SYSTEM_INFO info; |
160 | ::GetSystemInfo(&info); |
161 | return static_cast<int>(info.dwAllocationGranularity); |
162 | #else |
163 | return static_cast<int>(sysconf(_SC_PAGESIZE)); |
164 | #endif |
165 | } |
166 | |
167 | void mapped_file_impl::open_file(param_type p) |
168 | { |
169 | bool readonly = p.flags != mapped_file::readwrite; |
170 | #ifdef BOOST_IOSTREAMS_WINDOWS |
171 | |
172 | // Open file |
173 | DWORD dwDesiredAccess = |
174 | readonly ? |
175 | GENERIC_READ : |
176 | (GENERIC_READ | GENERIC_WRITE); |
177 | DWORD dwCreationDisposition = (p.new_file_size != 0 && !readonly) ? |
178 | CREATE_ALWAYS : |
179 | OPEN_EXISTING; |
180 | DWORD dwFlagsandAttributes = |
181 | readonly ? |
182 | FILE_ATTRIBUTE_READONLY : |
183 | FILE_ATTRIBUTE_TEMPORARY; |
184 | handle_ = p.path.is_wide() ? |
185 | ::CreateFileW( |
186 | p.path.c_wstr(), |
187 | dwDesiredAccess, |
188 | FILE_SHARE_READ, |
189 | NULL, |
190 | dwCreationDisposition, |
191 | dwFlagsandAttributes, |
192 | NULL ) : |
193 | ::CreateFileA( |
194 | p.path.c_str(), |
195 | dwDesiredAccess, |
196 | FILE_SHARE_READ, |
197 | NULL, |
198 | dwCreationDisposition, |
199 | dwFlagsandAttributes, |
200 | NULL ); |
201 | if (handle_ == INVALID_HANDLE_VALUE) |
202 | cleanup_and_throw("failed opening file" ); |
203 | |
204 | // Set file size |
205 | if (p.new_file_size != 0 && !readonly) { |
206 | LONG sizehigh = (p.new_file_size >> (sizeof(LONG) * 8)); |
207 | LONG sizelow = (p.new_file_size & 0xffffffff); |
208 | DWORD result = ::SetFilePointer(handle_, sizelow, &sizehigh, FILE_BEGIN); |
209 | if ((result == INVALID_SET_FILE_POINTER && ::GetLastError() != NO_ERROR) |
210 | || !::SetEndOfFile(handle_)) |
211 | cleanup_and_throw("failed setting file size" ); |
212 | } |
213 | |
214 | // Determine file size. Dynamically locate GetFileSizeEx for compatibility |
215 | // with old Platform SDK (thanks to Pavel Vozenilik). |
216 | typedef BOOL (WINAPI *func)(HANDLE, PLARGE_INTEGER); |
217 | HMODULE hmod = ::GetModuleHandleA("kernel32.dll" ); |
218 | func get_size = |
219 | reinterpret_cast<func>(::GetProcAddress(hmod, "GetFileSizeEx" )); |
220 | if (get_size) { |
221 | LARGE_INTEGER info; |
222 | if (get_size(handle_, &info)) { |
223 | boost::intmax_t size = |
224 | ( (static_cast<boost::intmax_t>(info.HighPart) << 32) | |
225 | info.LowPart ); |
226 | size_ = |
227 | static_cast<std::size_t>( |
228 | p.length != max_length ? |
229 | std::min<boost::intmax_t>(p.length, size) : |
230 | size |
231 | ); |
232 | } else { |
233 | cleanup_and_throw("failed querying file size" ); |
234 | return; |
235 | } |
236 | } else { |
237 | DWORD hi; |
238 | DWORD low; |
239 | if ( (low = ::GetFileSize(handle_, &hi)) |
240 | != |
241 | INVALID_FILE_SIZE ) |
242 | { |
243 | boost::intmax_t size = |
244 | (static_cast<boost::intmax_t>(hi) << 32) | low; |
245 | size_ = |
246 | static_cast<std::size_t>( |
247 | p.length != max_length ? |
248 | std::min<boost::intmax_t>(p.length, size) : |
249 | size |
250 | ); |
251 | } else { |
252 | cleanup_and_throw("failed querying file size" ); |
253 | return; |
254 | } |
255 | } |
256 | #else // #ifdef BOOST_IOSTREAMS_WINDOWS |
257 | |
258 | // Open file |
259 | int flags = (readonly ? O_RDONLY : O_RDWR); |
260 | if (p.new_file_size != 0 && !readonly) |
261 | flags |= (O_CREAT | O_TRUNC); |
262 | #ifdef _LARGEFILE64_SOURCE |
263 | flags |= O_LARGEFILE; |
264 | #endif |
265 | errno = 0; |
266 | if (p.path.is_wide()) { errno = EINVAL; cleanup_and_throw(msg: "wide path not supported here" ); } // happens on CYGWIN |
267 | handle_ = ::open(file: p.path.c_str(), oflag: flags, S_IRWXU); |
268 | if (errno != 0) |
269 | cleanup_and_throw(msg: "failed opening file" ); |
270 | |
271 | //--------------Set file size---------------------------------------------// |
272 | |
273 | if (p.new_file_size != 0 && !readonly) |
274 | if (BOOST_IOSTREAMS_FD_TRUNCATE(fd: handle_, length: p.new_file_size) == -1) |
275 | cleanup_and_throw(msg: "failed setting file size" ); |
276 | |
277 | //--------------Determine file size---------------------------------------// |
278 | |
279 | bool success = true; |
280 | if (p.length != max_length) { |
281 | size_ = p.length; |
282 | } else { |
283 | struct BOOST_IOSTREAMS_FD_STAT info; |
284 | success = ::BOOST_IOSTREAMS_FD_FSTAT(fd: handle_, buf: &info) != -1; |
285 | size_ = info.st_size; |
286 | } |
287 | if (!success) |
288 | cleanup_and_throw(msg: "failed querying file size" ); |
289 | #endif // #ifdef BOOST_IOSTREAMS_WINDOWS |
290 | } |
291 | |
292 | void mapped_file_impl::try_map_file(param_type p) |
293 | { |
294 | bool priv = p.flags == mapped_file::priv; |
295 | bool readonly = p.flags == mapped_file::readonly; |
296 | #ifdef BOOST_IOSTREAMS_WINDOWS |
297 | |
298 | // Create mapping |
299 | DWORD protect = priv ? |
300 | PAGE_WRITECOPY : |
301 | readonly ? |
302 | PAGE_READONLY : |
303 | PAGE_READWRITE; |
304 | mapped_handle_ = |
305 | ::CreateFileMappingA( |
306 | handle_, |
307 | NULL, |
308 | protect, |
309 | 0, |
310 | 0, |
311 | NULL ); |
312 | if (mapped_handle_ == NULL) |
313 | cleanup_and_throw("failed create mapping" ); |
314 | |
315 | // Access data |
316 | DWORD access = priv ? |
317 | FILE_MAP_COPY : |
318 | readonly ? |
319 | FILE_MAP_READ : |
320 | FILE_MAP_WRITE; |
321 | void* data = |
322 | ::MapViewOfFileEx( |
323 | mapped_handle_, |
324 | access, |
325 | (DWORD) (p.offset >> 32), |
326 | (DWORD) (p.offset & 0xffffffff), |
327 | (SIZE_T) (numeric_cast<size_type>(size_) != max_length ? size_ : 0), |
328 | (LPVOID) p.hint ); |
329 | if (!data) |
330 | cleanup_and_throw("failed mapping view" ); |
331 | #else |
332 | void* data = |
333 | ::BOOST_IOSTREAMS_FD_MMAP( |
334 | addr: const_cast<char*>(p.hint), |
335 | len: size_, |
336 | prot: readonly ? PROT_READ : (PROT_READ | PROT_WRITE), |
337 | flags: priv ? MAP_PRIVATE : MAP_SHARED, |
338 | fd: handle_, |
339 | offset: p.offset ); |
340 | if (data == MAP_FAILED) |
341 | cleanup_and_throw(msg: "failed mapping file" ); |
342 | #endif |
343 | data_ = static_cast<char*>(data); |
344 | } |
345 | |
346 | void mapped_file_impl::map_file(param_type& p) |
347 | { |
348 | try { |
349 | try_map_file(p); |
350 | } catch (const std::exception&) { |
351 | if (p.hint) { |
352 | p.hint = 0; |
353 | try_map_file(p); |
354 | } else { |
355 | throw; |
356 | } |
357 | } |
358 | } |
359 | |
360 | bool mapped_file_impl::unmap_file() |
361 | { |
362 | #ifdef BOOST_IOSTREAMS_WINDOWS |
363 | bool error = false; |
364 | error = !::UnmapViewOfFile(data_) || error; |
365 | error = !::CloseHandle(mapped_handle_) || error; |
366 | mapped_handle_ = NULL; |
367 | return !error; |
368 | #else |
369 | return ::munmap(addr: data_, len: size_) == 0; |
370 | #endif |
371 | } |
372 | |
373 | void mapped_file_impl::clear(bool error) |
374 | { |
375 | params_ = param_type(); |
376 | data_ = 0; |
377 | size_ = 0; |
378 | #ifdef BOOST_IOSTREAMS_WINDOWS |
379 | handle_ = INVALID_HANDLE_VALUE; |
380 | mapped_handle_ = NULL; |
381 | #else |
382 | handle_ = 0; |
383 | #endif |
384 | error_ = error; |
385 | } |
386 | |
387 | // Called when an error is encountered during the execution of open_file or |
388 | // map_file |
389 | void mapped_file_impl::cleanup_and_throw(const char* msg) |
390 | { |
391 | #ifdef BOOST_IOSTREAMS_WINDOWS |
392 | DWORD error = GetLastError(); |
393 | if (mapped_handle_ != NULL) |
394 | ::CloseHandle(mapped_handle_); |
395 | if (handle_ != INVALID_HANDLE_VALUE) |
396 | ::CloseHandle(handle_); |
397 | SetLastError(error); |
398 | #else |
399 | int error = errno; |
400 | if (handle_ != 0) |
401 | ::close(fd: handle_); |
402 | errno = error; |
403 | #endif |
404 | clear(error: true); |
405 | boost::iostreams::detail::throw_system_failure(msg); |
406 | } |
407 | |
408 | //------------------Implementation of mapped_file_params_base-----------------// |
409 | |
410 | void mapped_file_params_base::normalize() |
411 | { |
412 | if (mode && flags) |
413 | boost::throw_exception(BOOST_IOSTREAMS_FAILURE( |
414 | "at most one of 'mode' and 'flags' may be specified" |
415 | )); |
416 | if (flags) { |
417 | switch (flags) { |
418 | case mapped_file::readonly: |
419 | case mapped_file::readwrite: |
420 | case mapped_file::priv: |
421 | break; |
422 | default: |
423 | boost::throw_exception(BOOST_IOSTREAMS_FAILURE("invalid flags" )); |
424 | } |
425 | } else { |
426 | flags = (mode & BOOST_IOS::out) ? |
427 | mapped_file::readwrite : |
428 | mapped_file::readonly; |
429 | mode = BOOST_IOS::openmode(); |
430 | } |
431 | if (offset < 0) |
432 | boost::throw_exception(BOOST_IOSTREAMS_FAILURE("invalid offset" )); |
433 | if (new_file_size < 0) |
434 | boost::throw_exception( |
435 | BOOST_IOSTREAMS_FAILURE("invalid new file size" ) |
436 | ); |
437 | } |
438 | |
439 | } // End namespace detail. |
440 | |
441 | //------------------Implementation of mapped_file_source----------------------// |
442 | |
443 | mapped_file_source::mapped_file_source() |
444 | : pimpl_(new impl_type) |
445 | { } |
446 | |
447 | mapped_file_source::mapped_file_source(const mapped_file_source& other) |
448 | : pimpl_(other.pimpl_) |
449 | { } |
450 | |
451 | bool mapped_file_source::is_open() const |
452 | { return pimpl_->is_open(); } |
453 | |
454 | void mapped_file_source::close() { pimpl_->close(); } |
455 | |
456 | // safe_bool is explicitly qualified below to please msvc 7.1 |
457 | mapped_file_source::operator mapped_file_source::safe_bool() const |
458 | { return pimpl_->error() ? &safe_bool_helper::x : 0; } |
459 | |
460 | bool mapped_file_source::operator!() const |
461 | { return pimpl_->error(); } |
462 | |
463 | mapped_file_source::mapmode mapped_file_source::flags() const |
464 | { return pimpl_->flags(); } |
465 | |
466 | mapped_file_source::size_type mapped_file_source::size() const |
467 | { return pimpl_->size(); } |
468 | |
469 | const char* mapped_file_source::data() const { return pimpl_->data(); } |
470 | |
471 | const char* mapped_file_source::begin() const { return data(); } |
472 | |
473 | const char* mapped_file_source::end() const { return data() + size(); } |
474 | int mapped_file_source::alignment() |
475 | { return detail::mapped_file_impl::alignment(); } |
476 | |
477 | void mapped_file_source::init() { pimpl_.reset(p: new impl_type); } |
478 | |
479 | void mapped_file_source::open_impl(const param_type& p) |
480 | { pimpl_->open(p); } |
481 | |
482 | //------------------Implementation of mapped_file-----------------------------// |
483 | |
484 | mapped_file::mapped_file(const mapped_file& other) |
485 | : delegate_(other.delegate_) |
486 | { } |
487 | |
488 | void mapped_file::resize(stream_offset new_size) |
489 | { delegate_.pimpl_->resize(new_size); } |
490 | |
491 | //------------------Implementation of mapped_file_sink------------------------// |
492 | |
493 | mapped_file_sink::mapped_file_sink(const mapped_file_sink& other) |
494 | : mapped_file(static_cast<const mapped_file&>(other)) |
495 | { } |
496 | |
497 | //----------------------------------------------------------------------------// |
498 | |
499 | } } // End namespaces iostreams, boost. |
500 | |