Index: stand/efi/libefi/devicename.c =================================================================== --- stand/efi/libefi/devicename.c +++ stand/efi/libefi/devicename.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -209,8 +210,18 @@ rv = efi_parsedev(&ncurr, value, NULL); if (rv != 0) return (rv); - free(ncurr); - env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); - return (0); + + /* mount new rootfs */ + rv = mount(value, "/", 0, NULL); + if (rv == 0) { + /* + * Note we unmount only after successful mount because + * we do not want to end up without rootfs. + */ + if (ev->ev_value != NULL) + unmount(ev->ev_value, 0); + env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); + } + return (rv); } Index: stand/efi/loader/main.c =================================================================== --- stand/efi/loader/main.c +++ stand/efi/loader/main.c @@ -187,15 +187,12 @@ set_currdev(const char *devname) { + env_setenv("currdev", EV_VOLATILE, devname, efi_setcurrdev, + env_nounset); /* - * Don't execute hooks here; we may need to try setting these more than - * once here if we're probing for the ZFS pool we're supposed to boot. - * The currdev hook is intended to just validate user input anyways, - * while the loaddev hook makes it immutable once we've determined what - * the proper currdev is. + * Don't execute hook here;the loaddev hook makes it immutable + * once we've determined what the proper currdev is. */ - env_setenv("currdev", EV_VOLATILE | EV_NOHOOK, devname, efi_setcurrdev, - env_nounset); env_setenv("loaddev", EV_VOLATILE | EV_NOHOOK, devname, env_noset, env_nounset); } Index: stand/i386/libi386/devicename.c =================================================================== --- stand/i386/libi386/devicename.c +++ stand/i386/libi386/devicename.c @@ -29,6 +29,7 @@ #include #include +#include #include "bootstrap.h" #include "disk.h" #include "libi386.h" @@ -204,12 +205,23 @@ int i386_setcurrdev(struct env_var *ev, int flags, const void *value) { - struct i386_devdesc *ncurr; - int rv; - - if ((rv = i386_parsedev(&ncurr, value, NULL)) != 0) - return(rv); - free(ncurr); - env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); - return(0); + struct i386_devdesc *ncurr; + int rv; + + if ((rv = i386_parsedev(&ncurr, value, NULL)) != 0) + return (rv); + + free(ncurr); + /* mount new rootfs */ + rv = mount(value, "/", 0, NULL); + if (rv == 0) { + /* + * Note we unmount only after successful mount because + * we do not want to end up without rootfs. + */ + if (ev->ev_value != NULL) + unmount(ev->ev_value, 0); + env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); + } + return (rv); } Index: stand/libofw/devicename.c =================================================================== --- stand/libofw/devicename.c +++ stand/libofw/devicename.c @@ -134,13 +134,24 @@ int ofw_setcurrdev(struct env_var *ev, int flags, const void *value) { - struct ofw_devdesc *ncurr; - int rv; + struct ofw_devdesc *ncurr; + int rv; - if ((rv = ofw_parsedev(&ncurr, value, NULL)) != 0) - return rv; + if ((rv = ofw_parsedev(&ncurr, value, NULL)) != 0) + return (rv); - free(ncurr); - env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); - return 0; + free(ncurr); + + /* mount new rootfs */ + rv = mount(value, "/", 0, NULL); + if (rv == 0) { + /* + * Note we unmount only after successful mount because + * we do not want to end up without rootfs. + */ + if (ev->ev_value != NULL) + unmount(ev->ev_value, 0); + env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); + } + return (rv); } Index: stand/libsa/Makefile =================================================================== --- stand/libsa/Makefile +++ stand/libsa/Makefile @@ -131,7 +131,7 @@ CLEANFILES+= ${SAFE_INCS} ${STAND_H_INC} ${OTHER_INC} # io routines -SRCS+= closeall.c dev.c ioctl.c nullfs.c stat.c \ +SRCS+= closeall.c dev.c ioctl.c nullfs.c stat.c mount.c \ fstat.c close.c lseek.c open.c read.c write.c readdir.c # SMBios routines Index: stand/libsa/mount.c =================================================================== --- /dev/null +++ stand/libsa/mount.c @@ -0,0 +1,139 @@ +/*- + * Copyright 2021 Toomas Soome + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include + +/* + * While setting "currdev" environment variable, alse "mount" the + * new root file system. This is done to hold disk device open + * in between file accesses, and thus preserve block cache for + * this device. Additionally, this allows us to optimize filesystem + * access by sharing filesystem metadata (like superblock). + */ + +typedef STAILQ_HEAD(mnt_info_list, mnt_info) mnt_info_list_t; + +typedef struct mnt_info { + STAILQ_ENTRY(mnt_info) mnt_link; /* link in mount list */ + const struct fs_ops *mnt_fs; + char *mnt_dev; + char *mnt_path; + void *mnt_data; /* Private state */ +} mnt_info_t; + +/* list of mounted filesystems. */ +static mnt_info_list_t mnt_list = STAILQ_HEAD_INITIALIZER(mnt_list); + +static void +free_mnt(mnt_info_t *mnt) +{ + free(mnt->mnt_dev); + free(mnt->mnt_path); + free(mnt); +} + +static int +add_mnt_info(struct fs_ops *fs, const char *dev, const char *path, void *data) +{ + mnt_info_t *mnt; + + mnt = malloc(sizeof(*mnt)); + if (mnt == NULL) + return (ENOMEM); + + mnt->mnt_fs = fs; + mnt->mnt_dev = strdup(dev); + mnt->mnt_path = strdup(path); + mnt->mnt_data = data; + + if (mnt->mnt_dev == NULL || mnt->mnt_path == NULL) { + free_mnt(mnt); + return (ENOMEM); + } + STAILQ_INSERT_TAIL(&mnt_list, mnt, mnt_link); + return (0); +} + +static void +delete_mnt_info(mnt_info_t *mnt) +{ + STAILQ_REMOVE(&mnt_list, mnt, mnt_info, mnt_link); + free_mnt(mnt); +} + +int +mount(const char *dev, const char *path, int flag, void *data) +{ + mnt_info_t *mnt; + int rc = -1; + + /* Is it already mounted? */ + STAILQ_FOREACH(mnt, &mnt_list, mnt_link) { + if (strcmp(dev, mnt->mnt_dev) == 0 && + strcmp(path, mnt->mnt_path) == 0) + return (0); + } + + for (int i = 0; file_system[i] != NULL; i++) { + struct fs_ops *fs; + + fs = file_system[i]; + if (fs->fo_mount != NULL) { + void *data = NULL; + + rc = fs->fo_mount(dev, path, &data); + if (rc != 0) + continue; + rc = add_mnt_info(fs, dev, path, data); + break; + } + } + + if (rc == -1) + printf("Unknown or no file system on: %s", dev); + rc = 0; + return (rc); +} + +int +unmount(const char *dev, int flag) +{ + mnt_info_t *mnt; + + STAILQ_FOREACH(mnt, &mnt_list, mnt_link) { + if (strcmp(dev, mnt->mnt_dev) == 0) { + if (mnt->mnt_fs->fo_unmount != NULL) + mnt->mnt_fs->fo_unmount(dev, mnt->mnt_data); + delete_mnt_info(mnt); + break; + } + } + + return (0); +} Index: stand/libsa/stand.h =================================================================== --- stand/libsa/stand.h +++ stand/libsa/stand.h @@ -111,6 +111,8 @@ off_t (*fo_seek)(struct open_file *f, off_t offset, int where); int (*fo_stat)(struct open_file *f, struct stat *sb); int (*fo_readdir)(struct open_file *f, struct dirent *d); + int (*fo_mount)(const char *, const char *, void **); + int (*fo_unmount)(const char *, void *); }; /* Index: stand/libsa/ufs.c =================================================================== --- stand/libsa/ufs.c +++ stand/libsa/ufs.c @@ -81,6 +81,7 @@ #include #include #include "stand.h" +#include "disk.h" #include "string.h" static int ufs_open(const char *path, struct open_file *f); @@ -91,16 +92,20 @@ static off_t ufs_seek(struct open_file *f, off_t offset, int where); static int ufs_stat(struct open_file *f, struct stat *sb); static int ufs_readdir(struct open_file *f, struct dirent *d); +static int ufs_mount(const char *dev, const char *path, void **data); +static int ufs_unmount(const char *dev, void *data); struct fs_ops ufs_fsops = { - "ufs", - ufs_open, - ufs_close, - ufs_read, - ufs_write, - ufs_seek, - ufs_stat, - ufs_readdir + .fs_name = "ufs", + .fo_open = ufs_open, + .fo_close = ufs_close, + .fo_read = ufs_read, + .fo_write = ufs_write, + .fo_seek = ufs_seek, + .fo_stat = ufs_stat, + .fo_readdir = ufs_readdir, + .fo_mount = ufs_mount, + .fo_unmount = ufs_unmount }; /* @@ -130,6 +135,15 @@ ((fp)->f_fs->fs_magic == FS_UFS1_MAGIC ? \ (fp)->f_di.di1.field : (fp)->f_di.di2.field) +typedef struct ufs_mnt { + char *um_dev; + int um_fd; + STAILQ_ENTRY(ufs_mnt) um_link; +} ufs_mnt_t; + +typedef STAILQ_HEAD(ufs_mnt_list, ufs_mnt) ufs_mnt_list_t; +static ufs_mnt_list_t mnt_list = STAILQ_HEAD_INITIALIZER(mnt_list); + static int read_inode(ino_t, struct open_file *); static int block_map(struct open_file *, ufs2_daddr_t, ufs2_daddr_t *); static int buf_read_file(struct open_file *, char **, size_t *); @@ -502,9 +516,7 @@ * Open a file. */ static int -ufs_open(upath, f) - const char *upath; - struct open_file *f; +ufs_open(const char *upath, struct open_file *f) { char *cp, *ncp; int c; @@ -516,18 +528,37 @@ char namebuf[MAXPATHLEN+1]; char *buf = NULL; char *path = NULL; + const char *dev; + ufs_mnt_t *mnt; /* allocate file system specific data structure */ fp = malloc(sizeof(struct file)); bzero(fp, sizeof(struct file)); f->f_fsdata = (void *)fp; - /* read super block */ - twiddle(1); - if ((rc = ffs_sbget(f, &fs, STDSB_NOHASHFAIL, "stand", - ufs_use_sa_read)) != 0) - goto out; + dev = disk_fmtdev(f->f_devdata); + STAILQ_FOREACH(mnt, &mnt_list, um_link) { + if (strcmp(dev, mnt->um_dev) == 0) + break; + } + + if (mnt == NULL) { + /* read super block */ + twiddle(1); + if ((rc = ffs_sbget(f, &fs, STDSB_NOHASHFAIL, "stand", + ufs_use_sa_read)) != 0) + goto out; + } else { + struct open_file *sbf; + struct file *sfp; + + /* get superblock from mounted file system */ + sbf = fd2open_file(mnt->um_fd); + sfp = sbf->f_fsdata; + fs = sfp->f_fs; + } fp->f_fs = fs; + /* * Calculate indirect block levels. */ @@ -671,14 +702,12 @@ rc = 0; fp->f_seekp = 0; out: - if (buf) - free(buf); - if (path) - free(path); + free(buf); + free(path); if (rc) { - if (fp->f_buf) - free(fp->f_buf); - if (fp->f_fs != NULL) { + free(fp->f_buf); + + if (mnt == NULL && fp->f_fs != NULL) { free(fp->f_fs->fs_csp); free(fp->f_fs->fs_si); free(fp->f_fs); @@ -714,24 +743,32 @@ ufs_close(f) struct open_file *f; { + ufs_mnt_t *mnt; struct file *fp = (struct file *)f->f_fsdata; int level; + char *dev; - f->f_fsdata = (void *)0; - if (fp == (struct file *)0) + f->f_fsdata = NULL; + if (fp == NULL) return (0); for (level = 0; level < UFS_NIADDR; level++) { - if (fp->f_blk[level]) - free(fp->f_blk[level]); + free(fp->f_blk[level]); } - if (fp->f_buf) - free(fp->f_buf); - if (fp->f_fs != NULL) { + free(fp->f_buf); + + dev = disk_fmtdev(f->f_devdata); + STAILQ_FOREACH(mnt, &mnt_list, um_link) { + if (strcmp(dev, mnt->um_dev) == 0) + break; + } + + if (mnt == NULL && fp->f_fs != NULL) { free(fp->f_fs->fs_csp); free(fp->f_fs->fs_si); free(fp->f_fs); } + free(fp); return (0); } @@ -894,3 +931,52 @@ strcpy(d->d_name, dp->d_name); return (0); } + +static int +ufs_mount(const char *dev, const char *path, void **data) +{ + char *fs; + int fd = -1; + ufs_mnt_t *mnt; + + mnt = calloc(1, sizeof(*mnt)); + if (mnt == NULL) + return (errno); + mnt->um_fd = -1; + mnt->um_dev = strdup(dev); + if (mnt->um_dev == NULL) + goto done; + + if (asprintf(&fs, "%s%s", dev, path) < 0) + goto done; + + mnt->um_fd = open(fs, O_RDONLY); + free(fs); + if (mnt->um_fd == -1) + goto done; + + STAILQ_INSERT_TAIL(&mnt_list, mnt, um_link); +done: + if (errno != 0) { + free(mnt->um_dev); + if (fd >= 0) + close(fd); + free(mnt); + } else { + *data = mnt; + } + + return (errno); +} + +static int +ufs_unmount(const char *dev, void *data) +{ + ufs_mnt_t *mnt = data; + + STAILQ_REMOVE(&mnt_list, mnt, ufs_mnt, um_link); + free(mnt->um_dev); + close(mnt->um_fd); + free(mnt); + return (0); +} Index: stand/mips/beri/loader/devicename.c =================================================================== --- stand/mips/beri/loader/devicename.c +++ stand/mips/beri/loader/devicename.c @@ -195,12 +195,23 @@ int beri_arch_setcurrdev(struct env_var *ev, int flags, const void *value) { - struct disk_devdesc *ncurr; - int rv; - - if ((rv = beri_arch_parsedev(&ncurr, value, NULL)) != 0) - return(rv); - free(ncurr); - env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); - return(0); + struct disk_devdesc *ncurr; + int rv; + + if ((rv = beri_arch_parsedev(&ncurr, value, NULL)) != 0) + return (rv); + free(ncurr); + + /* mount new rootfs */ + rv = mount(value, "/", 0, NULL); + if (rv == 0) { + /* + * Note we unmount only after successful mount because + * we do not want to end up without rootfs. + */ + if (ev->ev_value != NULL) + unmount(ev->ev_value, 0); + env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); + } + return (rv); } Index: stand/powerpc/kboot/main.c =================================================================== --- stand/powerpc/kboot/main.c +++ stand/powerpc/kboot/main.c @@ -45,7 +45,6 @@ ssize_t kboot_readin(readin_handle_t fd, vm_offset_t dest, const size_t len); int kboot_autoload(void); uint64_t kboot_loadaddr(u_int type, void *data, uint64_t addr); -int kboot_setcurrdev(struct env_var *ev, int flags, const void *value); static void kboot_kseg_get(int *nseg, void **ptr); extern int command_fdt_internal(int argc, char *argv[]); Index: stand/uboot/lib/devicename.c =================================================================== --- stand/uboot/lib/devicename.c +++ stand/uboot/lib/devicename.c @@ -195,6 +195,17 @@ if ((rv = uboot_parsedev(&ncurr, value, NULL)) != 0) return (rv); free(ncurr); - env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); - return (0); + + /* mount new rootfs */ + rv = mount(value, "/", 0, NULL); + if (rv == 0) { + /* + * Note we unmount only after successful mount because + * we do not want to end up without rootfs. + */ + if (ev->ev_value != NULL) + unmount(ev->ev_value, 0); + env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); + } + return (rv); } Index: stand/userboot/userboot/devicename.c =================================================================== --- stand/userboot/userboot/devicename.c +++ stand/userboot/userboot/devicename.c @@ -222,6 +222,17 @@ if ((rv = userboot_parsedev(&ncurr, value, NULL)) != 0) return (rv); free(ncurr); - env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); - return (0); + + /* mount new rootfs */ + rv = mount(value, "/", 0, NULL); + if (rv == 0) { + /* + * Note we unmount only after successful mount because + * we do not want to end up without rootfs. + */ + if (ev->ev_value != NULL) + unmount(ev->ev_value, 0); + env_setenv(ev->ev_name, flags | EV_NOHOOK, value, NULL, NULL); + } + return (rv); }