+#include <cstring>
+#include <cstdlib>
+
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "dinit.h"
+#include "dinit-log.h"
#include "dinit-socket.h"
#include "proc-service.h"
+#include "baseproc-sys.h"
+
/*
* Base process implementation (base_process_service).
*
* See proc-service.h for interface documentation.
*/
-extern eventloop_t event_loop;
-
-using clock_type = dasynq::clock_type;
-using rearm = dasynq::rearm;
-using time_val = dasynq::time_val;
-
void base_process_service::do_smooth_recovery() noexcept
{
if (! restart_ps_process()) {
return true;
}
else {
- event_loop.get_time(restart_interval_time, clock_type::MONOTONIC);
+ if (! open_socket()) {
+ return false;
+ }
+
restart_interval_count = 0;
- if (start_ps_process(exec_arg_parts, onstart_flags.starts_on_console)) {
- if (start_timeout != time_val(0,0)) {
+ if (start_ps_process(exec_arg_parts,
+ onstart_flags.starts_on_console || onstart_flags.shares_console)) {
+ // start_ps_process updates last_start_time, use it also for restart_interval_time:
+ restart_interval_time = last_start_time;
+ // Note: we don't set a start timeout for PROCESS services.
+ if (start_timeout != time_val(0,0) && get_type() != service_type_t::PROCESS) {
restart_timer.arm_timer_rel(event_loop, start_timeout);
stop_timer_armed = true;
}
}
return true;
}
+ restart_interval_time = last_start_time;
return false;
}
}
event_loop.get_time(last_start_time, clock_type::MONOTONIC);
int pipefd[2];
- if (dasynq::pipe2(pipefd, O_CLOEXEC)) {
+ if (bp_sys::pipe2(pipefd, O_CLOEXEC)) {
log(loglevel_t::ERROR, get_name(), ": can't create status check pipe: ", strerror(errno));
return false;
}
control_conn_t *control_conn = nullptr;
int control_socket[2] = {-1, -1};
+ int notify_pipe[2] = {-1, -1};
+ bool have_notify = !notification_var.empty() || force_notification_fd != -1;
+ ready_notify_watcher * rwatcher = have_notify ? get_ready_watcher() : nullptr;
+ bool ready_watcher_registered = false;
+
if (onstart_flags.pass_cs_fd) {
if (dinit_socketpair(AF_UNIX, SOCK_STREAM, /* protocol */ 0, control_socket, SOCK_NONBLOCK)) {
log(loglevel_t::ERROR, get_name(), ": can't create control socket: ", strerror(errno));
}
// Make the server side socket close-on-exec:
- int fdflags = fcntl(control_socket[0], F_GETFD);
- fcntl(control_socket[0], F_SETFD, fdflags | FD_CLOEXEC);
+ int fdflags = bp_sys::fcntl(control_socket[0], F_GETFD);
+ bp_sys::fcntl(control_socket[0], F_SETFD, fdflags | FD_CLOEXEC);
try {
control_conn = new control_conn_t(event_loop, services, control_socket[0]);
}
}
+ if (have_notify) {
+ // Create a notification pipe:
+ if (bp_sys::pipe2(notify_pipe, 0) != 0) {
+ log(loglevel_t::ERROR, get_name(), ": can't create notification pipe: ", strerror(errno));
+ goto out_cs_h;
+ }
+
+ // Set the read side as close-on-exec:
+ int fdflags = bp_sys::fcntl(notify_pipe[0], F_GETFD);
+ bp_sys::fcntl(notify_pipe[0], F_SETFD, fdflags | FD_CLOEXEC);
+
+ // add, but don't yet enable, readiness watcher:
+ try {
+ rwatcher->add_watch(event_loop, notify_pipe[0], dasynq::IN_EVENTS, false);
+ ready_watcher_registered = true;
+ }
+ catch (std::exception &exc) {
+ log(loglevel_t::ERROR, get_name(), ": can't add notification watch: ", exc.what());
+ }
+ }
+
// Set up complete, now fork and exec:
pid_t forkpid;
}
if (forkpid == 0) {
- run_child_proc(cmd.data(), logfile, on_console, pipefd[1], control_socket[1]);
+ const char * working_dir_c = nullptr;
+ if (! working_dir.empty()) working_dir_c = working_dir.c_str();
+ after_fork(getpid());
+ run_child_proc(cmd.data(), working_dir_c, logfile, on_console, pipefd[1], control_socket[1],
+ socket_fd, notify_pipe[1], force_notification_fd, nullptr, run_as_uid, run_as_gid, rlimits);
}
else {
// Parent process
- close(pipefd[1]); // close the 'other end' fd
- if (control_socket[1] != -1) {
- close(control_socket[1]);
- }
pid = forkpid;
+ bp_sys::close(pipefd[1]); // close the 'other end' fd
+ if (control_socket[1] != -1) bp_sys::close(control_socket[1]);
+ if (notify_pipe[1] != -1) bp_sys::close(notify_pipe[1]);
+ notification_fd = notify_pipe[0];
waiting_for_execstat = true;
return true;
}
child_status_listener.deregister(event_loop);
}
+ if (notify_pipe[0] != -1) bp_sys::close(notify_pipe[0]);
+ if (notify_pipe[1] != -1) bp_sys::close(notify_pipe[1]);
+ if (ready_watcher_registered) {
+ rwatcher->deregister(event_loop);
+ }
+
if (onstart_flags.pass_cs_fd) {
delete control_conn;
out_cs:
- close(control_socket[0]);
- close(control_socket[1]);
+ bp_sys::close(control_socket[0]);
+ bp_sys::close(control_socket[1]);
}
out_p:
- close(pipefd[0]);
- close(pipefd[1]);
+ bp_sys::close(pipefd[0]);
+ bp_sys::close(pipefd[1]);
return false;
}
-void base_process_service::bring_down() noexcept
-{
- waiting_for_deps = false;
- if (pid != -1) {
- // The process is still kicking on - must actually kill it. We signal the process
- // group (-pid) rather than just the process as there's less risk then of creating
- // an orphaned process group:
- if (! onstart_flags.no_sigterm) {
- kill_pg(SIGTERM);
- }
- if (term_signal != -1) {
- kill_pg(term_signal);
- }
-
- // In most cases, the rest is done in handle_exit_status.
- // If we are a BGPROCESS and the process is not our immediate child, however, that
- // won't work - check for this now:
- if (get_type() == service_type_t::BGPROCESS && ! tracking_child) {
- stopped();
- }
- else if (stop_timeout != time_val(0,0)) {
- restart_timer.arm_timer_rel(event_loop, stop_timeout);
- stop_timer_armed = true;
- }
- }
- else {
- // The process is already dead.
- stopped();
- }
-}
-
base_process_service::base_process_service(service_set *sset, string name,
service_type_t service_type_p, string &&command,
- std::list<std::pair<unsigned,unsigned>> &command_offsets,
+ const std::list<std::pair<unsigned,unsigned>> &command_offsets,
const std::list<prelim_dep> &deplist_p)
: service_record(sset, name, service_type_p, deplist_p), child_listener(this),
child_status_listener(this), restart_timer(this)
reserved_child_watch = false;
tracking_child = false;
stop_timer_armed = false;
- start_is_interruptible = false;
}
void base_process_service::do_restart() noexcept
restart_interval_count++;
auto service_state = get_state();
- // We may be STARTING (regular restart) or STARTED ("smooth recovery"). This affects whether
- // the process should be granted access to the console:
- bool on_console = service_state == service_state_t::STARTING
- ? onstart_flags.starts_on_console : onstart_flags.runs_on_console;
-
if (service_state == service_state_t::STARTING) {
// for a smooth recovery, we want to check dependencies are available before actually
// starting:
}
}
- if (! start_ps_process(exec_arg_parts, on_console)) {
+ if (! start_ps_process(exec_arg_parts, have_console || onstart_flags.shares_console)) {
restarting = false;
if (service_state == service_state_t::STARTING) {
failed_to_start();
}
}
- // Check if enough time has lapsed since the prevous restart. If not, start a timer:
+ // Check if enough time has lapsed since the previous restart. If not, start a timer:
time_val tdiff = current_time - last_start_time;
if (restart_delay <= tdiff) {
// > restart delay (normally 200ms)
return service_record::interrupt_start();
}
else {
- log(loglevel_t::WARN, "Interrupting start of service ", get_name(), " with pid ", pid, " (with SIGINT).");
+ log(loglevel_t::WARN, "Interrupting start of service ", get_name(), " with pid ", pid,
+ " (with SIGINT).");
kill_pg(SIGINT);
+
if (stop_timeout != time_val(0,0)) {
restart_timer.arm_timer_rel(event_loop, stop_timeout);
stop_timer_armed = true;
restart_timer.stop_timer(event_loop);
stop_timer_armed = false;
}
+
set_state(service_state_t::STOPPING);
- notify_listeners(service_event_t::STARTCANCELLED);
return false;
}
}
void base_process_service::kill_with_fire() noexcept
{
if (pid != -1) {
- log(loglevel_t::WARN, "Service ", get_name(), " with pid ", pid, " exceeded allowed stop time; killing.");
+ log(loglevel_t::WARN, "Service ", get_name(), " with pid ", pid,
+ " exceeded allowed stop time; killing.");
kill_pg(SIGKILL);
}
}
void base_process_service::kill_pg(int signo) noexcept
{
- pid_t pgid = getpgid(pid);
- if (pgid == -1) {
- // only should happen if pid is invalid, which should never happen...
- log(loglevel_t::ERROR, get_name(), ": can't signal process: ", strerror(errno));
- return;
+ if (onstart_flags.signal_process_only) {
+ bp_sys::kill(pid, signo);
+ }
+ else {
+ pid_t pgid = bp_sys::getpgid(pid);
+ if (pgid == -1) {
+ // On some OSes (eg OpenBSD) we aren't allowed to get the pgid of a process in a different
+ // session. If the process is in a different session, however, it must be a process group
+ // leader and the pgid must equal the process id.
+ pgid = pid;
+ }
+ bp_sys::kill(-pgid, signo);
+ }
+}
+
+void base_process_service::timer_expired() noexcept
+{
+ stop_timer_armed = false;
+
+ // Timer expires if:
+ // We are stopping, including after having startup cancelled (stop timeout, state is STOPPING); We are
+ // starting (start timeout, state is STARTING); We are waiting for restart timer before restarting,
+ // including smooth recovery (restart timeout, state is STARTING or STARTED).
+ if (get_state() == service_state_t::STOPPING) {
+ kill_with_fire();
+ }
+ else if (pid != -1) {
+ // Starting, start timed out.
+ log(loglevel_t::WARN, "Service ", get_name(), " with pid ", pid,
+ " exceeded allowed start time; cancelling.");
+ interrupt_start();
+ stop_reason = stopped_reason_t::TIMEDOUT;
+ failed_to_start(false, false);
+ }
+ else {
+ // STARTING / STARTED, and we have a pid: must be restarting (smooth recovery if STARTED)
+ do_restart();
+ }
+}
+
+void base_process_service::emergency_stop() noexcept
+{
+ if (! do_auto_restart() && start_explicit) {
+ start_explicit = false;
+ release(false);
+ }
+ forced_stop();
+ stop_dependents();
+}
+
+void base_process_service::becoming_inactive() noexcept
+{
+ if (socket_fd != -1) {
+ close(socket_fd);
+ socket_fd = -1;
+ }
+}
+
+bool base_process_service::open_socket() noexcept
+{
+ if (socket_path.empty() || socket_fd != -1) {
+ // No socket, or already open
+ return true;
+ }
+
+ const char * saddrname = socket_path.c_str();
+
+ // Check the specified socket path
+ struct stat stat_buf;
+ if (stat(saddrname, &stat_buf) == 0) {
+ if ((stat_buf.st_mode & S_IFSOCK) == 0) {
+ // Not a socket
+ log(loglevel_t::ERROR, get_name(), ": Activation socket file exists (and is not a socket)");
+ return false;
+ }
+ }
+ else if (errno != ENOENT) {
+ // Other error
+ log(loglevel_t::ERROR, get_name(), ": Error checking activation socket: ", strerror(errno));
+ return false;
}
- kill(-pgid, signo);
+
+ // Remove stale socket file (if it exists).
+ // We won't test the return from unlink - if it fails other than due to ENOENT, we should get an
+ // error when we try to create the socket anyway.
+ unlink(saddrname);
+
+ uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + socket_path.length() + 1;
+ struct sockaddr_un * name = static_cast<sockaddr_un *>(malloc(sockaddr_size));
+ if (name == nullptr) {
+ log(loglevel_t::ERROR, get_name(), ": Opening activation socket: out of memory");
+ return false;
+ }
+
+ name->sun_family = AF_UNIX;
+ strcpy(name->sun_path, saddrname);
+
+ int sockfd = dinit_socket(AF_UNIX, SOCK_STREAM, 0, SOCK_NONBLOCK | SOCK_CLOEXEC);
+ if (sockfd == -1) {
+ log(loglevel_t::ERROR, get_name(), ": Error creating activation socket: ", strerror(errno));
+ free(name);
+ return false;
+ }
+
+ if (bind(sockfd, (struct sockaddr *) name, sockaddr_size) == -1) {
+ log(loglevel_t::ERROR, get_name(), ": Error binding activation socket: ", strerror(errno));
+ close(sockfd);
+ free(name);
+ return false;
+ }
+
+ free(name);
+
+ // POSIX (1003.1, 2013) says that fchown and fchmod don't necessarily work on sockets. We have to
+ // use chown and chmod instead.
+ if (chown(saddrname, socket_uid, socket_gid)) {
+ log(loglevel_t::ERROR, get_name(), ": Error setting activation socket owner/group: ",
+ strerror(errno));
+ close(sockfd);
+ return false;
+ }
+
+ if (chmod(saddrname, socket_perms) == -1) {
+ log(loglevel_t::ERROR, get_name(), ": Error setting activation socket permissions: ",
+ strerror(errno));
+ close(sockfd);
+ return false;
+ }
+
+ if (listen(sockfd, 128) == -1) { // 128 "seems reasonable".
+ log(loglevel_t::ERROR, ": Error listening on activation socket: ", strerror(errno));
+ close(sockfd);
+ return false;
+ }
+
+ socket_fd = sockfd;
+ return true;
}