cutelyst  5.0.1
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 
20 namespace {
21 /* The first passed file descriptor is fd 3 */
22 constexpr auto SD_LISTEN_FDS_START = 3;
23 Q_LOGGING_CATEGORY(C_SERVER_SYSTEMD, "cutelyst.server.systemd", QtWarningMsg)
24 
25 } // namespace
26 
27 using namespace Cutelyst;
28 
29 namespace Cutelyst {
30 
32 {
33 public:
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 
42 systemdNotify::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  const 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 
81 systemdNotify::~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 
92 int systemdNotify::watchdogUSec() const
93 {
94  Q_D(const systemdNotify);
95  return d->watchdog_usec;
96 }
97 
98 bool 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 
128 void 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 
152 void 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 
176 void 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 
200 int 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 
228 bool systemdNotify::is_systemd_notify_available()
229 {
230  return qEnvironmentVariableIsSet("NOTIFY_SOCKET");
231 }
232 
233 int 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 
260 int 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? */
270  if (QCoreApplication::applicationPid() != pid) {
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 
300 std::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"
int toInt(bool *ok, int base) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void timeout()
const char * constData() const const
qint64 applicationPid()
The Cutelyst namespace holds all public Cutelyst API.
qlonglong toLongLong(bool *ok, int base) const const
qsizetype size() const const