| // Copyright 2016 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "imageloader/imageloader_impl.h" |
| |
| #include <linux/magic.h> |
| #include <sys/statvfs.h> |
| #include <sys/vfs.h> |
| |
| #include <memory> |
| #include <string> |
| |
| #include <base/containers/adapters.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/files/important_file_writer.h> |
| #include <base/json/json_string_value_serializer.h> |
| #include <base/logging.h> |
| #include <base/values.h> |
| #include <base/version.h> |
| #include <brillo/files/file_util.h> |
| #include <chromeos/constants/imageloader.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <dlcservice/metadata/metadata.h> |
| #include <dlcservice/metadata/metadata_interface.h> |
| |
| #include "imageloader/component.h" |
| #include "imageloader/dlc.h" |
| |
| namespace imageloader { |
| |
| namespace { |
| |
| using imageloader::kBadResult; |
| |
| // The name of the file containing the latest component version. |
| constexpr char kLatestVersionFile[] = "latest-version"; |
| // The maximum size of the latest-version file. |
| constexpr int kMaximumLatestVersionSize = 4096; |
| // Maximum ID length. |
| constexpr size_t kMaxIdLength = 80; |
| // Default DLC package name. |
| constexpr char kDefaultPackage[] = "package"; |
| |
| // |mount_base_path| is the subfolder where all components are mounted. |
| // For example "/mnt/imageloader." |
| base::FilePath GetMountPoint(const base::FilePath& mount_base_path, |
| const std::string& component_name, |
| const std::string& component_version) { |
| return mount_base_path.Append(component_name).Append(component_version); |
| } |
| |
| bool AssertComponentDirPerms(const base::FilePath& path) { |
| int mode; |
| if (!GetPosixFilePermissions(path, &mode)) |
| return false; |
| return mode == kComponentDirPerms; |
| } |
| |
| } // namespace |
| |
| // static |
| bool ImageLoaderImpl::IsIdValid(const std::string& id) { |
| // |id| can not be empty or start with a non-alphanumerical character. |
| if (id.empty() || id.length() > kMaxIdLength || |
| (!isalpha(id[0]) && !isdigit(id[0]))) { |
| LOG(ERROR) << "Invalid ID: " << id; |
| return false; |
| } |
| // id can only contain alphanumerical character plus '_' and '-'. |
| for (const char& c : id) { |
| if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-') { |
| LOG(ERROR) << "Invalid ID: " << id; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void ImageLoaderImpl::Initialize() { |
| dlc_metadata_ = std::make_shared<dlcservice::metadata::Metadata>( |
| base::FilePath(kDlcManifestRootpath)); |
| if (!dlc_metadata_->Initialize()) { |
| LOG(ERROR) << "Failed to initialize the DLC metadata."; |
| dlc_metadata_.reset(); |
| } |
| } |
| |
| bool ImageLoaderImpl::LoadComponent(const std::string& name, |
| const std::string& mount_point_str, |
| HelperProcessProxy* proxy) { |
| if (!IsIdValid(name)) { |
| return false; |
| } |
| |
| base::FilePath component_path; |
| if (!GetPathToCurrentComponentVersion(name, &component_path)) { |
| return false; |
| } |
| |
| std::unique_ptr<Component> component = |
| Component::Create(component_path, config_.keys); |
| if (!component) { |
| LOG(ERROR) << "Failed to initialize component: " << name; |
| return false; |
| } |
| |
| base::FilePath mount_point(mount_point_str); |
| return component->Mount(proxy, mount_point); |
| } |
| |
| std::string ImageLoaderImpl::LoadComponent(const std::string& name, |
| HelperProcessProxy* proxy) { |
| if (!IsIdValid(name)) { |
| return kBadResult; |
| } |
| |
| base::FilePath component_path; |
| if (!GetPathToCurrentComponentVersion(name, &component_path)) { |
| return kBadResult; |
| } |
| |
| return LoadComponentAtPath(name, component_path, proxy); |
| } |
| |
| std::string ImageLoaderImpl::LoadDlcImage(const std::string& id, |
| const std::string& package, |
| const std::string& a_or_b, |
| HelperProcessProxy* proxy) { |
| if (!IsIdValid(id) || !IsIdValid(package)) { |
| return kBadResult; |
| } |
| |
| Dlc dlc(id, package, config_.mount_path, dlc_metadata_); |
| return dlc.Mount(proxy, a_or_b) ? dlc.GetMountPoint().value() : kBadResult; |
| } |
| |
| std::string ImageLoaderImpl::LoadDlc(const LoadDlcRequest& request, |
| HelperProcessProxy* proxy) { |
| auto package = request.package(); |
| if (package.empty()) |
| package = kDefaultPackage; |
| |
| if (!IsIdValid(request.id()) || !IsIdValid(request.package())) { |
| return kBadResult; |
| } |
| |
| Dlc dlc(request.id(), request.package(), config_.mount_path, dlc_metadata_); |
| return dlc.Mount(proxy, base::FilePath(request.path())) |
| ? dlc.GetMountPoint().value() |
| : kBadResult; |
| } |
| |
| std::string ImageLoaderImpl::LoadComponentAtPath( |
| const std::string& name, |
| const base::FilePath& component_path, |
| HelperProcessProxy* proxy) { |
| if (!IsIdValid(name)) { |
| return kBadResult; |
| } |
| |
| std::unique_ptr<Component> component = |
| Component::Create(component_path, config_.keys); |
| if (!component) { |
| LOG(ERROR) << "Failed to initialize component: " << name; |
| return kBadResult; |
| } |
| |
| base::FilePath mount_point( |
| GetMountPoint(config_.mount_path, name, component->manifest().version())); |
| return component->Mount(proxy, mount_point) ? mount_point.value() |
| : kBadResult; |
| } |
| |
| bool ImageLoaderImpl::RemoveComponent(const std::string& name) { |
| if (!IsIdValid(name)) { |
| return false; |
| } |
| |
| base::FilePath component_root(GetComponentRoot(name)); |
| base::FilePath component_path; |
| if (!GetPathToCurrentComponentVersion(name, &component_path)) { |
| LOG(ERROR) << "Failed to get current component version: " << name; |
| return false; |
| } |
| return RemoveComponentAtPath(name, component_root, component_path); |
| } |
| |
| bool ImageLoaderImpl::CleanupAll(bool dry_run, |
| const base::FilePath& parent_dir, |
| std::vector<std::string>* paths, |
| HelperProcessProxy* proxy) { |
| return proxy->SendUnmountAllCommand(dry_run, parent_dir.value(), paths); |
| } |
| |
| bool ImageLoaderImpl::Cleanup(const base::FilePath& path, |
| HelperProcessProxy* proxy) { |
| return proxy->SendUnmountCommand(path.value()); |
| } |
| |
| bool ImageLoaderImpl::UnloadDlcImage(const std::string& id, |
| const std::string& package, |
| HelperProcessProxy* proxy) { |
| if (!IsIdValid(id)) { |
| return false; |
| } |
| |
| return proxy->SendUnmountCommand( |
| Dlc::GetMountPoint(config_.mount_path, id, package).value()); |
| } |
| |
| bool ImageLoaderImpl::RemoveComponentAtPath( |
| const std::string& name, |
| const base::FilePath& component_root, |
| const base::FilePath& component_path) { |
| if (!IsIdValid(name)) { |
| return false; |
| } |
| |
| // Check if component is removable. |
| std::unique_ptr<Component> component = |
| Component::Create(component_path, config_.keys); |
| if (!component) { |
| LOG(ERROR) << "Failed to initialize component: " << name; |
| return false; |
| } |
| if (!component->manifest().is_removable()) { |
| LOG(ERROR) << "Component is not removable"; |
| return false; |
| } |
| |
| // Remove the component (all versions) and latest-version file. |
| if (!brillo::DeletePathRecursively(component_root)) { |
| LOG(ERROR) << "Failed to delete component."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool ImageLoaderImpl::RegisterComponent( |
| const std::string& name, |
| const std::string& version, |
| const std::string& component_folder_abs_path) { |
| if (!IsIdValid(name)) { |
| return false; |
| } |
| |
| base::FilePath components_dir(config_.storage_dir); |
| |
| // If the directory is writable by others, do not trust the components. |
| if (!AssertComponentDirPerms(components_dir)) |
| return false; |
| |
| std::string old_version_hint; |
| base::FilePath version_hint_path(GetLatestVersionFilePath(name)); |
| bool have_old_version = base::PathExists(version_hint_path); |
| if (have_old_version) { |
| if (!base::ReadFileToStringWithMaxSize(version_hint_path, &old_version_hint, |
| kMaximumLatestVersionSize)) { |
| return false; |
| } |
| |
| // Check for version rollback. |
| base::Version current_version(old_version_hint); |
| base::Version new_version(version); |
| if (!new_version.IsValid()) { |
| return false; |
| } |
| |
| if (current_version.IsValid() && new_version <= current_version) { |
| LOG(ERROR) << "Version [" << new_version << "] is not newer than [" |
| << current_version << "] for component [" << name |
| << "] and cannot be registered."; |
| return false; |
| } |
| } |
| |
| // Check if this specific component already exists in the filesystem. |
| base::FilePath component_root(GetComponentRoot(name)); |
| if (!base::PathExists(component_root)) { |
| if (mkdir(component_root.value().c_str(), kComponentDirPerms) != 0) { |
| PLOG(ERROR) << "Could not create component specific directory."; |
| return false; |
| } |
| } |
| |
| std::unique_ptr<Component> component = Component::Create( |
| base::FilePath(component_folder_abs_path), config_.keys); |
| if (!component) |
| return false; |
| |
| // Check that the reported version matches the component manifest version. |
| if (component->manifest().version() != version) { |
| LOG(ERROR) << "Version in signed manifest does not match the reported " |
| "component version."; |
| return false; |
| } |
| |
| // Take ownership of the component and verify it. |
| base::FilePath version_path(GetVersionPath(name, version)); |
| // If |version_path| exists but was not the active version, ImageLoader |
| // probably crashed previously and could not cleanup. |
| if (base::PathExists(version_path)) { |
| brillo::DeletePathRecursively(version_path); |
| } |
| |
| if (mkdir(version_path.value().c_str(), kComponentDirPerms) != 0) { |
| PLOG(ERROR) << "Could not create directory for new component version."; |
| return false; |
| } |
| |
| if (!component->CopyTo(version_path)) { |
| brillo::DeletePathRecursively(version_path); |
| return false; |
| } |
| |
| if (!base::ImportantFileWriter::WriteFileAtomically(version_hint_path, |
| version)) { |
| brillo::DeletePathRecursively(version_path); |
| LOG(ERROR) << "Failed to update current version hint file."; |
| return false; |
| } |
| |
| // Now delete the old component version, if there was one. |
| if (have_old_version) { |
| brillo::DeletePathRecursively(GetVersionPath(name, old_version_hint)); |
| } |
| |
| return true; |
| } |
| |
| std::string ImageLoaderImpl::GetComponentVersion(const std::string& name) { |
| if (!IsIdValid(name)) { |
| return kBadResult; |
| } |
| |
| base::FilePath component_path; |
| if (!GetPathToCurrentComponentVersion(name, &component_path)) { |
| return kBadResult; |
| } |
| |
| std::unique_ptr<Component> component = |
| Component::Create(component_path, config_.keys); |
| if (!component) |
| return kBadResult; |
| |
| return component->manifest().version(); |
| } |
| |
| bool ImageLoaderImpl::GetComponentMetadata( |
| const std::string& name, std::map<std::string, std::string>* out_metadata) { |
| if (!IsIdValid(name)) { |
| return false; |
| } |
| |
| base::FilePath component_path; |
| if (!GetPathToCurrentComponentVersion(name, &component_path)) { |
| return false; |
| } |
| |
| std::unique_ptr<Component> component = |
| Component::Create(component_path, config_.keys); |
| if (!component) |
| return false; |
| |
| *out_metadata = component->manifest().metadata(); |
| return true; |
| } |
| |
| base::FilePath ImageLoaderImpl::GetLatestVersionFilePath( |
| const std::string& component_name) { |
| return GetComponentRoot(component_name).Append(kLatestVersionFile); |
| } |
| |
| base::FilePath ImageLoaderImpl::GetVersionPath( |
| const std::string& component_name, const std::string& version) { |
| return GetComponentRoot(component_name).Append(version); |
| } |
| |
| base::FilePath ImageLoaderImpl::GetComponentRoot( |
| const std::string& component_name) { |
| return config_.storage_dir.Append(component_name); |
| } |
| |
| bool ImageLoaderImpl::GetPathToCurrentComponentVersion( |
| const std::string& component_name, base::FilePath* result) { |
| base::FilePath component_root(GetComponentRoot(component_name)); |
| base::FilePath latest_version_path = GetLatestVersionFilePath(component_name); |
| |
| // Check that the version file exists, otherwise the logging when |
| // ReadFileToString fails confuses the crash reporting. If the file doesn't |
| // exist, the component most likely isn't installed. |
| if (!base::PathExists(latest_version_path)) { |
| LOG(INFO) << "The latest-version file does not exist. Component " |
| << component_name << " is probably not installed."; |
| return false; |
| } |
| |
| std::string latest_version; |
| if (!base::ReadFileToStringWithMaxSize(latest_version_path, &latest_version, |
| kMaximumLatestVersionSize)) { |
| LOG(ERROR) << "Failed to read latest-version file."; |
| return false; |
| } |
| |
| *result = component_root.Append(latest_version); |
| return true; |
| } |
| |
| } // namespace imageloader |