| // 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/major.h> |
| #include <mntent.h> |
| #include <net/if.h> |
| #include <net/if_arp.h> |
| #include <selinux/restorecon.h> |
| #include <selinux/selinux.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/types.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <fstream> |
| #include <list> |
| #include <set> |
| #include <utility> |
| |
| #include <base/bind.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_split.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 <chromeos-config/libcros_config/cros_config.h> |
| #include <crypto/sha2.h> |
| |
| namespace arc { |
| |
| namespace { |
| |
| // The path in the chromeos-config database where Android properties will be |
| // looked up. |
| constexpr char kCrosConfigPropertiesPath[] = "/arc/build-properties"; |
| |
| // Maximum length of an Android property value. |
| constexpr int kAndroidMaxPropertyLength = 91; |
| |
| std::string GetLoopDevice(int32_t device) { |
| return base::StringPrintf("/dev/loop%d", device); |
| } |
| |
| // 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 restorecon_flags = |
| (is_recursive ? SELINUX_RESTORECON_RECURSE : 0) | |
| SELINUX_RESTORECON_REALPATH; |
| |
| bool success = true; |
| for (const auto& path : paths) { |
| 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, |
| const std::string& line, |
| std::string* out_prop) { |
| if (base::StartsWith(line, line_prefix_to_find, |
| base::CompareCase::SENSITIVE)) { |
| *out_prop = line.substr(line_prefix_to_find.length()); |
| return true; |
| } |
| return false; |
| } |
| |
| // A callback function for GetFingerprintFromPackagesXml. This checks if the |
| // |line| is like |
| // <version sdkVersion="25" databaseVersion="3" fingerprint="..." /> |
| // and store the fingerprint part in |out_fingerprint| if it is. Ignore a line |
| // with a volumeUuid attribute which means that the line is for an external |
| // storage. What we need is a fingerprint for an internal storage. |
| bool FindFingerprint(const std::string& line, std::string* out_fingerprint) { |
| constexpr char kElementVersion[] = "<version "; |
| constexpr char kAttributeVolumeUuid[] = " volumeUuid=\""; |
| constexpr char kAttributeSdkVersion[] = " sdkVersion=\""; |
| constexpr char kAttributeDatabaseVersion[] = " databaseVersion=\""; |
| constexpr char kAttributeFingerprint[] = " fingerprint=\""; |
| |
| // 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. |
| // TODO(yusukes): Check Android P's writeLPr() and FastXmlSerializer although |
| // they unlikely change. |
| std::string trimmed; |
| base::TrimWhitespaceASCII(line, base::TRIM_ALL, &trimmed); |
| 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. |
| |
| std::string::size_type pos = trimmed.find(kAttributeFingerprint); |
| if (pos == std::string::npos || |
| // Do some more sanity checks. |
| trimmed.find(kAttributeSdkVersion) == std::string::npos || |
| trimmed.find(kAttributeDatabaseVersion) == std::string::npos) { |
| LOG(WARNING) << "Unexpected <version> format: " << trimmed; |
| return false; |
| } |
| |
| // Found the line we need. |
| std::string fingerprint = trimmed.substr(pos + strlen(kAttributeFingerprint)); |
| pos = fingerprint.find('"'); |
| if (pos == std::string::npos) { |
| LOG(WARNING) << "<version> doesn't have a valid fingerprint: " << trimmed; |
| return false; |
| } |
| |
| *out_fingerprint = fingerprint.substr(0, pos); |
| return true; |
| } |
| |
| // Reads |file_path| line by line and pass each line to the |callback| after |
| // trimming it. |out_string| is also passed to the |callback| each time. If |
| // |callback| returns true, stops reading the file and returns true. |callback| |
| // must update |out_string| before returning true. |
| bool FindLine( |
| const base::FilePath& file_path, |
| const base::Callback<bool(const std::string&, std::string*)>& callback, |
| std::string* out_string) { |
| // 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, out_string)) |
| return true; |
| } while (!file.eof()); |
| |
| // |callback| didn't find anything in the file. |
| return false; |
| } |
| |
| // 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; |
| } |
| |
| // Opens the |path| with safety checks and returns a FD. This function returns |
| // an invalid FD if open() fails or the returned fd is not safe for use. The |
| // function also returns an invalid FD when |path| is relative. |mode| is |
| // ignored unless |flags| has either O_CREAT or O_TMPFILE. |
| // TODO(yusukes): Consider moving this to libbrillo. Add HANDLE_EINTR and |
| // O_CLOEXEC when doing that. |
| base::ScopedFD OpenSafelyInternal(const base::FilePath& path, |
| int flags, |
| mode_t mode) { |
| if (!path.IsAbsolute()) { |
| LOG(INFO) << "Relative paths are not supported: " << path.value(); |
| return base::ScopedFD(); |
| } |
| |
| base::ScopedFD fd( |
| open(path.value().c_str(), flags | O_NOFOLLOW | O_NONBLOCK, mode)); |
| if (!fd.is_valid()) { |
| // open(2) fails with ELOOP whtn the last component of the |path| is a |
| // symlink. It fails with ENXIO when |path| is a FIFO and |flags| is for |
| // writing because of the O_NONBLOCK flag added above. |
| if (errno == ELOOP || errno == ENXIO) |
| PLOG(WARNING) << "Failed to open " << path.value() << " safely."; |
| return base::ScopedFD(); |
| } |
| |
| // Finally, check if there are symlink(s) in other path components. |
| const base::FilePath proc_fd( |
| base::StringPrintf("/proc/self/fd/%d", fd.get())); |
| base::FilePath resolved; |
| if (!base::ReadSymbolicLink(proc_fd, &resolved)) { |
| LOG(ERROR) << "Failed to read " << proc_fd.value(); |
| return base::ScopedFD(); |
| } |
| // Note: |path| has to be absolute to pass this check. |
| if (resolved != path) { |
| LOG(ERROR) << "Symbolic link detected in " << path.value() |
| << ". Resolved path=" << resolved.value(); |
| return base::ScopedFD(); |
| } |
| |
| // Remove the O_NONBLOCK flag unless the orignal |flags| have it. |
| if ((flags & O_NONBLOCK) == 0) { |
| flags = fcntl(fd.get(), F_GETFL); |
| if (flags == -1) { |
| PLOG(ERROR) << "Failed to get fd flags for " << path.value(); |
| return base::ScopedFD(); |
| } |
| if (fcntl(fd.get(), F_SETFL, flags & ~O_NONBLOCK)) { |
| PLOG(ERROR) << "Failed to set fd flags for " << path.value(); |
| return base::ScopedFD(); |
| } |
| } |
| |
| return fd; |
| } |
| |
| // Calls OpenSafelyInternal() and checks if the returned FD is for a regular |
| // file or directory. Returns an invalid FD if it's not. |
| base::ScopedFD OpenSafely(const base::FilePath& path, int flags, mode_t mode) { |
| base::ScopedFD fd(OpenSafelyInternal(path, flags, mode)); |
| if (!fd.is_valid()) |
| return base::ScopedFD(); |
| |
| // Ensure the opened file is a regular file or directory. |
| struct stat st; |
| if (fstat(fd.get(), &st) < 0) { |
| PLOG(ERROR) << "Failed to fstat " << path.value(); |
| return base::ScopedFD(); |
| } |
| |
| if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) { |
| // This detects a FIFO opened for reading, for example. |
| LOG(ERROR) << path.value() |
| << " is not a regular file/directory: " << st.st_mode; |
| return base::ScopedFD(); |
| } |
| |
| return fd; |
| } |
| |
| 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 UmountLazily(const base::FilePath& path) override { |
| if (umount2(Realpath(path).value().c_str(), MNT_DETACH) != 0) { |
| PLOG(ERROR) << "Failed to lazy-umount " << path.value(); |
| return false; |
| } |
| return true; |
| } |
| |
| bool LoopUmount(const base::FilePath& path) override { |
| struct stat st; |
| if (stat(path.value().c_str(), &st) < 0) { |
| PLOG(ERROR) << "Failed to stat " << path.value(); |
| return false; |
| } |
| |
| if (!Umount(path)) |
| return false; |
| |
| if (major(st.st_dev) != LOOP_MAJOR) { |
| LOG(ERROR) << path.value() |
| << " is not loop-mounted. st_dev=" << st.st_dev; |
| return false; |
| } |
| |
| const std::string device_file = GetLoopDevice(minor(st.st_dev)); |
| const int fd = open(device_file.c_str(), O_RDWR); |
| if (fd < 0) { |
| PLOG(ERROR) << "Failed to open " << device_file; |
| return false; |
| } |
| base::ScopedFD scoped_loop_fd(fd); |
| |
| if (ioctl(scoped_loop_fd.get(), LOOP_CLR_FD)) { |
| PLOG(ERROR) << "Failed to free " << device_file; |
| return false; |
| } |
| return true; |
| } |
| |
| private: |
| 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; |
| int fd = open(kLoopControl, O_RDONLY); |
| if (fd < 0) { |
| PLOG(ERROR) << "Failed to open " << kLoopControl; |
| return false; |
| } |
| base::ScopedFD scoped_control_fd(fd); |
| |
| 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; |
| } |
| |
| const std::string device_file = GetLoopDevice(device_num); |
| fd = open(device_file.c_str(), O_RDWR); |
| if (fd < 0) { |
| PLOG(ERROR) << "Failed to open " << device_file; |
| return false; |
| } |
| base::ScopedFD scoped_loop_fd(fd); |
| |
| const bool is_readonly_mount = mount_flags & MS_RDONLY; |
| fd = open(source.c_str(), is_readonly_mount ? O_RDONLY : O_RDWR); |
| if (fd < 0) { |
| // 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"; |
| fd = open(source.c_str(), O_RDONLY); |
| } |
| if (fd < 0) { |
| PLOG(ERROR) << "Failed to open " << source; |
| return false; |
| } |
| } |
| base::ScopedFD scoped_source_fd(fd); |
| |
| if (ioctl(scoped_loop_fd.get(), LOOP_SET_FD, scoped_source_fd.get()) < 0) { |
| PLOG(ERROR) << "Failed to associate " << source << " with " |
| << device_file; |
| // 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; |
| } |
| |
| if (Mount(device_file, target, "squashfs", mount_flags, nullptr)) |
| return true; |
| |
| // For debugging, ext4 might be used. |
| if (Mount(device_file, target, "ext4", mount_flags, nullptr)) { |
| LOG(INFO) << "Mounted " << source << " as ext4"; |
| return true; |
| } |
| |
| // Mount failed. Remove |source| from the loop device so that |device_num| |
| // can be reused. |
| if (ioctl(scoped_loop_fd.get(), LOOP_CLR_FD) < 0) |
| PLOG(ERROR) << "Failed to remove " << source << " from " << device_file; |
| return false; |
| } |
| }; |
| |
| } // namespace |
| |
| ScopedMount::ScopedMount(const base::FilePath& path, ArcMounter* mounter) |
| : mounter_(mounter), path_(path) {} |
| |
| ScopedMount::~ScopedMount() { |
| PLOG_IF(INFO, !mounter_->UmountLazily(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); |
| } |
| |
| // 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); |
| } |
| |
| // 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); |
| } |
| |
| ScopedMountNamespace::ScopedMountNamespace(base::ScopedFD mount_namespace_fd) |
| : mount_namespace_fd_(std::move(mount_namespace_fd)) {} |
| |
| ScopedMountNamespace::~ScopedMountNamespace() { |
| PLOG_IF(ERROR, setns(mount_namespace_fd_.get(), CLONE_NEWNS) != 0) |
| << "Ignoring failure to restore original mount namespace"; |
| } |
| |
| // static |
| std::unique_ptr<ScopedMountNamespace> |
| ScopedMountNamespace::CreateScopedMountNamespaceForPid(pid_t pid) { |
| constexpr char kCurrentMountNamespacePath[] = "/proc/self/ns/mnt"; |
| base::ScopedFD original_mount_namespace_fd( |
| open(kCurrentMountNamespacePath, O_RDONLY)); |
| if (!original_mount_namespace_fd.is_valid()) { |
| PLOG(ERROR) << "Failed to get the original mount namespace FD"; |
| return nullptr; |
| } |
| base::ScopedFD mount_namespace_fd( |
| open(base::StringPrintf("/proc/%d/ns/mnt", pid).c_str(), O_RDONLY)); |
| if (!mount_namespace_fd.is_valid()) { |
| PLOG(ERROR) << "Failed to get PID " << pid << "'s mount namespace FD"; |
| return nullptr; |
| } |
| |
| if (setns(mount_namespace_fd.get(), CLONE_NEWNS) != 0) { |
| PLOG(ERROR) << "Failed to enter PID " << pid << "'s mount namespace"; |
| return nullptr; |
| } |
| return std::make_unique<ScopedMountNamespace>( |
| std::move(original_mount_namespace_fd)); |
| } |
| |
| std::string GetEnvOrDie(base::Environment* env, const char* name) { |
| DCHECK(env); |
| std::string result; |
| CHECK(env->GetVar(name, &result)) << name << " not found"; |
| return result; |
| } |
| |
| bool GetBooleanEnvOrDie(base::Environment* env, const char* name) { |
| return GetEnvOrDie(env, name) == "1"; |
| } |
| |
| base::FilePath GetFilePathOrDie(base::Environment* env, const char* name) { |
| return base::FilePath(GetEnvOrDie(env, name)); |
| } |
| |
| 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 MkdirRecursively(const base::FilePath& full_path) { |
| if (!full_path.IsAbsolute()) { |
| LOG(INFO) << "Relative paths are not supported: " << full_path.value(); |
| return false; |
| } |
| |
| // Collect a list of all parent directories. |
| std::vector<std::string> components; |
| full_path.GetComponents(&components); |
| DCHECK(!components.empty()); |
| |
| base::ScopedFD fd(OpenSafely(base::FilePath("/"), O_RDONLY, 0)); |
| if (!fd.is_valid()) |
| return false; |
| |
| // Iterate through the parents and create the missing ones. '+ 1' is for |
| // skipping "/". |
| for (std::vector<std::string>::const_iterator i = components.begin() + 1; |
| i != components.end(); ++i) { |
| // Try to create the directory. Note that Chromium's MkdirRecursively() uses |
| // 0700, but we use 0755. |
| if (mkdirat(fd.get(), i->c_str(), 0755) != 0) { |
| if (errno != EEXIST) { |
| PLOG(ERROR) << "Failed to mkdirat " << *i |
| << ": full_path=" << full_path.value(); |
| return false; |
| } |
| |
| // The path already exists. Make sure that the path is a directory. |
| struct stat st; |
| if (fstatat(fd.get(), i->c_str(), &st, AT_SYMLINK_NOFOLLOW) != 0) { |
| PLOG(ERROR) << "Failed to fstatat " << *i |
| << ": full_path=" << full_path.value(); |
| return false; |
| } |
| if (!S_ISDIR(st.st_mode)) { |
| LOG(ERROR) << *i << " is not a directory: st_mode=" << st.st_mode |
| << ", full_path=" << full_path.value(); |
| return false; |
| } |
| } |
| |
| // Updates the FD so it refers to the new directory created or checked |
| // above. |
| const int new_fd = |
| openat(fd.get(), i->c_str(), O_RDONLY | O_NOFOLLOW | O_NONBLOCK, 0); |
| if (new_fd < 0) { |
| PLOG(ERROR) << "Failed to openat " << *i |
| << ": full_path=" << full_path.value(); |
| return false; |
| } |
| fd.reset(new_fd); |
| continue; |
| } |
| return true; |
| } |
| |
| bool Chown(uid_t uid, gid_t gid, const base::FilePath& path) { |
| base::ScopedFD fd(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 (!MkdirRecursively(path)) |
| return false; |
| |
| base::ScopedFD fd(OpenSafely(path, 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(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 GetFingerprintFromPackagesXml(const base::FilePath& packages_xml_path, |
| std::string* out_fingerprint) { |
| if (FindLine(packages_xml_path, base::Bind(&FindFingerprint), |
| out_fingerprint)) { |
| 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; |
| } |
| |
| void MoveDataAppOatDirectory(const base::FilePath& data_app_directory, |
| const base::FilePath& old_executables_directory) { |
| base::FileEnumerator dir_enum(data_app_directory, false /* recursive */, |
| base::FileEnumerator::DIRECTORIES); |
| for (base::FilePath pkg_directory_name = dir_enum.Next(); |
| !pkg_directory_name.empty(); pkg_directory_name = dir_enum.Next()) { |
| const base::FilePath oat_directory = pkg_directory_name.Append("oat"); |
| if (!PathExists(oat_directory)) |
| continue; |
| |
| const base::FilePath temp_oat_directory = old_executables_directory.Append( |
| "oat-" + pkg_directory_name.BaseName().value()); |
| base::File::Error file_error; |
| if (!base::ReplaceFile(oat_directory, temp_oat_directory, &file_error)) { |
| PLOG(ERROR) << "Failed to move cache folder " << oat_directory.value() |
| << ". Error code: " << file_error; |
| } |
| } |
| } |
| |
| 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>(); |
| } |
| |
| bool FindLineForTesting( |
| const base::FilePath& file_path, |
| const base::Callback<bool(const std::string&, std::string*)>& callback, |
| std::string* out_string) { |
| return FindLine(file_path, callback, out_string); |
| } |
| |
| base::ScopedFD OpenSafelyForTesting(const base::FilePath& path, |
| int flags, |
| mode_t mode) { |
| return OpenSafely(path, flags, mode); |
| } |
| |
| 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; |
| } |
| std::string error_msg; |
| std::unique_ptr<base::Value> container_state_value = |
| base::JSONReader::ReadAndReturnError( |
| json_str, base::JSON_PARSE_RFC, nullptr /* error_code_out */, |
| &error_msg, nullptr /* error_line_out */, |
| nullptr /* error_column_out */); |
| if (!container_state_value) { |
| LOG(ERROR) << "Failed to parse json: " << error_msg; |
| return false; |
| } |
| const base::DictionaryValue* container_state = nullptr; |
| if (!container_state_value->GetAsDictionary(&container_state)) { |
| LOG(ERROR) << "Failed to read container state as dictionary"; |
| return false; |
| } |
| |
| // Get the container PID and the rootfs path. |
| if (!container_state->GetInteger("pid", out_container_pid)) { |
| LOG(ERROR) << "Failed to get PID from container state"; |
| return false; |
| } |
| const base::DictionaryValue* annotations = nullptr; |
| if (!container_state->GetDictionary("annotations", &annotations)) { |
| LOG(ERROR) << "Failed to get annotations from container state"; |
| return false; |
| } |
| std::string container_root_str; |
| if (!annotations->GetStringWithoutPathExpansion( |
| "org.chromium.run_oci.container_root", &container_root_str)) { |
| LOG(ERROR) |
| << "Failed to get org.chromium.run_oci.container_root annotation"; |
| return false; |
| } |
| base::FilePath container_root(container_root_str); |
| if (!base::ReadSymbolicLink( |
| container_root.Append("mountpoints/container-root"), out_rootfs)) { |
| PLOG(ERROR) << "Failed to read container root symlink"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ExpandPropertyContents(const std::string& content, |
| brillo::CrosConfigInterface* config, |
| std::string* expanded_content) { |
| const std::vector<std::string> lines = base::SplitString( |
| content, "\n", base::WhitespaceHandling::KEEP_WHITESPACE, |
| base::SplitResult::SPLIT_WANT_ALL); |
| |
| std::string new_properties; |
| for (std::string line : lines) { |
| // First expand {property} substitutions in the string. The insertions |
| // may contain substitutions of their own, so we need to repeat until |
| // nothing more is found. |
| bool inserted; |
| do { |
| inserted = false; |
| size_t match_start = line.find('{'); |
| size_t prev_match = 0; // 1 char past the end of the previous {} match. |
| std::string expanded; |
| // Find all of the {} matches on the line. |
| while (match_start != std::string::npos) { |
| expanded += line.substr(prev_match, match_start - prev_match); |
| |
| size_t match_end = line.find('}', match_start); |
| if (match_end == std::string::npos) { |
| LOG(ERROR) << "Unmatched { found in line: " << line; |
| return false; |
| } |
| |
| const std::string keyword = |
| line.substr(match_start + 1, match_end - match_start - 1); |
| std::string replacement; |
| if (config->GetString(kCrosConfigPropertiesPath, keyword, |
| &replacement)) { |
| expanded += replacement; |
| inserted = true; |
| } else { |
| LOG(ERROR) << "Did not find a value for " << keyword |
| << " while expanding " << line; |
| return false; |
| } |
| |
| prev_match = match_end + 1; |
| match_start = line.find('{', match_end); |
| } |
| if (prev_match != std::string::npos) |
| expanded += line.substr(prev_match); |
| line = expanded; |
| } while (inserted); |
| |
| new_properties += TruncateAndroidProperty(line) + "\n"; |
| } |
| |
| *expanded_content = new_properties; |
| return true; |
| } |
| |
| std::string TruncateAndroidProperty(const std::string& line) { |
| // If line looks like key=value, cut value down to the max length of an |
| // Android property. Build fingerprint needs special handling to preserve the |
| // trailing dev-keys indicator, but other properties can just be truncated. |
| size_t eq_pos = line.find('='); |
| if (eq_pos == std::string::npos) |
| return line; |
| |
| std::string val = line.substr(eq_pos + 1); |
| base::TrimWhitespaceASCII(val, base::TRIM_ALL, &val); |
| if (val.length() <= kAndroidMaxPropertyLength) |
| return line; |
| |
| const std::string key = line.substr(0, eq_pos); |
| LOG(WARNING) << "Truncating property " << key << " value: " << val; |
| if (key == "ro.bootimage.build.fingerprint" && |
| base::EndsWith(val, "/dev-keys", base::CompareCase::SENSITIVE)) { |
| // Typical format is brand/product/device/.... We want to remove |
| // characters from product and device to get below the length limit. |
| // Assume device has the format {product}_cheets. |
| std::vector<std::string> fields = |
| base::SplitString(val, "/", base::WhitespaceHandling::KEEP_WHITESPACE, |
| base::SplitResult::SPLIT_WANT_ALL); |
| |
| int remove_chars = (val.length() - kAndroidMaxPropertyLength + 1) / 2; |
| CHECK_GT(fields[1].length(), remove_chars) << fields[1]; |
| fields[1] = fields[1].substr(0, fields[1].length() - remove_chars); |
| fields[2] = fields[1] + "_cheets"; |
| val = base::JoinString(fields, "/"); |
| } else { |
| val = val.substr(0, kAndroidMaxPropertyLength); |
| } |
| |
| return key + "=" + val; |
| } |
| |
| base::ScopedFD OpenFifoSafely(const base::FilePath& path, |
| int flags, |
| mode_t mode) { |
| base::ScopedFD fd(OpenSafelyInternal(path, flags, mode)); |
| if (!fd.is_valid()) |
| return base::ScopedFD(); |
| |
| // Ensure the opened file is a regular file or directory. |
| struct stat st; |
| if (fstat(fd.get(), &st) < 0) { |
| PLOG(ERROR) << "Failed to fstat " << path.value(); |
| return base::ScopedFD(); |
| } |
| |
| if (!S_ISFIFO(st.st_mode)) { |
| LOG(ERROR) << path.value() << " is not a FIFO: " << st.st_mode; |
| return base::ScopedFD(); |
| } |
| |
| return fd; |
| } |
| |
| } // namespace arc |