Add service(8).

This commit is contained in:
Jonas 'Sortie' Termansen 2024-05-07 02:25:22 +02:00
parent 08a834d932
commit 189e79d62d
7 changed files with 1872 additions and 24 deletions

1
init/.gitignore vendored
View file

@ -1,2 +1,3 @@
init
service
*.o

View file

@ -8,26 +8,26 @@ CFLAGS?=$(OPTLEVEL)
CFLAGS:=$(CFLAGS) -Wall -Wextra
BINARY=init
BINARIES=\
init \
service \
OBJS=\
init.o \
MANPAGES8=\
init.8 \
service.8 \
all: $(BINARY)
all: $(BINARIES)
.PHONY: all install clean
$(BINARY): $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o $(BINARY) -lmount $(LIBS)
%.o: %.c
$(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) -c $< -o $@
%: %.c
$(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) $< -o $@ -lmount $(LIBS)
install: all
mkdir -p $(DESTDIR)$(SBINDIR)
install $(BINARY) $(DESTDIR)$(SBINDIR)
install $(BINARIES) $(DESTDIR)$(SBINDIR)
mkdir -p $(DESTDIR)$(MANDIR)/man8
cp init.8 $(DESTDIR)$(MANDIR)/man8/init.8
install $(MANPAGES8) $(DESTDIR)$(MANDIR)/man8
clean:
rm -f $(BINARY) $(OBJS) *.o
rm -f $(BINARIES) $(OBJS) *.o

View file

@ -301,6 +301,7 @@ exits with the same exit status as its target session if it terminates normally.
.Xr login 8 ,
.Xr poweroff 8 ,
.Xr reboot 8 ,
.Xr service 8 ,
.Xr sysmerge 8 ,
.Xr update-initrd 8
.Sh SECURITY CONSIDERATIONS

File diff suppressed because it is too large Load diff

391
init/service.8 Normal file
View file

