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