]>
Commit | Line | Data |
---|---|---|
950313eb MH |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Boot config tool for initrd image | |
4 | */ | |
5 | #include <stdio.h> | |
6 | #include <stdlib.h> | |
7 | #include <sys/types.h> | |
8 | #include <sys/stat.h> | |
9 | #include <fcntl.h> | |
10 | #include <unistd.h> | |
11 | #include <string.h> | |
12 | #include <errno.h> | |
13 | ||
14 | #include <linux/kernel.h> | |
15 | #include <linux/bootconfig.h> | |
16 | ||
950313eb MH |
17 | static int xbc_show_array(struct xbc_node *node) |
18 | { | |
19 | const char *val; | |
20 | int i = 0; | |
21 | ||
22 | xbc_array_for_each_value(node, val) { | |
23 | printf("\"%s\"%s", val, node->next ? ", " : ";\n"); | |
24 | i++; | |
25 | } | |
26 | return i; | |
27 | } | |
28 | ||
29 | static void xbc_show_compact_tree(void) | |
30 | { | |
31 | struct xbc_node *node, *cnode; | |
32 | int depth = 0, i; | |
33 | ||
34 | node = xbc_root_node(); | |
35 | while (node && xbc_node_is_key(node)) { | |
36 | for (i = 0; i < depth; i++) | |
37 | printf("\t"); | |
38 | cnode = xbc_node_get_child(node); | |
39 | while (cnode && xbc_node_is_key(cnode) && !cnode->next) { | |
40 | printf("%s.", xbc_node_get_data(node)); | |
41 | node = cnode; | |
42 | cnode = xbc_node_get_child(node); | |
43 | } | |
44 | if (cnode && xbc_node_is_key(cnode)) { | |
45 | printf("%s {\n", xbc_node_get_data(node)); | |
46 | depth++; | |
47 | node = cnode; | |
48 | continue; | |
49 | } else if (cnode && xbc_node_is_value(cnode)) { | |
50 | printf("%s = ", xbc_node_get_data(node)); | |
51 | if (cnode->next) | |
52 | xbc_show_array(cnode); | |
53 | else | |
54 | printf("\"%s\";\n", xbc_node_get_data(cnode)); | |
55 | } else { | |
56 | printf("%s;\n", xbc_node_get_data(node)); | |
57 | } | |
58 | ||
59 | if (node->next) { | |
60 | node = xbc_node_get_next(node); | |
61 | continue; | |
62 | } | |
63 | while (!node->next) { | |
64 | node = xbc_node_get_parent(node); | |
65 | if (!node) | |
66 | return; | |
67 | if (!xbc_node_get_child(node)->next) | |
68 | continue; | |
69 | depth--; | |
70 | for (i = 0; i < depth; i++) | |
71 | printf("\t"); | |
72 | printf("}\n"); | |
73 | } | |
74 | node = xbc_node_get_next(node); | |
75 | } | |
76 | } | |
77 | ||
78 | /* Simple real checksum */ | |
79 | int checksum(unsigned char *buf, int len) | |
80 | { | |
81 | int i, sum = 0; | |
82 | ||
83 | for (i = 0; i < len; i++) | |
84 | sum += buf[i]; | |
85 | ||
86 | return sum; | |
87 | } | |
88 | ||
89 | #define PAGE_SIZE 4096 | |
90 | ||
91 | int load_xbc_fd(int fd, char **buf, int size) | |
92 | { | |
93 | int ret; | |
94 | ||
95 | *buf = malloc(size + 1); | |
96 | if (!*buf) | |
97 | return -ENOMEM; | |
98 | ||
99 | ret = read(fd, *buf, size); | |
100 | if (ret < 0) | |
101 | return -errno; | |
102 | (*buf)[size] = '\0'; | |
103 | ||
104 | return ret; | |
105 | } | |
106 | ||
107 | /* Return the read size or -errno */ | |
108 | int load_xbc_file(const char *path, char **buf) | |
109 | { | |
110 | struct stat stat; | |
111 | int fd, ret; | |
112 | ||
113 | fd = open(path, O_RDONLY); | |
114 | if (fd < 0) | |
115 | return -errno; | |
116 | ret = fstat(fd, &stat); | |
117 | if (ret < 0) | |
118 | return -errno; | |
119 | ||
120 | ret = load_xbc_fd(fd, buf, stat.st_size); | |
121 | ||
122 | close(fd); | |
123 | ||
124 | return ret; | |
125 | } | |
126 | ||
127 | int load_xbc_from_initrd(int fd, char **buf) | |
128 | { | |
129 | struct stat stat; | |
130 | int ret; | |
131 | u32 size = 0, csum = 0, rcsum; | |
85c46b78 | 132 | char magic[BOOTCONFIG_MAGIC_LEN]; |
89b74cac | 133 | const char *msg; |
950313eb MH |
134 | |
135 | ret = fstat(fd, &stat); | |
136 | if (ret < 0) | |
137 | return -errno; | |
138 | ||
85c46b78 | 139 | if (stat.st_size < 8 + BOOTCONFIG_MAGIC_LEN) |
950313eb MH |
140 | return 0; |
141 | ||
85c46b78 MH |
142 | if (lseek(fd, -BOOTCONFIG_MAGIC_LEN, SEEK_END) < 0) { |
143 | pr_err("Failed to lseek: %d\n", -errno); | |
144 | return -errno; | |
145 | } | |
146 | if (read(fd, magic, BOOTCONFIG_MAGIC_LEN) < 0) | |
147 | return -errno; | |
148 | /* Check the bootconfig magic bytes */ | |
149 | if (memcmp(magic, BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_LEN) != 0) | |
150 | return 0; | |
151 | ||
152 | if (lseek(fd, -(8 + BOOTCONFIG_MAGIC_LEN), SEEK_END) < 0) { | |
97378001 | 153 | pr_err("Failed to lseek: %d\n", -errno); |
950313eb MH |
154 | return -errno; |
155 | } | |
156 | ||
157 | if (read(fd, &size, sizeof(u32)) < 0) | |
158 | return -errno; | |
159 | ||
160 | if (read(fd, &csum, sizeof(u32)) < 0) | |
161 | return -errno; | |
162 | ||
85c46b78 MH |
163 | /* Wrong size error */ |
164 | if (stat.st_size < size + 8 + BOOTCONFIG_MAGIC_LEN) { | |
165 | pr_err("bootconfig size is too big\n"); | |
166 | return -E2BIG; | |
167 | } | |
950313eb | 168 | |
85c46b78 MH |
169 | if (lseek(fd, stat.st_size - (size + 8 + BOOTCONFIG_MAGIC_LEN), |
170 | SEEK_SET) < 0) { | |
97378001 | 171 | pr_err("Failed to lseek: %d\n", -errno); |
950313eb MH |
172 | return -errno; |
173 | } | |
174 | ||
175 | ret = load_xbc_fd(fd, buf, size); | |
176 | if (ret < 0) | |
177 | return ret; | |
178 | ||
85c46b78 | 179 | /* Wrong Checksum */ |
950313eb MH |
180 | rcsum = checksum((unsigned char *)*buf, size); |
181 | if (csum != rcsum) { | |
97378001 | 182 | pr_err("checksum error: %d != %d\n", csum, rcsum); |
85c46b78 | 183 | return -EINVAL; |
950313eb MH |
184 | } |
185 | ||
89b74cac | 186 | ret = xbc_init(*buf, &msg, NULL); |
85c46b78 | 187 | /* Wrong data */ |
89b74cac MH |
188 | if (ret < 0) { |
189 | pr_err("parse error: %s.\n", msg); | |
85c46b78 | 190 | return ret; |
89b74cac | 191 | } |
950313eb MH |
192 | |
193 | return size; | |
194 | } | |
195 | ||
196 | int show_xbc(const char *path) | |
197 | { | |
198 | int ret, fd; | |
199 | char *buf = NULL; | |
200 | ||
201 | fd = open(path, O_RDONLY); | |
202 | if (fd < 0) { | |
97378001 | 203 | pr_err("Failed to open initrd %s: %d\n", path, fd); |
950313eb MH |
204 | return -errno; |
205 | } | |
206 | ||
207 | ret = load_xbc_from_initrd(fd, &buf); | |
208 | if (ret < 0) | |
97378001 | 209 | pr_err("Failed to load a boot config from initrd: %d\n", ret); |
950313eb MH |
210 | else |
211 | xbc_show_compact_tree(); | |
212 | ||
213 | close(fd); | |
214 | free(buf); | |
215 | ||
216 | return ret; | |
217 | } | |
218 | ||
219 | int delete_xbc(const char *path) | |
220 | { | |
221 | struct stat stat; | |
222 | int ret = 0, fd, size; | |
223 | char *buf = NULL; | |
224 | ||
225 | fd = open(path, O_RDWR); | |
226 | if (fd < 0) { | |
97378001 | 227 | pr_err("Failed to open initrd %s: %d\n", path, fd); |
950313eb MH |
228 | return -errno; |
229 | } | |
230 | ||
950313eb | 231 | size = load_xbc_from_initrd(fd, &buf); |
950313eb MH |
232 | if (size < 0) { |
233 | ret = size; | |
97378001 | 234 | pr_err("Failed to load a boot config from initrd: %d\n", ret); |
950313eb MH |
235 | } else if (size > 0) { |
236 | ret = fstat(fd, &stat); | |
237 | if (!ret) | |
85c46b78 MH |
238 | ret = ftruncate(fd, stat.st_size |
239 | - size - 8 - BOOTCONFIG_MAGIC_LEN); | |
950313eb MH |
240 | if (ret) |
241 | ret = -errno; | |
242 | } /* Ignore if there is no boot config in initrd */ | |
243 | ||
244 | close(fd); | |
245 | free(buf); | |
246 | ||
247 | return ret; | |
248 | } | |
249 | ||
89b74cac MH |
250 | static void show_xbc_error(const char *data, const char *msg, int pos) |
251 | { | |
252 | int lin = 1, col, i; | |
253 | ||
254 | if (pos < 0) { | |
255 | pr_err("Error: %s.\n", msg); | |
256 | return; | |
257 | } | |
258 | ||
259 | /* Note that pos starts from 0 but lin and col should start from 1. */ | |
260 | col = pos + 1; | |
261 | for (i = 0; i < pos; i++) { | |
262 | if (data[i] == '\n') { | |
263 | lin++; | |
264 | col = pos - i; | |
265 | } | |
266 | } | |
267 | pr_err("Parse Error: %s at %d:%d\n", msg, lin, col); | |
268 | ||
269 | } | |
270 | ||
950313eb MH |
271 | int apply_xbc(const char *path, const char *xbc_path) |
272 | { | |
273 | u32 size, csum; | |
274 | char *buf, *data; | |
275 | int ret, fd; | |
89b74cac MH |
276 | const char *msg; |
277 | int pos; | |
950313eb MH |
278 | |
279 | ret = load_xbc_file(xbc_path, &buf); | |
280 | if (ret < 0) { | |
97378001 | 281 | pr_err("Failed to load %s : %d\n", xbc_path, ret); |
950313eb MH |
282 | return ret; |
283 | } | |
284 | size = strlen(buf) + 1; | |
285 | csum = checksum((unsigned char *)buf, size); | |
286 | ||
287 | /* Prepare xbc_path data */ | |
288 | data = malloc(size + 8); | |
289 | if (!data) | |
290 | return -ENOMEM; | |
291 | strcpy(data, buf); | |
292 | *(u32 *)(data + size) = size; | |
293 | *(u32 *)(data + size + 4) = csum; | |
294 | ||
295 | /* Check the data format */ | |
89b74cac | 296 | ret = xbc_init(buf, &msg, &pos); |
950313eb | 297 | if (ret < 0) { |
89b74cac | 298 | show_xbc_error(data, msg, pos); |
950313eb MH |
299 | free(data); |
300 | free(buf); | |
89b74cac | 301 | |
950313eb MH |
302 | return ret; |
303 | } | |
304 | printf("Apply %s to %s\n", xbc_path, path); | |
0f0d0a77 | 305 | printf("\tNumber of nodes: %d\n", ret); |
950313eb MH |
306 | printf("\tSize: %u bytes\n", (unsigned int)size); |
307 | printf("\tChecksum: %d\n", (unsigned int)csum); | |
308 | ||
309 | /* TODO: Check the options by schema */ | |
310 | xbc_destroy_all(); | |
311 | free(buf); | |
312 | ||
313 | /* Remove old boot config if exists */ | |
314 | ret = delete_xbc(path); | |
315 | if (ret < 0) { | |
97378001 | 316 | pr_err("Failed to delete previous boot config: %d\n", ret); |
88426044 | 317 | free(data); |
950313eb MH |
318 | return ret; |
319 | } | |
320 | ||
321 | /* Apply new one */ | |
322 | fd = open(path, O_RDWR | O_APPEND); | |
323 | if (fd < 0) { | |
97378001 | 324 | pr_err("Failed to open %s: %d\n", path, fd); |
88426044 | 325 | free(data); |
950313eb MH |
326 | return fd; |
327 | } | |
328 | /* TODO: Ensure the @path is initramfs/initrd image */ | |
329 | ret = write(fd, data, size + 8); | |
330 | if (ret < 0) { | |
97378001 | 331 | pr_err("Failed to apply a boot config: %d\n", ret); |
88426044 | 332 | goto out; |
85c46b78 MH |
333 | } |
334 | /* Write a magic word of the bootconfig */ | |
335 | ret = write(fd, BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_LEN); | |
336 | if (ret < 0) { | |
337 | pr_err("Failed to apply a boot config magic: %d\n", ret); | |
88426044 | 338 | goto out; |
950313eb | 339 | } |
88426044 | 340 | out: |
950313eb MH |
341 | close(fd); |
342 | free(data); | |
343 | ||
88426044 | 344 | return ret; |
950313eb MH |
345 | } |
346 | ||
347 | int usage(void) | |
348 | { | |
349 | printf("Usage: bootconfig [OPTIONS] <INITRD>\n" | |
350 | " Apply, delete or show boot config to initrd.\n" | |
351 | " Options:\n" | |
352 | " -a <config>: Apply boot config to initrd\n" | |
353 | " -d : Delete boot config file from initrd\n\n" | |
354 | " If no option is given, show current applied boot config.\n"); | |
355 | return -1; | |
356 | } | |
357 | ||
358 | int main(int argc, char **argv) | |
359 | { | |
360 | char *path = NULL; | |
361 | char *apply = NULL; | |
362 | bool delete = false; | |
363 | int opt; | |
364 | ||
365 | while ((opt = getopt(argc, argv, "hda:")) != -1) { | |
366 | switch (opt) { | |
367 | case 'd': | |
368 | delete = true; | |
369 | break; | |
370 | case 'a': | |
371 | apply = optarg; | |
372 | break; | |
373 | case 'h': | |
374 | default: | |
375 | return usage(); | |
376 | } | |
377 | } | |
378 | ||
379 | if (apply && delete) { | |
97378001 | 380 | pr_err("Error: You can not specify both -a and -d at once.\n"); |
950313eb MH |
381 | return usage(); |
382 | } | |
383 | ||
384 | if (optind >= argc) { | |
97378001 | 385 | pr_err("Error: No initrd is specified.\n"); |
950313eb MH |
386 | return usage(); |
387 | } | |
388 | ||
389 | path = argv[optind]; | |
390 | ||
391 | if (apply) | |
392 | return apply_xbc(path, apply); | |
393 | else if (delete) | |
394 | return delete_xbc(path); | |
395 | ||
396 | return show_xbc(path); | |
397 | } |