update_engine: Support full update on MTD devices
This CL fleshes out the MTD and UBI FileDescriptor subclasses and
modifies the utility functions that deal with device name and partition
number to support NAND scheme.
BUG=brillo:45
BUG=brillo:43
TEST=unittest on rambi
TEST=cros flash to storm_nand, reboot, make sure rootdev -s says something
different. Then repeat and make sure that another device is reported.
TEST=If the above was done with verified-rootfs, repeat with
non-verified image.
Change-Id: I31b9a00642f9a7ff67ea51a4cf3dc31b86095e98
Reviewed-on: https://chromium-review.googlesource.com/257192
Reviewed-by: Nam Nguyen <namnguyen@chromium.org>
Commit-Queue: Nam Nguyen <namnguyen@chromium.org>
Tested-by: Nam Nguyen <namnguyen@chromium.org>
diff --git a/mtd_file_descriptor.cc b/mtd_file_descriptor.cc
index 1b33d05..2e0d329 100644
--- a/mtd_file_descriptor.cc
+++ b/mtd_file_descriptor.cc
@@ -10,24 +10,32 @@
#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/subprocess.h>
#include "update_engine/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::Subprocess;
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 char* path) {
+std::unique_ptr<UbiVolumeInfo> GetUbiVolumeInfo(const string& path) {
base::FilePath device_node(path);
base::FilePath ubi_name(device_node.BaseName());
@@ -39,23 +47,33 @@
// Obtain volume info from sysfs.
std::string s_reserved_ebs;
if (!ReadFile(sysfs_node + kReservedEbs, &s_reserved_ebs)) {
+ LOG(ERROR) << "Cannot read " << sysfs_node + kReservedEbs;
return ret;
}
std::string s_eb_size;
if (!ReadFile(sysfs_node + kUsableEbSize, &s_eb_size)) {
+ LOG(ERROR) << "Cannot read " << sysfs_node + kUsableEbSize;
return ret;
}
- size_t reserved_ebs, eb_size;
- if (!base::StringToSizeT(s_reserved_ebs, &reserved_ebs)) {
+ 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::StringToSizeT(s_eb_size, &eb_size)) {
+ 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->size = reserved_ebs * eb_size;
+ ret->reserved_ebs = reserved_ebs;
+ ret->eraseblock_size = eb_size;
return ret;
}
@@ -74,14 +92,22 @@
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_RDWR) != O_RDWR);
+ 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_RDONLY) {
- read_ctx_.reset(mtd_read_descriptor(fd_, path));
- } else if (flags & O_WRONLY) {
+ 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_) {
@@ -105,50 +131,66 @@
ssize_t MtdFileDescriptor::Write(const void* buf, size_t count) {
CHECK(write_ctx_);
- return mtd_write_data(write_ctx_.get(), static_cast<const char*>(buf), count);
+ 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) {
- CHECK(read_ctx_);
+ if (write_ctx_) {
+ // Ignore seek in write mode.
+ return nr_written_;
+ }
return EintrSafeFileDescriptor::Seek(offset, whence);
}
-void MtdFileDescriptor::Reset() {
- EintrSafeFileDescriptor::Reset();
+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(StartsWithASCII(ubi_name.MaybeAsASCII(), "ubi", true));
+
return static_cast<bool>(GetUbiVolumeInfo(path));
}
-std::unique_ptr<UbiVolumeInfo> UbiFileDescriptor::CreateWriteContext(
- const char* path) {
- std::unique_ptr<UbiVolumeInfo> info = GetUbiVolumeInfo(path);
- uint64_t volume_size;
- if (info && (ioctl(fd_, UBI_IOCVOLUP, &volume_size) != 0 ||
- volume_size != info->size)) {
- info.reset();
- }
- return info;
-}
-
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_RDWR) != O_RDWR);
+ TEST_AND_RETURN_FALSE((flags & O_ACCMODE) != O_RDWR);
TEST_AND_RETURN_FALSE(
EintrSafeFileDescriptor::Open(path, flags | O_CLOEXEC, mode));
- if (flags & O_RDONLY) {
- read_ctx_ = GetUbiVolumeInfo(path);
- } else if (flags & O_WRONLY) {
- write_ctx_ = CreateWriteContext(path);
- }
+ usable_eb_blocks_ = info->reserved_ebs;
+ eraseblock_size_ = info->eraseblock_size;
+ volume_size_ = usable_eb_blocks_ * eraseblock_size_;
- if (!read_ctx_ && !write_ctx_) {
- Close();
- return false;
+ 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;
@@ -161,24 +203,51 @@
}
ssize_t UbiFileDescriptor::Read(void* buf, size_t count) {
- CHECK(read_ctx_);
+ CHECK(mode_ == kReadOnly);
return EintrSafeFileDescriptor::Read(buf, count);
}
ssize_t UbiFileDescriptor::Write(const void* buf, size_t count) {
- CHECK(write_ctx_);
- return EintrSafeFileDescriptor::Write(buf, 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) {
- CHECK(read_ctx_);
+ if (mode_ == kWriteOnly) {
+ // Ignore seek in write mode.
+ return nr_written_;
+ }
return EintrSafeFileDescriptor::Seek(offset, whence);
}
-void UbiFileDescriptor::Reset() {
- EintrSafeFileDescriptor::Reset();
- read_ctx_.reset();
- write_ctx_.reset();
+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