GCC Code Coverage Report


Directory: ./
File: non-gpl/CapeUtils/CapeUtils.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 1205 1505 80.1%
Functions: 84 96 87.5%
Branches: 1595 3414 46.7%

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 CC-BY-ND as described in the
10 * included LICENSE.CC-BY-ND file. This file may be modified for
11 * personal use, but modified copies MAY NOT be redistributed in any form.
12 */
13
14 #include "CapeUtils.h"
15
16 #include <array>
17 #include <cstdio>
18 #include <iostream>
19 #include <map>
20 #include <memory>
21 #include <set>
22 #include <sstream>
23 #include <stdexcept>
24 #include <string>
25 #include <thread>
26
27 #include <sys/reboot.h>
28 #include <sys/stat.h>
29 #include <sys/types.h>
30 #include <vector>
31 #include <sys/wait.h>
32 #include <fcntl.h>
33 #include <fstream>
34 #include <pwd.h>
35 #include <unistd.h>
36
37 #include <dirent.h>
38 #include <libgen.h>
39 #include <string.h>
40
41 #include "fpp-json.h"
42
43 #include <filesystem>
44
45 #include <openssl/decoder.h>
46 #include <openssl/pem.h>
47 #include <openssl/rsa.h>
48 #include <openssl/sha.h>
49
50 #if defined(PLATFORM_BBB) || defined(PLATFORM_BB64)
51 #define I2C_DEV 2
52 #elif defined(PLATFORM_PI)
53 #define I2C_DEV 1
54 #else
55 #define I2C_DEV 0
56 #endif
57
58 // Thread-safe lookup of the "fpp" user's uid/gid. getpwnam() returns a pointer
59 // into a shared static buffer and is not reentrant, so use getpwnam_r().
60 603 static bool getFppUserIds(uid_t& uid, gid_t& gid) {
61 char buf[16384];
62 struct passwd pwd;
63 603 struct passwd* result = nullptr;
64
6/8
✓ Branch 0 taken 603 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 603 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 170 times.
✓ Branch 5 taken 433 times.
✓ Branch 6 taken 170 times.
✓ Branch 7 taken 433 times.
603 if (getpwnam_r("fpp", &pwd, buf, sizeof(buf), &result) != 0 || result == nullptr) {
65 170 return false;
66 }
67 433 uid = result->pw_uid;
68 433 gid = result->pw_gid;
69 433 return true;
70 }
71
72 unsigned char fp_pub_pem[] = {
73 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50,
74 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d,
75 0x2d, 0x2d, 0x0a, 0x4d, 0x46, 0x59, 0x77, 0x45, 0x41, 0x59, 0x48, 0x4b,
76 0x6f, 0x5a, 0x49, 0x7a, 0x6a, 0x30, 0x43, 0x41, 0x51, 0x59, 0x46, 0x4b,
77 0x34, 0x45, 0x45, 0x41, 0x41, 0x6f, 0x44, 0x51, 0x67, 0x41, 0x45, 0x64,
78 0x31, 0x79, 0x76, 0x42, 0x4a, 0x77, 0x70, 0x37, 0x5a, 0x62, 0x33, 0x51,
79 0x33, 0x49, 0x33, 0x79, 0x58, 0x51, 0x78, 0x33, 0x61, 0x42, 0x51, 0x4a,
80 0x6d, 0x6e, 0x37, 0x70, 0x77, 0x64, 0x57, 0x0a, 0x78, 0x37, 0x41, 0x41,
81 0x2f, 0x62, 0x45, 0x32, 0x6a, 0x78, 0x50, 0x53, 0x7a, 0x6e, 0x78, 0x36,
82 0x2f, 0x41, 0x58, 0x50, 0x61, 0x5a, 0x65, 0x75, 0x5a, 0x61, 0x37, 0x55,
83 0x75, 0x54, 0x33, 0x78, 0x34, 0x39, 0x53, 0x42, 0x70, 0x5a, 0x2b, 0x5a,
84 0x56, 0x4d, 0x45, 0x58, 0x63, 0x63, 0x54, 0x6f, 0x2f, 0x55, 0x7a, 0x44,
85 0x62, 0x41, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e,
86 0x44, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59,
87 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a
88 };
89 unsigned int fp_pub_pem_len = 174;
90
91 uint8_t dk_pub_pem[] = {
92 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50,
93 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d,
94 0x2d, 0x2d, 0x0a, 0x4d, 0x46, 0x59, 0x77, 0x45, 0x41, 0x59, 0x48, 0x4b,
95 0x6f, 0x5a, 0x49, 0x7a, 0x6a, 0x30, 0x43, 0x41, 0x51, 0x59, 0x46, 0x4b,
96 0x34, 0x45, 0x45, 0x41, 0x41, 0x6f, 0x44, 0x51, 0x67, 0x41, 0x45, 0x4e,
97 0x32, 0x76, 0x35, 0x67, 0x69, 0x6c, 0x78, 0x38, 0x55, 0x70, 0x70, 0x4f,
98 0x34, 0x6a, 0x63, 0x54, 0x38, 0x52, 0x53, 0x7a, 0x4c, 0x62, 0x49, 0x4d,
99 0x78, 0x74, 0x76, 0x32, 0x73, 0x72, 0x30, 0x0a, 0x59, 0x73, 0x4f, 0x6c,
100 0x56, 0x72, 0x68, 0x49, 0x5a, 0x5a, 0x4b, 0x62, 0x42, 0x6d, 0x39, 0x39,
101 0x6f, 0x50, 0x4f, 0x71, 0x6a, 0x57, 0x4e, 0x55, 0x6f, 0x45, 0x30, 0x53,
102 0x4c, 0x72, 0x61, 0x58, 0x49, 0x6d, 0x4b, 0x72, 0x75, 0x41, 0x74, 0x54,
103 0x53, 0x52, 0x50, 0x31, 0x42, 0x42, 0x30, 0x42, 0x52, 0x6a, 0x38, 0x45,
104 0x41, 0x77, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e,
105 0x44, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59,
106 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a
107 };
108 unsigned int dk_pub_pem_len = 174;
109 uint8_t ww_pub_pem[] = {
110 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50,
111 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d,
112 0x2d, 0x2d, 0x0a, 0x4d, 0x46, 0x59, 0x77, 0x45, 0x41, 0x59, 0x48, 0x4b,
113 0x6f, 0x5a, 0x49, 0x7a, 0x6a, 0x30, 0x43, 0x41, 0x51, 0x59, 0x46, 0x4b,
114 0x34, 0x45, 0x45, 0x41, 0x41, 0x6f, 0x44, 0x51, 0x67, 0x41, 0x45, 0x65,
115 0x75, 0x76, 0x6b, 0x42, 0x4b, 0x51, 0x32, 0x72, 0x72, 0x4c, 0x53, 0x6a,
116 0x62, 0x5a, 0x68, 0x51, 0x77, 0x63, 0x62, 0x66, 0x70, 0x65, 0x4b, 0x5a,
117 0x4c, 0x63, 0x52, 0x77, 0x79, 0x77, 0x4e, 0x0a, 0x6a, 0x2b, 0x38, 0x6f,
118 0x44, 0x34, 0x62, 0x4f, 0x42, 0x64, 0x73, 0x4f, 0x6c, 0x63, 0x44, 0x59,
119 0x47, 0x72, 0x47, 0x64, 0x76, 0x72, 0x36, 0x72, 0x69, 0x4b, 0x78, 0x7a,
120 0x69, 0x46, 0x35, 0x66, 0x57, 0x57, 0x4f, 0x5a, 0x5a, 0x51, 0x4a, 0x74,
121 0x50, 0x75, 0x46, 0x6d, 0x54, 0x51, 0x63, 0x70, 0x62, 0x73, 0x58, 0x34,
122 0x45, 0x51, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e,
123 0x44, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59,
124 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a
125 };
126 unsigned int ww_pub_pem_len = 174;
127 uint8_t jb_pub_pem[] = {
128 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50,
129 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d,
130 0x2d, 0x2d, 0x0a, 0x4d, 0x46, 0x59, 0x77, 0x45, 0x41, 0x59, 0x48, 0x4b,
131 0x6f, 0x5a, 0x49, 0x7a, 0x6a, 0x30, 0x43, 0x41, 0x51, 0x59, 0x46, 0x4b,
132 0x34, 0x45, 0x45, 0x41, 0x41, 0x6f, 0x44, 0x51, 0x67, 0x41, 0x45, 0x66,
133 0x58, 0x33, 0x63, 0x55, 0x44, 0x65, 0x38, 0x64, 0x30, 0x73, 0x35, 0x5a,
134 0x65, 0x4f, 0x65, 0x44, 0x56, 0x72, 0x77, 0x6b, 0x74, 0x59, 0x6f, 0x6d,
135 0x77, 0x76, 0x35, 0x6e, 0x4e, 0x35, 0x49, 0x0a, 0x39, 0x53, 0x43, 0x43,
136 0x65, 0x62, 0x6d, 0x48, 0x47, 0x6d, 0x50, 0x58, 0x4c, 0x43, 0x61, 0x54,
137 0x78, 0x59, 0x79, 0x45, 0x66, 0x6d, 0x4e, 0x6a, 0x78, 0x67, 0x34, 0x56,
138 0x44, 0x36, 0x6a, 0x78, 0x68, 0x65, 0x66, 0x30, 0x67, 0x71, 0x66, 0x7a,
139 0x6d, 0x79, 0x75, 0x76, 0x77, 0x48, 0x57, 0x7a, 0x33, 0x72, 0x79, 0x4c,
140 0x63, 0x51, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e,
141 0x44, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59,
142 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a
143 };
144 unsigned int jb_pub_pem_len = 174;
145 uint8_t aah_pub_pem[] = {
146 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50,
147 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d,
148 0x2d, 0x2d, 0x0a, 0x4d, 0x46, 0x59, 0x77, 0x45, 0x41, 0x59, 0x48, 0x4b,
149 0x6f, 0x5a, 0x49, 0x7a, 0x6a, 0x30, 0x43, 0x41, 0x51, 0x59, 0x46, 0x4b,
150 0x34, 0x45, 0x45, 0x41, 0x41, 0x6f, 0x44, 0x51, 0x67, 0x41, 0x45, 0x54,
151 0x6d, 0x6a, 0x33, 0x5a, 0x7a, 0x78, 0x4a, 0x32, 0x57, 0x53, 0x4f, 0x74,
152 0x6d, 0x6d, 0x7a, 0x65, 0x72, 0x45, 0x36, 0x68, 0x4d, 0x35, 0x4a, 0x71,
153 0x4c, 0x36, 0x56, 0x33, 0x79, 0x36, 0x4a, 0x0a, 0x4f, 0x49, 0x32, 0x51,
154 0x58, 0x36, 0x41, 0x67, 0x6b, 0x42, 0x30, 0x6d, 0x4d, 0x54, 0x63, 0x42,
155 0x47, 0x30, 0x52, 0x7a, 0x6a, 0x4d, 0x77, 0x63, 0x37, 0x39, 0x45, 0x41,
156 0x65, 0x33, 0x44, 0x72, 0x7a, 0x35, 0x75, 0x5a, 0x45, 0x48, 0x30, 0x7a,
157 0x57, 0x54, 0x78, 0x39, 0x6a, 0x33, 0x72, 0x5a, 0x7a, 0x49, 0x75, 0x58,
158 0x47, 0x67, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e,
159 0x44, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59,
160 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a
161 };
162 unsigned int aah_pub_pem_len = 174;
163
164 unsigned char fpp_pub_pem[] = {
165 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50,
166 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d,
167 0x2d, 0x2d, 0x0a, 0x4d, 0x46, 0x59, 0x77, 0x45, 0x41, 0x59, 0x48, 0x4b,
168 0x6f, 0x5a, 0x49, 0x7a, 0x6a, 0x30, 0x43, 0x41, 0x51, 0x59, 0x46, 0x4b,
169 0x34, 0x45, 0x45, 0x41, 0x41, 0x6f, 0x44, 0x51, 0x67, 0x41, 0x45, 0x59,
170 0x73, 0x44, 0x6a, 0x63, 0x45, 0x41, 0x52, 0x38, 0x69, 0x4c, 0x6d, 0x39,
171 0x53, 0x4a, 0x4b, 0x57, 0x6a, 0x42, 0x57, 0x79, 0x50, 0x56, 0x41, 0x72,
172 0x76, 0x49, 0x41, 0x68, 0x79, 0x47, 0x71, 0x0a, 0x38, 0x69, 0x45, 0x53,
173 0x76, 0x36, 0x4c, 0x37, 0x56, 0x52, 0x63, 0x58, 0x47, 0x4f, 0x49, 0x43,
174 0x4e, 0x74, 0x2b, 0x56, 0x38, 0x51, 0x5a, 0x54, 0x55, 0x52, 0x4d, 0x6d,
175 0x45, 0x58, 0x44, 0x64, 0x6e, 0x5a, 0x43, 0x68, 0x65, 0x45, 0x65, 0x6b,
176 0x77, 0x76, 0x77, 0x47, 0x4b, 0x4b, 0x68, 0x4e, 0x36, 0x77, 0x5a, 0x71,
177 0x64, 0x77, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e,
178 0x44, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59,
179 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a
180 };
181 unsigned int fpp_pub_pem_len = 174;
182
183 unsigned char sh_pub_pem[] = {
184 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x50,
185 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d,
186 0x2d, 0x2d, 0x0a, 0x4d, 0x46, 0x59, 0x77, 0x45, 0x41, 0x59, 0x48, 0x4b,
187 0x6f, 0x5a, 0x49, 0x7a, 0x6a, 0x30, 0x43, 0x41, 0x51, 0x59, 0x46, 0x4b,
188 0x34, 0x45, 0x45, 0x41, 0x41, 0x6f, 0x44, 0x51, 0x67, 0x41, 0x45, 0x71,
189 0x30, 0x4f, 0x66, 0x6a, 0x51, 0x50, 0x39, 0x44, 0x73, 0x59, 0x45, 0x5a,
190 0x43, 0x69, 0x76, 0x5a, 0x53, 0x73, 0x5a, 0x73, 0x55, 0x50, 0x47, 0x6b,
191 0x66, 0x6b, 0x4c, 0x55, 0x72, 0x78, 0x61, 0x0a, 0x76, 0x51, 0x52, 0x79,
192 0x34, 0x45, 0x4b, 0x46, 0x62, 0x6a, 0x59, 0x6a, 0x55, 0x72, 0x71, 0x39,
193 0x53, 0x6e, 0x71, 0x6d, 0x51, 0x69, 0x50, 0x36, 0x58, 0x32, 0x6a, 0x67,
194 0x30, 0x32, 0x35, 0x78, 0x46, 0x45, 0x51, 0x58, 0x61, 0x57, 0x49, 0x58,
195 0x30, 0x4a, 0x77, 0x34, 0x74, 0x38, 0x43, 0x37, 0x36, 0x36, 0x64, 0x79,
196 0x43, 0x51, 0x3d, 0x3d, 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e,
197 0x44, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x20, 0x4b, 0x45, 0x59,
198 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a
199 };
200 unsigned int sh_pub_pem_len = 174;
201
202 static std::map<std::string, std::pair<const uint8_t*, int>> KEYS = {
203 { "fp", std::pair(fp_pub_pem, fp_pub_pem_len) },
204 { "dk", std::pair(dk_pub_pem, dk_pub_pem_len) },
205 { "ww", std::pair(ww_pub_pem, ww_pub_pem_len) },
206 { "jb", std::pair(jb_pub_pem, jb_pub_pem_len) },
207 { "aah", std::pair(aah_pub_pem, aah_pub_pem_len) },
208 { "fpp", std::pair(fpp_pub_pem, fpp_pub_pem_len) },
209 { "sh", std::pair(sh_pub_pem, sh_pub_pem_len) }
210 };
211
212 template<typename... Args>
213 16 std::string string_sprintf(const char* format, Args... args) {
214 16 int length = std::snprintf(nullptr, 0, format, args...);
215
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
16 char* buf = new char[length + 1];
216 16 std::snprintf(buf, length + 1, format, args...);
217
218
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
16 std::string str(buf);
219
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
16 delete[] buf;
220 32 return std::move(str);
221 16 }
222
223 // One single-quoted shell word. Pin names, modes and module names reach the
224 // shell from cape-info.json, which an unsigned (uploaded) EEPROM supplies:
225 // pasted in bare, "x;curl h/s|sh" ran as root at boot.
226 203 static std::string CapeShellQuote(const std::string& v) {
227
1/2
✓ Branch 0 taken 203 times.
✗ Branch 1 not taken.
203 std::string out = "'";
228
2/2
✓ Branch 0 taken 974 times.
✓ Branch 1 taken 203 times.
1177 for (char ch : v) {
229
5/16
✗ Branch 0 not taken.
✓ Branch 1 taken 974 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 974 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 974 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 974 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 974 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
2922 out += (ch == '\'') ? std::string("'\\''") : std::string(1, ch);
230 }
231
1/2
✓ Branch 0 taken 203 times.
✗ Branch 1 not taken.
203 out += "'";
232 203 return out;
233 ✗ }
234 // A module spec ("name" or "name param=value ...") as quoted shell words.
235 7 static std::string QuotedModuleSpec(const std::string& spec) {
236 7 std::string out;
237 7 size_t pos = 0;
238
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 7 times.
16 while (pos < spec.size()) {
239 9 size_t end = spec.find(' ', pos);
240
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 2 times.
9 if (end == std::string::npos) {
241 7 end = spec.size();
242 }
243
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 if (end > pos) {
244
6/10
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 9 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 9 times.
✗ Branch 9 not taken.
9 out += (out.empty() ? "" : " ") + CapeShellQuote(spec.substr(pos, end - pos));
245 }
246 9 pos = end + 1;
247 }
248 7 return out;
249 ✗ }
250
251 1368 static std::string exec(const std::string& cmd) {
252 std::array<char, 128> buffer;
253 1368 std::string result;
254
1/2
✓ Branch 0 taken 1368 times.
✗ Branch 1 not taken.
1368 struct PipeCloser { void operator()(FILE* f) const { if (f) pclose(f); } };
255
1/2
✓ Branch 0 taken 1368 times.
✗ Branch 1 not taken.
1368 std::unique_ptr<FILE, PipeCloser> pipe(popen(cmd.c_str(), "r"));
256
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1368 times.
1368 if (!pipe) {
257 ✗ throw std::runtime_error("popen() failed!");
258 }
259
3/4
✓ Branch 0 taken 1459 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 91 times.
✓ Branch 3 taken 1368 times.
4286 while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
260
1/2
✓ Branch 0 taken 91 times.
✗ Branch 1 not taken.
91 result += buffer.data();
261 }
262 1368 return result;
263 1368 }
264
265 4617 static bool file_exists(const std::string& f) {
266 4617 return access(f.c_str(), F_OK) != -1;
267 }
268 2541 static std::string trim(std::string str) {
269 // remove trailing white space
270
8/8
✓ Branch 0 taken 19244 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 19154 times.
✓ Branch 3 taken 90 times.
✓ Branch 4 taken 16637 times.
✓ Branch 5 taken 2517 times.
✓ Branch 6 taken 16727 times.
✓ Branch 7 taken 2541 times.
19268 while (!str.empty() && (std::isspace(str.back()) || str.back() == 0))
271 16727 str.pop_back();
272
273 // return residue after leading white space
274 2541 std::size_t pos = 0;
275
6/6
✓ Branch 0 taken 2533 times.
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 2517 times.
✓ Branch 4 taken 16 times.
✓ Branch 5 taken 2541 times.
2557 while (pos < str.size() && std::isspace(str[pos]))
276 16 ++pos;
277 2541 return str.substr(pos);
278 }
279 2459 static std::string read_string(FILE* file, int len) {
280 2459 std::string buf;
281
1/2
✓ Branch 0 taken 2459 times.
✗ Branch 1 not taken.
2459 buf.reserve(len + 1);
282
1/2
✓ Branch 0 taken 2459 times.
✗ Branch 1 not taken.
2459 buf.resize(len);
283
1/2
✓ Branch 0 taken 2459 times.
✗ Branch 1 not taken.
2459 fread(&buf[0], 1, len, file);
284
2/4
✓ Branch 0 taken 2459 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2459 times.
✗ Branch 3 not taken.
4918 return trim(buf);
285 2459 }
286
287 // The EEPROM image is the largest thing parseEEPROM()'s buffers hold.
288 static constexpr int MAX_EEPROM_SIZE = 32768;
289
290 // A section's length field. Everything in the image is untrusted until the
291 // signature is checked - and a virtual EEPROM is an uploaded file, not a
292 // 32K chip - so a negative length (fseek backwards: the parse never ends) or
293 // one past the buffers (heap overflow) is a corrupt image.
294 868 static int read_section_length(FILE* file) {
295
2/4
✓ Branch 0 taken 868 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 868 times.
✗ Branch 3 not taken.
868 int len = std::stoi(read_string(file, 6));
296
4/4
✓ Branch 0 taken 867 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 866 times.
868 if (len < 0 || len > MAX_EEPROM_SIZE) {
297
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 throw std::runtime_error("invalid section length " + std::to_string(len));
298 }
299 866 return len;
300 }
301
302 // A file name from a section, relative to the extraction directory. These are
303 // read before the signature is known, so nothing that could leave that
304 // directory is accepted.
305 142 static std::string checked_section_name(const std::string& name) {
306
6/8
✓ Branch 0 taken 142 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 141 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 141 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 141 times.
142 if (name.empty() || name[0] == '/' || name.back() == '/') {
307
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 throw std::runtime_error("invalid file name in EEPROM: '" + name + "'");
308 }
309
2/2
✓ Branch 0 taken 2409 times.
✓ Branch 1 taken 141 times.
2550 for (char c : name) {
310
8/12
✓ Branch 0 taken 434 times.
✓ Branch 1 taken 1975 times.
✓ Branch 2 taken 286 times.
✓ Branch 3 taken 148 times.
✓ Branch 4 taken 286 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 145 times.
✓ Branch 7 taken 141 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 145 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
2409 if (!isalnum((unsigned char)c) && c != '.' && c != '_' && c != '-' && c != '/' && c != '+') {
311 ✗ throw std::runtime_error("invalid file name in EEPROM: '" + name + "'");
312 }
313 }
314
1/2
✓ Branch 0 taken 141 times.
✗ Branch 1 not taken.
141 std::stringstream parts(name);
315 141 std::string part;
316
4/6
✓ Branch 0 taken 421 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 421 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 281 times.
✓ Branch 5 taken 140 times.
421 while (std::getline(parts, part, '/')) {
317
8/12
✓ Branch 0 taken 281 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 281 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 281 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 281 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 280 times.
✓ Branch 10 taken 1 times.
✓ Branch 11 taken 280 times.
281 if (part.empty() || part == "." || part == "..") {
318
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 throw std::runtime_error("invalid file name in EEPROM: '" + name + "'");
319 }
320 }
321
1/2
✓ Branch 0 taken 140 times.
✗ Branch 1 not taken.
280 return name;
322 142 }
323
324 // Run an archive tool in dir with no shell in between: the archive's name
325 // came from the EEPROM. Waits for that child only (fppd sets SA_NOCLDWAIT).
326 139 static void run_in_dir(const std::string& dir, const std::vector<std::string>& args) {
327 139 std::vector<char*> argv;
328
2/2
✓ Branch 0 taken 417 times.
✓ Branch 1 taken 139 times.
556 for (auto& a : args) {
329
1/2
✓ Branch 0 taken 417 times.
✗ Branch 1 not taken.
417 argv.push_back((char*)a.c_str());
330 }
331
1/2
✓ Branch 0 taken 139 times.
✗ Branch 1 not taken.
139 argv.push_back(nullptr);
332 139 pid_t pid = fork();
333
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 278 times.
278 if (pid < 0) {
334 ✗ throw std::runtime_error("fork failed");
335 }
336
2/2
✓ Branch 0 taken 139 times.
✓ Branch 1 taken 139 times.
278 if (pid == 0) {
337
1/2
✓ Branch 0 taken 139 times.
✗ Branch 1 not taken.
139 int devnull = open("/dev/null", O_RDWR);
338
1/2
✓ Branch 0 taken 139 times.
✗ Branch 1 not taken.
139 if (devnull >= 0) {
339 139 dup2(devnull, 0);
340 139 dup2(devnull, 1);
341 139 dup2(devnull, 2);
342 }
343
1/2
✓ Branch 0 taken 139 times.
✗ Branch 1 not taken.
139 if (chdir(dir.c_str()) == 0) {
344 139 execvp(argv[0], argv.data());
345 }
346 ✗ _exit(127);
347 }
348
5/8
✓ Branch 0 taken 139 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 98 times.
✓ Branch 3 taken 41 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 98 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 139 times.
139 while (waitpid(pid, nullptr, 0) < 0 && errno == EINTR) {
349 }
350 139 }
351
352 371 static void put_file_contents(const std::string& path, const uint8_t* data, int len) {
353
1/2
✓ Branch 0 taken 371 times.
✗ Branch 1 not taken.
371 FILE* f = fopen(path.c_str(), "w+b");
354
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 370 times.
371 if (f == nullptr) {
355
4/8
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ 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 not taken.
1 throw std::runtime_error("could not write " + path + ": " + strerror(errno));
356 }
357
1/2
✓ Branch 0 taken 370 times.
✗ Branch 1 not taken.
370 fwrite(data, 1, len, f);
358
1/2
✓ Branch 0 taken 370 times.
✗ Branch 1 not taken.
370 fclose(f);
359
360 370 uid_t fppUid = 0;
361 370 gid_t fppGid = 0;
362
3/4
✓ Branch 0 taken 370 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 307 times.
✓ Branch 3 taken 63 times.
370 if (getFppUserIds(fppUid, fppGid)) {
363 307 chown(path.c_str(), fppUid, fppGid);
364 }
365
366 370 mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
367 370 chmod(path.c_str(), mode);
368 370 }
369 // An unreadable file comes back empty (len 0, still a terminated buffer the
370 // caller frees), which the callers already treat as "nothing there"; a null
371 // FILE* used to go straight into fseek().
372 356 static uint8_t* get_file_contents(const std::string& path, int& len) {
373 356 len = 0;
374 356 FILE* fp = fopen(path.c_str(), "rb");
375
2/2
✓ Branch 0 taken 355 times.
✓ Branch 1 taken 1 times.
356 if (fp != nullptr) {
376 355 fseek(fp, 0L, SEEK_END);
377 355 long l = ftell(fp);
378 355 fseek(fp, 0L, SEEK_SET);
379
1/2
✓ Branch 0 taken 285 times.
✗ Branch 1 not taken.
355 len = l > 0 ? (int)l : 0;
380 }
381 356 uint8_t* data = (uint8_t*)malloc(len + 1);
382
2/2
✓ Branch 0 taken 355 times.
✓ Branch 1 taken 1 times.
356 if (fp != nullptr) {
383 355 len = (int)fread(data, 1, len, fp);
384 355 fclose(fp);
385 }
386 356 data[len] = 0;
387 356 return data;
388 }
389
390 #ifdef PLATFORM_PI
391 static const std::string PLATFORM_DIR = "pi";
392 36 const std::string& getPlatformCapeDir() { return PLATFORM_DIR; }
393 #elif defined(PLATFORM_BB64)
394 static const std::string PLATFORM_DIR = "bb64";
395 56 const std::string& getPlatformCapeDir() { return PLATFORM_DIR; }
396 #elif defined(PLATFORM_BBB)
397 static const std::string PLATFORM_DIR_BBB = "bbb";
398 static const std::string PLATFORM_DIR_PB = "pb";
399 29 const std::string& getPlatformCapeDir() {
400 29 int len = 0;
401
2/4
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
29 char* buf = (char*)get_file_contents("/proc/device-tree/model", len);
402
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 16 times.
29 if (strcmp(&buf[10], "PocketBeagle") == 0) {
403 13 free(buf);
404 13 return PLATFORM_DIR_PB;
405 }
406 16 free(buf);
407 16 return PLATFORM_DIR_BBB;
408 }
409
410 #else
411 static const std::string PLATFORM_DIR = "";
412 3 const std::string& getPlatformCapeDir() { return PLATFORM_DIR; }
413 #endif
414
415 // A file that parses but holds the wrong kind of value at its root - an array
416 // where an object is expected - is a corrupt config, not usable data: the
417 // jsoncpp accessor the caller reaches for next throws Json::LogicError, and
418 // nothing here catches it. Callers pass the root they require so that throw
419 // stays unreachable. This mirrors the JsonRoot check on FPP's own loader in
420 // common.cpp; it is duplicated rather than shared because CapeUtils is a
421 // standalone component that does not pull in common.h.
422 enum class CapeJsonRoot {
423 Object,
424 Array
425 };
426 196 static bool LoadJsonFromFile(const std::string& filename, Json::Value& root, CapeJsonRoot expected) {
427
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 196 times.
196 if (!file_exists(filename)) {
428 ✗ return false;
429 }
430 196 int len = 0;
431
1/2
✓ Branch 0 taken 196 times.
✗ Branch 1 not taken.
196 char* data = (char*)get_file_contents(filename, len);
432
433
1/2
✓ Branch 0 taken 196 times.
✗ Branch 1 not taken.
196 Json::CharReaderBuilder builder;
434
1/2
✓ Branch 0 taken 196 times.
✗ Branch 1 not taken.
196 Json::CharReader* reader = builder.newCharReader();
435 196 std::string errors;
436
3/6
✓ Branch 0 taken 196 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 196 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 196 times.
✗ Branch 5 not taken.
588 builder["collectComments"] = false;
437
1/2
✓ Branch 0 taken 196 times.
✗ Branch 1 not taken.
196 bool success = reader->parse(data, data + len, &root, &errors);
438
1/2
✓ Branch 0 taken 196 times.
✗ Branch 1 not taken.
196 delete reader;
439
440 196 free(data);
441
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 196 times.
196 if (!success) {
442 ✗ printf("Failed to parse %s: %s\n", filename.c_str(), errors.c_str());
443 ✗ return false;
444 }
445
446 // A null root is not a mismatch - jsoncpp treats null as an empty value of
447 // whatever type it is first used as and never throws on it.
448
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 196 times.
196 Json::ValueType expectedType = (expected == CapeJsonRoot::Array) ? Json::arrayValue : Json::objectValue;
449
2/4
✓ Branch 0 taken 196 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 196 times.
196 if (root.isNull()) {
450 ✗ root = Json::Value(expectedType);
451 ✗ return true;
452 }
453
2/4
✓ Branch 0 taken 196 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 196 times.
196 if (root.type() != expectedType) {
454 ✗ printf("Wrong root type in %s: expected %s - treating as empty\n",
455 filename.c_str(), (expectedType == Json::arrayValue) ? "array" : "object");
456 ✗ root = Json::Value(expectedType);
457 ✗ return false;
458 }
459 196 return true;
460 196 }
461
462 ✗ static void disableOutputs(Json::Value& disables) {
463 ✗ for (int x = 0; x < disables.size(); x++) {
464 ✗ std::string file = disables[x]["file"].asString();
465 ✗ std::string type = disables[x]["type"].asString();
466 ✗ std::string subType = "";
467 ✗ if (disables[x].isMember("subType")) {
468 ✗ subType = disables[x]["subType"].asString();
469 }
470 ✗ std::string fullFile = "/home/fpp/media/" + file;
471 ✗ if (file_exists(fullFile)) {
472 ✗ Json::Value result;
473 ✗ Json::CharReaderBuilder builder;
474 ✗ Json::CharReader* reader = builder.newCharReader();
475 ✗ std::string errors;
476 ✗ std::ifstream istream(fullFile);
477 ✗ std::stringstream buffer;
478 ✗ buffer << istream.rdbuf();
479 ✗ istream.close();
480
481 ✗ std::string str = buffer.str();
482 ✗ bool success = reader->parse(str.c_str(), str.c_str() + str.size(), &result, &errors);
483 ✗ if (success) {
484 ✗ bool changed = false;
485 ✗ if (result.isMember("channelOutputs")) {
486 ✗ for (int co = 0; co < result["channelOutputs"].size(); co++) {
487 ✗ if (result["channelOutputs"][co]["type"].asString() == type && (subType == "" || (result["channelOutputs"][co].isMember("subType") && result["channelOutputs"][co]["subType"].asString() == subType))) {
488 ✗ if (result["channelOutputs"][co]["enabled"].asInt() == 1) {
489 ✗ result["channelOutputs"][co]["enabled"] = 0;
490 ✗ changed = true;
491 }
492 }
493 }
494 }
495 ✗ if (changed) {
496 ✗ Json::StreamWriterBuilder wbuilder;
497 ✗ std::string resultStr = Json::writeString(wbuilder, result);
498 ✗ put_file_contents(fullFile, (const uint8_t*)resultStr.c_str(), resultStr.size());
499 ✗ }
500 }
501 ✗ }
502 ✗ }
503 ✗ }
504 // The boot partition is mounted at /boot on some platforms and /boot/firmware on
505 // others, and that can differ between images of the same OS release, so it cannot
506 // be baked in at build time. Debian 12 on the BBB eMMC is the awkward case: it
507 // keeps uEnv.txt in /boot but still mounts an empty FAT filesystem on
508 // /boot/firmware, so the existence of the directory tells us nothing. Locate the
509 // boot config by looking for the file itself, newest layout first. Returns an
510 // empty string if it isn't found in either location.
511 25 [[maybe_unused]] static std::string findBootConfigFile(const std::string& filename) {
512
1/2
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
28 for (const char* dir : { "/boot/firmware", "/boot" }) {
513
3/6
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
28 std::string path = std::string(dir) + "/" + filename;
514
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 3 times.
28 if (file_exists(path)) {
515 25 return path;
516 }
517 3 }
518 ✗ return "";
519 }
520 // Is this directive already present in the boot config as a line of its own?
521 //
522 // A plain substring search used to stand in for this, and it stops being correct
523 // the moment one directive is a prefix of another: "dtoverlay=fpp-cape-overlay"
524 // is a substring of "dtoverlay=fpp-cape-overlay-pi5", so a config.txt carrying
525 // only a variant line looked like it already had the base overlay and the base
526 // line was never written. The cape then boots with its board specific fragments
527 // and nothing else, which is a worse failure than having no overlay at all.
528 // Surrounding whitespace is tolerated because the old behaviour tolerated an
529 // indented line; a longer directive on the same line is not.
530 18 static bool configContainsDirective(const std::string& config, const std::string& directive) {
531
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
18 if (directive.empty()) {
532 ✗ return false;
533 }
534
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 12 times.
20 for (size_t pos = config.find(directive); pos != std::string::npos;
535 2 pos = config.find(directive, pos + 1)) {
536 8 size_t b = pos;
537
4/8
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 8 times.
8 while (b > 0 && (config[b - 1] == ' ' || config[b - 1] == '\t')) {
538 ✗ --b;
539 }
540
3/6
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 8 times.
8 if (b != 0 && config[b - 1] != '\n') {
541 ✗ continue;
542 }
543 8 size_t e = pos + directive.length();
544
5/10
✓ 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 not taken.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 8 times.
8 while (e < config.length() && (config[e] == ' ' || config[e] == '\t' || config[e] == '\r')) {
545 ✗ ++e;
546 }
547
5/6
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 2 times.
8 if (e == config.length() || config[e] == '\n') {
548 6 return true;
549 }
550 }
551 12 return false;
552 }
553 // What an unsigned cape may do to the boot config: add or drop device tree
554 // overlays and parameters, which is what capes use it for. Anything else
555 // there (kernel=, initramfs, cmdline=, init=) decides what the box boots, and
556 // an unsigned image is as often an uploaded file as it is a chip. One line
557 // only: an embedded newline would carry any directive in behind an allowed
558 // prefix.
559 8 static bool UnsignedBootConfigLineAllowed(const std::string& v) {
560
2/2
✓ Branch 0 taken 167 times.
✓ Branch 1 taken 7 times.
174 for (char ch : v) {
561
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 166 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
167 if ((unsigned char)ch < 0x20 && ch != '\t') {
562 1 return false;
563 }
564 }
565
4/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 1 times.
10 auto startsWith = [&v](const char* p) { return v.rfind(p, 0) == 0 && v.size() > strlen(p); };
566 #if defined(PLATFORM_BBB)
567 // uEnv.txt: overlay slots 0 and 1 hold FPP's own base and cape overlays.
568 ✗ return startsWith("uboot_overlay_addr") && v.size() > 20 && v[18] >= '2' && v[18] <= '7' && v[19] == '=';
569 #elif defined(PLATFORM_ARMBIAN)
570 return startsWith("overlays=") || startsWith("param_");
571 #else
572
5/8
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
7 return startsWith("dtoverlay=") || startsWith("dtparam=");
573 #endif
574 }
575
576 // Remove every line of config that is exactly directive (ignoring the
577 // surrounding blanks configContainsDirective() ignores). An unsigned cape's
578 // remove is held to whole lines: as a substring erase, "dtparam=" stripped
579 // that prefix from every dtparam line in the file.
580 2 static void removeDirectiveLines(std::string& config, const std::string& directive) {
581 2 std::string out;
582 2 size_t pos = 0;
583
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
7 while (pos < config.size()) {
584 5 size_t end = config.find('\n', pos);
585
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 size_t next = (end == std::string::npos) ? config.size() : end + 1;
586
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 std::string line = config.substr(pos, (end == std::string::npos ? config.size() : end) - pos);
587 5 size_t b = line.find_first_not_of(" \t");
588 5 size_t e = line.find_last_not_of(" \t\r");
589
4/6
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 3 times.
5 bool match = b != std::string::npos && line.compare(b, e - b + 1, directive) == 0;
590
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 if (!match) {
591
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 out.append(config, pos, next - pos);
592 }
593 5 pos = next;
594 5 }
595
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 config = out;
596 2 }
597
598 4 static bool processBootConfig(Json::Value& bootConfig, bool trusted) {
599 #if defined(PLATFORM_PI)
600
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 const std::string fileName = findBootConfigFile("config.txt");
601 #elif defined(PLATFORM_BBB)
602 ✗ const std::string fileName = findBootConfigFile("uEnv.txt");
603 #elif defined(PLATFORM_BB64)
604 // TODO - booting is VERY different on BB64
605 ✗ const std::string fileName;
606 #elif defined(PLATFORM_ARMBIAN)
607 const std::string fileName = findBootConfigFile("armbianEnv.txt");
608 #else
609 // unknown platform
610 ✗ const std::string fileName;
611 #endif
612
613
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (fileName.empty())
614 ✗ return false;
615
616 4 int len = 0;
617
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 uint8_t* data = get_file_contents(fileName, len);
618
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (len == 0) {
619 ✗ return false;
620 }
621
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 std::string current = (char*)data;
622
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 std::string orig = current;
623
3/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
4 if (bootConfig.isMember("remove")) {
624
4/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 2 times.
6 for (int x = 0; x < bootConfig["remove"].size(); x++) {
625
3/6
✓ 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.
4 std::string v = bootConfig["remove"][x].asString();
626 // find("") matches at 0 forever and erasing it removes nothing, so
627 // an empty entry spun here for good and boot never finished.
628
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 if (v.empty()) {
629 1 continue;
630 }
631
6/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 2 times.
3 if (!trusted && !UnsignedBootConfigLineAllowed(v)) {
632
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 printf("CapeUtils: unsigned cape may not remove \"%s\" from the boot config\n", v.c_str());
633 1 continue;
634 }
635
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!trusted) {
636
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 removeDirectiveLines(current, v);
637 2 continue;
638 }
639 ✗ size_t pos = std::string::npos;
640 ✗ while ((pos = current.find(v)) != std::string::npos) {
641 // If found then erase it from string
642 ✗ current.erase(pos, v.length());
643 }
644
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 }
645 }
646
3/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
4 if (bootConfig.isMember("append")) {
647
4/6
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 2 times.
7 for (int x = 0; x < bootConfig["append"].size(); x++) {
648
3/6
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
5 std::string v = bootConfig["append"][x].asString();
649
6/8
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 2 times.
5 if (!trusted && !UnsignedBootConfigLineAllowed(v)) {
650
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 printf("CapeUtils: unsigned cape may not add \"%s\" to the boot config\n", v.c_str());
651 3 continue;
652 }
653
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!configContainsDirective(current, v)) {
654 // If not found then append it
655
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 printf("Adding config option: %s\n", v.c_str());
656
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 current += "\n";
657
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 current += v;
658
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 current += "\n";
659 }
660
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
5 }
661 }
662 4 free(data);
663
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (current != orig) {
664
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 put_file_contents(fileName, (const uint8_t*)current.c_str(), current.length());
665 }
666 4 return current != orig;
667 4 }
668 48 static void handleReboot(bool r) {
669
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 40 times.
48 if (r) {
670
2/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
16 if (file_exists("/.fppcapereboot")) {
671 ✗ const std::string msg = "Reboot Cycle Detected with FPP Cape Detect";
672 ✗ put_file_contents("/home/fpp/media/tmp/reboot", (const uint8_t*)msg.c_str(), msg.length());
673 ✗ printf("%s\n", msg.c_str());
674 ✗ } else {
675 8 const uint8_t data[2] = { 32, 0 };
676
2/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
8 put_file_contents("/.fppcapereboot", data, 1);
677 8 sync();
678 8 setuid(0);
679 8 reboot(RB_AUTOBOOT);
680 8 exit(0);
681 }
682 } else {
683 40 remove("/.fppcapereboot");
684 }
685 40 }
686 46 static void copyFile(const std::string& src, const std::string& target) {
687 int s, t;
688
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 s = open(src.c_str(), O_RDONLY);
689
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 if (s == -1) {
690 ✗ printf("Failed to open src %s - %s\n", src.c_str(), strerror(errno));
691 ✗ return;
692 }
693
694 46 size_t fsep = target.find_last_of("/");
695
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 if (fsep != std::string::npos)
696
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 mkdir(target.substr(0, fsep).c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); // ensure target folder exists
697 // if (mkdir(path, mode) != 0 && errno != EEXIST)
698 // else if (!S_ISDIR(st.st_mode)) errno = ENOTDIR;
699
700
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 t = open(target.c_str(), O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
701
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 if (t == -1) {
702 ✗ printf("Failed to open target %s - %s\n", target.c_str(), strerror(errno));
703 ✗ close(s);
704 ✗ return;
705 }
706 uint8_t buf[257];
707 while (true) {
708
1/2
✓ Branch 0 taken 174 times.
✗ Branch 1 not taken.
174 int l = read(s, buf, 256);
709
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 174 times.
174 if (l == -1) {
710 ✗ printf("Error copying file %s - %s\n", src.c_str(), strerror(errno));
711 ✗ close(s);
712 ✗ close(t);
713 ✗ return;
714
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 128 times.
174 } else if (l == 0) {
715
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 close(s);
716
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 close(t);
717 46 return;
718 }
719
1/2
✓ Branch 0 taken 128 times.
✗ Branch 1 not taken.
128 write(t, buf, l);
720 128 }
721 }
722 // similar to copy file, but do in 512 increments and check if the last 64 bytes are 0xFF
723 // to see if we can detect the end of the actual data and stop at that point as reading
724 // more data then needed is slow on the i2c bus
725 3 static void copyEEPROM(const std::string& src, const std::string& target) {
726 int s, t;
727
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 s = open(src.c_str(), O_RDONLY);
728
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (s == -1) {
729
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 printf("Failed to open src %s - %s\n", src.c_str(), strerror(errno));
730 1 return;
731 }
732
733 2 size_t fsep = target.find_last_of("/");
734
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (fsep != std::string::npos)
735
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 mkdir(target.substr(0, fsep).c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); // ensure target folder exists
736 // if (mkdir(path, mode) != 0 && errno != EEXIST)
737 // else if (!S_ISDIR(st.st_mode)) errno = ENOTDIR;
738
739
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 t = open(target.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
740
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (t == -1) {
741 ✗ printf("Failed to open target %s - %s\n", target.c_str(), strerror(errno));
742 ✗ close(s);
743 ✗ return;
744 }
745 2 constexpr int BUFSIZE = 256;
746 uint8_t buf[BUFSIZE + 1];
747 while (true) {
748
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 int l = read(s, buf, BUFSIZE);
749
2/4
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
22 while (l > 0 && l < BUFSIZE) {
750 ✗ int r = read(s, &buf[l], BUFSIZE - l);
751 ✗ if (r == -1) {
752 ✗ printf("Error copying file %s - %s\n", src.c_str(), strerror(errno));
753 ✗ close(s);
754 ✗ close(t);
755 ✗ return;
756 }
757 ✗ l += r;
758 }
759
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 if (l == -1) {
760 ✗ printf("Error copying file %s - %s\n", src.c_str(), strerror(errno));
761 ✗ close(s);
762 ✗ close(t);
763 ✗ return;
764
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 } else if (l == 0) {
765 ✗ close(s);
766 ✗ close(t);
767 ✗ return;
768 }
769
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 write(t, buf, l);
770
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 if (l > 64) {
771 22 bool allFF = true;
772
2/2
✓ Branch 0 taken 148 times.
✓ Branch 1 taken 2 times.
150 for (int x = 0; x < 64; x++) {
773
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 128 times.
148 if (buf[l - x - 1] != 0xFF) {
774 20 allFF = false;
775 20 break;
776 }
777 }
778
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 20 times.
22 if (allFF) {
779
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 close(s);
780
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 close(t);
781 2 return;
782 }
783 }
784 20 }
785 }
786 667 static size_t fileSize(const std::string& target) {
787 struct stat stat_buf;
788 667 int rc = stat(target.c_str(), &stat_buf);
789
2/2
✓ Branch 0 taken 666 times.
✓ Branch 1 taken 1 times.
667 return rc == 0 ? stat_buf.st_size : 0;
790 }
791
792 2 static void copyIfNotExist(const std::string& src, const std::string& target) {
793
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (fileSize(target) > 0) {
794 1 return;
795 }
796 1 copyFile(src, target);
797 }
798 372 static void removeIfExist(const std::string& src) {
799
2/2
✓ Branch 0 taken 144 times.
✓ Branch 1 taken 228 times.
372 if (file_exists(src)) {
800 144 unlink(src.c_str());
801 }
802 372 }
803 230 void setOwnerGroup(const std::string& filename) {
804 230 uid_t fppUid = 0;
805 230 gid_t fppGid = 0;
806
3/4
✓ Branch 0 taken 230 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 123 times.
✓ Branch 3 taken 107 times.
230 if (getFppUserIds(fppUid, fppGid)) {
807 123 chown(filename.c_str(), fppUid, fppGid);
808 }
809 230 }
810 172 bool setFilePerms(const std::string& filename) {
811 172 mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
812 172 chmod(filename.c_str(), mode);
813 172 setOwnerGroup(filename);
814 172 return true;
815 }
816 #if defined(PLATFORM_PI)
817 // A cape EEPROM ships a base device tree overlay as fpp-cape-overlay-rpi.dtb. It
818 // may also ship board specific variants named fpp-cape-overlay-rpi-<filter>.dtb,
819 // where <filter> is a config.txt conditional filter -- "pi5", "pi4", "pi02", "cm4"
820 // and so on. Each variant is installed as fpp-cape-overlay-<filter>.dtbo and
821 // referenced from a block that this code owns end to end:
822 //
823 // # FPP Cape Overlay Variants - BEGIN (managed by fppcapedetect, do not edit)
824 // [pi5]
825 // dtoverlay=fpp-cape-overlay-pi5
826 // [all]
827 // # FPP Cape Overlay Variants - END
828 //
829 // The point of the variants is that a symbol a fragment references has to exist in
830 // the base DTB or the firmware discards the WHOLE overlay, not just that fragment
831 // -- a Pi 5 only node such as rpi_rtc in an otherwise portable overlay leaves a Pi 4
832 // with no codec, no RTC and no i2s, and the only trace is a dterror in
833 // /proc/device-tree/chosen/user-warnings. Splitting the board specific fragments
834 // out into a filtered overlay keeps the base resolvable everywhere.
835 //
836 // The block is rewritten wholesale from the variants actually present, which is what
837 // makes removal work: swap in a cape that ships no pi5 variant, and the previous
838 // cape's [pi5] line has to go with it or the firmware fails to resolve an overlay
839 // that is no longer on disk. Anything outside the markers is left untouched, and
840 // the block always closes with [all] so a later append (this file's own bootConfig
841 // handling appends at EOF) does not land inside somebody's filter.
842 static const std::string CAPE_OVERLAY_VARIANT_BEGIN = "# FPP Cape Overlay Variants - BEGIN (managed by fppcapedetect, do not edit)";
843 static const std::string CAPE_OVERLAY_VARIANT_END = "# FPP Cape Overlay Variants - END";
844 static const std::string CAPE_OVERLAY_SRC_PREFIX = "fpp-cape-overlay-rpi-";
845 static const std::string CAPE_OVERLAY_SRC_SUFFIX = ".dtb";
846 static const std::string CAPE_OVERLAY_VARIANT_PREFIX = "fpp-cape-overlay-";
847 static const std::string CAPE_OVERLAY_VARIANT_SUFFIX = ".dtbo";
848
849 6 static const std::string& capeOverlayDir() {
850
6/12
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
26 static const std::string dir = file_exists("/boot/firmware/overlays") ? "/boot/firmware/overlays/" : "/boot/overlays/";
851 6 return dir;
852 }
853
854 // config.txt filter names are [pi5], [board-type=0x10], [HDMI:1] and similar. This
855 // is deliberately a strict allowlist rather than a blocklist: the name arrives from
856 // an EEPROM as part of a filename and is written verbatim into config.txt and into a
857 // path under /boot, so a newline or a slash in it would be a way to inject boot
858 // configuration or to write outside the overlays directory.
859 8 static bool isValidOverlayFilter(const std::string& f) {
860
3/6
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 8 times.
8 if (f.empty() || f.length() > 64) {
861 ✗ return false;
862 }
863
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 4 times.
40 for (char c : f) {
864
6/10
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 32 times.
✓ 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.
40 if (!(std::isalnum(static_cast<unsigned char>(c)) || c == '_' || c == '-' ||
865
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 c == '.' || c == '+' || c == ':' || c == '=')) {
866 4 return false;
867 }
868 }
869 4 return true;
870 }
871
872 // Rewrite (or drop) the managed block so it names exactly the given filters.
873 // Returns true if config.txt actually changed.
874 5 static bool writeCapeOverlayVariantBlock(const std::set<std::string>& filters) {
875
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 const std::string configFile = findBootConfigFile("config.txt");
876
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (configFile.empty()) {
877 ✗ return false;
878 }
879 5 int len = 0;
880
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 char* data = (char*)get_file_contents(configFile, len);
881
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
10 std::string orig(data, len);
882 5 free(data);
883
884
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 std::string current = orig;
885
6/6
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 2 times.
5 if (filters.empty() && current.find(CAPE_OVERLAY_VARIANT_BEGIN) == std::string::npos) {
886 // Nothing to write and nothing of ours to remove. Returning here rather
887 // than falling into the rewrite below is not an optimisation: the trailing
888 // newline normalisation would rewrite an untouched config.txt on any box
889 // whose file ends in a blank line, and a changed config.txt is what makes
890 // cape detection reboot. Every Pi with a cape that ships no variants would
891 // take one reboot for a cosmetic whitespace diff.
892 3 return false;
893 }
894 // Strip any block we wrote before. Tolerate a truncated block (BEGIN with no
895 // END, from an interrupted write) by cutting to end of file rather than leaving
896 // a marker behind that the next run would then nest a second block inside.
897 2 size_t begin = current.find(CAPE_OVERLAY_VARIANT_BEGIN);
898
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 while (begin != std::string::npos) {
899 1 size_t end = current.find(CAPE_OVERLAY_VARIANT_END, begin);
900
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
2 end = (end == std::string::npos) ? current.length()
901 1 : end + CAPE_OVERLAY_VARIANT_END.length();
902
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 if (end < current.length() && current[end] == '\n') {
903 1 ++end;
904 }
905
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 current.erase(begin, end - begin);
906 1 begin = current.find(CAPE_OVERLAY_VARIANT_BEGIN);
907 }
908
5/6
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 2 times.
5 while (!current.empty() && current.back() == '\n') {
909 3 current.pop_back();
910 }
911
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 current += "\n";
912
913
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (!filters.empty()) {
914
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 current += "\n" + CAPE_OVERLAY_VARIANT_BEGIN + "\n";
915
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (const auto& f : filters) {
916
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 current += "[" + f + "]\n";
917
4/8
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ 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 not taken.
1 current += "dtoverlay=" + CAPE_OVERLAY_VARIANT_PREFIX + f + "\n";
918 }
919
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 current += "[all]\n";
920
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 current += CAPE_OVERLAY_VARIANT_END + "\n";
921 }
922
923
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (current == orig) {
924 ✗ return false;
925 }
926
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 put_file_contents(configFile, (const uint8_t*)current.c_str(), current.length());
927 2 return true;
928 5 }
929
930 // Install the board specific overlays this cape ships and remove the ones it does
931 // not, then point config.txt at exactly that set. srcDir empty means "no cape",
932 // which clears everything.
933 // The variants a cape ships, as filter name -> source path. srcDir empty or
934 // missing means "no cape", which is a legitimate answer of none.
935 20 static std::map<std::string, std::string> collectCapeOverlayVariants(const std::string& srcDir) {
936 20 std::map<std::string, std::string> variants;
937
5/6
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 18 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 18 times.
20 if (srcDir.empty() || !file_exists(srcDir)) {
938 2 return variants;
939 }
940 18 std::error_code ec;
941
5/8
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 28 times.
✓ Branch 7 taken 18 times.
46 for (const auto& entry : std::filesystem::directory_iterator(srcDir, ec)) {
942
2/4
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
28 const std::string name = entry.path().filename().string();
943 28 if (name.rfind(CAPE_OVERLAY_SRC_PREFIX, 0) != 0 ||
944
5/6
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 20 times.
✓ Branch 5 taken 8 times.
36 name.length() <= CAPE_OVERLAY_SRC_PREFIX.length() + CAPE_OVERLAY_SRC_SUFFIX.length() ||
945
2/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
8 name.compare(name.length() - CAPE_OVERLAY_SRC_SUFFIX.length(),
946 CAPE_OVERLAY_SRC_SUFFIX.length(), CAPE_OVERLAY_SRC_SUFFIX) != 0) {
947 20 continue;
948 }
949 const std::string filter = name.substr(CAPE_OVERLAY_SRC_PREFIX.length(),
950
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 name.length() - CAPE_OVERLAY_SRC_PREFIX.length() - CAPE_OVERLAY_SRC_SUFFIX.length());
951
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
8 if (!isValidOverlayFilter(filter)) {
952
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 printf("CapeUtils: ignoring cape overlay variant with invalid filter name: %s\n", name.c_str());
953 4 continue;
954 }
955 // "default" is the stock overlay this code falls back to, and "all" would
956 // just be the base overlay under another name. Neither may be a variant.
957
7/10
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
✓ Branch 8 taken 2 times.
✓ Branch 9 taken 2 times.
4 if (filter == "default" || filter == "all") {
958
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 printf("CapeUtils: ignoring reserved cape overlay variant: %s\n", name.c_str());
959 2 continue;
960 }
961
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 variants[filter] = entry.path().string();
962
4/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 26 times.
52 }
963 18 return variants;
964 ✗ }
965
966 5 static bool syncCapeOverlayVariants(const std::string& srcDir) {
967 5 std::set<std::string> filters;
968 5 bool changed = false;
969
970
3/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 5 times.
6 for (const auto& [filter, src] : collectCapeOverlayVariants(srcDir)) {
971
4/8
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ 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 not taken.
1 const std::string target = capeOverlayDir() + CAPE_OVERLAY_VARIANT_PREFIX + filter + CAPE_OVERLAY_VARIANT_SUFFIX;
972
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 filters.insert(filter);
973
974 1 int slen = 0;
975 1 int tlen = 0;
976
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 uint8_t* sd = get_file_contents(src, slen);
977
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 uint8_t* td = file_exists(target) ? get_file_contents(target, tlen) : nullptr;
978
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 if (td == nullptr || slen != tlen || memcmp(sd, td, slen) != 0) {
979
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 printf("Installing cape overlay variant [%s]\n", filter.c_str());
980
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 copyFile(src, target);
981 1 changed = true;
982 }
983 1 free(sd);
984 1 free(td);
985 6 }
986
987 // Drop variants left behind by a previous cape. Only files matching the name we
988 // generate are considered, and the shipped default is never one of them.
989 5 std::error_code ec;
990
6/10
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 12 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 12 times.
✓ Branch 9 taken 5 times.
17 for (const auto& entry : std::filesystem::directory_iterator(capeOverlayDir(), ec)) {
991
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 const std::string name = entry.path().filename().string();
992 12 if (name.rfind(CAPE_OVERLAY_VARIANT_PREFIX, 0) != 0 ||
993
5/6
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 7 times.
19 name.length() <= CAPE_OVERLAY_VARIANT_PREFIX.length() + CAPE_OVERLAY_VARIANT_SUFFIX.length() ||
994
2/4
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
7 name.compare(name.length() - CAPE_OVERLAY_VARIANT_SUFFIX.length(),
995 CAPE_OVERLAY_VARIANT_SUFFIX.length(), CAPE_OVERLAY_VARIANT_SUFFIX) != 0) {
996 5 continue;
997 }
998 const std::string filter = name.substr(CAPE_OVERLAY_VARIANT_PREFIX.length(),
999
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 name.length() - CAPE_OVERLAY_VARIANT_PREFIX.length() - CAPE_OVERLAY_VARIANT_SUFFIX.length());
1000
8/10
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 6 times.
✓ Branch 9 taken 1 times.
7 if (filter == "default" || filters.find(filter) != filters.end()) {
1001 6 continue;
1002 }
1003
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 printf("Removing stale cape overlay variant [%s]\n", filter.c_str());
1004
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 unlink(entry.path().string().c_str());
1005 1 changed = true;
1006
4/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 11 times.
23 }
1007
1008 // config.txt is the authority on what is actually applied, so a change here
1009 // counts even when every .dtbo on disk already happened to be correct.
1010
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 changed |= writeCapeOverlayVariantBlock(filters);
1011 5 return changed;
1012 5 }
1013 #endif
1014
1015 // Whether two files both exist and hold identical bytes. Overlays are a couple
1016 // of KB, so comparing them outright is simpler than tracking versions.
1017 48 static bool filesIdentical(const std::string& a, const std::string& b) {
1018 48 int alen = 0;
1019 48 int blen = 0;
1020
3/4
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 38 times.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
48 uint8_t* ad = file_exists(a) ? get_file_contents(a, alen) : nullptr;
1021
3/4
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 38 times.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
48 uint8_t* bd = file_exists(b) ? get_file_contents(b, blen) : nullptr;
1022
6/8
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 38 times.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 7 times.
✗ Branch 7 not taken.
48 bool same = ad != nullptr && bd != nullptr && alen == blen && memcmp(ad, bd, alen) == 0;
1023 48 free(ad);
1024 48 free(bd);
1025 48 return same;
1026 }
1027
1028 2 static void restoreDefaultCapeOverlay() {
1029 #if defined(PLATFORM_BB64)
1030 ✗ copyFile("/boot/firmware/overlays/fpp-cape-overlay-default.dtb", "/boot/firmware/overlays/fpp-cape-overlay.dtb");
1031 #elif defined(PLATFORM_PI)
1032
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.
8 copyFile("/boot/firmware/overlays/fpp-cape-overlay-default.dtbo", "/boot/firmware/overlays/fpp-cape-overlay.dtbo");
1033 // Back to the stock overlay means back to no variants; leaving a [pi5] line
1034 // pointing at the last cape's .dtbo would fail to resolve on the next boot.
1035
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 syncCapeOverlayVariants("");
1036 #elif defined(PLATFORM_BBB)
1037 ✗ copyFile("/lib/firmware/fpp-cape-overlay-default.dtb", "/lib/firmware/fpp-cape-overlay.dtb");
1038 #endif
1039 2 }
1040 49 static bool handleCapeOverlay(const std::string& outputPath) {
1041 #ifdef PLATFORM_BB64
1042 ✗ static const std::string src = outputPath + "/tmp/fpp-cape-overlay-bb64.dtb";
1043 ✗ static const std::string target = "/boot/firmware/overlays/fpp-cape-overlay.dtb";
1044 ✗ static const std::string defaultOverlay = "/boot/firmware/overlays/fpp-cape-overlay-default.dtb";
1045 #elif defined(PLATFORM_BBB)
1046
3/8
✓ 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.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
3 static const std::string src = outputPath + "/tmp/fpp-cape-overlay-bbb.dtb";
1047
3/8
✓ 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.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
9 static const std::string target = "/lib/firmware/fpp-cape-overlay.dtb";
1048
3/8
✓ 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.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
9 static const std::string defaultOverlay = "/lib/firmware/fpp-cape-overlay-default.dtb";
1049 #elif defined(PLATFORM_PI)
1050
3/8
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
16 static const std::string src = outputPath + "/tmp/fpp-cape-overlay-rpi.dtb";
1051
3/8
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
48 static const std::string target = "/boot/firmware/overlays/fpp-cape-overlay.dtbo";
1052
3/8
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
48 static const std::string defaultOverlay = "/boot/firmware/overlays/fpp-cape-overlay-default.dtbo";
1053 // FPP 9.x has the param in config.txt set wrong for the RPi, so we need to check for that and if it's wrong then we need to flip it to the correct one
1054
2/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
16 const std::string configFile = findBootConfigFile("config.txt");
1055
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 if (!configFile.empty()) {
1056 16 int len = 0;
1057
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 char* data = (char*)get_file_contents(configFile, len);
1058
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
32 std::string configData(data, len);
1059 16 free(data);
1060
3/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 6 times.
32 if (!configContainsDirective(configData, "dtoverlay=fpp-cape-overlay")) {
1061 // not found, need to add it
1062
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 printf("Adding dtoverlay=fpp-cape-overlay to config.txt\n");
1063
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (configData.find("dtparam=fpp-cape-overlay") != std::string::npos) {
1064 // replace the wrong param with the right one
1065 ✗ size_t pos = configData.find("dtparam=fpp-cape-overlay");
1066 ✗ configData.replace(pos, strlen("dtparam=fpp-cape-overlay"), "dtoverlay=fpp-cape-overlay");
1067 } else {
1068 // [all] first: this appends at end of file, and whatever section
1069 // the file happens to end in is inherited. A config.txt whose
1070 // last section is a board filter would otherwise scope the cape's
1071 // own overlay to that board and leave every other Pi with no cape.
1072
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 configData += "\n[all]\ndtoverlay=fpp-cape-overlay\n";
1073 }
1074
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 times.
10 put_file_contents(configFile, (const uint8_t*)configData.c_str(), configData.size());
1075 }
1076 16 }
1077
1078
1079 #else
1080
2/4
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 static const std::string src = "";
1081
2/4
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 static const std::string target = "";
1082
2/4
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 static const std::string defaultOverlay = "";
1083 #endif
1084 48 bool changed = false;
1085
4/4
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 15 times.
48 bool haveBase = !src.empty() && file_exists(src);
1086 #if defined(PLATFORM_PI)
1087 // A cape whose overlay is entirely board specific ships variants and no
1088 // portable base. That is still a cape with an overlay, and must not fall
1089 // through to the "no cape, restore the default" branch below.
1090
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 const std::string variantDir = outputPath + "/tmp";
1091
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 const bool haveVariants = !collectCapeOverlayVariants(variantDir).empty();
1092 #else
1093 33 const bool haveVariants = false;
1094 #endif
1095
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 45 times.
48 if (haveBase) {
1096
3/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
3 if (!filesIdentical(src, target)) {
1097
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 copyFile(src, target);
1098 1 changed = true;
1099 }
1100
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 } else if (!haveVariants) {
1101 // The cape brings no overlay, so the shipped default belongs here. Ask
1102 // whether what is installed already IS the default by comparing the two
1103 // files (which also clears any variants the old cape installed).
1104 //
1105 // Not by reading /proc/device-tree/chosen/overlays/fpp-cape-overlay, as
1106 // this did before: that node exists only when an overlay actually
1107 // RESOLVED. A stale overlay left behind by a previous cape that cannot
1108 // resolve on this board -- a fragment referencing a Pi 5 only symbol, on
1109 // a Pi 4 -- creates no such node, so it was indistinguishable from "no
1110 // cape overlay applied" and the restore was skipped. The one state that
1111 // cannot recover by itself was the one state never repaired, on every
1112 // boot, indefinitely.
1113
8/8
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 32 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 41 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 10 times.
45 if (!filesIdentical(target, defaultOverlay) && file_exists(defaultOverlay)) {
1114
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 restoreDefaultCapeOverlay();
1115 2 changed = true;
1116 }
1117 }
1118 #if defined(PLATFORM_PI)
1119
3/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
15 if (haveBase || haveVariants) {
1120 // Board specific companions to the base overlay. Outside the compare
1121 // above because it has to run even when the base overlay is already up to
1122 // date -- the variants change independently of it. Skipped when neither
1123 // is present, since restoreDefaultCapeOverlay() has already cleared them.
1124
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 changed |= syncCapeOverlayVariants(variantDir);
1125 }
1126 #endif
1127 48 return changed;
1128 16 }
1129
1130 #ifdef PLATFORM_BBB
1131 58 bool isPocketBeagle() {
1132 58 bool ret = false;
1133 58 FILE* file = fopen("/proc/device-tree/model", "r");
1134
1/2
✓ Branch 0 taken 58 times.
✗ Branch 1 not taken.
58 if (file) {
1135 char buf[256];
1136
1/2
✓ Branch 0 taken 58 times.
✗ Branch 1 not taken.
58 fgets(buf, 256, file);
1137
1/2
✓ Branch 0 taken 58 times.
✗ Branch 1 not taken.
58 fclose(file);
1138
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 44 times.
58 if (strcmp(&buf[10], "PocketBeagle") == 0) {
1139 14 ret = true;
1140 }
1141 }
1142 58 return ret;
1143 }
1144 // Query a pin's current pinmux mode name via the pinctrl tool. Reads work on
1145 // both am335xpm and older /dev/mem kernels. Returns "" if the mode can't be
1146 // determined, which callers must treat as "don't touch this pin".
1147 //
1148 // Uses "-l" rather than "-q" because only "-l" marks the current mode
1149 // unambiguously, in brackets: "gpio gpio_out gpio_pd [gpio_pu] i2c ...".
1150 // "-q" prints the raw register value followed by *every* mode name that matches
1151 // it, so its trailing token is whichever alias happens to sort last -- P9_17 at
1152 // mux mode 2 comes back "i2c_scl", not "i2c", because both names map to the same
1153 // PIN_INPUT_PULLUP|2 -- and when no named mode matches the value at all, the last
1154 // token is a bare hex number that "-s" would just reject.
1155 62 static std::string PinCurrentMode(const std::string& pin) {
1156
2/4
✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 62 times.
124 if (!file_exists("/usr/bin/pinctrl")) {
1157 ✗ return "";
1158 }
1159 62 std::string out;
1160
3/6
✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 62 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 62 times.
✗ Branch 5 not taken.
62 std::string cmd = "/usr/bin/pinctrl -l " + CapeShellQuote(pin) + " 2>/dev/null";
1161
1/2
✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
62 FILE* p = popen(cmd.c_str(), "r");
1162
1/2
✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
62 if (p) {
1163 char buf[256];
1164
3/4
✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 62 times.
72 while (fgets(buf, sizeof(buf), p)) {
1165
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 out += buf;
1166 }
1167
1/2
✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
62 pclose(p);
1168 }
1169 62 size_t ob = out.find('[');
1170
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 10 times.
62 if (ob == std::string::npos) {
1171
1/2
✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
104 return "";
1172 }
1173 10 size_t cb = out.find(']', ob);
1174
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (cb == std::string::npos) {
1175 ✗ return "";
1176 }
1177
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 return out.substr(ob + 1, cb - ob - 1);
1178 62 }
1179 66 void ConfigurePin(const char* pin, const char* mode) {
1180 // Prefer the pinctrl tool: it writes via /dev/am335xpm on am335xpm kernels
1181 // (where direct userspace register writes are silently dropped) and via the
1182 // bone-pinmux-helper sysfs on older kernels. Fall back to the helper sysfs
1183 // directly only if the tool is absent (keeps pre-pinctrl images working).
1184
7/20
✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 66 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 66 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 66 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 66 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 66 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 66 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
198 if (file_exists("/usr/bin/pinctrl") && pin[0] == 'P' && (pin[2] == '_' || pin[2] == '-')) {
1185
7/14
✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 66 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 66 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 66 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 66 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 66 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 66 times.
✗ Branch 13 not taken.
198 std::string cmd = "/usr/bin/pinctrl -s " + CapeShellQuote(pin) + " " + CapeShellQuote(mode);
1186
3/4
✓ Branch 0 taken 66 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 52 times.
66 if (system(cmd.c_str()) == 0) {
1187 14 return;
1188 }
1189
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 14 times.
66 }
1190 char dir_name[255];
1191 52 snprintf(dir_name, sizeof(dir_name),
1192 "/sys/devices/platform/ocp/ocp:%s_pinmux/state",
1193 pin);
1194
1/2
✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
52 FILE* dir = fopen(dir_name, "w");
1195
1/2
✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
52 if (!dir) {
1196 52 return;
1197 }
1198 ✗ fprintf(dir, "%s\n", mode);
1199 ✗ fclose(dir);
1200 }
1201 #endif
1202 31 void ConfigureI2C1BusPins(bool enable) {
1203 #ifdef PLATFORM_BBB
1204 // The i2c1 pads are not dedicated to i2c -- on the BBB they are P9_17/P9_18,
1205 // which capes routinely use as plain GPIO (the HE123's OLED Back/Enter
1206 // buttons live there). So borrowing them for a probe has to be exactly
1207 // reversible, and there is no generic mode name that does that: the pinctrl
1208 // tool has no "default" (P9_17 offers gpio/gpio_out/gpio_pd/gpio_pu/i2c/
1209 // i2c_scl/spi/spi_cs0 plus "reset"), "reset" is bare PIN_INPUT|mode7 with no
1210 // pull, and BBBPinCapabilities maps "default" to a bias-less "gpio". Handing
1211 // back any of those turns a pulled-up button pin into a floating one. The
1212 // pin's real default is whatever the DT/overlay/cape put there, so capture
1213 // that exact mode and write the same string back. Mirrors what
1214 // findCapeEEPROM() already does for the i2c2 pads.
1215 //
1216 // Restoring only when the capture succeeded is deliberate: with no known-good
1217 // mode to return to, leaving the pad muxed to i2c is at least a state the
1218 // logs explain, whereas guessing puts a button pin somewhere nobody asked for.
1219
3/4
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
31 static std::string origMode1;
1220
3/4
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
31 static std::string origMode2;
1221
1222 31 const bool pb = isPocketBeagle();
1223
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 29 times.
31 const char* pin1 = pb ? "P2_09" : "P9_17";
1224
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 29 times.
31 const char* pin2 = pb ? "P2_11" : "P9_18";
1225
1226
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 15 times.
31 if (enable) {
1227
2/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
32 origMode1 = PinCurrentMode(pin1);
1228
2/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
16 origMode2 = PinCurrentMode(pin2);
1229 16 ConfigurePin(pin1, "i2c");
1230 16 ConfigurePin(pin2, "i2c");
1231 } else {
1232
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 13 times.
15 if (!origMode1.empty()) {
1233 2 ConfigurePin(pin1, origMode1.c_str());
1234 } else {
1235 13 printf("Could not read the original mode of %s; leaving it muxed for i2c1\n", pin1);
1236 }
1237
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 13 times.
15 if (!origMode2.empty()) {
1238 2 ConfigurePin(pin2, origMode2.c_str());
1239 } else {
1240 13 printf("Could not read the original mode of %s; leaving it muxed for i2c1\n", pin2);
1241 }
1242 15 origMode1.clear();
1243 15 origMode2.clear();
1244 }
1245 #endif
1246 31 }
1247
1248 // Kernel module names change over time, but a cape EEPROM is programmed once and
1249 // is then in the field forever, so an older EEPROM can name a module the running
1250 // kernel no longer has. Map the old name onto the current one. The map is only
1251 // consulted when the name from the EEPROM does not resolve on this kernel, so a
1252 // kernel that still has the old name keeps using it.
1253 // Note: modprobe treats '-' and '_' as equivalent, so only real renames belong here.
1254 static const std::map<std::string, std::string> LEGACY_MODULE_NAME_MAP = {
1255 // ASoC codec drivers gained the snd_soc_ prefix
1256 { "pcm5102a", "snd_soc_pcm5102a" }
1257 };
1258
1259 5 static bool moduleNameResolves(const std::string& module) {
1260
3/6
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
5 std::string cmd = "/sbin/modprobe -n -q " + QuotedModuleSpec(module) + " > /dev/null 2>&1";
1261
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
10 return system(cmd.c_str()) == 0;
1262 5 }
1263
1264 // modprobe every module the cape asks for, mapping any renamed-since-programmed
1265 // names to what this kernel calls them. Loading these at detect time rather than
1266 // letting them autoload keeps a cape's sound card/RTC/GPIO expander from showing
1267 // up after the rest of boot has already given up on it.
1268 3 static void loadModules(const Json::Value& modules) {
1269
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3 times.
7 for (int x = 0; x < modules.size(); x++) {
1270
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 std::string module = modules[x].asString();
1271
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (module.empty()) {
1272 ✗ continue;
1273 }
1274
3/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
4 if (!moduleNameResolves(module)) {
1275
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 auto it = LEGACY_MODULE_NAME_MAP.find(module);
1276
6/8
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 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 2 times.
3 if (it != LEGACY_MODULE_NAME_MAP.end() && moduleNameResolves(it->second)) {
1277
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 printf("Cape module \"%s\" not found, loading \"%s\" instead\n", module.c_str(), it->second.c_str());
1278
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 module = it->second;
1279 } else {
1280
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 printf("Cape module \"%s\" is not available on this kernel\n", module.c_str());
1281 2 continue;
1282 }
1283 }
1284
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 taken 2 times.
✗ Branch 7 not taken.
2 exec("/sbin/modprobe " + QuotedModuleSpec(module) + " 2> /dev/null > /dev/null");
1285
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 }
1286 3 }
1287
1288 static std::map<std::string, std::string> CONFIG_EEPROM_UPGRADE_MAP = {
1289 { "RPIWS281X:PiHat", "PiHat" },
1290 { "spixels:spixels", "spixels" },
1291 { "RPIWS281X:rPi-MFC", "rPi-MFC" },
1292 { "RPIWS281X:rPi-28D", "rPi-28D" },
1293 // The RPIWS281X entries above are only matched until the config is migrated to DPI.
1294 // These map the migrated configs back to the same EEPROMs so a cape that loses its
1295 // config/cape-eeprom.bin after migrating can still have it reinstalled.
1296 { "DPIPixels:PiHat-DPIPixels", "PiHat" },
1297 { "DPIPixels:rPi-MFC-DPIPixels", "rPi-MFC" },
1298 { "DPIPixels:rPi-28D-DPIPixels", "rPi-28D" },
1299 { "DPIPixels:rPi-28D-DPIPixels-4", "rPi-28D" },
1300 { "BBB48String:F8-B", "F8-B" },
1301 { "BBB48String:F8-B-16", "F8-B" },
1302 { "BBB48String:F8-B-20", "F8-B" },
1303 { "BBB48String:F8-B-EXP", "F8-B" },
1304 { "BBB48String:F8-B-EXP-32", "F8-B" },
1305 { "BBB48String:F8-B-EXP-36", "F8-B" },
1306 { "BBB48String:F16-B", "F16-B" },
1307 { "BBB48String:F16-B-32", "F16-B" },
1308 { "BBB48String:F16-B-48", "F16-B" },
1309 { "BBB48String:F4-B", "F4-B" },
1310 { "BBB48String:F32-B", "F-32" },
1311 { "BBB48String:F32-B-44", "F-32" },
1312 { "BBB48String:F32-B-48", "F-32" },
1313 { "BBB48String:RGBCape24", "RGB-123" },
1314 { "BBB48String:RGBCape48A", "RGB-123" },
1315 { "BBB48String:RGBCape48C", "RGB-123" },
1316 { "BBB48String:RGBCape48F", "RGB-123" },
1317 { "BBB48String:PB16", "PB16" },
1318 { "BBB48String:PB16-EXP", "PB16" }
1319 };
1320
1321 131 static const char* capeStatusName(CapeUtils::CapeStatus st) {
1322
3/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 116 times.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
131 switch (st) {
1323 ✗ case CapeUtils::CapeStatus::NOT_PRESENT:
1324 ✗ return "NOT_PRESENT";
1325 ✗ case CapeUtils::CapeStatus::CORRUPT:
1326 ✗ return "CORRUPT";
1327 116 case CapeUtils::CapeStatus::UNSIGNED:
1328 116 return "UNSIGNED";
1329 13 case CapeUtils::CapeStatus::SIGNED_GENERIC:
1330 13 return "SIGNED_GENERIC";
1331 2 case CapeUtils::CapeStatus::SIGNED:
1332 2 return "SIGNED";
1333 }
1334 ✗ return "UNKNOWN";
1335 }
1336
1337 // ---------------------------------------------------------------------------
1338 // Privacy jurisdiction
1339 //
1340 // A cape may set a default where the user has not chosen one, but it may only
1341 // move a *telemetry* setting toward transmitting more where the jurisdiction
1342 // permits a transmitting default at all. Which jurisdictions those are lives in
1343 // etc/jurisdictions.json, read by both this and the PHP side, so the policy is
1344 // stated once rather than re-listed per consumer.
1345 //
1346 // Everything here fails toward the permissive side ONLY on a positive match.
1347 // An unreadable file, an unknown jurisdiction, or a value nobody has set yet
1348 // resolves through inferJurisdiction()/priorOptInRequired() rather than by
1349 // assuming.
1350 // ---------------------------------------------------------------------------
1351 static const std::string JURISDICTIONS_FILE = "/opt/fpp/etc/jurisdictions.json";
1352
1353 // Value of a key in /home/fpp/media/settings, or "" when absent.
1354 // Matches on the exact key rather than a prefix: "statsPublish" must not be
1355 // satisfied by a line for "statsPublishUrl".
1356 68 static std::string readSettingValue(const std::vector<std::string>& lines, const std::string& key) {
1357
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 52 times.
78 for (const auto& line : lines) {
1358 26 std::size_t eq = line.find('=');
1359
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 if (eq == std::string::npos) {
1360 10 continue;
1361 }
1362
2/4
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
26 std::string k = trim(line.substr(0, eq));
1363
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 16 times.
26 if (k != key) {
1364 10 continue;
1365 }
1366
2/6
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
16 std::string v = trim(line.substr(eq + 1));
1367
4/8
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 16 times.
✗ Branch 7 not taken.
16 if (v.size() >= 2 && v.front() == '"' && v.back() == '"') {
1368
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 v = v.substr(1, v.size() - 2);
1369 }
1370
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 return v;
1371
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 16 times.
26 }
1372
1/2
✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
104 return "";
1373 }
1374
1375 // True when the key already has a value in the settings file. A cape never
1376 // overrides one, whatever it is and whatever the jurisdiction.
1377 29 static bool settingIsSet(const std::vector<std::string>& lines, const std::string& key) {
1378
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 24 times.
53 for (const auto& line : lines) {
1379 29 std::size_t eq = line.find('=');
1380
9/18
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 29 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✓ Branch 7 taken 24 times.
✓ Branch 8 taken 29 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 29 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 5 times.
✓ Branch 13 taken 24 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
29 if (eq != std::string::npos && trim(line.substr(0, eq)) == key) {
1381 5 return true;
1382 }
1383 }
1384 24 return false;
1385 }
1386
1387 // The jurisdiction to judge by: the recorded answer, or -- when it has never
1388 // been answered -- an inference from the time zone, which every device already
1389 // has. The inference is used for this decision only and is never written back;
1390 // a real answer always wins. Longest prefix wins so Europe/London reads as UK
1391 // rather than falling into the shorter Europe/ entry.
1392 36 static std::string inferJurisdiction(const std::vector<std::string>& lines, const Json::Value& cfg) {
1393
2/4
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 36 times.
✗ Branch 3 not taken.
36 std::string recorded = readSettingValue(lines, "LegalJurisdiction");
1394
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 27 times.
36 if (!recorded.empty()) {
1395 9 return recorded;
1396 }
1397
2/4
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
27 std::string tz = readSettingValue(lines, "TimeZone");
1398
6/8
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 25 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 25 times.
✓ Branch 7 taken 2 times.
27 if (tz.empty() || !cfg.isMember("timezonePrefix")) {
1399
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
50 return "";
1400 }
1401 2 std::string best;
1402 2 std::string bestPrefix;
1403
4/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 28 times.
✓ Branch 5 taken 2 times.
30 for (const auto& prefix : cfg["timezonePrefix"].getMemberNames()) {
1404
5/6
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 26 times.
28 if (tz.rfind(prefix, 0) == 0 && prefix.size() > bestPrefix.size()) {
1405
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 bestPrefix = prefix;
1406
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 best = cfg["timezonePrefix"][prefix].asString();
1407 }
1408 2 }
1409 2 return best;
1410 36 }
1411
1412 // Set only for a dry run, so the wizard can ask "what if the answer were X"
1413 // before the answer has been written anywhere.
1414 static std::string g_jurisdictionOverride;
1415
1416 38 static bool priorOptInRequired(const std::vector<std::string>& lines) {
1417
1/2
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
38 Json::Value cfg;
1418
2/4
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 38 times.
38 if (!LoadJsonFromFile(JURISDICTIONS_FILE, cfg, CapeJsonRoot::Object)) {
1419 // No policy file: take the cautious branch. Refusing a permissive cape
1420 // default is recoverable -- the user can still set it -- where wrongly
1421 // allowing one is not.
1422 ✗ printf("CapeUtils: %s unreadable, treating jurisdiction as requiring prior opt-in\n",
1423 JURISDICTIONS_FILE.c_str());
1424 ✗ return true;
1425 }
1426
3/6
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 38 times.
✗ Branch 5 not taken.
38 bool dflt = cfg.get("priorOptInDefault", false).asBool();
1427
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 2 times.
38 std::string j = g_jurisdictionOverride.empty() ? inferJurisdiction(lines, cfg)
1428
2/4
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
38 : g_jurisdictionOverride;
1429
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 13 times.
38 if (j.empty()) {
1430 // Nothing recorded and nothing inferable: we do not know where this box
1431 // is. That is the same state as an unreadable policy file above, and it
1432 // gets the same cautious answer.
1433 //
1434 // It is NOT the same as priorOptInDefault, which answers a different
1435 // question -- what an unrecognised jurisdiction VALUE means -- and is
1436 // false on purpose, so that the strict path is taken on a positive match
1437 // rather than by accident. Conflating the two made a fresh box
1438 // permissive, because nothing writes TimeZone into the settings file:
1439 // FPPINIT only reads it and the setup wizard is the first writer, so on
1440 // first boot both keys are absent and detection ran before anyone had
1441 // been asked anything. The PHP half disagreed, reading TimeZone from
1442 // $settings where settings.json's Europe/London default had been merged
1443 // in -- so the two halves of one policy answered differently on the same
1444 // box, which is precisely what sharing jurisdictions.json was meant to
1445 // prevent.
1446 //
1447 // The cost is bounded and is what the design always said should happen:
1448 // a cape's transmitting default is HELD on first boot and offered again
1449 // by the wizard once the user says where they are.
1450 25 return true;
1451 }
1452
3/6
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 13 times.
13 if (!cfg["jurisdictions"].isMember(j)) {
1453 ✗ return dflt;
1454 }
1455
5/10
✓ 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 13 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 13 times.
✗ Branch 9 not taken.
13 return cfg["jurisdictions"][j].get("priorOptIn", dflt).asBool();
1456 38 }
1457
1458 // Rank of a settings value on the "how much does this transmit" scale, from the
1459 // transmitRank map declared beside the setting in www/settings.json. Returns
1460 // false for anything unranked -- FetchVendorLogos has no rank because it is a
1461 // remote image rather than telemetry, and unranked keys are not subject to the
1462 // ratchet at all.
1463 25 static const Json::Value& settingMeta(const std::string& key) {
1464
4/8
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
25 static Json::Value settings;
1465 static bool loaded = false;
1466
4/8
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
25 static const Json::Value none;
1467
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 7 times.
25 if (!loaded) {
1468 18 loaded = true;
1469
2/4
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
36 LoadJsonFromFile("/opt/fpp/www/settings.json", settings, CapeJsonRoot::Object);
1470 }
1471
5/6
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 15 times.
✓ Branch 4 taken 10 times.
✓ Branch 5 taken 15 times.
25 if (!settings.isMember("settings") || !settings["settings"].isMember(key)) {
1472 10 return none;
1473 }
1474 15 return settings["settings"][key];
1475 }
1476
1477 // A setting governs what the device discloses if it declares a transmitRank map --
1478 // that map exists precisely to say how much each of its values transmits.
1479 ✗ static bool settingIsPrivacyGoverned(const std::string& key) {
1480 ✗ return settingMeta(key).isMember("transmitRank");
1481 }
1482
1483 25 static bool transmitRank(const std::string& key, const std::string& value, int& rank) {
1484 25 const Json::Value& meta = settingMeta(key);
1485
5/6
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 14 times.
✓ Branch 4 taken 11 times.
✓ Branch 5 taken 14 times.
25 if (!meta.isMember("transmitRank") || !meta["transmitRank"].isMember(value)) {
1486 11 return false;
1487 }
1488 14 rank = meta["transmitRank"][value].asInt();
1489 14 return true;
1490 }
1491
1492 // May a cape set `key` to `value`?
1493 // - never, if the user has already set it
1494 // - always, if the key is not ranked telemetry
1495 // - otherwise only where the jurisdiction permits a transmitting default, or
1496 // where the proposed value transmits no more than the shipped default
1497 // Three outcomes, not two. "Already set to something else" is worth saying out
1498 // loud -- the cape wanted one thing and the box has another. "Already set to
1499 // exactly this" is not: there is no conflict, nothing to change, and warning
1500 // about it just trains people to ignore the log.
1501 enum class CapeSettingAction {
1502 Apply,
1503 SkipUnchanged,
1504 Refuse
1505 };
1506
1507 // Whether a signed cape may retire a setting outright.
1508 //
1509 // This deliberately does NOT ask whether the value is already set. A removal's
1510 // precondition is that it IS set -- routing removes through the never-override rule
1511 // that guards defaultSettings made every removal of a real value refuse, which is
1512 // every removal that could have done anything, so removeSettings could only ever
1513 // clear a key whose value was the empty string. The signature the caller has
1514 // already checked is what authorizes a removal; that is the whole reason removes
1515 // require one and defaults do not.
1516 //
1517 // What a cape may not retire is a setting that governs what the device discloses.
1518 // Removing one restores FPP's shipped default, and those defaults transmit -- so a
1519 // cape could raise disclosure simply by deleting the user's answer. Whether it
1520 // would happen to raise it on this box is not the test: a cape has no business
1521 // deciding that question at all.
1522 ✗ static bool capeMayRemoveSetting(const std::string& key, std::string& why) {
1523 ✗ if (settingIsPrivacyGoverned(key)) {
1524 ✗ why = "it controls what this device transmits";
1525 ✗ return false;
1526 }
1527 ✗ return true;
1528 }
1529
1530 32 static CapeSettingAction capeMaySetSetting(const std::vector<std::string>& lines, const std::string& key,
1531 const std::string& value, bool strict, std::string& why,
1532 bool userRequestedReset = false) {
1533 // Every setting is one `key = "value"` line, written as is. A line break
1534 // in either half would add lines of the cape's choosing -- any key, past
1535 // every rule below, SigningAPIHost included -- and a quote or `=` in the
1536 // key would make it read back as some other key. The settings API and
1537 // fppd's own writer refuse these too; this is the cape's writer.
1538
3/4
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
32 static const std::string kLineBreaks("\r\n\0", 3);
1539
9/14
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 31 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 30 times.
✓ Branch 8 taken 32 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✓ Branch 11 taken 30 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
63 if (key.empty() || key.find_first_of(kLineBreaks + "\"=") != std::string::npos ||
1540 31 value.find_first_of(kLineBreaks) != std::string::npos) {
1541
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 why = "it would not stay a single settings line";
1542 2 return CapeSettingAction::Refuse;
1543 }
1544 // Settings no cape sets, signed or not, and however it asks: they decide
1545 // where this device sends secrets. SigningAPIHost is where the cape pages
1546 // send the SoC serial, the order ID and the FalconPlayer account password
1547 // (voucher redeem); unset by default, so an unsigned cape could point it
1548 // at its own server.
1549
4/8
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
30 static const std::set<std::string> kCapeNeverSets = { "SigningAPIHost" };
1550
3/4
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 29 times.
30 if (kCapeNeverSets.count(key)) {
1551
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 why = "it decides where this device sends its serial and account credentials";
1552 1 return CapeSettingAction::Refuse;
1553 }
1554 // Never-override protects the user from the CAPE deciding. It is not meant to
1555 // stop the user deciding: `fppcapedetect -force-defaults` is what the "reset
1556 // to defaults" action in the firmware-upgrade UI passes, so there the
1557 // overwrite is the thing that was asked for. Different actors, different
1558 // answers.
1559 //
1560 // The telemetry ratchet below still applies either way. Asking to reset
1561 // defaults is not a jurisdiction changing, and Art 25(2) does not bend
1562 // because somebody clicked a button.
1563
3/4
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 24 times.
29 if (settingIsSet(lines, key)) {
1564
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
5 if (readSettingValue(lines, key) == value) {
1565 // Nothing to do and nothing to report, whether or not a reset was
1566 // asked for -- rewriting a value with itself would only mark the
1567 // settings file dirty and cause a needless write.
1568 ✗ return CapeSettingAction::SkipUnchanged;
1569 }
1570
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
5 if (!userRequestedReset) {
1571
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 why = "the user has set it to something else";
1572 4 return CapeSettingAction::Refuse;
1573 }
1574 }
1575 25 int proposed = 0;
1576
3/4
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 14 times.
25 if (!transmitRank(key, value, proposed)) {
1577 11 return CapeSettingAction::Apply;
1578 }
1579
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 10 times.
14 if (!strict) {
1580 4 return CapeSettingAction::Apply;
1581 }
1582 // Under a prior-opt-in regime the only default a cape may set is one that
1583 // transmits nothing: rank 0.
1584 //
1585 // Anchoring this to FPP's *shipped* default instead was the obvious reading
1586 // and it is wrong. When that default was "3", the maximum-disclosure value,
1587 // a cape could have set full crash reporting under EU rules simply because
1588 // the baseline was already maximal. The unanswered default is 1 now, which
1589 // makes the example less alarming and changes nothing about the rule: a
1590 // ratchet against a baseline inherits whatever that baseline is, and 1 still
1591 // transmits. Art 25(2) asks for the protective value, not the incumbent one.
1592
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 times.
10 if (proposed > 0) {
1593 // Phrased without a subject: this reaches the boot log one setting at a
1594 // time, and the setup wizard groups several settings under one reason.
1595 // "it transmits" read wrong against a list of three.
1596
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 why = "this jurisdiction requires prior opt-in before a device may transmit";
1597 9 return CapeSettingAction::Refuse;
1598 }
1599 1 return CapeSettingAction::Apply;
1600 }
1601
1602 class CapeInfo {
1603 public:
1604 159 CapeInfo(bool ro, bool fd = false) :
1605 159 readOnly(ro),
1606 159 forceDefaults(fd),
1607
1/2
✓ Branch 0 taken 159 times.
✗ Branch 1 not taken.
159 bus(I2C_DEV) {
1608 159 char buf[256] = "/tmp/fppcuXXXXXX";
1609
1/2
✓ Branch 0 taken 159 times.
✗ Branch 1 not taken.
159 outputPath = mkdtemp(buf);
1610
1611
1/2
✓ Branch 0 taken 159 times.
✗ Branch 1 not taken.
159 std::string tmpPath = outputPath + "/tmp";
1612 159 mkdir(tmpPath.c_str(), 0700);
1613
1614
1/2
✓ Branch 0 taken 157 times.
✗ Branch 1 not taken.
159 findCapeEEPROM();
1615
3/4
✓ Branch 0 taken 157 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 144 times.
✓ Branch 3 taken 13 times.
157 if (EEPROM != "") {
1616 try {
1617
2/2
✓ Branch 0 taken 139 times.
✓ Branch 1 taken 5 times.
144 parseEEPROM();
1618
2/2
✓ Branch 0 taken 132 times.
✓ Branch 1 taken 1 times.
139 processEEPROM();
1619
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 } catch (std::exception& ex) {
1620 6 corruptEEPROM = true;
1621
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 printf("Corrupt EEPROM: %s\n", ex.what());
1622 // nothing from a corrupt image is published: files extracted
1623 // before the failure can be truncated (an EEPROM ending inside
1624 // a section, or a partly written one), and a truncated
1625 // cape-image.xbm takes down fppoled
1626
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 std::filesystem::remove_all(outputPath);
1627 6 mkdir(outputPath.c_str(), 0700);
1628 6 mkdir(tmpPath.c_str(), 0700);
1629 6 }
1630 }
1631
1/2
✓ Branch 0 taken 151 times.
✗ Branch 1 not taken.
151 loadFiles();
1632
2/4
✓ Branch 0 taken 151 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 151 times.
✗ Branch 3 not taken.
151 std::filesystem::remove_all(outputPath);
1633 151 }
1634
1635 53 ~CapeInfo() {
1636 53 }
1637
1638 292 CapeUtils::CapeStatus capeStatus() {
1639
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 286 times.
292 if (corruptEEPROM) {
1640 6 return CapeUtils::CapeStatus::CORRUPT;
1641 }
1642
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 253 times.
286 if (validSignature) {
1643
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 27 times.
33 if (ORIGEEPROM.find("sys/bus/i2c") != std::string::npos) {
1644 6 return CapeUtils::CapeStatus::SIGNED;
1645
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 } else if (devsn != "") {
1646 ✗ return CapeUtils::CapeStatus::SIGNED;
1647 }
1648 27 return CapeUtils::CapeStatus::SIGNED_GENERIC;
1649 }
1650
5/6
✓ Branch 0 taken 253 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 240 times.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 240 times.
✓ Branch 5 taken 13 times.
253 if (!validSignature && EEPROM != "") {
1651 240 return CapeUtils::CapeStatus::UNSIGNED;
1652 }
1653 13 return CapeUtils::CapeStatus::NOT_PRESENT;
1654 }
1655
1656 137 bool hasFile(const std::string& path) {
1657
1/2
✓ Branch 0 taken 137 times.
✗ Branch 1 not taken.
137 return fileMap.find(path) != fileMap.end();
1658 }
1659 ✗ const std::vector<uint8_t>& getFile(const std::string& path) {
1660 ✗ static std::vector<uint8_t> EMPTY;
1661 ✗ if (hasFile(path)) {
1662 ✗ return fileMap[path];
1663 }
1664 ✗ return EMPTY;
1665 }
1666
1667 119 int getLicensedOutputs() {
1668
6/8
✓ Branch 0 taken 119 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 105 times.
✓ Branch 4 taken 14 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 14 times.
✓ Branch 7 taken 105 times.
119 if (hasSignature && validSignature && (fkeyId != "")) {
1669
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 if (fkeyId != "fp") {
1670 14 return 9999;
1671 ✗ } else if (signedExtras.find("licensePorts") != signedExtras.end()) {
1672 ✗ return atoi(signedExtras["licensePorts"][0].c_str());
1673 }
1674 }
1675 105 return 0;
1676 }
1677 ✗ const std::string& getKeyId() const {
1678 ✗ return fkeyId;
1679 }
1680 19 const Json::Value& getCapeInfo() const {
1681 19 return capeInfo;
1682 }
1683
1684 private:
1685 151 void loadFiles() {
1686 151 std::vector<std::string> files;
1687
2/2
✓ Branch 0 taken 47 times.
✓ Branch 1 taken 104 times.
151 if (!readOnly) {
1688
3/6
✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 47 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 47 times.
✗ Branch 5 not taken.
69 getFileList(outputPath + "/tmp", "", files);
1689
3/4
✓ Branch 0 taken 95 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 95 times.
✓ Branch 3 taken 47 times.
142 for (auto a : files) {
1690
2/4
✓ Branch 0 taken 95 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 95 times.
✗ Branch 3 not taken.
95 std::string src = outputPath + "/tmp" + a;
1691
1/2
✓ Branch 0 taken 95 times.
✗ Branch 1 not taken.
95 std::string target = "/home/fpp/media/tmp" + a;
1692
1693
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 39 times.
95 if (target[target.length() - 1] == '/') {
1694 56 mkdir(target.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
1695
1/2
✓ Branch 0 taken 56 times.
✗ Branch 1 not taken.
56 setOwnerGroup(target);
1696 } else {
1697
1/2
✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
39 copyFile(src, target);
1698
1/2
✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
39 setFilePerms(target);
1699 }
1700 95 }
1701 47 files.clear();
1702 }
1703
2/4
✓ Branch 0 taken 151 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 151 times.
✗ Branch 3 not taken.
262 getFileList(outputPath, "", files);
1704
3/4
✓ Branch 0 taken 1231 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1231 times.
✓ Branch 3 taken 151 times.
1382 for (auto a : files) {
1705
1/2
✓ Branch 0 taken 1231 times.
✗ Branch 1 not taken.
1231 std::string src = outputPath + a;
1706
1707
2/2
✓ Branch 0 taken 665 times.
✓ Branch 1 taken 566 times.
1231 if (a[a.length() - 1] != '/') {
1708 665 int len = fileSize(src);
1709
2/4
✓ Branch 0 taken 665 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 665 times.
✗ Branch 3 not taken.
665 FILE* f = len > 0 ? fopen(src.c_str(), "rb") : nullptr;
1710
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 665 times.
665 if (f == nullptr) {
1711 ✗ fileMap[a].clear();
1712 ✗ continue;
1713 }
1714
2/4
✓ Branch 0 taken 665 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 665 times.
✗ Branch 3 not taken.
665 fileMap[a].resize(len);
1715
2/4
✓ Branch 0 taken 665 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 665 times.
✗ Branch 3 not taken.
665 int l2 = fread(&fileMap[a][0], 1, len, f);
1716
1/2
✓ Branch 0 taken 665 times.
✗ Branch 1 not taken.
665 fclose(f);
1717 }
1718
2/4
✓ Branch 0 taken 1231 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1231 times.
✗ Branch 3 not taken.
1231 }
1719 151 }
1720
1721 159 void findCapeEEPROM() {
1722 // The cape EEPROM is normally on i2c2 (UART1_RTSN/CTSN). On am335xpm
1723 // kernels with the trimmed overlay, the bone-pinmux-helper no longer
1724 // muxes those pins to i2c2 at boot, so do it here. Query first so we can
1725 // restore the original mode if the EEPROM turns out not to be on i2c2.
1726 // No-op on older kernels (helper already set i2c2; query reports "i2c").
1727 #ifdef PLATFORM_BBB
1728 27 std::string i2c2_o1, i2c2_o2;
1729
3/4
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 12 times.
27 if (!isPocketBeagle()) {
1730
2/4
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
30 i2c2_o1 = PinCurrentMode("P9_19");
1731
2/4
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 i2c2_o2 = PinCurrentMode("P9_20");
1732
4/6
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 14 times.
✗ Branch 5 not taken.
15 if (i2c2_o1 != "i2c") ConfigurePin("P9_19", "i2c");
1733
4/6
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 14 times.
✗ Branch 5 not taken.
15 if (i2c2_o2 != "i2c") ConfigurePin("P9_20", "i2c");
1734 }
1735 #endif
1736
1/2
✓ Branch 0 taken 159 times.
✗ Branch 1 not taken.
159 waitForI2CBus(bus);
1737
1738 // Cape detection is not the only thing that lands here: CapeInfo is built
1739 // lazily once per *process*, so fppd re-runs this whole probe when a
1740 // channel output first asks for a string/panel config, long after fppoled
1741 // has muxed and claimed its button pins. Only the first run has to search
1742 // for the bus -- it records the answer -- so later runs take it from the
1743 // recorded location and never go near the i2c1 pins.
1744
1/2
✓ Branch 0 taken 159 times.
✗ Branch 1 not taken.
159 int knownBus = recordedEEPROMBus();
1745
2/2
✓ Branch 0 taken 85 times.
✓ Branch 1 taken 74 times.
159 if (knownBus > 0) {
1746
1/2
✓ Branch 0 taken 85 times.
✗ Branch 1 not taken.
85 printf("Using the cape EEPROM already located on i2c%d this boot.\n", knownBus);
1747 85 bus = knownBus;
1748
6/8
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 58 times.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 16 times.
✓ Branch 7 taken 58 times.
74 } else if (bus == 2 && !HasI2CDevice(0x50, bus, 1000)) {
1749
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 printf("Did not find 0x50 on i2c2, trying i2c1.\n");
1750
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 ConfigureI2C1BusPins(true);
1751
3/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 15 times.
16 if (HasI2CDevice(0x50, 1, 250)) {
1752 1 bus = 1;
1753 } else {
1754
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 ConfigureI2C1BusPins(false);
1755 }
1756 }
1757 // If we forced i2c on the i2c2 pins but the EEPROM isn't on i2c2, restore.
1758 #ifdef PLATFORM_BBB
1759
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 26 times.
27 if (bus != 2) {
1760
5/10
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ 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 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
1 if (!i2c2_o1.empty() && i2c2_o1 != "i2c") ConfigurePin("P9_19", i2c2_o1.c_str());
1761
5/10
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ 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 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
1 if (!i2c2_o2.empty() && i2c2_o2 != "i2c") ConfigurePin("P9_20", i2c2_o2.c_str());
1762 }
1763 #endif
1764 // Give this one a short retry too. By now the bus is settled, but a miss
1765 // here is expensive out of proportion to the wait: it drops the cape's
1766 // whole config in favour of the generic /opt/fpp-vendor fallback, and in
1767 // fppd (which re-runs all of this per process) that means a cape that
1768 // worked at boot silently coming up as no cape at all.
1769
3/4
✓ Branch 0 taken 159 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 156 times.
159 if (HasI2CDevice(0x50, bus, 250)) {
1770
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 EEPROM = string_sprintf("/sys/bus/i2c/devices/%d-0050/eeprom", bus);
1771
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (!file_exists(EEPROM)) {
1772
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 std::string newDevFile = string_sprintf("/sys/bus/i2c/devices/i2c-%d/new_device", bus);
1773
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 int f = open(newDevFile.c_str(), O_WRONLY);
1774
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 write(f, "24c256 0x50", 11);
1775
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 close(f);
1776
1777
2/2
✓ Branch 0 taken 1500 times.
✓ Branch 1 taken 1 times.
1501 for (int x = 0; x < 1500; x++) {
1778
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1500 times.
1500 if (file_exists(EEPROM)) {
1779 ✗ break;
1780 }
1781
1/2
✓ Branch 0 taken 1500 times.
✗ Branch 1 not taken.
1500 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1782 }
1783
1784
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!file_exists(EEPROM)) {
1785 // try again
1786
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 newDevFile = string_sprintf("/sys/bus/i2c/devices/i2c-%d/new_device", bus);
1787
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 f = open(newDevFile.c_str(), O_WRONLY);
1788
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 write(f, "24c256 0x50", 11);
1789
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 close(f);
1790
2/2
✓ Branch 0 taken 200 times.
✓ Branch 1 taken 1 times.
201 for (int x = 0; x < 200; x++) {
1791
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 200 times.
200 if (file_exists(EEPROM)) {
1792 ✗ break;
1793 }
1794
1/2
✓ Branch 0 taken 200 times.
✗ Branch 1 not taken.
200 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1795 }
1796 }
1797 1 }
1798
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (!file_exists(EEPROM)) {
1799
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 printf("Couldn't get eeprom\n");
1800 }
1801
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 printf("Copying eeprom %s -> %s\n", EEPROM.c_str(), "/home/fpp/media/tmp/eeprom.bin");
1802
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 removeIfExist("/home/fpp/media/tmp/eeprom.bin");
1803
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
6 copyEEPROM(EEPROM, "/home/fpp/media/tmp/eeprom.bin");
1804 3 uid_t fppUid = 0;
1805 3 gid_t fppGid = 0;
1806
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 if (getFppUserIds(fppUid, fppGid)) {
1807 3 chown("/home/fpp/media/tmp/eeprom.bin", fppUid, fppGid);
1808 }
1809
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 ORIGEEPROM = EEPROM;
1810
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 put_file_contents("/home/fpp/media/tmp/eeprom_location.txt", (uint8_t*)ORIGEEPROM.c_str(), ORIGEEPROM.size());
1811
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 EEPROM = "/home/fpp/media/tmp/eeprom.bin";
1812
1813
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 std::string newDevFile = string_sprintf("/sys/bus/i2c/devices/i2c-%d/delete_device", bus);
1814
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 int f = open(newDevFile.c_str(), O_WRONLY);
1815
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 write(f, "0x50", 5);
1816
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 close(f);
1817 3 } else {
1818
1/2
✓ Branch 0 taken 156 times.
✗ Branch 1 not taken.
156 printf("Did not find eeprom on i2c.\n");
1819 }
1820
2/2
✓ Branch 0 taken 157 times.
✓ Branch 1 taken 2 times.
159 if (!file_exists(EEPROM)) {
1821
1/2
✓ Branch 0 taken 157 times.
✗ Branch 1 not taken.
157 printf("EEPROM file doesn't exist %s.\n", EEPROM.c_str());
1822
1/2
✓ Branch 0 taken 157 times.
✗ Branch 1 not taken.
157 EEPROM = "/opt/fpp-vendor/cape-eeprom.bin";
1823 }
1824
2/2
✓ Branch 0 taken 156 times.
✓ Branch 1 taken 3 times.
159 if (!file_exists(EEPROM)) {
1825
1/2
✓ Branch 0 taken 156 times.
✗ Branch 1 not taken.
156 EEPROM = "/home/fpp/media/config/cape-eeprom.bin";
1826 }
1827
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 144 times.
159 if (!file_exists(EEPROM)) {
1828
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 EEPROM = mapV5Config(EEPROM);
1829 }
1830
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 145 times.
159 if (!file_exists(EEPROM)) {
1831
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 EEPROM = checkUnsupported(EEPROM, bus);
1832 }
1833
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 145 times.
159 if (!file_exists(EEPROM)) {
1834
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 printf("Could not detect any cape\n");
1835
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 EEPROM = "";
1836 // A read-only probe (fppd's outputs, the setup wizard's dry run)
1837 // must not rewrite config.txt or reboot; the boot-time run does.
1838
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 2 times.
14 if (!readOnly) {
1839
3/6
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
23 handleReboot(handleCapeOverlay("/home/fpp/media/"));
1840 }
1841 13 return;
1842 }
1843
1/2
✓ Branch 0 taken 145 times.
✗ Branch 1 not taken.
145 printf("Using %s\n", EEPROM.c_str());
1844 uint8_t buffer[12];
1845
1/2
✓ Branch 0 taken 145 times.
✗ Branch 1 not taken.
145 FILE* file = fopen(EEPROM.c_str(), "rb");
1846
3/4
✓ Branch 0 taken 144 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 144 times.
✗ Branch 3 not taken.
145 int l = file ? fread(buffer, 1, 6, file) : 0;
1847
8/12
✓ Branch 0 taken 144 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 143 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 143 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 143 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 143 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 143 times.
145 if (l < 6 || buffer[0] != 'F' || buffer[1] != 'P' || buffer[2] != 'P' || buffer[3] != '0' || buffer[4] != '2') {
1848
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 printf("EEPROM header miss-match\n");
1849
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 EEPROM = "/home/fpp/media/config/cape-eeprom.bin";
1850
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (!file_exists(EEPROM)) {
1851
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 EEPROM = "";
1852 // No cape, same as the "Could not detect any cape" path above,
1853 // so drop a cape overlay left from a previous boot the same way.
1854 // Otherwise an eeprom that is present but unreadable keeps the
1855 // old overlay applied forever, and the i2c addresses it binds
1856 // then block rewriting the eeprom that would fix it.
1857
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (file) {
1858
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 fclose(file);
1859 }
1860
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!readOnly) {
1861
2/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 handleReboot(handleCapeOverlay("/home/fpp/media/"));
1862 }
1863 ✗ return;
1864 }
1865
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 printf("Using %s\n", EEPROM.c_str());
1866 }
1867
2/2
✓ Branch 0 taken 143 times.
✓ Branch 1 taken 1 times.
144 if (file) {
1868
1/2
✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
143 fclose(file);
1869 }
1870
4/4
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 24 times.
✓ Branch 3 taken 3 times.
30 }
1871
1872 144 void parseEEPROM() {
1873 // Owned here: the section parser throws on a malformed image, and the
1874 // buffer and file both leaked on every throw (and on the early return).
1875
1/2
✓ Branch 0 taken 144 times.
✗ Branch 1 not taken.
144 std::unique_ptr<uint8_t[]> bufferHolder(new uint8_t[32768]); // 32K is the largest eeprom we support, more than enough
1876 144 uint8_t* buffer = bufferHolder.get();
1877
1/2
✓ Branch 0 taken 144 times.
✗ Branch 1 not taken.
144 FILE* file = fopen(EEPROM.c_str(), "rb");
1878
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 143 times.
144 if (file == nullptr) {
1879
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 printf("Failed to open eeprom: %s\n", EEPROM.c_str());
1880
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 printf(" error: %s\n", strerror(errno));
1881 1 return;
1882 }
1883 143 std::unique_ptr<FILE, int (*)(FILE*)> fileHolder(file, fclose);
1884
1/2
✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
143 int l = fread(buffer, 1, 6, file);
1885
1886
5/10
✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 143 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 143 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 143 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 143 times.
✗ Branch 9 not taken.
143 if (buffer[0] == 'F' && buffer[1] == 'P' && buffer[2] == 'P' && buffer[3] == '0' && buffer[4] == '2') {
1887
1/2
✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
143 cape = read_string(file, 26); // cape name + nulls
1888
1/2
✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
143 capev = read_string(file, 10); // cape version + nulls
1889
1/2
✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
143 capesn = read_string(file, 16); // cape serial# + nulls
1890
1/2
✓ Branch 0 taken 143 times.
✗ Branch 1 not taken.
143 printf("Found cape %s, Version %s, Serial Number: %s\n", cape.c_str(), capev.c_str(), capesn.c_str());
1891
1892
2/2
✓ Branch 0 taken 141 times.
✓ Branch 1 taken 2 times.
143 int flen = read_section_length(file); // length of the section
1893
2/2
✓ Branch 0 taken 435 times.
✓ Branch 1 taken 138 times.
573 while (flen) {
1894
2/4
✓ Branch 0 taken 435 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 435 times.
✗ Branch 3 not taken.
435 int flag = std::stoi(read_string(file, 2));
1895 435 std::string path;
1896
2/2
✓ Branch 0 taken 142 times.
✓ Branch 1 taken 293 times.
435 if (flag < 50) {
1897
1/2
✓ Branch 0 taken 142 times.
✗ Branch 1 not taken.
142 path = outputPath + "/";
1898
4/6
✓ Branch 0 taken 142 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 140 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 140 times.
✗ Branch 5 not taken.
144 path += checked_section_name(read_string(file, 64));
1899 }
1900
4/6
✓ Branch 0 taken 140 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 97 times.
✓ Branch 3 taken 98 times.
✓ Branch 4 taken 98 times.
✗ Branch 5 not taken.
433 switch (flag) {
1901 140 case 0:
1902 case 1:
1903 case 2:
1904 case 3: {
1905
3/4
✓ Branch 0 taken 140 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 139 times.
140 if ((int)fread(buffer, 1, flen, file) != flen) {
1906
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 throw std::runtime_error("EEPROM image ends inside " + path);
1907 }
1908
3/6
✓ Branch 0 taken 139 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 139 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 139 times.
✗ Branch 5 not taken.
139 std::filesystem::create_directories(std::filesystem::path(path).parent_path());
1909
1/2
✓ Branch 0 taken 139 times.
✗ Branch 1 not taken.
139 put_file_contents(path, buffer, flen);
1910
3/6
✓ Branch 0 taken 139 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 139 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 139 times.
✗ Branch 5 not taken.
139 std::string dir = std::filesystem::path(path).parent_path().string();
1911
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 139 times.
139 if (flag == 1) {
1912 ✗ run_in_dir(dir, { "unzip", path });
1913 ✗ unlink(path.c_str());
1914
1/2
✓ Branch 0 taken 139 times.
✗ Branch 1 not taken.
139 } else if (flag == 2) {
1915
4/8
✓ Branch 0 taken 139 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 139 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 417 times.
✓ Branch 5 taken 139 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
695 run_in_dir(dir, { "tar", "-xzf", path });
1916 139 unlink(path.c_str());
1917 ✗ } else if (flag == 3) {
1918 ✗ run_in_dir(dir, { "tar", "-xjf", path });
1919 ✗ unlink(path.c_str());
1920 }
1921
1/2
✓ Branch 0 taken 139 times.
✗ Branch 1 not taken.
139 printf("- extracted file: %s\n", path.c_str());
1922 139 break;
1923 139 }
1924 ✗ case 96: {
1925 ✗ if (hasSignature && validEpromLocation && validSignature) {
1926 ✗ capesn = read_string(file, 16);
1927 ✗ devsn = read_string(file, 42);
1928 } else {
1929 // skip
1930 ✗ read_string(file, 16);
1931 ✗ read_string(file, 42);
1932 }
1933 ✗ break;
1934 }
1935 97 case 97: {
1936
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 97 times.
97 if (flen < 12) {
1937 ✗ throw std::runtime_error("invalid extras section length");
1938 }
1939
1/2
✓ Branch 0 taken 97 times.
✗ Branch 1 not taken.
97 std::string eKey = read_string(file, 12);
1940
1/2
✓ Branch 0 taken 97 times.
✗ Branch 1 not taken.
97 std::string eValue = read_string(file, flen - 12);
1941
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 97 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
97 if (hasSignature && validEpromLocation && validSignature) {
1942 ✗ signedExtras[eKey].push_back(eValue);
1943 } else {
1944
2/4
✓ Branch 0 taken 97 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 97 times.
✗ Branch 3 not taken.
97 extras[eKey].push_back(eValue);
1945 }
1946 97 break;
1947 97 }
1948 98 case 98: {
1949
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 std::string type = read_string(file, 2);
1950
3/4
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 84 times.
✓ Branch 3 taken 14 times.
98 if (type == "1") {
1951
2/2
✓ Branch 0 taken 82 times.
✓ Branch 1 taken 2 times.
84 if (ORIGEEPROM.find("sys/bus/i2c") == std::string::npos) {
1952 82 validEpromLocation = false;
1953 }
1954
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
14 } else if (type == "2") {
1955
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (EEPROM.find("cape-eeprom.bin") == std::string::npos) {
1956 ✗ validEpromLocation = false;
1957 }
1958 ✗ } else if (type != "0") {
1959 ✗ validEpromLocation = false;
1960 }
1961
1962
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 82 times.
98 if (validEpromLocation) {
1963
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 printf("- eeprom location is valid\n");
1964 } else {
1965
1/2
✓ Branch 0 taken 82 times.
✗ Branch 1 not taken.
82 printf("- ERROR eeprom location is NOT valid\n");
1966 }
1967 98 break;
1968 98 }
1969 98 case 99: {
1970
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98 if (flen < 6) {
1971 ✗ throw std::runtime_error("invalid signature section length");
1972 }
1973 98 hasSignature = true;
1974
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 fkeyId = read_string(file, 6);
1975
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 fread(buffer, 1, flen - 6, file);
1976
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 DoSignatureVerify(file, fkeyId, path, validSignature, buffer, flen - 6);
1977
1978
2/2
✓ Branch 0 taken 97 times.
✓ Branch 1 taken 1 times.
98 if (validSignature)
1979
1/2
✓ Branch 0 taken 97 times.
✗ Branch 1 not taken.
97 printf("- signature verified for key ID: %s\n", fkeyId.c_str());
1980 else
1981
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 printf("- ERROR signature is NOT valid for key ID: %s\n", fkeyId.c_str());
1982 98 break;
1983 }
1984 ✗ default:
1985 ✗ fseek(file, flen, SEEK_CUR);
1986 ✗ printf("Don't know how to handle -%s- with type %d and length %d\n", path.c_str(), flag, flen);
1987 }
1988
1/2
✓ Branch 0 taken 432 times.
✗ Branch 1 not taken.
432 flen = read_section_length(file); // length of the section
1989 435 }
1990 }
1991
1992
3/6
✓ Branch 0 taken 138 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 138 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 138 times.
414 if (signedExtras.find("deviceSerial") != signedExtras.end()) {
1993 ✗ char serialCmd[] = "sed -n 's/^Serial.*: //p' /proc/cpuinfo";
1994 ✗ std::string serialNumber = trim(exec(serialCmd));
1995 ✗ if (serialNumber != signedExtras["deviceSerial"][0]) {
1996 // If the device Serial Number does not match the signed Serial Number in the EEPROM, treat like an invalid signature
1997 ✗ validSignature = false;
1998 ✗ hasSignature = true;
1999 ✗ printf("- ERROR device serial number in EEPROM (%s) does NOT match physical device (%s)\n",
2000 ✗ signedExtras["deviceSerial"][0].c_str(), serialNumber.c_str());
2001 }
2002 ✗ }
2003
2004
2/2
✓ Branch 0 taken 82 times.
✓ Branch 1 taken 56 times.
138 if (!validEpromLocation) {
2005 // if the eeprom location is not valid, treat like an invalid signature
2006 82 validSignature = false;
2007 82 hasSignature = true;
2008 }
2009
4/4
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 40 times.
✓ Branch 2 taken 83 times.
✓ Branch 3 taken 15 times.
138 if (!hasSignature || !validSignature) {
2010 // not a valid signed eeprom, remove stuff that requires it
2011
1/2
✓ Branch 0 taken 123 times.
✗ Branch 1 not taken.
123 removeIfExist(outputPath + "/tmp/cape-sensors.json");
2012
1/2
✓ Branch 0 taken 123 times.
✗ Branch 1 not taken.
123 removeIfExist(outputPath + "/tmp/cape-inputs.json");
2013
1/2
✓ Branch 0 taken 123 times.
✗ Branch 1 not taken.
123 removeIfExist(outputPath + "/tmp/defaults/config/sensors.json");
2014 }
2015
5/30
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 139 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 139 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 139 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✓ Branch 16 taken 138 times.
✓ Branch 17 taken 1 times.
✗ Branch 18 not taken.
✗ 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.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
427 }
2016 ✗ void configurePin(const std::string& pin, const std::string& mode) {
2017 #ifdef PLATFORM_BB64
2018 ✗ std::string cmd = "/usr/bin/pinctrl -s " + CapeShellQuote(pin) + " " + CapeShellQuote(mode);
2019 ✗ exec(cmd);
2020 #elif defined(PLATFORM_PI)
2021 ✗ std::string cmd = "/usr/bin/pinctrl set " + CapeShellQuote(pin) + " " + CapeShellQuote(mode);
2022 ✗ exec(cmd);
2023 #endif
2024 ✗ }
2025
2026 139 void processEEPROM() {
2027 // if the cape-info has default settings and those settings are not already set, set them
2028 // also put the serialNumber into the cape-info for display
2029
3/4
✓ Branch 0 taken 139 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 138 times.
✓ Branch 3 taken 1 times.
139 if (file_exists(outputPath + "/tmp/cape-info.json")) {
2030
1/2
✓ Branch 0 taken 138 times.
✗ Branch 1 not taken.
138 Json::Value result;
2031
3/6
✓ Branch 0 taken 138 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 138 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 138 times.
✗ Branch 5 not taken.
138 if (LoadJsonFromFile(outputPath + "/tmp/cape-info.json", result, CapeJsonRoot::Object)) {
2032 138 std::set<std::string> removes;
2033
2/2
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 40 times.
138 if (hasSignature) {
2034
2/4
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 98 times.
✗ Branch 3 not taken.
98 result["validEepromLocation"] = validEpromLocation;
2035 }
2036
3/4
✓ Branch 0 taken 138 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 136 times.
138 if (ORIGEEPROM != "") {
2037
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 result["eepromLocation"] = ORIGEEPROM;
2038 } else {
2039
2/4
✓ Branch 0 taken 136 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 136 times.
✗ Branch 3 not taken.
136 result["eepromLocation"] = EEPROM;
2040 }
2041
3/4
✓ Branch 0 taken 138 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 123 times.
138 if (result.isMember("removeSettings")) {
2042
4/6
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 15 times.
✓ Branch 5 taken 15 times.
30 for (int x = 0; x < result["removeSettings"].size(); x++) {
2043
3/6
✓ 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.
15 std::string v = result["removeSettings"][x].asString();
2044
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 removes.insert(v);
2045 15 }
2046 }
2047
3/4
✓ Branch 0 taken 97 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 97 times.
✓ Branch 3 taken 138 times.
235 for (auto kv : extras) {
2048
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 97 times.
97 if (kv.second.size() > 1) {
2049 ✗ for (auto e : kv.second) {
2050 ✗ result[kv.first].append(e);
2051 ✗ }
2052 } else {
2053
2/4
✓ Branch 0 taken 97 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 97 times.
✗ Branch 3 not taken.
97 result[kv.first] = kv.second[0];
2054 }
2055 97 }
2056
2/4
✓ Branch 0 taken 138 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 138 times.
138 if (result.isMember("signed")) {
2057 // make sure any "not really signed" extras are removed from the cape-info
2058 ✗ result.removeMember("signed");
2059 }
2060
1/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 138 times.
138 for (auto kv : signedExtras) {
2061 ✗ if (kv.second.size() > 1) {
2062 ✗ for (auto e : kv.second) {
2063 ✗ result["signed"][kv.first].append(e);
2064 ✗ }
2065 } else {
2066 ✗ result["signed"][kv.first] = kv.second[0];
2067 }
2068 ✗ }
2069
2070 // A signature that verifies but is not bound to this hardware used
2071 // to be reported by deleting the user's FetchVendorLogos choice, so
2072 // that the page's logo <img> carried the serial to the vendor. The
2073 // goal was right and the mechanism was not: it overrode a privacy
2074 // choice to smuggle out a request, it was silent, and a user who
2075 // simply turned logos off defeated it. The state is recorded as
2076 // licenseCheck below and reported explicitly instead --
2077 // src/CapeLicenseNotify.cpp and www/capeLicense.inc.
2078
11/26
✓ Branch 0 taken 138 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 138 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 138 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 138 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 138 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 138 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 138 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 138 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 138 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 138 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 138 times.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
138 if (result["id"].asString() != "Unsupported" && result["id"].asString() != "Unknown") {
2079
2/4
✓ Branch 0 taken 138 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 138 times.
✗ Branch 3 not taken.
138 result["serialNumber"] = capesn;
2080 }
2081 138 std::vector<std::string> lines;
2082 138 bool settingsChanged = false;
2083
2/2
✓ Branch 0 taken 123 times.
✓ Branch 1 taken 15 times.
138 if (!validSignature) {
2084
2/4
✓ Branch 0 taken 123 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 123 times.
123 if (result.isMember("verifiedKeyId"))
2085 ✗ result.removeMember("verifiedKeyId");
2086 } else {
2087
2/4
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 result["verifiedKeyId"] = fkeyId;
2088 }
2089
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 102 times.
138 if (!readOnly) {
2090
1/2
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
36 readSettingsFile(lines);
2091 // Whether this device is somewhere that requires asking before
2092 // transmitting. Resolved once: the recorded LegalJurisdiction,
2093 // or an inference from the time zone until the user answers.
2094
1/2
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
36 bool strictPrivacy = priorOptInRequired(lines);
2095
2096 // removeSettings requires a signature; defaultSettings does not.
2097 //
2098 // The asymmetry is not arbitrary. defaultSettings only writes a key
2099 // that is absent, so the user is free to set it and keep it -- the
2100 // worst an unsigned cape achieves is choosing a default nobody had
2101 // chosen, which is what a default is. removeSettings deletes a value
2102 // the user may have set deliberately and restores FPP's shipped one,
2103 // which is a change nobody asked for; that wants provenance.
2104
5/6
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 35 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 35 times.
36 if (!removes.empty() && !validSignature) {
2105
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 printf("CapeUtils: ignoring removeSettings from an unsigned EEPROM "
2106 "(%zu key(s)); removing a setting requires a valid signature\n",
2107 removes.size());
2108 1 removes.clear();
2109 }
2110
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
36 for (auto& v : removes) {
2111 ✗ std::string why;
2112 ✗ if (!capeMayRemoveSetting(v, why)) {
2113 ✗ printf("CapeUtils: cape may not remove setting %s: %s\n",
2114 v.c_str(), why.c_str());
2115 ✗ continue;
2116 }
2117 ✗ for (int l = 0; l < lines.size(); l++) {
2118 ✗ std::size_t eq = lines[l].find('=');
2119 ✗ if (eq != std::string::npos && trim(lines[l].substr(0, eq)) == v) {
2120 ✗ lines.erase(lines.begin() + l);
2121 ✗ settingsChanged = true;
2122 ✗ break;
2123 }
2124 }
2125 ✗ }
2126
3/4
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 20 times.
36 if (result.isMember("defaultSettings")) {
2127
5/8
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 26 times.
✓ Branch 7 taken 16 times.
42 for (auto a : result["defaultSettings"].getMemberNames()) {
2128
3/6
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
26 std::string v = result["defaultSettings"][a].asString();
2129 // A cape may set a default; it may never override a
2130 // value the user has chosen. forceDefaults no longer
2131 // changes that -- it exists so a cape can refresh its
2132 // OWN defaults, not to overrule the person using it.
2133 26 std::string why;
2134 CapeSettingAction action =
2135
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 capeMaySetSetting(lines, a, v, strictPrivacy, why, forceDefaults);
2136
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 if (action == CapeSettingAction::SkipUnchanged) {
2137 // Already exactly this. Silent: there is nothing to
2138 // change and nothing to disagree about.
2139 ✗ continue;
2140 }
2141
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 13 times.
26 if (action == CapeSettingAction::Refuse) {
2142
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 printf("CapeUtils: cape may not set %s=%s: %s\n",
2143 a.c_str(), v.c_str(), why.c_str());
2144 13 continue;
2145 }
2146 // With -force-defaults an existing line is replaced rather
2147 // than duplicated; without it the key is absent by the
2148 // check above, so this only ever appends.
2149 13 bool replaced = false;
2150
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 12 times.
23 for (int l = 0; l < lines.size(); l++) {
2151 11 std::size_t eq = lines[l].find('=');
2152
9/18
✓ 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.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 10 times.
✓ Branch 8 taken 11 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 11 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 1 times.
✓ Branch 13 taken 10 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
11 if (eq != std::string::npos && trim(lines[l].substr(0, eq)) == a) {
2153
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 lines[l] = a + " = \"" + v + "\"";
2154 1 replaced = true;
2155 1 break;
2156 }
2157 }
2158
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 times.
13 if (!replaced) {
2159
4/8
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 12 times.
✗ Branch 7 not taken.
12 lines.push_back(a + " = \"" + v + "\"");
2160 }
2161 13 settingsChanged = true;
2162
6/6
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 13 times.
✓ Branch 3 taken 13 times.
✓ Branch 4 taken 13 times.
✓ Branch 5 taken 13 times.
68 }
2163 }
2164 // The branch that used to switch statistics ON for an unsigned
2165 // EEPROM is gone. It fired precisely when the cape was least
2166 // trusted, wrote a transmitting value the user had never chosen,
2167 // and stripped the cape's own sendStats opt-out while doing it.
2168
8/10
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 35 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 36 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 34 times.
✓ Branch 8 taken 2 times.
✓ Branch 9 taken 34 times.
36 if ((!hasSignature || !validSignature) && result.isMember("sendStats")) {
2169
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 result.removeMember("sendStats");
2170 }
2171
2172 36 bool reboot = false;
2173
3/4
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 32 times.
36 if (result.isMember("bootConfig")) {
2174 // if the cape requires changes/update to config.txt (Pi) or uEnv.txt (BBB)
2175 // we need to process them and see if we have to apply the changes and reboot or not
2176
3/8
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
4 reboot = processBootConfig(result["bootConfig"], hasSignature && validSignature);
2177 }
2178
3/4
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 35 times.
36 if (result.isMember("copyFiles")) {
2179 // if the cape requires certain files copied into place (asoundrc for example)
2180 //
2181 // The capability itself is legitimate and deliberately broad: a cape may
2182 // need to write outside the media directory, for instance an apt source in
2183 // /etc/apt/sources.list.d so a driver can be installed. What is constrained
2184 // here is provenance and one specific target, not paths in general.
2185
3/8
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
3 static const std::string MEDIA_DIR = "/home/fpp/media/";
2186
5/8
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 8 times.
✓ Branch 7 taken 1 times.
9 for (auto srcName : result["copyFiles"].getMemberNames()) {
2187
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 std::string src = srcName;
2188
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.
8 std::string target = result["copyFiles"][srcName].asString();
2189
3/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 1 times.
8 bool srcAbsolute = !src.empty() && src[0] == '/';
2190
3/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 7 times.
8 bool targetAbsolute = !target.empty() && target[0] == '/';
2191
2192 // No traversal in either direction. Nothing legitimate needs it, and
2193 // it is what makes the media-relative form below mean anything --
2194 // "../../etc/x" would otherwise walk straight out of it.
2195
6/6
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 6 times.
15 if (src.find("..") != std::string::npos ||
2196 7 target.find("..") != std::string::npos) {
2197
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 printf("CapeUtils: refusing copyFiles entry with '..' in a path: %s -> %s\n",
2198 src.c_str(), target.c_str());
2199 2 continue;
2200 }
2201
2202 // An unsigned EEPROM may read only from FPP's own install tree. The
2203 // risk is which file is read, not that the path is absolute: the
2204 // shipped asoundrc pattern names /opt/fpp/etc/... and must keep
2205 // working, while /home/fpp/media/settings as a source would copy every
2206 // password on the box somewhere the web UI serves. That holds for a
2207 // relative source too: only a signed image's is taken from the image
2208 // itself, so an unsigned "etc/shadow" resolved against the working
2209 // directory -- / at boot.
2210
6/8
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 3 times.
6 if (!validSignature && src.rfind("/opt/fpp/", 0) != 0) {
2211
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 printf("CapeUtils: refusing copyFiles from %s: unsigned EEPROMs may "
2212 "only copy from /opt/fpp/\n", src.c_str());
2213 3 continue;
2214 }
2215
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 if (validSignature && !srcAbsolute) {
2216 ✗ src = outputPath + "/" + src;
2217 }
2218
2219
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 if (!targetAbsolute) {
2220
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 target = MEDIA_DIR + target;
2221
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 } else if (!validSignature && target.rfind(MEDIA_DIR, 0) != 0) {
2222 // Writing outside the media directory means things like adding a
2223 // package source, which installs arbitrary code as root. A signed
2224 // cape may do it; an unsigned one may not.
2225
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 printf("CapeUtils: refusing copyFiles to %s: writing outside %s "
2226 "requires a valid signature\n", target.c_str(), MEDIA_DIR.c_str());
2227 1 continue;
2228 }
2229
2230 // The settings file is never a valid target, signed or not. Overwriting
2231 // it wholesale bypasses every rule applied to defaultSettings above --
2232 // never-override, the telemetry ratchet, the jurisdiction check -- and
2233 // does it silently. A cape that wants to set a setting has
2234 // defaultSettings, which is governed.
2235
6/10
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
5 if (target == MEDIA_DIR + "settings" ||
2236
4/8
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
3 target.rfind(MEDIA_DIR + "settings/", 0) == 0) {
2237
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 printf("CapeUtils: refusing copyFiles to the settings file; use "
2238 "defaultSettings\n");
2239 1 continue;
2240 }
2241
2242
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 copyFile(src, target);
2243
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 setFilePerms(target);
2244
6/6
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 7 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 7 times.
23 }
2245 }
2246
2/2
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 1 times.
36 reboot |= handleCapeOverlay(outputPath);
2247
1/2
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
35 handleReboot(reboot);
2248
3/4
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 26 times.
29 if (result.isMember("modules")) {
2249 // if the cape requires kernel modules, load them at this
2250 // time so they will be available later
2251
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 loadModules(result["modules"]);
2252 }
2253
2/4
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 29 times.
29 if (result.isMember("pinctrl")) {
2254 // if the cape requires pins set to a particular mode, set them now
2255 ✗ for (auto pin : result["pinctrl"].getMemberNames()) {
2256 ✗ for (int x = 0; x < result["pinctrl"][pin].size(); x++) {
2257 ✗ std::string v = result["pinctrl"][pin][x].asString();
2258 ✗ configurePin(pin, v);
2259 ✗ }
2260 ✗ }
2261
2262 // some modules will not bind until their pins are in the
2263 // right mode, so retry them now that pinctrl is applied
2264 ✗ if (result.isMember("modules")) {
2265 ✗ loadModules(result["modules"]);
2266 }
2267 }
2268
3/8
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 29 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 29 times.
29 if (result.isMember("i2cDevices") && !readOnly) {
2269 // if the cape has i2c devices on it that need to be registered, load them at this
2270 // time so they will be available later
2271 ✗ for (int x = 0; x < result["i2cDevices"].size(); x++) {
2272 ✗ std::string v = result["i2cDevices"][x].asString();
2273
2274 ✗ std::string newDevFile = string_sprintf("/sys/bus/i2c/devices/i2c-%d/new_device", bus);
2275 ✗ int f = open(newDevFile.c_str(), O_WRONLY);
2276 ✗ std::string newv = string_sprintf("%s", v.c_str());
2277 ✗ write(f, newv.c_str(), newv.size());
2278 ✗ close(f);
2279 ✗ }
2280 }
2281
2/8
✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 29 times.
29 if (validSignature && result.isMember("disableOutputs")) {
2282 ✗ disableOutputs(result["disableOutputs"]);
2283 }
2284
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 18 times.
29 if (settingsChanged) {
2285
2/4
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
11 lines.push_back("BootActions = \"settings\"");
2286
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 writeSettingsFile(lines);
2287 }
2288
2/4
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 29 times.
29 if (result.isMember("vendor")) {
2289 // A signature not bound to this hardware is reported to the
2290 // vendor from the browser as well as from the device, and
2291 // that request needs the vendor's host in connect-src as
2292 // well as img-src. Nothing else does, so it is asked for
2293 // only in the state that uses it.
2294 ✗ processVendor(result["vendor"], capeStatus() == CapeUtils::CapeStatus::SIGNED_GENERIC);
2295 }
2296 }
2297 // Publish how this cape verified. The enum has always existed but
2298 // never left C++, so the only way anything downstream could tell a
2299 // signed cape from one whose signature verifies but is not attached
2300 // to identifiable hardware was to infer it. Name it plainly instead.
2301
1/2
✓ Branch 0 taken 131 times.
✗ Branch 1 not taken.
131 CapeUtils::CapeStatus st = capeStatus();
2302
2/4
✓ Branch 0 taken 131 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 131 times.
✗ Branch 3 not taken.
131 result["capeStatus"] = capeStatusName(st);
2303
2304 // SIGNED_GENERIC means the signature verified but the EEPROM is not
2305 // on an i2c bus and carries no device serial -- what a signed image
2306 // lifted off the hardware it was licensed to looks like. A cape
2307 // bought from the store is on a real EEPROM or is locked to a board
2308 // and lands in SIGNED, so it never reaches here.
2309 //
2310 // Detection is recorded, not acted on. Cape detect runs from fppinit
2311 // before networking exists (the unit is ordered Before= the network
2312 // targets), so anything that tried to notify anyone from this point
2313 // would fail on every boot -- and fail silently, which is worse than
2314 // not trying. The record is what the notifier reads once there is a
2315 // network; see the plan item for the two transports.
2316
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 118 times.
131 if (st == CapeUtils::CapeStatus::SIGNED_GENERIC) {
2317
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 result["licenseCheck"] = "signed-not-on-hardware";
2318
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 printf("CapeUtils: cape signature verifies but is not bound to this hardware "
2319 "(no i2c EEPROM, no device serial) -- recording for the vendor\n");
2320 }
2321
2322
1/2
✓ Branch 0 taken 131 times.
✗ Branch 1 not taken.
131 Json::StreamWriterBuilder wbuilder;
2323
1/2
✓ Branch 0 taken 131 times.
✗ Branch 1 not taken.
131 std::string resultStr = Json::writeString(wbuilder, result);
2324
2/4
✓ Branch 0 taken 131 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 131 times.
✗ Branch 3 not taken.
131 put_file_contents(outputPath + "/tmp/cape-info.json", (const uint8_t*)resultStr.c_str(), resultStr.size());
2325
2/4
✓ Branch 0 taken 131 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 131 times.
✗ Branch 3 not taken.
131 setFilePerms(outputPath + "/tmp/cape-info.json");
2326 133 }
2327
1/2
✓ Branch 0 taken 131 times.
✗ Branch 1 not taken.
131 capeInfo = result;
2328 132 } else {
2329
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 printf("Did not find cape-info.json\n");
2330 }
2331
2332 // if there are default configurations, copy them into place if they dont already exist
2333 // unless forceDefaults is set, in which case overwrite with defaults from the new EEPROM image
2334
5/6
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 102 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 29 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
132 if (!readOnly && (!hasSignature || validSignature)) {
2335 29 std::vector<std::string> files;
2336
3/6
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 29 times.
✗ Branch 5 not taken.
33 getFileList(outputPath + "/tmp/defaults", "", files);
2337
3/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 29 times.
37 for (auto a : files) {
2338
2/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
8 std::string src = outputPath + "/tmp/defaults" + a;
2339
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 std::string target = "/home/fpp/media" + a;
2340
2341
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 if (target[target.length() - 1] == '/') {
2342 6 mkdir(target.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
2343 } else {
2344
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (forceDefaults) {
2345 ✗ copyFile(src, target);
2346 } else {
2347
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 copyIfNotExist(src, target);
2348 }
2349
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 setOwnerGroup(target);
2350 }
2351 8 }
2352 29 }
2353 132 std::vector<std::string> files;
2354
3/6
✓ Branch 0 taken 132 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 132 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 132 times.
✗ Branch 5 not taken.
332 getFileList(outputPath + "/tmp/strings", "", files);
2355
3/4
✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 147 times.
✓ Branch 3 taken 132 times.
279 for (auto a : files) {
2356
2/4
✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 147 times.
✗ Branch 3 not taken.
147 std::string src = outputPath + "/tmp/strings" + a;
2357
1/2
✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
147 Json::Value result;
2358
1/2
✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
147 Json::CharReaderBuilder factory;
2359 147 std::string errors;
2360
1/2
✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
147 std::ifstream istream(src);
2361
1/2
✓ Branch 0 taken 147 times.
✗ Branch 1 not taken.
147 bool success = Json::parseFromStream(factory, istream, &result, &errors);
2362
2/2
✓ Branch 0 taken 74 times.
✓ Branch 1 taken 73 times.
147 if (success) {
2363
2/4
✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 74 times.
✗ Branch 3 not taken.
74 result["supportsSmartReceivers"] = validSignature;
2364
1/2
✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
74 Json::StreamWriterBuilder wbuilder;
2365
1/2
✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
74 std::string resultStr = Json::writeString(wbuilder, result);
2366
1/2
✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
74 put_file_contents(src, (const uint8_t*)resultStr.c_str(), resultStr.size());
2367 74 }
2368 147 }
2369 132 }
2370
2371 ✗ bool checkAddURL(const std::string& url, Json::Value& csp) {
2372 ✗ if (url.empty()) {
2373 ✗ return false;
2374 }
2375 ✗ for (int x = 0; x < csp.size(); x++) {
2376 ✗ if (csp[x].asString() == url) {
2377 ✗ return false; // already exists
2378 }
2379 }
2380 ✗ csp.append(url);
2381 ✗ return true;
2382 }
2383 ✗ void processVendor(const Json::Value& vendor, bool allowConnect = false) {
2384 ✗ std::string url = vendor.isMember("url") ? vendor["url"].asString() : "";
2385 ✗ std::string imageUrl = vendor.isMember("image") ? vendor["image"].asString() : "";
2386 ✗ if (!imageUrl.empty()) {
2387 ✗ int idx = imageUrl.find("/", 9);
2388 ✗ if (idx != std::string::npos) {
2389 ✗ imageUrl = imageUrl.substr(0, idx + 1);
2390 }
2391 ✗ if (imageUrl == url) {
2392 ✗ imageUrl = "";
2393 }
2394 }
2395 ✗ Json::Value csp;
2396 ✗ if (!LoadJsonFromFile("/home/fpp/media/config/csp_allowed_domains.json", csp, CapeJsonRoot::Object)) {
2397 ✗ csp["default-src"] = Json::Value(Json::arrayValue);
2398 ✗ csp["img-src"] = Json::Value(Json::arrayValue);
2399 ✗ csp["script-src"] = Json::Value(Json::arrayValue);
2400 ✗ csp["style-src"] = Json::Value(Json::arrayValue);
2401 ✗ csp["connect-src"] = Json::Value(Json::arrayValue);
2402 ✗ csp["object-src"] = Json::Value(Json::arrayValue);
2403 }
2404 // Both, not `||`: short-circuiting meant that whenever the site URL was
2405 // new the logo URL was skipped, and only picked up on a later boot.
2406 ✗ bool changed = checkAddURL(url, csp["img-src"]);
2407 ✗ changed = checkAddURL(imageUrl, csp["img-src"]) || changed;
2408 ✗ if (allowConnect) {
2409 ✗ changed = checkAddURL(url, csp["connect-src"]) || changed;
2410 ✗ changed = checkAddURL(imageUrl, csp["connect-src"]) || changed;
2411 }
2412 ✗ if (changed) {
2413 ✗ Json::StreamWriterBuilder wbuilder;
2414 ✗ std::string resultStr = Json::writeString(wbuilder, csp);
2415 ✗ put_file_contents("/home/fpp/media/config/csp_allowed_domains.json", (const uint8_t*)resultStr.c_str(), resultStr.size());
2416 ✗ setFilePerms("/home/fpp/media/config/csp_allowed_domains.json");
2417 ✗ exec("/opt/fpp/scripts/ManageApacheContentPolicy.sh regenerate-norestart");
2418 ✗ }
2419 ✗ }
2420
2421 159 bool waitForI2CBus(int i2cBus) {
2422 char buf[256];
2423 159 snprintf(buf, sizeof(buf), "/sys/bus/i2c/devices/i2c-%d/new_device", i2cBus);
2424
2425 159 bool has0 = access("/sys/bus/i2c/devices/i2c-0/new_device", F_OK) != -1;
2426
2427 // wait for up to 15 seconds for the i2c bus to appear
2428 // if it's already there, this should be nearly immediate
2429
2/2
✓ Branch 0 taken 60119 times.
✓ Branch 1 taken 40 times.
60159 for (int x = 0; x < 1500; x++) {
2430
2/2
✓ Branch 0 taken 119 times.
✓ Branch 1 taken 60000 times.
60119 if (access(buf, F_OK) != -1) {
2431 119 return true;
2432 }
2433
2434 // after 1/10 of a second, if there is a 0 bus, then it's likely
2435 // that the wanted bus won't exist
2436
3/4
✓ Branch 0 taken 55960 times.
✓ Branch 1 taken 4040 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 55960 times.
60000 if (x > 100 && has0) {
2437 ✗ return false;
2438 }
2439
1/2
✓ Branch 0 taken 60000 times.
✗ Branch 1 not taken.
60000 std::this_thread::sleep_for(std::chrono::milliseconds(10));
2440 }
2441 40 return false;
2442 }
2443
2444 // The bus the EEPROM was found on during this boot's cape detection, or -1 if
2445 // it hasn't been located yet. findCapeEEPROM() records it after a successful
2446 // read; /home/fpp/media/tmp is wiped at boot, so a stale answer can't survive
2447 // into the next one.
2448 159 int recordedEEPROMBus() {
2449 // get_file_contents() does not survive a missing file
2450
3/4
✓ Branch 0 taken 159 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 74 times.
✓ Branch 3 taken 85 times.
318 if (!file_exists("/home/fpp/media/tmp/eeprom_location.txt")) {
2451 74 return -1;
2452 }
2453 85 int len = 0;
2454
2/4
✓ Branch 0 taken 85 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 85 times.
✗ Branch 3 not taken.
85 char* data = (char*)get_file_contents("/home/fpp/media/tmp/eeprom_location.txt", len);
2455
1/2
✓ Branch 0 taken 85 times.
✗ Branch 1 not taken.
170 std::string loc(data, len);
2456 85 free(data);
2457
2458 // "/sys/bus/i2c/devices/<bus>-0050/eeprom"
2459
3/8
✓ Branch 0 taken 85 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 85 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 85 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
255 static const std::string PREFIX = "/sys/bus/i2c/devices/";
2460
2/4
✓ Branch 0 taken 85 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 85 times.
85 if (loc.compare(0, PREFIX.size(), PREFIX) != 0) {
2461 ✗ return -1;
2462 }
2463 85 size_t dash = loc.find('-', PREFIX.size());
2464
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 85 times.
85 if (dash == std::string::npos) {
2465 ✗ return -1;
2466 }
2467 // Length-capped so std::stoi cannot throw: findCapeEEPROM() runs outside
2468 // the constructor's try block, so an out_of_range here would escape
2469 // CapeInfo entirely rather than degrade to "no cape".
2470
1/2
✓ Branch 0 taken 85 times.
✗ Branch 1 not taken.
85 std::string busStr = loc.substr(PREFIX.size(), dash - PREFIX.size());
2471
4/8
✓ Branch 0 taken 85 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 85 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 85 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 85 times.
85 if (busStr.empty() || busStr.size() > 4 || busStr.find_first_not_of("0123456789") != std::string::npos) {
2472 ✗ return -1;
2473 }
2474
1/2
✓ Branch 0 taken 85 times.
✗ Branch 1 not taken.
85 return std::stoi(busStr);
2475 85 }
2476
2477 // A device that isn't answering yet is not the same as a device that isn't
2478 // there, and the difference matters: a single missed probe on i2c2 sends us
2479 // to i2c1, which on the BBB steals P9_17/P9_18 from whatever the cape put
2480 // there. The pads for a bus are often re-muxed microseconds earlier (see
2481 // findCapeEEPROM), and the lines still have to charge through their pull-ups
2482 // before anyone will ACK -- so give a "missing" device retryMS to show up
2483 // before believing it. retryMS=0 keeps the old single-shot behaviour for
2484 // callers that are only confirming a device already known to be there.
2485 191 bool HasI2CDevice(int i, int i2cBus, int retryMS = 0) {
2486 char buf[256];
2487 191 snprintf(buf, sizeof(buf), "i2cdetect -y -r %d 0x%X 0x%X", i2cBus, i, i);
2488 191 int waited = 0;
2489 while (true) {
2490
2/4
✓ Branch 0 taken 1366 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1366 times.
✗ Branch 3 not taken.
1366 std::string result = exec(buf);
2491
7/8
✓ Branch 0 taken 1366 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 91 times.
✓ Branch 3 taken 1275 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 87 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 1362 times.
1366 if (result != "" && result.find("--") == std::string::npos) {
2492
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (waited) {
2493 ✗ printf("Found 0x%X on i2c%d after %dms\n", i, i2cBus, waited);
2494 }
2495 4 return true;
2496 }
2497
2/2
✓ Branch 0 taken 187 times.
✓ Branch 1 taken 1175 times.
1362 if (waited >= retryMS) {
2498 187 return false;
2499 }
2500
1/2
✓ Branch 0 taken 1175 times.
✗ Branch 1 not taken.
1175 std::this_thread::sleep_for(std::chrono::milliseconds(50));
2501 1175 waited += 50;
2502
2/2
✓ Branch 0 taken 1175 times.
✓ Branch 1 taken 191 times.
2541 }
2503 }
2504 14 std::string checkUnsupported(const std::string& orig, int i2cbus) {
2505 14 return orig;
2506 }
2507
2508 15 std::string mapV5Config(const std::string& orig) {
2509
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 std::string stringsConfigFile = "";
2510
2/4
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 std::string platformDir = "/opt/fpp/capes/" + getPlatformCapeDir();
2511 #if defined(PLATFORM_BBB) || defined(PLATFORM_BB64)
2512
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 stringsConfigFile = "/home/fpp/media/config/co-bbbStrings.json";
2513 #elif defined(PLATFORM_PI)
2514
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 stringsConfigFile = "/home/fpp/media/config/co-pixelStrings.json";
2515 #endif
2516
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 13 times.
15 if (file_exists(stringsConfigFile)) {
2517
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 Json::Value root;
2518
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (LoadJsonFromFile(stringsConfigFile, root, CapeJsonRoot::Object)) {
2519
5/10
✓ 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 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
2 if (root["channelOutputs"][0]["enabled"].asInt()) {
2520
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 taken 2 times.
✗ Branch 7 not taken.
2 std::string type = root["channelOutputs"][0]["type"].asString();
2521
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 taken 2 times.
✗ Branch 7 not taken.
2 std::string subtype = root["channelOutputs"][0]["subType"].asString();
2522
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::string pinout = "1.x";
2523
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.
2 if (root["channelOutputs"][0].isMember("pinoutVersion")) {
2524 ✗ pinout = root["channelOutputs"][0]["pinoutVersion"].asString();
2525 }
2526
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 std::string key = type + ":" + subtype;
2527
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 std::string newEeprom = CONFIG_EEPROM_UPGRADE_MAP[key];
2528
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (pinout == "2.x") {
2529 ✗ newEeprom += "v2";
2530 }
2531
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (readOnly) {
2532 // installing it is the boot-time run's job; a read-only
2533 // probe used to install it here and then switch itself
2534 // to read-write for the rest of the run
2535
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 printf("Not installing a virtual eeprom for %s from a read-only probe\n", newEeprom.c_str());
2536
4/8
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ 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 not taken.
1 } else if (file_exists(platformDir + "/" + newEeprom + "-eeprom.bin")) {
2537 // found a virtual eeprom, install it and use it
2538
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 printf("Installing new virtual eeprom based on prior string configuration: %s\n", newEeprom.c_str());
2539
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 newEeprom = platformDir + "/" + newEeprom + "-eeprom.bin";
2540
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 copyFile(newEeprom, "/home/fpp/media/config/cape-eeprom.bin");
2541
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 setFilePerms("/home/fpp/media/config/cape-eeprom.bin");
2542
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
2 return "/home/fpp/media/config/cape-eeprom.bin";
2543 }
2544
10/10
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 1 times.
6 }
2545 }
2546
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 }
2547
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 return orig;
2548 15 }
2549 36 void readSettingsFile(std::vector<std::string>& lines) {
2550 36 std::string line;
2551
1/2
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
36 std::ifstream settingsIstream("/home/fpp/media/settings");
2552
3/4
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 22 times.
36 if (settingsIstream.good()) {
2553
4/6
✓ Branch 0 taken 30 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✓ Branch 5 taken 14 times.
30 while (std::getline(settingsIstream, line)) {
2554
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 if (line.size() > 0) {
2555
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 lines.push_back(line);
2556 }
2557 }
2558 }
2559
1/2
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
36 settingsIstream.close();
2560 36 }
2561 11 void writeSettingsFile(const std::vector<std::string>& lines) {
2562
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 std::ofstream settingsOstream("/home/fpp/media/settings");
2563
3/4
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32 times.
✓ Branch 3 taken 11 times.
43 for (auto l : lines) {
2564
2/4
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32 times.
✗ Branch 3 not taken.
32 settingsOstream << l << "\n";
2565 32 }
2566
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 settingsOstream.close();
2567 11 }
2568 1573 void getFileList(const std::string& basepath, const std::string& path, std::vector<std::string>& files) {
2569 // Archives from the EEPROM are extracted here, and tar and unzip both
2570 // restore symlinks: following one would recurse forever (a link to
2571 // "..") or copy whatever it points at into the media directory.
2572 // Only plain files and directories are taken.
2573 struct stat st;
2574
8/8
✓ Branch 0 taken 1488 times.
✓ Branch 1 taken 85 times.
✓ Branch 2 taken 787 times.
✓ Branch 3 taken 701 times.
✓ Branch 4 taken 7 times.
✓ Branch 5 taken 780 times.
✓ Branch 6 taken 92 times.
✓ Branch 7 taken 1481 times.
1573 if (lstat(basepath.c_str(), &st) != 0 || !(S_ISDIR(st.st_mode) || S_ISREG(st.st_mode))) {
2575 92 return;
2576 }
2577
1/2
✓ Branch 0 taken 1481 times.
✗ Branch 1 not taken.
1481 DIR* dir = opendir(basepath.c_str());
2578
2/2
✓ Branch 0 taken 701 times.
✓ Branch 1 taken 780 times.
1481 if (dir) {
2579
2/4
✓ Branch 0 taken 701 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 701 times.
✗ Branch 3 not taken.
701 files.push_back(path + "/");
2580 struct dirent* ep;
2581
3/4
✓ Branch 0 taken 3317 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2616 times.
✓ Branch 3 taken 701 times.
3317 while ((ep = readdir(dir))) {
2582
1/2
✓ Branch 0 taken 2616 times.
✗ Branch 1 not taken.
4838 std::string v = ep->d_name;
2583
8/10
✓ Branch 0 taken 2616 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1915 times.
✓ Branch 3 taken 701 times.
✓ Branch 4 taken 1915 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1214 times.
✓ Branch 7 taken 701 times.
✓ Branch 8 taken 1214 times.
✓ Branch 9 taken 1402 times.
2616 if (v != "." && v != "..") {
2584
2/4
✓ Branch 0 taken 1214 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1214 times.
✗ Branch 3 not taken.
1214 std::string newBPath = basepath + "/" + v;
2585
2/4
✓ Branch 0 taken 1214 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1214 times.
✗ Branch 3 not taken.
1214 std::string newPath = path + "/" + v;
2586
1/2
✓ Branch 0 taken 1214 times.
✗ Branch 1 not taken.
1214 getFileList(newBPath, newPath, files);
2587 1214 }
2588 2616 }
2589
1/2
✓ Branch 0 taken 701 times.
✗ Branch 1 not taken.
701 closedir(dir);
2590
1/2
✓ Branch 0 taken 780 times.
✗ Branch 1 not taken.
780 } else if (file_exists(basepath)) {
2591
1/2
✓ Branch 0 taken 780 times.
✗ Branch 1 not taken.
780 files.push_back(path);
2592 }
2593 }
2594 98 void DoSignatureVerify(FILE* file, const std::string& fKeyId, const std::string& path, bool& validSignature, uint8_t* signature, int sigLen) {
2595
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 uint8_t* b = new uint8_t[32768];
2596
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 size_t pos = ftell(file);
2597
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 int flen = read_section_length(file);
2598
2/2
✓ Branch 0 taken 195 times.
✓ Branch 1 taken 98 times.
293 while (flen != 0) {
2599
2/4
✓ Branch 0 taken 195 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 195 times.
✗ Branch 3 not taken.
195 int flag = std::stoi(read_string(file, 2));
2600
2/2
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 97 times.
195 if (flag < 50) {
2601 98 flen += 64;
2602 }
2603 195 fseek(file, flen, SEEK_CUR);
2604
1/2
✓ Branch 0 taken 195 times.
✗ Branch 1 not taken.
195 flen = read_section_length(file);
2605 }
2606
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 int end = ftell(file);
2607
2/4
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 98 times.
98 if (end < (int)pos || end - (int)pos > MAX_EEPROM_SIZE) {
2608 ✗ delete[] b;
2609 ✗ throw std::runtime_error("signed EEPROM content larger than an EEPROM");
2610 }
2611 98 fseek(file, pos, SEEK_SET);
2612
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 int len = fread(b, 1, end - pos, file);
2613 98 fseek(file, pos, SEEK_SET);
2614
2615
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 const uint8_t* key = KEYS[fKeyId].first;
2616
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 size_t keyLen = KEYS[fKeyId].second;
2617
2618 98 EVP_PKEY* pubKey = NULL;
2619
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 OSSL_DECODER_CTX* ctx = OSSL_DECODER_CTX_new_for_pkey(&pubKey, "PEM", nullptr,
2620 "EC",
2621 0,
2622 NULL, NULL);
2623
2/4
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 98 times.
98 if (OSSL_DECODER_from_data(ctx, &key, &keyLen) == 0) {
2624 ✗ OSSL_DECODER_CTX_free(ctx);
2625 ✗ validSignature = 0;
2626 ✗ delete[] b;
2627 ✗ return;
2628 }
2629
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 OSSL_DECODER_CTX_free(ctx);
2630
2631
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 EVP_MD_CTX* m_VerifyCtx = EVP_MD_CTX_create();
2632
2/4
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 98 times.
✗ Branch 3 not taken.
98 EVP_DigestVerifyInit(m_VerifyCtx, NULL, EVP_sha256(), NULL, pubKey);
2633
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 EVP_DigestVerifyUpdate(m_VerifyCtx, b, len);
2634
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 int AuthStatus = EVP_DigestVerifyFinal(m_VerifyCtx, signature, sigLen);
2635
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 EVP_MD_CTX_free(m_VerifyCtx);
2636
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 EVP_PKEY_free(pubKey);
2637 98 validSignature = AuthStatus == 1;
2638
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 delete[] b;
2639 }
2640
2641 bool readOnly;
2642 bool forceDefaults;
2643 int bus;
2644 std::string EEPROM;
2645 std::string ORIGEEPROM;
2646 std::string outputPath;
2647
2648 std::string cape;
2649 std::string capev;
2650 std::string capesn;
2651 std::string devsn;
2652
2653 std::string fkeyId;
2654 std::map<std::string, std::vector<std::string>> extras;
2655 std::map<std::string, std::vector<std::string>> signedExtras;
2656 bool validSignature = false;
2657 bool hasSignature = false;
2658 bool validEpromLocation = true;
2659 bool corruptEEPROM = false;
2660
2661 std::map<std::string, std::vector<uint8_t>> fileMap;
2662 Json::Value capeInfo;
2663 };
2664
2665 CapeUtils CapeUtils::INSTANCE;
2666 262 CapeUtils::CapeUtils() :
2667 262 capeInfo(nullptr) {
2668 262 }
2669 97 CapeUtils::~CapeUtils() {
2670
2/2
✓ Branch 0 taken 53 times.
✓ Branch 1 taken 44 times.
97 if (capeInfo) {
2671
1/2
✓ Branch 0 taken 53 times.
✗ Branch 1 not taken.
53 delete capeInfo;
2672 }
2673 97 }
2674 348 CapeInfo* CapeUtils::initCapeInfo(bool ro, bool forceDefaults) {
2675
2/2
✓ Branch 0 taken 159 times.
✓ Branch 1 taken 189 times.
348 if (capeInfo == nullptr) {
2676
2/6
✓ Branch 0 taken 151 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 151 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
159 capeInfo = new CapeInfo(ro, forceDefaults);
2677 }
2678 340 return capeInfo;
2679 }
2680
2681 73 CapeUtils::CapeStatus CapeUtils::initCape(bool readOnly, bool forceDefaults) {
2682 73 return initCapeInfo(readOnly, forceDefaults)->capeStatus();
2683 }
2684
2685 2 std::string CapeUtils::dryRunSettings(const std::string& jurisdiction) {
2686 // Read-only construction already writes nothing -- no settings file, no boot
2687 // config, no file copies, no CSP changes -- so the dry run is that path plus
2688 // the defaultSettings evaluation the read-only path skips.
2689
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 g_jurisdictionOverride = jurisdiction;
2690
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 const Json::Value& info = initCapeInfo(true)->getCapeInfo();
2691
2692 2 std::vector<std::string> lines;
2693 {
2694 2 std::string line;
2695
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::ifstream in("/home/fpp/media/settings");
2696
4/6
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 2 times.
4 while (std::getline(in, line)) {
2697
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!line.empty()) {
2698
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 lines.push_back(line);
2699 }
2700 }
2701 2 }
2702
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 bool strict = priorOptInRequired(lines);
2703
2704
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 Json::Value out(Json::objectValue);
2705
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 Json::Value settings(Json::objectValue);
2706
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 Json::Value refused(Json::objectValue);
2707
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (info.isMember("defaultSettings")) {
2708
4/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 2 times.
8 for (const auto& key : info["defaultSettings"].getMemberNames()) {
2709
3/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 std::string value = info["defaultSettings"][key].asString();
2710 6 std::string why;
2711 // The same predicate a real run uses. Anything the user has already
2712 // set is refused here exactly as it would be there, which is why a
2713 // non-privacy default applied at boot does not come back: it is set.
2714
3/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
6 switch (capeMaySetSetting(lines, key, value, strict, why)) {
2715 3 case CapeSettingAction::Apply:
2716
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 settings[key] = value;
2717 3 break;
2718 3 case CapeSettingAction::Refuse:
2719
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 refused[key] = why;
2720 3 break;
2721 ✗ case CapeSettingAction::SkipUnchanged:
2722 // Neither proposed nor refused: the box already holds exactly this
2723 // value, so the wizard has nothing to offer and nothing to explain.
2724 ✗ break;
2725 }
2726 8 }
2727 }
2728
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 out["jurisdiction"] = jurisdiction;
2729
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 out["priorOptIn"] = strict;
2730
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 out["settings"] = settings;
2731
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 out["refused"] = refused;
2732
2733 2 g_jurisdictionOverride.clear();
2734
2735 // Compact, single line, and printed last. Cape detection writes progress to
2736 // stdout throughout -- signature results, extracted files -- so a
2737 // pretty-printed object would be interleaved with it and unparseable. One
2738 // line means the caller can take the last non-empty line of output and be
2739 // done, which is what the wizard's PHP wrapper does.
2740
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
4 Json::StreamWriterBuilder wbuilder;
2741
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.
6 wbuilder["indentation"] = "";
2742
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
4 return Json::writeString(wbuilder, out);
2743 2 }
2744
2745 137 bool CapeUtils::hasFile(const std::string& path) {
2746 137 return initCapeInfo(true)->hasFile(path);
2747 }
2748 ✗ std::vector<uint8_t> CapeUtils::getFile(const std::string& path) {
2749 ✗ return initCapeInfo(true)->getFile(path);
2750 }
2751 17 const Json::Value& CapeUtils::getCapeInfo() {
2752
3/8
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
17 static Json::Value val;
2753 17 initCapeInfo(true);
2754 17 val = capeInfo->getCapeInfo();
2755 17 return val;
2756 }
2757
2758 119 int CapeUtils::getLicensedOutputs() {
2759 119 return initCapeInfo(true)->getLicensedOutputs();
2760 }
2761
2762 ✗ std::string CapeUtils::getKeyId() {
2763 ✗ return initCapeInfo(true)->getKeyId();
2764 }
2765
2766 109 bool CapeUtils::getStringConfig(const std::string& type, Json::Value& val) {
2767
1/2
✓ Branch 0 taken 109 times.
✗ Branch 1 not taken.
109 Json::Value result;
2768
1/2
✓ Branch 0 taken 109 times.
✗ Branch 1 not taken.
109 Json::CharReaderBuilder factory;
2769 109 std::string errors;
2770
2771
2/4
✓ Branch 0 taken 109 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 109 times.
✗ Branch 3 not taken.
109 std::string fn = "/tmp/strings/" + type + ".json";
2772
2/4
✓ Branch 0 taken 109 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 109 times.
109 if (hasFile(fn)) {
2773 ✗ const std::vector<uint8_t>& f = getFile(fn);
2774 ✗ std::istringstream istream(std::string((const char*)&f[0], f.size()));
2775 ✗ bool success = Json::parseFromStream(factory, istream, &val, &errors);
2776 ✗ if (success) {
2777 ✗ val["supportsSmartReceivers"] = capeInfo->capeStatus() >= CapeUtils::CapeStatus::SIGNED_GENERIC;
2778 ✗ return true;
2779 }
2780 ✗ } else {
2781
1/2
✓ Branch 0 taken 109 times.
✗ Branch 1 not taken.
109 fn = "/opt/fpp/capes/";
2782
2/4
✓ Branch 0 taken 109 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 109 times.
✗ Branch 3 not taken.
109 fn += getPlatformCapeDir();
2783
3/6
✓ Branch 0 taken 109 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 109 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 109 times.
✗ Branch 5 not taken.
109 fn += "/strings/" + type + ".json";
2784
2/2
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 13 times.
109 if (file_exists(fn)) {
2785
1/2
✓ Branch 0 taken 96 times.
✗ Branch 1 not taken.
96 std::ifstream istream(fn);
2786
1/2
✓ Branch 0 taken 96 times.
✗ Branch 1 not taken.
96 bool success = Json::parseFromStream(factory, istream, &val, &errors);
2787
1/2
✓ Branch 0 taken 96 times.
✗ Branch 1 not taken.
96 if (success) {
2788
3/4
✓ Branch 0 taken 96 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 80 times.
✓ Branch 3 taken 16 times.
96 if (capeInfo->capeStatus() <= CapeUtils::CapeStatus::UNSIGNED) {
2789
2/4
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 80 times.
✗ Branch 3 not taken.
80 val["supportsSmartReceivers"] = false;
2790 }
2791 96 return true;
2792 }
2793
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 96 times.
96 }
2794 }
2795 13 return false;
2796 109 }
2797
2798 ✗ bool CapeUtils::getPWMConfig(const std::string& type, Json::Value& val) {
2799 ✗ Json::Value result;
2800 ✗ Json::CharReaderBuilder factory;
2801 ✗ std::string errors;
2802
2803 ✗ std::string fn = "/tmp/pwm/" + type + ".json";
2804 ✗ if (hasFile(fn) && getLicensedOutputs() > 0) {
2805 ✗ const std::vector<uint8_t>& f = getFile(fn);
2806 ✗ std::istringstream istream(std::string((const char*)&f[0], f.size()));
2807 ✗ bool success = Json::parseFromStream(factory, istream, &val, &errors);
2808 ✗ if (success) {
2809 ✗ return true;
2810 }
2811 ✗ }
2812 ✗ return false;
2813 ✗ }
2814
2815 28 bool CapeUtils::getPanelConfig(const std::string& type, Json::Value& val) {
2816
1/2
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
28 Json::Value result;
2817
1/2
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
28 Json::CharReaderBuilder factory;
2818 28 std::string errors;
2819
2820
2/4
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
28 std::string fn = "/tmp/panels/" + type + ".json";
2821
2/4
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 28 times.
28 if (hasFile(fn)) {
2822 ✗ const std::vector<uint8_t>& f = getFile(fn);
2823 ✗ std::istringstream istream(std::string((const char*)&f[0], f.size()));
2824 ✗ return Json::parseFromStream(factory, istream, &val, &errors);
2825 ✗ }
2826 28 return false;
2827 28 }
2828