/src/barebox/lib/loadable-file.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-only |
2 | | |
3 | | #include <fs.h> |
4 | | #include <libfile.h> |
5 | | #include <linux/ctype.h> |
6 | | #include <linux/minmax.h> |
7 | | #include <linux/sprintf.h> |
8 | | #include <linux/stat.h> |
9 | | #include <loadable.h> |
10 | | #include <string.h> |
11 | | #include <xfuncs.h> |
12 | | |
13 | | struct file_loadable_priv { |
14 | | char *path; |
15 | | int fd; |
16 | | int read_fd; |
17 | | }; |
18 | | |
19 | | static int file_loadable_get_info(struct loadable *l, |
20 | | struct loadable_info *info) |
21 | 0 | { |
22 | 0 | struct file_loadable_priv *priv = l->priv; |
23 | 0 | struct stat s; |
24 | 0 | int ret; |
25 | |
|
26 | 0 | ret = stat(priv->path, &s); |
27 | 0 | if (ret) |
28 | 0 | return ret; |
29 | | |
30 | 0 | if (s.st_size == FILE_SIZE_STREAM) |
31 | 0 | s.st_size = LOADABLE_SIZE_UNKNOWN; |
32 | 0 | else if (FILE_SIZE_STREAM != LOADABLE_SIZE_UNKNOWN && |
33 | 0 | s.st_size > LOADABLE_SIZE_UNKNOWN) |
34 | 0 | return -EOVERFLOW; |
35 | | |
36 | 0 | info->final_size = s.st_size; |
37 | |
|
38 | 0 | return 0; |
39 | 0 | } |
40 | | |
41 | | static int file_loadable_open_and_lseek(struct file_loadable_priv *priv, loff_t pos) |
42 | 0 | { |
43 | 0 | int ret, fd = priv->read_fd; |
44 | |
|
45 | 0 | if (fd < 0) { |
46 | 0 | fd = open(priv->path, O_RDONLY); |
47 | 0 | if (fd < 0) |
48 | 0 | return fd; |
49 | 0 | } |
50 | | |
51 | 0 | if (lseek(fd, pos, SEEK_SET) != pos) { |
52 | 0 | ret = -errno; |
53 | 0 | priv->read_fd = -1; |
54 | 0 | close(fd); |
55 | 0 | return ret; |
56 | 0 | } |
57 | | |
58 | 0 | priv->read_fd = fd; |
59 | 0 | return fd; |
60 | 0 | } |
61 | | |
62 | | /** |
63 | | * file_loadable_extract_into_buf - load file data to target address |
64 | | * @l: loadable representing a file |
65 | | * @load_addr: virtual address to load data to |
66 | | * @size: size of buffer at load_addr |
67 | | * @offset: offset within the file to start reading from |
68 | | * @flags: A bitmask of OR-ed LOADABLE_EXTRACT_ flags |
69 | | * |
70 | | * Extracts the file to the specified memory address. |
71 | | * |
72 | | * File is read as-is from the filesystem. |
73 | | * The caller must provide a valid address range; this function does not allocate |
74 | | * memory. |
75 | | * |
76 | | * Return: actual number of bytes read on success, negative errno on error |
77 | | * -ENOSPC if size is too small for the file (only when loading from start) |
78 | | */ |
79 | | static ssize_t file_loadable_extract_into_buf(struct loadable *l, |
80 | | void *load_addr, size_t size, |
81 | | loff_t offset, unsigned flags) |
82 | 0 | { |
83 | 0 | struct file_loadable_priv *priv = l->priv; |
84 | 0 | char buf; |
85 | 0 | ssize_t ret; |
86 | 0 | int fd; |
87 | |
|
88 | 0 | fd = file_loadable_open_and_lseek(priv, offset); |
89 | 0 | if (fd < 0) |
90 | 0 | return fd; |
91 | | |
92 | 0 | ret = __read_full_anywhere(fd, load_addr, size); |
93 | 0 | if (!(flags & LOADABLE_EXTRACT_PARTIAL) && ret == size && |
94 | 0 | __read_full_anywhere(fd, &buf, 1) > 0) |
95 | 0 | ret = -ENOSPC; |
96 | |
|
97 | 0 | return ret; |
98 | 0 | } |
99 | | |
100 | | /** |
101 | | * file_loadable_extract - allocate buffer and load file data into it |
102 | | * @l: loadable representing a file |
103 | | * @size: on success, set to the number of bytes read |
104 | | * |
105 | | * Allocates a buffer and extracts the file data into it. |
106 | | * |
107 | | * No decompression is performed - the file is read as-is from the filesystem. |
108 | | * The caller is responsible for freeing the returned buffer with free(). |
109 | | * |
110 | | * Return: allocated buffer, NULL for zero-size, or error pointer on failure |
111 | | */ |
112 | | static void *file_loadable_extract(struct loadable *l, size_t *size) |
113 | 0 | { |
114 | 0 | struct file_loadable_priv *priv = l->priv; |
115 | 0 | void *buf; |
116 | 0 | int ret; |
117 | |
|
118 | 0 | ret = read_file_2(priv->path, size, &buf, FILESIZE_MAX); |
119 | 0 | if (ret) |
120 | 0 | return ERR_PTR(ret); |
121 | | |
122 | 0 | if (*size == 0) { |
123 | 0 | free(buf); |
124 | 0 | buf = NULL; |
125 | 0 | } |
126 | |
|
127 | 0 | return buf; |
128 | 0 | } |
129 | | |
130 | | /** |
131 | | * file_loadable_mmap - memory map file data |
132 | | * @l: loadable representing a file |
133 | | * @size: on success, set to the size of the mapped region |
134 | | * |
135 | | * Memory maps the file without copying data into a separate buffer. |
136 | | * |
137 | | * No decompression is performed - the file is mapped as-is from the filesystem. |
138 | | * |
139 | | * Return: read-only pointer to mapped data, NULL for zero-size, or MAP_FAILED on error |
140 | | */ |
141 | | static const void *file_loadable_mmap(struct loadable *l, size_t *size) |
142 | 0 | { |
143 | 0 | struct file_loadable_priv *priv = l->priv; |
144 | 0 | struct stat st; |
145 | 0 | void *buf; |
146 | 0 | int fd, ret; |
147 | |
|
148 | 0 | if (unlikely(priv->fd >= 0)) |
149 | 0 | return MAP_FAILED; |
150 | | |
151 | | /* Reuse the read fd if already open, otherwise open a new one */ |
152 | 0 | if (priv->read_fd >= 0) { |
153 | 0 | fd = priv->read_fd; |
154 | 0 | priv->read_fd = -1; |
155 | 0 | } else { |
156 | 0 | fd = open(priv->path, O_RDONLY); |
157 | 0 | } |
158 | 0 | if (fd < 0) |
159 | 0 | return MAP_FAILED; |
160 | | |
161 | 0 | ret = fstat(fd, &st); |
162 | 0 | if (ret || st.st_size == FILE_SIZE_STREAM) |
163 | 0 | goto err; |
164 | 0 | if (!st.st_size) { |
165 | 0 | close(fd); |
166 | 0 | *size = 0; |
167 | 0 | return NULL; |
168 | 0 | } |
169 | | |
170 | 0 | buf = memmap(fd, PROT_READ); |
171 | 0 | if (buf == MAP_FAILED) |
172 | 0 | goto err; |
173 | | |
174 | 0 | *size = min_t(u64, st.st_size, SIZE_MAX); |
175 | 0 | priv->fd = fd; |
176 | 0 | return buf; |
177 | | |
178 | 0 | err: |
179 | 0 | close(fd); |
180 | 0 | return MAP_FAILED; |
181 | 0 | } |
182 | | |
183 | | static void file_loadable_munmap(struct loadable *l, const void *buf, |
184 | | size_t size) |
185 | 0 | { |
186 | 0 | struct file_loadable_priv *priv = l->priv; |
187 | |
|
188 | 0 | if (priv->fd >= 0) { |
189 | 0 | close(priv->fd); |
190 | 0 | priv->fd = -1; |
191 | 0 | } |
192 | 0 | } |
193 | | |
194 | | static void file_loadable_release(struct loadable *l) |
195 | 0 | { |
196 | 0 | struct file_loadable_priv *priv = l->priv; |
197 | |
|
198 | 0 | if (priv->read_fd >= 0) |
199 | 0 | close(priv->read_fd); |
200 | 0 | if (priv->fd >= 0) |
201 | 0 | close(priv->fd); |
202 | 0 | free(priv->path); |
203 | 0 | free(priv); |
204 | 0 | } |
205 | | |
206 | | static const struct loadable_ops file_loadable_ops = { |
207 | | .get_info = file_loadable_get_info, |
208 | | .extract_into_buf = file_loadable_extract_into_buf, |
209 | | .extract = file_loadable_extract, |
210 | | .mmap = file_loadable_mmap, |
211 | | .munmap = file_loadable_munmap, |
212 | | .release = file_loadable_release, |
213 | | }; |
214 | | |
215 | | /** |
216 | | * loadable_from_file - create a loadable from filesystem file |
217 | | * @path: filesystem path to the file |
218 | | * @type: type of loadable (LOADABLE_KERNEL, LOADABLE_INITRD, etc.) |
219 | | * |
220 | | * Creates a loadable structure that wraps access to a file in the filesystem. |
221 | | * The file is read directly during extract or via mmap with no decompression |
222 | | * - it is loaded as-is from the filesystem. |
223 | | * |
224 | | * The created loadable must be freed with loadable_release() when done. |
225 | | * The file path is copied internally, so the caller's string can be freed. |
226 | | * |
227 | | * Return: pointer to allocated loadable on success, ERR_PTR() on error |
228 | | */ |
229 | | struct loadable *loadable_from_file(const char *path, enum loadable_type type) |
230 | 0 | { |
231 | 0 | char *cpath; |
232 | 0 | struct loadable *l; |
233 | 0 | struct file_loadable_priv *priv; |
234 | |
|
235 | 0 | cpath = canonicalize_path(AT_FDCWD, path); |
236 | 0 | if (!cpath) { |
237 | 0 | const char *typestr = loadable_type_tostr(type); |
238 | 0 | int ret = -errno; |
239 | |
|
240 | 0 | pr_err("%s%simage \"%s\" not found: %m\n", |
241 | 0 | typestr, typestr ? " ": "", path); |
242 | |
|
243 | 0 | return ERR_PTR(ret); |
244 | 0 | } |
245 | | |
246 | 0 | l = xzalloc(sizeof(*l)); |
247 | 0 | priv = xzalloc(sizeof(*priv)); |
248 | |
|
249 | 0 | priv->path = cpath; |
250 | 0 | priv->fd = -1; |
251 | 0 | priv->read_fd = -1; |
252 | |
|
253 | 0 | l->name = xasprintf("File(%s)", path); |
254 | 0 | l->type = type; |
255 | 0 | l->ops = &file_loadable_ops; |
256 | 0 | l->priv = priv; |
257 | 0 | loadable_init(l); |
258 | |
|
259 | 0 | return l; |
260 | 0 | } |
261 | | |
262 | | /** |
263 | | * loadables_from_files - create loadables from a list of file paths |
264 | | * @l: pointer to loadable chain (updated on return) |
265 | | * @files: delimited list of file paths |
266 | | * @delimiters: each character on its own is a valid delimiter for @files |
267 | | * @type: type of loadable (LOADABLE_KERNEL, LOADABLE_INITRD, etc.) |
268 | | * |
269 | | * This helper splits up @files by any character in @delimiters and creates |
270 | | * loadables for every file it extracts. File extraction is done as follows: |
271 | | * |
272 | | * - Multiple whitespace is treated as if it were a single whitespace. |
273 | | * - Leading and trailing whitespace is ignored |
274 | | * - When a file name is empty, because a non-whitespace delimiter was placed |
275 | | * at the beginning, the end or two delimiters were repeated, the original |
276 | | * loadable from @l is spliced into the chain at that location. If @l is NULL, |
277 | | * nothing happens |
278 | | * - Regular non-empty file names have loadables created for them |
279 | | * |
280 | | * This is basically the algorithm by which PATH is interpreted in |
281 | | * a POSIX-conformant shell (when delimiters=":" and the original is the current |
282 | | * working directory). |
283 | | * |
284 | | * Return: 0 on success, negative errno on failure |
285 | | */ |
286 | | int loadables_from_files(struct loadable **l, |
287 | | const char *files, const char *delimiters, |
288 | | enum loadable_type type) |
289 | 0 | { |
290 | 0 | struct loadable *orig = *l; |
291 | 0 | struct loadable *main = NULL; |
292 | 0 | struct loadable *lf, *ltmp; |
293 | 0 | char *file, *buf, *origbuf = xstrdup(files); |
294 | 0 | LIST_HEAD(tmp); |
295 | 0 | bool orig_included = false; |
296 | 0 | char delim; |
297 | |
|
298 | 0 | if (isempty(files)) |
299 | 0 | goto out; |
300 | | |
301 | 0 | buf = origbuf; |
302 | 0 | while ((file = strsep_unescaped(&buf, delimiters, &delim))) { |
303 | 0 | if (*file == '\0') { |
304 | 0 | if (!isspace(delim) && !orig_included && orig) { |
305 | 0 | list_add_tail(&orig->list, &tmp); |
306 | 0 | orig_included = true; |
307 | 0 | } |
308 | 0 | continue; |
309 | 0 | } |
310 | | |
311 | 0 | lf = loadable_from_file(file, type); |
312 | 0 | if (IS_ERR(lf)) { |
313 | 0 | int ret = PTR_ERR(lf); |
314 | |
|
315 | 0 | if (orig_included) |
316 | 0 | list_del_init(&orig->list); |
317 | 0 | list_for_each_entry_safe(lf, ltmp, &tmp, list) |
318 | 0 | loadable_release(&lf); |
319 | 0 | free(origbuf); |
320 | 0 | return ret; |
321 | 0 | } |
322 | | |
323 | 0 | list_add_tail(&lf->list, &tmp); |
324 | 0 | } |
325 | | |
326 | 0 | free(origbuf); |
327 | | |
328 | | /* Build the final chain from collected entries */ |
329 | 0 | list_for_each_entry_safe(lf, ltmp, &tmp, list) { |
330 | 0 | list_del_init(&lf->list); |
331 | 0 | loadable_chain(&main, lf); |
332 | 0 | } |
333 | |
|
334 | 0 | out: |
335 | 0 | if (!orig_included) |
336 | 0 | loadable_release(&orig); |
337 | |
|
338 | 0 | *l = main; |
339 | 0 | return 0; |
340 | 0 | } |