1 /*
2 * Copyright 2011-2017 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
3 * Copyright 2025 MNX Cloud, Inc.
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #include <sys/types.h>
19 #include <sys/stdbool.h>
20 #include <sys/sysmacros.h>
21 #include <sys/bootvfs.h>
22 #include <sys/filep.h>
23 #include <sys/sunddi.h>
24 #include <sys/ccompile.h>
25 #include <sys/kobj.h>
26 #include <sys/queue.h>
27
28 /*
29 * A cpio archive is just a sequence of files, each consisting of a header
30 * (struct cpio_hdr) and the file contents.
31 */
32
33 struct cpio_hdr {
34 uint8_t magic[6];
35 uint8_t dev[6];
36 uint8_t ino[6];
37 uint8_t mode[6];
38 uint8_t uid[6];
39 uint8_t gid[6];
40 uint8_t nlink[6];
41 uint8_t rdev[6];
42 uint8_t mtime[11];
43 uint8_t namesize[6];
44 uint8_t filesize[11];
45 char data[];
46 };
47
48 /*
49 * This structure represents an open file. The list of all open files is
50 * rooted in the open_files global.
51 */
52 struct cpio_file {
53 /* pointers into the archive */
54 const struct cpio_hdr *hdr;
55 const char *path; /* pointer into the archive */
56 const void *data; /* pointer into the archive */
57
58 int fd;
59 off_t off;
60 struct bootstat stat;
61
62 SLIST_ENTRY(cpio_file) next;
63 };
64
65 /*
66 * in bootrd.c
67 */
68 extern void *bkmem_alloc(size_t);
69 extern void bkmem_free(void *, size_t);
70
71 static void cpio_closeall(int flag);
72
73 static bool mounted;
74 static SLIST_HEAD(cpio_file_list, cpio_file)
75 open_files = SLIST_HEAD_INITIALIZER(open_files);
76
77 /*
78 * Returns the parsed number on success, or UINT64_MAX on error. This is
79 * ok because we will never deal with numbers that large in a cpio archive.
80 */
81 static uint64_t
__get_uint64(const uint8_t * str,size_t len,const size_t output_size)82 __get_uint64(const uint8_t *str, size_t len, const size_t output_size)
83 {
84 uint64_t v;
85
86 /* check that we can represent every number */
87 if (len * 3 > output_size)
88 return (UINT64_MAX);
89
90 for (v = 0; len > 0; len--, str++) {
91 const uint8_t c = *str;
92
93 if ((c < '0') || (c > '7'))
94 return (UINT64_MAX);
95
96 v = (v * 8) + (c - '0');
97 }
98
99 return (v);
100 }
101
102 static bool
get_uint64(const uint8_t * str,size_t len,uint64_t * out)103 get_uint64(const uint8_t *str, size_t len, uint64_t *out)
104 {
105 *out = __get_uint64(str, len, NBBY * sizeof (*out));
106 return (*out != UINT64_MAX);
107 }
108
109 static bool
get_int64(const uint8_t * str,size_t len,int64_t * out)110 get_int64(const uint8_t *str, size_t len, int64_t *out)
111 {
112 uint64_t tmp;
113
114 tmp = __get_uint64(str, len, NBBY * sizeof (*out) - 1);
115
116 *out = tmp;
117
118 return (tmp != UINT64_MAX);
119 }
120
121 static bool
get_uint32(const uint8_t * str,size_t len,uint32_t * out)122 get_uint32(const uint8_t *str, size_t len, uint32_t *out)
123 {
124 uint64_t tmp;
125
126 tmp = __get_uint64(str, len, NBBY * sizeof (*out));
127
128 *out = tmp;
129
130 return (tmp != UINT64_MAX);
131 }
132
133 static bool
get_int32(const uint8_t * str,size_t len,int32_t * out)134 get_int32(const uint8_t *str, size_t len, int32_t *out)
135 {
136 uint64_t tmp;
137
138 tmp = __get_uint64(str, len, NBBY * sizeof (*out) - 1);
139
140 *out = tmp;
141
142 return (tmp != UINT64_MAX);
143 }
144
145 static void
add_open_file(struct cpio_file * file)146 add_open_file(struct cpio_file *file)
147 {
148 SLIST_INSERT_HEAD(&open_files, file, next);
149 }
150
151 static void
remove_open_file(struct cpio_file * file)152 remove_open_file(struct cpio_file *file)
153 {
154 SLIST_REMOVE(&open_files, file, cpio_file, next);
155 }
156
157 static struct cpio_file *
find_open_file(int fd)158 find_open_file(int fd)
159 {
160 struct cpio_file *file;
161
162 if (fd < 0)
163 return (NULL);
164
165 SLIST_FOREACH(file, &open_files, next)
166 if (file->fd == fd)
167 return (file);
168
169 return (NULL);
170 }
171
172 static const void *
read_ramdisk(size_t off,size_t len)173 read_ramdisk(size_t off, size_t len)
174 {
175 const size_t first_block_offset = off % DEV_BSIZE;
176 fileid_t tmpfile;
177
178 /* return a dummy non-NULL pointer */
179 if (len == 0)
180 return ("");
181
182 /* we have to read the stuff before the desired location as well */
183 len += first_block_offset;
184
185 tmpfile.fi_blocknum = off / DEV_BSIZE;
186 tmpfile.fi_count = P2ROUNDUP_TYPED(len, DEV_BSIZE, size_t);
187 tmpfile.fi_memp = NULL;
188
189 if (diskread(&tmpfile) != 0)
190 return (NULL);
191
192 return (tmpfile.fi_memp + first_block_offset);
193 }
194
195 static bool
parse_stat(const struct cpio_hdr * hdr,struct bootstat * stat)196 parse_stat(const struct cpio_hdr *hdr, struct bootstat *stat)
197 {
198 if (!get_uint64(hdr->dev, sizeof (hdr->dev), &stat->st_dev))
199 return (false);
200 if (!get_uint64(hdr->ino, sizeof (hdr->ino), &stat->st_ino))
201 return (false);
202 if (!get_uint32(hdr->mode, sizeof (hdr->mode), &stat->st_mode))
203 return (false);
204 if (!get_int32(hdr->uid, sizeof (hdr->uid), &stat->st_uid))
205 return (false);
206 if (!get_int32(hdr->gid, sizeof (hdr->gid), &stat->st_gid))
207 return (false);
208 if (!get_uint32(hdr->nlink, sizeof (hdr->nlink), &stat->st_nlink))
209 return (false);
210 if (!get_uint64(hdr->rdev, sizeof (hdr->rdev), &stat->st_rdev))
211 return (false);
212
213 stat->st_mtim.tv_nsec = 0;
214 if (!get_int64(hdr->mtime, sizeof (hdr->mtime), &stat->st_mtim.tv_sec))
215 return (false);
216
217 stat->st_atim = stat->st_mtim;
218 stat->st_ctim = stat->st_mtim;
219
220 if (!get_uint64(hdr->filesize, sizeof (hdr->filesize), &stat->st_size))
221 return (false);
222
223 stat->st_blksize = DEV_BSIZE;
224 stat->st_blocks = P2ROUNDUP(stat->st_size, DEV_BSIZE);
225
226 return (true);
227 }
228
229 static int
check_archive_hdr(const struct cpio_hdr * hdr)230 check_archive_hdr(const struct cpio_hdr *hdr)
231 {
232 if ((hdr->magic[0] != '0') || (hdr->magic[1] != '7') ||
233 (hdr->magic[2] != '0') || (hdr->magic[3] != '7') ||
234 (hdr->magic[4] != '0') || (hdr->magic[5] != '7'))
235 return (-1);
236 return (0);
237 }
238
239 /*
240 * Check if specified header is for a file with a specific path. If so,
241 * fill in the file struct and return 0. If not, return number of bytes to
242 * skip over to get to the next header. If an error occurs, -1 is returned.
243 * If end of archive is reached, return -2 instead.
244 */
245 static ssize_t
scan_archive_hdr(const struct cpio_hdr * hdr,size_t off,struct cpio_file * file,const char * wanted_path)246 scan_archive_hdr(const struct cpio_hdr *hdr, size_t off,
247 struct cpio_file *file, const char *wanted_path)
248 {
249 struct bootstat stat;
250 uint32_t namesize;
251 uint64_t filesize;
252 const char *path;
253 const void *data;
254
255 if (check_archive_hdr(hdr))
256 return (-1);
257
258 if (!get_uint32(hdr->namesize, sizeof (hdr->namesize), &namesize))
259 return (-1);
260 if (!get_uint64(hdr->filesize, sizeof (hdr->filesize), &filesize))
261 return (-1);
262
263 /*
264 * We have the two sizes, let's try to read the name and file
265 * contents to make sure they are part of the ramdisk.
266 */
267
268 off += offsetof(struct cpio_hdr, data[0]);
269 path = read_ramdisk(off, namesize);
270 data = read_ramdisk(off + namesize, filesize);
271
272 /* either read failing is fatal */
273 if (path == NULL || data == NULL)
274 return (-1);
275
276 if (strcmp(path, "TRAILER!!!") == 0)
277 return (-2);
278
279 if (strcmp(path, wanted_path) != 0)
280 return (offsetof(struct cpio_hdr, data[namesize + filesize]));
281
282 /*
283 * This is the file we want!
284 */
285
286 if (!parse_stat(hdr, &stat))
287 return (-1);
288
289 file->hdr = hdr;
290 file->path = path;
291 file->data = data;
292 file->stat = stat;
293
294 return (0);
295 }
296
297 static int
find_filename(char * path,struct cpio_file * file)298 find_filename(char *path, struct cpio_file *file)
299 {
300 size_t off;
301
302 /*
303 * The paths in the cpio boot archive omit the leading '/'. So,
304 * skip checking for it. If the searched for path does not include
305 * the leading path (it's a relative path), fail the lookup.
306 */
307 if (path[0] != '/')
308 return (-1);
309
310 path++;
311
312 /* now scan the archive for the relevant file */
313
314 off = 0;
315
316 for (;;) {
317 const struct cpio_hdr *hdr;
318 ssize_t size;
319
320 hdr = (struct cpio_hdr *)read_ramdisk(off,
321 sizeof (struct cpio_hdr));
322 if (hdr == NULL)
323 return (-1);
324
325 size = scan_archive_hdr(hdr, off, file, path);
326 if (size <= 0)
327 return (size);
328
329 off += size;
330 }
331 }
332
333 static int
bcpio_mountroot(char * str __unused)334 bcpio_mountroot(char *str __unused)
335 {
336 const struct cpio_hdr *hdr;
337
338 if (mounted)
339 return (-1);
340
341 hdr = (struct cpio_hdr *)read_ramdisk(0, sizeof (struct cpio_hdr));
342 if (hdr == NULL)
343 return (-1);
344
345 if (check_archive_hdr(hdr))
346 return (-1);
347
348 mounted = true;
349
350 return (0);
351 }
352
353 static int
bcpio_unmountroot(void)354 bcpio_unmountroot(void)
355 {
356 if (!mounted)
357 return (-1);
358
359 mounted = false;
360
361 return (0);
362 }
363
364 static int
bcpio_open(char * path,int flags __unused)365 bcpio_open(char *path, int flags __unused)
366 {
367 static int filedes = 1;
368 struct cpio_file temp_file;
369 struct cpio_file *file;
370
371 if (find_filename(path, &temp_file) != 0)
372 return (-1);
373
374 file = bkmem_alloc(sizeof (struct cpio_file));
375 file->hdr = temp_file.hdr;
376 file->path = temp_file.path;
377 file->data = temp_file.data;
378 file->stat = temp_file.stat;
379 file->fd = filedes++;
380 file->off = 0;
381
382 add_open_file(file);
383
384 return (file->fd);
385 }
386
387 static int
bcpio_close(int fd)388 bcpio_close(int fd)
389 {
390 struct cpio_file *file;
391
392 file = find_open_file(fd);
393 if (file == NULL)
394 return (-1);
395
396 remove_open_file(file);
397
398 bkmem_free(file, sizeof (struct cpio_file));
399
400 return (0);
401 }
402
403 static void
bcpio_closeall(int flag __unused)404 bcpio_closeall(int flag __unused)
405 {
406 struct cpio_file *file;
407
408 while (!SLIST_EMPTY(&open_files)) {
409 file = SLIST_FIRST(&open_files);
410
411 if (bcpio_close(file->fd) != 0) {
412 kobj_printf("closeall invoked close(%d) failed\n",
413 file->fd);
414 }
415 }
416 }
417
418 static ssize_t
bcpio_read(int fd,caddr_t buf,size_t size)419 bcpio_read(int fd, caddr_t buf, size_t size)
420 {
421 struct cpio_file *file;
422
423 file = find_open_file(fd);
424 if (file == NULL)
425 return (-1);
426
427 if (size == 0)
428 return (0);
429
430 if (file->off + size > file->stat.st_size)
431 size = file->stat.st_size - file->off;
432
433 bcopy((void *)((uintptr_t)file->data + file->off), buf, size);
434
435 file->off += size;
436
437 return (size);
438 }
439
440 static off_t
bcpio_lseek(int fd,off_t addr,int whence)441 bcpio_lseek(int fd, off_t addr, int whence)
442 {
443 struct cpio_file *file;
444
445 file = find_open_file(fd);
446 if (file == NULL)
447 return (-1);
448
449 switch (whence) {
450 case SEEK_CUR:
451 file->off += addr;
452 break;
453 case SEEK_SET:
454 file->off = addr;
455 break;
456 case SEEK_END:
457 file->off = file->stat.st_size;
458 break;
459 default:
460 kobj_printf("lseek(): invalid whence value %d\n",
461 whence);
462 return (-1);
463 }
464
465 return (0);
466 }
467
468 static int
bcpio_fstat(int fd,struct bootstat * buf)469 bcpio_fstat(int fd, struct bootstat *buf)
470 {
471 const struct cpio_file *file;
472
473 file = find_open_file(fd);
474 if (file == NULL)
475 return (-1);
476
477 *buf = file->stat;
478
479 return (0);
480 }
481
482 struct boot_fs_ops bcpio_ops = {
483 .fsw_name = "boot_cpio",
484 .fsw_mountroot = bcpio_mountroot,
485 .fsw_unmountroot = bcpio_unmountroot,
486 .fsw_open = bcpio_open,
487 .fsw_close = bcpio_close,
488 .fsw_closeall = bcpio_closeall,
489 .fsw_read = bcpio_read,
490 .fsw_lseek = bcpio_lseek,
491 .fsw_fstat = bcpio_fstat,
492 };
493