GCC Code Coverage Report


Directory: ./
File: common_mini.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 460 539 85.3%
Functions: 62 69 89.9%
Branches: 377 688 54.8%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2022 by the Falcon Player Developers.
4 *
5 * The Falcon Player (FPP) is free software, and is covered under
6 * multiple Open Source licenses. Please see the included 'LICENSES'
7 * file for descriptions of what files are covered by each license.
8 *
9 * This source file is covered under the LGPL v2.1 as described in the
10 * included LICENSE.LGPL file.
11 */
12 // This #define must be before any #include's
13 #define _FILE_OFFSET_BITS 64
14 #define __STDC_FORMAT_MACROS
15
16 #include "common_mini.h"
17 #include "SysSocket.h"
18
19 #ifndef _GNU_SOURCE
20 #define _GNU_SOURCE
21 #endif
22
23 #if defined(__APPLE__)
24 #include <copyfile.h>
25 #else
26 #include <sys/sendfile.h>
27 #endif
28
29 #include <arpa/inet.h>
30 #include <curl/curl.h>
31 #include <net/if.h>
32 #include <netinet/in.h>
33 #include <sys/file.h>
34 #include <sys/ioctl.h>
35 #include <sys/socket.h>
36 #include <sys/stat.h>
37 #include <sys/time.h>
38 #include <algorithm>
39 #include <climits>
40 #include <cstdint>
41 #include <ctime>
42 #include <ctype.h>
43 #include <cxxabi.h>
44 #include <dirent.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <ifaddrs.h>
48 #include <iomanip>
49 #include <list>
50 #include <map>
51 #include <netdb.h>
52 #include <pwd.h>
53 #include <random>
54 #include <sstream>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <string>
59 #include <unistd.h>
60 #include <utility>
61 #include <vector>
62
63 /*
64 * Get the current time down to the microsecond
65 */
66 34173 long long GetTime(void) {
67 struct timeval now_tv;
68 34173 gettimeofday(&now_tv, NULL);
69 34173 return now_tv.tv_sec * 1000000LL + now_tv.tv_usec;
70 }
71 44 long long GetTimeMicros(void) {
72 struct timeval now_tv;
73 44 gettimeofday(&now_tv, NULL);
74 44 return now_tv.tv_sec * 1000000LL + now_tv.tv_usec;
75 }
76
77 466998 long long GetTimeMS(void) {
78 struct timeval now_tv;
79 466998 gettimeofday(&now_tv, NULL);
80 466998 return now_tv.tv_sec * 1000LL + now_tv.tv_usec / 1000;
81 }
82
83 2656751 long long GetMonotonicTimeMS(void) {
84 struct timespec ts;
85 2656751 clock_gettime(CLOCK_MONOTONIC, &ts);
86 2656751 return ts.tv_sec * 1000LL + ts.tv_nsec / 1000000;
87 }
88
89 969196 long long GetMonotonicTimeMicros(void) {
90 struct timespec ts;
91 969196 clock_gettime(CLOCK_MONOTONIC, &ts);
92 969196 return ts.tv_sec * 1000000LL + ts.tv_nsec / 1000;
93 }
94
95 275278 std::string GetTimeStr(std::string fmt) {
96 275278 auto t = std::time(nullptr);
97 struct tm tm;
98 275278 localtime_r(&t, &tm);
99
1/2
✓ Branch 0 taken 275278 times.
✗ Branch 1 not taken.
275278 std::stringstream sstr;
100
1/2
✓ Branch 0 taken 275278 times.
✗ Branch 1 not taken.
275278 sstr << std::put_time(&tm, fmt.c_str());
101
102
1/2
✓ Branch 0 taken 275278 times.
✗ Branch 1 not taken.
550556 return sstr.str();
103 275278 }
104
105 137639 std::string GetDateStr(std::string fmt) {
106 137639 auto t = std::time(nullptr);
107 struct tm tm;
108 137639 localtime_r(&t, &tm);
109
1/2
✓ Branch 0 taken 137639 times.
✗ Branch 1 not taken.
137639 std::stringstream sstr;
110
1/2
✓ Branch 0 taken 137639 times.
✗ Branch 1 not taken.
137639 sstr << std::put_time(&tm, fmt.c_str());
111
112
1/2
✓ Branch 0 taken 137639 times.
✗ Branch 1 not taken.
275278 return sstr.str();
113 137639 }
114
115 // strerror_r() comes in two flavours: the GNU version returns char* while the
116 // XSI/POSIX version returns int and fills the supplied buffer. These overloads
117 // pick the right behaviour at compile time via the return type.
118 721 static inline const char* fppStrerrorResult(char* gnuResult, char* /*buf*/) {
119 721 return gnuResult;
120 }
121 static inline const char* fppStrerrorResult(int xsiResult, char* buf) {
122 return xsiResult == 0 ? buf : "Unknown error";
123 }
124 721 const char* FPPstrerror(int errnum) {
125 static thread_local char buf[256];
126 721 buf[0] = '\0';
127 721 return fppStrerrorResult(strerror_r(errnum, buf, sizeof(buf)), buf);
128 }
129
130 3133 int FPPrand() {
131
5/8
✓ Branch 0 taken 1885 times.
✓ Branch 1 taken 1248 times.
✓ Branch 2 taken 1885 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1885 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1885 times.
✗ Branch 7 not taken.
3133 static thread_local std::mt19937 engine(std::random_device{}());
132
2/2
✓ Branch 0 taken 1885 times.
✓ Branch 1 taken 1248 times.
3133 static thread_local std::uniform_int_distribution<int> dist(0, RAND_MAX);
133 3133 return dist(engine);
134 }
135
136 17904 bool GetUserIds(const std::string& username, uid_t* uid, gid_t* gid, std::string* homedir) {
137 17904 long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
138
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17904 times.
17904 if (bufsize <= 0) {
139 ✗ bufsize = 16384;
140 }
141
1/2
✓ Branch 0 taken 17904 times.
✗ Branch 1 not taken.
19344 std::vector<char> buf(bufsize);
142 struct passwd pwd;
143 17904 struct passwd* result = nullptr;
144
1/2
✓ Branch 0 taken 17904 times.
✗ Branch 1 not taken.
17904 int rc = getpwnam_r(username.c_str(), &pwd, buf.data(), buf.size(), &result);
145
3/4
✓ Branch 0 taken 17904 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16464 times.
✓ Branch 3 taken 1440 times.
17904 if (rc != 0 || result == nullptr) {
146 16464 return false;
147 }
148
1/2
✓ Branch 0 taken 1440 times.
✗ Branch 1 not taken.
1440 if (uid) {
149 1440 *uid = result->pw_uid;
150 }
151
1/2
✓ Branch 0 taken 1440 times.
✗ Branch 1 not taken.
1440 if (gid) {
152 1440 *gid = result->pw_gid;
153 }
154
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1440 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1440 if (homedir && result->pw_dir) {
155 ✗ *homedir = result->pw_dir;
156 }
157 1440 return true;
158 17904 }
159
160 /*
161 * Check to see if the specified directory exists
162 */
163 4084 int DirectoryExists(const char* Directory) {
164 4084 DIR* dir = opendir(Directory);
165
2/2
✓ Branch 0 taken 2145 times.
✓ Branch 1 taken 1939 times.
4084 if (dir) {
166 2145 closedir(dir);
167 2145 return 1;
168 } else {
169 1939 return 0;
170 }
171 }
172 271 int DirectoryExists(const std::string& Directory) {
173 271 return DirectoryExists(Directory.c_str());
174 }
175
176 /*
177 * Check if the specified file exists or not
178 */
179 1337323 int FileExists(const char* File) {
180 struct stat sts;
181
2/2
✓ Branch 0 taken 1245892 times.
✓ Branch 1 taken 91431 times.
1337323 if (stat(File, &sts) == -1) {
182 1245892 return 0;
183 } else {
184 91431 return 1;
185 }
186 }
187 1239451 int FileExists(const std::string& f) {
188 1239451 return FileExists(f.c_str());
189 }
190 19051 uint64_t FileTimestamp(const std::string& File) {
191 struct stat sts;
192
2/2
✓ Branch 0 taken 14105 times.
✓ Branch 1 taken 4946 times.
19051 if (stat(File.c_str(), &sts) == -1) {
193 14105 return 0;
194 } else {
195 4946 return (uint64_t)sts.st_mtime;
196 }
197 }
198
199 520 int Touch(const std::string& File) {
200 520 int fd = open(File.c_str(), O_WRONLY | O_CREAT | O_NOCTTY | O_NONBLOCK, 0666);
201
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 520 times.
520 if (fd < 0)
202 ✗ return 0;
203
204 520 close(fd);
205
206 520 return 1;
207 }
208
209 /*
210 * Get IP address on specified network interface
211 */
212 2461 int GetInterfaceAddress(const char* interface, char* addr, char* mask, char* gw) {
213 int fd;
214 struct ifreq ifr;
215
216 2461 memset(&ifr, 0, sizeof(ifr));
217
218 2461 fd = OpenSocket(AF_INET, SOCK_DGRAM, 0);
219 2461 ifr.ifr_addr.sa_family = AF_INET;
220
221 /* I want IP address attached to E131interface */
222 2461 strncpy(ifr.ifr_name, (const char*)interface, IFNAMSIZ - 1);
223 2461 int retVal = 0;
224
1/2
✓ Branch 0 taken 2461 times.
✗ Branch 1 not taken.
2461 if (addr) {
225
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 2437 times.
2461 if (ioctl(fd, SIOCGIFADDR, &ifr)) {
226 24 strcpy(addr, "127.0.0.1");
227 24 retVal = 1;
228 } else {
229 2437 inet_ntop(AF_INET, &((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr, addr, INET_ADDRSTRLEN);
230 }
231 }
232
233
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2461 times.
2461 if (mask) {
234 ✗ if (ioctl(fd, SIOCGIFNETMASK, &ifr))
235 ✗ strcpy(mask, "255.255.255.255");
236 else
237 ✗ inet_ntop(AF_INET, &((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr, mask, INET_ADDRSTRLEN);
238 }
239
240
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2461 times.
2461 if (gw) {
241 ✗ *gw = 0;
242
243 FILE* f;
244 char line[100];
245 char* iface;
246 char* dest;
247 char* route;
248 char* saveptr;
249
250 ✗ f = fopen("/proc/net/route", "r");
251
252 ✗ if (f) {
253 ✗ while (fgets(line, 100, f)) {
254 ✗ iface = strtok_r(line, " \t", &saveptr);
255 ✗ dest = strtok_r(NULL, " \t", &saveptr);
256 ✗ route = strtok_r(NULL, " \t", &saveptr);
257
258 ✗ if ((iface && dest && route) &&
259 ✗ (!strcmp(iface, interface)) &&
260 ✗ (!strcmp(dest, "00000000"))) {
261 char* end;
262 ✗ int ng = strtol(route, &end, 16);
263 struct in_addr addr;
264 ✗ addr.s_addr = ng;
265 ✗ inet_ntop(AF_INET, &addr, gw, INET_ADDRSTRLEN);
266 }
267 }
268 ✗ fclose(f);
269 }
270 }
271
272
1/2
✓ Branch 0 taken 2461 times.
✗ Branch 1 not taken.
2461 close(fd);
273
274 2461 return retVal;
275 }
276
277 /*
278 *
279 */
280
281 /*
282 *
283 */
284
285 static unsigned char bitLookup[16] = {
286 0x0,
287 0x8,
288 0x4,
289 0xc,
290 0x2,
291 0xa,
292 0x6,
293 0xe,
294 0x1,
295 0x9,
296 0x5,
297 0xd,
298 0x3,
299 0xb,
300 0x7,
301 0xf,
302 };
303
304 /*
305 * Reverse bits in a byte
306 */
307 ✗ uint8_t ReverseBitsInByte(uint8_t n) {
308 ✗ return (bitLookup[n & 0b1111] << 4) | bitLookup[n >> 4];
309 }
310
311 /*
312 * Convert a string of the form "YYYY-MM-DD to an integer YYYYMMDD
313 */
314 86175 int DateStrToInt(const char* str) {
315 // Check the length before indexing: a short string put str[7] and
316 // str[10] past its terminator.
317
4/8
✓ Branch 0 taken 86175 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 86175 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 86175 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 86175 times.
86175 if ((!str) || (strnlen(str, 11) != 10) || (str[4] != '-') || (str[7] != '-'))
318 ✗ return 0;
319
320 86175 int result = 0;
321
322 86175 result += atoi(str) * 10000; // Year
323 86175 result += atoi(str + 5) * 100; // Month
324 86175 result += atoi(str + 8); // Day
325
326 86175 return result;
327 }
328
329 /*
330 * Get the current date in an integer form YYYYMMDD
331 */
332 10627 int GetCurrentDateInt(int daysOffset) {
333 10627 time_t currTime = time(NULL) + (daysOffset * 86400);
334 struct tm now;
335 10627 int result = 0;
336
337 10627 localtime_r(&currTime, &now);
338
339 10627 result += (now.tm_year + 1900) * 10000;
340 10627 result += (now.tm_mon + 1) * 100;
341 10627 result += (now.tm_mday);
342
343 10627 return result;
344 }
345 567903 std::string secondsToTime(int i) {
346
1/2
✓ Branch 0 taken 567903 times.
✗ Branch 1 not taken.
567903 std::stringstream sstr;
347
348
2/2
✓ Branch 0 taken 81 times.
✓ Branch 1 taken 567822 times.
567903 if (i > (24 * 60 * 60)) {
349 81 int days = i / (24 * 60 * 60);
350
1/2
✓ Branch 0 taken 81 times.
✗ Branch 1 not taken.
81 sstr << days;
351
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 81 times.
81 if (days == 1)
352 ✗ sstr << " day, ";
353 else
354
1/2
✓ Branch 0 taken 81 times.
✗ Branch 1 not taken.
81 sstr << " days, ";
355
356 81 i = i % (24 * 60 * 60);
357 }
358
359
2/2
✓ Branch 0 taken 81 times.
✓ Branch 1 taken 567822 times.
567903 if (i > (60 * 60)) {
360 81 int hour = i / (60 * 60);
361
2/4
✓ Branch 0 taken 81 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 81 times.
✗ Branch 3 not taken.
81 sstr << std::setw(2) << std::setfill('0') << hour;
362
1/2
✓ Branch 0 taken 81 times.
✗ Branch 1 not taken.
81 sstr << ":";
363 81 i = i % (60 * 60);
364 }
365
366 567903 int min = i / 60;
367 567903 int sec = i % 60;
368
2/4
✓ Branch 0 taken 567903 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 567903 times.
✗ Branch 3 not taken.
567903 sstr << std::setw(2) << std::setfill('0') << min;
369
1/2
✓ Branch 0 taken 567903 times.
✗ Branch 1 not taken.
567903 sstr << ":";
370
2/4
✓ Branch 0 taken 567903 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 567903 times.
✗ Branch 3 not taken.
567903 sstr << std::setw(2) << std::setfill('0') << sec;
371
1/2
✓ Branch 0 taken 567903 times.
✗ Branch 1 not taken.
1135806 return sstr.str();
372 567903 }
373 // fds 0-2 are always open: on /dev/null, when the process was started
374 // without them. This runs before the static initializers, because they open
375 // descriptors too: drogon's main loop (its epoll, timerfd and eventfd) took
376 // 0-2 in an fppd started without them (a "-d" restart closed them before its
377 // exec), and CreateDaemon() then closed them, so nothing on that loop ran
378 // again.
379 2606 __attribute__((constructor(101))) static void OpenStandardDescriptors() {
380 int fd;
381
3/6
✓ Branch 0 taken 2606 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2606 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2606 times.
2606 while ((fd = open("/dev/null", O_RDWR)) >= 0 && fd <= STDERR_FILENO) {
382 }
383
1/2
✓ Branch 0 taken 2606 times.
✗ Branch 1 not taken.
2606 if (fd > STDERR_FILENO) {
384 2606 close(fd);
385 }
386 2606 }
387
388 // A descriptor CloseOpenFiles() and MarkOpenFilesCloseOnExec() act on: a
389 // file, unix domain socket or similar, or a TCP/UDP socket. Anything else is
390 // left alone: on OSX it may be a "NPOLICY" descriptor which, if closed,
391 // terminates the process immediately.
392 1880125890 static bool isOpenFileOrSocket(int fd) {
393
4/4
✓ Branch 0 taken 1880125454 times.
✓ Branch 1 taken 436 times.
✓ Branch 2 taken 1880075987 times.
✓ Branch 3 taken 15189 times.
1880125890 if (fcntl(fd, F_GETFD) == -1) {
394 1880110265 return false;
395 }
396 struct stat statBuf;
397
1/2
✓ Branch 0 taken 15625 times.
✗ Branch 1 not taken.
15625 if (fstat(fd, &statBuf) == 0) {
398 15625 return true;
399 }
400 struct sockaddr_in address;
401 ✗ memset(&address, 0, sizeof(address));
402 ✗ socklen_t addrLen = sizeof(address);
403 ✗ getsockname(fd, (struct sockaddr*)&address, &addrLen);
404 ✗ return address.sin_family != 0;
405 }
406
407 /*
408 * Close all open file descriptors (used after fork())
409 *
410 * stdin/stdout/stderr are left open, daemonMode or not: a daemon's are
411 * /dev/null, and closed, they were taken by the next files the process
412 * opened. daemonMode is kept for plugins that call this.
413 */
414 31 void CloseOpenFiles(int daemonMode) {
415 31 int maxfd = sysconf(_SC_OPEN_MAX);
416
2/2
✓ Branch 0 taken 23070627 times.
✓ Branch 1 taken 31 times.
23070658 for (int fd = 3; fd < maxfd; fd++) {
417
2/2
✓ Branch 0 taken 2863 times.
✓ Branch 1 taken 23067764 times.
23070627 if (isOpenFileOrSocket(fd)) {
418 2863 close(fd);
419 }
420 }
421 31 }
422
423 /*
424 * The same descriptors as CloseOpenFiles(), marked close-on-exec instead of
425 * closed: for a process that is about to exec or _exit() while other threads
426 * of its own may still be using them, which closing would pull out from under
427 * them (a detached media teardown, GStreamer's and PipeWire's threads), and a
428 * descriptor number then reused would take their writes.
429 */
430 1867 void MarkOpenFilesCloseOnExec() {
431 1867 int maxfd = sysconf(_SC_OPEN_MAX);
432
2/2
✓ Branch 0 taken 1857055263 times.
✓ Branch 1 taken 1867 times.
1857057130 for (int fd = 3; fd < maxfd; fd++) {
433
2/2
✓ Branch 0 taken 12762 times.
✓ Branch 1 taken 1857042501 times.
1857055263 if (isOpenFileOrSocket(fd)) {
434 12762 int flags = fcntl(fd, F_GETFD);
435
3/4
✓ Branch 0 taken 12762 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2310 times.
✓ Branch 3 taken 10452 times.
12762 if (flags != -1 && !(flags & FD_CLOEXEC)) {
436 2310 fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
437 }
438 }
439 }
440 1867 }
441
442 // Redirect our stdout (and everything we fork, which is why this dup2's the fd
443 // rather than just wrapping printf) into logs/fppd.log, prefixing every line
444 // with fppd's own line shape: "<date time.ms> <program>(<pid>) [<facility>] ".
445 //
446 // fppd.log is the merged, sequential record of the system: boot -> init -> fppd
447 // -> restarts -> operations, in one file, in time order. Init used to write its
448 // own fpp_init.log / fpp_boot.log, which could not be interleaved with fppd's
449 // log after the fact -- exactly the problem this arc exists to fix.
450 //
451 // This is a tee, not a redirect: the line is also echoed to the stdout we were
452 // started with. Under systemd that is journald, so `systemctl status fppinit`
453 // and `journalctl -u fpprtc` keep showing what the service did; run by hand from
454 // a shell it is the terminal, so `fppinit setupAudio` still prints as it goes.
455 // Only the fppd.log copy carries the timestamp/program/facility prefix -- the
456 // console copy is the raw line, because journald stamps and tags it already.
457 //
458 // The prefixing is done by bash rather than in C++ because the point of the
459 // dup2 is to capture child output too: the filter has to live on the far side of
460 // the pipe, where tee used to sit. printf '%(...)T' and EPOCHREALTIME are bash
461 // builtins (hence /bin/bash explicitly -- popen uses /bin/sh, which is dash on
462 // Debian and has neither), so this forks nothing per line. Appending per line
463 // rather than holding the file open is what makes it survive a logrotate rename.
464 //
465 // `|| [ -n "$__l" ]` keeps a final line that has no trailing newline: read
466 // returns false at EOF, and that unterminated line is what a process killed
467 // mid-write leaves behind -- the output most worth having.
468 148 void teeOutput(const std::string& log, const std::string& program, const std::string& facility, pid_t pid) {
469 148 std::size_t slash = log.rfind('/');
470
2/4
✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 148 times.
✗ Branch 3 not taken.
148 if (slash != std::string::npos && slash > 0) {
471 // logs/ under the media dir, and the media dir itself: a first boot can
472 // reach here before either exists.
473
1/2
✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
148 std::string dir = log.substr(0, slash);
474 148 std::size_t parent = dir.rfind('/');
475
2/4
✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 148 times.
✗ Branch 3 not taken.
148 if (parent != std::string::npos && parent > 0) {
476
1/2
✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
148 mkdir(dir.substr(0, parent).c_str(), 0775);
477 }
478 148 mkdir(dir.c_str(), 0775);
479 148 }
480
481 // The bash `>>` below would create the file root-owned (these services run as
482 // root), which then locks out fppd and the PHP-side writers that share it.
483 // Whoever creates fppd.log owns making it group-writable by fpp -- log.cpp
484 // and FPPINIT do the same for the cases where they get there first.
485
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 140 times.
148 if (access(log.c_str(), F_OK) != 0) {
486
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 FILE* nf = fopen(log.c_str(), "a");
487
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if (nf) {
488 uid_t uid;
489 gid_t gid;
490
7/18
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 8 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 8 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 8 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
24 if (GetUserIds("fpp", &uid, &gid) && fchown(fileno(nf), uid, gid) == 0) {
491 8 fchmod(fileno(nf), 0664);
492 }
493
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 fclose(nf);
494 }
495 }
496
497 // Grab a second handle on the console (journald/terminal) BEFORE the dup2
498 // below takes fd 1 over, so the writer can still reach it. dup() leaves
499 // FD_CLOEXEC clear, so it survives popen's exec into bash and is addressable
500 // there by number -- and it returns the lowest free descriptor, which for a
501 // service this early is a single digit, well clear of the fds bash reserves
502 // for its own redirections.
503 148 int consoleFd = dup(STDOUT_FILENO);
504 148 std::string echoConsole;
505
1/2
✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
148 if (consoleFd >= 0) {
506
2/4
✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 148 times.
✗ Branch 3 not taken.
148 echoConsole = "printf \"%s\\n\" \"$__l\" >&" + std::to_string(consoleFd) + "; ";
507 }
508
509 148 std::string cmd = "/bin/bash -c 'while IFS= read -r __l || [ -n \"$__l\" ]; do " +
510
1/2
✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
296 echoConsole +
511 "__now=${EPOCHREALTIME}; __us=${__now#*.}; "
512
1/2
✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
296 "printf \"%(%Y-%m-%d %H:%M:%S)T.%s " +
513
6/12
✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 148 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 148 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 148 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 148 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 148 times.
✗ Branch 11 not taken.
452 program + "(" + std::to_string(pid) + ") [" + facility + "] %s\\n\" " +
514
3/6
✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 148 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 148 times.
✗ Branch 5 not taken.
148 "\"${__now%.*}\" \"${__us:0:3}\" \"$__l\" >> " + log + "; done'";
515
516
1/2
✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
148 FILE* f = popen(cmd.c_str(), "w");
517
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 148 times.
148 if (!f) {
518 ✗ printf("Couldn't start the log writer\n");
519 ✗ if (consoleFd >= 0) {
520 ✗ close(consoleFd);
521 }
522 ✗ return;
523 }
524
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 148 times.
148 if (dup2(fileno(f), STDOUT_FILENO) < 0) {
525 ✗ printf("Couldn't redirect output to log file\n");
526 }
527 // The writer child has its own copy; ours would otherwise be inherited by
528 // every command we fork from here on.
529
1/2
✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
148 if (consoleFd >= 0) {
530
1/2
✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
148 close(consoleFd);
531 }
532 148 setvbuf(stdout, NULL, _IOLBF, 0);
533
2/4
✓ Branch 0 taken 148 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 148 times.
✗ Branch 3 not taken.
148 }
534
535 51981 int DateInRange(time_t when, int startDate, int endDate) {
536 struct tm dt;
537 51981 localtime_r(&when, &dt);
538
539 51981 int dateInt = 0;
540 51981 dateInt += (dt.tm_year + 1900) * 10000;
541 51981 dateInt += (dt.tm_mon + 1) * 100;
542 51981 dateInt += (dt.tm_mday);
543
544
1/2
✓ Branch 0 taken 51981 times.
✗ Branch 1 not taken.
103962 return DateInRange(dateInt, startDate, endDate);
545 }
546
547 /*
548 * Check to see if current date int is in the range specified
549 */
550 344 int CurrentDateInRange(int startDate, int endDate) {
551 344 int currentDate = GetCurrentDateInt();
552
553 344 return DateInRange(currentDate, startDate, endDate);
554 }
555
556 55532 int DateInRange(int currentDate, int startDate, int endDate) {
557
3/4
✓ Branch 0 taken 13087 times.
✓ Branch 1 taken 42445 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 13087 times.
55532 if ((startDate < 10000) || (endDate < 10000)) {
558 42445 startDate = startDate % 10000;
559 42445 endDate = endDate % 10000;
560 42445 currentDate = currentDate % 10000;
561
562
2/2
✓ Branch 0 taken 210 times.
✓ Branch 1 taken 42235 times.
42445 if (endDate < startDate) {
563
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 186 times.
210 if (currentDate <= endDate)
564 24 currentDate += 10000;
565
566 210 endDate += 10000; // next year
567 }
568 }
569
570
2/4
✓ Branch 0 taken 55532 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 55532 times.
55532 if ((startDate < 100) || (endDate < 100)) {
571 ✗ startDate = startDate % 100;
572 ✗ endDate = endDate % 100;
573 ✗ currentDate = currentDate % 100;
574
575 ✗ if (endDate < startDate) {
576 ✗ if (currentDate <= endDate)
577 ✗ currentDate += 100;
578
579 ✗ endDate += 100; // next month
580 }
581 }
582
583
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 55532 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
55532 if ((startDate == 0) && (endDate == 0))
584 ✗ return 1;
585
586
4/4
✓ Branch 0 taken 33593 times.
✓ Branch 1 taken 21939 times.
✓ Branch 2 taken 6164 times.
✓ Branch 3 taken 27429 times.
55532 if ((startDate <= currentDate) && (currentDate <= endDate))
587 6164 return 1;
588
589 49368 return 0;
590 }
591
592 ✗ std::string tail(std::string const& source, size_t const length) {
593 ✗ if (length >= source.size())
594 ✗ return source;
595
596 ✗ return source.substr(source.size() - length);
597 }
598
599 /*
600 * Helpers to split a string on the specified character delimiter
601 */
602 146081 std::vector<std::string>& split(const std::string& s, char delim, std::vector<std::string>& elems) {
603
1/2
✓ Branch 0 taken 146081 times.
✗ Branch 1 not taken.
146081 std::stringstream ss(s);
604 146081 std::string item;
605
606
4/6
✓ Branch 0 taken 733632 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 733632 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 587551 times.
✓ Branch 5 taken 146081 times.
733632 while (getline(ss, item, delim)) {
607
1/2
✓ Branch 0 taken 587551 times.
✗ Branch 1 not taken.
587551 elems.push_back(item);
608 }
609
610 146081 return elems;
611 146081 }
612
613 146081 std::vector<std::string> split(const std::string& s, char delim) {
614 146081 std::vector<std::string> elems;
615
616
1/2
✓ Branch 0 taken 146081 times.
✗ Branch 1 not taken.
146081 split(s, delim, elems);
617
618 146081 return elems;
619 ✗ }
620
621 693 bool ParseTimeString(const std::string& str, int& hour, int& minute, int& second) {
622
1/2
✓ Branch 0 taken 693 times.
✗ Branch 1 not taken.
693 std::vector<std::string> parts = split(str, ':');
623
624
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 692 times.
693 if (parts.size() < 2) {
625 1 return false;
626 }
627
628 692 hour = atoi(parts[0].c_str());
629 692 minute = atoi(parts[1].c_str());
630
2/2
✓ Branch 0 taken 691 times.
✓ Branch 1 taken 1 times.
692 second = parts.size() > 2 ? atoi(parts[2].c_str()) : 0;
631
632 692 return true;
633 693 }
634
635 633 inline std::string dequote(const std::string& s) {
636
8/10
✓ Branch 0 taken 604 times.
✓ Branch 1 taken 29 times.
✓ Branch 2 taken 604 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 601 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 601 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 601 times.
✓ Branch 9 taken 32 times.
633 if (s.length() > 2 && (s[0] == '\'' || s[0] == '"') && s[0] == s[s.length() - 1]) {
637 601 return s.substr(1, s.length() - 2);
638 }
639 32 return s;
640 }
641
642 25 std::vector<std::string> splitWithQuotes(const std::string& s, char delim) {
643 25 std::vector<std::string> ret;
644 25 const char* mystart = s.c_str();
645 25 bool instring = false;
646
2/2
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 25 times.
60 for (const char* p = mystart; *p; p++) {
647
3/4
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 33 times.
35 if (*p == '"' || *p == '\'') {
648 2 instring = !instring;
649
4/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 29 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
33 } else if (*p == delim && !instring) {
650
3/6
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 ret.push_back(dequote(std::string(mystart, p - mystart)));
651 3 mystart = p + 1;
652 }
653 }
654
3/6
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 25 times.
✗ Branch 5 not taken.
25 ret.push_back(dequote(std::string(mystart)));
655 25 return ret;
656 ✗ }
657
658 // Upper bound on what GetFileContents() returns. Its callers read configs,
659 // sysfs/proc nodes and small data files; this stops a runaway file or a
660 // device node from being read into an allocation that throws.
661 static constexpr size_t MAX_FILE_CONTENTS = 64 * 1024 * 1024;
662
663 80792 std::string GetFileContents(const std::string& filename) {
664 80792 FILE* fd = fopen(filename.c_str(), "r");
665 80792 std::string contents;
666
2/2
✓ Branch 0 taken 68374 times.
✓ Branch 1 taken 12418 times.
80792 if (fd != nullptr) {
667 // A directory opens fine for reading on Linux, and its seek-to-end
668 // offset is not a size (ext4 reports LLONG_MAX), which made the
669 // resize below throw.
670 struct stat st;
671
5/6
✓ Branch 0 taken 68374 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 68373 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 68373 times.
68374 if (fstat(fileno(fd), &st) != 0 || S_ISDIR(st.st_mode)) {
672
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 fclose(fd);
673 1 return contents;
674 }
675 68373 flock(fileno(fd), LOCK_SH);
676 // st_size is only a hint: /proc reports 0 and sysfs 4096 whatever
677 // the real length, and the file can change under us. Read to EOF
678 // and keep what fread actually returned.
679
4/6
✓ Branch 0 taken 68373 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 54247 times.
✓ Branch 3 taken 14126 times.
✓ Branch 4 taken 54247 times.
✗ Branch 5 not taken.
68373 if (S_ISREG(st.st_mode) && st.st_size > 0 && (uint64_t)st.st_size <= MAX_FILE_CONTENTS) {
680
1/2
✓ Branch 0 taken 54247 times.
✗ Branch 1 not taken.
54247 contents.reserve(st.st_size);
681 }
682 char buf[4096];
683 size_t i;
684
3/4
✓ Branch 0 taken 191542 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 123169 times.
✓ Branch 3 taken 68373 times.
191542 while ((i = fread(buf, 1, sizeof(buf), fd)) > 0) {
685
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 123169 times.
123169 if (contents.size() + i > MAX_FILE_CONTENTS) {
686 ✗ fprintf(stderr, "ERROR: %s is larger than %zu bytes, not reading it.\n", filename.c_str(), MAX_FILE_CONTENTS);
687 ✗ contents.clear();
688 ✗ break;
689 }
690
1/2
✓ Branch 0 taken 123169 times.
✗ Branch 1 not taken.
123169 contents.append(buf, i);
691 }
692 68373 flock(fileno(fd), LOCK_UN);
693
1/2
✓ Branch 0 taken 68373 times.
✗ Branch 1 not taken.
68373 fclose(fd);
694 68373 int x = contents.size() - 1;
695
2/2
✓ Branch 0 taken 68751 times.
✓ Branch 1 taken 1 times.
68752 for (; x > 0; x--) {
696
2/2
✓ Branch 0 taken 68372 times.
✓ Branch 1 taken 379 times.
68751 if (contents[x] != 0) {
697 68372 break;
698 }
699 }
700
1/2
✓ Branch 0 taken 68373 times.
✗ Branch 1 not taken.
68373 contents.resize(x + 1);
701 }
702 80791 return contents;
703 ✗ }
704
705 18114 bool PutFileContents(const std::string& filename, const std::string& str) {
706 // Lock before truncating and write unbuffered, so a LOCK_SH reader never
707 // sees the file empty or half written.
708
1/2
✓ Branch 0 taken 18114 times.
✗ Branch 1 not taken.
18114 int fd = open(filename.c_str(), O_WRONLY | O_CREAT, 0666);
709
2/2
✓ Branch 0 taken 516 times.
✓ Branch 1 taken 17598 times.
18114 if (fd == -1) {
710
1/2
✓ Branch 0 taken 516 times.
✗ Branch 1 not taken.
516 fprintf(stderr, "ERROR: Unable to open %s for writing.\n", filename.c_str());
711 516 return false;
712 }
713
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 17598 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 17598 times.
17598 while (flock(fd, LOCK_EX) == -1 && errno == EINTR) {
714 }
715 // Only regular files can be truncated; device nodes and FIFOs are just written.
716 17598 bool ok = true;
717 17598 int err = 0;
718 struct stat st;
719
4/8
✓ Branch 0 taken 17598 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17598 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 17598 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 17598 times.
17598 if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode) && ftruncate(fd, 0) != 0) {
720 ✗ ok = false;
721 ✗ err = errno;
722 }
723 17598 const char* p = str.data();
724 17598 size_t left = str.size();
725
3/4
✓ Branch 0 taken 35193 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17595 times.
✓ Branch 3 taken 17598 times.
35193 while (ok && left > 0) {
726
1/2
✓ Branch 0 taken 17595 times.
✗ Branch 1 not taken.
17595 ssize_t n = write(fd, p, left);
727
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17595 times.
17595 if (n < 0) {
728 ✗ if (errno == EINTR) {
729 ✗ continue;
730 }
731 ✗ ok = false;
732 ✗ err = errno;
733
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17595 times.
17595 } else if (n == 0) {
734 // A sysfs store() returning 0 makes no progress; don't spin on it.
735 ✗ ok = false;
736 ✗ err = EIO;
737 } else {
738 17595 p += n;
739 17595 left -= n;
740 }
741 }
742 17598 flock(fd, LOCK_UN);
743
1/2
✓ Branch 0 taken 17598 times.
✗ Branch 1 not taken.
17598 close(fd);
744
1/2
✓ Branch 0 taken 17598 times.
✗ Branch 1 not taken.
17598 SetFilePerms(filename);
745
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17598 times.
17598 if (!ok) {
746 ✗ fprintf(stderr, "ERROR: Unable to write %s: %s\n", filename.c_str(), strerror(err));
747 }
748 17598 return ok;
749 }
750 195 bool CopyFileContents(const std::string& srcFile, const std::string& destFile) {
751 int input, output;
752
3/4
✓ Branch 0 taken 195 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 194 times.
195 if ((input = open(srcFile.c_str(), O_RDONLY)) == -1) {
753
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 fprintf(stderr, "ERROR: Unable to open %s for reading.\n", srcFile.c_str());
754 1 return false;
755 }
756
3/4
✓ Branch 0 taken 194 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 44 times.
✓ Branch 3 taken 150 times.
194 if ((output = creat(destFile.c_str(), 0660)) == -1) {
757
1/2
✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
44 close(input);
758 44 return false;
759 }
760 // Here we use kernel-space copying for performance
761 #if defined(__APPLE__)
762 int result = fcopyfile(input, output, 0, COPYFILE_ALL);
763 bool ok = result == 0;
764 #else
765 150 off_t bytesCopied = 0;
766 150 struct stat fileinfo = { 0 };
767 150 fstat(input, &fileinfo);
768 // sendfile returns the number of bytes copied, not 0, on success
769 150 ssize_t result = sendfile(output, input, &bytesCopied, fileinfo.st_size);
770 150 bool ok = result == fileinfo.st_size;
771 #endif
772
1/2
✓ Branch 0 taken 150 times.
✗ Branch 1 not taken.
150 close(input);
773
1/2
✓ Branch 0 taken 150 times.
✗ Branch 1 not taken.
150 close(output);
774
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 150 times.
150 if (!ok) {
775 ✗ fprintf(stderr, "ERROR: Unable to copy %s to %s.\n", srcFile.c_str(), destFile.c_str());
776 }
777 150 return ok;
778 }
779
780 17893 bool SetFilePerms(const std::string& filename, bool exBit) {
781 17893 mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
782
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 17875 times.
17893 if (exBit) {
783 18 mode |= S_IRWXU | S_IRWXG | S_IXOTH;
784 }
785 17893 chmod(filename.c_str(), mode);
786 #ifndef PLATFORM_OSX
787 uid_t uid;
788 gid_t gid;
789
4/6
✓ Branch 0 taken 17893 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17893 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1430 times.
✓ Branch 5 taken 16463 times.
35786 if (GetUserIds("fpp", &uid, &gid)) {
790 1430 chown(filename.c_str(), uid, gid);
791 }
792 #endif
793
794 17893 return true;
795 }
796
797 15 bool SetFilePerms(const char* file, bool exBit) {
798
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 const std::string filename = file;
799
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
30 return SetFilePerms(filename, exBit);
800 15 }
801
802 /////////////////////////////////////////////////////////////////////////////
803 // trim from start (in place)
804 761656 static inline void ltrim(std::string& s) {
805
2/4
✓ Branch 0 taken 761656 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 761656 times.
✗ Branch 3 not taken.
761656 s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
806 761590 return !std::isspace(ch);
807 }));
808 761656 }
809 // trim from end (in place)
810 761656 static inline void rtrim(std::string& s) {
811
2/4
✓ Branch 0 taken 761656 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 761656 times.
✗ Branch 3 not taken.
1534626 s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
812 758581 return !std::isspace(ch);
813 761656 }).base(),
814 761656 s.end());
815 761656 }
816 // trim from both ends (in place)
817 761656 void TrimWhiteSpace(std::string& s) {
818 761656 ltrim(s);
819 761656 rtrim(s);
820 761656 }
821
822 3220204 bool startsWith(const std::string& str, const std::string& prefix) {
823
4/4
✓ Branch 0 taken 3104356 times.
✓ Branch 1 taken 115848 times.
✓ Branch 2 taken 228454 times.
✓ Branch 3 taken 2875902 times.
3220204 return ((prefix.size() <= str.size()) && std::equal(prefix.begin(), prefix.end(), str.begin()));
824 }
825 426797 bool endsWith(const std::string& str, const std::string& suffix) {
826
4/4
✓ Branch 0 taken 230280 times.
✓ Branch 1 taken 196517 times.
✓ Branch 2 taken 3582 times.
✓ Branch 3 taken 226698 times.
426797 return str.size() >= suffix.size() && 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix);
827 }
828 216 bool IsShowEthernetInterface(const std::string& dev) {
829 // kernel ethernet naming only (eth0, eno1, enp3s0, enx<mac>, end0).
830 // Deliberately not every 'e' name: erspan0 or an admin's e-named
831 // bridge/veth must not get its qdisc replaced or its drops counted as
832 // FPP show-traffic drops.
833
13/28
✓ Branch 0 taken 216 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 216 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 144 times.
✓ Branch 5 taken 72 times.
✓ Branch 6 taken 144 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 144 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 144 times.
✓ Branch 12 taken 144 times.
✓ Branch 13 taken 72 times.
✓ Branch 14 taken 144 times.
✓ Branch 15 taken 72 times.
✓ Branch 16 taken 216 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 216 times.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
936 return startsWith(dev, "eth") || startsWith(dev, "en");
834 }
835 1584 std::string TcPath() {
836
3/6
✓ Branch 0 taken 1584 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1584 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1544 times.
✗ Branch 5 not taken.
3208 return FileExists("/usr/sbin/tc") ? "/usr/sbin/tc" : "/sbin/tc";
837 }
838 23 static std::string currentRootQdisc(const std::string& dev) {
839
4/8
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 23 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 23 times.
✗ Branch 7 not taken.
23 std::string cmd = TcPath() + " qdisc show dev " + dev + " 2>/dev/null";
840 23 std::string ret;
841
1/2
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
23 FILE* pipe = popen(cmd.c_str(), "r");
842
1/2
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
23 if (pipe) {
843 char buf[256];
844
2/4
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
23 while (fgets(buf, sizeof(buf), pipe)) {
845
2/4
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
23 if (strstr(buf, " root ") && strncmp(buf, "qdisc ", 6) == 0) {
846 23 char* start = &buf[6];
847 23 char* end = strchr(start, ' ');
848
1/2
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
23 if (end) {
849
1/2
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
46 ret = std::string(start, end - start);
850 }
851 23 break;
852 }
853 }
854
1/2
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
23 pclose(pipe);
855 }
856 23 return ret;
857 23 }
858 23 bool InstallShowTrafficQdisc(const std::string& dev) {
859 // fq enables per-socket pacing (SO_MAX_PACING_RATE). flow_limit must
860 // exceed the packets one paced socket can queue (wmem_max ~768KB
861 // effective / ~2KB skb truesize ~= 330) or fq silently drops the tail
862 // of large frames at enqueue, invisibly to the sender.
863
1/2
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
23 std::string cur = currentRootQdisc(dev);
864
3/4
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✓ Branch 3 taken 4 times.
23 if (cur == "fq") {
865 19 return true;
866 }
867
11/26
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 4 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 4 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 4 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✓ Branch 19 taken 4 times.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✓ Branch 25 taken 4 times.
4 if (cur != "" && cur != "pfifo_fast" && cur != "fq_codel" && cur != "mq" && cur != "noqueue" && cur != "pfifo") {
868 // an admin deliberately installed something (htb/tbf/cake/prio/...);
869 // leave it alone - pacing will just fall back to drain-based sending
870 ✗ return false;
871 }
872
4/8
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
4 std::string cmd = TcPath() + " qdisc replace dev " + dev + " root fq flow_limit 1000 > /dev/null 2>&1";
873
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 return system(cmd.c_str()) == 0;
874 23 }
875 27 bool InterfaceHasRoutableIPv4(const std::string& dev) {
876 struct ifaddrs* ifaddr;
877
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 if (getifaddrs(&ifaddr) != 0) {
878 ✗ return false;
879 }
880 27 bool found = false;
881
2/2
✓ Branch 0 taken 97 times.
✓ Branch 1 taken 4 times.
101 for (struct ifaddrs* ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
882
8/10
✓ Branch 0 taken 97 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 46 times.
✓ Branch 3 taken 51 times.
✓ Branch 4 taken 46 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 23 times.
✓ Branch 7 taken 23 times.
✓ Branch 8 taken 23 times.
✓ Branch 9 taken 74 times.
97 if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET && dev == ifa->ifa_name) {
883 23 uint32_t addr = ntohl(((struct sockaddr_in*)ifa->ifa_addr)->sin_addr.s_addr);
884 // 169.254.0.0/16 link-local is what every unconfigured interface
885 // gets from the catch-all networkd config; not a show network
886
2/4
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
23 if ((addr & 0xFFFF0000) != 0xA9FE0000 && addr != 0) {
887 23 found = true;
888 23 break;
889 }
890 }
891 }
892 27 freeifaddrs(ifaddr);
893 27 return found;
894 }
895 21025 bool contains(const std::string& str, const std::string& v) {
896 21025 return str.find(v) != std::string::npos;
897 }
898 144191 void replaceAll(std::string& str, const std::string& from, const std::string& to) {
899 144191 size_t start_pos = 0;
900
2/2
✓ Branch 0 taken 141216 times.
✓ Branch 1 taken 144191 times.
285407 while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
901 141216 str.replace(start_pos, from.length(), to);
902 141216 start_pos += to.length(); // ...
903 }
904 144191 }
905
906 226422 bool replaceStart(std::string& str, const std::string& from, const std::string& to) {
907
2/2
✓ Branch 0 taken 2970 times.
✓ Branch 1 taken 223452 times.
226422 if (!startsWith(str, from))
908 2970 return false;
909
910 223452 str.replace(0, from.size(), to);
911 223452 return true;
912 }
913
914 ✗ bool replaceEnd(std::string& str, const std::string& from, const std::string& to) {
915 ✗ if (!endsWith(str, from))
916 ✗ return false;
917
918 ✗ str.replace(str.size() - from.size(), from.size(), to);
919 ✗ return true;
920 }
921
922 ✗ void toUpper(std::string& str) {
923 ✗ std::transform(str.begin(), str.end(), str.begin(), ::toupper);
924 ✗ }
925 22064 void toLower(std::string& str) {
926 22064 std::transform(str.begin(), str.end(), str.begin(), ::tolower);
927 22064 }
928 ✗ std::string toUpperCopy(const std::string& str) {
929 ✗ std::string cp = str;
930 ✗ toUpper(cp);
931 ✗ return cp;
932 ✗ }
933 22064 std::string toLowerCopy(const std::string& str) {
934 22064 std::string cp = str;
935
1/2
✓ Branch 0 taken 22064 times.
✗ Branch 1 not taken.
22064 toLower(cp);
936 22064 return cp;
937 ✗ }
938
939 3082 const std::string& getRawMediaDir() {
940 166 static const std::string dir = [] {
941 166 const char* e = getenv("FPP_MEDIA_DIR");
942
4/6
✓ Branch 0 taken 154 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 154 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 166 times.
✗ Branch 5 not taken.
420 return std::string(e && *e ? e : "/home/fpp/media");
943
4/8
✓ Branch 0 taken 166 times.
✓ Branch 1 taken 2916 times.
✓ Branch 2 taken 166 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 166 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
3082 }();
944 3082 return dir;
945 }
946
947 2459 bool getRawSetting(const std::string& setting, std::string& value) {
948
2/4
✓ Branch 0 taken 2459 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2459 times.
✗ Branch 3 not taken.
2459 const std::string settingsFile = getRawMediaDir() + "/settings";
949
3/4
✓ Branch 0 taken 2459 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 268 times.
✓ Branch 3 taken 2191 times.
2459 if (!FileExists(settingsFile)) {
950 268 return false;
951 }
952
1/2
✓ Branch 0 taken 2191 times.
✗ Branch 1 not taken.
2191 std::string c = GetFileContents(settingsFile);
953
1/2
✓ Branch 0 taken 2191 times.
✗ Branch 1 not taken.
2191 size_t idx = c.find(setting + " = ");
954
2/2
✓ Branch 0 taken 605 times.
✓ Branch 1 taken 1586 times.
2191 if (idx != std::string::npos) {
955 605 int offset = idx + setting.size() + 3;
956 605 size_t idx2 = c.find("\r", offset);
957 605 size_t idx3 = c.find("\n", offset);
958
1/2
✓ Branch 0 taken 605 times.
✗ Branch 1 not taken.
605 if (idx2 == std::string::npos) {
959 605 idx2 = idx3;
960 }
961
2/4
✓ Branch 0 taken 605 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 605 times.
605 if (idx3 != std::string::npos && idx3 < idx2) {
962 ✗ idx2 = idx3;
963 }
964
1/2
✓ Branch 0 taken 605 times.
✗ Branch 1 not taken.
605 std::string s = c.substr(offset, idx2 - offset);
965
1/2
✓ Branch 0 taken 605 times.
✗ Branch 1 not taken.
605 TrimWhiteSpace(s);
966
1/2
✓ Branch 0 taken 605 times.
✗ Branch 1 not taken.
605 value = dequote(s);
967 605 return true;
968 605 }
969 1586 return false;
970 2459 }
971 1294 int getRawSettingInt(const std::string& setting, int def) {
972 // Parse with strtol and check where it stopped: the hand-rolled digit
973 // loop turned "" into -14, read past the end of a file without a final
974 // newline, and accepted any junk as digits.
975 1294 std::string s;
976
3/4
✓ Branch 0 taken 1294 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 896 times.
✓ Branch 3 taken 398 times.
1294 if (!getRawSetting(setting, s)) {
977 896 return def;
978 }
979
1/2
✓ Branch 0 taken 398 times.
✗ Branch 1 not taken.
398 TrimWhiteSpace(s);
980
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 398 times.
398 if (s.empty()) {
981 ✗ return def;
982 }
983 398 errno = 0;
984 398 char* end = nullptr;
985 398 long v = strtol(s.c_str(), &end, 10);
986
10/12
✓ Branch 0 taken 397 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 397 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 391 times.
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 391 times.
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 391 times.
✓ Branch 10 taken 1 times.
✓ Branch 11 taken 391 times.
398 if (end == s.c_str() || *end != '\0' || errno == ERANGE || v < INT_MIN || v > INT_MAX) {
987 1 return def;
988 }
989 397 return (int)v;
990 1294 }
991
992 473 void setRawSetting(const std::string& setting, const std::string& value) {
993
2/4
✓ Branch 0 taken 473 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 473 times.
✗ Branch 3 not taken.
473 const std::string settingsFile = getRawMediaDir() + "/settings";
994
1/2
✓ Branch 0 taken 473 times.
✗ Branch 1 not taken.
473 std::string content = GetFileContents(settingsFile);
995
1/2
✓ Branch 0 taken 473 times.
✗ Branch 1 not taken.
473 std::vector<std::string> lines = split(content, '\n');
996
1/2
✓ Branch 0 taken 473 times.
✗ Branch 1 not taken.
473 content = "";
997
2/2
✓ Branch 0 taken 2449 times.
✓ Branch 1 taken 473 times.
2922 for (auto& line : lines) {
998
3/4
✓ Branch 0 taken 2449 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2365 times.
✓ Branch 3 taken 84 times.
2449 if (line.find(setting + " = ") == std::string::npos) {
999
1/2
✓ Branch 0 taken 2365 times.
✗ Branch 1 not taken.
2365 content += line;
1000
1/2
✓ Branch 0 taken 2365 times.
✗ Branch 1 not taken.
2365 content += "\n";
1001 }
1002 }
1003
1/2
✓ Branch 0 taken 473 times.
✗ Branch 1 not taken.
473 content += setting;
1004
1/2
✓ Branch 0 taken 473 times.
✗ Branch 1 not taken.
473 content += " = \"";
1005
1/2
✓ Branch 0 taken 473 times.
✗ Branch 1 not taken.
473 content += value;
1006
1/2
✓ Branch 0 taken 473 times.
✗ Branch 1 not taken.
473 content += "\"\n";
1007
1/2
✓ Branch 0 taken 473 times.
✗ Branch 1 not taken.
473 PutFileContents(settingsFile, content);
1008 473 }
1009
1010 106 std::map<std::string, std::string> loadSettingsFile(const std::string& filename) {
1011 106 std::map<std::string, std::string> ret;
1012
1/2
✓ Branch 0 taken 106 times.
✗ Branch 1 not taken.
106 std::string content = GetFileContents(filename);
1013
1/2
✓ Branch 0 taken 106 times.
✗ Branch 1 not taken.
106 std::vector<std::string> lines = split(content, '\n');
1014
2/2
✓ Branch 0 taken 355 times.
✓ Branch 1 taken 106 times.
461 for (auto& line : lines) {
1015 355 size_t idx = line.find("=");
1016
1/2
✓ Branch 0 taken 355 times.
✗ Branch 1 not taken.
355 if (idx != std::string::npos) {
1017
1/2
✓ Branch 0 taken 355 times.
✗ Branch 1 not taken.
355 std::string key = line.substr(0, idx);
1018
1/2
✓ Branch 0 taken 355 times.
✗ Branch 1 not taken.
355 std::string value = line.substr(idx + 1);
1019
1/2
✓ Branch 0 taken 355 times.
✗ Branch 1 not taken.
355 TrimWhiteSpace(key);
1020
1/2
✓ Branch 0 taken 355 times.
✗ Branch 1 not taken.
355 TrimWhiteSpace(value);
1021
3/6
✓ Branch 0 taken 355 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 355 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 355 times.
✗ Branch 5 not taken.
355 if (!value.empty() && value[0] == '"') {
1022
1/2
✓ Branch 0 taken 355 times.
✗ Branch 1 not taken.
355 value = value.substr(1);
1023
1/2
✓ Branch 0 taken 355 times.
✗ Branch 1 not taken.
355 value = value.substr(0, value.size() - 1);
1024 }
1025
2/4
✓ Branch 0 taken 355 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 355 times.
✗ Branch 3 not taken.
355 ret[key] = value;
1026 355 }
1027 }
1028 106 return ret;
1029 106 }
1030