]> git.ipfire.org Git - thirdparty/systemd.git/blob - man/notify-selfcontained-example.c
Merge pull request #32677 from keszybz/wording-fixes
[thirdparty/systemd.git] / man / notify-selfcontained-example.c
1 /* SPDX-License-Identifier: MIT-0 */
2
3 /* Implement the systemd notify protocol without external dependencies.
4 * Supports both readiness notification on startup and on reloading,
5 * according to the protocol defined at:
6 * https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html
7 * This protocol is guaranteed to be stable as per:
8 * https://systemd.io/PORTABILITY_AND_STABILITY/ */
9
10 #define _GNU_SOURCE 1
11 #include <errno.h>
12 #include <inttypes.h>
13 #include <signal.h>
14 #include <stdbool.h>
15 #include <stddef.h>
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <sys/socket.h>
19 #include <sys/un.h>
20 #include <time.h>
21 #include <unistd.h>
22
23 #define _cleanup_(f) __attribute__((cleanup(f)))
24
25 static void closep(int *fd) {
26 if (!fd || *fd < 0)
27 return;
28
29 close(*fd);
30 *fd = -1;
31 }
32
33 static int notify(const char *message) {
34 union sockaddr_union {
35 struct sockaddr sa;
36 struct sockaddr_un sun;
37 } socket_addr = {
38 .sun.sun_family = AF_UNIX,
39 };
40 size_t path_length, message_length;
41 _cleanup_(closep) int fd = -1;
42 const char *socket_path;
43
44 /* Verify the argument first */
45 if (!message)
46 return -EINVAL;
47
48 message_length = strlen(message);
49 if (message_length == 0)
50 return -EINVAL;
51
52 /* If the variable is not set, the protocol is a noop */
53 socket_path = getenv("NOTIFY_SOCKET");
54 if (!socket_path)
55 return 0; /* Not set? Nothing to do */
56
57 /* Only AF_UNIX is supported, with path or abstract sockets */
58 if (socket_path[0] != '/' && socket_path[0] != '@')
59 return -EAFNOSUPPORT;
60
61 path_length = strlen(socket_path);
62 /* Ensure there is room for NUL byte */
63 if (path_length >= sizeof(socket_addr.sun.sun_path))
64 return -E2BIG;
65
66 memcpy(socket_addr.sun.sun_path, socket_path, path_length);
67
68 /* Support for abstract socket */
69 if (socket_addr.sun.sun_path[0] == '@')
70 socket_addr.sun.sun_path[0] = 0;
71
72 fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
73 if (fd < 0)
74 return -errno;
75
76 if (connect(fd, &socket_addr.sa, offsetof(struct sockaddr_un, sun_path) + path_length) != 0)
77 return -errno;
78
79 ssize_t written = write(fd, message, message_length);
80 if (written != (ssize_t) message_length)
81 return written < 0 ? -errno : -EPROTO;
82
83 return 1; /* Notified! */
84 }
85
86 static int notify_ready(void) {
87 return notify("READY=1");
88 }
89
90 static int notify_reloading(void) {
91 /* A buffer with length sufficient to format the maximum UINT64 value. */
92 char reload_message[sizeof("RELOADING=1\nMONOTONIC_USEC=18446744073709551615")];
93 struct timespec ts;
94 uint64_t now;
95
96 /* Notify systemd that we are reloading, including a CLOCK_MONOTONIC timestamp in usec
97 * so that the program is compatible with a Type=notify-reload service. */
98
99 if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
100 return -errno;
101
102 if (ts.tv_sec < 0 || ts.tv_nsec < 0 ||
103 (uint64_t) ts.tv_sec > (UINT64_MAX - (ts.tv_nsec / 1000ULL)) / 1000000ULL)
104 return -EINVAL;
105
106 now = (uint64_t) ts.tv_sec * 1000000ULL + (uint64_t) ts.tv_nsec / 1000ULL;
107
108 if (snprintf(reload_message, sizeof(reload_message), "RELOADING=1\nMONOTONIC_USEC=%" PRIu64, now) < 0)
109 return -EINVAL;
110
111 return notify(reload_message);
112 }
113
114 static int notify_stopping(void) {
115 return notify("STOPPING=1");
116 }
117
118 static volatile sig_atomic_t reloading = 0;
119 static volatile sig_atomic_t terminating = 0;
120
121 static void signal_handler(int sig) {
122 if (sig == SIGHUP)
123 reloading = 1;
124 else if (sig == SIGINT || sig == SIGTERM)
125 terminating = 1;
126 }
127
128 int main(int argc, char **argv) {
129 struct sigaction sa = {
130 .sa_handler = signal_handler,
131 .sa_flags = SA_RESTART,
132 };
133 int r;
134
135 /* Setup signal handlers */
136 sigemptyset(&sa.sa_mask);
137 sigaction(SIGHUP, &sa, NULL);
138 sigaction(SIGINT, &sa, NULL);
139 sigaction(SIGTERM, &sa, NULL);
140
141 /* Do more service initialization work here … */
142
143 /* Now that all the preparations steps are done, signal readiness */
144
145 r = notify_ready();
146 if (r < 0) {
147 fprintf(stderr, "Failed to notify readiness to $NOTIFY_SOCKET: %s\n", strerror(-r));
148 return EXIT_FAILURE;
149 }
150
151 while (!terminating) {
152 if (reloading) {
153 reloading = false;
154
155 /* As a separate but related feature, we can also notify the manager
156 * when reloading configuration. This allows accurate state-tracking,
157 * and also automated hook-in of 'systemctl reload' without having to
158 * specify manually an ExecReload= line in the unit file. */
159
160 r = notify_reloading();
161 if (r < 0) {
162 fprintf(stderr, "Failed to notify reloading to $NOTIFY_SOCKET: %s\n", strerror(-r));
163 return EXIT_FAILURE;
164 }
165
166 /* Do some reconfiguration work here … */
167
168 r = notify_ready();
169 if (r < 0) {
170 fprintf(stderr, "Failed to notify readiness to $NOTIFY_SOCKET: %s\n", strerror(-r));
171 return EXIT_FAILURE;
172 }
173 }
174
175 /* Do some daemon work here … */
176 sleep(5);
177 }
178
179 r = notify_stopping();
180 if (r < 0) {
181 fprintf(stderr, "Failed to report termination to $NOTIFY_SOCKET: %s\n", strerror(-r));
182 return EXIT_FAILURE;
183 }
184
185 /* Do some shutdown work here … */
186
187 return EXIT_SUCCESS;
188 }