blob: 941fd1d06a59e65f8240ab70badc252da998bfcd [file] [log] [blame]
// Copyright 2020 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 "init/periodic_scheduler.h"
#include <signal.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/process/launch.h>
#include <base/strings/string_number_conversions.h>
#include <brillo/process/process.h>
#include <brillo/syslog_logging.h>
namespace {
constexpr time_t kCheckDelay = 300;
constexpr time_t kKillDelay = 10;
constexpr char kSpoolDir[] = "/var/spool";
constexpr char kSpoolCronLiteDir[] = "cron-lite";
// Make sure that the path is a directory.
bool SanitizePath(const base::FilePath& path) {
struct stat path_stat;
// Avoid weird spool paths if possible.
if (lstat(path.value().c_str(), &path_stat) != 0 ||
!S_ISDIR(path_stat.st_mode)) {
// Don't recursively delete the directory if we can't stat it.
base::DeleteFile(path);
if (!base::CreateDirectory(path)) {
PLOG(ERROR) << "Failed to create new directory " << path.value();
return false;
}
}
return true;
}
bool CheckAndFixSpoolPaths(const base::FilePath& spool_dir) {
return SanitizePath(spool_dir) &&
SanitizePath(spool_dir.Append(kSpoolCronLiteDir));
}
base::Time GetPathMtime(const base::FilePath& path) {
struct stat path_stat;
if (stat(path.value().c_str(), &path_stat)) {
PLOG(WARNING) << "Failed to get mtime for file " << path.value();
return base::Time::FromTimeT(0);
}
return base::Time::FromTimeT(path_stat.st_mtime);
}
// Sets up PR_SET_PDEATHSIG to send SIGTERMs to the running subprocesses in case
// the scheduler process is killed.
class TerminateWithParentDelegate
: public base::LaunchOptions::PreExecDelegate {
public:
TerminateWithParentDelegate() = default;
~TerminateWithParentDelegate() = default;
void RunAsyncSafe() override { prctl(PR_SET_PDEATHSIG, SIGTERM); }
};
} // namespace
PeriodicScheduler::PeriodicScheduler(
const base::TimeDelta& period,
const base::TimeDelta& timeout,
const std::string& task_name,
const std::vector<std::string>& task_command)
: period_seconds_(period),
timeout_seconds_(timeout),
check_frequency_seconds_(
base::TimeDelta::FromSeconds(kCheckDelay + kKillDelay)),
task_name_(task_name),
spool_dir_(base::FilePath(kSpoolDir)),
process_args_(task_command) {}
bool PeriodicScheduler::Run(bool start_immediately) {
if (!CheckAndFixSpoolPaths(spool_dir_)) {
LOG(ERROR) << "Spool directory is damaged. Aborting!";
return false;
}
const base::FilePath spool_file =
spool_dir_.Append(kSpoolCronLiteDir).Append(task_name_);
while (true) {
if (!start_immediately) {
if (!base::PathExists(spool_file)) {
base::WriteFile(spool_file, nullptr, 0);
auto now = base::Time::Now();
base::TouchFile(spool_file, now, now);
}
base::PlatformThread::Sleep(check_frequency_seconds_);
}
auto file_last_mtime = GetPathMtime(spool_file);
auto current_time = base::Time::Now();
if (start_immediately || current_time - file_last_mtime > period_seconds_) {
base::DeleteFile(spool_file);
base::WriteFile(spool_file, nullptr, 0);
auto now = base::Time::Now();
base::TouchFile(spool_file, now, now);
int exit_code;
base::LaunchOptions opts;
TerminateWithParentDelegate terminate_with_parent_delegate;
opts.pre_exec_delegate = &terminate_with_parent_delegate;
auto p = base::LaunchProcess(process_args_, opts);
LOG(INFO) << task_name_ << ": running "
<< base::JoinString(process_args_, " ");
if (!p.IsValid()) {
PLOG(ERROR) << "Failed to launch process";
return false;
}
if (!p.WaitForExitWithTimeout(timeout_seconds_, &exit_code)) {
LOG(ERROR) << task_name_ << ": timed out";
}
if (exit_code != EXIT_SUCCESS) {
LOG(ERROR) << task_name_ << ": process exited " << exit_code;
}
LOG(INFO) << task_name_ << ": job completed";
}
start_immediately = false;
}
}