|  | // 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 |