GCC Code Coverage Report


Directory: ./
File: settings.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 344 388 88.7%
Functions: 34 35 97.1%
Branches: 365 676 54.0%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2025 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 #include "fpp-json.h"
13
14 #include <atomic>
15 #include <cerrno>
16 #include <condition_variable>
17 #include <cstring>
18 #include <ctype.h>
19 #include <fcntl.h>
20 #include <map>
21 #include <memory>
22 #include <mutex>
23 #include <pwd.h>
24 #include <shared_mutex>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string>
28 #include <sys/file.h>
29 #include <unistd.h>
30 #include <utility>
31 #include <vector>
32
33 #include "common.h"
34 #include "log.h"
35 #include "settings.h"
36
37 #ifdef PLATFORM_OSX
38 #include <mach-o/dyld.h>
39 #endif
40 #include <sys/fcntl.h>
41
42 // Built on first use and never destroyed. Both are reached from other static
43 // objects: AES67Manager's constructor asks for the media directory during
44 // static initialization, and ~PluginManager -> CommandManager::Cleanup() asks
45 // again when fppd leaves through exit(). File-scope strings could be used
46 // there before they were constructed, or after they were destroyed.
47 23105 static std::string& FPPBinDir() {
48
5/10
✓ Branch 0 taken 2385 times.
✓ Branch 1 taken 20720 times.
✓ Branch 2 taken 2385 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2385 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2385 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
23105 static std::string* dir = new std::string();
49 23105 return *dir;
50 }
51 1436103 static std::string& FPPMediaDir() {
52
5/10
✓ Branch 0 taken 2385 times.
✓ Branch 1 taken 1433718 times.
✓ Branch 2 taken 2385 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2385 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2385 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
1436103 static std::string* dir = new std::string();
53 1436103 return *dir;
54 }
55
56 23105 std::string getFPPDDir(const std::string& path) {
57 23105 std::string& FPP_BIN_DIR = FPPBinDir();
58
2/2
✓ Branch 0 taken 2385 times.
✓ Branch 1 taken 20720 times.
23105 if (FPP_BIN_DIR == "") {
59 char exePath[2048];
60 2385 exePath[0] = 0;
61 #ifdef PLATFORM_OSX
62 uint32_t len = sizeof(exePath);
63 _NSGetExecutablePath(exePath, &len);
64 #else
65 2385 ssize_t len = ::readlink("/proc/self/exe", exePath, sizeof(exePath) - 1);
66
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2385 times.
2385 if (len == -1) {
67 ✗ LogWarn(VB_GENERAL, "readlink /proc/self/exe failed: %s\n", strerror(errno));
68 ✗ exePath[0] = '\0';
69 } else {
70 2385 exePath[len] = '\0';
71 }
72 #endif
73
1/2
✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
2385 FPP_BIN_DIR = exePath;
74
2/4
✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2385 times.
✗ Branch 3 not taken.
2385 FPP_BIN_DIR = FPP_BIN_DIR.substr(0, FPP_BIN_DIR.rfind("/"));
75
2/4
✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2385 times.
✗ Branch 3 not taken.
2385 FPP_BIN_DIR = FPP_BIN_DIR.substr(0, FPP_BIN_DIR.rfind("/"));
76 }
77 23105 return FPP_BIN_DIR + path;
78 }
79 1436103 std::string getFPPMediaDir(const std::string& path) {
80 1436103 std::string& FPP_MEDIA_DIR = FPPMediaDir();
81
2/2
✓ Branch 0 taken 2385 times.
✓ Branch 1 taken 1433718 times.
1436103 if (FPP_MEDIA_DIR == "") {
82
2/4
✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2385 times.
✗ Branch 3 not taken.
4770 std::string filename = getFPPDDir("/www/media_root.txt");
83
1/2
✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
2385 std::string fc = "";
84
3/4
✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1927 times.
✓ Branch 3 taken 458 times.
2385 if (FileExists(filename)) {
85
1/2
✓ Branch 0 taken 1927 times.
✗ Branch 1 not taken.
1927 fc = GetFileContents(filename);
86
1/2
✓ Branch 0 taken 1927 times.
✗ Branch 1 not taken.
1927 FPP_MEDIA_DIR = fc;
87
1/2
✓ Branch 0 taken 1927 times.
✗ Branch 1 not taken.
1927 TrimWhiteSpace(FPP_MEDIA_DIR);
88 }
89
3/4
✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 458 times.
✓ Branch 3 taken 1927 times.
2385 if (FPP_MEDIA_DIR == "") {
90 #ifdef PLATFORM_OSX
91 // getpwuid() is not thread-safe; use getpwuid_r(). Also guards
92 // against a NULL deref when neither $HOME nor the passwd entry exist.
93 const char* homedir = getenv("HOME");
94 struct passwd pwd;
95 struct passwd* pwres = nullptr;
96 char pbuf[16384];
97 if (homedir == NULL &&
98 getpwuid_r(getuid(), &pwd, pbuf, sizeof(pbuf), &pwres) == 0 && pwres) {
99 homedir = pwres->pw_dir;
100 }
101 if (homedir) {
102 FPP_MEDIA_DIR = homedir;
103 FPP_MEDIA_DIR += "/Documents/fpp";
104 }
105 #else
106
1/2
✓ Branch 0 taken 458 times.
✗ Branch 1 not taken.
458 FPP_MEDIA_DIR = "/home/fpp/media";
107 #endif
108 }
109
2/2
✓ Branch 0 taken 458 times.
✓ Branch 1 taken 1927 times.
2385 if (FPP_MEDIA_DIR != fc) {
110
1/2
✓ Branch 0 taken 458 times.
✗ Branch 1 not taken.
458 PutFileContents(filename, FPP_MEDIA_DIR);
111 }
112 2385 }
113 1436103 return FPP_MEDIA_DIR + path;
114 }
115
116 class SettingListener {
117 public:
118 std::string id;
119 std::function<void(const std::string&)> callback;
120 };
121 class SettingsConfig {
122 public:
123 SettingsConfig();
124 ~SettingsConfig();
125
126 void Init();
127
128 // written by the settings-file reload, read unlocked by getFPPmode()
129 std::atomic<FPPMode> fppMode;
130
131 std::string getSetting(const std::string& setting, const std::string& defaultVal);
132 int getSettingInt(const std::string& setting, int defaultVal);
133
134 void setSetting(const std::string& key, const std::string& value, bool persist);
135
136 int LoadSettings(const std::string& base, bool logChanges = false);
137
138 void registerSettingsListener(const std::string& id, const std::string& setting, std::function<void(const std::string&)>&& cb);
139 void unregisterSettingsListener(const std::string& id, const std::string& setting);
140
141 private:
142 bool setSettingLocked(const std::string& key, const std::string& value, bool logInfo);
143 bool persistSetting(const std::string& key, const std::string& value);
144 void notifyListeners(const std::map<std::string, std::string>& changed);
145 void LoadSettingsInfo();
146
147 Json::Value settingsInfo;
148 Json::Value settings;
149 std::shared_mutex settingsMutex;
150 // Serializes setSetting(persist) so the file is written in the same order
151 // the in-memory values changed. Taken before settingsMutex, never after.
152 std::mutex persistMutex;
153
154 std::map<std::string, std::list<SettingListener>> settingsListeners;
155 std::shared_mutex settingsListenersMutex;
156 // Listener calls copied out by notifyListeners() and not yet returned,
157 // per (setting, id): unregisterSettingsListener() waits for its own.
158 // listenerCallsLock is a leaf lock; no callback runs under it.
159 std::map<std::pair<std::string, std::string>, int> listenerCallsInFlight;
160 std::mutex listenerCallsLock;
161 std::condition_variable listenerCallFinished;
162 void retireListenerCalls(const std::vector<std::pair<SettingListener, const std::string*>>& calls,
163 size_t from, size_t to);
164 };
165 // Nonzero while this thread is inside a settings listener: a listener that
166 // unregisters (itself or another) must not wait on a call that may be its
167 // own, or one waiting on it.
168 static thread_local int t_inSettingsListener = 0;
169 static SettingsConfig settings;
170
171
1/2
✓ Branch 0 taken 2385 times.
✗ Branch 1 not taken.
2385 SettingsConfig::SettingsConfig() {
172 2385 }
173
174 497 SettingsConfig::~SettingsConfig() {
175 497 }
176
177 2211 void SettingsConfig::Init() {
178
2/2
✓ Branch 0 taken 1923 times.
✓ Branch 1 taken 288 times.
2211 if (settingsInfo.empty()) {
179 1923 LoadSettingsInfo();
180 1923 fppMode = PLAYER_MODE;
181
182 // load UUID File
183
3/6
✓ Branch 0 taken 1923 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1923 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1923 times.
✗ Branch 5 not taken.
3846 if (access(getFPPDDir("/scripts/get_uuid").c_str(), F_OK) == 0) {
184 FILE* uuid_fp;
185 char temp_uuid[500];
186
3/6
✓ Branch 0 taken 1923 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1923 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1923 times.
✗ Branch 5 not taken.
1923 uuid_fp = popen(getFPPDDir("/scripts/get_uuid").c_str(), "r");
187
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1922 times.
1923 if (uuid_fp == NULL) {
188
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogWarn(VB_SETTING, "Couldn't execute get_uuid\n");
189 } else {
190
3/4
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1921 times.
✓ Branch 3 taken 1 times.
1922 if (fgets(temp_uuid, sizeof(temp_uuid), uuid_fp) != NULL) {
191 1921 int pos = strlen(temp_uuid) - 1;
192 // Strip newline
193
2/4
✓ Branch 0 taken 1921 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1921 times.
✗ Branch 3 not taken.
1921 if (pos >= 0 && '\n' == temp_uuid[pos]) {
194 1921 temp_uuid[pos] = '\0';
195 }
196
3/6
✓ Branch 0 taken 1921 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1921 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1921 times.
✗ Branch 5 not taken.
3842 settings["SystemUUID"] = std::string(temp_uuid);
197 }
198
1/2
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
1922 pclose(uuid_fp);
199 }
200 }
201
202 // Set to unknown if not found
203
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1921 times.
1923 if (!settings.isMember("SystemUUID")) {
204
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 settings["SystemUUID"] = "Unknown";
205 2 LogWarn(VB_SETTING, "Couldn't find UUID\n");
206 }
207 }
208 2211 }
209 41522 void SettingsConfig::registerSettingsListener(const std::string& id, const std::string& setting, std::function<void(const std::string&)>&& cb) {
210
1/2
✓ Branch 0 taken 41522 times.
✗ Branch 1 not taken.
41522 std::unique_lock<std::shared_mutex> lock(settingsListenersMutex);
211 41522 SettingListener listener;
212
1/2
✓ Branch 0 taken 41522 times.
✗ Branch 1 not taken.
41522 listener.id = id;
213 41522 listener.callback = std::move(cb);
214
215
3/4
✓ Branch 0 taken 41522 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 41509 times.
✓ Branch 3 taken 13 times.
41522 if (settingsListeners.find(setting) == settingsListeners.end()) {
216
1/2
✓ Branch 0 taken 41509 times.
✗ Branch 1 not taken.
41509 settingsListeners[setting] = std::list<SettingListener>();
217 }
218
2/4
✓ Branch 0 taken 41522 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 41522 times.
✗ Branch 3 not taken.
41522 settingsListeners[setting].push_back(listener);
219
1/2
✓ Branch 0 taken 41522 times.
✗ Branch 1 not taken.
41522 LogDebug(VB_SETTING, "Registered settings listener for %s setting with id %s\n", setting.c_str(), id.c_str());
220 41522 }
221 29830 void SettingsConfig::unregisterSettingsListener(const std::string& id, const std::string& setting) {
222 {
223
1/2
✓ Branch 0 taken 29830 times.
✗ Branch 1 not taken.
29830 std::unique_lock<std::shared_mutex> lock(settingsListenersMutex);
224
3/4
✓ Branch 0 taken 29830 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26165 times.
✓ Branch 3 taken 3665 times.
29830 if (settingsListeners.find(setting) != settingsListeners.end()) {
225
1/2
✓ Branch 0 taken 26165 times.
✗ Branch 1 not taken.
26165 auto& listeners = settingsListeners[setting];
226
2/2
✓ Branch 0 taken 26178 times.
✓ Branch 1 taken 26165 times.
52343 for (auto it = listeners.begin(); it != listeners.end();) {
227
2/2
✓ Branch 0 taken 26152 times.
✓ Branch 1 taken 26 times.
26178 if (it->id == id) {
228 26152 it = listeners.erase(it);
229 } else {
230 26 ++it;
231 }
232 }
233
2/2
✓ Branch 0 taken 26139 times.
✓ Branch 1 taken 26 times.
26165 if (listeners.empty()) {
234
1/2
✓ Branch 0 taken 26139 times.
✗ Branch 1 not taken.
26139 settingsListeners.erase(setting);
235 }
236 }
237 29830 }
238 // Listeners are called from a copy with no lock held, so one copied out
239 // before the erase above can still be running, or about to run. Its
240 // owner frees what it captured once this returns (an output deleted by a
241 // reload), so wait for those calls to finish. Not under
242 // settingsListenersMutex: a running listener may register or unregister.
243
1/2
✓ Branch 0 taken 29830 times.
✗ Branch 1 not taken.
29830 if (t_inSettingsListener == 0) {
244
1/2
✓ Branch 0 taken 29830 times.
✗ Branch 1 not taken.
29830 std::unique_lock<std::mutex> lock(listenerCallsLock);
245
1/2
✓ Branch 0 taken 29830 times.
✗ Branch 1 not taken.
29830 auto key = std::make_pair(setting, id);
246
1/2
✓ Branch 0 taken 29830 times.
✗ Branch 1 not taken.
59661 listenerCallFinished.wait(lock, [this, &key]() { return listenerCallsInFlight.count(key) == 0; });
247 29830 }
248 29830 }
249 57 void SettingsConfig::retireListenerCalls(const std::vector<std::pair<SettingListener, const std::string*>>& calls,
250 size_t from, size_t to) {
251
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 30 times.
57 if (from >= to) {
252 27 return;
253 }
254 {
255
1/2
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
30 std::lock_guard<std::mutex> lock(listenerCallsLock);
256
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 30 times.
60 for (size_t i = from; i < to; i++) {
257
2/4
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 auto f = listenerCallsInFlight.find(std::make_pair(*calls[i].second, calls[i].first.id));
258
3/6
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 30 times.
✗ Branch 5 not taken.
30 if (f != listenerCallsInFlight.end() && --f->second <= 0) {
259
1/2
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
30 listenerCallsInFlight.erase(f);
260 }
261 }
262 30 }
263 30 listenerCallFinished.notify_all();
264 }
265
266 1923 void SettingsConfig::LoadSettingsInfo() {
267
2/4
✓ Branch 0 taken 1923 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1923 times.
✗ Branch 3 not taken.
1923 std::string settingsJson = getFPPDDir("/www/settings.json");
268
2/4
✓ Branch 0 taken 1923 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1923 times.
✗ Branch 3 not taken.
1923 if (FileExists(settingsJson)) {
269
1/2
✓ Branch 0 taken 1923 times.
✗ Branch 1 not taken.
1923 settingsInfo = LoadJsonFromFile(settingsJson, JsonRoot::Object);
270
271
2/4
✓ Branch 0 taken 1923 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1923 times.
✗ Branch 3 not taken.
1923 Json::Value s = settingsInfo["settings"];
272
1/2
✓ Branch 0 taken 1923 times.
✗ Branch 1 not taken.
1923 Json::Value::Members memberNames = s.getMemberNames();
273
274
2/2
✓ Branch 0 taken 484596 times.
✓ Branch 1 taken 1923 times.
486519 for (int i = 0; i < memberNames.size(); i++) {
275
3/6
✓ Branch 0 taken 484596 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 484596 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 484596 times.
✗ Branch 5 not taken.
484596 std::string type = s[memberNames[i]]["type"].asString();
276
1/2
✓ Branch 0 taken 484596 times.
✗ Branch 1 not taken.
484596 std::string def = "";
277
278
4/6
✓ Branch 0 taken 484596 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 484596 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 248067 times.
✓ Branch 5 taken 236529 times.
484596 if (s[memberNames[i]].isMember("default")) {
279
3/6
✓ Branch 0 taken 248067 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 248067 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 248067 times.
✗ Branch 5 not taken.
248067 def = s[memberNames[i]]["default"].asString();
280 } else {
281
5/6
✓ Branch 0 taken 236529 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 188454 times.
✓ Branch 3 taken 48075 times.
✓ Branch 4 taken 49998 times.
✓ Branch 5 taken 186531 times.
424983 if ((type == "checkbox") ||
282
3/4
✓ Branch 0 taken 188454 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1923 times.
✓ Branch 3 taken 186531 times.
188454 (type == "number")) {
283
1/2
✓ Branch 0 taken 49998 times.
✗ Branch 1 not taken.
99996 def = std::string("0");
284 } else {
285
1/2
✓ Branch 0 taken 186531 times.
✗ Branch 1 not taken.
373062 def = std::string("");
286 }
287 }
288
289
2/4
✓ Branch 0 taken 484596 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 484596 times.
✗ Branch 3 not taken.
484596 settings[memberNames[i]] = def;
290 // The following will never be logged due to startup order
291
1/2
✓ Branch 0 taken 484596 times.
✗ Branch 1 not taken.
484596 LogExcess(VB_SETTING, "Setting default for '%s' setting to '%s'\n",
292 memberNames[i].c_str(), def.c_str());
293 484596 }
294 1923 }
295 #ifdef PLATFORM_OSX
296 char hostname[256];
297 gethostname(hostname, 256);
298 std::string hn = hostname;
299 int idx = hn.find(".");
300 if (idx != std::string::npos) {
301 hn = hn.substr(0, idx);
302 }
303 settings["HostName"] = hn;
304 #endif
305 1923 }
306 24128 void SettingsConfig::setSetting(const std::string& key, const std::string& value, bool persist) {
307 24128 std::unique_lock<std::mutex> persistLock(persistMutex, std::defer_lock);
308
2/2
✓ Branch 0 taken 304 times.
✓ Branch 1 taken 23824 times.
24128 if (persist) {
309 // The file is line based and nothing escapes a value, so a line break
310 // would start a new setting. Same rule as WriteSettingToFile().
311
2/4
✓ Branch 0 taken 304 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 304 times.
608 if (value.find_first_of(std::string("\r\n\0", 3)) != std::string::npos) {
312 ✗ LogWarn(VB_SETTING, "Not saving setting %s: value contains a line break or NUL\n", key.c_str());
313 ✗ return;
314 }
315
1/2
✓ Branch 0 taken 304 times.
✗ Branch 1 not taken.
304 persistLock.lock();
316 }
317
1/2
✓ Branch 0 taken 24128 times.
✗ Branch 1 not taken.
24128 std::unique_lock<std::shared_mutex> lock(settingsMutex);
318
3/4
✓ Branch 0 taken 24128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4083 times.
✓ Branch 3 taken 20045 times.
24128 if (setSettingLocked(key, value, false)) {
319
1/2
✓ Branch 0 taken 4083 times.
✗ Branch 1 not taken.
4083 lock.unlock();
320
2/2
✓ Branch 0 taken 259 times.
✓ Branch 1 taken 3824 times.
4083 if (persist) {
321
1/2
✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
259 persistSetting(key, value);
322
1/2
✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
259 persistLock.unlock();
323 }
324
4/8
✓ Branch 0 taken 4083 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4083 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4083 times.
✓ Branch 5 taken 4083 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
12249 notifyListeners({ { key, value } });
325 }
326
2/8
✓ Branch 0 taken 4083 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24128 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
28211 }
327 // Rewrites one key in the settings file under a single LOCK_EX held across
328 // the read and the write, so a concurrent writer (the PHP side holds the
329 // same lock) can't slip in between and have its change lost. Called without
330 // settingsMutex: LoadSettings() takes settingsMutex and then the file lock.
331 259 bool SettingsConfig::persistSetting(const std::string& key, const std::string& value) {
332
3/6
✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 259 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 259 times.
✗ Branch 5 not taken.
259 int fd = open(FPP_FILE_SETTINGS.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0666);
333
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 259 times.
259 if (fd == -1) {
334 ✗ LogWarn(VB_SETTING, "Could not open %s to save %s: %s\n", FPP_FILE_SETTINGS.c_str(), key.c_str(), strerror(errno));
335 ✗ return false;
336 }
337
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 259 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 259 times.
259 while (flock(fd, LOCK_EX) == -1 && errno == EINTR) {
338 }
339 259 std::string contents;
340 char buf[4096];
341 ssize_t n;
342
3/4
✓ Branch 0 taken 518 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 259 times.
✓ Branch 3 taken 259 times.
518 while ((n = read(fd, buf, sizeof(buf))) != 0) {
343
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 259 times.
259 if (n < 0) {
344 ✗ if (errno == EINTR) {
345 ✗ continue;
346 }
347 ✗ LogWarn(VB_SETTING, "Could not read %s to save %s: %s\n", FPP_FILE_SETTINGS.c_str(), key.c_str(), strerror(errno));
348 ✗ flock(fd, LOCK_UN);
349 ✗ close(fd);
350 ✗ return false;
351 }
352
1/2
✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
259 contents.append(buf, n);
353 }
354
355 // Match the key the way LoadSettings() reads it (the text before the
356 // first '=', trimmed) rather than searching for "key = " anywhere, which
357 // also hit a key that ends with this one or a value containing that text.
358 // The first line for the key is replaced and any later duplicates are
359 // dropped, since the last one would win on the next load.
360
3/6
✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 259 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 259 times.
✗ Branch 5 not taken.
259 const std::string newLine = key + " = \"" + value + "\"\n";
361 259 std::string out;
362
1/2
✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
259 out.reserve(contents.size() + newLine.size());
363 259 bool found = false;
364 259 size_t pos = 0;
365
2/2
✓ Branch 0 taken 3434 times.
✓ Branch 1 taken 259 times.
3693 while (pos < contents.size()) {
366 3434 size_t end = contents.find('\n', pos);
367
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3434 times.
3434 end = (end == std::string::npos) ? contents.size() : end + 1;
368
1/2
✓ Branch 0 taken 3434 times.
✗ Branch 1 not taken.
3434 std::string line = contents.substr(pos, end - pos);
369 3434 pos = end;
370 3434 size_t eq = line.find('=');
371
1/2
✓ Branch 0 taken 3434 times.
✗ Branch 1 not taken.
3434 if (eq != std::string::npos) {
372
2/4
✓ Branch 0 taken 3434 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3434 times.
✗ Branch 3 not taken.
3434 char* k = trimwhitespace(line.substr(0, eq).c_str());
373
1/2
✓ Branch 0 taken 3434 times.
✗ Branch 1 not taken.
3434 bool match = (key == k);
374 3434 free(k);
375
2/2
✓ Branch 0 taken 147 times.
✓ Branch 1 taken 3287 times.
3434 if (match) {
376
1/2
✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
147 if (!found) {
377
1/2
✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
147 out += newLine;
378 147 found = true;
379 }
380 147 continue;
381 }
382 }
383
1/2
✓ Branch 0 taken 3287 times.
✗ Branch 1 not taken.
3287 out += line;
384
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3287 times.
3287 if (out.back() != '\n') {
385 ✗ out += '\n';
386 }
387
2/2
✓ Branch 0 taken 3287 times.
✓ Branch 1 taken 147 times.
3434 }
388
2/2
✓ Branch 0 taken 112 times.
✓ Branch 1 taken 147 times.
259 if (!found) {
389
1/2
✓ Branch 0 taken 112 times.
✗ Branch 1 not taken.
112 out += newLine;
390 }
391
392 259 bool ok = ftruncate(fd, 0) == 0;
393 259 const char* p = out.data();
394 259 size_t left = out.size();
395 259 off_t off = 0;
396
3/4
✓ Branch 0 taken 518 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 259 times.
✓ Branch 3 taken 259 times.
518 while (ok && left > 0) {
397
1/2
✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
259 ssize_t w = pwrite(fd, p, left, off);
398
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 259 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
259 if (w < 0 && errno == EINTR) {
399 ✗ continue;
400 }
401
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 259 times.
259 if (w <= 0) {
402 ✗ ok = false;
403 ✗ break;
404 }
405 259 p += w;
406 259 left -= w;
407 259 off += w;
408 }
409
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 259 times.
259 if (!ok) {
410 ✗ LogWarn(VB_SETTING, "Could not write %s to save %s: %s\n", FPP_FILE_SETTINGS.c_str(), key.c_str(), strerror(errno));
411 }
412 259 flock(fd, LOCK_UN);
413
1/2
✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
259 close(fd);
414
3/6
✓ Branch 0 taken 259 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 259 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 259 times.
✗ Branch 5 not taken.
259 SetFilePerms(FPP_FILE_SETTINGS);
415 259 return ok;
416 259 }
417 // Listeners run with no settings lock held. They take their own locks and
418 // read settings back (MultiSync holds its lock and calls getSetting, which
419 // takes settingsMutex), and settingsMutex is held while settingsListenersMutex
420 // is taken, so calling them under either lock closes a cycle. It also lets a
421 // listener register or unregister listeners. Each call is counted until it
422 // returns, for unregisterSettingsListener() to wait on.
423 6283 void SettingsConfig::notifyListeners(const std::map<std::string, std::string>& changed) {
424 6283 std::vector<std::pair<SettingListener, const std::string*>> toCall;
425 {
426
1/2
✓ Branch 0 taken 6283 times.
✗ Branch 1 not taken.
6283 std::shared_lock<std::shared_mutex> listenersLock(settingsListenersMutex);
427
1/2
✓ Branch 0 taken 6283 times.
✗ Branch 1 not taken.
6283 std::lock_guard<std::mutex> callsLock(listenerCallsLock);
428
2/2
✓ Branch 0 taken 8006 times.
✓ Branch 1 taken 6283 times.
14289 for (const auto& change : changed) {
429
1/2
✓ Branch 0 taken 8006 times.
✗ Branch 1 not taken.
8006 auto it = settingsListeners.find(change.first);
430
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 7977 times.
8006 if (it != settingsListeners.end()) {
431
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 29 times.
59 for (const auto& listener : it->second) {
432
1/2
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
30 toCall.emplace_back(listener, &change.first);
433
2/4
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 ++listenerCallsInFlight[std::make_pair(change.first, listener.id)];
434 }
435 }
436 }
437 6283 }
438
2/2
✓ Branch 0 taken 6256 times.
✓ Branch 1 taken 27 times.
6283 if (toCall.empty()) {
439 6256 return;
440 }
441 // Each call is retired as it returns; a throw retires the rest.
442 27 size_t next = 0;
443 struct InFlight {
444 SettingsConfig* self;
445 const std::vector<std::pair<SettingListener, const std::string*>>& calls;
446 const size_t& next;
447 27 ~InFlight() {
448 27 --t_inSettingsListener;
449 27 self->retireListenerCalls(calls, next, calls.size());
450 27 }
451 };
452 27 ++t_inSettingsListener;
453 27 InFlight inFlight{ this, toCall, next };
454
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 27 times.
57 while (next < toCall.size()) {
455 30 const auto& [listener, key] = toCall[next];
456
1/2
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
30 const std::string& value = changed.at(*key);
457
1/2
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
30 LogDebug(VB_SETTING, "Notifying listener %s for setting %s with value '%s'\n",
458 listener.id.c_str(), key->c_str(), value.c_str());
459
1/2
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
30 listener.callback(value);
460
1/2
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
30 retireListenerCalls(toCall, next, next + 1);
461 30 ++next;
462 }
463
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 6256 times.
6283 }
464 32059 bool SettingsConfig::setSettingLocked(const std::string& key, const std::string& value, bool logInfo) {
465
2/4
✓ Branch 0 taken 32059 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32059 times.
✗ Branch 3 not taken.
32059 std::string val = settings[key].asString();
466
2/2
✓ Branch 0 taken 8066 times.
✓ Branch 1 taken 23993 times.
32059 if (val != value) {
467
2/2
✓ Branch 0 taken 66 times.
✓ Branch 1 taken 8000 times.
8066 if (logInfo) {
468
1/2
✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
66 std::shared_lock<std::shared_mutex> listenersLock(settingsListenersMutex);
469
3/4
✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✓ Branch 3 taken 3 times.
66 if (settingsListeners.find(key) != settingsListeners.end()) {
470
1/2
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
63 LogInfo(VB_SETTING, "Setting %s changed from %s to %s\n", key.c_str(), val.c_str(), value.c_str());
471 } else {
472
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 LogDebug(VB_SETTING, "Setting %s: from %s to %s\n", key.c_str(), val.c_str(), value.c_str());
473 }
474 66 } else {
475
1/2
✓ Branch 0 taken 8000 times.
✗ Branch 1 not taken.
8000 LogDebug(VB_SETTING, "Setting %s: from %s to %s\n", key.c_str(), val.c_str(), value.c_str());
476 }
477
2/4
✓ Branch 0 taken 8066 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8066 times.
✗ Branch 3 not taken.
8066 settings[key] = value;
478
3/4
✓ Branch 0 taken 8066 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 41 times.
✓ Branch 3 taken 8025 times.
8066 if (key == "fppMode") {
479
6/8
✓ Branch 0 taken 41 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 38 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 38 times.
41 if (value == "player" || value.empty()) {
480 3 fppMode = PLAYER_MODE;
481
2/4
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 38 times.
38 } else if (value == "bridge") {
482 // legacy, remap to PLAYER
483 ✗ fppMode = PLAYER_MODE;
484
2/4
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 38 times.
38 } else if (value == "master") {
485 // legacy, remap to player, but turn on MultiSync setting
486 ✗ fppMode = PLAYER_MODE;
487 ✗ settings[key] = "player";
488 ✗ settings["MultiSyncEnabled"] = "1";
489
2/4
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
✗ Branch 3 not taken.
38 } else if (value == "remote") {
490 38 fppMode = REMOTE_MODE;
491 } else {
492 // default to playmode if string is invalid
493 ✗ fppMode = PLAYER_MODE;
494 ✗ settings[key] = "player";
495 }
496
4/6
✓ Branch 0 taken 8025 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8025 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 306 times.
✓ Branch 5 taken 7719 times.
16050 } else if (startsWith(key, "LogLevel_")) {
497 // Starts with LogLevel_
498
2/4
✓ Branch 0 taken 306 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 306 times.
✗ Branch 3 not taken.
306 if (value != "") {
499
1/2
✓ Branch 0 taken 306 times.
✗ Branch 1 not taken.
306 FPPLogger::INSTANCE.SetLevel(key.c_str(), value.c_str());
500 } else {
501 ✗ FPPLogger::INSTANCE.SetLevel(key.c_str(), "warn");
502 }
503 }
504 8066 return true;
505 }
506 23993 return false;
507 32059 }
508 2386311 std::string SettingsConfig::getSetting(const std::string& setting, const std::string& defaultVal) {
509
1/2
✓ Branch 0 taken 2386311 times.
✗ Branch 1 not taken.
2386311 std::shared_lock<std::shared_mutex> lock(settingsMutex);
510
1/2
✓ Branch 0 taken 2386311 times.
✗ Branch 1 not taken.
2386311 std::string result = defaultVal;
511
3/4
✓ Branch 0 taken 2386311 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2368362 times.
✓ Branch 3 taken 17949 times.
2386311 if (settings.isMember(setting)) {
512
2/4
✓ Branch 0 taken 2368362 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2368362 times.
✗ Branch 3 not taken.
2368362 result = settings[setting].asString();
513 }
514
1/2
✓ Branch 0 taken 2386311 times.
✗ Branch 1 not taken.
2386311 LogExcess(VB_SETTING, "getSetting(%s) returning '%s'\n", setting.c_str(), result.c_str());
515 2386311 return result;
516 2386311 }
517 340733 int SettingsConfig::getSettingInt(const std::string& setting, int defaultVal) {
518
1/2
✓ Branch 0 taken 340733 times.
✗ Branch 1 not taken.
340733 std::shared_lock<std::shared_mutex> lock(settingsMutex);
519 340733 int result = defaultVal;
520
3/4
✓ Branch 0 taken 340733 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 311532 times.
✓ Branch 3 taken 29201 times.
340733 if (settings.isMember(setting)) {
521
2/4
✓ Branch 0 taken 311532 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 311532 times.
✗ Branch 3 not taken.
311532 result = atoi(settings[setting].asString().c_str());
522 }
523
1/2
✓ Branch 0 taken 340733 times.
✗ Branch 1 not taken.
340733 LogExcess(VB_SETTING, "getSettingInt(%s) returning %d\n", setting.c_str(), result);
524 340733 return result;
525 340733 }
526
527 // Returns a string that's the white-space trimmed version
528 // of the input string. Also trim double quotes now that the
529 // settings file will have double quotes in it.
530 11374 char* trimwhitespace(const char* str, int quotesAlso) {
531 const char* end;
532 size_t out_size;
533 char* out;
534
535 // Trim leading space
536
3/6
✗ Branch 0 not taken.
✓ Branch 1 taken 11374 times.
✓ Branch 2 taken 11374 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 11374 times.
11374 while ((isspace(*str)) || (quotesAlso && (*str == '"')))
537 ✗ str++;
538
539
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 11373 times.
11374 if (*str == 0) // All spaces?
540 1 return strdup("");
541
542 // Trim trailing space
543 11373 end = str + strlen(str) - 1;
544
5/8
✓ Branch 0 taken 22746 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11373 times.
✓ Branch 3 taken 11373 times.
✓ Branch 4 taken 11373 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 11373 times.
22746 while (end > str && ((isspace(*end)) || (quotesAlso && (*end == '"'))))
545 11373 end--;
546 11373 end++;
547
548 // Set output size to minimum of trimmed string length
549 11373 out_size = end - str;
550
551 // All whitespace
552
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11373 times.
11373 if (out_size == 0)
553 ✗ return strdup("");
554
555 11373 out = (char*)malloc(out_size + 1);
556 // If we didn't allocate anything, die!
557
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11373 times.
11373 if (!out) {
558 ✗ fprintf(stderr, "Failed to allocate memory for string trimming\n");
559 ✗ exit(EXIT_FAILURE);
560 }
561
562 // Copy trimmed string and add null terminator
563 11373 memcpy(out, str, out_size);
564 11373 out[out_size] = 0;
565
566 11373 return out;
567 }
568
569 // Caller must free the char array we give them
570 333252 char* modeToString(int mode) {
571
2/2
✓ Branch 0 taken 313797 times.
✓ Branch 1 taken 19455 times.
333252 if (mode == PLAYER_MODE)
572 313797 return strdup("player");
573
2/2
✓ Branch 0 taken 19317 times.
✓ Branch 1 taken 138 times.
19455 else if (mode == REMOTE_MODE)
574 19317 return strdup("remote");
575
2/2
✓ Branch 0 taken 112 times.
✓ Branch 1 taken 26 times.
138 else if (mode == BRIDGE_MODE)
576 112 return strdup("bridge");
577
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 else if (mode == MASTER_MODE)
578 ✗ return strdup("master");
579
580 26 return strdup("unknown");
581 }
582
583 2211 int SettingsConfig::LoadSettings(const std::string& base, bool logChanges) {
584
1/2
✓ Branch 0 taken 2211 times.
✗ Branch 1 not taken.
2211 std::unique_lock<std::shared_mutex> lock(settingsMutex);
585
1/2
✓ Branch 0 taken 2211 times.
✗ Branch 1 not taken.
2211 Init();
586
587
5/8
✓ Branch 0 taken 2211 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2211 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2211 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 11 times.
✓ Branch 7 taken 2200 times.
4422 if (!FileExists(FPP_FILE_SETTINGS)) {
588
3/6
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
11 LogWarn(VB_SETTING,
589 "Attempted to load settings file %s which does not exist!\n", FPP_FILE_SETTINGS.c_str());
590 11 return -1;
591 }
592
593
3/6
✓ Branch 0 taken 2200 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2200 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2200 times.
✗ Branch 5 not taken.
2200 FILE* file = fopen(FPP_FILE_SETTINGS.c_str(), "r");
594 2200 std::map<std::string, std::string> changed;
595
1/2
✓ Branch 0 taken 2200 times.
✗ Branch 1 not taken.
2200 if (file != NULL) {
596 2200 flock(fileno(file), LOCK_EX); // Lock the file for exclusive access
597 2200 char* line = (char*)calloc(256, 1);
598 2200 size_t len = 256;
599 ssize_t read;
600 2200 int sIndex = 0;
601
602
3/4
✓ Branch 0 taken 10132 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7932 times.
✓ Branch 3 taken 2200 times.
10132 while ((read = getline(&line, &len, file)) != -1) {
603
3/6
✓ Branch 0 taken 7932 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7932 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 7932 times.
7932 if ((!line) || (!read) || (read == 1))
604 1 continue;
605
606 // Split on the first '=' only and drop exactly one pair of
607 // surrounding quotes, the way the PHP side and getRawSetting()
608 // read this file. Splitting on every '=' cut base64 passwords
609 // and JSON values short, and trimming every quote ate quotes
610 // that belong to the value.
611
1/2
✓ Branch 0 taken 7932 times.
✗ Branch 1 not taken.
8279 std::string l(line, read);
612 7932 size_t eq = l.find('=');
613
2/4
✓ Branch 0 taken 7932 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7932 times.
✗ Branch 3 not taken.
7932 char* key = trimwhitespace(l.substr(0, eq).c_str());
614
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7931 times.
7932 if (!strlen(key)) {
615 1 free(key);
616 1 continue;
617 }
618
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7931 times.
7931 if (eq == std::string::npos) {
619 ✗ fprintf(stderr, "Error tokenizing value for %s setting\n", key);
620 ✗ free(key);
621 ✗ continue;
622 }
623
1/2
✓ Branch 0 taken 7931 times.
✗ Branch 1 not taken.
7931 std::string value = l.substr(eq + 1);
624
1/2
✓ Branch 0 taken 7931 times.
✗ Branch 1 not taken.
7931 TrimWhiteSpace(value);
625
4/8
✓ Branch 0 taken 7931 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7931 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7931 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7931 times.
✗ Branch 7 not taken.
7931 if (value.size() >= 2 && value.front() == '"' && value.back() == '"') {
626
1/2
✓ Branch 0 taken 7931 times.
✗ Branch 1 not taken.
7931 value = value.substr(1, value.size() - 2);
627 }
628
4/6
✓ Branch 0 taken 7931 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7931 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3983 times.
✓ Branch 5 taken 3948 times.
23446 if (setSettingLocked(key, value, logChanges)) {
629
3/6
✓ Branch 0 taken 3983 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3983 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3983 times.
✗ Branch 5 not taken.
7966 changed[key] = value; // Store the changed setting
630 }
631 7931 free(key);
632
2/2
✓ Branch 0 taken 7931 times.
✓ Branch 1 taken 1 times.
7932 }
633
634
1/2
✓ Branch 0 taken 2200 times.
✗ Branch 1 not taken.
2200 if (line) {
635 2200 free(line);
636 }
637 2200 flock(fileno(file), LOCK_UN); // Lock the file for exclusive access
638
1/2
✓ Branch 0 taken 2200 times.
✗ Branch 1 not taken.
2200 fclose(file);
639 } else {
640 ✗ LogWarn(VB_SETTING, "Warning: couldn't open settings file: '%s'!\n", FPP_FILE_SETTINGS.c_str());
641 ✗ return -1;
642 }
643
1/2
✓ Branch 0 taken 2200 times.
✗ Branch 1 not taken.
2200 lock.unlock();
644
1/2
✓ Branch 0 taken 2200 times.
✗ Branch 1 not taken.
2200 notifyListeners(changed);
645 2200 return 0;
646 2211 }
647
648 2211 int LoadSettings(const char* base, bool logChanges) {
649
2/4
✓ Branch 0 taken 2211 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2211 times.
✗ Branch 3 not taken.
6461 return settings.LoadSettings(base, logChanges);
650 }
651
652 1915 int setSetting(const std::string& key, const int value) {
653
1/2
✓ Branch 0 taken 1915 times.
✗ Branch 1 not taken.
1915 return setSetting(key, std::to_string(value));
654 }
655 24128 int setSetting(const std::string& key, const std::string& value, bool persist) {
656 24128 settings.setSetting(key, value, persist);
657 24128 return 1;
658 }
659 1915 int SetSetting(const std::string& key, const int value) {
660 1915 return setSetting(key, value);
661 }
662 21909 int SetSetting(const std::string& key, const std::string& value) {
663 21909 return setSetting(key, value);
664 }
665 2365272 std::string getSetting(const char* setting, const char* defaultVal) {
666
2/4
✓ Branch 0 taken 2365272 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2365272 times.
2365272 if ((!setting) || (setting[0] == 0x00)) {
667 ✗ LogErr(VB_SETTING, "getSetting() called with NULL or empty value\n");
668 ✗ std::string result = defaultVal;
669 ✗ return result;
670 ✗ }
671
3/6
✓ Branch 0 taken 2365272 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2365272 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2365272 times.
✗ Branch 5 not taken.
9461088 return settings.getSetting(setting, defaultVal);
672 }
673
674 340733 int getSettingInt(const char* setting, int defaultVal) {
675
2/4
✓ Branch 0 taken 340733 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 340733 times.
340733 if ((!setting) || (setting[0] == 0x00)) {
676 ✗ LogErr(VB_SETTING, "getSetting() called with NULL or empty value\n");
677 ✗ return defaultVal;
678 }
679
2/4
✓ Branch 0 taken 340733 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 340733 times.
✗ Branch 3 not taken.
681466 return settings.getSettingInt(setting, defaultVal);
680 }
681
682 21039 bool isPipeWireBackend() {
683 #ifdef PLATFORM_OSX
684 // There is no PipeWire on macOS, whatever MediaBackend says (its default is
685 // a PipeWire mode). Media plays through GStreamer's non-PipeWire path there -
686 // autoaudiosink, i.e. osxaudiosink - so every PipeWire-only feature has to
687 // see "no" from here rather than build pipewiresink/pipewiresrc pipelines
688 // that cannot be created.
689 return false;
690 #endif
691 // Default to the settings.json default rather than "". ALSA is retired --
692 // the UI offers only the two PipeWire modes and FPPINIT migrates a stored
693 // "alsa" away -- so a box that simply has no MediaBackend key must not fall
694 // through to the retired path.
695
3/6
✓ Branch 0 taken 21039 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 21039 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 21039 times.
✗ Branch 5 not taken.
63117 std::string mb = settings.getSetting("MediaBackend", "pipewire-simple");
696 // Lowercase compare without pulling in toLowerCopy here
697
2/2
✓ Branch 0 taken 311438 times.
✓ Branch 1 taken 21039 times.
332477 for (char& c : mb) {
698
3/4
✓ Branch 0 taken 290964 times.
✓ Branch 1 taken 20474 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 290964 times.
311438 if (c >= 'A' && c <= 'Z')
699 ✗ c = c + ('a' - 'A');
700 }
701
6/8
✓ Branch 0 taken 21039 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20522 times.
✓ Branch 3 taken 517 times.
✓ Branch 4 taken 20522 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 20474 times.
✓ Branch 7 taken 48 times.
42078 return (mb == "pipewire" || mb == "pipewire-simple");
702 21039 }
703
704 1951432 FPPMode getFPPmode(void) {
705 1951432 return settings.fppMode;
706 }
707
708 24 const std::string getFPPmodeStr(FPPMode mode) {
709
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 if (mode == UNKNOWN_MODE)
710 ✗ mode = settings.fppMode;
711
712 24 std::string result;
713
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 char* modePtr = modeToString((int)mode);
714
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 if (modePtr) {
715
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 result = modePtr;
716 24 free(modePtr);
717 } else {
718 ✗ result = "unknown";
719 }
720
721 24 return result;
722 ✗ }
723
724 41522 void registerSettingsListener(const std::string& id, const std::string& key, std::function<void(const std::string&)>&& cb) {
725 41522 settings.registerSettingsListener(id, key, std::move(cb));
726 41522 }
727 29830 void unregisterSettingsListener(const std::string& id, const std::string& key) {
728 29830 settings.unregisterSettingsListener(id, key);
729 29830 }
730