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