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