cutelyst 5.0.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
systemdnotify.cpp
1/*
2 * SPDX-FileCopyrightText: (C) 2017-2022 Daniel Nicoletti <dantti12@gmail.com>
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5#include "systemdnotify.h"
6
7#include "server.h"
8
9#include <cstring>
10#include <fcntl.h>
11#include <sys/socket.h>
12#include <sys/un.h>
13#include <unistd.h>
14
15#include <QCoreApplication>
16#include <QLoggingCategory>
17#include <QScopeGuard>
18#include <QTimer>
19
20namespace {
21/* The first passed file descriptor is fd 3 */
22constexpr auto SD_LISTEN_FDS_START = 3;
23} // namespace
24
25Q_LOGGING_CATEGORY(C_SERVER_SYSTEMD, "cutelyst.server.systemd", QtWarningMsg)
26
27using namespace Cutelyst;
28
29namespace Cutelyst {
30
32{
33public:
34 struct msghdr *notification_object = nullptr;
35 QTimer *watchdog = nullptr;
36 int notification_fd = 0;
37 int watchdog_usec = 0;
38};
39
40} // namespace Cutelyst
41
42systemdNotify::systemdNotify(QObject *parent)
43 : QObject(parent)
44 , d_ptr(new systemdNotifyPrivate)
45{
46 Q_D(systemdNotify);
47
48 d->notification_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
49 if (d->notification_fd < 0) {
50 qCWarning(C_SERVER_SYSTEMD, "socket()");
51 return;
52 }
53
54 auto systemd_socket = getenv("NOTIFY_SOCKET");
55 if (systemd_socket) {
56 struct sockaddr_un *sd_sun;
57 struct msghdr *msghdr;
58
59 size_t len = strlen(systemd_socket);
60 sd_sun = new struct sockaddr_un;
61 memset(sd_sun, 0, sizeof(struct sockaddr_un));
62 sd_sun->sun_family = AF_UNIX;
63 strncpy(sd_sun->sun_path, systemd_socket, qMin(len, sizeof(sd_sun->sun_path)));
64 if (sd_sun->sun_path[0] == '@') {
65 sd_sun->sun_path[0] = 0;
66 }
67
68 msghdr = new struct msghdr;
69 memset(msghdr, 0, sizeof(struct msghdr));
70
71 msghdr->msg_iov = new struct iovec[3];
72 memset(msghdr->msg_iov, 0, sizeof(struct iovec) * 3);
73
74 msghdr->msg_name = sd_sun;
75 msghdr->msg_namelen = sizeof(struct sockaddr_un) - (sizeof(sd_sun->sun_path) - len);
76
77 d->notification_object = msghdr;
78 }
79}
80
81systemdNotify::~systemdNotify()
82{
83 Q_D(systemdNotify);
84 if (d->notification_object) {
85 delete static_cast<struct sockaddr_un *>(d->notification_object->msg_name);
86 delete[] d->notification_object->msg_iov;
87 delete d->notification_object;
88 }
89 delete d_ptr;
90}
91
92int systemdNotify::watchdogUSec() const
93{
94 Q_D(const systemdNotify);
95 return d->watchdog_usec;
96}
97
98bool systemdNotify::setWatchdog(bool enable, int usec)
99{
100 Q_D(systemdNotify);
101 if (enable) {
102 d->watchdog_usec = usec;
103 if (d->watchdog_usec > 0) {
104 if (!d->watchdog) {
105 // Issue first ping immediately
106 d->watchdog = new QTimer(this);
107 // SD recommends half the defined interval
108 d->watchdog->setInterval(std::chrono::seconds{d->watchdog_usec} / 2);
109 sendWatchdog(QByteArrayLiteral("1"));
110 connect(d->watchdog, &QTimer::timeout, this, [this] {
111 sendWatchdog(QByteArrayLiteral("1"));
112 });
113 d->watchdog->start();
114 qCInfo(C_SERVER_SYSTEMD)
115 << "watchdog enabled" << d->watchdog_usec << d->watchdog->interval();
116 }
117 return true;
118 } else {
119 return false;
120 }
121 } else {
122 delete d->watchdog;
123 d->watchdog = nullptr;
124 }
125 return true;
126}
127
128void systemdNotify::sendStatus(const QByteArray &data)
129{
130 Q_D(systemdNotify);
131 Q_ASSERT(d->notification_fd);
132
133 struct msghdr *msghdr = d->notification_object;
134 struct iovec *iovec = msghdr->msg_iov;
135
136 iovec[0].iov_base = const_cast<char *>("STATUS=");
137 iovec[0].iov_len = 7;
138
139 iovec[1].iov_base = const_cast<char *>(data.constData());
140 iovec[1].iov_len = data.size();
141
142 iovec[2].iov_base = const_cast<char *>("\n");
143 iovec[2].iov_len = 1;
144
145 msghdr->msg_iovlen = 3;
146
147 if (sendmsg(d->notification_fd, msghdr, 0) < 0) {
148 qCWarning(C_SERVER_SYSTEMD, "sendStatus()");
149 }
150}
151
152void systemdNotify::sendWatchdog(const QByteArray &data)
153{
154 Q_D(systemdNotify);
155 Q_ASSERT(d->notification_fd);
156
157 struct msghdr *msghdr = d->notification_object;
158 struct iovec *iovec = msghdr->msg_iov;
159
160 iovec[0].iov_base = const_cast<char *>("WATCHDOG=");
161 iovec[0].iov_len = 9;
162
163 iovec[1].iov_base = const_cast<char *>(data.constData());
164 iovec[1].iov_len = data.size();
165
166 iovec[2].iov_base = const_cast<char *>("\n");
167 iovec[2].iov_len = 1;
168
169 msghdr->msg_iovlen = 3;
170
171 if (sendmsg(d->notification_fd, msghdr, 0) < 0) {
172 qCWarning(C_SERVER_SYSTEMD, "sendWatchdog()");
173 }
174}
175
176void systemdNotify::sendReady(const QByteArray &data)
177{
178 Q_D(systemdNotify);
179 Q_ASSERT(d->notification_fd);
180
181 struct msghdr *msghdr = d->notification_object;
182 struct iovec *iovec = msghdr->msg_iov;
183
184 iovec[0].iov_base = const_cast<char *>("READY=");
185 iovec[0].iov_len = 6;
186
187 iovec[1].iov_base = const_cast<char *>(data.constData());
188 iovec[1].iov_len = data.size();
189
190 iovec[2].iov_base = const_cast<char *>("\n");
191 iovec[2].iov_len = 1;
192
193 msghdr->msg_iovlen = 3;
194
195 if (sendmsg(d->notification_fd, msghdr, 0) < 0) {
196 qCWarning(C_SERVER_SYSTEMD, "sendReady()");
197 }
198}
199
200int systemdNotify::sd_watchdog_enabled(bool unset)
201{
202 int ret = 0;
203 auto cleanup = qScopeGuard([unset, &ret] {
204 if (unset && ret > 0) {
205 qunsetenv("WATCHDOG_USEC");
206 qunsetenv("WATCHDOG_PID");
207 }
208 });
209
210 QByteArray wusec = qgetenv("WATCHDOG_USEC");
211 bool ok;
212 ret = wusec.toInt(&ok);
213 if (!ok) {
214 return -1;
215 }
216
217 if (qEnvironmentVariableIsSet("WATCHDOG_PID")) {
218 QByteArray wpid = qgetenv("WATCHDOG_PID");
219 qint64 pid = wpid.toLongLong(&ok);
220 if (pid != qApp->applicationPid()) {
221 return -2;
222 }
223 }
224
225 return ret;
226}
227
228bool systemdNotify::is_systemd_notify_available()
229{
230 return qEnvironmentVariableIsSet("NOTIFY_SOCKET");
231}
232
233int fd_cloexec(int fd, bool cloexec)
234{
235 Q_ASSERT(fd >= 0);
236
237 const int flags = fcntl(fd, F_GETFD, 0);
238 if (flags < 0) {
239 return -errno;
240 }
241
242 int nflags;
243 if (cloexec) {
244 nflags = flags | FD_CLOEXEC;
245 } else {
246 nflags = flags & ~FD_CLOEXEC;
247 }
248
249 if (nflags == flags) {
250 return 0;
251 }
252
253 if (fcntl(fd, F_SETFD, nflags) < 0) {
254 return -errno;
255 }
256
257 return 0;
258}
259
260int sd_listen_fds()
261{
262 const QByteArray listenPid = qgetenv("LISTEN_PID");
263 bool ok;
264 qint64 pid = static_cast<pid_t>(listenPid.toLongLong(&ok));
265 if (!ok) {
266 return 0;
267 }
268
269 /* Is this for us? */
271 return 0;
272 }
273
274 const QByteArray listenFDS = qgetenv("LISTEN_FDS");
275 int n = listenFDS.toInt(&ok);
276 if (!ok) {
277 return 0;
278 }
279
280 Q_ASSERT(SD_LISTEN_FDS_START < INT_MAX);
281 if (n <= 0 || n > INT_MAX - SD_LISTEN_FDS_START) {
282 return -EINVAL;
283 }
284
285 qCInfo(C_SERVER_SYSTEMD, "systemd socket activation detected");
286
287 int r = 0;
288 for (int fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
289 r = fd_cloexec(fd, true);
290 if (r < 0) {
291 return r;
292 }
293 }
294
295 r = n;
296
297 return r;
298}
299
300std::vector<int> systemdNotify::listenFds(bool unsetEnvironment)
301{
302 std::vector<int> ret;
303 if (const int maxFD = sd_listen_fds(); maxFD > 0) {
304 for (int fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + maxFD; ++fd) {
305 ret.push_back(fd);
306 }
307 }
308
309 if (unsetEnvironment) {
310 qunsetenv("LISTEN_PID");
311 qunsetenv("LISTEN_FDS");
312 qunsetenv("LISTEN_FDNAMES");
313 }
314
315 return ret;
316}
317
318#include "moc_systemdnotify.cpp"
The Cutelyst namespace holds all public Cutelyst API.
const char * constData() const const
qsizetype size() const const
int toInt(bool *ok, int base) const const
qlonglong toLongLong(bool *ok, int base) const const
qint64 applicationPid()
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void timeout()