• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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