root/usr/src/common/fs/bootfsops.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2013 Joyent, Inc.  All rights reserved.
 * Copyright 2025 MNX Cloud, Inc.
 */

#include <sys/bootconf.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/vnode.h>
#include <sys/fs/ufs_fsdir.h>
#include <sys/fs/ufs_fs.h>
#include <sys/fs/ufs_inode.h>
#include <sys/sysmacros.h>
#include <sys/bootvfs.h>
#include <sys/bootinfo.h>
#include <sys/filep.h>
#include <sys/sunddi.h>

#define MAX_FILES       MAX_BOOT_MODULES
#define MAX_FDS         256

extern void *bkmem_alloc(size_t);
extern void bkmem_free(void *, size_t);

/*
 * TODO: Replace these declarations with inclusion of the ordinary userland
 * bootfs headers once they're available.
 */
typedef struct bfile {
        char bf_name[MAXPATHLEN];
        caddr_t bf_addr;
        size_t bf_size;
        struct bfile *bf_next;
        uint64_t bf_ino;
} bfile_t;

typedef struct bf_fd {
        bfile_t *fd_file;
        off_t fd_pos;
} bf_fd_t;

static bfile_t *head;
static uint_t init_done;
static bf_fd_t fds[MAX_FDS];

static char cpath[MAXPATHLEN];  /* For canonicalising filenames */

static void bbootfs_closeall(int);

static void
canonicalise(const char *fn, char *out)
{
        const char *p;
        char *q, *s;
        char *last;
        char *oc;
        int is_slash = 0;
        static char scratch[MAXPATHLEN];

        if (fn == NULL) {
                *out = '\0';
                return;
        }

        /*
         * Remove leading slashes and condense all multiple slashes into one.
         */
        p = fn;
        while (*p == '/')
                ++p;

        for (q = scratch; *p != '\0'; p++) {
                if (*p == '/' && !is_slash) {
                        *q++ = '/';
                        is_slash = 1;
                } else if (*p != '/') {
                        *q++ = *p;
                        is_slash = 0;
                }
        }
        *q = '\0';

        if (strncmp(scratch, "system/boot/", 12) == 0 ||
            strcmp(scratch, "system/boot") == 0) {
                s = scratch + 12;
        } else {
                s = scratch;
        }

        for (last = strsep(&s, "/"), q = oc = out; last != NULL;
            last = strsep(&s, "/")) {
                if (strcmp(last, ".") == 0)
                        continue;
                if (strcmp(last, "..") == 0) {
                        for (oc = q; oc > out && *oc != '/'; oc--)
                                ;
                        q = oc;
                        continue;
                }
                if (q > out)
                        *q++ = '/';
                q += snprintf(q, MAXPATHLEN - (q - out), "%s", last);
        }

        *q = '\0';
}

static int
bbootfs_mountroot(char *str __unused)
{
        return (-1);
}

static int
bbootfs_unmountroot(void)
{
        return (-1);
}

static int
bbootfs_init(void)
{
        bfile_t *fp;
        char propname[32];
        uint64_t propval;
        uint_t i;

        for (i = 0; i < MAX_FILES; i++) {
                (void) snprintf(propname, sizeof (propname),
                    "module-name-%u", i);
                if (do_bsys_getproplen(NULL, propname) < 0)
                        break;

                if ((fp = bkmem_alloc(sizeof (bfile_t))) == NULL) {
                        bbootfs_closeall(1);
                        return (-1);
                }

                (void) do_bsys_getprop(NULL, propname, cpath);
                canonicalise(cpath, fp->bf_name);

                (void) snprintf(propname, sizeof (propname),
                    "module-addr-%u", i);
                if (do_bsys_getproplen(NULL, propname) != sizeof (uint64_t)) {
                        bkmem_free(fp, sizeof (bfile_t));
                        continue;
                }
                (void) do_bsys_getprop(NULL, propname, &propval);
                fp->bf_addr = (void *)(uintptr_t)propval;

                (void) snprintf(propname, sizeof (propname),
                    "module-size-%u", i);
                if (do_bsys_getproplen(NULL, propname) != sizeof (uint64_t)) {
                        bkmem_free(fp, sizeof (bfile_t));
                        continue;
                }
                (void) do_bsys_getprop(NULL, propname, &propval);
                fp->bf_size = (size_t)propval;
                fp->bf_ino = i;

                fp->bf_next = head;
                head = fp;
        }

        return (0);
}

