Skip to content

Commit 7781092

Browse files
dkulpclaude
andcommitted
fix(commands): do not throw on a commandPresets.json that is not an object
jsoncpp throws Json::LogicError from Json::Value::isMember() on a value that is neither an object nor null, and nothing in fppd catches it, so a commandPresets.json holding an array (or a scalar) took the daemon down with SIGABRT - at startup inside LoadPresets(), and again on every FileMonitor reload of that file, i.e. a boot loop. Master is covered by LoadJsonFromFile(..., JsonRoot::Object) from the config-root-shape sweep; that sweep does not exist on this branch, so guard the reads instead. All three readers of this file get the same isObject() test, not just the reported one - the two in the /api/commandPresets handler read the same file and would abort the daemon the moment the presets page was opened. Behaviour for a well-formed file is unchanged: isObject() is true for it, and a missing file still yields a null root that isMember() already returned false for. Verified against the real libjsoncpp by running this exact expression over each root shape a config file can hold. The control dies first: an array root gives "in Json::Value::find(begin, end): requires objectValue or nullValue" as an uncaught throw, which is this bucket's stack and signal exactly. With the guard, every shape - object, empty array, array of objects, null and a bare scalar - returns cleanly and the valid object still resolves. The translation unit compiles on this branch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent ca97190 commit 7781092

1 file changed

Lines changed: 9 additions & 3 deletions

File tree

src/commands/Commands.cpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ HTTP_RESPONSE_CONST std::shared_ptr<httpserver::http_response> CommandManager::r
360360
allCommands = LoadJsonFromFile(commandsFile);
361361
}
362362
if (plen > 1) {
363-
if (allCommands.isMember("commands")) {
363+
if (allCommands.isObject() && allCommands.isMember("commands")) {
364364
std::string p2 = req.get_path_pieces()[1];
365365
for (int x = 0; x < allCommands["commands"].size(); x++) {
366366
if (allCommands["commands"][x]["name"].asString() == p2) {
@@ -372,7 +372,7 @@ HTTP_RESPONSE_CONST std::shared_ptr<httpserver::http_response> CommandManager::r
372372
} else {
373373
if (std::string(req.get_arg("names")) == "true") {
374374
Json::Value names;
375-
if (allCommands.isMember("commands")) {
375+
if (allCommands.isObject() && allCommands.isMember("commands")) {
376376
for (int x = 0; x < allCommands["commands"].size(); x++) {
377377
names.append(allCommands["commands"][x]["name"].asString());
378378
}
@@ -633,7 +633,13 @@ void CommandManager::LoadPresets() {
633633
lastPresetTimeStamp = FileTimestamp(commandsFile);
634634
}
635635

636-
if (allCommands.isMember("commands")) {
636+
// isObject() first: jsoncpp throws Json::LogicError from isMember() on a
637+
// value that is not an object or null, and nothing here catches it, so a
638+
// commandPresets.json holding an array (or a scalar) aborted fppd at
639+
// startup and again on every FileMonitor reload of the file. Master gets
640+
// this from LoadJsonFromFile(..., JsonRoot::Object), which does not exist
641+
// on this branch.
642+
if (allCommands.isObject() && allCommands.isMember("commands")) {
637643
for (int i = 0; i < allCommands["commands"].size(); i++) {
638644
if (presets.isMember(allCommands["commands"][i]["name"].asString())) {
639645
presets[allCommands["commands"][i]["name"].asString()].append(allCommands["commands"][i]);

0 commit comments

Comments
 (0)