Skip to content

Commit 2db9710

Browse files
committed
Implement systemd-style readiness notification via NOTIFY_SOCKET
The service may specify this with ready-notification=socket:path. This will result in dinit creating an abstract datagram socket and perform reads on it. As soon as the READY=1 datagram arrives, the readiness notification is signaled.
1 parent e7ad5b1 commit 2db9710

8 files changed

Lines changed: 167 additions & 52 deletions

File tree

src/baseproc-service.cc

Lines changed: 76 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ void base_process_service::do_smooth_recovery() noexcept
3030

3131
bool base_process_service::bring_up() noexcept
3232
{
33-
if (!open_socket()) {
33+
if (!open_socket() || !open_ready_socket()) {
34+
becoming_inactive();
3435
return false;
3536
}
3637

@@ -152,7 +153,7 @@ bool base_process_service::start_ps_process(const std::vector<const char *> &cmd
152153

153154
int control_socket[2] = {-1, -1};
154155
int notify_pipe[2] = {-1, -1};
155-
bool have_notify = !notification_var.empty() || force_notification_fd != -1;
156+
bool have_notify = !notification_var.empty() || ready_socket_fd >= 0 || force_notification_fd != -1;
156157
ready_notify_watcher * rwatcher = have_notify ? get_ready_watcher() : nullptr;
157158
bool ready_watcher_registered = false;
158159

@@ -175,7 +176,7 @@ bool base_process_service::start_ps_process(const std::vector<const char *> &cmd
175176
}
176177
}
177178

178-
if (have_notify) {
179+
if (have_notify && ready_socket_fd < 0) {
179180
// Create a notification pipe:
180181
if (bp_sys::pipe2(notify_pipe, 0) != 0) {
181182
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<const char *> &cmd
185186
// Set the read side as close-on-exec:
186187
int fdflags = bp_sys::fcntl(notify_pipe[0], F_GETFD);
187188
bp_sys::fcntl(notify_pipe[0], F_SETFD, fdflags | FD_CLOEXEC);
189+
} else if (have_notify) {
190+
notification_var = "NOTIFY_SOCKET=" + ready_socket_path;
191+
}
188192

193+
if (have_notify) {
189194
// add, but don't yet enable, readiness watcher:
190195
try {
191-
rwatcher->add_watch(event_loop, notify_pipe[0], dasynq::IN_EVENTS, false);
196+
rwatcher->add_watch(event_loop, ready_socket_fd >= 0 ? ready_socket_fd : notify_pipe[0], dasynq::IN_EVENTS, false);
192197
ready_watcher_registered = true;
193198
}
194199
catch (std::exception &exc) {
@@ -251,7 +256,7 @@ bool base_process_service::start_ps_process(const std::vector<const char *> &cmd
251256
run_params.unmask_sigint = onstart_flags.unmask_intr;
252257
run_params.csfd = control_socket[1];
253258
run_params.socket_fd = socket_fd;
254-
run_params.notify_fd = notify_pipe[1];
259+
run_params.notify_fd = ready_socket_fd >= 0 ? ready_socket_fd : notify_pipe[1];
255260
run_params.force_notify_fd = force_notification_fd;
256261
run_params.notify_var = notification_var.c_str();
257262
run_params.env_file = env_file.c_str();
@@ -469,86 +474,116 @@ void base_process_service::becoming_inactive() noexcept
469474
close(socket_fd);
470475
socket_fd = -1;
471476
}
472-
}
473-
474-
bool base_process_service::open_socket() noexcept
475-
{
476-
if (socket_path.empty() || socket_fd != -1) {
477-
// No socket, or already open
478-
return true;
477+
if (ready_socket_fd != -1) {
478+
close(ready_socket_fd);
479+
ready_socket_fd = -1;
479480
}
481+
free(ready_socket_name);
482+
ready_socket_name = nullptr;
483+
}
480484

481-
const char * saddrname = socket_path.c_str();
482-
485+
static int open_sock(const char *path, const std::string &svcname, int type,
486+
uid_t uid, gid_t gid, int perms, struct sockaddr_un *&name) noexcept {
483487
// Check the specified socket path
484488
struct stat stat_buf;
485-
if (stat(saddrname, &stat_buf) == 0) {
489+
if (stat(path, &stat_buf) == 0) {
486490
if ((stat_buf.st_mode & S_IFSOCK) == 0) {
487491
// Not a socket
488-
log(loglevel_t::ERROR, get_name(), ": activation socket file exists (and is not a socket)");
489-
return false;
492+
log(loglevel_t::ERROR, svcname, ": socket file exists (and is not a socket)");
493+
return -1;
490494
}
491495
}
492496
else if (errno != ENOENT) {
493497
// Other error
494-
log(loglevel_t::ERROR, get_name(), ": error checking activation socket: ", strerror(errno));
495-
return false;
498+
log(loglevel_t::ERROR, svcname, ": error checking socket: ", strerror(errno));
499+
return -1;
496500
}
497501

498502
// Remove stale socket file (if it exists).
499503
// We won't test the return from unlink - if it fails other than due to ENOENT, we should get an
500504
// error when we try to create the socket anyway.
501-
unlink(saddrname);
505+
unlink(path);
502506

503-
uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + socket_path.length() + 1;
504-
struct sockaddr_un * name = static_cast<sockaddr_un *>(malloc(sockaddr_size));
507+
uint sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen(path) + 1;
508+
name = static_cast<sockaddr_un *>(malloc(sockaddr_size));
505509
if (name == nullptr) {
506-
log(loglevel_t::ERROR, get_name(), ": opening activation socket: out of memory");
507-
return false;
510+
log(loglevel_t::ERROR, svcname, ": opening socket: out of memory");
511+
return -1;
508512
}
509513

510514
name->sun_family = AF_UNIX;
511-
strcpy(name->sun_path, saddrname);
515+
strcpy(name->sun_path, path);
512516

513-
int sockfd = dinit_socket(AF_UNIX, SOCK_STREAM, 0, SOCK_NONBLOCK | SOCK_CLOEXEC);
517+
int sockfd = dinit_socket(AF_UNIX, type, 0, SOCK_NONBLOCK | SOCK_CLOEXEC);
514518
if (sockfd == -1) {
515-
log(loglevel_t::ERROR, get_name(), ": error creating activation socket: ", strerror(errno));
519+
log(loglevel_t::ERROR, svcname, ": error creating socket: ", strerror(errno));
516520
free(name);
517-
return false;
521+
return -1;
518522
}
519523

520524
if (bind(sockfd, (struct sockaddr *) name, sockaddr_size) == -1) {
521-
log(loglevel_t::ERROR, get_name(), ": error binding activation socket: ", strerror(errno));
525+
log(loglevel_t::ERROR, svcname, ": error binding socket: ", strerror(errno));
522526
close(sockfd);
523527
free(name);
524-
return false;
528+
return -1;
525529
}
526530

527-
free(name);
528-
529531
// POSIX (1003.1, 2013) says that fchown and fchmod don't necessarily work on sockets. We have to
530532
// use chown and chmod instead.
531-
if (chown(saddrname, socket_uid, socket_gid)) {
532-
log(loglevel_t::ERROR, get_name(), ": error setting activation socket owner/group: ",
533+
if (chown(path, uid, gid)) {
534+
log(loglevel_t::ERROR, svcname, ": error setting socket owner/group: ",
533535
strerror(errno));
534536
close(sockfd);
535-
return false;
537+
return -1;
536538
}
537539

538-
if (chmod(saddrname, socket_perms) == -1) {
539-
log(loglevel_t::ERROR, get_name(), ": Error setting activation socket permissions: ",
540+
if (chmod(path, perms) == -1) {
541+
log(loglevel_t::ERROR, svcname, ": Error setting socket permissions: ",
540542
strerror(errno));
541543
close(sockfd);
542-
return false;
544+
return -1;
543545
}
544546

545-
if (listen(sockfd, 128) == -1) { // 128 "seems reasonable".
546-
log(loglevel_t::ERROR, ": error listening on activation socket: ", strerror(errno));
547+
if (type != SOCK_DGRAM && listen(sockfd, 128) == -1) { // 128 "seems reasonable".
548+
log(loglevel_t::ERROR, ": error listening on socket: ", strerror(errno));
547549
close(sockfd);
550+
return -1;
551+
}
552+
553+
return sockfd;
554+
}
555+
556+
bool base_process_service::open_socket() noexcept
557+
{
558+
if (socket_path.empty() || socket_fd != -1) {
559+
// No socket, or already open
560+
return true;
561+
}
562+
563+
struct sockaddr_un *name = nullptr;
564+
socket_fd = open_sock(socket_path.c_str(), get_name(), SOCK_STREAM, socket_uid,
565+
socket_gid, socket_perms, name);
566+
free(name);
567+
568+
return socket_fd >= 0;
569+
}
570+
571+
bool base_process_service::open_ready_socket() noexcept
572+
{
573+
if (ready_socket_path.empty() || ready_socket_fd != -1) {
574+
// No socket, or already open
575+
return true;
576+
}
577+
578+
ready_socket_fd = open_sock(ready_socket_path.c_str(), get_name(), SOCK_DGRAM,
579+
ready_socket_uid, ready_socket_gid, ready_socket_perms, ready_socket_name);
580+
581+
if (ready_socket_fd < 0) {
582+
free(ready_socket_name);
583+
ready_socket_name = nullptr;
548584
return false;
549585
}
550586

551-
socket_fd = sockfd;
552587
return true;
553588
}
554589

src/includes/load-service.h

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ enum class setting_id_t {
225225
LOGFILE_GID, LOG_TYPE, LOG_BUFFER_SIZE, CONSUMER_OF, RESTART, SMOOTH_RECOVERY, OPTIONS,
226226
LOAD_OPTIONS, TERM_SIGNAL, TERMSIGNAL /* deprecated */, RESTART_LIMIT_INTERVAL, RESTART_DELAY,
227227
RESTART_LIMIT_COUNT, STOP_TIMEOUT, START_TIMEOUT, RUN_AS, CHAIN_TO, READY_NOTIFICATION,
228-
INITTAB_ID, INITTAB_LINE,
228+
READY_SOCKET_PERMISSIONS, READY_SOCKET_UID, READY_SOCKET_GID, INITTAB_ID, INITTAB_LINE,
229229
// Prefixed with SETTING_ to avoid name collision with system macros:
230230
SETTING_RLIMIT_NOFILE, SETTING_RLIMIT_CORE, SETTING_RLIMIT_DATA, SETTING_RLIMIT_ADDRSPACE,
231231
// Possibly unsupported depending on platform/build options:
@@ -1282,12 +1282,17 @@ class service_settings_wrapper
12821282
auto_restart_mode auto_restart = auto_restart_mode::DEFAULT_AUTO_RESTART;
12831283
bool smooth_recovery = false;
12841284
string socket_path;
1285+
string ready_socket_path;
12851286
int socket_perms = 0666;
12861287
// Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
12871288
// invalid value, so it's safe to assume that we can do the same:
12881289
uid_t socket_uid = -1;
12891290
gid_t socket_uid_gid = -1; // primary group of socket user if known
12901291
gid_t socket_gid = -1;
1292+
int ready_socket_perms = 0600;
1293+
uid_t ready_socket_uid = -1;
1294+
gid_t ready_socket_uid_gid = -1;
1295+
gid_t ready_socket_gid = -1;
12911296
// Restart limit interval / count; default is 10 seconds, 3 restarts:
12921297
timespec restart_interval = { .tv_sec = 10, .tv_nsec = 0 };
12931298
int max_restarts = 3;
@@ -1362,6 +1367,9 @@ class service_settings_wrapper
13621367
if (!socket_path.empty()) {
13631368
report_lint("'socket-listen' specified, but ignored for the specified (or default) service type'.");
13641369
}
1370+
if (!ready_socket_path.empty()) {
1371+
report_lint("'ready-notification' specified, but ignored for the specified (or default) service type'.");
1372+
}
13651373
#if USE_UTMPX
13661374
if (inittab_id[0] != 0 || inittab_line[0] != 0) {
13671375
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
13951403
report_error("process ID file ('pid-file') not specified for bgprocess service.");
13961404
}
13971405

1398-
if (readiness_fd != -1 || !readiness_var.empty()) {
1406+
if (readiness_fd != -1 || !ready_socket_path.empty() || !readiness_var.empty()) {
13991407
report_error("readiness notification ('ready-notification') is not supported "
14001408
"for bgprocess services.");
14011409
}
@@ -1421,6 +1429,7 @@ class service_settings_wrapper
14211429
};
14221430

14231431
do_resolve("socket-listen", socket_path);
1432+
do_resolve("ready-notification", ready_socket_path);
14241433
do_resolve("logfile", logfile);
14251434
do_resolve("working-dir", working_dir);
14261435
do_resolve("pid-file", pid_file);
@@ -1429,6 +1438,7 @@ class service_settings_wrapper
14291438
// If socket_gid hasn't been explicitly set, but the socket_uid was specified as a name (and
14301439
// we therefore recovered the primary group), use the primary group of the specified user.
14311440
if (socket_gid == (gid_t)-1) socket_gid = socket_uid_gid;
1441+
if (ready_socket_gid == (gid_t)-1) ready_socket_gid = ready_socket_uid_gid;
14321442
// Also for logfile_uid/gid, we reset logfile ownership to dinit process uid/gid if uid/gid
14331443
// wasn't specified by service
14341444
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
18721882
"ready-notification", input_pos);
18731883
}
18741884
}
1885+
else if (starts_with(notify_setting, "socket:")) {
1886+
settings.ready_socket_path = notify_setting.substr(7 /* len 'socket:' */);
1887+
if (settings.ready_socket_path.empty()) {
1888+
throw service_description_exc(name, "invalid readiness socket path",
1889+
"ready-notification", input_pos);
1890+
}
1891+
}
18751892
else {
18761893
throw service_description_exc(name, "unrecognised setting: " + notify_setting,
18771894
"ready-notification", input_pos);
18781895
}
18791896
break;
18801897
}
1898+
case setting_id_t::READY_SOCKET_PERMISSIONS:
1899+
{
1900+
string sock_perm_str = read_setting_value(input_pos, i, end, nullptr);
1901+
settings.ready_socket_perms = parse_perms(input_pos, sock_perm_str, name, "ready-socket-permissions");
1902+
break;
1903+
}
1904+
case setting_id_t::READY_SOCKET_UID:
1905+
{
1906+
string sock_uid_s = read_setting_value(input_pos, i, end, nullptr);
1907+
settings.ready_socket_uid = parse_uid_param(input_pos, sock_uid_s, name, "ready-socket-uid",
1908+
&settings.ready_socket_uid_gid);
1909+
break;
1910+
}
1911+
case setting_id_t::READY_SOCKET_GID:
1912+
{
1913+
string sock_gid_s = read_setting_value(input_pos, i, end, nullptr);
1914+
settings.ready_socket_gid = parse_gid_param(input_pos, sock_gid_s, "ready-socket-gid", name);
1915+
break;
1916+
}
18811917
case setting_id_t::INITTAB_ID:
18821918
{
18831919
string inittab_setting = read_setting_value(input_pos, i, end, nullptr);

src/includes/proc-service.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ class base_process_service : public service_record
227227
int notification_fd = -1; // If readiness notification is via fd
228228
int log_output_fd = -1; // If logging via buffer/pipe, write end of the pipe
229229
int log_input_fd = -1; // If logging via buffer/pipe, read end of the pipe
230+
int ready_socket_fd = -1; // For socket ready notification, this is the file descriptor for the socket.
231+
struct sockaddr_un *ready_socket_name = nullptr; // Since ready socket is UDP, we need this for recvfrom().
230232

231233
// Only one of waiting_restart_timer and waiting_stopstart_timer should be set at any time.
232234
// They indicate that the process timer is armed (and why).
@@ -298,6 +300,9 @@ class base_process_service : public service_record
298300
// Open the activation socket, return false on failure
299301
bool open_socket() noexcept;
300302

303+
// Open the readiness socket, return false on failure
304+
bool open_ready_socket() noexcept;
305+
301306
// Get the readiness notification watcher for this service, if it has one; may return nullptr.
302307
virtual ready_notify_watcher *get_ready_watcher() noexcept
303308
{

src/includes/service.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,11 @@ class service_record
327327
uid_t socket_uid = -1; // socket user id or -1
328328
gid_t socket_gid = -1; // socket group id or -1
329329

330+
string ready_socket_path; // path to the socket for ready-notification
331+
int ready_socket_perms = 0;
332+
uid_t ready_socket_uid = -1;
333+
gid_t ready_socket_gid = -1;
334+
330335
stopped_reason_t stop_reason = stopped_reason_t::NORMAL; // reason why stopped
331336

332337
string start_on_completion; // service to start when this one completes
@@ -601,6 +606,15 @@ class service_record
601606
this->socket_gid = socket_gid;
602607
}
603608

609+
void set_ready_socket_details(string &&socket_path, int socket_perms, uid_t socket_uid, uid_t socket_gid)
610+
noexcept
611+
{
612+
this->ready_socket_path = std::move(socket_path);
613+
this->ready_socket_perms = socket_perms;
614+
this->ready_socket_uid = socket_uid;
615+
this->ready_socket_gid = socket_gid;
616+
}
617+
604618
// Set the service that this one "chains" to. When this service completes, the named service is started.
605619
void set_chain_to(string &&chain_to) noexcept
606620
{

src/load-service.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,8 @@ service_record * dirload_service_set::load_reload_service(const char *fullname,
851851
rval->set_flags(settings.onstart_flags);
852852
rval->set_socket_details(std::move(settings.socket_path), settings.socket_perms,
853853
settings.socket_uid, settings.socket_gid);
854+
rval->set_ready_socket_details(std::move(settings.ready_socket_path),
855+
settings.ready_socket_perms, settings.ready_socket_uid, settings.ready_socket_gid);
854856
rval->set_chain_to(std::move(settings.chain_to_name));
855857
rval->set_environment(std::move(srv_env));
856858

0 commit comments

Comments
 (0)