blob: afca1116849ccbd38f0625a32c73e6a412d20656 [file] [log] [blame]
// Copyright 2020 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "components/update_client/cobalt_slot_management.h"
#include <sys/stat.h>
#include <algorithm>
#include <vector>
#include "base/files/file_util.h"
#include "base/values.h"
#include "cobalt/updater/utils.h"
#include "components/update_client/utils.h"
#include "starboard/configuration_constants.h"
#include "starboard/file.h"
#include "starboard/loader_app/app_key_files.h"
#include "starboard/loader_app/drain_file.h"
namespace update_client {
namespace {
bool CheckBadFileExists(const char* installation_path, const char* app_key) {
std::string bad_app_key_file_path =
starboard::loader_app::GetBadAppKeyFilePath(installation_path, app_key);
SB_DCHECK(!bad_app_key_file_path.empty());
struct stat file_info;
bool file_exists = stat(bad_app_key_file_path.c_str(), &file_info) == 0;
LOG(INFO) << "bad_app_key_file_path: " << bad_app_key_file_path;
LOG(INFO) << "bad_app_key_file_path FileExists: " << file_exists;
return !bad_app_key_file_path.empty() && file_exists;
}
uint64_t ComputeSlotSize(
const CobaltExtensionInstallationManagerApi* installation_api,
int index) {
if (!installation_api) {
LOG(WARNING) << "ComputeSlotSize: "
<< "Missing installation manager extension.";
return 0;
}
std::vector<char> installation_path(kSbFileMaxPath);
if (installation_api->GetInstallationPath(index, installation_path.data(),
kSbFileMaxPath) == IM_EXT_ERROR) {
LOG(WARNING) << "ComputeSlotSize: "
<< "Failed to get installation path for slot " << index;
return 0;
}
int64_t slot_size =
base::ComputeDirectorySize(base::FilePath(installation_path.data()));
LOG(INFO) << "ComputeSlotSize: slot_size=" << slot_size;
if (slot_size <= 0) {
LOG(WARNING) << "ComputeSlotSize: "
<< "Failed to compute slot " << index << " size";
return 0;
}
return slot_size;
}
} // namespace
CobaltSlotManagement::CobaltSlotManagement() : installation_api_(nullptr) {}
bool CobaltSlotManagement::Init(
const CobaltExtensionInstallationManagerApi* installation_api) {
LOG(INFO) << "CobaltSlotManagement::Init";
installation_api_ = installation_api;
// Make sure the index is reset
installation_index_ = IM_EXT_INVALID_INDEX;
if (!installation_api_) {
LOG(ERROR) << "Failed to get installation manager";
return false;
}
char app_key[IM_EXT_MAX_APP_KEY_LENGTH];
if (installation_api_->GetAppKey(app_key, IM_EXT_MAX_APP_KEY_LENGTH) ==
IM_EXT_ERROR) {
LOG(ERROR) << "Failed to get app key.";
return false;
}
app_key_ = app_key;
initialized_ = true;
return true;
}
bool CobaltSlotManagement::SelectSlot(base::FilePath* dir) {
SB_DCHECK(initialized_);
if (!initialized_) {
return false;
}
LOG(INFO) << "CobaltSlotManagement::SelectSlot";
int max_slots = installation_api_->GetMaxNumberInstallations();
if (max_slots == IM_EXT_ERROR) {
LOG(ERROR) << "Failed to get max number of slots";
return false;
}
// Default invalid version.
base::Version slot_candidate_version;
int slot_candidate = -1;
base::FilePath slot_candidate_path;
// Iterate over all writeable slots - index >= 1.
for (int i = 1; i < max_slots; i++) {
LOG(INFO) << "CobaltSlotManagement::SelectSlot iterating slot=" << i;
std::vector<char> installation_path(kSbFileMaxPath);
if (installation_api_->GetInstallationPath(i, installation_path.data(),
installation_path.size()) ==
IM_EXT_ERROR) {
LOG(ERROR) << "CobaltSlotManagement::SelectSlot: Failed to get "
"installation path for slot="
<< i;
continue;
}
SB_DLOG(INFO) << "CobaltSlotManagement::SelectSlot: installation_path = "
<< installation_path.data();
base::FilePath installation_dir(
std::string(installation_path.begin(), installation_path.end()));
// Cleanup all expired files from all apps.
DrainFileClearExpired(installation_dir.value().c_str());
// Cleanup all drain files from the current app.
DrainFileClearForApp(installation_dir.value().c_str(), app_key_.c_str());
base::Version version =
cobalt::updater::ReadEvergreenVersion(installation_dir);
if (!version.IsValid()) {
LOG(INFO) << "CobaltSlotManagement::SelectSlot installed version invalid";
if (!DrainFileIsAnotherAppDraining(installation_dir.value().c_str(),
app_key_.c_str())) {
LOG(INFO) << "CobaltSlotManagement::SelectSlot not draining";
// Found empty slot.
slot_candidate = i;
slot_candidate_path = installation_dir;
break;
} else {
// There is active draining from another updater so bail out.
LOG(ERROR) << "CobaltSlotManagement::SelectSlot bailing out";
return false;
}
} else if ((!slot_candidate_version.IsValid() ||
slot_candidate_version > version)) {
if (!DrainFileIsAnotherAppDraining(installation_dir.value().c_str(),
app_key_.c_str())) {
// Found a slot with older version that's not draining.
LOG(INFO) << "CobaltSlotManagement::SelectSlot slot candidate: " << i;
slot_candidate_version = version;
slot_candidate = i;
slot_candidate_path = installation_dir;
} else {
// There is active draining from another updater so bail out.
LOG(ERROR) << "CobaltSlotManagement::SelectSlot bailing out";
return false;
}
}
}
installation_index_ = slot_candidate;
*dir = slot_candidate_path;
if (installation_index_ == -1 ||
!DrainFileTryDrain(dir->value().c_str(), app_key_.c_str())) {
LOG(ERROR)
<< "CobaltSlotManagement::SelectSlot unable to find a slot, candidate="
<< installation_index_;
return false;
}
return true;
}
bool CobaltSlotManagement::ConfirmSlot(const base::FilePath& dir) {
SB_DCHECK(initialized_);
if (!initialized_) {
return false;
}
LOG(INFO) << "CobaltSlotManagement::ConfirmSlot ";
if (!DrainFileRankAndCheck(dir.value().c_str(), app_key_.c_str())) {
LOG(INFO) << "CobaltSlotManagement::ConfirmSlot: failed to lock slot ";
return false;
}
// TODO: Double check the installed_version.
// Use the installation slot
LOG(INFO) << "Resetting the slot: " << installation_index_;
if (installation_api_->ResetInstallation(installation_index_) ==
IM_EXT_ERROR) {
LOG(INFO) << "CobaltSlotManagement::ConfirmSlot: failed to reset slot ";
return false;
}
// Remove all files and directories except for our ranking drain file.
DrainFilePrepareDirectory(dir.value().c_str(), app_key_.c_str());
return true;
}
void CobaltSlotManagement::CleanupAllDrainFiles() {
SB_DCHECK(initialized_);
if (!initialized_) {
return;
}
int max_slots = installation_api_->GetMaxNumberInstallations();
if (max_slots == IM_EXT_ERROR) {
LOG(ERROR) << "CobaltSlotManagement::CleanupAllDrainFile: Failed to get "
"max number of slots";
return;
}
// Iterate over all writeable installation slots.
for (int i = 1; i < max_slots; i++) {
LOG(INFO) << "CobaltSlotManagement::CleanupAllDrainFile iterating slot="
<< i;
std::vector<char> installation_path(kSbFileMaxPath);
if (installation_api_->GetInstallationPath(i, installation_path.data(),
installation_path.size()) ==
IM_EXT_ERROR) {
LOG(ERROR) << "CobaltSlotManagement::CleanupAllDrainFile: Failed to get "
"installation path for slot="
<< i;
continue;
}
LOG(INFO)
<< "CobaltSlotManagement::CleanupAllDrainFile: installation_path = "
<< installation_path.data();
base::FilePath installation_dir(
std::string(installation_path.begin(), installation_path.end()));
DrainFileClearForApp(installation_dir.value().c_str(), app_key_.c_str());
}
}
int CobaltSlotManagement::GetInstallationIndex() {
SB_DCHECK(initialized_);
if (!initialized_) {
return false;
}
return installation_index_;
}
bool CobaltFinishInstallation(
const CobaltExtensionInstallationManagerApi* installation_api,
int installation_index,
const std::string& dir,
const std::string& app_key) {
std::string good_app_key_file_path =
starboard::loader_app::GetGoodAppKeyFilePath(dir, app_key);
SB_CHECK(!good_app_key_file_path.empty());
if (!starboard::loader_app::CreateAppKeyFile(good_app_key_file_path)) {
LOG(WARNING) << "Failed to create good app key file";
}
DrainFileClearForApp(dir.c_str(), app_key.c_str());
int ret =
installation_api->RequestRollForwardToInstallation(installation_index);
if (ret == IM_EXT_ERROR) {
LOG(ERROR) << "Failed to request roll forward.";
return false;
}
return true;
}
bool CobaltQuickUpdate(
const CobaltExtensionInstallationManagerApi* installation_api,
const base::Version& current_version) {
if (!current_version.IsValid()) {
return false;
}
char app_key[IM_EXT_MAX_APP_KEY_LENGTH];
if (installation_api->GetAppKey(app_key, IM_EXT_MAX_APP_KEY_LENGTH) ==
IM_EXT_ERROR) {
LOG(ERROR) << "CobaltQuickUpdate: Failed to get app key.";
return true;
}
int max_slots = installation_api->GetMaxNumberInstallations();
if (max_slots == IM_EXT_ERROR) {
LOG(ERROR) << "CobaltQuickUpdate: Failed to get max number of slots.";
return true;
}
// We'll find the newest version of the installation that satisfies the
// requirements as the final candidate slot.
base::Version slot_candidate_version("1.0.1");
int slot_candidate = -1;
// Iterate over all slots - index >= 0.
for (int i = 0; i < max_slots; i++) {
LOG(INFO) << "CobaltQuickInstallation: iterating slot=" << i;
// Get the path to new installation.
std::vector<char> installation_path(kSbFileMaxPath);
if (installation_api->GetInstallationPath(i, installation_path.data(),
installation_path.size()) ==
IM_EXT_ERROR) {
LOG(ERROR) << "CobaltQuickInstallation: Failed to get "
<< "installation path for slot=" << i;
continue;
}
SB_DLOG(INFO) << "CobaltQuickInstallation: installation_path = "
<< installation_path.data();
base::FilePath installation_dir = base::FilePath(
std::string(installation_path.begin(), installation_path.end()));
base::Version installed_version =
cobalt::updater::ReadEvergreenVersion(installation_dir);
std::string good_app_key_file_path =
starboard::loader_app::GetGoodAppKeyFilePath(
installation_dir.value().c_str(), app_key);
struct stat file_info;
if (!installed_version.IsValid()) {
LOG(WARNING) << "CobaltQuickInstallation: invalid version ";
continue;
} else if (slot_candidate_version < installed_version &&
current_version < installed_version &&
(i == 0 ||
!DrainFileIsAnotherAppDraining(installation_dir.value().c_str(),
app_key) &&
!CheckBadFileExists(installation_dir.value().c_str(),
app_key) &&
stat(good_app_key_file_path.c_str(), &file_info) != 0 &&
starboard::loader_app::AnyGoodAppKeyFile(
installation_dir.value().c_str()))) {
// Found a slot with newer version than the current version. It's either
// the system image slot, or a writeable installation slot that's not
// draining, and no bad file of current app exists, and a good file
// exists from a another app, but not from the current app.
// The final candidate is the newest version of the valid
// candidates.
LOG(INFO) << "CobaltQuickInstallation: slot candidate: " << i;
slot_candidate_version = installed_version;
slot_candidate = i;
}
}
if (slot_candidate != -1) {
if (installation_api->RequestRollForwardToInstallation(slot_candidate) !=
IM_EXT_ERROR) {
LOG(INFO) << "CobaltQuickInstallation: quick update succeeded.";
return true;
}
}
LOG(WARNING) << "CobaltQuickInstallation: quick update failed.";
return false;
}
bool CobaltSkipUpdate(
const CobaltExtensionInstallationManagerApi* installation_api,
uint64_t min_free_space_bytes,
int64_t free_space_bytes,
uint64_t installation_cleanup_size) {
LOG(INFO) << "CobaltSkipUpdate: "
<< " min_free_space_bytes=" << min_free_space_bytes
<< " free_space_bytes=" << free_space_bytes
<< " installation_cleanup_size=" << installation_cleanup_size;
if (free_space_bytes < 0) {
LOG(WARNING) << "CobaltSkipUpdate: "
<< "Unable to determine free space";
return false;
}
if (free_space_bytes + installation_cleanup_size < min_free_space_bytes) {
LOG(WARNING) << "CobaltSkipUpdate: Not enough free space";
return true;
}
return false;
}
uint64_t CobaltInstallationCleanupSize(
const CobaltExtensionInstallationManagerApi* installation_api) {
if (!installation_api) {
LOG(WARNING) << "CobaltInstallationCleanupSize: "
<< "Missing installation manager extension.";
return 0;
}
int max_slots = installation_api->GetMaxNumberInstallations();
if (max_slots == IM_EXT_ERROR) {
LOG(ERROR)
<< "CobaltInstallationCleanupSize: Failed to get max number of slots.";
return 0;
}
// Ignore the system slot 0 and start with slot 1.
uint64_t min_slot_size = ComputeSlotSize(installation_api, 1);
for (int i = 2; i < max_slots; i++) {
uint64_t slot_size = ComputeSlotSize(installation_api, i);
if (slot_size < min_slot_size) {
min_slot_size = slot_size;
}
}
return min_slot_size;
}
} // namespace update_client