huangcm
2025-02-24 69ed55dec4b2116a19e4cca4393cbc014fce5fb2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// 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/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, &params) != 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