static int
bbootfs_open(char *fn, int flags __unused)
{
        uint_t i;
        bfile_t *fp;

        if (!init_done) {
                if (bbootfs_init() != 0)
                        return (-1);

                init_done = 1;
        }

        canonicalise(fn, cpath);

        for (fp = head; fp != NULL; fp = fp->bf_next) {
                if (strcmp(fp->bf_name, cpath) == 0)
                        break;
        }

        if (fp == NULL)
                return (-1);

        for (i = 0; i < MAX_FDS; i++) {
                if (fds[i].fd_file == NULL) {
                        fds[i].fd_file = fp;
                        fds[i].fd_pos = 0;
                        return (i);
                }
        }

        return (-1);
}

static int
bbootfs_close(int fd)
{
        if (fds[fd].fd_file == NULL)
                return (-1);

        fds[fd].fd_file = NULL;
        fds[fd].fd_pos = 0;

        return (0);
}

static ssize_t
bbootfs_read(int fd, caddr_t buf, size_t size)
{
        ssize_t len;
        bf_fd_t *fdp = &fds[fd];

        if (fdp->fd_file == NULL)
                return (-1);

        if (fdp->fd_pos >= fdp->fd_file->bf_size)
                return (-1);

        if (fdp->fd_pos + size > fdp->fd_file->bf_size)
                len = fdp->fd_file->bf_size - fdp->fd_pos;
        else
                len = size;

        bcopy(fdp->fd_file->bf_addr + fdp->fd_pos, buf, len);

        fdp->fd_pos += len;

        return (len);
}

static off_t
bbootfs_lseek(int fd, off_t addr, int whence)
{
        bf_fd_t *fdp = &fds[fd];

        if (fdp->fd_file == NULL)
                return (-1);

        switch (whence) {
        case SEEK_CUR:
                fdp->fd_pos += addr;
                break;
        case SEEK_SET:
                fdp->fd_pos = addr;
                break;
        case SEEK_END:
                fdp->fd_pos = fdp->fd_file->bf_size;
                break;
        default:
                return (-1);
        }

        return (0);
}

static int
bbootfs_fstat(int fd, struct bootstat *bsp)
{
        bf_fd_t *fdp = &fds[fd];

        if (fdp->fd_file == NULL)
                return (-1);

        bsp->st_dev = 1;
        bsp->st_ino = fdp->fd_file->bf_ino;
        bsp->st_mode = 0444;
        bsp->st_nlink = 1;
        bsp->st_uid = bsp->st_gid = 0;
        bsp->st_rdev = 0;
        bsp->st_size = fdp->fd_file->bf_size;
        bsp->st_blksize = 1;
        bsp->st_blocks = fdp->fd_file->bf_size;
        (void) strcpy(bsp->st_fstype, "bootfs");

        return (0);
}

static void
bbootfs_closeall(int flag __unused)
{
        bfile_t *fp;

        while (head != NULL) {
                fp = head;
                head = head->bf_next;

                bkmem_free(fp, sizeof (bfile_t));
        }

        init_done = 0;
}

struct boot_fs_ops bbootfs_ops = {
        "bootfs",
        bbootfs_mountroot,
        bbootfs_unmountroot,
        bbootfs_open,
        bbootfs_close,
        bbootfs_read,
        bbootfs_lseek,
        bbootfs_fstat,
        bbootfs_closeall,
        NULL
};