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
31struct 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 */
50struct 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
63extern void *bkmem_alloc(size_t);
64extern void bkmem_free(void *, size_t);
65
66static void cpio_closeall(int flag);
67
68static bool mounted;
69static SLIST_HEAD(cpio_file_list, cpio_file)
70    open_files = SLIST_HEAD_INITIALIZER(open_files);
71
72static int
73cpio_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 */
91static 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
112static bool
113get_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
119static bool
120get_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
131static bool
132get_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
143static bool
144get_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
155static void
156add_open_file(struct cpio_file *file)
157{
158	SLIST_INSERT_HEAD(&open_files, file, next);
159}
160
161static void
162remove_open_file(struct cpio_file *file)
163{
164	SLIST_REMOVE(&open_files, file, cpio_file, next);
165}
166
167static struct cpio_file *
168find_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
182static const void *
183read_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
205static bool
206parse_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 */
245static ssize_t
246scan_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
299static int
300find_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 */
335static int
336bcpio_mountroot(char *str __unused)
337{
338	if (mounted)
339		return (-1);
340
341	mounted = true;
342
343	return (0);
344}
345
346static int
347bcpio_unmountroot(void)
348{
349	if (!mounted)
350		return (-1);
351
352	mounted = false;
353
354	return (0);
355}
356
357/* ARGSUSED */
358static int
359bcpio_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
381static int
382bcpio_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 */
398static void
399bcpio_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
411static ssize_t
412bcpio_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
433static off_t
434bcpio_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
460static int
461bcpio_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
474struct 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