#include <sys/types.h>
+#include "baseproc-sys.h"
#include "service.h"
// This header defines base_proc_service (base process service) and several derivatives, as well as some
pid_t pid = -1; // PID of the process. If state is STARTING or STOPPING,
// this is PID of the service script; otherwise it is the
// PID of the process itself (process service).
- int exit_status = 0; // Exit status, if the process has exited (pid == -1).
+ bp_sys::exit_status exit_status; // Exit status, if the process has exited (pid == -1).
int socket_fd = -1; // For socket-activation services, this is the file
// descriptor for the socket.
// Called when the process exits. The exit_status is the status value yielded by
// the "wait" system call.
- virtual void handle_exit_status(int exit_status) noexcept = 0;
+ virtual void handle_exit_status(bp_sys::exit_status exit_status) noexcept = 0;
// Called if an exec fails.
virtual void exec_failed(int errcode) noexcept = 0;
// Standard process service.
class process_service : public base_process_service
{
- virtual void handle_exit_status(int exit_status) noexcept override;
+ virtual void handle_exit_status(bp_sys::exit_status exit_status) noexcept override;
virtual void exec_failed(int errcode) noexcept override;
virtual void exec_succeeded() noexcept override;
virtual void bring_down() noexcept override;
// Bgproc (self-"backgrounding", i.e. double-forking) process service
class bgproc_service : public base_process_service
{
- virtual void handle_exit_status(int exit_status) noexcept override;
+ virtual void handle_exit_status(bp_sys::exit_status exit_status) noexcept override;
virtual void exec_failed(int errcode) noexcept override;
virtual void bring_down() noexcept override;
};
// Read the pid-file, return false on failure
- pid_result_t read_pid_file(int *exit_status) noexcept;
+ pid_result_t read_pid_file(bp_sys::exit_status *exit_status) noexcept;
public:
bgproc_service(service_set *sset, string name, string &&command,
// Service which is started and stopped via separate commands
class scripted_service : public base_process_service
{
- virtual void handle_exit_status(int exit_status) noexcept override;
+ virtual void handle_exit_status(bp_sys::exit_status exit_status) noexcept override;
virtual void exec_failed(int errcode) noexcept override;
virtual void bring_down() noexcept override;
base_process_service *sr = service;
sr->pid = -1;
- sr->exit_status = status;
+ sr->exit_status = bp_sys::exit_status(status);
// Ok, for a process service, any process death which we didn't rig
// ourselves is a bit... unexpected. Probably, the child died because
sr->stop_timer_armed = false;
}
- sr->handle_exit_status(status);
+ sr->handle_exit_status(bp_sys::exit_status(status));
return dasynq::rearm::NOOP;
}
-void process_service::handle_exit_status(int exit_status) noexcept
+void process_service::handle_exit_status(bp_sys::exit_status exit_status) noexcept
{
- bool did_exit = WIFEXITED(exit_status);
- bool was_signalled = WIFSIGNALED(exit_status);
+ bool did_exit = exit_status.did_exit();
+ bool was_signalled = exit_status.was_signalled();
restarting = false;
auto service_state = get_state();
- if (exit_status != 0 && service_state != service_state_t::STOPPING) {
+ if (exit_status.did_exit_clean() && service_state != service_state_t::STOPPING) {
if (did_exit) {
log(loglevel_t::ERROR, "Service ", get_name(), " process terminated with exit code ",
- WEXITSTATUS(exit_status));
+ exit_status.get_exit_status());
}
else if (was_signalled) {
log(loglevel_t::ERROR, "Service ", get_name(), " terminated due to signal ",
- WTERMSIG(exit_status));
+ exit_status.get_term_sig());
}
}
if (service_state == service_state_t::STARTING) {
- if (did_exit && WEXITSTATUS(exit_status) == 0) {
+ if (exit_status.did_exit_clean()) {
started();
}
else {
}
}
-void bgproc_service::handle_exit_status(int exit_status) noexcept
+void bgproc_service::handle_exit_status(bp_sys::exit_status exit_status) noexcept
{
begin:
- bool did_exit = WIFEXITED(exit_status);
- bool was_signalled = WIFSIGNALED(exit_status);
+ bool did_exit = exit_status.did_exit();
+ bool was_signalled = exit_status.was_signalled();
auto service_state = get_state();
- if (exit_status != 0 && service_state != service_state_t::STOPPING) {
+ if (!exit_status.did_exit_clean() && service_state != service_state_t::STOPPING) {
if (did_exit) {
log(loglevel_t::ERROR, "Service ", get_name(), " process terminated with exit code ",
- WEXITSTATUS(exit_status));
+ exit_status.get_exit_status());
}
else if (was_signalled) {
log(loglevel_t::ERROR, "Service ", get_name(), " terminated due to signal ",
- WTERMSIG(exit_status));
+ exit_status.get_term_sig());
}
}
if (restarting && service_state == service_state_t::STARTED) {
restarting = false;
bool need_stop = false;
- if ((did_exit && WEXITSTATUS(exit_status) != 0) || was_signalled) {
+ if ((did_exit && exit_status.get_exit_status() != 0) || was_signalled) {
need_stop = true;
}
else {
if (service_state == service_state_t::STARTING) {
// POSIX requires that if the process exited clearly with a status code of 0,
// the exit status value will be 0:
- if (exit_status == 0) {
+ if (exit_status.did_exit_clean()) {
auto pid_result = read_pid_file(&exit_status);
switch (pid_result) {
case pid_result_t::FAILED:
failed_to_start();
}
-void scripted_service::handle_exit_status(int exit_status) noexcept
+void scripted_service::handle_exit_status(bp_sys::exit_status exit_status) noexcept
{
- bool did_exit = WIFEXITED(exit_status);
- bool was_signalled = WIFSIGNALED(exit_status);
+ bool did_exit = exit_status.did_exit();
+ bool was_signalled = exit_status.was_signalled();
auto service_state = get_state();
// For a scripted service, a termination occurs in one of three main cases:
// a cancel order via SIGINT:
if (interrupting_start) {
// We issued a start interrupt, so we expected this failure:
- if (did_exit && WEXITSTATUS(exit_status) != 0) {
+ if (did_exit && exit_status.get_exit_status() != 0) {
log(loglevel_t::INFO, "Service ", get_name(), " start cancelled; exit code ",
- WEXITSTATUS(exit_status));
+ exit_status.get_exit_status());
// Assume that a command terminating normally requires no cleanup:
stopped();
}
else {
if (was_signalled) {
log(loglevel_t::INFO, "Service ", get_name(), " start cancelled from signal ",
- WTERMSIG(exit_status));
+ exit_status.get_term_sig());
}
// If the start script completed successfully, or was interrupted via our signal,
// we want to run the stop script to clean up:
}
interrupting_start = false;
}
- else if (did_exit && WEXITSTATUS(exit_status) == 0) {
+ else if (exit_status.did_exit_clean()) {
// We were running the stop script and finished successfully
stopped();
}
// ??? failed to stop! Let's log it as warning:
if (did_exit) {
log(loglevel_t::WARN, "Service ", get_name(), " stop command failed with exit code ",
- WEXITSTATUS(exit_status));
+ exit_status.get_exit_status());
}
else if (was_signalled) {
log(loglevel_t::WARN, "Service ", get_name(), " stop command terminated due to signal ",
- WTERMSIG(exit_status));
+ exit_status.get_term_sig());
}
// Even if the stop script failed, assume that service is now stopped, so that any dependencies
// can be stopped. There's not really any other useful course of action here.
services->process_queues();
}
else { // STARTING
- if (exit_status == 0) {
+ if (exit_status.did_exit_clean()) {
started();
}
else {
// failed to start
if (did_exit) {
log(loglevel_t::ERROR, "Service ", get_name(), " command failed with exit code ",
- WEXITSTATUS(exit_status));
+ exit_status.get_exit_status());
}
else if (was_signalled) {
log(loglevel_t::ERROR, "Service ", get_name(), " command terminated due to signal ",
- WTERMSIG(exit_status));
+ exit_status.get_term_sig());
}
failed_to_start();
}
}
bgproc_service::pid_result_t
-bgproc_service::read_pid_file(int *exit_status) noexcept
+bgproc_service::read_pid_file(bp_sys::exit_status *exit_status) noexcept
{
const char *pid_file_c = pid_file.c_str();
int fd = open(pid_file_c, O_CLOEXEC);
extern eventloop_t event_loop;
+constexpr static auto REG = dependency_type::REGULAR;
+constexpr static auto WAITS = dependency_type::WAITS_FOR;
+constexpr static auto MS = dependency_type::MILESTONE;
+
// Friend interface to access base_process_service private/protected members.
class base_process_service_test
{
static void handle_exit(base_process_service *bsp, int exit_status)
{
bsp->pid = -1;
- bsp->handle_exit_status(exit_status);
+ bsp->handle_exit_status(bp_sys::exit_status(true, false, exit_status));
}
};
sset.remove_service(&p);
}
+void test_scripted_start_fail()
+{
+ using namespace std;
+
+ service_set sset;
+
+ string command = "test-command";
+ string stopcommand = "stop-command";
+ list<pair<unsigned,unsigned>> command_offsets;
+ command_offsets.emplace_back(0, command.length());
+ std::list<prelim_dep> depends;
+
+ scripted_service p = scripted_service(&sset, "testscripted", std::move(command), command_offsets, depends);
+ init_service_defaults(p);
+ p.set_stop_command(stopcommand, command_offsets);
+ sset.add_service(&p);
+
+ service_record *s2 = new service_record(&sset, "test-service-2", service_type_t::INTERNAL, {{&p, REG}});
+ service_record *s3 = new service_record(&sset, "test-service-3", service_type_t::INTERNAL, {{&p, REG}, {s2, REG}});
+ sset.add_service(s2);
+ sset.add_service(s3);
+
+ s3->start(true);
+ sset.process_queues();
+
+ assert(p.get_state() == service_state_t::STARTING);
+
+ base_process_service_test::exec_succeeded(&p);
+ sset.process_queues();
+ base_process_service_test::handle_exit(&p, 0x1); // exit fail
+ sset.process_queues();
+
+ // failed to start:
+ assert(p.get_state() == service_state_t::STOPPED);
+ assert(s2->get_state() == service_state_t::STOPPED);
+ assert(s3->get_state() == service_state_t::STOPPED);
+
+ event_loop.active_timers.clear();
+ sset.remove_service(&p);
+
+ assert(sset.count_active_services() == 0);
+}
+
#define RUN_TEST(name, spacing) \
std::cout << #name "..." spacing; \
RUN_TEST(test_proc_smooth_recovery1, "");
RUN_TEST(test_proc_smooth_recovery2, "");
RUN_TEST(test_scripted_stop_timeout, "");
+ RUN_TEST(test_scripted_start_fail, " ");
}