319 lines
11 KiB
C++
319 lines
11 KiB
C++
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#define LOG_TAG "apexd"
|
|
|
|
#include <strings.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <ApexProperties.sysprop.h>
|
|
#include <android-base/logging.h>
|
|
|
|
#include "apexd.h"
|
|
#include "apexd_checkpoint_vold.h"
|
|
#include "apexd_lifecycle.h"
|
|
#include "apexservice.h"
|
|
|
|
#include <android-base/properties.h>
|
|
|
|
#define APEXD_LOG_UNLIMIT
|
|
#ifdef APEXD_LOG_UNLIMIT
|
|
#include <fcntl.h>
|
|
#include <android-base/chrono_utils.h>
|
|
#include <android-base/parseint.h>
|
|
using android::base::boot_clock;
|
|
#include <utils/Mutex.h>
|
|
#endif //APEXD_LOG_UNLIMIT
|
|
|
|
namespace {
|
|
|
|
using android::base::SetDefaultTag;
|
|
|
|
int HandleSubcommand(char** argv) {
|
|
if (strcmp("--bootstrap", argv[1]) == 0) {
|
|
SetDefaultTag("apexd-bootstrap");
|
|
LOG(INFO) << "Bootstrap subcommand detected";
|
|
return android::apex::OnBootstrap();
|
|
}
|
|
|
|
if (strcmp("--unmount-all", argv[1]) == 0) {
|
|
SetDefaultTag("apexd-unmount-all");
|
|
LOG(INFO) << "Unmount all subcommand detected";
|
|
return android::apex::UnmountAll();
|
|
}
|
|
|
|
if (strcmp("--otachroot-bootstrap", argv[1]) == 0) {
|
|
SetDefaultTag("apexd-otachroot");
|
|
LOG(INFO) << "OTA chroot bootstrap subcommand detected";
|
|
return android::apex::OnOtaChrootBootstrap();
|
|
}
|
|
|
|
if (strcmp("--snapshotde", argv[1]) == 0) {
|
|
SetDefaultTag("apexd-snapshotde");
|
|
LOG(INFO) << "Snapshot DE subcommand detected";
|
|
// Need to know if checkpointing is enabled so that a prerestore snapshot
|
|
// can be taken if it's not.
|
|
android::base::Result<android::apex::VoldCheckpointInterface>
|
|
vold_service_st = android::apex::VoldCheckpointInterface::Create();
|
|
if (!vold_service_st.ok()) {
|
|
LOG(ERROR) << "Could not retrieve vold service: "
|
|
<< vold_service_st.error();
|
|
} else {
|
|
android::apex::InitializeVold(&*vold_service_st);
|
|
}
|
|
|
|
int result = android::apex::SnapshotOrRestoreDeUserData();
|
|
|
|
if (result == 0) {
|
|
// Notify other components (e.g. init) that all APEXs are ready to be used
|
|
// Note that it's important that the binder service is registered at this
|
|
// point, since other system services might depend on it.
|
|
android::apex::OnAllPackagesReady();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
if (strcmp("--vm", argv[1]) == 0) {
|
|
SetDefaultTag("apexd-vm");
|
|
LOG(INFO) << "VM subcommand detected";
|
|
return android::apex::OnStartInVmMode();
|
|
}
|
|
|
|
LOG(ERROR) << "Unknown subcommand: " << argv[1];
|
|
return 1;
|
|
}
|
|
|
|
void InstallSigtermSignalHandler() {
|
|
struct sigaction action = {};
|
|
action.sa_handler = [](int /*signal*/) {
|
|
// Handle SIGTERM gracefully.
|
|
// By default, when SIGTERM is received a process will exit with non-zero
|
|
// exit code, which will trigger reboot_on_failure handler if one is
|
|
// defined. This doesn't play well with userspace reboot which might
|
|
// terminate apexd with SIGTERM if apexd was running at the moment of
|
|
// userspace reboot, hence this custom handler to exit gracefully.
|
|
_exit(0);
|
|
};
|
|
sigaction(SIGTERM, &action, nullptr);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
#ifdef APEXD_LOG_UNLIMIT
|
|
#if defined(__linux__)
|
|
|
|
static int kmsg_fd_ulimit = -1;
|
|
static uint64_t log_time;
|
|
static unsigned int log_printed = 0;
|
|
#define DEFAULT_RATELIMIT_INTERVALMS 5000
|
|
#define DEFAULT_RATELIMIT_BURST 9
|
|
static android::Mutex kmsg_mutex;
|
|
|
|
static int OpenKmsg_ulimit() {
|
|
android::Mutex::Autolock _l(kmsg_mutex);
|
|
|
|
uint64_t nowms = std::chrono::duration_cast<std::chrono::milliseconds>(boot_clock::now().time_since_epoch()).count();
|
|
|
|
if (kmsg_fd_ulimit == -1) {
|
|
kmsg_fd_ulimit = TEMP_FAILURE_RETRY(open("/dev/kmsg", O_WRONLY | O_CLOEXEC));
|
|
log_time = nowms;
|
|
log_printed = 0;
|
|
}
|
|
|
|
if (log_printed >= DEFAULT_RATELIMIT_BURST) {
|
|
int newfd;
|
|
newfd = TEMP_FAILURE_RETRY(open("/dev/kmsg", O_WRONLY | O_CLOEXEC));
|
|
|
|
if (newfd >= 0) {
|
|
close(kmsg_fd_ulimit);
|
|
kmsg_fd_ulimit = newfd;
|
|
log_time = nowms;
|
|
log_printed = 0;
|
|
}
|
|
}
|
|
if (nowms >= log_time + DEFAULT_RATELIMIT_INTERVALMS) {
|
|
log_time = nowms;
|
|
log_printed = 0;
|
|
}
|
|
|
|
log_printed++;
|
|
|
|
return kmsg_fd_ulimit;
|
|
}
|
|
|
|
static void KernelLogLine_ulimit(const char* msg, int length, android::base::LogSeverity severity,
|
|
const char* tag) {
|
|
// clang-format off
|
|
static constexpr int kLogSeverityToKernelLogLevel[] = {
|
|
[android::base::VERBOSE] = 7, // KERN_DEBUG (there is no verbose kernel log
|
|
// level)
|
|
[android::base::DEBUG] = 7, // KERN_DEBUG
|
|
[android::base::INFO] = 6, // KERN_INFO
|
|
[android::base::WARNING] = 4, // KERN_WARNING
|
|
[android::base::ERROR] = 3, // KERN_ERROR
|
|
[android::base::FATAL_WITHOUT_ABORT] = 2, // KERN_CRIT
|
|
[android::base::FATAL] = 2, // KERN_CRIT
|
|
};
|
|
// clang-format on
|
|
static_assert(arraysize(kLogSeverityToKernelLogLevel) == android::base::FATAL + 1,
|
|
"Mismatch in size of kLogSeverityToKernelLogLevel and values in LogSeverity");
|
|
|
|
int klog_fd = OpenKmsg_ulimit();
|
|
if (klog_fd == -1) return;
|
|
|
|
int level = kLogSeverityToKernelLogLevel[severity];
|
|
|
|
// The kernel's printk buffer is only |1024 - PREFIX_MAX| bytes, where
|
|
// PREFIX_MAX could be 48 or 32.
|
|
// Reference: kernel/printk/printk.c
|
|
static constexpr int LOG_LINE_MAX = 1024 - 48;
|
|
char buf[LOG_LINE_MAX] __attribute__((__uninitialized__));
|
|
size_t size = snprintf(buf, sizeof(buf), "<%d>%s: %.*s\n", level, tag, length, msg);
|
|
TEMP_FAILURE_RETRY(write(klog_fd, buf, std::min(size, sizeof(buf))));
|
|
|
|
if (size > sizeof(buf)) {
|
|
size_t truncated = size - sizeof(buf);
|
|
size = snprintf(
|
|
buf, sizeof(buf),
|
|
"<%d>%s: **previous message missing %zu bytes** %zu-byte message too long for printk\n",
|
|
level, tag, truncated, size);
|
|
TEMP_FAILURE_RETRY(write(klog_fd, buf, std::min(size, sizeof(buf))));
|
|
}
|
|
}
|
|
|
|
template <typename F, typename... Args>
|
|
static void SplitByLines_ulimit(const char* msg, const F& log_function, Args&&... args) {
|
|
const char* newline = strchr(msg, '\n');
|
|
while (newline != nullptr) {
|
|
log_function(msg, newline - msg, args...);
|
|
msg = newline + 1;
|
|
newline = strchr(msg, '\n');
|
|
}
|
|
|
|
log_function(msg, -1, args...);
|
|
}
|
|
|
|
void KernelLogger_ulimit(android::base::LogId, android::base::LogSeverity severity, const char* tag,
|
|
const char*, unsigned int, const char* full_message) {
|
|
SplitByLines_ulimit(full_message, KernelLogLine_ulimit, severity, tag);
|
|
}
|
|
#endif //defined(__linux__)
|
|
#endif //APEXD_LOG_UNLIMIT
|
|
|
|
int main(int /*argc*/, char** argv) {
|
|
#ifndef APEXD_LOG_UNLIMIT
|
|
android::base::InitLogging(argv, &android::base::KernelLogger);
|
|
#else
|
|
android::base::InitLogging(argv, &KernelLogger_ulimit);
|
|
#endif //APEXD_LOG_UNLIMIT
|
|
// TODO(b/158468454): add a -v flag or an external setting to change severity.
|
|
android::base::SetMinimumLogSeverity(android::base::INFO);
|
|
|
|
// set umask to 022 so that files/dirs created are accessible to other
|
|
// processes e.g.) /apex/apex-info-list.xml is supposed to be read by other
|
|
// processes
|
|
umask(022);
|
|
|
|
InstallSigtermSignalHandler();
|
|
|
|
android::apex::SetConfig(android::apex::kDefaultConfig);
|
|
|
|
android::apex::ApexdLifecycle& lifecycle =
|
|
android::apex::ApexdLifecycle::GetInstance();
|
|
bool booting = lifecycle.IsBooting();
|
|
|
|
const bool has_subcommand = argv[1] != nullptr;
|
|
if (!android::sysprop::ApexProperties::updatable().value_or(false)) {
|
|
if (!has_subcommand) {
|
|
if (!booting) {
|
|
// We've finished booting, but for some reason somebody tried to start
|
|
// apexd. Simply exit.
|
|
return 0;
|
|
}
|
|
|
|
LOG(INFO) << "This device does not support updatable APEX. Exiting";
|
|
// Mark apexd as activated so that init can proceed.
|
|
android::apex::OnAllPackagesActivated(/*is_bootstrap=*/false);
|
|
} else if (strcmp("--snapshotde", argv[1]) == 0) {
|
|
LOG(INFO) << "This device does not support updatable APEX. Exiting";
|
|
// mark apexd as ready
|
|
android::apex::OnAllPackagesReady();
|
|
} else if (strcmp("--otachroot-bootstrap", argv[1]) == 0) {
|
|
SetDefaultTag("apexd-otachroot");
|
|
LOG(INFO) << "OTA chroot bootstrap subcommand detected";
|
|
return android::apex::ActivateFlattenedApex();
|
|
} else if (strcmp("--bootstrap", argv[1]) == 0) {
|
|
LOG(INFO) << "Bootstrap subcommand detected";
|
|
return android::apex::ActivateFlattenedApex();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (has_subcommand) {
|
|
return HandleSubcommand(argv);
|
|
}
|
|
|
|
android::base::Result<android::apex::VoldCheckpointInterface>
|
|
vold_service_st = android::apex::VoldCheckpointInterface::Create();
|
|
android::apex::VoldCheckpointInterface* vold_service = nullptr;
|
|
if (!vold_service_st.ok()) {
|
|
LOG(ERROR) << "Could not retrieve vold service: "
|
|
<< vold_service_st.error();
|
|
} else {
|
|
vold_service = &*vold_service_st;
|
|
}
|
|
android::apex::Initialize(vold_service);
|
|
|
|
if (booting) {
|
|
if (auto res = android::apex::MigrateSessionsDirIfNeeded(); !res.ok()) {
|
|
LOG(ERROR) << "Failed to migrate sessions to /metadata partition : "
|
|
<< res.error();
|
|
}
|
|
android::apex::OnStart();
|
|
} else {
|
|
// TODO(b/172911822): Trying to use data apex related ApexFileRepository
|
|
// apis without initializing it should throw error. Also, unit tests should
|
|
// not pass without initialization.
|
|
// TODO(b/172911822): Consolidate this with Initialize() when
|
|
// ApexFileRepository can act as cache and re-scanning is not expensive
|
|
android::apex::InitializeDataApex();
|
|
}
|
|
// start apexservice before ApexdLifecycle::WaitForBootStatus which waits for
|
|
// IApexService::markBootComplete().
|
|
android::apex::binder::CreateAndRegisterService();
|
|
android::apex::binder::StartThreadPool();
|
|
|
|
if (booting) {
|
|
// Notify other components (e.g. init) that all APEXs are correctly mounted
|
|
// and activated (but are not yet ready to be used). Configuration based on
|
|
// activated APEXs may be performed at this point, but use of APEXs
|
|
// themselves should wait for the ready status instead, which is set when
|
|
// the "--snapshotde" subcommand is received and snapshot/restore is
|
|
// complete.
|
|
android::apex::OnAllPackagesActivated(/*is_bootstrap=*/false);
|
|
lifecycle.WaitForBootStatus(android::apex::RevertActiveSessionsAndReboot);
|
|
// Run cleanup routine on boot complete.
|
|
// This should run before AllowServiceShutdown() to prevent
|
|
// service_manager killing apexd in the middle of the cleanup.
|
|
android::apex::BootCompletedCleanup();
|
|
}
|
|
|
|
android::apex::binder::AllowServiceShutdown();
|
|
|
|
android::apex::binder::JoinThreadPool();
|
|
return 1;
|
|
}
|