blob: 4d107715a418cb178828846f005061f8747d3df7 [file] [log] [blame]
// Copyright 2016 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 "arc/setup/arc_setup_util.h"
#include <fcntl.h>
#include <limits.h>
#include <linux/loop.h>
#include <linux/magic.h>
#include <linux/major.h>
#include <mntent.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <openssl/sha.h>
#include <pwd.h>
#include <selinux/restorecon.h>
#include <selinux/selinux.h>
#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <algorithm>
#include <fstream>
#include <list>
#include <set>
#include <utility>
#include <base/bind.h>
#include <base/callback_helpers.h>
#include <base/environment.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/json/json_reader.h>
#include <base/logging.h>
#include <base/process/launch.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/time/time.h>
#include <base/timer/elapsed_timer.h>
#include <base/values.h>
#include <brillo/file_utils.h>
#include <brillo/files/safe_fd.h>
#include <crypto/sha2.h>
using base::StringPiece;
namespace arc {
namespace {
// Version element prefix in packages.xml and packages_cache.xml files.
// Note: This constant has to be in sync with Android's arc-boot-type-detector.
constexpr char kElementVersion[] = "<version ";
// Fingerprint attribute prefix in packages.xml and packages_cache.xml files.
// Note: This constant has to be in sync with Android's arc-boot-type-detector.
constexpr char kAttributeFingerprint[] = " fingerprint=\"";
// Gets the loop device path for a loop device number.
base::FilePath GetLoopDevicePath(int32_t device) {
return base::FilePath(base::StringPrintf("/dev/loop%d", device));
}
// Immediately removes the loop device from the system.
void RemoveLoopDevice(int control_fd, int32_t device) {
if (ioctl(control_fd, LOOP_CTL_REMOVE, device) < 0)
PLOG(ERROR) << "Failed to free /dev/loop" << device;
}
// Disassociates the loop device from any file descriptor.
void DisassociateLoopDevice(int loop_fd,
const std::string& source,
const base::FilePath& device_path) {
if (ioctl(loop_fd, LOOP_CLR_FD) < 0) {
PLOG(ERROR) << "Failed to remove " << source << " from "
<< device_path.value();
}
}
// A callback function for SELinux restorecon.
PRINTF_FORMAT(2, 3)
int RestoreConLogCallback(int type, const char* fmt, ...) {
va_list ap;
std::string message = "restorecon: ";
va_start(ap, fmt);
message += base::StringPrintV(fmt, ap);
va_end(ap);
// This already has a line feed at the end, so trim it off to avoid
// empty lines in the log.
base::TrimString(message, "\r\n", &message);
if (type == SELINUX_INFO)
LOG(INFO) << message;
else
LOG(ERROR) << message;
return 0;
}
bool RestoreconInternal(const std::vector<base::FilePath>& paths,
bool is_recursive) {
union selinux_callback cb;
cb.func_log = RestoreConLogCallback;
selinux_set_callback(SELINUX_CB_LOG, cb);
const unsigned int base_flags =
(is_recursive ? SELINUX_RESTORECON_RECURSE : 0) |
SELINUX_RESTORECON_REALPATH;
bool success = true;
for (const auto& path : paths) {
unsigned int restorecon_flags = base_flags;
struct statfs fsinfo;
if (statfs(path.value().c_str(), &fsinfo) != 0) {
PLOG(WARNING) << "Failed to statfs for " << path.value();
// Continue anyway because restorecon should work even if it can't
// update digests.
} else if (fsinfo.f_type == TRACEFS_MAGIC ||
fsinfo.f_type == DEBUGFS_MAGIC) {
// tracefs and debugfs don't support xattrs, so restorecon can't store
// digests.
restorecon_flags |= SELINUX_RESTORECON_SKIP_DIGEST;
}
if (selinux_restorecon(path.value().c_str(), restorecon_flags) != 0) {
LOG(ERROR) << "Error in restorecon of " << path.value();
success = false;
}
}
return success;
}
// A callback function for GetPropertyFromFile.
bool FindProperty(const std::string& line_prefix_to_find,
std::string* out_prop,
const std::string& line) {
if (base::StartsWith(line, line_prefix_to_find,
base::CompareCase::SENSITIVE)) {
*out_prop = line.substr(line_prefix_to_find.length());
return true;
}
return false;
}
// Helper function for extracting an attribute value from an XML line.
// Expects |key| to be suffixed with '=\"' (e.g. ' sdkVersion=\"').
// Note: This function has to be in sync with Android's arc-boot-type-detector.
StringPiece GetAttributeValue(const StringPiece& line, const StringPiece& key) {
StringPiece::size_type key_begin_pos = line.find(key);
if (key_begin_pos == StringPiece::npos)
return StringPiece();
StringPiece::size_type value_begin_pos = key_begin_pos + key.length();
StringPiece::size_type value_end_pos = line.find('"', value_begin_pos);
if (value_end_pos == StringPiece::npos)
return StringPiece();
return line.substr(value_begin_pos, value_end_pos - value_begin_pos);
}
// Sets the permission of the given |fd|.
bool SetPermissions(base::PlatformFile fd, mode_t mode) {
struct stat st;
if (fstat(fd, &st) < 0) {
PLOG(ERROR) << "Failed to stat";
return false;
}
if ((st.st_mode & 07000) && ((st.st_mode & 07000) != (mode & 07000))) {
LOG(INFO) << "Changing permissions from " << (st.st_mode & ~S_IFMT)
<< " to " << (mode & ~S_IFMT);
}
if (fchmod(fd, mode) != 0) {
PLOG(ERROR) << "Failed to fchmod " << mode;
return false;
}
return true;
}
class ArcMounterImpl : public ArcMounter {
public:
ArcMounterImpl() = default;
~ArcMounterImpl() override = default;
bool Mount(const std::string& source,
const base::FilePath& target,
const char* filesystem_type,
unsigned long mount_flags, // NOLINT(runtime/int)
const char* data) override {
std::string source_resolved;
if (!source.empty() && source[0] == '/')
source_resolved = Realpath(base::FilePath(source)).value();
else
source_resolved = source; // not a path (e.g. "tmpfs")
if (mount(source_resolved.c_str(), Realpath(target).value().c_str(),
filesystem_type, mount_flags, data) != 0) {
PLOG(ERROR) << "Failed to mount " << source << " to " << target.value();
return false;
}
return true;
}
bool Remount(const base::FilePath& target_directory,
unsigned long mount_flags, // NOLINT(runtime/int)
const char* data) override {
return Mount(std::string(), // ignored
target_directory,
nullptr, // ignored
mount_flags | MS_REMOUNT, data);
}
bool LoopMount(const std::string& source,
const base::FilePath& target,
unsigned long mount_flags) override { // NOLINT(runtime/int)
constexpr size_t kRetryMax = 10;
for (size_t i = 0; i < kRetryMax; ++i) {
bool retry = false;
if (LoopMountInternal(source, target, mount_flags, &retry))
return true;
if (!retry)
break;
LOG(INFO) << "LoopMountInternal failed with EBUSY. Retrying...";
}
return false;
}
bool BindMount(const base::FilePath& old_path,
const base::FilePath& new_path) override {
return Mount(old_path.value(), new_path, nullptr, MS_BIND, nullptr);
}
bool SharedMount(const base::FilePath& path) override {
return Mount("none", path, nullptr, MS_SHARED, nullptr);
}
bool Umount(const base::FilePath& path) override {
if (umount(Realpath(path).value().c_str()) != 0) {
PLOG(ERROR) << "Failed to umount " << path.value();
return false;
}
return true;
}
bool UmountIfExists(const base::FilePath& path) override {
if (umount(Realpath(path).value().c_str()) != 0) {
// We tolerate nothing mounted on the path (EINVAL) and we tolerate the
// path not existing (ENOENT)
if (errno != EINVAL && errno != ENOENT) {
PLOG(ERROR) << "Mount exists but failed to umount " << path.value();
return false;
}
}
return true;
}
bool LoopUmount(const base::FilePath& path) override {
if (!LoopUmountInternal(path, /*ignore_missing=*/false)) {
LOG(ERROR) << "Failed to unmount loop " << path.value();
return false;
}
return true;
}
bool LoopUmountIfExists(const base::FilePath& path) override {
if (!LoopUmountInternal(path, /*ignore_missing=*/true)) {
LOG(ERROR) << "Mount exists but failed to unmount loop " << path.value();
return false;
}
return true;
}
private:
bool LoopUmountInternal(const base::FilePath& path,
const bool ignore_missing) {
struct stat st;
if (stat(path.value().c_str(), &st) < 0) {
if (!ignore_missing || errno != ENOENT) {
PLOG(ERROR) << "Failed to stat " << path.value();
return false;
}
return true;
}
if (major(st.st_dev) != LOOP_MAJOR) {
if (!ignore_missing) {
LOG(ERROR) << path.value()
<< " is not loop-mounted. st_dev=" << st.st_dev;
return false;
}
return true;
}
bool autoclear = false;
const base::FilePath device_path = GetLoopDevicePath(minor(st.st_dev));
{
base::ScopedFD scoped_loop_fd(
open(device_path.value().c_str(), O_RDONLY | O_CLOEXEC));
if (!scoped_loop_fd.is_valid()) {
PLOG(ERROR) << "Failed to open " << device_path.value();
return false;
}
struct loop_info64 loop_info;
if (ioctl(scoped_loop_fd.get(), LOOP_GET_STATUS64, &loop_info) < 0) {
PLOG(ERROR) << "Failed to get info " << device_path.value();
return false;
}
autoclear = loop_info.lo_flags & LO_FLAGS_AUTOCLEAR;
}
if (!Umount(path))
return false;
if (!autoclear) {
base::ScopedFD scoped_loop_fd(
open(device_path.value().c_str(), O_RDWR | O_CLOEXEC));
if (!scoped_loop_fd.is_valid()) {
PLOG(ERROR) << "Failed to open " << device_path.value();
return false;
}
if (ioctl(scoped_loop_fd.get(), LOOP_CLR_FD) < 0) {
PLOG(ERROR) << "Failed to free " << device_path.value();
return false;
}
}
return true;
}
bool LoopMountInternal(const std::string& source,
const base::FilePath& target,
unsigned long mount_flags, // NOLINT(runtime/int)
bool* out_retry) {
constexpr char kLoopControl[] = "/dev/loop-control";
*out_retry = false;
base::ScopedFD scoped_control_fd(open(kLoopControl, O_RDONLY));
if (!scoped_control_fd.is_valid()) {
PLOG(ERROR) << "Failed to open " << kLoopControl;
return false;
}
const int32_t device_num =
ioctl(scoped_control_fd.get(), LOOP_CTL_GET_FREE);
if (device_num < 0) {
PLOG(ERROR) << "Failed to allocate a loop device";
return false;
}
// Cleanup in case mount fails. This frees |device_num| altogether.
base::ScopedClosureRunner loop_device_cleanup(
base::Bind(&RemoveLoopDevice, scoped_control_fd.get(), device_num));
const base::FilePath device_path = GetLoopDevicePath(device_num);
base::ScopedFD scoped_loop_fd(open(device_path.value().c_str(), O_RDWR));
if (!scoped_loop_fd.is_valid()) {
PLOG(ERROR) << "Failed to open " << device_path.value();
return false;
}
const bool is_readonly_mount = mount_flags & MS_RDONLY;
base::ScopedFD scoped_source_fd(
open(source.c_str(), is_readonly_mount ? O_RDONLY : O_RDWR));
if (!scoped_source_fd.is_valid()) {
// If the open failed because we tried to open a read only file as RW
// we fallback to opening it with O_RDONLY
if (!is_readonly_mount && (errno == EROFS || errno == EACCES)) {
LOG(WARNING) << source << " is write-protected, using read-only";
scoped_source_fd.reset(open(source.c_str(), O_RDONLY));
}
if (!scoped_source_fd.is_valid()) {
PLOG(ERROR) << "Failed to open " << source;
return false;
}
}
if (ioctl(scoped_loop_fd.get(), LOOP_SET_FD, scoped_source_fd.get()) < 0) {
PLOG(ERROR) << "Failed to associate " << source << " with "
<< device_path.value();
// Set |out_retry| to true if LOOP_SET_FD returns EBUSY. The errno
// indicates that another process has grabbed the same |device_num|
// before arc-setup does that.
*out_retry = (errno == EBUSY);
return false;
}
// Set the autoclear flag on the loop device, which will release it when
// there are no more references to it.
struct loop_info64 loop_info = {};
if (ioctl(scoped_loop_fd.get(), LOOP_GET_STATUS64, &loop_info) < 0) {
PLOG(ERROR) << "Failed to get loop status for " << device_path.value();
return false;
}
loop_info.lo_flags |= LO_FLAGS_AUTOCLEAR;
if (ioctl(scoped_loop_fd.get(), LOOP_SET_STATUS64, &loop_info) < 0) {
PLOG(ERROR) << "Failed to set autoclear loop status for "
<< device_path.value();
return false;
}
// Substitute the removal of the device number by disassociating |source|
// from the loop device, such that the autoclear flag on |device_num| can
// automatically remove the loop device.
loop_device_cleanup.ReplaceClosure(base::Bind(
&DisassociateLoopDevice, scoped_loop_fd.get(), source, device_path));
if (Mount(device_path.value(), target, "squashfs", mount_flags, nullptr)) {
ignore_result(loop_device_cleanup.Release());
return true;
}
// For debugging, ext4 might be used.
if (Mount(device_path.value(), target, "ext4", mount_flags, nullptr)) {
LOG(INFO) << "Mounted " << source << " as ext4";
ignore_result(loop_device_cleanup.Release());
return true;
}
return false;
}
};
} // namespace
ScopedMount::ScopedMount(const base::FilePath& path,
ArcMounter* mounter,
bool is_loop)
: mounter_(mounter), path_(path), is_loop_(is_loop) {}
ScopedMount::~ScopedMount() {
if (is_loop_) {
PLOG_IF(INFO, !mounter_->LoopUmount(path_))
<< "Ignoring failure to umount " << path_.value();
} else {
PLOG_IF(INFO, !mounter_->Umount(path_))
<< "Ignoring failure to umount " << path_.value();
}
}
// static
std::unique_ptr<ScopedMount> ScopedMount::CreateScopedMount(
ArcMounter* mounter,
const std::string& source,
const base::FilePath& target,
const char* filesystem_type,
unsigned long mount_flags, // NOLINT(runtime/int)
const char* data) {
if (!mounter->Mount(source, target, filesystem_type, mount_flags, data))
return nullptr;
return std::make_unique<ScopedMount>(target, mounter, false /*is_loop*/);
}
// static
std::unique_ptr<ScopedMount> ScopedMount::CreateScopedLoopMount(
ArcMounter* mounter,
const std::string& source,
const base::FilePath& target,
unsigned long flags) { // NOLINT(runtime/int)
if (!mounter->LoopMount(source, target, flags))
return nullptr;
return std::make_unique<ScopedMount>(target, mounter, true /*is_loop*/);
}
// static
std::unique_ptr<ScopedMount> ScopedMount::CreateScopedBindMount(
ArcMounter* mounter,
const base::FilePath& old_path,
const base::FilePath& new_path) {
if (!mounter->BindMount(old_path, new_path))
return nullptr;
return std::make_unique<ScopedMount>(new_path, mounter, false /*is_loop*/);
}
base::FilePath Realpath(const base::FilePath& path) {
// We cannot use base::NormalizeFilePath because the function fails
// if |path| points to a directory (for Windows compatibility.)
char buf[PATH_MAX] = {};
if (!realpath(path.value().c_str(), buf)) {
if (errno != ENOENT)
PLOG(WARNING) << "Failed to resolve " << path.value();
return path;
}
return base::FilePath(buf);
}
bool Chown(uid_t uid, gid_t gid, const base::FilePath& path) {
base::ScopedFD fd(brillo::OpenSafely(path, O_RDONLY, 0));
if (!fd.is_valid())
return false;
return fchown(fd.get(), uid, gid) == 0;
}
bool Chcon(const std::string& context, const base::FilePath& path) {
if (lsetfilecon(path.value().c_str(), context.c_str()) < 0) {
PLOG(ERROR) << "Could not label " << path.value() << " with " << context;
return false;
}
return true;
}
bool InstallDirectory(mode_t mode,
uid_t uid,
gid_t gid,
const base::FilePath& path) {
if (!brillo::MkdirRecursively(path, 0755).is_valid())
return false;
base::ScopedFD fd(brillo::OpenSafely(path, O_DIRECTORY | O_RDONLY, 0));
if (!fd.is_valid())
return false;
// Unlike 'mkdir -m mode -p' which does not change modes when the path already
// exists, 'install -d' always sets modes and owner regardless of whether the
// path exists or not.
const bool chown_result = (fchown(fd.get(), uid, gid) == 0);
const bool chmod_result = SetPermissions(fd.get(), mode);
return chown_result && chmod_result;
}
bool WriteToFile(const base::FilePath& file_path,
mode_t mode,
const std::string& content) {
// Use the same mode as base/files/file_posix.cc's.
constexpr mode_t kMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
base::ScopedFD fd(
brillo::OpenSafely(file_path, O_WRONLY | O_CREAT | O_TRUNC, kMode));
if (!fd.is_valid())
return false;
if (!SetPermissions(fd.get(), mode))
return false;
if (content.empty())
return true;
// Note: WriteFileDescriptor() makes a best effort to write all data.
// While-loop for handling partial-write is not needed here.
return base::WriteFileDescriptor(fd.get(), content.c_str(), content.size());
}
bool GetPropertyFromFile(const base::FilePath& prop_file_path,
const std::string& prop_name,
std::string* out_prop) {
const std::string line_prefix_to_find = prop_name + '=';
if (FindLine(prop_file_path,
base::Bind(&FindProperty, line_prefix_to_find, out_prop))) {
return true; // found the line.
}
LOG(WARNING) << prop_name << " is not in " << prop_file_path.value();
return false;
}
bool GetPropertiesFromFile(const base::FilePath& prop_file_path,
std::map<std::string, std::string>* out_properties) {
if (FindLine(prop_file_path,
base::Bind(&FindAllProperties, out_properties))) {
// Failed to parse the file.
out_properties->clear();
return false;
}
return true;
}
// Note: This function has to be in sync with Android's arc-boot-type-detector.
bool GetFingerprintAndSdkVersionFromPackagesXml(
const base::FilePath& packages_xml_path,
std::string* out_fingerprint,
std::string* out_sdk_version) {
if (FindLine(packages_xml_path,
base::Bind(&FindFingerprintAndSdkVersion, out_fingerprint,
out_sdk_version))) {
return true; // found it.
}
LOG(WARNING) << "No fingerprint found in " << packages_xml_path.value();
return false;
}
bool CreateOrTruncate(const base::FilePath& file_path, mode_t mode) {
return WriteToFile(file_path, mode, std::string());
}
bool WaitForPaths(std::initializer_list<base::FilePath> paths,
const base::TimeDelta& timeout,
base::TimeDelta* out_elapsed) {
const base::TimeDelta sleep_interval = timeout / 20;
std::list<base::FilePath> left(paths);
base::ElapsedTimer timer;
do {
left.erase(std::remove_if(left.begin(), left.end(), base::PathExists),
left.end());
if (left.empty())
break; // all paths are found.
base::PlatformThread::Sleep(sleep_interval);
} while (timeout >= timer.Elapsed());
if (out_elapsed)
*out_elapsed = timer.Elapsed();
for (const auto& path : left)
LOG(ERROR) << path.value() << " not found";
return left.empty();
}
bool LaunchAndWait(const std::vector<std::string>& argv) {
base::Process process(base::LaunchProcess(argv, base::LaunchOptions()));
if (!process.IsValid())
return false;
int exit_code = -1;
return process.WaitForExit(&exit_code) && (exit_code == 0);
}
bool RestoreconRecursively(const std::vector<base::FilePath>& directories) {
return RestoreconInternal(directories, true /* is_recursive */);
}
bool Restorecon(const std::vector<base::FilePath>& paths) {
return RestoreconInternal(paths, false /* is_recursive */);
}
std::string GenerateFakeSerialNumber(const std::string& chromeos_user,
const std::string& salt) {
constexpr size_t kMaxHardwareIdLen = 20;
const std::string hash(crypto::SHA256HashString(chromeos_user + salt));
return base::HexEncode(hash.data(), hash.length())
.substr(0, kMaxHardwareIdLen);
}
uint64_t GetArtCompilationOffsetSeed(const std::string& image_build_id,
const std::string& salt) {
uint64_t result = 0;
std::string input;
do {
input += image_build_id + salt;
crypto::SHA256HashString(input, &result, sizeof(result));
} while (!result);
return result;
}
// Note: This function has to be in sync with Android's arc-boot-type-detector.
bool MoveDirIntoDataOldDir(const base::FilePath& dir,
const base::FilePath& android_data_old_dir) {
if (!base::DirectoryExists(dir)) {
// Nothing to do.
return true;
}
brillo::SafeFD root = brillo::SafeFD::Root().first;
brillo::SafeFD source_parent = root.OpenExistingDir(dir.DirName()).first;
if (!source_parent.is_valid()) {
LOG(ERROR) << "Cannot open " << dir.DirName().value();
return false;
}
brillo::SafeFD dest_dir_parent =
root.OpenExistingDir(android_data_old_dir.DirName()).first;
if (!dest_dir_parent.is_valid()) {
LOG(ERROR) << "Cannot open " << android_data_old_dir.DirName().value();
return false;
}
brillo::SafeFD dest_dir;
brillo::SafeFD::Error err;
std::tie(dest_dir, err) = dest_dir_parent.MakeDir(
android_data_old_dir.BaseName(), 0700 /*permissions*/);
if (err == brillo::SafeFD::Error::kWrongType) {
LOG(INFO) << "Deleting something that is not a directory at "
<< android_data_old_dir.value();
if (dest_dir_parent.Unlink(android_data_old_dir.BaseName().value()) !=
brillo::SafeFD::Error::kNoError) {
LOG(ERROR) << "Failed to delete " << android_data_old_dir.value();
return false;
}
std::tie(dest_dir, err) = dest_dir_parent.MakeDir(
android_data_old_dir.BaseName(), 0700 /*permissions*/);
}
if (!dest_dir.is_valid()) {
LOG(ERROR) << "Cannot open or create " << android_data_old_dir.value();
return false;
}
// Create a unique directory under android_data_old_dir.
// TODO(crbug.com/1076654): Add temporary directory create and rename
// APIs to SafeFD and replace the inline implementation here.
int seed =
std::chrono::high_resolution_clock::now().time_since_epoch().count() &
0xFFFFFF;
const std::string dest_base = dir.BaseName().value();
bool moved = false;
// How many different unique names shall we try? FreeBSD insists on
// exhaustively trying all 56**5 (2.4e10) combinations to find that last
// possible free filename. In contrast, glibc "only" tries 300000 times.
// Conservativelty, we do not expect more than something on the order of
// 100 names to be taken, but failing to opt-out is pretty bad for the
// user so we use a safety factor of 1000 to be very sure. At 100000
// syscalls, will still only block for a second before erroring out.
constexpr int kMaxTries = 100000;
for (int i = 0; i < kMaxTries; i++) {
std::string new_name =
base::StringPrintf("%s_%06x", dest_base.c_str(), seed);
if (renameat(source_parent.get(), dir.BaseName().value().c_str(),
dest_dir.get(), new_name.c_str()) == 0) {
moved = true;
break;
}
if (errno != EEXIST && errno != ENOTEMPTY && errno != ENOTDIR) {
PLOG(ERROR) << "Cannot move" << dir.value() << " to "
<< android_data_old_dir.Append(new_name).value();
return false;
}
seed = (seed + 1) & 0xFFFFFF;
}
if (!moved) {
LOG(ERROR) << "Giving up, cannot move " << dir.value()
<< " to a directory of the form "
<< android_data_old_dir.Append(dest_base + "_XXXXXX").value();
return false;
}
return true;
}
bool DeleteFilesInDir(const base::FilePath& directory) {
base::FileEnumerator files(
directory, true /* recursive */,
base::FileEnumerator::FILES | base::FileEnumerator::SHOW_SYM_LINKS);
bool retval = true;
for (base::FilePath file = files.Next(); !file.empty(); file = files.Next()) {
if (!DeleteFile(file, false /*recursive*/)) {
LOG(ERROR) << "Failed to delete file " << file.value();
retval = false;
}
}
return retval;
}
std::unique_ptr<ArcMounter> GetDefaultMounter() {
return std::make_unique<ArcMounterImpl>();
}
// Note: This function has to be in sync with Android's arc-boot-type-detector.
bool FindLine(const base::FilePath& file_path,
const base::Callback<bool(const std::string&)>& callback) {
// Do exactly the same stream handling as TextContentsEqual() in
// base/files/file_util.cc which is known to work.
std::ifstream file(file_path.value().c_str(), std::ios::in);
if (!file.is_open()) {
PLOG(WARNING) << "Cannot open " << file_path.value();
return false;
}
do {
std::string line;
std::getline(file, line);
// Check for any error state.
if (file.bad()) {
PLOG(WARNING) << "Failed to read " << file_path.value();
return false;
}
// Trim all '\r' and '\n' characters from the end of the line.
std::string::size_type end = line.find_last_not_of("\r\n");
if (end == std::string::npos)
line.clear();
else if (end + 1 < line.length())
line.erase(end + 1);
// Stop reading the file if |callback| returns true.
if (callback.Run(line))
return true;
} while (!file.eof());
// |callback| didn't find anything in the file.
return false;
}
std::string GetChromeOsChannelFromFile(
const base::FilePath& lsb_release_file_path) {
constexpr char kChromeOsReleaseTrackProp[] = "CHROMEOS_RELEASE_TRACK";
const std::set<std::string> kChannels = {
"beta-channel", "canary-channel", "dev-channel",
"dogfood-channel", "stable-channel", "testimage-channel"};
const std::string kUnknown = "unknown";
const std::string kChannelSuffix = "-channel";
// Read the channel property from /etc/lsb-release
std::string chromeos_channel;
if (!GetPropertyFromFile(lsb_release_file_path, kChromeOsReleaseTrackProp,
&chromeos_channel)) {
LOG(ERROR) << "Failed to get the ChromeOS channel from "
<< lsb_release_file_path.value();
return kUnknown;
}
if (kChannels.find(chromeos_channel) == kChannels.end()) {
LOG(WARNING) << "Unknown ChromeOS channel: \"" << chromeos_channel << "\"";
return kUnknown;
}
return chromeos_channel.erase(chromeos_channel.find(kChannelSuffix),
kChannelSuffix.size());
}
bool GetOciContainerState(const base::FilePath& path,
pid_t* out_container_pid,
base::FilePath* out_rootfs) {
// Read the OCI container state from |path|. Its format is documented in
// https://github.com/opencontainers/runtime-spec/blob/master/runtime.md#state
std::string json_str;
if (!base::ReadFileToString(path, &json_str)) {
PLOG(ERROR) << "Failed to read json string from " << path.value();
return false;
}
auto container_state = base::JSONReader::ReadAndReturnValueWithError(
json_str, base::JSON_PARSE_RFC);
if (!container_state.value) {
LOG(ERROR) << "Failed to parse json: " << container_state.error_message;
return false;
}
if (!container_state.value->is_dict()) {
LOG(ERROR) << "Failed to read container state as dictionary";
return false;
}
// Get the container PID and the rootfs path.
base::Optional<int> pid = container_state.value->FindIntKey("pid");
if (!pid) {
LOG(ERROR) << "Failed to get PID from container state";
return false;
}
*out_container_pid = pid.value();
const base::Value* annotations =
container_state.value->FindDictKey("annotations");
if (!annotations) {
LOG(ERROR) << "Failed to get annotations from container state";
return false;
}
const std::string* container_root_path =
annotations->FindStringKey("org.chromium.run_oci.container_root");
if (!container_root_path) {
LOG(ERROR)
<< "Failed to get org.chromium.run_oci.container_root annotation";
return false;
}
base::FilePath container_root(*container_root_path);
if (!base::ReadSymbolicLink(
container_root.Append("mountpoints/container-root"), out_rootfs)) {
PLOG(ERROR) << "Failed to read container root symlink";
return false;
}
return true;
}
bool IsProcessAlive(pid_t pid) {
return kill(pid, 0 /* sig */) == 0;
}
bool GetSha1HashOfFiles(const std::vector<base::FilePath>& files,
std::string* out_hash) {
SHA_CTX sha_context;
SHA1_Init(&sha_context);
for (const auto& file : files) {
std::string file_str;
if (!base::ReadFileToString(file, &file_str))
return false;
SHA1_Update(&sha_context, file_str.data(), file_str.size());
}
unsigned char hash[SHA_DIGEST_LENGTH];
SHA1_Final(hash, &sha_context);
out_hash->assign(reinterpret_cast<const char*>(hash), sizeof(hash));
return true;
}
bool SetXattr(const base::FilePath& path,
const char* name,
const std::string& value) {
base::ScopedFD fd(brillo::OpenSafely(path, O_RDONLY, 0));
if (!fd.is_valid())
return false;
if (fsetxattr(fd.get(), name, value.data(), value.size(), 0 /* flags */) !=
0) {
PLOG(ERROR) << "Failed to change xattr " << name << " of " << path.value();
return false;
}
return true;
}
bool ShouldDeleteAndroidData(AndroidSdkVersion system_sdk_version,
AndroidSdkVersion data_sdk_version) {
// Downgraded. (b/80113276)
if (data_sdk_version > system_sdk_version) {
LOG(INFO) << "Clearing /data dir because ARC was downgraded from "
<< static_cast<int>(data_sdk_version) << " to "
<< static_cast<int>(system_sdk_version) << ".";
return true;
}
// Upgraded from pre-M to post-P. (b/77591360)
if (data_sdk_version > AndroidSdkVersion::UNKNOWN &&
data_sdk_version <= AndroidSdkVersion::ANDROID_M &&
system_sdk_version >= AndroidSdkVersion::ANDROID_P) {
LOG(INFO) << "Clearing /data dir because ARC was upgraded from pre-M("
<< static_cast<int>(data_sdk_version) << ") to post-P("
<< static_cast<int>(system_sdk_version) << ").";
return true;
}
return false;
}
bool FindAllProperties(std::map<std::string, std::string>* out_properties,
const std::string& line) {
// Ignore empty lines and comments.
if (line.empty() || line.at(0) == '#') {
// Continue reading next lines.
return false;
}
std::string::size_type separator = line.find('=');
if (separator == std::string::npos) {
LOG(WARNING) << "Failed to parse: " << line;
// Stop reading next lines on error.
return true;
}
(*out_properties)[line.substr(0, separator)] = line.substr(separator + 1);
// Continue reading next lines.
return false;
}
// Note: This function has to be in sync with Android's arc-boot-type-detector.
bool FindFingerprintAndSdkVersion(std::string* out_fingerprint,
std::string* out_sdk_version,
const std::string& line) {
constexpr char kAttributeVolumeUuid[] = " volumeUuid=\"";
constexpr char kAttributeSdkVersion[] = " sdkVersion=\"";
constexpr char kAttributeDatabaseVersion[] = " databaseVersion=\"";
// Parsing an XML this way is not very clean but in this case, it works (and
// fast.) Android's packages.xml is written in com.android.server.pm.Settings'
// writeLPr(), and the write function always uses Android's FastXmlSerializer.
// The serializer does not try to pretty-print the XML, and inserts '\n' only
// to certain places like endTag.
StringPiece trimmed = base::TrimWhitespaceASCII(line, base::TRIM_ALL);
if (!base::StartsWith(trimmed, kElementVersion, base::CompareCase::SENSITIVE))
return false; // Not a <version> element. Ignoring.
if (trimmed.find(kAttributeVolumeUuid) != std::string::npos)
return false; // This is for an external storage. Ignoring.
StringPiece fingerprint = GetAttributeValue(trimmed, kAttributeFingerprint);
if (fingerprint.empty()) {
LOG(WARNING) << "<version> doesn't have a valid fingerprint: " << trimmed;
return false;
}
StringPiece sdk_version = GetAttributeValue(trimmed, kAttributeSdkVersion);
if (sdk_version.empty()) {
LOG(WARNING) << "<version> doesn't have a valid sdkVersion: " << trimmed;
return false;
}
// Also checks existence of databaseVersion.
if (GetAttributeValue(trimmed, kAttributeDatabaseVersion).empty()) {
LOG(WARNING) << "<version> doesn't have a databaseVersion: " << trimmed;
return false;
}
out_fingerprint->assign(fingerprint.data(), fingerprint.size());
out_sdk_version->assign(sdk_version.data(), sdk_version.size());
return true;
}
bool GetUserId(const std::string& user, uid_t* user_id, gid_t* group_id) {
constexpr int kDefaultPwnameLength = 1024;
// Load the passwd entry
long user_name_length = sysconf(_SC_GETPW_R_SIZE_MAX); // NOLINT long
if (user_name_length == -1)
user_name_length = kDefaultPwnameLength;
passwd user_info;
passwd* user_infop = nullptr;
std::vector<char> user_name_buf(user_name_length);
if (getpwnam_r(user.c_str(), &user_info, user_name_buf.data(),
user_name_length, &user_infop)) {
return false;
}
if (!user_infop)
return false; // no such user
*user_id = user_info.pw_uid;
*group_id = user_info.pw_gid;
return true;
}
} // namespace arc