diff --git a/src/baseproc-service.cc b/src/baseproc-service.cc index 9347dc87..6272b8d9 100644 --- a/src/baseproc-service.cc +++ b/src/baseproc-service.cc @@ -30,7 +30,8 @@ void base_process_service::do_smooth_recovery() noexcept bool base_process_service::bring_up() noexcept { - if (!open_socket()) { + if (!open_socket() || !open_ready_socket()) { + becoming_inactive(); return false; } @@ -152,7 +153,7 @@ bool base_process_service::start_ps_process(const std::vector &cmd int control_socket[2] = {-1, -1}; int notify_pipe[2] = {-1, -1}; - bool have_notify = !notification_var.empty() || force_notification_fd != -1; + bool have_notify = !notification_var.empty() || ready_socket_fd >= 0 || force_notification_fd != -1; ready_notify_watcher * rwatcher = have_notify ? get_ready_watcher() : nullptr; bool ready_watcher_registered = false; @@ -175,7 +176,7 @@ bool base_process_service::start_ps_process(const std::vector &cmd } } - if (have_notify) { + if (have_notify && ready_socket_fd < 0) { // 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)); @@ -185,10 +186,14 @@ bool base_process_service::start_ps_process(const std::vector &cmd // 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); + } else if (have_notify) { + notification_var = "NOTIFY_SOCKET=" + ready_socket_path; + } + if (have_notify) { // add, but don't yet enable, readiness watcher: try { - rwatcher->add_watch(event_loop, notify_pipe[0], dasynq::IN_EVENTS, false); + rwatcher->add_watch(event_loop, ready_socket_fd >= 0 ? ready_socket_fd : notify_pipe[0], dasynq::IN_EVENTS, false); ready_watcher_registered = true; } catch (std::exception &exc) { @@ -251,7 +256,7 @@ bool base_process_service::start_ps_process(const std::vector &cmd run_params.unmask_sigint = onstart_flags.unmask_intr; run_params.csfd = control_socket[1]; run_params.socket_fd = socket_fd; - run_params.notify_fd = notify_pipe[1]; + run_params.notify_fd = ready_socket_fd >= 0 ? ready_socket_fd : notify_pipe[1]; run_params.force_notify_fd = force_notification_fd; run_params.notify_var = notification_var.c_str(); run_params.env_file = env_file.c_str(); @@ -469,86 +474,116 @@ void base_process_service::becoming_inactive() noexcept 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; + if (ready_socket_fd != -1) { + close(ready_socket_fd); + ready_socket_fd = -1; } + free(ready_socket_name); + ready_socket_name = nullptr; +} - const char * saddrname = socket_path.c_str(); - +static int open_sock(const char *path, const std::string &svcname, int type, + uid_t uid, gid_t gid, int perms, struct sockaddr_un *&name) noexcept { // Check the specified socket path struct stat stat_buf; - if (stat(saddrname, &stat_buf) == 0) { + if (stat(path, &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; + log(loglevel_t::ERROR, svcname, ": socket file exists (and is not a socket)"); + return -1; } } else if (errno != ENOENT) { // Other error - log(loglevel_t::ERROR, get_name(), ": error checking activation socket: ", strerror(errno)); - return false; + log(loglevel_t::ERROR, svcname, ": error checking socket: ", strerror(errno)); + return -1; } // 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); + unlink(path); - uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + socket_path.length() + 1; - struct sockaddr_un * name = static_cast(malloc(sockaddr_size)); + uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen(path) + 1; + name = static_cast(malloc(sockaddr_size)); if (name == nullptr) { - log(loglevel_t::ERROR, get_name(), ": opening activation socket: out of memory"); - return false; + log(loglevel_t::ERROR, svcname, ": opening socket: out of memory"); + return -1; } name->sun_family = AF_UNIX; - strcpy(name->sun_path, saddrname); + strcpy(name->sun_path, path); - int sockfd = dinit_socket(AF_UNIX, SOCK_STREAM, 0, SOCK_NONBLOCK | SOCK_CLOEXEC); + int sockfd = dinit_socket(AF_UNIX, type, 0, SOCK_NONBLOCK | SOCK_CLOEXEC); if (sockfd == -1) { - log(loglevel_t::ERROR, get_name(), ": error creating activation socket: ", strerror(errno)); + log(loglevel_t::ERROR, svcname, ": error creating socket: ", strerror(errno)); free(name); - return false; + return -1; } if (bind(sockfd, (struct sockaddr *) name, sockaddr_size) == -1) { - log(loglevel_t::ERROR, get_name(), ": error binding activation socket: ", strerror(errno)); + log(loglevel_t::ERROR, svcname, ": error binding socket: ", strerror(errno)); close(sockfd); free(name); - return false; + return -1; } - 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: ", + if (chown(path, uid, gid)) { + log(loglevel_t::ERROR, svcname, ": error setting socket owner/group: ", strerror(errno)); close(sockfd); - return false; + return -1; } - if (chmod(saddrname, socket_perms) == -1) { - log(loglevel_t::ERROR, get_name(), ": Error setting activation socket permissions: ", + if (chmod(path, perms) == -1) { + log(loglevel_t::ERROR, svcname, ": Error setting socket permissions: ", strerror(errno)); close(sockfd); - return false; + return -1; } - if (listen(sockfd, 128) == -1) { // 128 "seems reasonable". - log(loglevel_t::ERROR, ": error listening on activation socket: ", strerror(errno)); + if (type != SOCK_DGRAM && listen(sockfd, 128) == -1) { // 128 "seems reasonable". + log(loglevel_t::ERROR, ": error listening on socket: ", strerror(errno)); close(sockfd); + return -1; + } + + return sockfd; +} + +bool base_process_service::open_socket() noexcept +{ + if (socket_path.empty() || socket_fd != -1) { + // No socket, or already open + return true; + } + + struct sockaddr_un *name = nullptr; + socket_fd = open_sock(socket_path.c_str(), get_name(), SOCK_STREAM, socket_uid, + socket_gid, socket_perms, name); + free(name); + + return socket_fd >= 0; +} + +bool base_process_service::open_ready_socket() noexcept +{ + if (ready_socket_path.empty() || ready_socket_fd != -1) { + // No socket, or already open + return true; + } + + ready_socket_fd = open_sock(ready_socket_path.c_str(), get_name(), SOCK_DGRAM, + ready_socket_uid, ready_socket_gid, ready_socket_perms, ready_socket_name); + + if (ready_socket_fd < 0) { + free(ready_socket_name); + ready_socket_name = nullptr; return false; } - socket_fd = sockfd; return true; } diff --git a/src/includes/load-service.h b/src/includes/load-service.h index e4a9753b..d136d982 100644 --- a/src/includes/load-service.h +++ b/src/includes/load-service.h @@ -225,7 +225,7 @@ enum class setting_id_t { LOGFILE_GID, LOG_TYPE, LOG_BUFFER_SIZE, CONSUMER_OF, RESTART, SMOOTH_RECOVERY, OPTIONS, LOAD_OPTIONS, TERM_SIGNAL, TERMSIGNAL /* deprecated */, RESTART_LIMIT_INTERVAL, RESTART_DELAY, RESTART_LIMIT_COUNT, STOP_TIMEOUT, START_TIMEOUT, RUN_AS, CHAIN_TO, READY_NOTIFICATION, - INITTAB_ID, INITTAB_LINE, + READY_SOCKET_PERMISSIONS, READY_SOCKET_UID, READY_SOCKET_GID, INITTAB_ID, INITTAB_LINE, // Prefixed with SETTING_ to avoid name collision with system macros: SETTING_RLIMIT_NOFILE, SETTING_RLIMIT_CORE, SETTING_RLIMIT_DATA, SETTING_RLIMIT_ADDRSPACE, // Possibly unsupported depending on platform/build options: @@ -1282,12 +1282,17 @@ class service_settings_wrapper auto_restart_mode auto_restart = auto_restart_mode::DEFAULT_AUTO_RESTART; bool smooth_recovery = false; string socket_path; + string ready_socket_path; int socket_perms = 0666; // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an // invalid value, so it's safe to assume that we can do the same: uid_t socket_uid = -1; gid_t socket_uid_gid = -1; // primary group of socket user if known gid_t socket_gid = -1; + int ready_socket_perms = 0600; + uid_t ready_socket_uid = -1; + gid_t ready_socket_uid_gid = -1; + gid_t ready_socket_gid = -1; // Restart limit interval / count; default is 10 seconds, 3 restarts: timespec restart_interval = { .tv_sec = 10, .tv_nsec = 0 }; int max_restarts = 3; @@ -1362,6 +1367,9 @@ class service_settings_wrapper if (!socket_path.empty()) { report_lint("'socket-listen' specified, but ignored for the specified (or default) service type'."); } + if (!ready_socket_path.empty()) { + report_lint("'ready-notification' specified, but ignored for the specified (or default) service type'."); + } #if USE_UTMPX if (inittab_id[0] != 0 || inittab_line[0] != 0) { report_lint("'inittab_line' or 'inittab_id' specified, but ignored for the specified (or default) service type."); @@ -1395,7 +1403,7 @@ class service_settings_wrapper report_error("process ID file ('pid-file') not specified for bgprocess service."); } - if (readiness_fd != -1 || !readiness_var.empty()) { + if (readiness_fd != -1 || !ready_socket_path.empty() || !readiness_var.empty()) { report_error("readiness notification ('ready-notification') is not supported " "for bgprocess services."); } @@ -1421,6 +1429,7 @@ class service_settings_wrapper }; do_resolve("socket-listen", socket_path); + do_resolve("ready-notification", ready_socket_path); do_resolve("logfile", logfile); do_resolve("working-dir", working_dir); do_resolve("pid-file", pid_file); @@ -1429,6 +1438,7 @@ class service_settings_wrapper // If socket_gid hasn't been explicitly set, but the socket_uid was specified as a name (and // we therefore recovered the primary group), use the primary group of the specified user. if (socket_gid == (gid_t)-1) socket_gid = socket_uid_gid; + if (ready_socket_gid == (gid_t)-1) ready_socket_gid = ready_socket_uid_gid; // Also for logfile_uid/gid, we reset logfile ownership to dinit process uid/gid if uid/gid // wasn't specified by service if (logfile_uid == (uid_t) -1) logfile_uid = getuid(); @@ -1872,12 +1882,38 @@ void process_service_line(settings_wrapper &settings, const char *name, const ch "ready-notification", input_pos); } } + else if (starts_with(notify_setting, "socket:")) { + settings.ready_socket_path = notify_setting.substr(7 /* len 'socket:' */); + if (settings.ready_socket_path.empty()) { + throw service_description_exc(name, "invalid readiness socket path", + "ready-notification", input_pos); + } + } else { throw service_description_exc(name, "unrecognised setting: " + notify_setting, "ready-notification", input_pos); } break; } + case setting_id_t::READY_SOCKET_PERMISSIONS: + { + string sock_perm_str = read_setting_value(input_pos, i, end, nullptr); + settings.ready_socket_perms = parse_perms(input_pos, sock_perm_str, name, "ready-socket-permissions"); + break; + } + case setting_id_t::READY_SOCKET_UID: + { + string sock_uid_s = read_setting_value(input_pos, i, end, nullptr); + settings.ready_socket_uid = parse_uid_param(input_pos, sock_uid_s, name, "ready-socket-uid", + &settings.ready_socket_uid_gid); + break; + } + case setting_id_t::READY_SOCKET_GID: + { + string sock_gid_s = read_setting_value(input_pos, i, end, nullptr); + settings.ready_socket_gid = parse_gid_param(input_pos, sock_gid_s, "ready-socket-gid", name); + break; + } case setting_id_t::INITTAB_ID: { string inittab_setting = read_setting_value(input_pos, i, end, nullptr); diff --git a/src/includes/proc-service.h b/src/includes/proc-service.h index 3f3dc176..d7418f28 100644 --- a/src/includes/proc-service.h +++ b/src/includes/proc-service.h @@ -227,6 +227,8 @@ class base_process_service : public service_record int notification_fd = -1; // If readiness notification is via fd int log_output_fd = -1; // If logging via buffer/pipe, write end of the pipe int log_input_fd = -1; // If logging via buffer/pipe, read end of the pipe + int ready_socket_fd = -1; // For socket ready notification, this is the file descriptor for the socket. + struct sockaddr_un *ready_socket_name = nullptr; // Since ready socket is UDP, we need this for recvfrom(). // Only one of waiting_restart_timer and waiting_stopstart_timer should be set at any time. // They indicate that the process timer is armed (and why). @@ -298,6 +300,9 @@ class base_process_service : public service_record // Open the activation socket, return false on failure bool open_socket() noexcept; + // Open the readiness socket, return false on failure + bool open_ready_socket() noexcept; + // Get the readiness notification watcher for this service, if it has one; may return nullptr. virtual ready_notify_watcher *get_ready_watcher() noexcept { diff --git a/src/includes/service.h b/src/includes/service.h index 6de8b50d..b2218afb 100644 --- a/src/includes/service.h +++ b/src/includes/service.h @@ -327,6 +327,11 @@ class service_record uid_t socket_uid = -1; // socket user id or -1 gid_t socket_gid = -1; // socket group id or -1 + string ready_socket_path; // path to the socket for ready-notification + int ready_socket_perms = 0; + uid_t ready_socket_uid = -1; + gid_t ready_socket_gid = -1; + stopped_reason_t stop_reason = stopped_reason_t::NORMAL; // reason why stopped string start_on_completion; // service to start when this one completes @@ -601,6 +606,15 @@ class service_record this->socket_gid = socket_gid; } + void set_ready_socket_details(string &&socket_path, int socket_perms, uid_t socket_uid, uid_t socket_gid) + noexcept + { + this->ready_socket_path = std::move(socket_path); + this->ready_socket_perms = socket_perms; + this->ready_socket_uid = socket_uid; + this->ready_socket_gid = socket_gid; + } + // Set the service that this one "chains" to. When this service completes, the named service is started. void set_chain_to(string &&chain_to) noexcept { diff --git a/src/load-service.cc b/src/load-service.cc index fd4f46b2..4f1b2b51 100644 --- a/src/load-service.cc +++ b/src/load-service.cc @@ -851,6 +851,8 @@ service_record * dirload_service_set::load_reload_service(const char *fullname, rval->set_flags(settings.onstart_flags); rval->set_socket_details(std::move(settings.socket_path), settings.socket_perms, settings.socket_uid, settings.socket_gid); + rval->set_ready_socket_details(std::move(settings.ready_socket_path), + settings.ready_socket_perms, settings.ready_socket_uid, settings.ready_socket_gid); rval->set_chain_to(std::move(settings.chain_to_name)); rval->set_environment(std::move(srv_env)); diff --git a/src/proc-service.cc b/src/proc-service.cc index 96e4c06e..8898c74f 100644 --- a/src/proc-service.cc +++ b/src/proc-service.cc @@ -175,8 +175,20 @@ rearm ready_notify_watcher::fd_event(eventloop_t &, int fd, int flags) noexcept { char buf[128]; if (service->get_state() == service_state_t::STARTING) { - // can we actually read anything from the notification pipe? - int r = bp_sys::read(fd, buf, sizeof(buf)); + // can we actually read anything from the notification pipe/socket? + ssize_t r; + if (fd != service->ready_socket_fd) { + r = bp_sys::read(fd, buf, sizeof(buf)); + } + else { + socklen_t alen = service->ready_socket_path.length() + sizeof(sa_family_t); + r = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)service->ready_socket_name, &alen); + if (r > 0 && (r != strlen("READY=1") || memcmp(buf, "READY=1", strlen("READY=1")))) { + /* ignore datagram */ + errno = EAGAIN; + r = -1; + } + } if (r > 0) { if (service->waiting_stopstart_timer) { service->process_timer.stop_timer(event_loop); @@ -195,7 +207,7 @@ rearm ready_notify_watcher::fd_event(eventloop_t &, int fd, int flags) noexcept } service->services->process_queues(); } - else { + else if (fd != service->ready_socket_fd) { // Just keep consuming data from the pipe: int r = bp_sys::read(fd, buf, sizeof(buf)); if (r == 0) { @@ -204,6 +216,10 @@ rearm ready_notify_watcher::fd_event(eventloop_t &, int fd, int flags) noexcept service->notification_fd = -1; return rearm::DISARM; } + } else { + // Just consume the datagram + socklen_t alen = service->ready_socket_path.length() + sizeof(sa_family_t); + recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)service->ready_socket_name, &alen); } return rearm::REARM; diff --git a/src/run-child-proc.cc b/src/run-child-proc.cc index be443624..71cae077 100644 --- a/src/run-child-proc.cc +++ b/src/run-child-proc.cc @@ -156,12 +156,16 @@ void base_process_service::run_child_proc(run_proc_params params) noexcept err.stage = exec_stage::SET_NOTIFYFD_VAR; // We need to do an allocation: the variable name length, '=', and space for the value, // and nul terminator: - int notify_var_len = strlen(notify_var); - int req_sz = notify_var_len + ((CHAR_BIT * sizeof(int) - 1 + 2) / 3) + 1; - char * var_str = (char *) malloc(req_sz); - if (var_str == nullptr) goto failure_out; - snprintf(var_str, req_sz, "%s=%d", notify_var, notify_fd); - service_env.set_var(var_str); + if (!strchr(notify_var, '=')) { + int notify_var_len = strlen(notify_var); + int req_sz = notify_var_len + ((CHAR_BIT * sizeof(int) - 1 + 2) / 3) + 1; + char * var_str = (char *) malloc(req_sz); + if (var_str == nullptr) goto failure_out; + snprintf(var_str, req_sz, "%s=%d", notify_var, notify_fd); + service_env.set_var(var_str); + } else { + service_env.set_var(notify_var); + } } // Set up Systemd-style socket activation: diff --git a/src/settings.cc b/src/settings.cc index f789bcf7..c5915a6f 100644 --- a/src/settings.cc +++ b/src/settings.cc @@ -47,6 +47,9 @@ setting_details all_settings[] = { {"run-as", setting_id_t::RUN_AS, false, true, false}, {"chain-to", setting_id_t::CHAIN_TO, false, true, false}, {"ready-notification", setting_id_t::READY_NOTIFICATION, false, true, false}, + {"ready-socket-permissions", setting_id_t::READY_SOCKET_PERMISSIONS, false, true, false}, + {"ready-socket-uid", setting_id_t::READY_SOCKET_UID, false, true, false}, + {"ready-socket-gid", setting_id_t::READY_SOCKET_GID, false, true, false}, // Note: inittab-_ settings are supported even if functionality is not built in {"inittab-id", setting_id_t::INITTAB_ID, false, true, false},