GCC Code Coverage Report


Directory: ./
File: scripts.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 86 98 87.8%
Functions: 3 3 100.0%
Branches: 122 228 53.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 <cstring>
16 #include <errno.h>
17 #include <memory>
18 #include <stdlib.h>
19 #include <string>
20 #include <unistd.h>
21 #include <vector>
22
23 #include "Warnings.h"
24 #include "common.h"
25 #include "log.h"
26 #include "settings.h"
27 #include "commands/Commands.h"
28
29 #include "scripts.h"
30
31 extern char** environ;
32
33 /*
34 * Fork and run a script with accompanying args
35 */
36 24 pid_t RunScript(std::string script, std::string scriptArgs) {
37 24 std::vector<std::pair<std::string, std::string>> envVars;
38
39
4/8
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 24 times.
✗ Branch 7 not taken.
48 return RunScript(script, scriptArgs, envVars);
40 24 }
41
42 // Refused a script path outside the scripts directory. Timed: it is raised
43 // per attempt, so it ages out once nothing asks for that path any more.
44 static constexpr int WARNING_ID_SCRIPT_OUTSIDE_DIR = 70;
45
46 53 pid_t RunScript(std::string script, std::string scriptArgs, std::vector<std::pair<std::string, std::string>> envVars) {
47 53 pid_t pid = 0;
48 char userScript[1024];
49 char eventScript[1024];
50
51
1/2
✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
53 LogDebug(VB_COMMAND, "Script %s: Args: %s\n", script.c_str(), scriptArgs.c_str());
52
53 // Only scripts in the scripts directory run. The name is appended to that
54 // directory, so a ".." component was the one way out of it -- and names
55 // arrive from dynamic playlist sources (a remote URL) and from commands,
56 // not just from the UI's script picker. Everything up to the first space
57 // is the path; the rest are arguments.
58
1/2
✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
53 std::string scriptPath = script.substr(0, script.find(' '));
59
3/4
✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 53 times.
✓ Branch 3 taken 31 times.
84 for (auto& part : split(scriptPath, '/')) {
60
3/4
✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 31 times.
53 if (part == "..") {
61
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 LogErr(VB_COMMAND, "RunScript: refusing script outside the scripts directory: %s\n", script.c_str());
62
3/6
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
22 WarningHolder::AddWarningTimeout(60, WARNING_ID_SCRIPT_OUTSIDE_DIR, "Refused to run a script outside the scripts directory: " + scriptPath);
63 22 return -1;
64 }
65
2/2
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 22 times.
53 }
66
67 // Setup the script from our user
68
3/6
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 31 times.
✗ Branch 5 not taken.
46 int n = snprintf(userScript, sizeof(userScript), "%s%s", FPP_DIR_SCRIPT("/").c_str(), script.c_str());
69
2/4
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 31 times.
31 if (n < 0 || n >= (int)sizeof(userScript)) {
70 // Truncated: running a prefix of the requested path is never right.
71 ✗ LogErr(VB_COMMAND, "RunScript: script path too long: %s\n", script.c_str());
72 ✗ return -1;
73 }
74
75 // Setup the wrapper
76
2/4
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
31 n = snprintf(eventScript, sizeof(eventScript), "%s/scripts/eventScript", FPP_DIR.c_str());
77
2/4
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 31 times.
31 if (n < 0 || n >= (int)sizeof(eventScript)) {
78 ✗ LogWarn(VB_COMMAND, "RunScript: eventScript path truncated\n");
79 ✗ eventScript[sizeof(eventScript) - 1] = '\0';
80 }
81
82 // Everything the child needs is built here, before fork(). fppd is
83 // multithreaded, and after fork() the child may only make async-signal-
84 // safe calls: it used to read a setting (a lock), log (locks), strdup and
85 // setenv (malloc) and exit() (static destructors) -- if another thread
86 // held any of those locks at the fork, the child deadlocked before exec,
87 // and a blocking script entry waited on it forever.
88 62 std::vector<std::string> argStrs;
89 {
90
1/2
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
31 std::string userScriptStr = userScript;
91
2/4
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
31 argStrs.push_back(userScriptStr.substr(0, userScriptStr.find(' ')));
92
3/4
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✓ Branch 3 taken 31 times.
62 for (auto& t : split(userScriptStr, ' ')) {
93
3/6
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 31 times.
✗ Branch 5 not taken.
31 if (!t.empty() && argStrs.size() < 126)
94
1/2
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
31 argStrs.push_back(t);
95 31 }
96
97
1/2
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
31 std::vector<std::string> parts = split(scriptArgs, ' ');
98
1/2
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
62 std::string tmpPart = "";
99
1/2
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
31 std::string quote = "";
100
2/2
✓ Branch 0 taken 172 times.
✓ Branch 1 taken 30 times.
202 for (int p = 0; p < parts.size(); p++) {
101
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 171 times.
172 if (argStrs.size() >= 127) {
102
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogWarn(VB_COMMAND, "RunScript: too many args, truncating\n");
103 1 break;
104 }
105
3/4
✓ Branch 0 taken 171 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 164 times.
✓ Branch 3 taken 7 times.
171 if (tmpPart == "") {
106
4/6
✓ Branch 0 taken 164 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 164 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 161 times.
356 if (startsWith(parts[p], "\"")) {
107
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 quote = std::string("\"");
108
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 tmpPart = parts[p].substr(1);
109
4/6
✓ Branch 0 taken 161 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 161 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 157 times.
350 } else if (startsWith(parts[p], "'")) {
110
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 quote = std::string("'");
111
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 tmpPart = parts[p].substr(1);
112 } else {
113
1/2
✓ Branch 0 taken 157 times.
✗ Branch 1 not taken.
157 argStrs.push_back(parts[p]);
114 }
115
116
8/14
✓ Branch 0 taken 164 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 157 times.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 7 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 7 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 164 times.
164 if ((tmpPart != "") && (quote != "") && (endsWith(tmpPart, quote))) {
117 // Chop off the ending quote
118 ✗ argStrs.push_back(tmpPart.substr(0, tmpPart.size() - 1));
119 ✗ quote = "";
120 ✗ tmpPart = "";
121 }
122 } else {
123
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 tmpPart += " ";
124
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 tmpPart += parts[p];
125
126
2/4
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 if (endsWith(parts[p], quote)) {
127
2/4
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 argStrs.push_back(tmpPart.substr(0, tmpPart.size() - 1));
128
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 quote = "";
129
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 tmpPart = "";
130 }
131 }
132 }
133 31 }
134
2/2
✓ Branch 0 taken 226 times.
✓ Branch 1 taken 31 times.
257 for (int x = 0; x < argStrs.size(); x++) {
135
1/2
✓ Branch 0 taken 226 times.
✗ Branch 1 not taken.
226 LogExcess(VB_COMMAND, "Script Arg %d: %s\n", x, argStrs[x].c_str());
136 }
137
138 // The child's environment: ours, plus FPP_SCRIPT, FPP_SCRIPTARGS and the
139 // caller's variables -- each only if not already set, as setenv(..., 0)
140 // did in the child.
141 31 std::vector<std::string> envStrs;
142
3/4
✓ Branch 0 taken 994 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 963 times.
✓ Branch 3 taken 31 times.
994 for (char** e = environ; e && *e; e++) {
143
2/4
✓ Branch 0 taken 963 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 963 times.
✗ Branch 3 not taken.
2521 envStrs.push_back(*e);
144 }
145 65 auto addEnv = [&envStrs](const std::string& k, const std::string& v) {
146
1/2
✓ Branch 0 taken 65 times.
✗ Branch 1 not taken.
65 std::string prefix = k + "=";
147
2/2
✓ Branch 0 taken 2028 times.
✓ Branch 1 taken 64 times.
2092 for (auto& e : envStrs) {
148
3/4
✓ Branch 0 taken 2028 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2027 times.
2028 if (e.compare(0, prefix.size(), prefix) == 0)
149 1 return;
150 }
151
2/4
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 64 times.
✗ Branch 3 not taken.
64 envStrs.push_back(prefix + v);
152
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 1 times.
96 };
153
2/4
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
62 addEnv("FPP_SCRIPT", script);
154
2/4
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
31 addEnv("FPP_SCRIPTARGS", scriptArgs);
155
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 31 times.
34 for (auto& ev : envVars) {
156
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 addEnv(ev.first, ev.second);
157 }
158
159 31 std::vector<char*> argv;
160
1/2
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
31 argv.reserve(argStrs.size() + 1);
161
2/2
✓ Branch 0 taken 226 times.
✓ Branch 1 taken 31 times.
257 for (auto& a : argStrs)
162
1/2
✓ Branch 0 taken 226 times.
✗ Branch 1 not taken.
226 argv.push_back(const_cast<char*>(a.c_str()));
163
1/2
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
31 argv.push_back(nullptr);
164 31 std::vector<char*> envp;
165
1/2
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
31 envp.reserve(envStrs.size() + 1);
166
2/2
✓ Branch 0 taken 1027 times.
✓ Branch 1 taken 31 times.
1058 for (auto& e : envStrs)
167
1/2
✓ Branch 0 taken 1027 times.
✗ Branch 1 not taken.
1027 envp.push_back(const_cast<char*>(e.c_str()));
168
1/2
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
31 envp.push_back(nullptr);
169
170
3/6
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 31 times.
✗ Branch 5 not taken.
31 const std::string scriptDir = FPP_DIR_SCRIPT("");
171
1/2
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
31 const int daemonize = getSettingInt("daemonize");
172 static const char chdirFailed[] = "RunScript: unable to change to the scripts directory\n";
173 static const char execFailed[] = "RunScript: execve() of the eventScript wrapper failed\n";
174
175 31 pid = fork();
176
177
2/2
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 31 times.
62 if (pid == 0) // Event Script process: async-signal-safe calls only
178 {
179
1/2
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
31 CloseOpenFiles(daemonize);
180
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
31 if (chdir(scriptDir.c_str())) {
181 ✗ if (!daemonize)
182 ✗ (void)!write(2, chdirFailed, sizeof(chdirFailed) - 1);
183 ✗ _exit(EXIT_FAILURE);
184 }
185 31 execve(eventScript, argv.data(), envp.data());
186
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
31 if (!daemonize)
187 ✗ (void)!write(2, execFailed, sizeof(execFailed) - 1);
188 31 _exit(EXIT_FAILURE);
189 }
190
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
31 if (pid < 0) {
191 ✗ LogErr(VB_COMMAND, "RunScript: fork() failed: %s\n", FPPstrerror(errno));
192 }
193 31 return pid;
194 53 }
195