From 02dba4f75f86f3d5ae5a6a438b5bcfdc667929bb Mon Sep 17 00:00:00 2001 From: Warner Losh Date: Thu, 27 Oct 2022 11:37:54 -0600 Subject: [PATCH] kboot: Add hostfs Add hostfs for the Linux environment. We can't use the userboot one that's kinda similar because the Linux system calls we have in kboot are not quite POSIX compliant (Linux takes care of providing the POSIX interface in libc), so we have to cope with a number of quirks in that area. Sponsored by: Netflix Differential Revision: https://reviews.freebsd.org/D36607 --- stand/kboot/Makefile | 1 + stand/kboot/conf.c | 5 + stand/kboot/hostfs.c | 280 +++++++++++++++++++++++++++++++++++++++++++ stand/kboot/kboot.h | 2 + stand/kboot/main.c | 4 +- 5 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 stand/kboot/hostfs.c diff --git a/stand/kboot/Makefile b/stand/kboot/Makefile index 518e945a596..c8bd313d1d1 100644 --- a/stand/kboot/Makefile +++ b/stand/kboot/Makefile @@ -25,6 +25,7 @@ SRCS= \ host_syscalls.c \ hostcons.c \ hostdisk.c \ + hostfs.c \ init.c \ kbootfdt.c \ main.c \ diff --git a/stand/kboot/conf.c b/stand/kboot/conf.c index b840d008a34..26788342c4b 100644 --- a/stand/kboot/conf.c +++ b/stand/kboot/conf.c @@ -35,6 +35,7 @@ __FBSDID("$FreeBSD$"); #endif extern struct devsw hostdisk; +extern struct devsw host_dev; /* * We could use linker sets for some or all of these, but @@ -53,9 +54,12 @@ struct devsw *devsw[] = { #if defined(LOADER_NET_SUPPORT) &netdev, #endif + &host_dev, NULL }; +extern struct fs_ops hostfs_fsops; + struct fs_ops *file_system[] = { #if defined(LOADER_UFS_SUPPORT) &ufs_fsops, @@ -79,6 +83,7 @@ struct fs_ops *file_system[] = { &bzipfs_fsops, #endif &dosfs_fsops, + &hostfs_fsops, NULL }; diff --git a/stand/kboot/hostfs.c b/stand/kboot/hostfs.c new file mode 100644 index 00000000000..08f532999ab --- /dev/null +++ b/stand/kboot/hostfs.c @@ -0,0 +1,280 @@ +/*- + * Copyright (c) 2022 Netflix, Inc + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include "stand.h" +#include "host_syscall.h" +#include "kboot.h" + +#define HOST_PATH_MAX 1025 + +extern struct devsw host_dev; + +const char *hostfs_root = "/"; + +enum FTYPE { + regular, + dir, +}; + +typedef struct _hostfs_file { + enum FTYPE hf_type; + int hf_fd; + /* The following are only used for FTYPE == dir */ + char hf_dents[2048]; + char *hf_curdent; + int hf_dentlen; /* Valid part of hf_dents */ +} hostfs_file; + +static hostfs_file * +hostfs_alloc(void) +{ + hostfs_file *hf; + + hf = malloc(sizeof(*hf)); + if (hf != NULL) + memset(hf, 0, sizeof(*hf)); + return (hf); +} + +static void +hostfs_free(hostfs_file *hf) +{ + free(hf); +} + +static int +hostfs_open(const char *fn, struct open_file *f) +{ + hostfs_file *hf; + struct host_kstat ksb; + char path[HOST_PATH_MAX]; + + if (f->f_dev != &host_dev) { + return (EINVAL); + } + + /* + * Normally, we root everything at hostfs_root. However, there are two + * exceptions that make it easier to write code. First is /sys and /proc + * are special Linux filesystems, so we pass those paths + * through. Second, if the path starts with //, then we strip off the + * first / and pass it through (in a weird way, this is actually in + * POSIX: hosts are allowed to do specail things with paths that start + * with two //, but one or three or more are required to be treated as + * one). + */ + if (strncmp("/sys/", fn, 5) == 0 || strncmp("/proc/", fn, 6) == 0) + strlcpy(path, fn, sizeof(path)); + else if (fn[0] == '/' && fn[1] == '/' && fn[2] != '/') + strlcpy(path, fn + 1, sizeof(path)); + else + snprintf(path, sizeof(path), "%s/%s", hostfs_root, fn); + hf = hostfs_alloc(); + hf->hf_fd = host_open(path, HOST_O_RDONLY, 0); + if (hf->hf_fd < 0) { + hostfs_free(hf); + return (EINVAL); + } + + if (host_fstat(hf->hf_fd, &ksb) < 0) { + hostfs_free(hf); + return (EINVAL); + } + if (S_ISDIR(hf->hf_fd)) { + hf->hf_type = dir; + } else { + hf->hf_type = regular; + } + f->f_fsdata = hf; + return (0); +} + +static int +hostfs_close(struct open_file *f) +{ + hostfs_file *hf = f->f_fsdata; + + host_close(hf->hf_fd); + hostfs_free(hf); + f->f_fsdata = NULL; + + return (0); +} + +static int +hostfs_read(struct open_file *f, void *start, size_t size, size_t *resid) +{ + hostfs_file *hf = f->f_fsdata; + ssize_t sz; + + sz = host_read(hf->hf_fd, start, size); + if (sz < 0) { + return (EINVAL); + } + *resid = size - sz; + + return (0); +} + +static off_t +hostfs_seek(struct open_file *f, off_t offset, int whence) +{ + hostfs_file *hf = f->f_fsdata; + uint32_t offl, offh; + int err; + uint64_t res; + + /* + * Assumes Linux host with 'reduced' system call wrappers. Also assume + * host and libstand have same whence encoding (safe since it all comes + * from V7 later ISO-C). Also assumes we have to support powerpc still, + * it's interface is weird for legacy reasons.... + */ + offl = offset & 0xffffffff; + offh = (offset >> 32) & 0xffffffff; + err = host_llseek(hf->hf_fd, offh, offl, &res, whence); + if (err < 0) + return (err); + return (res); +} + +static int +hostfs_stat(struct open_file *f, struct stat *sb) +{ + struct host_kstat ksb; + hostfs_file *hf = f->f_fsdata; + + if (host_fstat(hf->hf_fd, &ksb) < 0) + return (EINVAL); + /* + * Translate Linux stat info to lib stand's notion (which uses FreeBSD's + * stat structure, missing fields are zero and commented below). + */ + memset(sb, 0, sizeof(*sb)); + sb->st_dev = ksb.st_dev; + sb->st_ino = ksb.st_ino; + sb->st_nlink = ksb.st_nlink; + sb->st_mode = ksb.st_mode; + sb->st_uid = ksb.st_uid; + sb->st_gid = ksb.st_gid; + sb->st_rdev = ksb.st_rdev; + /* No st_?time_ext on i386 */ + sb->st_atim.tv_sec = ksb.st_atime_sec; + sb->st_atim.tv_nsec = ksb.st_atime_nsec; + sb->st_mtim.tv_sec = ksb.st_mtime_sec; + sb->st_mtim.tv_nsec = ksb.st_mtime_nsec; + sb->st_ctim.tv_sec = ksb.st_ctime_sec; + sb->st_ctim.tv_nsec = ksb.st_ctime_nsec; + /* No st_birthtim */ + sb->st_size = ksb.st_size; + sb->st_blocks = ksb.st_blocks; + sb->st_blksize = ksb.st_blksize; + /* no st_flags */ + /* no st_get */ + + return (0); +} + +static int +hostfs_readdir(struct open_file *f, struct dirent *d) +{ + hostfs_file *hf = f->f_fsdata; + int dentlen; + struct host_dirent64 *dent; + + if (hf->hf_curdent == NULL) { + dentlen = host_getdents64(hf->hf_fd, hf->hf_dents, sizeof(hf->hf_dents)); + if (dentlen <= 0) + return (EINVAL); + hf->hf_dentlen = dentlen; + hf->hf_curdent = hf->hf_dents; + } + dent = (struct host_dirent64 *)hf->hf_curdent; + d->d_fileno = dent->d_ino; + d->d_type = dent->d_type; /* HOST_DT_XXXX == DX_XXXX for all values */ + strlcpy(d->d_name, dent->d_name, sizeof(d->d_name)); /* d_name is NUL terminated */ + d->d_namlen = strlen(d->d_name); + hf->hf_curdent += dent->d_reclen; + if (hf->hf_curdent >= hf->hf_dents + hf->hf_dentlen) { + hf->hf_curdent = NULL; + hf->hf_dentlen = 0; + } + + return (0); +} + +struct fs_ops hostfs_fsops = { + .fs_name = "host", + .fo_open = hostfs_open, + .fo_close = hostfs_close, + .fo_read = hostfs_read, + .fo_write = null_write, + .fo_seek = hostfs_seek, + .fo_stat = hostfs_stat, + .fo_readdir = hostfs_readdir +}; + +/* + * Generic "null" host device. This goes hand and hand with the host fs object + * + * XXXX This and userboot for bhyve both use exactly the same code, modulo some + * formatting nits. Make them common. We mostly use it to 'gate' the open of the + * filesystem to only this device. + */ + +static int +host_dev_init(void) +{ + return (0); +} + +static int +host_dev_print(int verbose) +{ + char line[80]; + + printf("%s devices:", host_dev.dv_name); + if (pager_output("\n") != 0) + return (1); + + snprintf(line, sizeof(line), " host%d: Host filesystem\n", 0); + return (pager_output(line)); +} + +/* + * 'Open' the host device. + */ +static int +host_dev_open(struct open_file *f, ...) +{ + return (0); +} + +static int +host_dev_close(struct open_file *f) +{ + return (0); +} + +static int +host_dev_strategy(void *devdata, int rw, daddr_t dblk, size_t size, + char *buf, size_t *rsize) +{ + return (ENOSYS); +} + +struct devsw host_dev = { + .dv_name = "host", + .dv_type = DEVT_NET, + .dv_init = host_dev_init, + .dv_strategy = host_dev_strategy, + .dv_open = host_dev_open, + .dv_close = host_dev_close, + .dv_ioctl = noioctl, + .dv_print = host_dev_print, + .dv_cleanup = NULL +}; diff --git a/stand/kboot/kboot.h b/stand/kboot/kboot.h index 81bd18faa89..5441c90eaec 100644 --- a/stand/kboot/kboot.h +++ b/stand/kboot/kboot.h @@ -11,6 +11,8 @@ void do_init(void); +extern const char *hostfs_root; + /* Per-platform fdt fixup */ void fdt_arch_fixups(void *fdtp); diff --git a/stand/kboot/main.c b/stand/kboot/main.c index 10dd6b05194..23211ce9df4 100644 --- a/stand/kboot/main.c +++ b/stand/kboot/main.c @@ -120,8 +120,10 @@ main(int argc, const char **argv) bootdev = argv[1]; else bootdev = ""; + if (argc > 2) + hostfs_root = argv[2]; - printf("Boot device: %s\n", bootdev); + printf("Boot device: %s with hostfs_root %s\n", bootdev, hostfs_root); archsw.arch_getdev = kboot_getdev; archsw.arch_copyin = kboot_copyin;