GCC Code Coverage Report


Directory: ./
File: command.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 180 216 83.3%
Functions: 5 5 100.0%
Branches: 224 462 48.5%

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
13 #include "fpp-pch.h"
14
15 #include "command.h"
16 #include <sys/socket.h>
17 #include <sys/stat.h>
18 #include <sys/un.h>
19 #include <array>
20 #include <cstring>
21 #include <ctime>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <signal.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string>
28 #include <unistd.h>
29
30 #include "Player.h"
31 #include "Scheduler.h"
32 #include "Sequence.h"
33 #include "Warnings.h"
34 #include "common.h"
35 #include "effects.h"
36 #include "fpp.h"
37 #include "gpio.h"
38 #include "log.h"
39 #include "settings.h"
40 #include "channeltester/ChannelTester.h"
41 #include "commands/Commands.h"
42 #include "mediaoutput/MediaOutputBase.h"
43 #include "mediaoutput/MediaOutputStatus.h"
44 #include "mediaoutput/RTSPOutputManager.h"
45 #include "mediaoutput/VideoInputManager.h"
46 #include "mediaoutput/VideoOutputManager.h"
47 #include "mediaoutput/mediaoutput.h"
48 #include "playlist/Playlist.h"
49 #include "util/GPIOUtils.h"
50 #include "SysSocket.h"
51
52 constexpr int MAX_RESPONSE_SIZE = 1501;
53
54 int socket_fd = -1;
55 struct sockaddr_un server_address;
56 int integer_buffer;
57 int fppdStartTime = 0;
58
59 // SIGINT and SIGTERM are not handled here. They used to be, by a handler
60 // that logged, closed media and ran ShutdownFPPD() inside the signal handler,
61 // none of which is async-signal-safe. fppd's own handler (installed with the
62 // HTTP server) only clears the main-loop flag; before that the default action
63 // simply terminates the process, which is safe this early in startup.
64 // A startup failure here leaves fppd half started, with threads running
65 // (the volume worker, the warnings notifier) that the static destructors
66 // exit() would run can block on or free memory under. Leave without them,
67 // as fppd's own ExitWithoutTeardown() does.
68 1 [[noreturn]] static void ExitEarly(int code) {
69 1 fflush(nullptr);
70 1 _exit(code);
71 }
72
73 1887 int Command_Initialize() {
74 mode_t old_umask;
75
76 1887 LogInfo(VB_COMMAND, "Initializing Command Module\n");
77
78
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1887 times.
1887 if ((socket_fd = OpenSocket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
79 ✗ perror("server: socket");
80 ✗ ExitEarly(1);
81 }
82
83 1887 mkdir(FPP_SOCKET_PATH, 0777);
84 1887 chmod(FPP_SOCKET_PATH, 0777);
85
86 1887 fcntl(socket_fd, F_SETFL, O_NONBLOCK);
87 1887 memset(&server_address, 0, sizeof(server_address));
88 1887 server_address.sun_family = AF_UNIX;
89 1887 strcpy(server_address.sun_path, FPP_SERVER_SOCKET);
90 1887 unlink(FPP_SERVER_SOCKET);
91
92 1887 old_umask = umask(0011);
93
94
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1886 times.
1887 if (bind(socket_fd, (const struct sockaddr*)&server_address, sizeof(server_address)) < 0) {
95 1 close(socket_fd);
96 1 perror("command socket bind");
97 1 ExitEarly(1);
98 }
99
100 1886 symlink(FPP_SERVER_SOCKET, FPP_SERVER_SOCKET_OLD);
101 1886 umask(old_umask);
102
103 1886 fppdStartTime = time(NULL);
104
105 1886 return socket_fd;
106 }
107
108 1867 void CloseCommand() {
109
1/2
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
1867 if (socket_fd >= 0) {
110 1867 close(socket_fd);
111 1867 unlink(FPP_SERVER_SOCKET);
112 1867 socket_fd = -1;
113 }
114 1867 }
115
116 369 char* ProcessCommand(char* command, char* response) {
117 char* s;
118 char* s2;
119 char* s3;
120 char* s4;
121 369 char* response2 = NULL;
122 int i;
123
1/2
✓ Branch 0 taken 369 times.
✗ Branch 1 not taken.
738 std::string NextPlaylist = "No playlist scheduled.";
124
1/2
✓ Branch 0 taken 369 times.
✗ Branch 1 not taken.
369 std::string NextPlaylistStart = "";
125 char CommandStr[64];
126 369 char* saveptr = nullptr; // for thread-safe strtok_r below
127
1/2
✓ Branch 0 taken 369 times.
✗ Branch 1 not taken.
369 LogExcess(VB_COMMAND, "CMD: %s\n", command);
128 369 s = strtok_r(command, ",", &saveptr);
129
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 367 times.
369 if (!s) {
130 2 s = command; // empty/all-delimiter command; graceful "Invalid command" path below
131 }
132 369 strncpy(CommandStr, s, sizeof(CommandStr)); // s can be 256 bytes long
133 369 CommandStr[sizeof(CommandStr) - 1] = '\0';
134
135
2/2
✓ Branch 0 taken 367 times.
✓ Branch 1 taken 2 times.
369 if ((!strcmp(CommandStr, "d")) ||
136
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 367 times.
367 (!strcmp(CommandStr, "StopNow"))) {
137
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if ((Player::INSTANCE.GetStatus() == FPP_STATUS_PLAYLIST_PLAYING) ||
138
4/8
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
4 (Player::INSTANCE.GetStatus() == FPP_STATUS_STOPPING_GRACEFULLY) ||
139
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 (Player::INSTANCE.GetStatus() == FPP_STATUS_STOPPING_GRACEFULLY_AFTER_LOOP)) {
140 ✗ Player::INSTANCE.StopNow(1);
141 ✗ snprintf(response, MAX_RESPONSE_SIZE - 1, "%d,%d,Playlist Stopping Now,,,,,,,,,,\n", getFPPmode(), COMMAND_SUCCESS);
142
4/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
4 } else if ((Player::INSTANCE.GetStatus() == FPP_STATUS_IDLE) &&
143
3/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 (sequence->IsSequenceRunning())) {
144
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 sequence->CloseSequenceFile();
145
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 snprintf(response, MAX_RESPONSE_SIZE - 1, "%d,%d,Sequence Stopping Now,,,,,,,,,,\n", getFPPmode(), COMMAND_SUCCESS);
146 } else {
147
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 snprintf(response, MAX_RESPONSE_SIZE - 1, "%d,%d,Not playing,,,,,,,,,,\n", getFPPmode(), COMMAND_FAILED);
148 }
149
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 366 times.
367 } else if (!strcmp(CommandStr, "R")) {
150
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 scheduler->ReloadScheduleFile();
151
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 snprintf(response, MAX_RESPONSE_SIZE - 1, "%d,%d,Reloading Schedule,,,,,,,,,,\n", getFPPmode(), COMMAND_SUCCESS);
152
2/2
✓ Branch 0 taken 50 times.
✓ Branch 1 taken 316 times.
366 } else if (!strcmp(CommandStr, "v")) {
153 50 s = strtok_r(NULL, ",", &saveptr);
154
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 2 times.
50 if (s) {
155
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 setVolume(atoi(s));
156
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 snprintf(response, MAX_RESPONSE_SIZE - 1, "%d,%d,Setting Volume,,,,,,,,,,\n", getFPPmode(), COMMAND_SUCCESS);
157 } else {
158
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 snprintf(response, MAX_RESPONSE_SIZE - 1, "%d,%d,Invalid Volume,,,,,,,,,,\n", getFPPmode(), COMMAND_FAILED);
159 }
160
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 314 times.
316 } else if (!strcmp(CommandStr, "q")) {
161 // Quit/Shutdown fppd
162
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if ((Player::INSTANCE.GetStatus() == FPP_STATUS_PLAYLIST_PLAYING) ||
163
6/8
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 1 times.
3 (Player::INSTANCE.GetStatus() == FPP_STATUS_STOPPING_GRACEFULLY) ||
164
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 (Player::INSTANCE.GetStatus() == FPP_STATUS_STOPPING_GRACEFULLY_AFTER_LOOP)) {
165
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 Player::INSTANCE.StopNow(1);
166
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 sleep(2);
167 }
168
169
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 ShutdownFPPD();
170 // fpp waits for a non-empty reply, so an empty one stalls it until
171 // its ~2s timeout.
172
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 snprintf(response, MAX_RESPONSE_SIZE - 1, "%d,%d,Shutting Down,,,,,,,,,,\n", getFPPmode(), COMMAND_SUCCESS);
173
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 314 times.
314 } else if (!strcmp(CommandStr, "restart")) {
174 ✗ ShutdownFPPD(true);
175
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 311 times.
314 } else if (!strcmp(CommandStr, "GetTestMode")) {
176 // GetConfig() is the full SetTestMode JSON (API-controlled, unbounded);
177 // strcpy/strcat would smash the 1501-byte stack reply. snprintf keeps
178 // the exact "config\n" shape for fitting values and truncates the rest.
179
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 snprintf(response, MAX_RESPONSE_SIZE - 1, "%s\n", ChannelTester::INSTANCE.GetConfig().c_str());
180
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 298 times.
311 } else if (!strcmp(CommandStr, "SetTestMode")) {
181 // The test config is everything after the first comma, which is where
182 // strtok_r left saveptr (NULL or "" when there was no comma). It used
183 // to be s + strlen(s) + 1, which with no comma pointed past the end of
184 // the command into stale receive-buffer bytes.
185
5/8
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 9 times.
26 if (ChannelTester::INSTANCE.SetupTest(std::string(saveptr ? saveptr : ""))) {
186 4 snprintf(response, MAX_RESPONSE_SIZE - 1, "0,%d,Test Mode Activated,,,,,,,,,\n",
187 COMMAND_SUCCESS);
188 } else {
189 9 snprintf(response, MAX_RESPONSE_SIZE - 1, "0,%d,Test Mode Deactivated,,,,,,,,,\n",
190 COMMAND_SUCCESS);
191 }
192
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 280 times.
298 } else if (!strcmp(CommandStr, "LogLevel")) {
193 18 s = strtok_r(NULL, ",", &saveptr);
194 18 s2 = NULL;
195
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 2 times.
18 if (s != NULL)
196 16 s2 = strtok_r(NULL, ",", &saveptr);
197
198
3/4
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✓ Branch 3 taken 5 times.
18 if (FPPLogger::INSTANCE.SetLevel(s, s2)) {
199 13 snprintf(response, MAX_RESPONSE_SIZE - 1, "%d,%d,Log Level Updated,%s,%s,,,,,,,,,\n",
200
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 getFPPmode(), COMMAND_SUCCESS, s, s2);
201
202
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 WarningHolder::UpdateLogLevelWarnings();
203 } else {
204
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 2 times.
5 snprintf(response, MAX_RESPONSE_SIZE - 1, "%d,%d,Error Updating Log Level,%s,%s,,,,,,,,,\n",
205
1/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
5 getFPPmode(), COMMAND_FAILED, s ? s : "", s2 ? s2 : "");
206 }
207 /*
208 } else if (!strcmp(CommandStr, "LogMask")) {
209 s = strtok_r(NULL,",", &saveptr);
210
211 if ((s && SetLogMask(s)) || SetLogMask("")) {
212 snprintf(response,MAX_RESPONSE_SIZE-1,"%d,%d,Log Mask Updated,%d,%d,,,,,,,,,\n",
213 getFPPmode(),COMMAND_SUCCESS,logLevel,logMask);
214 } else {
215 snprintf(response,MAX_RESPONSE_SIZE-1,"%d,%d,Error Updating Log Mask,%d,%d,,,,,,,,,\n",
216 getFPPmode(),COMMAND_FAILED,logLevel,logMask);
217 }
218 */
219
2/2
✓ Branch 0 taken 94 times.
✓ Branch 1 taken 186 times.
280 } else if (!strcmp(CommandStr, "reloadVideoOutputs")) {
220
2/4
✓ Branch 0 taken 94 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 94 times.
✗ Branch 3 not taken.
94 VideoOutputManager::Instance().Reload();
221
1/2
✓ Branch 0 taken 94 times.
✗ Branch 1 not taken.
94 snprintf(response, MAX_RESPONSE_SIZE - 1, "%d,%d,Video outputs reloaded,,,,,,,,,,\n", getFPPmode(), COMMAND_SUCCESS);
222
2/2
✓ Branch 0 taken 53 times.
✓ Branch 1 taken 133 times.
186 } else if (!strcmp(CommandStr, "reloadVideoInputs")) {
223
2/4
✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 53 times.
✗ Branch 3 not taken.
53 VideoInputManager::Instance().RequestReload();
224
1/2
✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
53 snprintf(response, MAX_RESPONSE_SIZE - 1, "%d,%d,Video inputs reloaded,,,,,,,,,,\n", getFPPmode(), COMMAND_SUCCESS);
225 #ifdef HAS_RTSP_OUTPUT_GSTREAMER
226
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 129 times.
133 } else if (!strcmp(CommandStr, "reloadRTSPOutputs")) {
227
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 RTSPOutputManager::INSTANCE.ApplyConfig();
228
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 snprintf(response, MAX_RESPONSE_SIZE - 1, "%d,%d,RTSP outputs reloaded,,,,,,,,,,\n", getFPPmode(), COMMAND_SUCCESS);
229 #endif
230
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 119 times.
129 } else if (!strcmp(CommandStr, "SetSetting")) {
231 char name[128];
232
233 10 s = strtok_r(NULL, ",", &saveptr);
234
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
10 if (s) {
235 // Bounded copy - the token can be longer than the 128-byte buffer.
236 8 snprintf(name, sizeof(name), "%s", s);
237 // Don't use strtok_r for the value: it skips empty fields, so a
238 // command clearing a setting to "" (e.g. "SetSetting,foo,,")
239 // would otherwise never call SetSetting() at all. saveptr points
240 // at the start of the value here; split it on the next comma
241 // manually so an empty value still comes through.
242 // With no comma after the name ("SetSetting,foo") glibc leaves
243 // saveptr at the terminating NUL but BSD/macOS strtok_r sets it to
244 // NULL, so treat a NULL saveptr as an empty value.
245 static char emptyValue[1] = { 0 };
246
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 char* value = saveptr ? saveptr : emptyValue;
247 8 char* comma = strchr(value, ',');
248
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
8 if (comma)
249 4 *comma = '\0';
250
3/6
✓ 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.
32 SetSetting(name, value);
251 }
252
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 100 times.
119 } else if (!strcmp(CommandStr, "StartSequence")) {
253
3/6
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 19 times.
✗ Branch 5 not taken.
38 if ((Player::INSTANCE.GetStatus() == FPP_STATUS_IDLE) &&
254
2/4
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
19 (!sequence->IsSequenceRunning())) {
255 19 s = strtok_r(NULL, ",", &saveptr);
256 19 s2 = strtok_r(NULL, ",", &saveptr);
257
3/4
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
19 if (s && s2) {
258 18 i = atoi(s2);
259 // Start only what this request opened. A refused name leaves
260 // any sequence already open but not started (a remote's,
261 // waiting for its master) in place, and StartSequence() would
262 // start that one instead.
263
4/6
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✓ Branch 5 taken 5 times.
72 if (sequence->OpenSequenceFile(s, 0, i) > 0) {
264
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 sequence->StartSequence();
265 } else {
266
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 LogWarn(VB_COMMAND, "Not starting sequence %s: it could not be opened\n", s);
267 }
268 } else {
269
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogDebug(VB_COMMAND, "Invalid command: %s\n", command);
270 }
271 } else {
272 ✗ LogErr(VB_COMMAND, "Tried to start a sequence when a playlist or "
273 "sequence is already running\n");
274 }
275
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 87 times.
100 } else if (!strcmp(CommandStr, "StopSequence")) {
276
4/6
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✓ Branch 5 taken 1 times.
26 if ((Player::INSTANCE.GetStatus() == FPP_STATUS_IDLE) &&
277
3/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 1 times.
13 (sequence->IsSequenceRunning())) {
278
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 sequence->CloseSequenceFile();
279 } else {
280
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogDebug(VB_COMMAND,
281 "Tried to stop a sequence when no sequence is running\n");
282 }
283
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 65 times.
87 } else if (!strcmp(CommandStr, "ToggleSequencePause")) {
284
3/4
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 21 times.
✓ Branch 3 taken 1 times.
43 if ((sequence->IsSequenceRunning()) &&
285
3/4
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 11 times.
21 ((Player::INSTANCE.GetStatus() == FPP_STATUS_IDLE) ||
286
2/4
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
10 ((Player::INSTANCE.GetStatus() != FPP_STATUS_IDLE) &&
287
12/22
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 10 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 10 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 10 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 10 times.
✓ Branch 13 taken 12 times.
✓ Branch 14 taken 10 times.
✓ Branch 15 taken 12 times.
✓ Branch 16 taken 21 times.
✓ Branch 17 taken 1 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
32 (Player::INSTANCE.GetInfo()["currentEntry"]["type"] == "sequence")))) {
288
1/2
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
21 sequence->ToggleSequencePause();
289 }
290
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 33 times.
65 } else if (!strcmp(CommandStr, "SingleStepSequence")) {
291 127 if ((sequence->IsSequenceRunning()) &&
292
5/8
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 31 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 31 times.
✗ Branch 7 not taken.
63 (sequence->SequenceIsPaused()) &&
293
3/4
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 16 times.
31 ((Player::INSTANCE.GetStatus() == FPP_STATUS_IDLE) ||
294
2/4
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 ((Player::INSTANCE.GetStatus() != FPP_STATUS_IDLE) &&
295
12/22
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 15 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 15 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 15 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 15 times.
✓ Branch 13 taken 17 times.
✓ Branch 14 taken 15 times.
✓ Branch 15 taken 17 times.
✓ Branch 16 taken 31 times.
✓ Branch 17 taken 1 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
47 (Player::INSTANCE.GetInfo()["currentEntry"]["type"] == "sequence")))) {
296
1/2
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
31 sequence->SingleStepSequence();
297 }
298 // Has been broken for some time issue #986
299 // } else if (!strcmp(CommandStr, "SingleStepSequenceBack")) {
300 // if ((sequence->IsSequenceRunning()) &&
301 // (sequence->SequenceIsPaused()) &&
302 // ((Player::INSTANCE.GetStatus() == FPP_STATUS_IDLE) ||
303 // ((Player::INSTANCE.GetStatus() != FPP_STATUS_IDLE) &&
304 // (Player::INSTANCE.GetInfo()["currentEntry"]["type"] == "sequence")))) {
305 // sequence->SingleStepSequenceBack();
306 // }
307
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 30 times.
33 } else if (!strcmp(CommandStr, "SetupExtGPIO")) {
308 // Configure the given GPIO to the given mode
309 3 s = strtok_r(NULL, ",", &saveptr);
310 3 s2 = strtok_r(NULL, ",", &saveptr);
311
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
3 if (s && s2) {
312
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 const PinCapabilities* pin = PinCapabilities::getPinByName(s).ptr();
313
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!pin) {
314
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 pin = PinCapabilities::getPinByGPIO(0, atoi(s)).ptr();
315 }
316 2 bool OK = false;
317
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (pin) {
318 ✗ OK = true;
319 ✗ std::string mode = s2;
320 ✗ if (mode == "Input") {
321 ✗ pin->configPin("gpio", false);
322 ✗ } else if (mode == "Output") {
323 ✗ pin->configPin("gpio", true);
324 ✗ } else if (mode == "SoftPWM" || mode == "PWM") {
325 ✗ if (pin->supportPWM()) {
326 ✗ pin->setupPWM(10000);
327 } else {
328 ✗ LogDebug(VB_GPIO, "GPIO %s does not support PWM\n", pin->name.c_str());
329 ✗ OK = false;
330 }
331 }
332 ✗ }
333
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (OK) {
334 ✗ snprintf(response, MAX_RESPONSE_SIZE - 1, "%d,%d,Configuring GPIO,%s,%s,,,,,,,,,\n", getFPPmode(), COMMAND_SUCCESS, s, s2);
335 } else {
336
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 snprintf(response, MAX_RESPONSE_SIZE - 1, "%d,%d,Configuring GPIO,%s,%s,,,,,,,,,\n", getFPPmode(), COMMAND_FAILED, s, s2);
337 }
338 }
339
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 27 times.
30 } else if (!strcmp(CommandStr, "ExtGPIO")) {
340 3 s = strtok_r(NULL, ",", &saveptr);
341 3 s2 = strtok_r(NULL, ",", &saveptr);
342 3 s3 = strtok_r(NULL, ",", &saveptr);
343
4/6
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
3 if (s && s2 && s3) {
344
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 const PinCapabilities* pin = PinCapabilities::getPinByName(s).ptr();
345
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!pin) {
346
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 pin = PinCapabilities::getPinByGPIO(0, atoi(s)).ptr();
347 }
348 2 int i = 0;
349
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (pin) {
350 ✗ std::string mode = s2;
351 ✗ if (mode == "Input") {
352 ✗ pin->configPin("gpio", false);
353 ✗ i = pin->getValue();
354 ✗ } else if (mode == "Output") {
355 ✗ pin->configPin("gpio", true);
356 ✗ pin->setValue(atoi(s3));
357 ✗ } else if (mode == "SoftPWM" || mode == "PWM") {
358 ✗ if (pin->supportPWM()) {
359 ✗ pin->setPWMValue(100 * atoi(s3)); // Convert to percentage
360 } else {
361 ✗ LogDebug(VB_GPIO, "GPIO %s does not support PWM\n", pin->name.c_str());
362 }
363 }
364 ✗ }
365
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (i >= 0) {
366
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 snprintf(response, MAX_RESPONSE_SIZE - 1, "%d,%d,Setting GPIO,%s,%s,%s,%d,,,,,,,\n", getFPPmode(), COMMAND_SUCCESS, s, s2, s3, i);
367 } else {
368 ✗ snprintf(response, MAX_RESPONSE_SIZE - 1, "%d,%d,Setting GPIO,%s,%s,%s,,,,,,,,\n", getFPPmode(), COMMAND_FAILED, s, s2, s3);
369 }
370 }
371 } else {
372 27 snprintf(response, MAX_RESPONSE_SIZE - 1, "Invalid command: '%s'\n", CommandStr);
373 }
374
375 369 return response2;
376 369 }
377
378 340 void CommandProc() {
379 340 constexpr int MAX_COMMAND_SIZE = 4096;
380 std::array<char, MAX_COMMAND_SIZE> command;
381
382 struct sockaddr_un client_address;
383 int bytes_received, bytes_sent;
384 socklen_t address_length;
385
386 // recvfrom() shrinks address_length to the sender's address size, so it
387 // (and the address itself) must be reset before every call: otherwise a
388 // short sender address truncates the next sender's path, or leaves the
389 // previous sender's bytes behind it, and the reply goes nowhere.
390 340 address_length = sizeof(client_address);
391 340 memset(&client_address, 0, sizeof(client_address));
392 340 memset(&command[0], 0, MAX_COMMAND_SIZE);
393
1/2
✓ Branch 0 taken 340 times.
✗ Branch 1 not taken.
340 bytes_received = recvfrom(socket_fd, &command[0], MAX_COMMAND_SIZE - 1, 0,
394 (struct sockaddr*)&(client_address),
395 &address_length);
396
397
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 339 times.
340 if (bytes_received <= 0) {
398
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogWarn(VB_COMMAND, "Command socket received but no bytes\n");
399 }
400
401
2/2
✓ Branch 0 taken 344 times.
✓ Branch 1 taken 340 times.
684 while (bytes_received > 0) {
402 344 command[bytes_received] = 0;
403
404 std::array<char, MAX_RESPONSE_SIZE> response;
405 344 response[0] = 0;
406 344 response[MAX_RESPONSE_SIZE - 1] = '\n';
407 // The socket protocol carries no caller identity (no PID/process info
408 // is sent with the datagram), so this can't say which local caller it
409 // was - only that it was local. A native C++ plugin calling
410 // CommandManager in-process never reaches this socket at all, but a
411 // plugin's own PHP page using commandsocket.php's SendCommand() looks
412 // identical to any other FPP UI page here - hence "plugin web page"
413 // rather than a bare "plugin", which would invite exactly the wrong
414 // inference when tracking down what a C++ plugin did. Realistic
415 // callers: the FPP UI, a plugin's own PHP page, the "fpp" CLI tool, or
416 // a local script talking to the socket directly.
417 //
418 // Naming the real caller is possible but not free: the receiver would
419 // set SO_PASSCRED on the socket and switch the recvfrom() calls below
420 // to recvmsg() to read the sender's pid out of SCM_CREDENTIALS, then
421 // resolve /proc/<pid>/comm. Worth doing; deliberately not done here.
422 //
423 // command is NUL-terminated and bounded by MAX_COMMAND_SIZE (4096), so
424 // this can't run away - but 4095 chars is still ~17 crash-ring slots,
425 // so truncate for the log like every other caller-supplied string.
426
3/6
✓ Branch 0 taken 344 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 344 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 344 times.
✗ Branch 5 not taken.
347 LogDebug(VB_COMMAND, "Local command socket (FPP UI, plugin web page, fpp CLI, script, etc) running \"%s\"\n",
427 TruncateForLog(&command[0]).c_str());
428
1/2
✓ Branch 0 taken 344 times.
✗ Branch 1 not taken.
344 char* response2 = ProcessCommand(&command[0], &response[0]);
429 344 errno = 0;
430
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 344 times.
344 if (response2) {
431 ✗ bytes_sent = sendto(socket_fd, response2, strlen(response2), 0,
432 (struct sockaddr*)&(client_address), sizeof(struct sockaddr_un));
433 ✗ LogDebug(VB_COMMAND, "%s - (%d %d %s) %s", &command[0], bytes_sent, errno, FPPstrerror(errno), response2);
434 ✗ free(response2);
435 ✗ response2 = NULL;
436 } else {
437
1/2
✓ Branch 0 taken 344 times.
✗ Branch 1 not taken.
344 bytes_sent = sendto(socket_fd, &response[0], strlen(&response[0]), 0,
438 (struct sockaddr*)&(client_address), sizeof(struct sockaddr_un));
439
2/4
✓ Branch 0 taken 344 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 344 times.
✗ Branch 3 not taken.
344 LogDebug(VB_COMMAND, "%s - (%d %d %s) %s",
440 &command[0], bytes_sent, errno, FPPstrerror(errno), &response[0]);
441 }
442
443 344 address_length = sizeof(client_address);
444 344 memset(&client_address, 0, sizeof(client_address));
445 344 memset(&command[0], 0, MAX_COMMAND_SIZE);
446
1/2
✓ Branch 0 taken 344 times.
✗ Branch 1 not taken.
344 bytes_received = recvfrom(socket_fd, &command[0], MAX_COMMAND_SIZE - 1, 0,
447 (struct sockaddr*)&(client_address),
448 &address_length);
449 }
450 340 }
451