@ -0,0 +1,391 @@
.Dd May 24, 2024
.Dt SERVICE 8
.Os
.Sh NAME
.Nm service
.Nd daemon maintenance
.Sh SYNOPSIS
.Nm service
.Op Fl lr
.Op Fl \-s Ns "=" Ns source-daemon
.Op Fl \-exit-code
.Op Fl \-no-await
.Op Fl \-no-optional
.Op Fl \-raw
.Op Fl \-source Ns "=" Ns source-daemon
.Ar daemon
.Oo
.Sy dependents
|
.Sy disable
|
.Sy edges
|
.Sy enable
|
.Sy exit-code
|
.Sy kill
|
.Sy pid
|
.Sy reconfigure
|
.Sy reload
|
.Sy requirements
|
.Sy restart
|
.Sy signal
|
.Sy start
|
.Sy state
|
.Sy status
|
.Sy stop
|
.Sy terminate
.Oc
.Sh DESCRIPTION
.Nm
performs maintenance of daemons run by
.Xr init 8
as configured in
.Xr init 5 .
The daemons are serviced by connecting to the
.Nm init
process and writing the requested command to the
.Pa /var/run/init
filesytem socket.
.Pp
The options are as follows:
.Bl -tag -width "12345678"
.It Fl s , Fl \-source-daemon Ns "=" Ns Ar source-daemon
When modifying a dependency using the
.Sy enable , disable , start
and
.Sy stop
commands, use the
.Ar source-daemon
as the source daemon in the dependency on the target
.Ar daemon .
The default the
.Sy local
daemon which is the parent of the locally configured daemons.
.It Fl \-exit-code
Set the
.Sy exit-code
flag on the dependency created in the
.Sy enable
and
.Sy start
commands.
.It Fl \-no-await
Set the
.Sy no-await
flag on the dependency created in the
.Sy enable
and
.Sy start
commands.
.It Fl \-no-optional
Unset the
.Sy optional
flag on the dependency created in the
.Sy enable
and
.Sy start
commands.
The default is to set the
.Sy optional
flag, which is the opposite of the
.Xr init 5
.Sy require
declaration where dependencies are mandatory by default.
.It Fl l , \-list
Write a table containing the status of every loaded daemon in the format of the
.Sy status
command.
.It Fl r , \-raw
Write the command and additional operands as a raw message without verification
on the
.Nm init
socket
and output the raw reply sent from
.Nm init.
.El
.Pp
The commands are as follows:
.Bl -tag -width "requirements"
.It Sy dependents
Write the incoming dependencies on the
.Ar daemon
in the format of the
.Sy edges
command, which explains why a daemon is running.
.It Sy disable
Permanently disable the
.Ar daemon
by removing the dependency on it from the configuration of the
.Ar source-daemon
(the
.Sy local
daemon by default, see
.Xr init 5 )
and then stopping it using the
.Sy stop
command.
The daemon will continue to run if any other daemon depends on it, which can
be diagnosed using the
.Sy dependents
command.
.It Sy edges
Write the incoming dependencies on the
.Ar daemon
and its outgoing dependencies in the format of the
.Sy require
declaration in
.Xr init 5
with an extra
.Ar source
operand:
.Pp
.Sy require
.Ar source
.Ar target
[exit-code]
[no-await]
[optional]
.It Sy enable
Permanently enable the
.Ar daemon
by adding it to the configuration of the
.Ar source-daemon
(the
.Sy local
daemon by default, see
.Xr init 5 )
with the dependency flags per
.Fl \-exit-code ,
.Fl \-no-await ,
and
.Fl \-no-optional
and starting it by sending the
.Sy start
command.
The daemon only starts if the
.Ar source-daemon
is running.
.It Sy exit-code
Write the exit code of the
.Ar daemon ,
or the empty string if it has not exited.
.It Sy kill
Kill the
.Ar daemon
by sending the
.Sy SIGKILL
signal as a last resort that may cause data loss.
.It Sy pid
Write the process id of the
.Ar daemon
if it is running, or the empty output if it
does not have a process.
Process ids can be recycled and are subject to inherent race conditions.
Prefer to use the other commands in
.Nm
that addresses the daemon by its symbolic name as
.Xr init 8
will ensure the command operates on the correct process.
.It Sy reconfigure
Reread the
.Xr init 5
configuration for the
.Ar daemon
and apply it after restarting the daemon.
.It Sy reload
Request the
.Ar daemon
gracefully reload its own configuration sending
the reload signal (usually
.Sy SIGHUP )
without restarting the daemon and without reloading the
.Xr init 5
configuration.
.It Sy requirements
Write the outgoing dependencies from the
.Ar daemon
in the format of the
.Sy edges
command, which explains what daemons the daemon needs.
.It Sy restart
Restart the
.Ar daemon
by terminating it and starting it up again afterwards.
.It Sy signal Ar signal
Send the
.Ar signal
in the symbolic signal name format (e.g.
.Sy SIGUSR1 )
to the
.Ar daemon .
.It Sy start
Start the
.Ar daemon
by asking
.Xr init 8
to add a runtime dependency from the
.Ar source-daemon
(the
.Sy local
daemon by default, see
.Xr init 5 )
to the daemon with the dependency flags per
.Fl \-exit-code ,
.Fl \-no-await ,
and
.Fl \-no-optional
and starting it by sending the
.Sy start
command.
The daemon only starts if the
.Ar source-daemon
is running.
.It Sy state
Write which the state the
.Ar daemon
is in.
.It Sy status
Write the status of the
.Ar daemon
as a single table row in the format:
.Pp
.Ar name
.Ar state
.Li pid Ns "=" Ns Ar pid
.Li exit Ns "=" Ns Ar exit-code
.Pp
The
.Ar state
is one of
.Sy terminated ,
.Sy scheduled ,
.Sy waiting ,
.Sy satisfied ,
.Sy starting ,
.Sy running ,
.Sy terminating ,
.Sy finishing ,
.Sy finished ,
or
.Sy failed .
The
.Ar pid
is the process id if any, or
.Li 0
otherwise.
The
.Ar exit-code
is the exit code of the daemon if it has exited, or the name of a signal that
killed it, or
.Li n/a
if the daemon has not exited.
.It Sy stop
Stop the
.Ar daemon
by asking
.Xr init 8
to remove the dependency from the
.Ar source-daemon
(the
.Sy local
daemon by default, see
.Xr init 5 )
on the daemon.
The daemon will continue to run as long if other daemon depends on it, which can
be diagnosed using the
.Sy dependents
command.
.It Sy terminate
Terminate the
.Ar daemon
gracefully by sending the
.Sy SIGTERM
signal and
.Sy SIGKILL
after a timeout.
Prefer the
.Sy stop
command if possible as the
.Sy terminate
command bypasses the reference count and may cause data loss if other daemons
malfunction when the daemon is unexpectedly terminated.
.El
.Sh ENVIRONMENT
.Bl -tag -width "INIT_SOCKET"
.It Ev INIT_SOCKET
.Xr init 8 Ns 's
filesystem socket for communication,
.Pa /var/run/init
by default.
.El
.Sh FILES
.Bl -tag -width "/etc/init/local" -compact
.It Pa /etc/init/
Daemon configuration for the local system (first in search path) (see
.Xr init 5 )
.It Pa /etc/init/local
Configuration for the
.Sy local
daemon (see
.Xr init 5 )
.El
.Sh EXIT STATUS
.Nm
will exit 0 on success and non-zero otherwise.
.Sh EXAMPLES
Permanently enable the sshd daemon:
.Bd -literal
$ service sshd enable
.Ed
.Pp
Permanently disable the ntpd daemon:
.Bd -literal
$ service ntpd disable
.Ed
.Pp
Temporarily start the nginx daemon without changing
.Pa /etc/init/local :
.Bd -literal
$ service nginx start
.Ed
.Pp
Temporarily disable the sshd daemon without changing
.Pa /etc/init/local :
.Bd -literal
$ service sshd stop
.Ed
.Pp
Temporarily stop the ntpd daemon and diagnose why it kept running due to the
.Sy time
daemon depending on it:
.Bd -literal
$ service ntpd stop
$ service ntpd state
running
$ service ntpd dependents
require time ntpd exit-code
$ service --source-daemon=time ntpd stop
$ service ntpd state
finished
.Ed
.Sh SEE ALSO
.Xr kill 1 ,
.Xr init 5 ,
.Xr halt 8 ,
.Xr init 8 ,
.Xr poweroff 8 ,
.Xr reboot 8

