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             ok &= android::base::RemoveFileIfExists(file, message);
200         }
201     }
202     ok &= android::base::RemoveFileIfExists(file_path, message);
203     return ok;
204 }
205 
HasPinnedExtents() const206 bool SplitFiemap::HasPinnedExtents() const {
207     for (const auto& file : files_) {
208         if (!FiemapWriter::HasPinnedExtents(file->file_path())) {
209             return false;
210         }
211     }
212     return true;
213 }
214 
extents()215 const std::vector<struct fiemap_extent>& SplitFiemap::extents() {
216     if (extents_.empty()) {
217         for (const auto& file : files_) {
218             const auto& extents = file->extents();
219             extents_.insert(extents_.end(), extents.begin(), extents.end());
220         }
221     }
222     return extents_;
223 }
224 
Write(const void * data,uint64_t bytes)225 bool SplitFiemap::Write(const void* data, uint64_t bytes) {
226     // Open the current file.
227     FiemapWriter* file = files_[cursor_index_].get();
228 
229     const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(data);
230     uint64_t bytes_remaining = bytes;
231     while (bytes_remaining) {
232         // How many bytes can we write into the current file?
233         uint64_t file_bytes_left = file->size() - cursor_file_pos_;
234         if (!file_bytes_left) {
235             if (cursor_index_ == files_.size() - 1) {
236                 LOG(ERROR) << "write past end of file requested";
237                 return false;
238             }
239 
240             // No space left in the current file, but we have more files to
241             // use, so prep the next one.
242             cursor_fd_ = {};
243             cursor_file_pos_ = 0;
244             file = files_[++cursor_index_].get();
245             file_bytes_left = file->size();
246         }
247 
248         // Open the current file if it's not open.
249         if (cursor_fd_ < 0) {
250             cursor_fd_.reset(open(file->file_path().c_str(), O_CLOEXEC | O_WRONLY));
251             if (cursor_fd_ < 0) {
252                 PLOG(ERROR) << "open failed: " << file->file_path();
253                 return false;
254             }
255             CHECK(cursor_file_pos_ == 0);
256         }
257 
258         if (!FiemapWriter::HasPinnedExtents(file->file_path())) {
259             LOG(ERROR) << "file is no longer pinned: " << file->file_path();
260             return false;
261         }
262 
263         uint64_t bytes_to_write = std::min(file_bytes_left, bytes_remaining);
264         if (!android::base::WriteFully(cursor_fd_, data_ptr, bytes_to_write)) {
265             PLOG(ERROR) << "write failed: " << file->file_path();
266             return false;
267         }
268         data_ptr += bytes_to_write;
269         bytes_remaining -= bytes_to_write;
270         cursor_file_pos_ += bytes_to_write;
271     }
272 
273     // If we've reached the end of the current file, close it.
274     if (cursor_file_pos_ == file->size()) {
275         cursor_fd_ = {};
276     }
277     return true;
278 }
279 
Flush()280 bool SplitFiemap::Flush() {
281     for (const auto& file : files_) {
282         unique_fd fd(open(file->file_path().c_str(), O_RDONLY | O_CLOEXEC));
283         if (fd < 0) {
284             PLOG(ERROR) << "open failed: " << file->file_path();
285             return false;
286         }
287         if (fsync(fd)) {
288             PLOG(ERROR) << "fsync failed: " << file->file_path();
289             return false;
290         }
291     }
292     return true;
293 }
294 
~SplitFiemap()295 SplitFiemap::~SplitFiemap() {
296     if (!creating_) {
297         return;
298     }
299 
300     // We failed to finish creating, so unlink everything.
301     unlink(list_file_.c_str());
302     for (auto&& file : files_) {
303         std::string path = file->file_path();
304         file = nullptr;
305 
306         unlink(path.c_str());
307     }
308 }
309 
AddFile(FiemapUniquePtr && file)310 void SplitFiemap::AddFile(FiemapUniquePtr&& file) {
311     total_size_ += file->size();
312     files_.emplace_back(std::move(file));
313 }
314 
block_size() const315 uint32_t SplitFiemap::block_size() const {
316     return files_[0]->block_size();
317 }
318 
bdev_path() const319 const std::string& SplitFiemap::bdev_path() const {
320     return files_[0]->bdev_path();
321 }
322 
323 }  // namespace fiemap
324 }  // namespace android
325