blob: e575cc72bf543efdf9f6bb8f0bd9ea7dcdf36cf4 [file] [log] [blame]
// Copyright 2019 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 "dev-install/dev_install.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
#include <istream>
#include <string>
#include <vector>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <brillo/key_value_store.h>
#include <vboot/crossystem.h>
namespace dev_install {
namespace {
// The legacy dev_install shell script to be migrated here.
constexpr char kDevInstallScript[] = "/usr/share/dev-install/main.sh";
// The root path that we install our dev packages into.
constexpr char kUsrLocal[] = "/usr/local";
// The Portage profile path as a subdir under the various roots.
constexpr char kPortageProfileSubdir[] = "etc/portage";
// Path to lsb-release file.
constexpr char kLsbReleasePath[] = "/etc/lsb-release";
// The devserer URL for this developer build.
constexpr char kLsbChromeosDevserver[] = "CHROMEOS_DEVSERVER";
// The current OS version.
constexpr char kLsbChromeosReleaseVersion[] = "CHROMEOS_RELEASE_VERSION";
// Setting for the board name.
constexpr char kLsbChromeosReleaseBoard[] = "CHROMEOS_RELEASE_BOARD";
// The base URL of the repository holding our portage prebuilt binpkgs.
constexpr char kDefaultBinhostPrefix[] =
"https://commondatastorage.googleapis.com/chromeos-dev-installer/board";
} // namespace
DevInstall::DevInstall()
: reinstall_(false),
uninstall_(false),
yes_(false),
only_bootstrap_(false),
state_dir_(kUsrLocal),
binhost_(""),
binhost_version_("") {}
DevInstall::DevInstall(const std::string& binhost,
const std::string& binhost_version,
bool reinstall,
bool uninstall,
bool yes,
bool only_bootstrap)
: reinstall_(reinstall),
uninstall_(uninstall),
yes_(yes),
only_bootstrap_(only_bootstrap),
state_dir_(kUsrLocal),
binhost_(binhost),
binhost_version_(binhost_version) {}
bool DevInstall::IsDevMode() const {
int value = ::VbGetSystemPropertyInt("cros_debug");
return value == 1;
}
bool DevInstall::PromptUser(std::istream& input, const std::string& prompt) {
if (yes_)
return true;
std::cout << prompt << "? (y/N) " << std::flush;
std::string buffer;
if (std::getline(input, buffer)) {
return (buffer == "y" || buffer == "y\n");
}
return false;
}
// We have a custom DeletePath helper because we don't want to descend across
// mount points, and no base:: helper supports that.
bool DevInstall::DeletePath(const struct stat& base_stat,
const base::FilePath& dir) {
base::FileEnumerator iter(dir, true,
base::FileEnumerator::FILES |
base::FileEnumerator::DIRECTORIES |
base::FileEnumerator::SHOW_SYM_LINKS);
for (base::FilePath current = iter.Next(); !current.empty();
current = iter.Next()) {
const auto& info(iter.GetInfo());
if (info.IsDirectory()) {
// Abort if the dir is mounted.
if (base_stat.st_dev != info.stat().st_dev) {
LOG(ERROR) << "directory is mounted: " << current.value();
return false;
}
// Clear the contents of this directory.
if (!DeletePath(base_stat, current))
return false;
// Clear this directory itself.
if (rmdir(current.value().c_str())) {
PLOG(ERROR) << "deleting failed: " << current.value();
return false;
}
} else {
if (unlink(current.value().c_str())) {
PLOG(ERROR) << "deleting failed: " << current.value();
return false;
}
}
}
return true;
}
bool DevInstall::ClearStateDir(const base::FilePath& dir) {
LOG(INFO) << "To clean up, we will run:\n rm -rf /usr/local/\n"
<< "Any content you have stored in there will be deleted.";
if (!PromptUser(std::cin, "Remove all installed packages now")) {
LOG(INFO) << "Operation cancelled.";
return false;
}
// Normally we'd use base::DeleteFile, but we don't want to traverse mounts.
struct stat base_stat;
if (stat(dir.value().c_str(), &base_stat)) {
if (errno == ENOENT)
return true;
PLOG(ERROR) << "Could not access " << dir.value();
return false;
}
bool success = DeletePath(base_stat, dir);
if (success)
LOG(INFO) << "Removed all installed packages.";
else
LOG(ERROR) << "Deleting " << dir.value() << " failed";
return success;
}
bool DevInstall::InitializeStateDir(const base::FilePath& dir) {
// Create this loop so uncompressed files in /usr/local/usr/ will be reachable
// through /usr/local/.
// Note: /usr/local is mount-binded onto the /mnt/stateful_partition/dev_mode
// during chromeos_startup during boot for machines in dev_mode.
const base::FilePath usr = dir.Append("usr");
if (!base::PathExists(usr)) {
// Create /usr/local/usr -> . symlink.
if (!base::CreateSymbolicLink(base::FilePath("."), usr)) {
PLOG(ERROR) << "Creating " << usr.value() << " failed";
return false;
}
}
const base::FilePath local = usr.Append("local");
if (!base::PathExists(local)) {
// Create /usr/local/usr/local -> . symlink.
if (!base::CreateSymbolicLink(base::FilePath("."), local)) {
PLOG(ERROR) << "Creating " << local.value() << " failed";
return false;
}
}
// Set up symlinks for etc/{group,passwd}, so that packages can look up users
// and groups correctly.
const base::FilePath etc = usr.Append("etc");
if (!base::PathExists(etc)) {
if (!base::CreateDirectory(etc) ||
!base::SetPosixFilePermissions(etc, 0755)) {
PLOG(ERROR) << "Creating " << etc.value() << " failed";
return false;
}
}
// Create /usr/local/etc/group -> /etc/group symlink.
const base::FilePath group = etc.Append("group");
if (!base::PathExists(group)) {
if (!base::CreateSymbolicLink(base::FilePath("/etc/group"), group)) {
PLOG(ERROR) << "Creating " << group.value() << " failed";
return false;
}
}
// Create /usr/local/etc/passwd -> /etc/passwd symlink.
const base::FilePath passwd = etc.Append("passwd");
if (!base::PathExists(passwd)) {
if (!base::CreateSymbolicLink(base::FilePath("/etc/passwd"), passwd)) {
PLOG(ERROR) << "Creating " << passwd.value() << " failed";
return false;
}
}
return true;
}
bool DevInstall::LoadRuntimeSettings(const base::FilePath& lsb_release) {
brillo::KeyValueStore store;
if (!store.Load(lsb_release)) {
PLOG(WARNING) << "Could not read " << kLsbReleasePath;
return true;
}
if (!store.GetString(kLsbChromeosDevserver, &devserver_url_))
devserver_url_.clear();
if (store.GetString(kLsbChromeosReleaseBoard, &board_)) {
size_t pos = board_.find("-signed-");
if (pos != std::string::npos)
board_.erase(pos);
} else {
board_.clear();
}
// If --binhost_version wasn't specified, calculate it.
if (binhost_version_.empty()) {
store.GetString(kLsbChromeosReleaseVersion, &binhost_version_);
}
return true;
}
void DevInstall::InitializeBinhost() {
if (!binhost_.empty())
return;
if (!devserver_url_.empty()) {
LOG(INFO) << "Devserver URL set to: " << devserver_url_;
if (PromptUser(std::cin, "Use it as the binhost")) {
binhost_ = devserver_url_ + "/static/pkgroot/" + board_ + "/packages";
return;
}
}
binhost_ = std::string(kDefaultBinhostPrefix) + "/" + board_ + "/" +
binhost_version_ + "/packages";
}
int DevInstall::Exec(const std::vector<const char*>& argv) {
execv(kDevInstallScript, const_cast<char* const*>(argv.data()));
PLOG(ERROR) << kDevInstallScript << " failed";
return EXIT_FAILURE;
}
int DevInstall::Run() {
// Only run if dev mode is enabled.
if (!IsDevMode()) {
LOG(ERROR) << "Chrome OS is not in developer mode";
return 2;
}
// Handle reinstall & uninstall operations.
if (reinstall_ || uninstall_) {
if (!ClearStateDir(state_dir_))
return 1;
if (uninstall_)
return 0;
LOG(INFO) << "Reinstalling dev state";
}
// See if the system has been initialized already.
const base::FilePath state_dir(state_dir_);
const base::FilePath profile = state_dir.Append(kPortageProfileSubdir);
if (base::DirectoryExists(profile)) {
LOG(ERROR) << "Directory " << profile.value() << " exists.";
LOG(ERROR) << "Did you mean dev_install --reinstall?";
return 4;
}
// Initialize the base set of paths before we install any packages.
if (!InitializeStateDir(state_dir_))
return 5;
// Load the settings from the active device.
if (!LoadRuntimeSettings(base::FilePath(kLsbReleasePath)))
return 6;
// Parses flags to return the binhost or if none set, use the default binhost
// set up from installation.
InitializeBinhost();
LOG(INFO) << "Using binhost: " << binhost_;
std::vector<const char*> argv{kDevInstallScript};
if (!binhost_.empty()) {
argv.push_back("--binhost");
argv.push_back(binhost_.c_str());
}
if (yes_)
argv.push_back("--yes");
if (only_bootstrap_)
argv.push_back("--only_bootstrap");
argv.push_back(nullptr);
return Exec(argv);
}
} // namespace dev_install