xref: /freebsd/stand/userboot/test/test.c (revision 46b606c8fd9aabfdc880ca98241df443450b5791)
1 /*-
2  * Copyright (c) 2011 Google, Inc.
3  * Copyright (c) 2023-2024 Juniper Networks, Inc.
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/types.h>
29 #include <sys/disk.h>
30 #include <sys/ioctl.h>
31 #include <sys/stat.h>
32 #include <dirent.h>
33 #include <dlfcn.h>
34 #include <err.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <getopt.h>
38 #include <inttypes.h>
39 #include <libgen.h>
40 #include <limits.h>
41 #include <stdbool.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <termios.h>
46 #include <unistd.h>
47 
48 #include <userboot.h>
49 
50 char **vars;
51 
52 char *host_base = NULL;
53 struct termios term, oldterm;
54 char *image;
55 size_t image_size;
56 
57 uint64_t regs[16];
58 uint64_t pc;
59 int *disk_fd;
60 int disk_index = -1;
61 
62 void test_exit(void *arg, int v);
63 
64 /*
65  * Console i/o
66  */
67 
68 void
69 test_putc(void *arg, int ch)
70 {
71 	char c = ch;
72 
73 	write(1, &c, 1);
74 }
75 
76 int
77 test_getc(void *arg)
78 {
79 	char c;
80 
81 	if (read(0, &c, 1) == 1)
82 		return c;
83 	return -1;
84 }
85 
86 int
87 test_poll(void *arg)
88 {
89 	int n;
90 
91 	if (ioctl(0, FIONREAD, &n) >= 0)
92 		return (n > 0);
93 	return (0);
94 }
95 
96 /*
97  * Host filesystem i/o
98  */
99 
100 struct test_file {
101 	int tf_isdir;
102 	size_t tf_size;
103 	struct stat tf_stat;
104 	union {
105 		int fd;
106 		DIR *dir;
107 	} tf_u;
108 };
109 
110 static int
111 test_open_internal(void *arg, const char *filename, void **h_return,
112     struct test_file *tf, int depth)
113 {
114 	char path[PATH_MAX];
115 	char linkpath[PATH_MAX];
116 	char *component, *cp, *linkptr;
117 	ssize_t slen;
118 	int comp_fd, dir_fd, error;
119 	char c;
120 	bool openbase;
121 
122 	if (depth++ >= MAXSYMLINKS)
123 		return (ELOOP);
124 
125 	openbase = false;
126 	error = EINVAL;
127 	if (tf == NULL) {
128 		tf = calloc(1, sizeof(struct test_file));
129 		if (tf == NULL)
130 			return (error);
131 		openbase = true;
132 	} else if (tf->tf_isdir) {
133 		if (filename[0] == '/') {
134 			closedir(tf->tf_u.dir);
135 			openbase = true;
136 		}
137 	} else
138 		return (error);
139 
140 	if (openbase) {
141 		dir_fd = open(host_base, O_RDONLY);
142 		if (dir_fd < 0)
143 			goto out;
144 
145 		tf->tf_isdir = 1;
146 		tf->tf_u.dir = fdopendir(dir_fd);
147 
148 		if (fstat(dir_fd, &tf->tf_stat) < 0) {
149 			error = errno;
150 			goto out;
151 		}
152 		tf->tf_size = tf->tf_stat.st_size;
153 	}
154 
155 	strlcpy(path, filename, sizeof(path));
156 	cp = path;
157 	while (*cp) {
158 		/*
159 		 * The test file should be a directory at this point.
160 		 * If it is not, then the caller provided an invalid filename.
161 		 */
162 		if (!tf->tf_isdir)
163 			goto out;
164 
165 		/* Trim leading slashes */
166 		while (*cp == '/')
167 			cp++;
168 
169 		/* If we reached the end, we are done */
170 		if (*cp == '\0')
171 			break;
172 
173 		/* Get the file descriptor for the directory */
174 		dir_fd = dirfd(tf->tf_u.dir);
175 
176 		/* Get the next component path */
177 		component = cp;
178 		while ((c = *cp) != '\0' && c != '/')
179 			cp++;
180 		if (c == '/')
181 			*cp++ = '\0';
182 
183 		/* Get status of the component */
184 		if (fstatat(dir_fd, component, &tf->tf_stat,
185 		    AT_SYMLINK_NOFOLLOW) < 0) {
186 			error = errno;
187 			goto out;
188 		}
189 		tf->tf_size = tf->tf_stat.st_size;
190 
191 		/*
192 		 * Check that the path component is a directory, regular file,
193 		 * or a symlink.
194 		 */
195 		if (!S_ISDIR(tf->tf_stat.st_mode) &&
196 		    !S_ISREG(tf->tf_stat.st_mode) &&
197 		    !S_ISLNK(tf->tf_stat.st_mode))
198 			goto out;
199 
200 		/* For anything that is not a symlink, open it */
201 		if (!S_ISLNK(tf->tf_stat.st_mode)) {
202 			comp_fd = openat(dir_fd, component, O_RDONLY);
203 			if (comp_fd < 0)
204 				goto out;
205 		}
206 
207 		if (S_ISDIR(tf->tf_stat.st_mode)) {
208 			/* Directory */
209 
210 			/* close the parent directory */
211 			closedir(tf->tf_u.dir);
212 
213 			/* Open the directory from the component descriptor */
214 			tf->tf_isdir = 1;
215 			tf->tf_u.dir = fdopendir(comp_fd);
216 			if (!tf->tf_u.dir)
217 				goto out;
218 		} else if (S_ISREG(tf->tf_stat.st_mode)) {
219 			/* Regular file */
220 
221 			/* close the parent directory */
222 			closedir(tf->tf_u.dir);
223 
224 			/* Stash the component descriptor */
225 			tf->tf_isdir = 0;
226 			tf->tf_u.fd = comp_fd;
227 		} else if (S_ISLNK(tf->tf_stat.st_mode)) {
228 			/* Symlink */
229 
230 			/* Read what the symlink points to */
231 			slen = readlinkat(dir_fd, component, linkpath,
232 			    sizeof(linkpath));
233 			if (slen < 0)
234 				goto out;
235 			/* NUL-terminate the string */
236 			linkpath[(size_t)slen] = '\0';
237 
238 			/* Open the thing that the symlink points to */
239 			error = test_open_internal(arg, linkpath, NULL,
240 			    tf, depth);
241 			if (error != 0)
242 				goto out;
243 		}
244 	}
245 
246 	/* Completed the entire path and have a good file/directory */
247 	if (h_return != NULL)
248 		*h_return = tf;
249 	return (0);
250 
251 out:
252 	/* Failure of some sort, clean up */
253 	if (tf->tf_isdir)
254 		closedir(tf->tf_u.dir);
255 	else
256 		close(tf->tf_u.fd);
257 	free(tf);
258 	return (error);
259 }
260 
261 int
262 test_open(void *arg, const char *filename, void **h_return)
263 {
264 	if (host_base == NULL)
265 		return (ENOENT);
266 
267 	return (test_open_internal(arg, filename, h_return, NULL, 0));
268 }
269 
270 int
271 test_close(void *arg, void *h)
272 {
273 	struct test_file *tf = h;
274 
275 	if (tf->tf_isdir)
276 		closedir(tf->tf_u.dir);
277 	else
278 		close(tf->tf_u.fd);
279 	free(tf);
280 
281 	return (0);
282 }
283 
284 int
285 test_isdir(void *arg, void *h)
286 {
287 	struct test_file *tf = h;
288 
289 	return (tf->tf_isdir);
290 }
291 
292 int
293 test_read(void *arg, void *h, void *dst, size_t size, size_t *resid_return)
294 {
295 	struct test_file *tf = h;
296 	ssize_t sz;
297 
298 	if (tf->tf_isdir)
299 		return (EINVAL);
300 	sz = read(tf->tf_u.fd, dst, size);
301 	if (sz < 0)
302 		return (EINVAL);
303 	*resid_return = size - sz;
304 	return (0);
305 }
306 
307 int
308 test_readdir(void *arg, void *h, uint32_t *fileno_return, uint8_t *type_return,
309     size_t *namelen_return, char *name)
310 {
311 	struct test_file *tf = h;
312 	struct dirent *dp;
313 
314 	if (!tf->tf_isdir)
315 		return (EINVAL);
316 
317 	dp = readdir(tf->tf_u.dir);
318 	if (!dp)
319 		return (ENOENT);
320 
321 	/*
322 	 * Note: d_namlen is in the range 0..255 and therefore less
323 	 * than PATH_MAX so we don't need to test before copying.
324 	 */
325 	*fileno_return = dp->d_fileno;
326 	*type_return = dp->d_type;
327 	*namelen_return = dp->d_namlen;
328 	memcpy(name, dp->d_name, dp->d_namlen);
329 	name[dp->d_namlen] = 0;
330 
331 	return (0);
332 }
333 
334 int
335 test_seek(void *arg, void *h, uint64_t offset, int whence)
336 {
337 	struct test_file *tf = h;
338 
339 	if (tf->tf_isdir)
340 		return (EINVAL);
341 	if (lseek(tf->tf_u.fd, offset, whence) < 0)
342 		return (errno);
343 	return (0);
344 }
345 
346 int
347 test_stat(void *arg, void *h, struct stat *stp)
348 {
349 	struct test_file *tf = h;
350 
351 	if (!stp)
352 		return (-1);
353 	memset(stp, 0, sizeof(struct stat));
354 	stp->st_mode = tf->tf_stat.st_mode;
355 	stp->st_uid = tf->tf_stat.st_uid;
356 	stp->st_gid = tf->tf_stat.st_gid;
357 	stp->st_size = tf->tf_stat.st_size;
358 	stp->st_ino = tf->tf_stat.st_ino;
359 	stp->st_dev = tf->tf_stat.st_dev;
360 	stp->st_mtime = tf->tf_stat.st_mtime;
361 	return (0);
362 }
363 
364 /*
365  * Disk image i/o
366  */
367 
368 int
369 test_diskread(void *arg, int unit, uint64_t offset, void *dst, size_t size,
370     size_t *resid_return)
371 {
372 	ssize_t n;
373 
374 	if (unit > disk_index || disk_fd[unit] == -1)
375 		return (EIO);
376 	n = pread(disk_fd[unit], dst, size, offset);
377 	if (n == 0) {
378 		printf("%s: end of disk (%ju)\n", __func__, (intmax_t)offset);
379 		return (EIO);
380 	}
381 
382 	if (n < 0)
383 		return (errno);
384 	*resid_return = size - n;
385 	return (0);
386 }
387 
388 int
389 test_diskwrite(void *arg, int unit, uint64_t offset, void *src, size_t size,
390     size_t *resid_return)
391 {
392 	ssize_t n;
393 
394 	if (unit > disk_index || disk_fd[unit] == -1)
395 		return (EIO);
396 	n = pwrite(disk_fd[unit], src, size, offset);
397 	if (n < 0)
398 		return (errno);
399 	*resid_return = size - n;
400 	return (0);
401 }
402 
403 int
404 test_diskioctl(void *arg, int unit, u_long cmd, void *data)
405 {
406 	struct stat sb;
407 
408 	if (unit > disk_index || disk_fd[unit] == -1)
409 		return (EBADF);
410 	switch (cmd) {
411 	case DIOCGSECTORSIZE:
412 		*(u_int *)data = 512;
413 		break;
414 	case DIOCGMEDIASIZE:
415 		if (fstat(disk_fd[unit], &sb) == 0)
416 			*(off_t *)data = sb.st_size;
417 		else
418 			return (ENOTTY);
419 		break;
420 	default:
421 		return (ENOTTY);
422 	}
423 	return (0);
424 }
425 
426 /*
427  * Guest virtual machine i/o
428  *
429  * Note: guest addresses are kernel virtual
430  */
431 
432 int
433 test_copyin(void *arg, const void *from, uint64_t to, size_t size)
434 {
435 
436 	to &= 0x7fffffff;
437 	if (to > image_size)
438 		return (EFAULT);
439 	if (to + size > image_size)
440 		size = image_size - to;
441 	memcpy(&image[to], from, size);
442 	return(0);
443 }
444 
445 int
446 test_copyout(void *arg, uint64_t from, void *to, size_t size)
447 {
448 
449 	from &= 0x7fffffff;
450 	if (from > image_size)
451 		return (EFAULT);
452 	if (from + size > image_size)
453 		size = image_size - from;
454 	memcpy(to, &image[from], size);
455 	return(0);
456 }
457 
458 void
459 test_setreg(void *arg, int r, uint64_t v)
460 {
461 
462 	if (r < 0 || r >= 16)
463 		return;
464 	regs[r] = v;
465 }
466 
467 void
468 test_setmsr(void *arg, int r, uint64_t v)
469 {
470 }
471 
472 void
473 test_setcr(void *arg, int r, uint64_t v)
474 {
475 }
476 
477 void
478 test_setgdt(void *arg, uint64_t v, size_t sz)
479 {
480 }
481 
482 void
483 test_exec(void *arg, uint64_t pc)
484 {
485 	printf("Execute at 0x%"PRIx64"\n", pc);
486 	test_exit(arg, 0);
487 }
488 
489 /*
490  * Misc
491  */
492 
493 void
494 test_delay(void *arg, int usec)
495 {
496 
497 	usleep(usec);
498 }
499 
500 void
501 test_exit(void *arg, int v)
502 {
503 
504 	tcsetattr(0, TCSAFLUSH, &oldterm);
505 	exit(v);
506 }
507 
508 void
509 test_getmem(void *arg, uint64_t *lowmem, uint64_t *highmem)
510 {
511 
512         *lowmem = 128*1024*1024;
513         *highmem = 0;
514 }
515 
516 char *
517 test_getenv(void *arg, int idx)
518 {
519 	static char *myvars[] = {
520 		"USERBOOT=1"
521 	};
522 	static const int num_myvars = nitems(myvars);
523 
524 	if (idx < num_myvars)
525 		return (myvars[idx]);
526 	else
527 		return (vars[idx - num_myvars]);
528 }
529 
530 struct loader_callbacks cb = {
531 	.putc = test_putc,
532 	.getc = test_getc,
533 	.poll = test_poll,
534 
535 	.open = test_open,
536 	.close = test_close,
537 	.isdir = test_isdir,
538 	.read = test_read,
539 	.readdir = test_readdir,
540 	.seek = test_seek,
541 	.stat = test_stat,
542 
543 	.diskread = test_diskread,
544 	.diskwrite = test_diskwrite,
545 	.diskioctl = test_diskioctl,
546 
547 	.copyin = test_copyin,
548 	.copyout = test_copyout,
549 	.setreg = test_setreg,
550 	.setmsr = test_setmsr,
551 	.setcr = test_setcr,
552         .setgdt = test_setgdt,
553 	.exec = test_exec,
554 
555 	.delay = test_delay,
556 	.exit = test_exit,
557         .getmem = test_getmem,
558 
559 	.getenv = test_getenv,
560 };
561 
562 void
563 usage()
564 {
565 
566 	printf("usage: [-b <userboot shared object>] [-d <disk image path>] [-h <host filesystem path>\n");
567 	exit(1);
568 }
569 
570 int
571 main(int argc, char** argv, char ** environment)
572 {
573 	void *h;
574 	void (*func)(struct loader_callbacks *, void *, int, int) __dead2;
575 	int opt;
576 	const char *userboot_obj = "/boot/userboot.so";
577 	int oflag = O_RDONLY;
578 
579 	vars = environment;
580 
581 	while ((opt = getopt(argc, argv, "wb:d:h:")) != -1) {
582 		switch (opt) {
583 		case 'b':
584 			userboot_obj = optarg;
585 			break;
586 
587 		case 'd':
588 			disk_index++;
589 			disk_fd = reallocarray(disk_fd, disk_index + 1,
590 			    sizeof (int));
591 			disk_fd[disk_index] = open(optarg, oflag);
592 			if (disk_fd[disk_index] < 0)
593 				err(1, "Can't open disk image '%s'", optarg);
594 			break;
595 
596 		case 'h':
597 			host_base = optarg;
598 			break;
599 
600 		case 'w':
601 			oflag = O_RDWR;
602 			break;
603 
604 		case '?':
605 			usage();
606 		}
607 	}
608 
609 	h = dlopen(userboot_obj, RTLD_LOCAL);
610 	if (!h) {
611 		printf("%s\n", dlerror());
612 		return (1);
613 	}
614 	func = dlsym(h, "loader_main");
615 	if (!func) {
616 		printf("%s\n", dlerror());
617 		return (1);
618 	}
619 
620 	image_size = 128*1024*1024;
621 	image = malloc(image_size);
622 
623 	tcgetattr(0, &term);
624 	oldterm = term;
625 	term.c_iflag &= ~(ICRNL);
626 	term.c_lflag &= ~(ICANON|ECHO);
627 	tcsetattr(0, TCSAFLUSH, &term);
628 
629 	func(&cb, NULL, USERBOOT_VERSION_3, disk_index + 1);
630 }
631