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