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_writer/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_writer {
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 if (!file_size) {
49 LOG(ERROR) << "Cannot create a fiemap for a 0-length file: " << file_path;
50 return nullptr;
51 }
52
53 if (!max_piece_size) {
54 max_piece_size = DetermineMaximumFileSize(file_path);
55 if (!max_piece_size) {
56 LOG(ERROR) << "Could not determine maximum file size for " << file_path;
57 return nullptr;
58 }
59 }
60
61 // Call |progress| only when the total percentage would significantly change.
62 int permille = -1;
63 uint64_t total_bytes_written = 0;
64 auto on_progress = [&](uint64_t written, uint64_t) -> bool {
65 uint64_t actual_written = total_bytes_written + written;
66 int new_permille = (actual_written * 1000) / file_size;
67 if (new_permille != permille && actual_written < file_size) {
68 if (progress && !progress(actual_written, file_size)) {
69 return false;
70 }
71 permille = new_permille;
72 }
73 return true;
74 };
75
76 std::unique_ptr<SplitFiemap> out(new SplitFiemap());
77 out->creating_ = true;
78 out->list_file_ = file_path;
79
80 // Create the split files.
81 uint64_t remaining_bytes = file_size;
82 while (remaining_bytes) {
83 if (out->files_.size() >= kMaxFilePieces) {
84 LOG(ERROR) << "Requested size " << file_size << " created too many split files";
85 return nullptr;
86 }
87 std::string chunk_path =
88 android::base::StringPrintf("%s.%04d", file_path.c_str(), (int)out->files_.size());
89 uint64_t chunk_size = std::min(max_piece_size, remaining_bytes);
90 auto writer = FiemapWriter::Open(chunk_path, chunk_size, true, on_progress);
91 if (!writer) {
92 return nullptr;
93 }
94
95 // To make sure the alignment doesn't create too much inconsistency, we
96 // account the *actual* size, not the requested size.
97 total_bytes_written += writer->size();
98 remaining_bytes -= writer->size();
99
100 out->AddFile(std::move(writer));
101 }
102
103 // Create the split file list.
104 unique_fd fd(open(out->list_file_.c_str(), O_CREAT | O_WRONLY | O_CLOEXEC, 0660));
105 if (fd < 0) {
106 PLOG(ERROR) << "Failed to open " << file_path;
107 return nullptr;
108 }
109
110 for (const auto& writer : out->files_) {
111 std::string line = android::base::Basename(writer->file_path()) + "\n";
112 if (!android::base::WriteFully(fd, line.data(), line.size())) {
113 PLOG(ERROR) << "Write failed " << file_path;
114 return nullptr;
115 }
116 }
117
118 // Unset this bit, so we don't unlink on destruction.
119 out->creating_ = false;
120 return out;
121 }
122
Open(const std::string & file_path)123 std::unique_ptr<SplitFiemap> SplitFiemap::Open(const std::string& file_path) {
124 std::vector<std::string> files;
125 if (!GetSplitFileList(file_path, &files)) {
126 return nullptr;
127 }
128
129 std::unique_ptr<SplitFiemap> out(new SplitFiemap());
130 out->list_file_ = file_path;
131
132 for (const auto& file : files) {
133 auto writer = FiemapWriter::Open(file, 0, false);
134 if (!writer) {
135 // Error was logged in Open().
136 return nullptr;
137 }
138 out->AddFile(std::move(writer));
139 }
140 return out;
141 }
142
GetSplitFileList(const std::string & file_path,std::vector<std::string> * list)143 bool SplitFiemap::GetSplitFileList(const std::string& file_path, std::vector<std::string>* list) {
144 // This is not the most efficient thing, but it is simple and recovering
145 // the fiemap/fibmap is much more expensive.
146 std::string contents;
147 if (!android::base::ReadFileToString(file_path, &contents, true)) {
148 PLOG(ERROR) << "Error reading file: " << file_path;
149 return false;
150 }
151
152 std::vector<std::string> names = android::base::Split(contents, "\n");
153 std::string dir = android::base::Dirname(file_path);
154 for (const auto& name : names) {
155 if (!name.empty()) {
156 list->emplace_back(dir + "/" + name);
157 }
158 }
159 return true;
160 }
161
RemoveSplitFiles(const std::string & file_path,std::string * message)162 bool SplitFiemap::RemoveSplitFiles(const std::string& file_path, std::string* message) {
163 // Early exit if this does not exist, and do not report an error.
164 if (access(file_path.c_str(), F_OK) && errno == ENOENT) {
165 return true;
166 }
167
168 bool ok = true;
169 std::vector<std::string> files;
170 if (GetSplitFileList(file_path, &files)) {
171 for (const auto& file : files) {
172 ok &= android::base::RemoveFileIfExists(file, message);
173 }
174 }
175 ok &= android::base::RemoveFileIfExists(file_path, message);
176 return ok;
177 }
178
HasPinnedExtents() const179 bool SplitFiemap::HasPinnedExtents() const {
180 for (const auto& file : files_) {
181 if (!FiemapWriter::HasPinnedExtents(file->file_path())) {
182 return false;
183 }
184 }
185 return true;
186 }
187
extents()188 const std::vector<struct fiemap_extent>& SplitFiemap::extents() {
189 if (extents_.empty()) {
190 for (const auto& file : files_) {
191 const auto& extents = file->extents();
192 extents_.insert(extents_.end(), extents.begin(), extents.end());
193 }
194 }
195 return extents_;
196 }
197
Write(const void * data,uint64_t bytes)198 bool SplitFiemap::Write(const void* data, uint64_t bytes) {
199 // Open the current file.
200 FiemapWriter* file = files_[cursor_index_].get();
201
202 const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(data);
203 uint64_t bytes_remaining = bytes;
204 while (bytes_remaining) {
205 // How many bytes can we write into the current file?
206 uint64_t file_bytes_left = file->size() - cursor_file_pos_;
207 if (!file_bytes_left) {
208 if (cursor_index_ == files_.size() - 1) {
209 LOG(ERROR) << "write past end of file requested";
210 return false;
211 }
212
213 // No space left in the current file, but we have more files to
214 // use, so prep the next one.
215 cursor_fd_ = {};
216 cursor_file_pos_ = 0;
217 file = files_[++cursor_index_].get();
218 file_bytes_left = file->size();
219 }
220
221 // Open the current file if it's not open.
222 if (cursor_fd_ < 0) {
223 cursor_fd_.reset(open(file->file_path().c_str(), O_CLOEXEC | O_WRONLY));
224 if (cursor_fd_ < 0) {
225 PLOG(ERROR) << "open failed: " << file->file_path();
226 return false;
227 }
228 CHECK(cursor_file_pos_ == 0);
229 }
230
231 if (!FiemapWriter::HasPinnedExtents(file->file_path())) {
232 LOG(ERROR) << "file is no longer pinned: " << file->file_path();
233 return false;
234 }
235
236 uint64_t bytes_to_write = std::min(file_bytes_left, bytes_remaining);
237 if (!android::base::WriteFully(cursor_fd_, data_ptr, bytes_to_write)) {
238 PLOG(ERROR) << "write failed: " << file->file_path();
239 return false;
240 }
241 data_ptr += bytes_to_write;
242 bytes_remaining -= bytes_to_write;
243 cursor_file_pos_ += bytes_to_write;
244 }
245
246 // If we've reached the end of the current file, close it for sanity.
247 if (cursor_file_pos_ == file->size()) {
248 cursor_fd_ = {};
249 }
250 return true;
251 }
252
Flush()253 bool SplitFiemap::Flush() {
254 for (const auto& file : files_) {
255 unique_fd fd(open(file->file_path().c_str(), O_RDONLY | O_CLOEXEC));
256 if (fd < 0) {
257 PLOG(ERROR) << "open failed: " << file->file_path();
258 return false;
259 }
260 if (fsync(fd)) {
261 PLOG(ERROR) << "fsync failed: " << file->file_path();
262 return false;
263 }
264 }
265 return true;
266 }
267
~SplitFiemap()268 SplitFiemap::~SplitFiemap() {
269 if (!creating_) {
270 return;
271 }
272
273 // We failed to finish creating, so unlink everything.
274 unlink(list_file_.c_str());
275 for (auto&& file : files_) {
276 std::string path = file->file_path();
277 file = nullptr;
278
279 unlink(path.c_str());
280 }
281 }
282
AddFile(FiemapUniquePtr && file)283 void SplitFiemap::AddFile(FiemapUniquePtr&& file) {
284 total_size_ += file->size();
285 files_.emplace_back(std::move(file));
286 }
287
block_size() const288 uint32_t SplitFiemap::block_size() const {
289 return files_[0]->block_size();
290 }
291
bdev_path() const292 const std::string& SplitFiemap::bdev_path() const {
293 return files_[0]->bdev_path();
294 }
295
296 } // namespace fiemap_writer
297 } // namespace android
298