You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
265 lines
7.9 KiB
265 lines
7.9 KiB
// |
|
// 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 <string> |
|
#include <sys/ioctl.h> |
|
#include <sys/stat.h> |
|
#include <sys/types.h> |
|
#include <vector> |
|
|
|
#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; |
|
using std::vector; |
|
|
|
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
|
|
|