Coverage Report

Created: 2026-10-13 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}