| // 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/sendfile.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)) { |
| 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/HEAD/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) { |
| // Initial launch with clean data. |
| if (data_sdk_version == AndroidSdkVersion::UNKNOWN) |
| return false; |
| // 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; |
| } |
| // Skip-upgraded from M to post-P. (b/77591360) |
| if (data_sdk_version == AndroidSdkVersion::ANDROID_M && |
| system_sdk_version >= AndroidSdkVersion::ANDROID_P) { |
| LOG(INFO) << "Clearing /data dir because ARC was skip-upgraded from M(" |
| << static_cast<int>(data_sdk_version) << ") to post-P(" |
| << static_cast<int>(system_sdk_version) << ")."; |
| return true; |
| } |
| // Skip-upgraded from N to post-R. (b/167635130) |
| if (data_sdk_version == AndroidSdkVersion::ANDROID_N_MR1 && |
| system_sdk_version >= AndroidSdkVersion::ANDROID_R) { |
| LOG(INFO) << "Clearing /data dir because ARC was skip-upgraded from N(" |
| << static_cast<int>(data_sdk_version) << ") to post-R(" |
| << 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; |
| } |
| |
| bool SafeCopyFile(const base::FilePath& src_path, |
| brillo::SafeFD src_parent, |
| const base::FilePath& dest_path, |
| brillo::SafeFD dest_parent, |
| mode_t permissions, |
| uid_t uid, |
| gid_t gid) { |
| struct stat st; |
| ssize_t len, ret; |
| |
| if (!src_parent.is_valid()) { |
| LOG(ERROR) << "Invalid src_parent fd"; |
| return false; |
| } |
| |
| if (!dest_parent.is_valid()) { |
| LOG(ERROR) << "Invalid dest_parent fd"; |
| return false; |
| } |
| |
| brillo::SafeFD root = brillo::SafeFD::Root().first; |
| brillo::SafeFD::SafeFDResult result( |
| src_parent.OpenExistingFile(src_path, O_RDONLY | O_CLOEXEC)); |
| if (brillo::SafeFD::IsError(result.second)) { |
| LOG(ERROR) << "Failed to open src path " << src_path; |
| return false; |
| } |
| brillo::SafeFD src_fd(std::move(result.first)); |
| |
| brillo::SafeFD dest_fd( |
| dest_parent.MakeFile(dest_path, permissions, uid, gid).first); |
| if (!dest_fd.is_valid()) { |
| LOG(ERROR) << "Failed to open dest path " << dest_path; |
| return false; |
| } |
| |
| fstat(src_fd.get(), &st); |
| len = st.st_size; |
| |
| do { |
| ret = sendfile(dest_fd.get(), src_fd.get(), NULL, len); |
| if (ret == -1) { |
| PLOG(ERROR) << "Fail to copy file " << src_path << " to " << dest_path |
| << errno; |
| return false; |
| } |
| len -= ret; |
| } while (len > 0 && ret > 0); |
| |
| return true; |
| } |
| |
| } // namespace arc |