xref: /aosp_15_r20/external/coreboot/util/cbfstool/partitioned_file.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /* read and write binary file "partitions" described by FMAP */
2 /* SPDX-License-Identifier: GPL-2.0-only */
3 
4 #define __BSD_VISIBLE 1
5 
6 #include "partitioned_file.h"
7 
8 #include "cbfs_sections.h"
9 
10 #include <assert.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <sys/file.h>
14 
15 struct partitioned_file {
16 	struct fmap *fmap;
17 	struct buffer buffer;
18 	FILE *stream;
19 };
20 
fill_ones_through(struct partitioned_file * file)21 static bool fill_ones_through(struct partitioned_file *file)
22 {
23 	assert(file);
24 
25 	memset(file->buffer.data, 0xff, file->buffer.size);
26 	return partitioned_file_write_region(file, &file->buffer);
27 }
28 
count_selected_fmap_entries(const struct fmap * fmap,partitioned_file_fmap_selector_t callback,const void * arg)29 static unsigned count_selected_fmap_entries(const struct fmap *fmap,
30 		partitioned_file_fmap_selector_t callback, const void *arg)
31 {
32 	assert(fmap);
33 	assert(callback);
34 
35 	unsigned count = 0;
36 	for (unsigned i = 0; i < fmap->nareas; ++i) {
37 		if (callback(fmap->areas + i, arg))
38 			++count;
39 	}
40 	return count;
41 }
42 
reopen_flat_file(const char * filename,bool write_access)43 static partitioned_file_t *reopen_flat_file(const char *filename,
44 					    bool write_access)
45 {
46 	assert(filename);
47 	struct partitioned_file *file = calloc(1, sizeof(*file));
48 	const char *access_mode;
49 
50 	if (!file) {
51 		ERROR("Failed to allocate partitioned file structure\n");
52 		return NULL;
53 	}
54 
55 	if (buffer_from_file(&file->buffer, filename)) {
56 		free(file);
57 		return NULL;
58 	}
59 
60 	access_mode = write_access ?  "rb+" : "rb";
61 	file->stream = fopen(filename, access_mode);
62 
63 	if (!file->stream || flock(fileno(file->stream), LOCK_EX)) {
64 		perror(filename);
65 		partitioned_file_close(file);
66 		return NULL;
67 	}
68 
69 	return file;
70 }
71 
partitioned_file_create_flat(const char * filename,size_t image_size)72 partitioned_file_t *partitioned_file_create_flat(const char *filename,
73 							size_t image_size)
74 {
75 	assert(filename);
76 
77 	struct partitioned_file *file = calloc(1, sizeof(*file));
78 	if (!file) {
79 		ERROR("Failed to allocate partitioned file structure\n");
80 		return NULL;
81 	}
82 
83 	file->stream = fopen(filename, "wb");
84 	if (!file->stream || flock(fileno(file->stream), LOCK_EX)) {
85 		perror(filename);
86 		free(file);
87 		return NULL;
88 	}
89 
90 	if (buffer_create(&file->buffer, image_size, filename)) {
91 		partitioned_file_close(file);
92 		return NULL;
93 	}
94 
95 	if (!fill_ones_through(file)) {
96 		partitioned_file_close(file);
97 		return NULL;
98 	}
99 
100 	return file;
101 }
102 
partitioned_file_create(const char * filename,struct buffer * flashmap)103 partitioned_file_t *partitioned_file_create(const char *filename,
104 							struct buffer *flashmap)
105 {
106 	assert(filename);
107 	assert(flashmap);
108 	assert(flashmap->data);
109 
110 	if (fmap_find((const uint8_t *)flashmap->data, flashmap->size) != 0) {
111 		ERROR("Attempted to create a partitioned image out of something that isn't an FMAP\n");
112 		return NULL;
113 	}
114 	struct fmap *bootstrap_fmap = (struct fmap *)flashmap->data;
115 
116 	const struct fmap_area *fmap_area =
117 			fmap_find_area(bootstrap_fmap, SECTION_NAME_FMAP);
118 	if (!fmap_area) {
119 		ERROR("Provided FMAP missing '%s' region\n", SECTION_NAME_FMAP);
120 		return NULL;
121 	}
122 
123 	if (count_selected_fmap_entries(bootstrap_fmap,
124 		partitioned_file_fmap_select_children_of, fmap_area)) {
125 		ERROR("Provided FMAP's '%s' region contains other regions\n",
126 							SECTION_NAME_FMAP);
127 		return NULL;
128 	}
129 
130 	int fmap_len = fmap_size(bootstrap_fmap);
131 	if (fmap_len < 0) {
132 		ERROR("Unable to determine size of provided FMAP\n");
133 		return NULL;
134 	}
135 	assert((size_t)fmap_len <= flashmap->size);
136 	if ((uint32_t)fmap_len > fmap_area->size) {
137 		ERROR("Provided FMAP's '%s' region needs to be at least %d bytes\n",
138 						SECTION_NAME_FMAP, fmap_len);
139 		return NULL;
140 	}
141 
142 	partitioned_file_t *file = partitioned_file_create_flat(filename,
143 							bootstrap_fmap->size);
144 	if (!file)
145 		return NULL;
146 
147 	struct buffer fmap_region;
148 	buffer_splice(&fmap_region, &file->buffer, fmap_area->offset, fmap_area->size);
149 	memcpy(fmap_region.data, bootstrap_fmap, fmap_len);
150 	if (!partitioned_file_write_region(file, &fmap_region)) {
151 		partitioned_file_close(file);
152 		return NULL;
153 	}
154 	file->fmap = (struct fmap *)(file->buffer.data + fmap_area->offset);
155 
156 	return file;
157 }
158 
partitioned_file_reopen(const char * filename,bool write_access)159 partitioned_file_t *partitioned_file_reopen(const char *filename,
160 					    bool write_access)
161 {
162 	assert(filename);
163 
164 	partitioned_file_t *file = reopen_flat_file(filename, write_access);
165 	if (!file)
166 		return NULL;
167 
168 	long fmap_region_offset = fmap_find((const uint8_t *)file->buffer.data,
169 							file->buffer.size);
170 	if (fmap_region_offset < 0) {
171 		INFO("Opening image as a flat file because it doesn't contain any FMAP\n");
172 		return file;
173 	}
174 	file->fmap = (struct fmap *)(file->buffer.data + fmap_region_offset);
175 
176 	if (file->fmap->size > file->buffer.size) {
177 		int fmap_region_size = fmap_size(file->fmap);
178 		ERROR("FMAP records image size as %u, but file is only %zu bytes%s\n",
179 					file->fmap->size, file->buffer.size,
180 						fmap_region_offset == 0 &&
181 				(signed)file->buffer.size == fmap_region_size ?
182 				" (is it really an image, or *just* an FMAP?)" :
183 					" (did something truncate this file?)");
184 		partitioned_file_close(file);
185 		return NULL;
186 	}
187 
188 	const struct fmap_area *fmap_fmap_entry =
189 				fmap_find_area(file->fmap, SECTION_NAME_FMAP);
190 
191 	if (!fmap_fmap_entry) {
192 		partitioned_file_close(file);
193 		return NULL;
194 	}
195 
196 	if ((long)fmap_fmap_entry->offset != fmap_region_offset) {
197 		ERROR("FMAP's '%s' section doesn't point back to FMAP start (did something corrupt this file?)\n",
198 							SECTION_NAME_FMAP);
199 		partitioned_file_close(file);
200 		return NULL;
201 	}
202 
203 	return file;
204 }
205 
partitioned_file_write_region(partitioned_file_t * file,const struct buffer * buffer)206 bool partitioned_file_write_region(partitioned_file_t *file,
207 						const struct buffer *buffer)
208 {
209 	assert(file);
210 	assert(file->stream);
211 	assert(buffer);
212 	assert(buffer->data);
213 
214 	if (buffer->data - buffer->offset != file->buffer.data) {
215 		ERROR("Attempted to write a partition buffer back to a different file than it came from\n");
216 		return false;
217 	}
218 	if (buffer->offset + buffer->size > file->buffer.size) {
219 		ERROR("Attempted to write data off the end of image file\n");
220 		return false;
221 	}
222 
223 	if (fseek(file->stream, buffer->offset, SEEK_SET)) {
224 		ERROR("Failed to seek within image file\n");
225 		return false;
226 	}
227 	if (!fwrite(buffer->data, buffer->size, 1, file->stream)) {
228 		ERROR("Failed to write to image file\n");
229 		return false;
230 	}
231 	return true;
232 }
233 
partitioned_file_read_region(struct buffer * dest,const partitioned_file_t * file,const char * region)234 bool partitioned_file_read_region(struct buffer *dest,
235 			const partitioned_file_t *file, const char *region)
236 {
237 	assert(dest);
238 	assert(file);
239 	assert(file->buffer.data);
240 	assert(region);
241 
242 	if (file->fmap) {
243 		const struct fmap_area *area = fmap_find_area(file->fmap,
244 									region);
245 		if (!area) {
246 			ERROR("Image is missing '%s' region\n", region);
247 			return false;
248 		}
249 		if (area->offset + area->size > file->buffer.size) {
250 			ERROR("Region '%s' runs off the end of the image file\n",
251 									region);
252 			return false;
253 		}
254 		buffer_splice(dest, &file->buffer, area->offset, area->size);
255 	} else {
256 		if (strcmp(region, SECTION_NAME_PRIMARY_CBFS) != 0) {
257 			ERROR("This is a legacy image that contains only a CBFS\n");
258 			return false;
259 		}
260 		buffer_clone(dest, &file->buffer);
261 	}
262 
263 	return true;
264 }
265 
partitioned_file_close(partitioned_file_t * file)266 void partitioned_file_close(partitioned_file_t *file)
267 {
268 	if (!file)
269 		return;
270 
271 	file->fmap = NULL;
272 	buffer_delete(&file->buffer);
273 	if (file->stream) {
274 		flock(fileno(file->stream), LOCK_UN);
275 		fclose(file->stream);
276 		file->stream = NULL;
277 	}
278 	free(file);
279 }
280 
partitioned_file_is_partitioned(const partitioned_file_t * file)281 bool partitioned_file_is_partitioned(const partitioned_file_t *file)
282 {
283 	return partitioned_file_get_fmap(file) != NULL;
284 }
285 
partitioned_file_total_size(const partitioned_file_t * file)286 size_t partitioned_file_total_size(const partitioned_file_t *file)
287 {
288 	assert(file);
289 
290 	return file->buffer.size;
291 }
292 
partitioned_file_region_check_magic(const partitioned_file_t * file,const char * region,const char * magic,size_t magic_len)293 bool partitioned_file_region_check_magic(const partitioned_file_t *file,
294 			const char *region, const char *magic, size_t magic_len)
295 {
296 	struct buffer area;
297 	return partitioned_file_read_region(&area, file, region) &&
298 				buffer_check_magic(&area, magic, magic_len);
299 }
300 
partitioned_file_region_contains_nested(const partitioned_file_t * file,const char * region)301 bool partitioned_file_region_contains_nested(const partitioned_file_t *file,
302 							const char *region)
303 {
304 	assert(file);
305 	assert(region);
306 
307 	if (!file->fmap)
308 		return false;
309 	const struct fmap_area *area = fmap_find_area(file->fmap, region);
310 	return area && partitioned_file_fmap_count(file,
311 			partitioned_file_fmap_select_children_of, area);
312 }
313 
partitioned_file_get_fmap(const partitioned_file_t * file)314 const struct fmap *partitioned_file_get_fmap(const partitioned_file_t *file)
315 {
316 	assert(file);
317 
318 	return file->fmap;
319 }
320 
partitioned_file_fmap_count(const partitioned_file_t * file,partitioned_file_fmap_selector_t callback,const void * arg)321 unsigned partitioned_file_fmap_count(const partitioned_file_t *file,
322 		partitioned_file_fmap_selector_t callback, const void *arg)
323 {
324 	assert(file);
325 	assert(callback);
326 
327 	if (!file->fmap)
328 		return 0;
329 	return count_selected_fmap_entries(file->fmap, callback, arg);
330 }
331 
select_all(unused const struct fmap_area * area,unused const void * arg)332 static bool select_all(unused const struct fmap_area *area,
333 							unused const void *arg)
334 {
335 	return true;
336 }
337 const partitioned_file_fmap_selector_t partitioned_file_fmap_select_all =
338 								select_all;
339 
select_children_of(const struct fmap_area * child,const void * arg)340 static bool select_children_of(const struct fmap_area *child, const void *arg)
341 {
342 	assert(child);
343 	assert(arg);
344 
345 	const struct fmap_area *parent = (const struct fmap_area *)arg;
346 	if (child == arg || (child->offset == parent->offset &&
347 						child->size == parent->size))
348 		return false;
349 	return child->offset >= parent->offset &&
350 		child->offset + child->size <= parent->offset + parent->size;
351 }
352 const partitioned_file_fmap_selector_t
353 		partitioned_file_fmap_select_children_of = select_children_of;
354 
select_parents_of(const struct fmap_area * parent,const void * arg)355 static bool select_parents_of(const struct fmap_area *parent, const void *arg)
356 {
357 	return select_children_of((const struct fmap_area *)arg, parent);
358 }
359 const partitioned_file_fmap_selector_t partitioned_file_fmap_select_parents_of =
360 							select_parents_of;
361