aboutsummaryrefslogtreecommitdiff
path: root/icing/file/memory-mapped-file.cc
blob: bda01f2acbf47aa2eb8f7198936d1b844d52870f (plain)
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// Copyright (C) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// TODO(cassiewang) Add unit-tests to this class.

#include "icing/file/memory-mapped-file.h"

#include <sys/mman.h>

#include <cerrno>

#include "icing/text_classifier/lib3/utils/base/status.h"
#include "icing/absl_ports/canonical_errors.h"
#include "icing/absl_ports/str_cat.h"
#include "icing/file/filesystem.h"
#include "icing/legacy/core/icing-string-util.h"
#include "icing/util/math-util.h"

namespace icing {
namespace lib {

MemoryMappedFile::MemoryMappedFile(const Filesystem& filesystem,
                                   const std::string_view file_path,
                                   Strategy mmap_strategy)
    : filesystem_(&filesystem),
      file_path_(file_path),
      strategy_(mmap_strategy) {}

MemoryMappedFile::MemoryMappedFile(MemoryMappedFile&& other)
    // Make sure that mmap_result_ is a nullptr before we call Swap. We don't
    // care what values the remaining members hold before we swap into other,
    // but if mmap_result_ holds a non-NULL value before we initialized anything
    // then other will try to free memory at that address when it's destroyed!
    : mmap_result_(nullptr) {
  Swap(&other);
}

MemoryMappedFile& MemoryMappedFile::operator=(MemoryMappedFile&& other) {
  // Swap all of our elements with other. This will ensure that both this now
  // holds other's previous resources and that this's previous resources will be
  // properly freed when other is destructed at the end of this function.
  Swap(&other);
  return *this;
}

MemoryMappedFile::~MemoryMappedFile() { Unmap(); }

void MemoryMappedFile::MemoryMappedFile::Unmap() {
  if (mmap_result_ != nullptr) {
    munmap(mmap_result_, adjusted_mmap_size_);
    mmap_result_ = nullptr;
  }

  file_offset_ = 0;
  region_ = nullptr;
  region_size_ = 0;
  adjusted_mmap_size_ = 0;
}

libtextclassifier3::Status MemoryMappedFile::Remap(size_t file_offset,
                                                   size_t mmap_size) {
  // First unmap any previously mmapped region.
  Unmap();

  if (mmap_size == 0) {
    // Nothing more to do.
    return libtextclassifier3::Status::OK;
  }

  size_t aligned_offset =
      math_util::RoundDownTo(file_offset, system_page_size());
  size_t alignment_adjustment = file_offset - aligned_offset;
  size_t adjusted_mmap_size = alignment_adjustment + mmap_size;

  int mmap_flags = 0;
  // Determines if the mapped region should just be readable or also writable.
  int protection_flags = 0;
  ScopedFd fd;
  switch (strategy_) {
    case Strategy::READ_ONLY: {
      mmap_flags = MAP_PRIVATE;
      protection_flags = PROT_READ;
      fd.reset(filesystem_->OpenForRead(file_path_.c_str()));
      break;
    }
    case Strategy::READ_WRITE_AUTO_SYNC: {
      mmap_flags = MAP_SHARED;
      protection_flags = PROT_READ | PROT_WRITE;
      fd.reset(filesystem_->OpenForWrite(file_path_.c_str()));
      break;
    }
    case Strategy::READ_WRITE_MANUAL_SYNC: {
      mmap_flags = MAP_PRIVATE;
      protection_flags = PROT_READ | PROT_WRITE;
      // TODO(cassiewang) MAP_PRIVATE effectively makes it a read-only file.
      // figure out if we can open this file in read-only mode.
      fd.reset(filesystem_->OpenForWrite(file_path_.c_str()));
      break;
    }
    default:
      return absl_ports::UnknownError(IcingStringUtil::StringPrintf(
          "Invalid value in switch statement: %d", strategy_));
  }

  if (!fd.is_valid()) {
    return absl_ports::InternalError(absl_ports::StrCat(
        "Unable to open file meant to be mmapped: ", file_path_));
  }

  mmap_result_ = mmap(nullptr, adjusted_mmap_size, protection_flags, mmap_flags,
                      fd.get(), aligned_offset);

  if (mmap_result_ == MAP_FAILED) {
    mmap_result_ = nullptr;
    return absl_ports::InternalError(absl_ports::StrCat(
        "Failed to mmap region due to error: ", strerror(errno)));
  }

  file_offset_ = file_offset;
  region_ = reinterpret_cast<char*>(mmap_result_) + alignment_adjustment;
  region_size_ = mmap_size;
  adjusted_mmap_size_ = adjusted_mmap_size;
  return libtextclassifier3::Status::OK;
}

libtextclassifier3::Status MemoryMappedFile::PersistToDisk() {
  if (strategy_ == Strategy::READ_ONLY) {
    return absl_ports::FailedPreconditionError(absl_ports::StrCat(
        "Attempting to PersistToDisk on a read-only file: ", file_path_));
  }

  if (region_ == nullptr) {
    // Nothing mapped to sync.
    return libtextclassifier3::Status::OK;
  }

  if (strategy_ == Strategy::READ_WRITE_AUTO_SYNC &&
      msync(mmap_result_, adjusted_mmap_size_, MS_SYNC) != 0) {
    return absl_ports::InternalError(
        absl_ports::StrCat("Unable to sync file using msync(): ", file_path_));
  }

  // In order to prevent automatic syncing of changes, files that use the
  // READ_WRITE_MANUAL_SYNC strategy are mmapped using MAP_PRIVATE. Such files
  // can't be synced using msync(). So, we have to directly write to the
  // underlying file to update it.
  if (strategy_ == Strategy::READ_WRITE_MANUAL_SYNC &&
      !filesystem_->PWrite(file_path_.c_str(), 0, region(), region_size())) {
    return absl_ports::InternalError(
        absl_ports::StrCat("Unable to sync file using PWrite(): ", file_path_));
  }

  return libtextclassifier3::Status::OK;
}

libtextclassifier3::Status MemoryMappedFile::OptimizeFor(
    AccessPattern access_pattern) {
  int madvise_flag = 0;
  if (access_pattern == AccessPattern::ACCESS_ALL) {
    madvise_flag = MADV_WILLNEED;
  } else if (access_pattern == AccessPattern::ACCESS_NONE) {
    madvise_flag = MADV_DONTNEED;
  } else if (access_pattern == AccessPattern::ACCESS_RANDOM) {
    madvise_flag = MADV_RANDOM;
  } else if (access_pattern == AccessPattern::ACCESS_SEQUENTIAL) {
    madvise_flag = MADV_SEQUENTIAL;
  }

  if (madvise(mmap_result_, adjusted_mmap_size_, madvise_flag) != 0) {
    return absl_ports::InternalError(absl_ports::StrCat(
        "Unable to madvise file ", file_path_, "; Error: ", strerror(errno)));
  }
  return libtextclassifier3::Status::OK;
}

void MemoryMappedFile::Swap(MemoryMappedFile* other) {
  std::swap(filesystem_, other->filesystem_);
  std::swap(file_path_, other->file_path_);
  std::swap(strategy_, other->strategy_);
  std::swap(file_offset_, other->file_offset_);
  std::swap(region_, other->region_);
  std::swap(region_size_, other->region_size_);
  std::swap(adjusted_mmap_size_, other->adjusted_mmap_size_);
  std::swap(mmap_result_, other->mmap_result_);
}

}  // namespace lib
}  // namespace icing