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);
is_open() const57     bool is_open() const { return data_ != 0; }
58     void close();
error() const59     bool error() const { return error_; }
flags() const60     mapmode flags() const { return params_.flags; }
size() const61     std::size_t size() const { return static_cast<std::size_t>(size_); }
data() const62     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 
mapped_file_impl()82 mapped_file_impl::mapped_file_impl() { clear(false); }
83 
~mapped_file_impl()84 mapped_file_impl::~mapped_file_impl()
85 { try { close(); } catch (...) { } }
86 
open(param_type p)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 
close()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(handle_) != 0
108         #endif
109             || error;
110     clear(error);
111     if (error)
112         throw_system_failure("failed closing mapped file");
113 }
114 
resize(stream_offset new_size)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("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(handle_, new_size) == -1)
148         cleanup_and_throw("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 
alignment()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 
open_file(param_type p)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("wide path not supported here"); } // happens on CYGWIN
267     handle_ = ::open(p.path.c_str(), flags, S_IRWXU);
268     if (errno != 0)
269         cleanup_and_throw("failed opening file");
270 
271     //--------------Set file size---------------------------------------------//
272 
273     if (p.new_file_size != 0 && !readonly)
274         if (BOOST_IOSTREAMS_FD_TRUNCATE(handle_, p.new_file_size) == -1)
275             cleanup_and_throw("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(handle_, &info) != -1;
285         size_ = info.st_size;
286     }
287     if (!success)
288         cleanup_and_throw("failed querying file size");
289 #endif // #ifdef BOOST_IOSTREAMS_WINDOWS
290 }
291 
try_map_file(param_type p)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             const_cast<char*>(p.hint),
335             size_,
336             readonly ? PROT_READ : (PROT_READ | PROT_WRITE),
337             priv ? MAP_PRIVATE : MAP_SHARED,
338             handle_,
339             p.offset );
340     if (data == MAP_FAILED)
341         cleanup_and_throw("failed mapping file");
342 #endif
343     data_ = static_cast<char*>(data);
344 }
345 
map_file(param_type & p)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 
unmap_file()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(data_, size_) == 0;
370 #endif
371 }
372 
clear(bool error)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
cleanup_and_throw(const char * msg)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(handle_);
402     errno = error;
403 #endif
404     clear(true);
405     boost::iostreams::detail::throw_system_failure(msg);
406 }
407 
408 //------------------Implementation of mapped_file_params_base-----------------//
409 
normalize()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 
mapped_file_source()443 mapped_file_source::mapped_file_source()
444     : pimpl_(new impl_type)
445     { }
446 
mapped_file_source(const mapped_file_source & other)447 mapped_file_source::mapped_file_source(const mapped_file_source& other)
448     : pimpl_(other.pimpl_)
449     { }
450 
is_open() const451 bool mapped_file_source::is_open() const
452 { return pimpl_->is_open(); }
453 
close()454 void mapped_file_source::close() { pimpl_->close(); }
455 
456 // safe_bool is explicitly qualified below to please msvc 7.1
operator mapped_file_source::safe_bool() const457 mapped_file_source::operator mapped_file_source::safe_bool() const
458 { return pimpl_->error() ? &safe_bool_helper::x : 0; }
459 
operator !() const460 bool mapped_file_source::operator!() const
461 { return pimpl_->error(); }
462 
flags() const463 mapped_file_source::mapmode mapped_file_source::flags() const
464 { return pimpl_->flags(); }
465 
size() const466 mapped_file_source::size_type mapped_file_source::size() const
467 { return pimpl_->size(); }
468 
data() const469 const char* mapped_file_source::data() const { return pimpl_->data(); }
470 
begin() const471 const char* mapped_file_source::begin() const { return data(); }
472 
end() const473 const char* mapped_file_source::end() const { return data() + size(); }
alignment()474 int mapped_file_source::alignment()
475 { return detail::mapped_file_impl::alignment(); }
476 
init()477 void mapped_file_source::init() { pimpl_.reset(new impl_type); }
478 
open_impl(const param_type & p)479 void mapped_file_source::open_impl(const param_type& p)
480 { pimpl_->open(p); }
481 
482 //------------------Implementation of mapped_file-----------------------------//
483 
mapped_file(const mapped_file & other)484 mapped_file::mapped_file(const mapped_file& other)
485     : delegate_(other.delegate_)
486     { }
487 
resize(stream_offset new_size)488 void mapped_file::resize(stream_offset new_size)
489 { delegate_.pimpl_->resize(new_size); }
490 
491 //------------------Implementation of mapped_file_sink------------------------//
492 
mapped_file_sink(const mapped_file_sink & other)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