594
init/service.c Normal file
View file

@ -0,0 +1,594 @@
/*
* Copyright (c) 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* service.c
* Start and stop services.
*/
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <getopt.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <wchar.h>
static bool array_add(void*** array_ptr,
size_t* used_ptr,
size_t* length_ptr,
void* value)
{
void** array;
memcpy(&array, array_ptr, sizeof(array)); // Strict aliasing.
if ( *used_ptr == *length_ptr )
{
size_t length = *length_ptr;
if ( !length )
length = 4;
void** new_array = reallocarray(array, length, 2 * sizeof(void*));
if ( !new_array )
return false;
array = new_array;
memcpy(array_ptr, &array, sizeof(array)); // Strict aliasing.
*length_ptr = length * 2;
}
memcpy(array + (*used_ptr)++, &value, sizeof(value)); // Strict aliasing.
return true;
}
static char** tokenize(size_t* out_tokens_used, const char* string)
{
size_t tokens_used = 0;
size_t tokens_length = 0;
char** tokens = malloc(sizeof(char*));
if ( !tokens )
return NULL;
bool failed = false;
bool invalid = false;
while ( *string )
{
if ( isspace((unsigned char) *string) )
{
string++;
continue;
}
if ( *string == '#' )
break;
char* token;
size_t token_size;
FILE* fp = open_memstream(&token, &token_size);
if ( !fp )
{
failed = true;
break;
}
bool singly = false;
bool doubly = false;
bool escaped = false;
while ( *string )
{
char c = *string++;
if ( !escaped && !singly && !doubly && isspace((unsigned char) c) )
break;
if ( !escaped && !doubly && c == '\'' )
{
singly = !singly;
continue;
}
if ( !escaped && !singly && c == '"' )
{
doubly = !doubly;
continue;
}
if ( !singly && !escaped && c == '\\' )
{
escaped = true;
continue;
}
if ( escaped )
{
switch ( c )
{
case 'a': c = '\a'; break;
case 'b': c = '\b'; break;
case 'e': c = '\e'; break;
case 'f': c = '\f'; break;
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'v': c = '\v'; break;
default: break;
};
}
escaped = false;
if ( fputc((unsigned char) c, fp) == EOF )
{
failed = true;
break;
}
}
if ( singly || doubly || escaped )
{
fclose(fp);
free(token);
invalid = true;
break;
}
if ( fflush(fp) == EOF )
{
fclose(fp);
free(token);
failed = true;
break;
}
fclose(fp);
if ( !array_add((void***) &tokens, &tokens_used, &tokens_length,
token) )
{
free(token);
failed = true;
break;
}
}
if ( failed || invalid )
{
for ( size_t i = 0; i < tokens_used; i++ )
free(tokens[i]);
free(tokens);
if ( invalid )
errno = 0;
return NULL;
}
char** new_tokens = reallocarray(tokens, tokens_used, sizeof(char*));
if ( new_tokens )
tokens = new_tokens;
*out_tokens_used = tokens_used;
return tokens;
}
static char** receive(FILE* fp, size_t* out_tokens_used)
{
char* line = NULL;
size_t line_size;
if ( getline(&line, &line_size, fp) < 0 )
{
if ( ferror(fp) )
errx(1, "receiving reply: Unexpected end of connection");
else
err(1, "receiving reply");
}
char** result = tokenize(out_tokens_used, line);
free(line);
if ( !result )
{
if ( errno )
errx(1, "invalid reply: %s", line);
else
errx(1, "failed to parse replyt");
}
if ( !*out_tokens_used )
errx(1, "invalid empty reply");
if ( !strcmp(result[0], "ok") )
return result;
else if ( !strcmp(result[0], "error") )
errx(1, "error: %s", 2 <= *out_tokens_used ? result[1] : "Unknown");
else
errx(1, "unknown reply: %s", result[0]);
}
static int open_local_client_socket(const char* path, int flags)
{
size_t path_length = strlen(path);
size_t addr_size = offsetof(struct sockaddr_un, sun_path) + path_length + 1;
struct sockaddr_un* sockaddr = malloc(addr_size);
if ( !sockaddr )
return -1;
sockaddr->sun_family = AF_LOCAL;
strcpy(sockaddr->sun_path, path);
int fd = socket(AF_LOCAL, SOCK_STREAM | flags, 0);
if ( fd < 0 )
return free(sockaddr), -1;
if ( connect(fd, (const struct sockaddr*) sockaddr, addr_size) < 0 )
return close(fd), free(sockaddr), -1;
free(sockaddr);
return fd;
}
static void rewrite(const char* path, const char* daemon, const char* flags)
{
FILE* fp = fopen(path, "r");
if ( !fp && errno != ENOENT )
err(1, "%s", path);
char* out_path;
if ( asprintf(&out_path, "%s.XXXXXX", path) < 0 )
err(1, "malloc");
int out_fd = mkstemp(out_path);
if ( out_fd < 0 )
err(1, "mkstemp: %s.XXXXXX", path);
FILE* out = fdopen(out_fd, "w");
if ( !out )
{
unlink(out_path);
err(1, "fdopen");
}
bool found = false;
char* line = NULL;
size_t line_size = 0;
ssize_t line_length;
off_t line_number = 0;
while ( fp && 0 < (line_length = getline(&line, &line_size, fp)) )
{
line_number++;
size_t tokenc;
char** tokens = tokenize(&tokenc, line);
if ( !tokens )
{
unlink(out_path);
if ( errno )
err(1, "%s", path);
else
errx(1, "%s:%ji: Syntax error", path, (intmax_t) line_number);
}
if ( 2 <= tokenc &&
!strcmp(tokens[0], "require") && !strcmp(tokens[1], daemon) )
{
found = true;
if ( flags )
fprintf(out, "require %s%s\n", daemon, flags);
}
else
fputs(line, out);
}
free(line);
if ( !found && flags )
fprintf(out, "require %s%s\n", daemon, flags);
if ( (fp && ferror(fp)) || ferror(out) || fflush(out) == EOF )
{
unlink(out_path);
err(1, "%s", path);
}
if ( fp )
{
struct stat st;
fstat(fileno(fp), &st);
fchmod(out_fd, st.st_mode & 07777);
fchown(out_fd, st.st_uid, st.st_gid);
fclose(fp);
}
else
fchmod(out_fd, 0666 & ~getumask());
if ( rename(out_path, path) < 0 )
{
unlink(out_path);
err(1, "rename: %s -> %s", out_path, path);
}
fclose(out);
}
static bool check_daemon_exists_in_dir(const char* dir, const char* daemon)
{
char* path;
if ( asprintf(&path, "%s/%s", dir, daemon) < 0 )
err(1, "malloc");
bool result = !access(path, F_OK);
free(path);
return result;
}
static void check_daemon_exists(const char* daemon)
{
if ( !check_daemon_exists_in_dir("/etc/init", daemon) &&
!check_daemon_exists_in_dir("/share/init", daemon) )
errx(1, "%s: Daemon does not exist", daemon);
}
static size_t string_display_length(const char* str)
{
size_t display_length = 0;
mbstate_t ps;
memset(&ps, 0, sizeof(ps));
while ( true )
{
wchar_t wc;
size_t amount = mbrtowc(&wc, str, SIZE_MAX, &ps);
if ( amount == 0 )
break;
if ( amount == (size_t) -1 || amount == (size_t) -2 )
{
display_length++;
str++;
memset(&ps, 0, sizeof(ps));
continue;
}
int width = wcwidth(wc);
if ( width < 0 )
width = 0;
if ( SIZE_MAX - display_length < (size_t) width )
display_length = SIZE_MAX;
else
display_length += (size_t) width;
str += amount;
}
return display_length;
}
static void pad(const char* string, size_t padding)
{
fputs(string, stdout);
for ( size_t length = string_display_length(string);
length < padding; padding-- )
putchar(' ');
}
static void format_statuses(char** tokens, size_t count)
{
size_t daemon_length = 0;
size_t state_length = 10;
for ( size_t i = 0; i < count; i++ )
{
if ( !strncmp(tokens[i], "daemon=", strlen("daemon=")) )
{
const char* arg = tokens[i] + strlen("daemon=");
size_t length = string_display_length (arg);
if ( daemon_length < length )
daemon_length = length;
}
else if ( !strncmp(tokens[i], "state=", strlen("state=")) )
{
const char* arg = tokens[i] + strlen("state=");
size_t length = string_display_length(arg);
if ( state_length < length )
state_length = length;
}
}
size_t i = 0;
while ( i < count )
{
size_t next = i;
while ( next < count && strcmp(tokens[next], ",") )
next++;
const char* daemon = NULL;
const char* state = NULL;
for ( size_t n = i; n < next; n++ )
{
if ( !strncmp(tokens[n], "state=", strlen("state=")) )
state = tokens[n] + strlen("state=");
else if ( !strncmp(tokens[n], "daemon=", strlen("daemon=")) )
daemon = tokens[n] + strlen("daemon=");
}
if ( !state || !daemon )
errx(1, "missing information in reply");
pad(daemon, daemon_length + 2);
pad(state, state_length);
for ( size_t n = i; n < next; n++ )
{
if ( strncmp(tokens[n], "state=", strlen("state=")) &&
strncmp(tokens[n], "daemon=", strlen("daemon=")) )
printf(" %s", tokens[n]);
}
putchar('\n');
i = next;
if ( i < count && !strcmp(tokens[i], ",") )
i++;
}
}
int main(int argc, char* argv[])
{
const char* init_socket = getenv("INIT_SOCKET");
if ( !init_socket )
init_socket = "/var/run/init";
bool exit_code = false;
bool list = false;
bool no_await = false;
bool optional = true;
bool raw = false;
const char* source = "local";
const struct option longopts[] =
{
{"exit-code", no_argument, NULL, 256},
{"list", no_argument, NULL, 'l'},
{"no-await", no_argument, NULL, 257},
{"no-optional", no_argument, NULL, 258},
{"source", required_argument, NULL, 's'},
{"raw", no_argument, NULL, 'r'},
{0, 0, 0, 0}
};
const char* opts = "lrs:";
int opt;
while ( (opt = getopt_long(argc, argv, opts, longopts, NULL)) != -1 )
{
switch ( opt )
{
case 'l': list = true; break;
case 'r': raw = true; break;
case 's': source = optarg; break;
case 256: exit_code = true; break;
case 257: no_await = true; break;
case 258: optional = false; break;
default: return 2;
}
}
int fd = open_local_client_socket(init_socket, 0);
if ( fd < 0 )
err(1, "%s", init_socket);
FILE* fp = fdopen(fd, "r+");
if ( !fp )
err(1, "fdopen");
if ( raw )
{
for ( int i = optind; i < argc; i++ )
{
if ( fprintf(fp, "%s%c", argv[i], i + 1 == argc ? '\n' : ' ') < 0 )
err(1, "%s", init_socket);
}
size_t tokens_count;
char** tokens = receive(fp, &tokens_count);
for ( size_t i = 0; i < tokens_count; i++ )
printf("%s%c", tokens[i], i + 1 == tokens_count ? '\n' : ' ');
return 0;
}
char flags[sizeof(" optional no-await exit-code")];
snprintf(flags, sizeof(flags), "%s%s%s",
optional ? " optional" : "",
no_await ? " no-await" : "",
exit_code ? " exit-code" : "");
char* source_path;
if ( asprintf(&source_path, "/etc/init/%s", source) < 0 )
err(1, "malloc");
bool no_command = list;
if ( no_command && 0 < argc - optind )
errx(1, "unexpected extra operand: %s", argv[optind]);
else if ( !no_command && argc - optind < 2 )
errx(1, "usage: <daemon> <command>");
const char* daemon = !no_command ? argv[optind++] : NULL;
const char* command = !no_command ? argv[optind++] : NULL;
if ( command && strcmp(command, "signal") && 0 < argc - optind )
errx(1, "unexpected extra operand: %s", argv[optind]);
if ( list )
fprintf(fp, "list\n");
else if ( !strcmp(command, "enable") )
{
check_daemon_exists(daemon);
rewrite(source_path, daemon, flags);
fprintf(fp, "require %s %s %s start\n", source, daemon, flags);
}
else if ( !strcmp(command, "disable") )
{
rewrite(source_path, daemon, NULL);
fprintf(fp, "unrequire %s %s\n", source, daemon);
}
else if ( !strcmp(command, "start") )
fprintf(fp, "require %s %s %s start\n", source, daemon, flags);
else if ( !strcmp(command, "stop") )
fprintf(fp, "unrequire %s %s\n", source, daemon);
else if ( !strcmp(command, "restart") )
fprintf(fp, "restart %s\n", daemon);
else if ( !strcmp(command, "reload") )
fprintf(fp, "reload %s\n", daemon);
else if ( !strcmp(command, "reconfigure") )
fprintf(fp, "reconfigure %s\n", daemon);
else if ( !strcmp(command, "terminate") )
fprintf(fp, "terminate %s\n", daemon);
else if ( !strcmp(command, "kill") )
fprintf(fp, "kill %s\n", daemon);
else if ( !strcmp(command, "signal") )
{
if ( argc - optind < 1 )
errx(1, "expected signal name");
const char* signal = argv[optind++];
if ( 0 < argc - optind )
errx(1, "unexpected extra operand: %s", argv[optind]);
fprintf(fp, "signal %s %s\n", daemon, signal);
}
else if ( !strcmp(command, "status") )
fprintf(fp, "status %s\n", daemon);
else if ( !strcmp(command, "state") )
fprintf(fp, "status %s\n", daemon);
else if ( !strcmp(command, "pid") )
fprintf(fp, "status %s\n", daemon);
else if ( !strcmp(command, "exit-code") )
fprintf(fp, "status %s\n", daemon);
else if ( !strcmp(command, "requirements") ||
!strcmp(command, "dependents") ||
!strcmp(command, "edges") )
fprintf(fp, "%s %s\n", command, daemon);
else
errx(1, "unknown command: %s", command);
if ( ferror(fp) || fflush(fp) == EOF )
err(1, "%s", init_socket);
size_t tokens_count;
char** tokens = receive(fp, &tokens_count);
if ( list )
format_statuses(tokens + 1, tokens_count - 1);
else if ( !strcmp(command, "status") )
format_statuses(tokens + 1, tokens_count - 1);
else if ( !strcmp(command, "state") )
{
for ( size_t i = 1; i < tokens_count; i++ )
{
if ( !strncmp(tokens[i], "state=", strlen("state=")) )
{
puts(tokens[i] + strlen("state="));
break;
}
}
}
else if ( !strcmp(command, "pid") )
{
for ( size_t i = 1; i < tokens_count; i++ )
{
if ( !strncmp(tokens[i], "pid=", strlen("pid=")) )
{
const char* arg = tokens[i] + strlen("pid=");
if ( strcmp(arg, "0") )
puts(arg);
break;
}
}
}
else if ( !strcmp(command, "exit-code") )
{
for ( size_t i = 1; i < tokens_count; i++ )
{
if ( !strncmp(tokens[i], "exit=", strlen("exit=")) )
{
const char* arg = tokens[i] + strlen("exit=");
if ( strcmp(arg, "n/a") )
puts(arg);
break;
}
}
}
else if ( !strcmp(command, "requirements") ||
!strcmp(command, "dependents") ||
!strcmp(command, "edges") )
{
for ( size_t i = 1; i < tokens_count; i++ )
{
if ( !strcmp(tokens[i], ",") )
continue;
size_t eol = i + 1 == tokens_count || !strcmp(tokens[i + 1], ",");
printf("%s%c", tokens[i], eol ? '\n' : ' ');
}
}
return 0;
}

View file

@ -653,7 +653,8 @@ require exampled exit-code
.Ed
.Sh SEE ALSO
.Xr daemon 7 ,
.Xr init 8
.Xr init 8 ,
.Xr service 8
.Sh BUGS
The control messages mentioned in
.Sy log-control-messages