| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * 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. |
| */ |
| |
| #include <libfiemap/split_fiemap_writer.h> |
| |
| #include <fcntl.h> |
| #include <stdint.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/stringprintf.h> |
| #include <android-base/strings.h> |
| #include <android-base/unique_fd.h> |
| |
| #include "utility.h" |
| |
| namespace android { |
| namespace fiemap { |
| |
| using android::base::unique_fd; |
| |
| // We use a four-digit suffix at the end of filenames. |
| static const size_t kMaxFilePieces = 500; |
| |
| std::unique_ptr<SplitFiemap> SplitFiemap::Create(const std::string& file_path, uint64_t file_size, |
| uint64_t max_piece_size, |
| ProgressCallback progress) { |
| std::unique_ptr<SplitFiemap> ret; |
| if (!Create(file_path, file_size, max_piece_size, &ret, progress).is_ok()) { |
| return nullptr; |
| } |
| return ret; |
| } |
| |
| FiemapStatus SplitFiemap::Create(const std::string& file_path, uint64_t file_size, |
| uint64_t max_piece_size, std::unique_ptr<SplitFiemap>* out_val, |
| ProgressCallback progress) { |
| out_val->reset(); |
| |
| if (!file_size) { |
| LOG(ERROR) << "Cannot create a fiemap for a 0-length file: " << file_path; |
| return FiemapStatus::Error(); |
| } |
| |
| if (!max_piece_size) { |
| auto status = DetermineMaximumFileSize(file_path, &max_piece_size); |
| if (!status.is_ok()) { |
| LOG(ERROR) << "Could not determine maximum file size for " << file_path; |
| return status; |
| } |
| } |
| |
| // Remove any existing file. |
| RemoveSplitFiles(file_path); |
| |
| // Call |progress| only when the total percentage would significantly change. |
| int permille = -1; |
| uint64_t total_bytes_written = 0; |
| auto on_progress = [&](uint64_t written, uint64_t) -> bool { |
| uint64_t actual_written = total_bytes_written + written; |
| int new_permille = (actual_written * 1000) / file_size; |
| if (new_permille != permille && actual_written < file_size) { |
| if (progress && !progress(actual_written, file_size)) { |
| return false; |
| } |
| permille = new_permille; |
| } |
| return true; |
| }; |
| std::unique_ptr<SplitFiemap> out(new SplitFiemap()); |
| out->creating_ = true; |
| out->list_file_ = file_path; |
| |
| // Create the split files. |
| uint64_t remaining_bytes = file_size; |
| while (remaining_bytes) { |
| if (out->files_.size() >= kMaxFilePieces) { |
| LOG(ERROR) << "Requested size " << file_size << " created too many split files"; |
| out.reset(); |
| return FiemapStatus::Error(); |
| } |
| std::string chunk_path = |
| android::base::StringPrintf("%s.%04d", file_path.c_str(), (int)out->files_.size()); |
| uint64_t chunk_size = std::min(max_piece_size, remaining_bytes); |
| FiemapUniquePtr writer; |
| auto status = FiemapWriter::Open(chunk_path, chunk_size, &writer, true, on_progress); |
| if (!status.is_ok()) { |
| out.reset(); |
| return status; |
| } |
| |
| // To make sure the alignment doesn't create too much inconsistency, we |
| // account the *actual* size, not the requested size. |
| total_bytes_written += writer->size(); |
| |
| // writer->size() is block size aligned and could be bigger than remaining_bytes |
| // If remaining_bytes is bigger, set remaining_bytes to 0 to avoid underflow error. |
| remaining_bytes = remaining_bytes > writer->size() ? (remaining_bytes - writer->size()) : 0; |
| |
| out->AddFile(std::move(writer)); |
| } |
| |
| // Create the split file list. |
| unique_fd fd(open(out->list_file_.c_str(), O_CREAT | O_WRONLY | O_CLOEXEC, 0660)); |
| if (fd < 0) { |
| PLOG(ERROR) << "Failed to open " << file_path; |
| out.reset(); |
| return FiemapStatus::FromErrno(errno); |
| } |
| |
| for (const auto& writer : out->files_) { |
| std::string line = android::base::Basename(writer->file_path()) + "\n"; |
| if (!android::base::WriteFully(fd, line.data(), line.size())) { |
| PLOG(ERROR) << "Write failed " << file_path; |
| out.reset(); |
| return FiemapStatus::FromErrno(errno); |
| } |
| } |
| fsync(fd.get()); |
| |
| // Unset this bit, so we don't unlink on destruction. |
| out->creating_ = false; |
| *out_val = std::move(out); |
| return FiemapStatus::Ok(); |
| } |
| |
| std::unique_ptr<SplitFiemap> SplitFiemap::Open(const std::string& file_path) { |
| std::vector<std::string> files; |
| if (!GetSplitFileList(file_path, &files)) { |
| return nullptr; |
| } |
| |
| std::unique_ptr<SplitFiemap> out(new SplitFiemap()); |
| out->list_file_ = file_path; |
| |
| for (const auto& file : files) { |
| auto writer = FiemapWriter::Open(file, 0, false); |
| if (!writer) { |
| // Error was logged in Open(). |
| return nullptr; |
| } |
| out->AddFile(std::move(writer)); |
| } |
| return out; |
| } |
| |
| bool SplitFiemap::GetSplitFileList(const std::string& file_path, std::vector<std::string>* list) { |
| // This is not the most efficient thing, but it is simple and recovering |
| // the fiemap/fibmap is much more expensive. |
| std::string contents; |
| if (!android::base::ReadFileToString(file_path, &contents, true)) { |
| PLOG(ERROR) << "Error reading file: " << file_path; |
| return false; |
| } |
| |
| std::vector<std::string> names = android::base::Split(contents, "\n"); |
| std::string dir = android::base::Dirname(file_path); |
| for (const auto& name : names) { |
| if (!name.empty()) { |
| list->emplace_back(dir + "/" + name); |
| } |
| } |
| return true; |
| } |
| |
| bool SplitFiemap::RemoveSplitFiles(const std::string& file_path, std::string* message) { |
| // Early exit if this does not exist, and do not report an error. |
| if (access(file_path.c_str(), F_OK) && errno == ENOENT) { |
| return true; |
| } |
| |
| bool ok = true; |
| std::vector<std::string> files; |
| if (GetSplitFileList(file_path, &files)) { |
| for (const auto& file : files) { |
| if (access(file.c_str(), F_OK) != 0 && (errno == ENOENT || errno == ENAMETOOLONG)) { |
| continue; |
| } |
| ok &= android::base::RemoveFileIfExists(file, message); |
| } |
| } |
| ok &= android::base::RemoveFileIfExists(file_path, message); |
| return ok; |
| } |
| |
| bool SplitFiemap::HasPinnedExtents() const { |
| for (const auto& file : files_) { |
| if (!FiemapWriter::HasPinnedExtents(file->file_path())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| const std::vector<struct fiemap_extent>& SplitFiemap::extents() { |
| if (extents_.empty()) { |
| for (const auto& file : files_) { |
| const auto& extents = file->extents(); |
| extents_.insert(extents_.end(), extents.begin(), extents.end()); |
| } |
| } |
| return extents_; |
| } |
| |
| bool SplitFiemap::Write(const void* data, uint64_t bytes) { |
| // Open the current file. |
| FiemapWriter* file = files_[cursor_index_].get(); |
| |
| const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(data); |
| uint64_t bytes_remaining = bytes; |
| while (bytes_remaining) { |
| // How many bytes can we write into the current file? |
| uint64_t file_bytes_left = file->size() - cursor_file_pos_; |
| if (!file_bytes_left) { |
| if (cursor_index_ == files_.size() - 1) { |
| LOG(ERROR) << "write past end of file requested"; |
| return false; |
| } |
| |
| // No space left in the current file, but we have more files to |
| // use, so prep the next one. |
| cursor_fd_ = {}; |
| cursor_file_pos_ = 0; |
| file = files_[++cursor_index_].get(); |
| file_bytes_left = file->size(); |
| } |
| |
| // Open the current file if it's not open. |
| if (cursor_fd_ < 0) { |
| cursor_fd_.reset(open(file->file_path().c_str(), O_CLOEXEC | O_WRONLY)); |
| if (cursor_fd_ < 0) { |
| PLOG(ERROR) << "open failed: " << file->file_path(); |
| return false; |
| } |
| CHECK(cursor_file_pos_ == 0); |
| } |
| |
| if (!FiemapWriter::HasPinnedExtents(file->file_path())) { |
| LOG(ERROR) << "file is no longer pinned: " << file->file_path(); |
| return false; |
| } |
| |
| uint64_t bytes_to_write = std::min(file_bytes_left, bytes_remaining); |
| if (!android::base::WriteFully(cursor_fd_, data_ptr, bytes_to_write)) { |
| PLOG(ERROR) << "write failed: " << file->file_path(); |
| return false; |
| } |
| data_ptr += bytes_to_write; |
| bytes_remaining -= bytes_to_write; |
| cursor_file_pos_ += bytes_to_write; |
| } |
| |
| // If we've reached the end of the current file, close it. |
| if (cursor_file_pos_ == file->size()) { |
| cursor_fd_ = {}; |
| } |
| return true; |
| } |
| |
| bool SplitFiemap::Flush() { |
| for (const auto& file : files_) { |
| unique_fd fd(open(file->file_path().c_str(), O_RDONLY | O_CLOEXEC)); |
| if (fd < 0) { |
| PLOG(ERROR) << "open failed: " << file->file_path(); |
| return false; |
| } |
| if (fsync(fd)) { |
| PLOG(ERROR) << "fsync failed: " << file->file_path(); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| SplitFiemap::~SplitFiemap() { |
| if (!creating_) { |
| return; |
| } |
| |
| // We failed to finish creating, so unlink everything. |
| unlink(list_file_.c_str()); |
| for (auto&& file : files_) { |
| std::string path = file->file_path(); |
| file = nullptr; |
| |
| unlink(path.c_str()); |
| } |
| } |
| |
| void SplitFiemap::AddFile(FiemapUniquePtr&& file) { |
| total_size_ += file->size(); |
| files_.emplace_back(std::move(file)); |
| } |
| |
| uint32_t SplitFiemap::block_size() const { |
| return files_[0]->block_size(); |
| } |
| |
| const std::string& SplitFiemap::bdev_path() const { |
| return files_[0]->bdev_path(); |
| } |
| |
| } // namespace fiemap |
| } // namespace android |