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
This commit is contained in:
Warner Losh 2022-10-27 11:37:54 -06:00
parent cc9f1b4c35
commit 02dba4f75f
5 changed files with 291 additions and 1 deletions

View File

@ -25,6 +25,7 @@ SRCS= \
host_syscalls.c \ host_syscalls.c \
hostcons.c \ hostcons.c \
hostdisk.c \ hostdisk.c \
hostfs.c \
init.c \ init.c \
kbootfdt.c \ kbootfdt.c \
main.c \ main.c \

View File

@ -35,6 +35,7 @@ __FBSDID("$FreeBSD$");
#endif #endif
extern struct devsw hostdisk; extern struct devsw hostdisk;
extern struct devsw host_dev;
/* /*
* We could use linker sets for some or all of these, but * We could use linker sets for some or all of these, but
@ -53,9 +54,12 @@ struct devsw *devsw[] = {
#if defined(LOADER_NET_SUPPORT) #if defined(LOADER_NET_SUPPORT)
&netdev, &netdev,
#endif #endif
&host_dev,
NULL NULL
}; };
extern struct fs_ops hostfs_fsops;
struct fs_ops *file_system[] = { struct fs_ops *file_system[] = {
#if defined(LOADER_UFS_SUPPORT) #if defined(LOADER_UFS_SUPPORT)
&ufs_fsops, &ufs_fsops,
@ -79,6 +83,7 @@ struct fs_ops *file_system[] = {
&bzipfs_fsops, &bzipfs_fsops,
#endif #endif
&dosfs_fsops, &dosfs_fsops,
&hostfs_fsops,
NULL NULL
}; };

280
stand/kboot/hostfs.c Normal file
View File

@ -0,0 +1,280 @@
/*-
* Copyright (c) 2022 Netflix, Inc
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <sys/types.h>
#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
};

View File

@ -11,6 +11,8 @@
void do_init(void); void do_init(void);
extern const char *hostfs_root;
/* Per-platform fdt fixup */ /* Per-platform fdt fixup */
void fdt_arch_fixups(void *fdtp); void fdt_arch_fixups(void *fdtp);

View File

@ -120,8 +120,10 @@ main(int argc, const char **argv)
bootdev = argv[1]; bootdev = argv[1];
else else
bootdev = ""; 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_getdev = kboot_getdev;
archsw.arch_copyin = kboot_copyin; archsw.arch_copyin = kboot_copyin;