| 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 |