1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <libfiemap/split_fiemap_writer.h>
18
19 #include <fcntl.h>
20 #include <stdint.h>
21 #include <sys/stat.h>
22 #include <sys/types.h>
23 #include <unistd.h>
24
25 #include <memory>
26 #include <string>
27 #include <vector>
28
29 #include <android-base/file.h>
30 #include <android-base/logging.h>
31 #include <android-base/stringprintf.h>
32 #include <android-base/strings.h>
33 #include <android-base/unique_fd.h>
34
35 #include "utility.h"
36
37 namespace android {
38 namespace fiemap {
39
40 using android::base::unique_fd;
41
42 // We use a four-digit suffix at the end of filenames.
43 static const size_t kMaxFilePieces = 500;
44
Create(const std::string & file_path,uint64_t file_size,uint64_t max_piece_size,ProgressCallback progress)45 std::unique_ptr<SplitFiemap> SplitFiemap::Create(const std::string& file_path, uint64_t file_size,
46 uint64_t max_piece_size,
47 ProgressCallback progress) {
48 std::unique_ptr<SplitFiemap> ret;
49 if (!Create(file_path, file_size, max_piece_size, &ret, progress).is_ok()) {
50 return nullptr;
51 }
52 return ret;
53 }
54
Create(const std::string & file_path,uint64_t file_size,uint64_t max_piece_size,std::unique_ptr<SplitFiemap> * out_val,ProgressCallback progress)55 FiemapStatus SplitFiemap::Create(const std::string& file_path, uint64_t file_size,
56 uint64_t max_piece_size, std::unique_ptr<SplitFiemap>* out_val,
57 ProgressCallback progress) {
58 out_val->reset();
59
60 if (!file_size) {
61 LOG(ERROR) << "Cannot create a fiemap for a 0-length file: " << file_path;
62 return FiemapStatus::Error();
63 }
64
65 if (!max_piece_size) {
66 auto status = DetermineMaximumFileSize(file_path, &max_piece_size);
67 if (!status.is_ok()) {
68 LOG(ERROR) << "Could not determine maximum file size for " << file_path;
69 return status;
70 }
71 }
72
73 // Remove any existing file.
74 RemoveSplitFiles(file_path);
75
76 // Call |progress| only when the total percentage would significantly change.
77 int permille = -1;
78 uint64_t total_bytes_written = 0;
79 auto on_progress = [&](uint64_t written, uint64_t) -> bool {
80 uint64_t actual_written = total_bytes_written + written;
81 int new_permille = (actual_written * 1000) / file_size;
82 if (new_permille != permille && actual_written < file_size) {
83 if (progress && !progress(actual_written, file_size)) {
84 return false;
85 }
86 permille = new_permille;
87 }
88 return true;
89 };
90 std::unique_ptr<SplitFiemap> out(new SplitFiemap());
91 out->creating_ = true;
92 out->list_file_ = file_path;
93
94 // Create the split files.
95 uint64_t remaining_bytes = file_size;
96 while (remaining_bytes) {
97 if (out->files_.size() >= kMaxFilePieces) {
98 LOG(ERROR) << "Requested size " << file_size << " created too many split files";
99 out.reset();
100 return FiemapStatus::Error();
101 }
102 std::string chunk_path =
103 android::base::StringPrintf("%s.%04d", file_path.c_str(), (int)out->files_.size());
104 uint64_t chunk_size = std::min(max_piece_size, remaining_bytes);
105 FiemapUniquePtr writer;
106 auto status = FiemapWriter::Open(chunk_path, chunk_size, &writer, true, on_progress);
107 if (!status.is_ok()) {
108 out.reset();
109 return status;
110 }
111
112 // To make sure the alignment doesn't create too much inconsistency, we
113 // account the *actual* size, not the requested size.
114 total_bytes_written += writer->size();
115
116 // writer->size() is block size aligned and could be bigger than remaining_bytes
117 // If remaining_bytes is bigger, set remaining_bytes to 0 to avoid underflow error.
118 remaining_bytes = remaining_bytes > writer->size() ? (remaining_bytes - writer->size()) : 0;
119
120 out->AddFile(std::move(writer));
121 }
122
123 // Create the split file list.
124 unique_fd fd(open(out->list_file_.c_str(), O_CREAT | O_WRONLY | O_CLOEXEC, 0660));
125 if (fd < 0) {
126 PLOG(ERROR) << "Failed to open " << file_path;
127 out.reset();
128 return FiemapStatus::FromErrno(errno);
129 }
130
131 for (const auto& writer : out->files_) {
132 std::string line = android::base::Basename(writer->file_path()) + "\n";
133 if (!android::base::WriteFully(fd, line.data(), line.size())) {
134 PLOG(ERROR) << "Write failed " << file_path;
135 out.reset();
136 return FiemapStatus::FromErrno(errno);
137 }
138 }
139 fsync(fd.get());
140
141 // Unset this bit, so we don't unlink on destruction.
142 out->creating_ = false;
143 *out_val = std::move(out);
144 return FiemapStatus::Ok();
145 }
146
Open(const std::string & file_path)147 std::unique_ptr<SplitFiemap> SplitFiemap::Open(const std::string& file_path) {
148 std::vector<std::string> files;
149 if (!GetSplitFileList(file_path, &files)) {
150 return nullptr;
151 }
152
153 std::unique_ptr<SplitFiemap> out(new SplitFiemap());
154 out->list_file_ = file_path;
155
156 for (const auto& file : files) {
157 auto writer = FiemapWriter::Open(file, 0, false);
158 if (!writer) {
159 // Error was logged in Open().
160 return nullptr;
161 }
162 out->AddFile(std::move(writer));
163 }
164 return out;
165 }
166
GetSplitFileList(const std::string & file_path,std::vector<std::string> * list)167 bool SplitFiemap::GetSplitFileList(const std::string& file_path, std::vector<std::string>* list) {
168 // This is not the most efficient thing, but it is simple and recovering
169 // the fiemap/fibmap is much more expensive.
170 std::string contents;
171 if (!android::base::ReadFileToString(file_path, &contents, true)) {
172 PLOG(ERROR) << "Error reading file: " << file_path;
173 return false;
174 }
175
176 std::vector<std::string> names = android::base::Split(contents, "\n");
177 std::string dir = android::base::Dirname(file_path);
178 for (const auto& name : names) {
179 if (!name.empty()) {
180 list->emplace_back(dir + "/" + name);
181 }
182 }
183 return true;
184 }
185
RemoveSplitFiles(const std::string & file_path,std::string * message)186 bool SplitFiemap::RemoveSplitFiles(const std::string& file_path, std::string* message) {
187 // Early exit if this does not exist, and do not report an error.
188 if (access(file_path.c_str(), F_OK) && errno == ENOENT) {
189 return true;
190 }
191
192 bool ok = true;
193 std::vector<std::string> files;
194 if (GetSplitFileList(file_path, &files)) {
195 for (const auto& file : files) {
196 if (access(file.c_str(), F_OK) != 0 && (errno == ENOENT || errno == ENAMETOOLONG)) {
197 continue;
198 }
199 truncate(file.c_str(), 0);
200 ok &= android::base::RemoveFileIfExists(file, message);
201 }
202 }
203 truncate(file_path.c_str(), 0);
204 ok &= android::base::RemoveFileIfExists(file_path, message);
205 sync();
206 return ok;
207 }
208
HasPinnedExtents() const209 bool SplitFiemap::HasPinnedExtents() const {
210 for (const auto& file : files_) {
211 if (!FiemapWriter::HasPinnedExtents(file->file_path())) {
212 return false;
213 }
214 }
215 return true;
216 }
217
extents()218 const std::vector<struct fiemap_extent>& SplitFiemap::extents() {
219 if (extents_.empty()) {
220 for (const auto& file : files_) {
221 const auto& extents = file->extents();
222 extents_.insert(extents_.end(), extents.begin(), extents.end());
223 }
224 }
225 return extents_;
226 }
227
Write(const void * data,uint64_t bytes)228 bool SplitFiemap::Write(const void* data, uint64_t bytes) {
229 // Open the current file.
230 FiemapWriter* file = files_[cursor_index_].get();
231
232 const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(data);
233 uint64_t bytes_remaining = bytes;
234 while (bytes_remaining) {
235 // How many bytes can we write into the current file?
236 uint64_t file_bytes_left = file->size() - cursor_file_pos_;
237 if (!file_bytes_left) {
238 if (cursor_index_ == files_.size() - 1) {
239 LOG(ERROR) << "write past end of file requested";
240 return false;
241 }
242
243 // No space left in the current file, but we have more files to
244 // use, so prep the next one.
245 cursor_fd_ = {};
246 cursor_file_pos_ = 0;
247 file = files_[++cursor_index_].get();
248 file_bytes_left = file->size();
249 }
250
251 // Open the current file if it's not open.
252 if (cursor_fd_ < 0) {
253 cursor_fd_.reset(open(file->file_path().c_str(), O_CLOEXEC | O_WRONLY));
254 if (cursor_fd_ < 0) {
255 PLOG(ERROR) << "open failed: " << file->file_path();
256 return false;
257 }
258 CHECK(cursor_file_pos_ == 0);
259 }
260
261 if (!FiemapWriter::HasPinnedExtents(file->file_path())) {
262 LOG(ERROR) << "file is no longer pinned: " << file->file_path();
263 return false;
264 }
265
266 uint64_t bytes_to_write = std::min(file_bytes_left, bytes_remaining);
267 if (!android::base::WriteFully(cursor_fd_, data_ptr, bytes_to_write)) {
268 PLOG(ERROR) << "write failed: " << file->file_path();
269 return false;
270 }
271 data_ptr += bytes_to_write;
272 bytes_remaining -= bytes_to_write;
273 cursor_file_pos_ += bytes_to_write;
274 }
275
276 // If we've reached the end of the current file, close it.
277 if (cursor_file_pos_ == file->size()) {
278 cursor_fd_ = {};
279 }
280 return true;
281 }
282
Flush()283 bool SplitFiemap::Flush() {
284 for (const auto& file : files_) {
285 unique_fd fd(open(file->file_path().c_str(), O_RDONLY | O_CLOEXEC));
286 if (fd < 0) {
287 PLOG(ERROR) << "open failed: " << file->file_path();
288 return false;
289 }
290 if (fsync(fd)) {
291 PLOG(ERROR) << "fsync failed: " << file->file_path();
292 return false;
293 }
294 }
295 return true;
296 }
297
~SplitFiemap()298 SplitFiemap::~SplitFiemap() {
299 if (!creating_) {
300 return;
301 }
302
303 // We failed to finish creating, so unlink everything.
304 unlink(list_file_.c_str());
305 for (auto&& file : files_) {
306 std::string path = file->file_path();
307 file = nullptr;
308
309 unlink(path.c_str());
310 }
311 }
312
AddFile(FiemapUniquePtr && file)313 void SplitFiemap::AddFile(FiemapUniquePtr&& file) {
314 total_size_ += file->size();
315 files_.emplace_back(std::move(file));
316 }
317
block_size() const318 uint32_t SplitFiemap::block_size() const {
319 return files_[0]->block_size();
320 }
321
bdev_path() const322 const std::string& SplitFiemap::bdev_path() const {
323 return files_[0]->bdev_path();
324 }
325
326 } // namespace fiemap
327 } // namespace android
328