// // Copyright (C) 2014 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 "update_engine/payload_consumer/mtd_file_descriptor.h" #include <fcntl.h> #include <mtd/ubi-user.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <sys/types.h> #include <memory> #include <string> #include <base/files/file_path.h> #include <base/strings/string_number_conversions.h> #include <base/strings/string_util.h> #include <base/strings/stringprintf.h> #include "update_engine/common/subprocess.h" #include "update_engine/common/utils.h" using std::string; namespace { static const char kSysfsClassUbi[] = "/sys/class/ubi/"; static const char kUsableEbSize[] = "/usable_eb_size"; static const char kReservedEbs[] = "/reserved_ebs"; using chromeos_update_engine::UbiVolumeInfo; using chromeos_update_engine::utils::ReadFile; // Return a UbiVolumeInfo pointer if |path| is a UBI volume. Otherwise, return // a null unique pointer. std::unique_ptr<UbiVolumeInfo> GetUbiVolumeInfo(const string& path) { base::FilePath device_node(path); base::FilePath ubi_name(device_node.BaseName()); string sysfs_node(kSysfsClassUbi); sysfs_node.append(ubi_name.MaybeAsASCII()); std::unique_ptr<UbiVolumeInfo> ret; // Obtain volume info from sysfs. string s_reserved_ebs; if (!ReadFile(sysfs_node + kReservedEbs, &s_reserved_ebs)) { LOG(ERROR) << "Cannot read " << sysfs_node + kReservedEbs; return ret; } string s_eb_size; if (!ReadFile(sysfs_node + kUsableEbSize, &s_eb_size)) { LOG(ERROR) << "Cannot read " << sysfs_node + kUsableEbSize; return ret; } base::TrimWhitespaceASCII(s_reserved_ebs, base::TRIM_TRAILING, &s_reserved_ebs); base::TrimWhitespaceASCII(s_eb_size, base::TRIM_TRAILING, &s_eb_size); uint64_t reserved_ebs, eb_size; if (!base::StringToUint64(s_reserved_ebs, &reserved_ebs)) { LOG(ERROR) << "Cannot parse reserved_ebs: " << s_reserved_ebs; return ret; } if (!base::StringToUint64(s_eb_size, &eb_size)) { LOG(ERROR) << "Cannot parse usable_eb_size: " << s_eb_size; return ret; } ret.reset(new UbiVolumeInfo); ret->reserved_ebs = reserved_ebs; ret->eraseblock_size = eb_size; return ret; } } // namespace namespace chromeos_update_engine { MtdFileDescriptor::MtdFileDescriptor() : read_ctx_(nullptr, &mtd_read_close), write_ctx_(nullptr, &mtd_write_close) {} bool MtdFileDescriptor::IsMtd(const char* path) { uint64_t size; return mtd_node_info(path, &size, nullptr, nullptr) == 0; } bool MtdFileDescriptor::Open(const char* path, int flags, mode_t mode) { // This File Descriptor does not support read and write. TEST_AND_RETURN_FALSE((flags & O_ACCMODE) != O_RDWR); // But we need to open the underlying file descriptor in O_RDWR mode because // during write, we need to read back to verify the write actually sticks or // we have to skip the block. That job is done by mtdutils library. if ((flags & O_ACCMODE) == O_WRONLY) { flags &= ~O_ACCMODE; flags |= O_RDWR; } TEST_AND_RETURN_FALSE( EintrSafeFileDescriptor::Open(path, flags | O_CLOEXEC, mode)); if ((flags & O_ACCMODE) == O_RDWR) { write_ctx_.reset(mtd_write_descriptor(fd_, path)); nr_written_ = 0; } else { read_ctx_.reset(mtd_read_descriptor(fd_, path)); } if (!read_ctx_ && !write_ctx_) { Close(); return false; } return true; } bool MtdFileDescriptor::Open(const char* path, int flags) { mode_t cur = umask(022); umask(cur); return Open(path, flags, 0777 & ~cur); } ssize_t MtdFileDescriptor::Read(void* buf, size_t count) { CHECK(read_ctx_); return mtd_read_data(read_ctx_.get(), static_cast<char*>(buf), count); } ssize_t MtdFileDescriptor::Write(const void* buf, size_t count) { CHECK(write_ctx_); ssize_t result = mtd_write_data(write_ctx_.get(), static_cast<const char*>(buf), count); if (result > 0) { nr_written_ += result; } return result; } off64_t MtdFileDescriptor::Seek(off64_t offset, int whence) { if (write_ctx_) { // Ignore seek in write mode. return nr_written_; } return EintrSafeFileDescriptor::Seek(offset, whence); } bool MtdFileDescriptor::Close() { read_ctx_.reset(); write_ctx_.reset(); return EintrSafeFileDescriptor::Close(); } bool UbiFileDescriptor::IsUbi(const char* path) { base::FilePath device_node(path); base::FilePath ubi_name(device_node.BaseName()); TEST_AND_RETURN_FALSE(base::StartsWith(ubi_name.MaybeAsASCII(), "ubi", base::CompareCase::SENSITIVE)); return static_cast<bool>(GetUbiVolumeInfo(path)); } bool UbiFileDescriptor::Open(const char* path, int flags, mode_t mode) { std::unique_ptr<UbiVolumeInfo> info = GetUbiVolumeInfo(path); if (!info) { return false; } // This File Descriptor does not support read and write. TEST_AND_RETURN_FALSE((flags & O_ACCMODE) != O_RDWR); TEST_AND_RETURN_FALSE( EintrSafeFileDescriptor::Open(path, flags | O_CLOEXEC, mode)); usable_eb_blocks_ = info->reserved_ebs; eraseblock_size_ = info->eraseblock_size; volume_size_ = usable_eb_blocks_ * eraseblock_size_; if ((flags & O_ACCMODE) == O_WRONLY) { // It's best to use volume update ioctl so that UBI layer will mark the // volume as being updated, and only clear that mark if the update is // successful. We will need to pad to the whole volume size at close. uint64_t vsize = volume_size_; if (ioctl(fd_, UBI_IOCVOLUP, &vsize) != 0) { PLOG(ERROR) << "Cannot issue volume update ioctl"; EintrSafeFileDescriptor::Close(); return false; } mode_ = kWriteOnly; nr_written_ = 0; } else { mode_ = kReadOnly; } return true; } bool UbiFileDescriptor::Open(const char* path, int flags) { mode_t cur = umask(022); umask(cur); return Open(path, flags, 0777 & ~cur); } ssize_t UbiFileDescriptor::Read(void* buf, size_t count) { CHECK(mode_ == kReadOnly); return EintrSafeFileDescriptor::Read(buf, count); } ssize_t UbiFileDescriptor::Write(const void* buf, size_t count) { CHECK(mode_ == kWriteOnly); ssize_t nr_chunk = EintrSafeFileDescriptor::Write(buf, count); if (nr_chunk >= 0) { nr_written_ += nr_chunk; } return nr_chunk; } off64_t UbiFileDescriptor::Seek(off64_t offset, int whence) { if (mode_ == kWriteOnly) { // Ignore seek in write mode. return nr_written_; } return EintrSafeFileDescriptor::Seek(offset, whence); } bool UbiFileDescriptor::Close() { bool pad_ok = true; if (IsOpen() && mode_ == kWriteOnly) { char buf[1024]; memset(buf, 0xFF, sizeof(buf)); while (nr_written_ < volume_size_) { // We have written less than the whole volume. In order for us to clear // the update marker, we need to fill the rest. It is recommended to fill // UBI writes with 0xFF. uint64_t to_write = volume_size_ - nr_written_; if (to_write > sizeof(buf)) { to_write = sizeof(buf); } ssize_t nr_chunk = EintrSafeFileDescriptor::Write(buf, to_write); if (nr_chunk < 0) { LOG(ERROR) << "Cannot 0xFF-pad before closing."; // There is an error, but we can't really do any meaningful thing here. pad_ok = false; break; } nr_written_ += nr_chunk; } } return EintrSafeFileDescriptor::Close() && pad_ok; } } // namespace chromeos_update_engine