cutelyst  4.4.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 
23 Q_LOGGING_CATEGORY(C_SERVER_SYSTEMD, "cutelyst.server.systemd", QtWarningMsg)
24 
25 using namespace Cutelyst;
26 
27 namespace Cutelyst {
28 
30 {
31 public:
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 
40 systemdNotify::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 
78 systemdNotify::~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 
89 int systemdNotify::watchdogUSec() const
90 {
91  Q_D(const systemdNotify);
92  return d->watchdog_usec;
93 }
94 
95 bool 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 
125 void 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 
149 void 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 
173 void 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 
197 int 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 
225 bool systemdNotify::is_systemd_notify_available()
226 {
227  return qEnvironmentVariableIsSet("NOTIFY_SOCKET");
228 }
229 
230 int 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 
254 int 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? */
264  if (QCoreApplication::applicationPid() != pid) {
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 
294 std::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()