for this service. Starting this service will automatically start
the named service.
+socket-listen = (socket path)
+ Pre-open a socket for the service and pass it to the service using the
+ Systemd activation protocol. This by itself does not give so called
+ "socket activation", but does allow that any process trying to connect
+ to the specified socket will be able to do so, even before the service
+ is properly prepared to accept connections.
+
+socket-permissions = (octal permissions mask)
+ Gives the permissions for the socket specified using socket-listen.
+ Normally this will be 600 (user access only), 660 (user and group
+ access), or 666 (all users).
+
termsignal = HUP | INT | QUIT | USR1 | USR2
Specifies an additional signal to send to the process when requesting it
to terminate (applies to 'process' services only). SIGTERM is always
int term_signal = -1; // additional termination signal
bool auto_restart = false;
bool smooth_recovery = false;
+ string socket_path;
+ int socket_perms = 0666;
string line;
ifstream service_file;
if (setting == "command") {
command = read_setting_value(i, end, &command_offsets);
}
+ else if (setting == "socket-listen") {
+ socket_path = read_setting_value(i, end, nullptr);
+ }
+ else if (setting == "socket-permissions") {
+ string sock_perm_str = read_setting_value(i, end, nullptr);
+ std::size_t ind = 0;
+ try {
+ socket_perms = std::stoi(sock_perm_str, &ind, 8);
+ if (ind != sock_perm_str.length()) {
+ throw std::logic_error("");
+ }
+ }
+ catch (std::logic_error &exc) {
+ throw ServiceDescriptionExc(name, "socket-permissions: Badly-formed or out-of-range numeric value");
+ }
+ }
else if (setting == "stop-command") {
stop_command = read_setting_value(i, end, &stop_command_offsets);
}
rval->setOnstartFlags(onstart_flags);
rval->setExtraTerminationSignal(term_signal);
rval->set_pid_file(std::move(pid_file));
+ rval->set_socket_details(std::move(socket_path), socket_perms);
*iter = rval;
break;
}
#include <sstream>
#include <iterator>
#include <memory>
+#include <cstddef>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
+#include <sys/un.h>
+#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
// Desired state is "started".
start();
}
+ else if (socket_fd != -1) {
+ close(socket_fd);
+ socket_fd = -1;
+ }
}
void ServiceRecord::process_child_callback(struct ev_loop *loop, ev_child *w, int revents) noexcept
return all_deps_started;
}
+bool ServiceRecord::open_socket() noexcept
+{
+ if (socket_path.empty() || socket_fd != -1) {
+ // No socket, or already open
+ return true;
+ }
+
+ const char * saddrname = socket_path.c_str();
+ 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::ERROR, service_name, ": Opening activation socket: out of memory");
+ return false;
+ }
+
+ // Un-link any stale socket. TODO: safety check? should at least confirm the path is a socket.
+ unlink(saddrname);
+
+ name->sun_family = AF_UNIX;
+ strcpy(name->sun_path, saddrname);
+
+ int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+ if (sockfd == -1) {
+ log(LogLevel::ERROR, service_name, ": Error creating activation socket: ", strerror(errno));
+ free(name);
+ return false;
+ }
+
+ if (bind(sockfd, (struct sockaddr *) name, sockaddr_size) == -1) {
+ log(LogLevel::ERROR, service_name, ": Error binding activation socket: ", strerror(errno));
+ close(sockfd);
+ free(name);
+ return false;
+ }
+
+ free(name);
+
+ if (chmod(saddrname, socket_perms) == -1) {
+ log(LogLevel::ERROR, service_name, ": Error setting activation socket permissions: ", strerror(errno));
+ close(sockfd);
+ return false;
+ }
+
+ if (listen(sockfd, 128) == -1) { // 128 "seems reasonable".
+ log(LogLevel::ERROR, ": Error listening on activation socket: ", strerror(errno));
+ close(sockfd);
+ return false;
+ }
+
+ socket_fd = sockfd;
+ return true;
+}
+
void ServiceRecord::allDepsStarted(bool has_console) noexcept
{
if (onstart_flags.runs_on_console && ! has_console) {
waiting_for_deps = false;
+ if (! open_socket()) {
+ failed_to_start();
+ }
+
if (service_type == ServiceType::PROCESS || service_type == ServiceType::BGPROCESS
|| service_type == ServiceType::SCRIPTED) {
bool start_success = start_ps_process();
// from here until exit().
ev_default_destroy(); // won't need that on this side, free up fds.
+ constexpr int bufsz = ((CHAR_BIT * sizeof(pid_t) - 1) / 3 + 2) + 11;
+ // "LISTEN_PID=" - 11 characters
+ char nbuf[bufsz];
+
+ if (socket_fd != -1) {
+ dup2(socket_fd, 3);
+ if (socket_fd != 3) {
+ close(socket_fd);
+ }
+
+ if (putenv(const_cast<char *>("LISTEN_FDS=1"))) goto failure_out;
+
+ snprintf(nbuf, bufsz, "LISTEN_PID=%jd", static_cast<intmax_t>(getpid()));
+
+ if (putenv(nbuf)) goto failure_out;
+ }
+
if (! on_console) {
// Re-set stdin, stdout, stderr
close(0); close(1); close(2);
execvp(exec_arg_parts[0], const_cast<char **>(args));
// If we got here, the exec failed:
+ failure_out:
int exec_status = errno;
write(pipefd[1], &exec_status, sizeof(int));
exit(0);
// Next service (after this one) in the queue for the console:
ServiceRecord *next_for_console;
+
+ std::unordered_set<ServiceListener *> listeners;
// Process services:
bool force_stop; // true if the service must actually stop. This is the
// case if for example the process dies; the service,
// and all its dependencies, MUST be stopped.
- std::unordered_set<ServiceListener *> listeners;
-
int term_signal = -1; // signal to use for process termination
+
+ string socket_path; // path to the socket for socket-activation service
+ int socket_perms; // socket permissions ("mode")
// Implementation details
- pid_t pid; // 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; // Exit status, if the process has exited (pid == -1).
+ 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; // 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.
ev_child child_listener;
ev_io child_status_listener;
// Read the pid-file, return false on failure
bool read_pid_file() noexcept;
+ // Open the activation socket, return false on failure
+ bool open_socket() noexcept;
+
// Check whether dependencies have started, and optionally ask them to start
bool startCheckDependencies(bool do_start) noexcept;
{
this->pid_file = pid_file;
}
+
+ void set_socket_details(string &&socket_path, int socket_perms) noexcept
+ {
+ this->socket_path = socket_path;
+ this->socket_perms = socket_perms;
+ }
const char *getServiceName() const noexcept { return service_name.c_str(); }
ServiceState getState() const noexcept { return service_state; }