| // Copyright 2013 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "base/files/memory_mapped_file.h" | 
 |  | 
 | #include <fcntl.h> | 
 | #include <stddef.h> | 
 | #include <stdint.h> | 
 | #include <sys/mman.h> | 
 | #include <sys/stat.h> | 
 | #include <unistd.h> | 
 |  | 
 | #include "base/logging.h" | 
 | #include "base/numerics/safe_conversions.h" | 
 | #include "base/threading/thread_restrictions.h" | 
 | #include "build_config.h" | 
 |  | 
 | #if defined(OS_ANDROID) | 
 | #include <android/api-level.h> | 
 | #endif | 
 |  | 
 | namespace base { | 
 |  | 
 | MemoryMappedFile::MemoryMappedFile() : data_(nullptr), length_(0) {} | 
 |  | 
 | #if !defined(OS_NACL) | 
 | bool MemoryMappedFile::MapFileRegionToMemory( | 
 |     const MemoryMappedFile::Region& region, | 
 |     Access access) { | 
 |   AssertBlockingAllowed(); | 
 |  | 
 |   off_t map_start = 0; | 
 |   size_t map_size = 0; | 
 |   int32_t data_offset = 0; | 
 |  | 
 |   if (region == MemoryMappedFile::Region::kWholeFile) { | 
 |     int64_t file_len = file_.GetLength(); | 
 |     if (file_len < 0) { | 
 |       DPLOG(ERROR) << "fstat " << file_.GetPlatformFile(); | 
 |       return false; | 
 |     } | 
 |     if (!IsValueInRangeForNumericType<size_t>(file_len)) | 
 |       return false; | 
 |     map_size = static_cast<size_t>(file_len); | 
 |     length_ = map_size; | 
 |   } else { | 
 |     // The region can be arbitrarily aligned. mmap, instead, requires both the | 
 |     // start and size to be page-aligned. Hence, we map here the page-aligned | 
 |     // outer region [|aligned_start|, |aligned_start| + |size|] which contains | 
 |     // |region| and then add up the |data_offset| displacement. | 
 |     int64_t aligned_start = 0; | 
 |     size_t aligned_size = 0; | 
 |     CalculateVMAlignedBoundaries(region.offset, | 
 |                                  region.size, | 
 |                                  &aligned_start, | 
 |                                  &aligned_size, | 
 |                                  &data_offset); | 
 |  | 
 |     // Ensure that the casts in the mmap call below are sane. | 
 |     if (aligned_start < 0 || | 
 |         !IsValueInRangeForNumericType<off_t>(aligned_start)) { | 
 |       DLOG(ERROR) << "Region bounds are not valid for mmap"; | 
 |       return false; | 
 |     } | 
 |  | 
 |     map_start = static_cast<off_t>(aligned_start); | 
 |     map_size = aligned_size; | 
 |     length_ = region.size; | 
 |   } | 
 |  | 
 |   int flags = 0; | 
 |   switch (access) { | 
 |     case READ_ONLY: | 
 |       flags |= PROT_READ; | 
 |       break; | 
 |  | 
 |     case READ_WRITE: | 
 |       flags |= PROT_READ | PROT_WRITE; | 
 |       break; | 
 |  | 
 |     case READ_WRITE_EXTEND: | 
 |       flags |= PROT_READ | PROT_WRITE; | 
 |  | 
 |       const int64_t new_file_len = region.offset + region.size; | 
 |  | 
 |       // POSIX won't auto-extend the file when it is written so it must first | 
 |       // be explicitly extended to the maximum size. Zeros will fill the new | 
 |       // space. It is assumed that the existing file is fully realized as | 
 |       // otherwise the entire file would have to be read and possibly written. | 
 |       const int64_t original_file_len = file_.GetLength(); | 
 |       if (original_file_len < 0) { | 
 |         DPLOG(ERROR) << "fstat " << file_.GetPlatformFile(); | 
 |         return false; | 
 |       } | 
 |  | 
 |       // Increase the actual length of the file, if necessary. This can fail if | 
 |       // the disk is full and the OS doesn't support sparse files. | 
 |       if (!file_.SetLength(std::max(original_file_len, new_file_len))) { | 
 |         DPLOG(ERROR) << "ftruncate " << file_.GetPlatformFile(); | 
 |         return false; | 
 |       } | 
 |  | 
 |       // Realize the extent of the file so that it can't fail (and crash) later | 
 |       // when trying to write to a memory page that can't be created. This can | 
 |       // fail if the disk is full and the file is sparse. | 
 |       bool do_manual_extension = false; | 
 |  | 
 | #if defined(OS_ANDROID) && __ANDROID_API__ < 21 | 
 |       // Only Android API>=21 supports the fallocate call. Older versions need | 
 |       // to manually extend the file by writing zeros at block intervals. | 
 |       do_manual_extension = true; | 
 | #elif defined(OS_MACOSX) | 
 |       // MacOS doesn't support fallocate even though their new APFS filesystem | 
 |       // does support sparse files. It does, however, have the functionality | 
 |       // available via fcntl. | 
 |       // See also: https://openradar.appspot.com/32720223 | 
 |       fstore_t params = {F_ALLOCATEALL, F_PEOFPOSMODE, region.offset, | 
 |                          region.size, 0}; | 
 |       if (fcntl(file_.GetPlatformFile(), F_PREALLOCATE, ¶ms) != 0) { | 
 |         DPLOG(ERROR) << "F_PREALLOCATE"; | 
 |         // This can fail because the filesystem doesn't support it so don't | 
 |         // give up just yet. Try the manual method below. | 
 |         do_manual_extension = true; | 
 |       } | 
 | #else | 
 |       if (posix_fallocate(file_.GetPlatformFile(), region.offset, | 
 |                           region.size) != 0) { | 
 |         DPLOG(ERROR) << "posix_fallocate"; | 
 |         // This can fail because the filesystem doesn't support it so don't | 
 |         // give up just yet. Try the manual method below. | 
 |         do_manual_extension = true; | 
 |       } | 
 | #endif | 
 |  | 
 |       // Manually realize the extended file by writing bytes to it at intervals. | 
 |       if (do_manual_extension) { | 
 |         int64_t block_size = 512;  // Start with something safe. | 
 |         struct stat statbuf; | 
 |         if (fstat(file_.GetPlatformFile(), &statbuf) == 0 && | 
 |             statbuf.st_blksize > 0) { | 
 |           block_size = statbuf.st_blksize; | 
 |         } | 
 |  | 
 |         // Write starting at the next block boundary after the old file length. | 
 |         const int64_t extension_start = | 
 |             (original_file_len + block_size - 1) & ~(block_size - 1); | 
 |         for (int64_t i = extension_start; i < new_file_len; i += block_size) { | 
 |           char existing_byte; | 
 |           if (pread(file_.GetPlatformFile(), &existing_byte, 1, i) != 1) | 
 |             return false;  // Can't read? Not viable. | 
 |           if (existing_byte != 0) | 
 |             continue;  // Block has data so must already exist. | 
 |           if (pwrite(file_.GetPlatformFile(), &existing_byte, 1, i) != 1) | 
 |             return false;  // Can't write? Not viable. | 
 |         } | 
 |       } | 
 |  | 
 |       break; | 
 |   } | 
 |  | 
 |   data_ = static_cast<uint8_t*>(mmap(nullptr, map_size, flags, MAP_SHARED, | 
 |                                      file_.GetPlatformFile(), map_start)); | 
 |   if (data_ == MAP_FAILED) { | 
 |     DPLOG(ERROR) << "mmap " << file_.GetPlatformFile(); | 
 |     return false; | 
 |   } | 
 |  | 
 |   data_ += data_offset; | 
 |   return true; | 
 | } | 
 | #endif | 
 |  | 
 | void MemoryMappedFile::CloseHandles() { | 
 |   AssertBlockingAllowed(); | 
 |  | 
 |   if (data_ != nullptr) | 
 |     munmap(data_, length_); | 
 |   file_.Close(); | 
 |  | 
 |   data_ = nullptr; | 
 |   length_ = 0; | 
 | } | 
 |  | 
 | }  // namespace base |