xref: /illumos-gate/usr/src/common/fs/bootrd_cpio.c (revision 44af466baa3420f5636d8d7d1c9279f8bf27ce23)
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