blob: e96285dba42d11c3826abdc7bfe143c4e17f6c15 [file] [log] [blame]
// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "image-burner/image_burner_impl.h"
#include <ctype.h>
#include <regex.h>
#include <stdint.h>
#include <memory>
#include <vector>
#include <base/files/file_path.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <brillo/cryptohome.h>
namespace imageburn {
namespace {
const int kBurningBlockSize = 4 * 1024; // 4 KiB
const char* kFilePathPatterns[] = {"^/dev/sd[a-z]+$", "^/dev/mmcblk[0-9]+$"};
const int kFilePathPatternCount = 2;
const char kMediaDirectory[] = "/media";
const char kUserRootDirectory[] = "/home/chronos";
bool IsUserOrUserHash(const std::string& path) {
if (path == "user")
return true;
if (base::StartsWith(path, "u-", base::CompareCase::INSENSITIVE_ASCII) &&
brillo::cryptohome::home::IsSanitizedUserName(path.substr(2))) {
return true;
}
return false;
}
} // namespace
BurnerImpl::BurnerImpl(FileSystemWriter* writer,
FileSystemReader* reader,
SignalSender* signal_sender,
PathGetter* path_getter)
: writer_(writer),
reader_(reader),
signal_sender_(signal_sender),
path_getter_(path_getter),
data_block_size_(kBurningBlockSize) {}
ErrorCode BurnerImpl::BurnImage(const char* from_path, const char* to_path) {
ErrorCode err = IMAGEBURN_OK;
if (!writer_ || !reader_ || !signal_sender_ || !path_getter_)
err = IMAGEBURN_ERROR_BURNER_NOT_INITIALIZED;
if (!err)
ValidateTargetPath(to_path, &err);
std::string resolved_from_path;
if (!err)
ValidateSourcePath(from_path, &resolved_from_path, &err);
if (!err)
DoBurn(resolved_from_path.c_str(), to_path, &err);
// TODO(tbarzic) send error specific error message.
signal_sender_->SendFinishedSignal(to_path, !err,
(!err ? "" : "Error during burn"));
return err;
}
void BurnerImpl::InitSignalSender(SignalSender* signal_sender) {
signal_sender_ = signal_sender;
}
bool BurnerImpl::ValidateTargetPath(const char* path, ErrorCode* error) {
if (!path) {
*error = IMAGEBURN_ERROR_NULL_TARGET_PATH;
LOG(ERROR) << "Target path set to NULL.";
return false;
}
// Check if path conforms to a valid regex.
bool is_pattern_valid = false;
for (int i = 0; i < kFilePathPatternCount; ++i) {
regex_t re;
int status = regcomp(&re, kFilePathPatterns[i], REG_EXTENDED);
DCHECK_EQ(0, status);
status = regexec(&re, path, 0, NULL, 0);
regfree(&re);
if (status == 0) {
is_pattern_valid = true;
break;
}
}
if (!is_pattern_valid) {
*error = IMAGEBURN_ERROR_INVALID_TARGET_PATH;
LOG(ERROR) << "Target path does not have a valid file path pattern.";
return false;
}
std::string root_path;
// This will return roots file path, so we can compare target path, which
// should also be devices file path, to it.
bool got_root_path = path_getter_->GetRootPath(&root_path);
if (!got_root_path ||
strncmp(root_path.c_str(), path, root_path.length()) == 0) {
*error = IMAGEBURN_ERROR_TARGET_PATH_ON_ROOT;
LOG(ERROR) << "Target path is on root device.";
return false;
}
return true;
}
bool BurnerImpl::ValidateSourcePath(const char* path,
std::string* resolved_path,
ErrorCode* error) {
resolved_path->clear();
if (!path) {
*error = IMAGEBURN_ERROR_NULL_SOURCE_PATH;
LOG(ERROR) << "Source path set to NULL.";
return false;
}
if (!path_getter_->GetRealPath(path, resolved_path)) {
*error = IMAGEBURN_ERROR_SOURCE_REAL_PATH_NOT_DETERMINED;
return false;
}
if (!IsSourcePathAllowed(*resolved_path)) {
*error = IMAGEBURN_ERROR_SOURCE_PATH_NOT_ALLOWED;
LOG(ERROR) << "Source path is not from one of the allowed locations.";
return false;
}
return true;
}
bool BurnerImpl::DoBurn(const char* from_path,
const char* to_path,
ErrorCode* error) {
LOG(INFO) << "Burning " << from_path << " to " << to_path;
*error = IMAGEBURN_OK;
bool success = reader_->Open(from_path);
if (success) {
if (!(success = writer_->Open(to_path)))
*error = IMAGEBURN_ERROR_CANNOT_OPEN_TARGET;
} else {
*error = IMAGEBURN_ERROR_CANNOT_OPEN_SOURCE;
}
if (success) {
std::unique_ptr<char[]> buffer(new char[data_block_size_]);
int64_t total_burnt = 0;
int64_t image_size = reader_->GetSize();
int len = 0;
while ((len = reader_->Read(buffer.get(), data_block_size_)) > 0) {
if (writer_->Write(buffer.get(), len) == len) {
total_burnt += static_cast<int64_t>(len);
signal_sender_->SendProgressSignal(total_burnt, image_size, to_path);
} else {
success = false;
*error = IMAGEBURN_ERROR_FAILED_WRITING_TO_TARGET;
break;
}
}
if (len < 0) {
success = false;
*error = IMAGEBURN_ERROR_FAILED_READING_SOURCE;
}
}
if (!writer_->Close() && success) {
// We set this only when there is no previous error.
success = false;
*error = IMAGEBURN_ERROR_CANNOT_CLOSE_TARGET;
}
if (!reader_->Close() && success) {
// We set this only when there is no previous error.
success = false;
*error = IMAGEBURN_ERROR_CANNOT_CLOSE_SOURCE;
}
return success;
}
// Derived from cros-disks::ArchiveManager::CanMount().
bool BurnerImpl::IsSourcePathAllowed(const std::string& source_path) const {
// TODO(benchan): Revisit the need to allow .../Downloads once the MyFiles
// migration (chromium:873539) is complete.
//
// The following paths are allowed:
// /home/chronos/user/Downloads/...<file>
// /home/chronos/user/MyFiles/...<file>
// /home/chronos/user/GCache/...<file>
// /home/chronos/u-<user-id>/Downloads/...<file>
// /home/chronos/u-<user-id>/MyFiles/...<file>
// /home/chronos/u-<user-id>/GCache/...<file>
// /media/<dir>/<dir>/...<file>
//
base::FilePath file_path(source_path);
if (base::FilePath(kUserRootDirectory).IsParent(file_path)) {
std::vector<std::string> components;
file_path.StripTrailingSeparators().GetComponents(&components);
// The file path of an image file under a user's Downloads, MyFiles or
// GCache directory path is split into the following components:
// '/', 'home', 'chronos', 'user', 'Downloads', ..., <file>
// '/', 'home', 'chronos', 'user', 'MyFiles', ..., <file>
// '/', 'home', 'chronos', 'user', 'GCache', ..., <file>
// '/', 'home', 'chronos', 'u-<userid>', 'Downloads', ..., <file>
// '/', 'home', 'chronos', 'u-<userid>', 'MyFiles', ..., <file>
// '/', 'home', 'chronos', 'u-<userid>', 'GCache', ..., <file>
if (components.size() > 5 && IsUserOrUserHash(components[3]) &&
(components[4] == "Downloads" || components[4] == "MyFiles" ||
components[4] == "GCache")) {
return true;
}
}
if (base::FilePath(kMediaDirectory).IsParent(file_path)) {
std::vector<std::string> components;
file_path.StripTrailingSeparators().GetComponents(&components);
// A mount directory is always created under /media/<sub type>/<mount dir>,
// so the file path of an image file under a mount directory is split
// into more than 4 components:
// '/', 'media', 'removable', 'usb', ..., <file>
if (components.size() > 4)
return true;
}
return false;
}
} // namespace imageburn