xref: /aosp_15_r20/external/vboot_reference/futility/cmd_read.c (revision 8617a60d3594060b7ecbd21bc622a7c14f3cf2bc)
1 /* Copyright 2022 The ChromiumOS Authors
2  * Use of this source code is governed by a BSD-style license that can be
3  * found in the LICENSE file.
4  */
5 
6 #include <assert.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <getopt.h>
10 
11 #include "flash_helpers.h"
12 #include "futility.h"
13 #include "updater.h"
14 
15 #ifdef USE_FLASHROM
16 
17 /* Command line options */
18 static struct option const long_opts[] = {
19 	SHARED_FLASH_ARGS_LONGOPTS
20 	/* name  has_arg *flag val */
21 	{"help", 0, NULL, 'h'},
22 	{"debug", 0, NULL, 'd'},
23 	{"region", 1, NULL, 'r'},
24 	{"split-output", 0, NULL, 's'},
25 	{"verbose", 0, NULL, 'v'},
26 	{NULL, 0, NULL, 0},
27 };
28 
29 static const char *const short_opts = "hdsr:v" SHARED_FLASH_ARGS_SHORTOPTS;
30 
print_help(int argc,char * argv[])31 static void print_help(int argc, char *argv[])
32 {
33 	printf("\n"
34 	       "Usage:  " MYNAME " %s [OPTIONS] FILE\n"
35 	       "\n"
36 	       "Reads AP firmware to the FILE\n"
37 	       "-d, --debug            \tPrint debugging messages\n"
38 	       "-r, --region=REGIONS   \tComma delimited regions to read (optional)\n"
39 	       "-v, --verbose          \tPrint verbose messages\n"
40 	       "-s, --split-output     \tOutput each comma delimited regions to own {FILE}_{region_name} (optional)\n"
41 	       SHARED_FLASH_ARGS_HELP,
42 	       argv[0]);
43 }
44 
append_to_str_array(const char *** arr,const char * str,size_t * len)45 static int append_to_str_array(const char ***arr, const char *str, size_t *len)
46 {
47 	const char **expanded_arr = realloc(*arr, sizeof(char *) * ((*len) + 1));
48 	if (!expanded_arr) {
49 		WARN("Failed to allocate memory for string array");
50 		return -1;
51 	}
52 
53 	*arr = expanded_arr;
54 	(*arr)[*len] = str;
55 	(*len)++;
56 	return 0;
57 }
58 
parse_region_string(const char *** regions,char * str,size_t * rlen)59 static int parse_region_string(const char ***regions, char *str, size_t *rlen)
60 {
61 	if (!str)
62 		return -1; /* no regions to parse. */
63 
64 	char *savedptr;
65 	const char *delim = ",";
66 	char *region = strtok_r(str, delim, &savedptr);
67 
68 	while (region) {
69 		if (append_to_str_array(regions, region, rlen))
70 			return -1;
71 		region = strtok_r(NULL, delim, &savedptr);
72 	}
73 
74 	return 0;
75 }
76 
read_flash_regions_to_file(struct updater_config * cfg,const char * path,char * str,bool do_split)77 static int read_flash_regions_to_file(struct updater_config *cfg,
78 				      const char *path, char *str,
79 				      bool do_split)
80 {
81 	int ret = 0;
82 	size_t rlen = 0;
83 	const char **regions = NULL;
84 
85 	if (parse_region_string(&regions, str, &rlen) || !regions || !rlen) {
86 		WARN("No parsable regions to process.\n");
87 		ret = -1;
88 		goto out_free;
89 	}
90 
91 	/* Need to read the FMAP to find regions if we are going to write each
92 	 * region to a separate file. */
93 	if (do_split) {
94 		if (append_to_str_array(&regions, FMAP_RO_FMAP, &rlen)) {
95 			ret = -1;
96 			goto out_free;
97 		}
98 	}
99 
100 	/* Read only the specified regions */
101 	if (flashrom_read_image(&cfg->image_current, regions,
102 				rlen, cfg->verbosity + 1)) {
103 		ret = -1;
104 		goto out_free;
105 	}
106 
107 	if (!do_split) {
108 		if (write_to_file("Wrote AP firmware region to", path,
109 				  cfg->image_current.data,
110 				  cfg->image_current.size)) {
111 			return -1;
112 		}
113 		return 0;
114 	}
115 
116 	/*
117 	 * The last element of regions is FMAP_RO_FMAP, stop at (rlen-1) to
118 	 * only process the user-specified regions.
119 	 */
120 	for (size_t i = 0; i < rlen - 1; i++) {
121 		const char *region = regions[i];
122 
123 		struct firmware_section section;
124 		if (find_firmware_section(&section, &cfg->image_current, region)) {
125 			ERROR("Region '%s' not found in image.\n", region);
126 			ret = -1;
127 			goto out_free;
128 		}
129 		const char *separator = "_";
130 		const size_t fpath_sz = strlen(path) +
131 					strlen(separator) +
132 					strlen(region) +
133 					1; /* +1 for null termination. */
134 		char *fpath = calloc(1, fpath_sz);
135 		if (!fpath) {
136 			ret = -1;
137 			goto out_free;
138 		}
139 		snprintf(fpath, fpath_sz, "%s%s%s", path, separator, region);
140 		if (write_to_file("Wrote AP firmware region to",
141 				  fpath, section.data, section.size)) {
142 			free(fpath);
143 			ret = -1;
144 			goto out_free;
145 		}
146 		free(fpath);
147 	}
148 
149 out_free:
150 	free(regions);
151 	return ret;
152 }
153 
do_read(int argc,char * argv[])154 static int do_read(int argc, char *argv[])
155 {
156 	struct updater_config *cfg = NULL;
157 	struct updater_config_arguments args = {0};
158 	int i, errorcnt = 0;
159 	char *regions = NULL;
160 	bool do_split = false;
161 
162 	opterr = 0;
163 	while ((i = getopt_long(argc, argv, short_opts, long_opts, 0)) != -1) {
164 		if (handle_flash_argument(&args, i, optarg))
165 			continue;
166 		switch (i) {
167 		case 'h':
168 			print_help(argc, argv);
169 			return 0;
170 		case 'd':
171 			debugging_enabled = 1;
172 			args.verbosity++;
173 			break;
174 		case 'r':
175 			regions = strdup(optarg);
176 			if (!regions) {
177 				ERROR("strdup() returned NULL\n");
178 				return 1;
179 			}
180 			break;
181 		case 's':
182 			do_split = true;
183 			break;
184 		case 'v':
185 			args.verbosity++;
186 			break;
187 		case '?':
188 			errorcnt++;
189 			if (optopt)
190 				ERROR("Unrecognized option: -%c\n", optopt);
191 			else if (argv[optind - 1])
192 				ERROR("Unrecognized option (possibly '%s')\n",
193 				      argv[optind - 1]);
194 			else
195 				ERROR("Unrecognized option.\n");
196 			break;
197 		default:
198 			errorcnt++;
199 			ERROR("Failed parsing options.\n");
200 		}
201 	}
202 	if (argc - optind < 1) {
203 		ERROR("Missing output filename\n");
204 		print_help(argc, argv);
205 		return 1;
206 	}
207 	const char *output_file_name = argv[optind++];
208 	if (optind < argc) {
209 		ERROR("Unexpected arguments.\n");
210 		print_help(argc, argv);
211 		return 1;
212 	}
213 	if (do_split && !regions) {
214 		ERROR("Cannot split read of regions without list of regions.\n");
215 		print_help(argc, argv);
216 		return 1;
217 	}
218 
219 	if (setup_flash(&cfg, &args)) {
220 		ERROR("While preparing flash\n");
221 		return 1;
222 	}
223 
224 	if (!regions) {
225 		/* full image read. */
226 		int r = load_system_firmware(cfg, &cfg->image_current);
227 		/*
228 		 * Ignore a parse error as we still want to write the file
229 		 * out in that case
230 		 */
231 		if (r && r != IMAGE_PARSE_FAILURE) {
232 			errorcnt++;
233 			goto err;
234 		}
235 		if (write_to_file("Wrote AP firmware to", output_file_name,
236 				  cfg->image_current.data,
237 				  cfg->image_current.size)) {
238 			errorcnt++;
239 			goto err;
240 		}
241 	} else {
242 		if (read_flash_regions_to_file(cfg, output_file_name, regions, do_split))
243 			errorcnt++;
244 		free(regions);
245 	}
246 
247 err:
248 	teardown_flash(cfg);
249 	return !!errorcnt;
250 }
251 #define CMD_HELP_STR "Read AP firmware"
252 
253 #else /* USE_FLASHROM */
254 
do_read(int argc,char * argv[])255 static int do_read(int argc, char *argv[])
256 {
257 	FATAL(MYNAME " was built without flashrom support, `read` command unavailable!\n");
258 	return -1;
259 }
260 #define CMD_HELP_STR "Read system firmware (unavailable in this build)"
261 
262 #endif /* !USE_FLASHROM */
263 
264 DECLARE_FUTIL_COMMAND(read, do_read, VBOOT_VERSION_ALL, CMD_HELP_STR);
265