/* * Copyright 2011-2017 Josef 'Jeff' Sipek * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ bootrd_cpio /* * 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 #include #include #include #include #include #include #include #include #include #include #include #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 }; /* * Copyright 2011-2017 Josef 'Jeff' Sipek * Copyright 2025 MNX Cloud, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include /* * A cpio archive is just a sequence of files, each consisting of a header * (struct cpio_hdr) and the file contents. */ struct cpio_hdr { uint8_t magic[6]; uint8_t dev[6]; uint8_t ino[6]; uint8_t mode[6]; uint8_t uid[6]; uint8_t gid[6]; uint8_t nlink[6]; uint8_t rdev[6]; uint8_t mtime[11]; uint8_t namesize[6]; uint8_t filesize[11]; char data[]; }; /* * This structure represents an open file. The list of all open files is * rooted in the open_files global. */ struct cpio_file { /* pointers into the archive */ const struct cpio_hdr *hdr; const char *path; /* pointer into the archive */ const void *data; /* pointer into the archive */ int fd; off_t off; struct bootstat stat; SLIST_ENTRY(cpio_file) next; }; /* * in bootrd.c */ extern void *bkmem_alloc(size_t); extern void bkmem_free(void *, size_t); static void cpio_closeall(int flag); static bool mounted; static SLIST_HEAD(cpio_file_list, cpio_file) open_files = SLIST_HEAD_INITIALIZER(open_files); /* * Returns the parsed number on success, or UINT64_MAX on error. This is * ok because we will never deal with numbers that large in a cpio archive. */ static uint64_t __get_uint64(const uint8_t *str, size_t len, const size_t output_size) { uint64_t v; /* check that we can represent every number */ if (len * 3 > output_size) return (UINT64_MAX); for (v = 0; len > 0; len--, str++) { const uint8_t c = *str; if ((c < '0') || (c > '7')) return (UINT64_MAX); v = (v * 8) + (c - '0'); } return (v); } static bool get_uint64(const uint8_t *str, size_t len, uint64_t *out) { *out = __get_uint64(str, len, NBBY * sizeof (*out)); return (*out != UINT64_MAX); } static bool get_int64(const uint8_t *str, size_t len, int64_t *out) { uint64_t tmp; tmp = __get_uint64(str, len, NBBY * sizeof (*out) - 1); *out = tmp; return (tmp != UINT64_MAX); } static bool get_uint32(const uint8_t *str, size_t len, uint32_t *out) { uint64_t tmp; tmp = __get_uint64(str, len, NBBY * sizeof (*out)); *out = tmp; return (tmp != UINT64_MAX); } static bool get_int32(const uint8_t *str, size_t len, int32_t *out) { uint64_t tmp; tmp = __get_uint64(str, len, NBBY * sizeof (*out) - 1); *out = tmp; return (tmp != UINT64_MAX); } static void add_open_file(struct cpio_file *file) { SLIST_INSERT_HEAD(&open_files, file, next); } static void remove_open_file(struct cpio_file *file) { SLIST_REMOVE(&open_files, file, cpio_file, next); } static struct cpio_file * find_open_file(int fd) { struct cpio_file *file; if (fd < 0) return (NULL); SLIST_FOREACH(file, &open_files, next) if (file->fd == fd) return (file); return (NULL); } static const void * read_ramdisk(size_t off, size_t len) { const size_t first_block_offset = off % DEV_BSIZE; fileid_t tmpfile; /* return a dummy non-NULL pointer */ if (len == 0) return (""); /* we have to read the stuff before the desired location as well */ len += first_block_offset; tmpfile.fi_blocknum = off / DEV_BSIZE; tmpfile.fi_count = P2ROUNDUP_TYPED(len, DEV_BSIZE, size_t); tmpfile.fi_memp = NULL; if (diskread(&tmpfile) != 0) return (NULL); return (tmpfile.fi_memp + first_block_offset); } static bool parse_stat(const struct cpio_hdr *hdr, struct bootstat *stat) { if (!get_uint64(hdr->dev, sizeof (hdr->dev), &stat->st_dev)) return (false); if (!get_uint64(hdr->ino, sizeof (hdr->ino), &stat->st_ino)) return (false); if (!get_uint32(hdr->mode, sizeof (hdr->mode), &stat->st_mode)) return (false); if (!get_int32(hdr->uid, sizeof (hdr->uid), &stat->st_uid)) return (false); if (!get_int32(hdr->gid, sizeof (hdr->gid), &stat->st_gid)) return (false); if (!get_uint32(hdr->nlink, sizeof (hdr->nlink), &stat->st_nlink)) return (false); if (!get_uint64(hdr->rdev, sizeof (hdr->rdev), &stat->st_rdev)) return (false); stat->st_mtim.tv_nsec = 0; if (!get_int64(hdr->mtime, sizeof (hdr->mtime), &stat->st_mtim.tv_sec)) return (false); stat->st_atim = stat->st_mtim; stat->st_ctim = stat->st_mtim; if (!get_uint64(hdr->filesize, sizeof (hdr->filesize), &stat->st_size)) return (false); stat->st_blksize = DEV_BSIZE; stat->st_blocks = P2ROUNDUP(stat->st_size, DEV_BSIZE); return (true); } static int check_archive_hdr(const struct cpio_hdr *hdr) { if ((hdr->magic[0] != '0') || (hdr->magic[1] != '7') || (hdr->magic[2] != '0') || (hdr->magic[3] != '7') || (hdr->magic[4] != '0') || (hdr->magic[5] != '7')) return (-1); return (0); } /* * Check if specified header is for a file with a specific path. If so, * fill in the file struct and return 0. If not, return number of bytes to * skip over to get to the next header. If an error occurs, -1 is returned. * If end of archive is reached, return -2 instead. */ static ssize_t scan_archive_hdr(const struct cpio_hdr *hdr, size_t off, struct cpio_file *file, const char *wanted_path) { struct bootstat stat; uint32_t namesize; uint64_t filesize; const char *path; const void *data; if (check_archive_hdr(hdr)) return (-1); if (!get_uint32(hdr->namesize, sizeof (hdr->namesize), &namesize)) return (-1); if (!get_uint64(hdr->filesize, sizeof (hdr->filesize), &filesize)) return (-1); /* * We have the two sizes, let's try to read the name and file * contents to make sure they are part of the ramdisk. */ off += offsetof(struct cpio_hdr, data[0]); path = read_ramdisk(off, namesize); data = read_ramdisk(off + namesize, filesize); /* either read failing is fatal */ if (path == NULL || data == NULL) return (-1); if (strcmp(path, "TRAILER!!!") == 0) return (-2); if (strcmp(path, wanted_path) != 0) return (offsetof(struct cpio_hdr, data[namesize + filesize])); /* * This is the file we want! */ if (!parse_stat(hdr, &stat)) return (-1); file->hdr = hdr; file->path = path; file->data = data; file->stat = stat; return (0); } static int find_filename(char *path, struct cpio_file *file) { size_t off; /* * The paths in the cpio boot archive omit the leading '/'. So, * skip checking for it. If the searched for path does not include * the leading path (it's a relative path), fail the lookup. */ if (path[0] != '/') return (-1); path++; /* now scan the archive for the relevant file */ off = 0; for (;;) { const struct cpio_hdr *hdr; ssize_t size; hdr = (struct cpio_hdr *)read_ramdisk(off, sizeof (struct cpio_hdr)); if (hdr == NULL) return (-1); size = scan_archive_hdr(hdr, off, file, path); if (size <= 0) return (size); off += size; } } static int bcpio_mountroot(char *str __unused) { const struct cpio_hdr *hdr; if (mounted) return (-1); hdr = (struct cpio_hdr *)read_ramdisk(0, sizeof (struct cpio_hdr)); if (hdr == NULL) return (-1); if (check_archive_hdr(hdr)) return (-1); mounted = true; return (0); } static int bcpio_unmountroot(void) { if (!mounted) return (-1); mounted = false; return (0); } static int bcpio_open(char *path, int flags __unused) { static int filedes = 1; struct cpio_file temp_file; struct cpio_file *file; if (find_filename(path, &temp_file) != 0) return (-1); file = bkmem_alloc(sizeof (struct cpio_file)); file->hdr = temp_file.hdr; file->path = temp_file.path; file->data = temp_file.data; file->stat = temp_file.stat; file->fd = filedes++; file->off = 0; add_open_file(file); return (file->fd); } static int bcpio_close(int fd) { struct cpio_file *file; file = find_open_file(fd); if (file == NULL) return (-1); remove_open_file(file); bkmem_free(file, sizeof (struct cpio_file)); return (0); } static void bcpio_closeall(int flag __unused) { struct cpio_file *file; while (!SLIST_EMPTY(&open_files)) { file = SLIST_FIRST(&open_files); if (bcpio_close(file->fd) != 0) { kobj_printf("closeall invoked close(%d) failed\n", file->fd); } } } static ssize_t bcpio_read(int fd, caddr_t buf, size_t size) { struct cpio_file *file; file = find_open_file(fd); if (file == NULL) return (-1); if (size == 0) return (0); if (file->off + size > file->stat.st_size) size = file->stat.st_size - file->off; bcopy((void *)((uintptr_t)file->data + file->off), buf, size); file->off += size; return (size); } static off_t bcpio_lseek(int fd, off_t addr, int whence) { struct cpio_file *file; file = find_open_file(fd); if (file == NULL) return (-1); switch (whence) { case SEEK_CUR: file->off += addr; break; case SEEK_SET: file->off = addr; break; case SEEK_END: file->off = file->stat.st_size; break; default: kobj_printf("lseek(): invalid whence value %d\n", whence); return (-1); } return (0); } static int bcpio_fstat(int fd, struct bootstat *buf) { const struct cpio_file *file; file = find_open_file(fd); if (file == NULL) return (-1); *buf = file->stat; return (0); } struct boot_fs_ops bcpio_ops = { .fsw_name = "boot_cpio", .fsw_mountroot = bcpio_mountroot, .fsw_unmountroot = bcpio_unmountroot, .fsw_open = bcpio_open, .fsw_close = bcpio_close, .fsw_closeall = bcpio_closeall, .fsw_read = bcpio_read, .fsw_lseek = bcpio_lseek, .fsw_fstat = bcpio_fstat, }; /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2025 MNX Cloud, Inc. */ /* * Decompression module for stand alone file systems. */ #include #include #include #include #include #include #include #include #define MAX_DECOMP_BUFS 8 #define GZIP_ID_BYTE_1 0x1f #define GZIP_ID_BYTE_2 0x8b #define GZIP_CM_DEFLATE 0x08 #define SEEKBUFSIZE 8192 extern int bootrd_debug; extern void *bkmem_alloc(size_t); extern void bkmem_free(void *, size_t); caddr_t scratch_bufs[MAX_DECOMP_BUFS]; /* array of free scratch mem bufs */ int decomp_bufcnt; /* total no, of allocated decomp bufs */ int free_dcomp_bufs; /* no. of free decomp bufs */ char seek_scrbuf[SEEKBUFSIZE]; /* buffer for seeking */ int cf_debug = 0; /* non-zero enables debug prints */ void * cf_alloc(void *opaque, unsigned int items, unsigned int size) { fileid_t *filep; unsigned int nbytes; caddr_t ptr; filep = (fileid_t *)opaque; nbytes = roundup(items * size, sizeof (long)); if (nbytes > (DECOMP_BUFSIZE - filep->fi_dcscrused)) { ptr = bkmem_alloc(nbytes); } else { ptr = &filep->fi_dcscrbuf[filep->fi_dcscrused]; filep->fi_dcscrused += nbytes; } bzero(ptr, nbytes); return (ptr); } /* * Decompression scratch memory free routine, does nothing since we free * the entire scratch area all at once on file close. */ void cf_free(void *opaque __unused, void *addr __unused) { } /* * Read the first block of the file described by filep and determine if * the file is gzip-compressed. If so, the compressed flag will be set * in the fileid_t struct pointed to by filep and it will be initialized * for doing decompression on reads to the file. */ int cf_check_compressed(fileid_t *filep) { unsigned char *filebytes; z_stream *zsp; /* * checking for a dcfs compressed file first would involve: * * if (filep->fi_inode->i_cflags & ICOMPRESS) * filep->fi_flags |= FI_COMPRESSED; */ /* * If the file is not long enough to check for a * decompression header then return not compressed. */ if (filep->fi_inode->i_size < 3) return (0); filep->fi_offset = 0; if ((filep->fi_getblock)(filep) == -1) return (-1); filep->fi_offset = 0; filep->fi_count = 0; filep->fi_cfoff = 0; filebytes = (unsigned char *)filep->fi_memp; if (filebytes[0] != GZIP_ID_BYTE_1 || filebytes[1] != GZIP_ID_BYTE_2 || filebytes[2] != GZIP_CM_DEFLATE) return (0); /* not compressed */ filep->fi_flags |= FI_COMPRESSED; if (cf_debug) kobj_printf("file %s is compressed\n", filep->fi_path); /* * Allocate decompress scratch buffer */ if (free_dcomp_bufs) { filep->fi_dcscrbuf = scratch_bufs[--free_dcomp_bufs]; } else { filep->fi_dcscrbuf = bkmem_alloc(DECOMP_BUFSIZE); decomp_bufcnt++; } filep->fi_dcscrused = 0; zsp = bkmem_alloc(sizeof (*zsp)); filep->fi_dcstream = zsp; /* * Initialize the decompression stream. Adding 16 to the window size * indicates that zlib should expect a gzip header. */ bzero(zsp, sizeof (*zsp)); zsp->opaque = filep; zsp->zalloc = cf_alloc; zsp->zfree = cf_free; zsp->avail_in = 0; zsp->next_in = NULL; zsp->avail_out = 0; zsp->next_out = NULL; if (inflateInit2(zsp, MAX_WBITS | 0x20) != Z_OK) { if (cf_debug) kobj_printf("inflateInit2() failed\n"); return (-1); } return (0); } /* * If the file described by fileid_t struct at *filep is compressed * free any resources associated with the decompression. (decompression * buffer, etc.). */ void cf_close(fileid_t *filep) { if ((filep->fi_flags & FI_COMPRESSED) == 0) return; if (cf_debug) kobj_printf("cf_close: %s\n", filep->fi_path); (void) inflateEnd(filep->fi_dcstream); bkmem_free(filep->fi_dcstream, sizeof (z_stream)); if (free_dcomp_bufs == MAX_DECOMP_BUFS) { bkmem_free(filep->fi_dcscrbuf, DECOMP_BUFSIZE); } else { scratch_bufs[free_dcomp_bufs++] = filep->fi_dcscrbuf; } } void cf_rewind(fileid_t *filep) { z_stream *zsp; if (cf_debug) kobj_printf("cf_rewind: %s\n", filep->fi_path); zsp = filep->fi_dcstream; zsp->avail_in = 0; zsp->next_in = NULL; (void) inflateReset(zsp); filep->fi_cfoff = 0; } #define FLG_FHCRC 0x02 /* crc field present */ #define FLG_FEXTRA 0x04 /* "extra" field present */ #define FLG_FNAME 0x08 /* file name field present */ #define FLG_FCOMMENT 0x10 /* comment field present */ /* * Read at the current uncompressed offset from the compressed file described * by *filep. Will return decompressed data. */ int cf_read(fileid_t *filep, caddr_t buf, size_t count) { z_stream *zsp; struct inode *ip; int err = Z_OK; int infbytes; off_t soff; caddr_t smemp; if (cf_debug) kobj_printf("cf_read: %s %lx bytes\n", filep->fi_path, count); zsp = filep->fi_dcstream; ip = filep->fi_inode; if (cf_debug) kobj_printf(" reading at offset %lx\n", zsp->total_out); zsp->next_out = (unsigned char *)buf; zsp->avail_out = count; while (zsp->avail_out != 0) { if (zsp->avail_in == 0 && filep->fi_cfoff < ip->i_size) { /* * read a block of the file to inflate */ soff = filep->fi_offset; smemp = filep->fi_memp; filep->fi_memp = NULL; filep->fi_offset = filep->fi_cfoff; filep->fi_count = 0; if ((*filep->fi_getblock)(filep) == -1) return (-1); filep->fi_offset = soff; zsp->next_in = (unsigned char *)filep->fi_memp; zsp->avail_in = filep->fi_count; filep->fi_memp = smemp; filep->fi_cfoff += filep->fi_count; } infbytes = zsp->avail_out; if (cf_debug) { kobj_printf("attempting inflate of %x bytes to " "buf at: %lx\n", zsp->avail_out, (unsigned long)zsp->next_out); } err = inflate(zsp, Z_NO_FLUSH); infbytes -= zsp->avail_out; if (cf_debug) { kobj_printf("inflated %x bytes, errcode=%d\n", infbytes, err); } /* * break out if we hit end of the compressed file * or the end of the compressed byte stream */ if (filep->fi_cfoff >= ip->i_size || err == Z_STREAM_END) break; } if (cf_debug) { kobj_printf("cf_read: returned %lx bytes\n", count - zsp->avail_out); } return (count - zsp->avail_out); } /* * Seek to the location specified by addr */ void cf_seek(fileid_t *filep, off_t addr, int whence) { z_stream *zsp; int readsz; if (cf_debug) kobj_printf("cf_seek: %s to %lx\n", filep->fi_path, addr); zsp = filep->fi_dcstream; if (whence == SEEK_CUR) addr += zsp->total_out; /* * To seek backwards, must rewind and seek forwards */ if (addr < zsp->total_out) { cf_rewind(filep); filep->fi_offset = 0; } else { addr -= zsp->total_out; } while (addr > 0) { readsz = MIN(addr, SEEKBUFSIZE); (void) cf_read(filep, seek_scrbuf, readsz); addr -= readsz; } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2025 MNX Cloud, Inc. */ /* * Basic file system reading code for standalone I/O system. * Simulates a primitive UNIX I/O system (read(), write(), open(), etc). * Does not support writes. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define hdbtodb(n) ((ISO_SECTOR_SIZE / DEV_BSIZE) * (n)) #define HSFS_NUM_SIG 14 #define SUSP_SP_IX 0 #define SUSP_CE_IX 1 #define SUSP_PD_IX 2 #define SUSP_ST_IX 3 #define SUSP_ER_IX 4 #define RRIP_PX_IX 5 #define RRIP_PN_IX 6 #define RRIP_SL_IX 7 #define RRIP_CL_IX 8 #define RRIP_PL_IX 9 #define RRIP_RE_IX 10 #define RRIP_RF_IX 11 #define RRIP_RR_IX 12 #define RRIP_NM_IX 13 extern int bootrd_debug; extern void *bkmem_alloc(size_t); extern void bkmem_free(void *, size_t); extern int cf_check_compressed(fileid_t *); extern void cf_close(fileid_t *); extern void cf_seek(fileid_t *, off_t, int); extern int cf_read(fileid_t *, caddr_t, size_t); struct dirstuff { int loc; fileid_t *filep; }; struct hs_direct { struct direct hs_ufs_dir; struct hs_direntry hs_dir; }; static uint_t root_ino = 0; static struct hs_volume *hsfsp; static fileid_t *head; static char *hsfs_sig_tab[] = { SUSP_SP, SUSP_CE, SUSP_PD, SUSP_ST, SUSP_ER, RRIP_PX, RRIP_PN, RRIP_SL, RRIP_CL, RRIP_PL, RRIP_RE, RRIP_TF, RRIP_RR, RRIP_NM }; static int hsfs_num_sig = sizeof (hsfs_sig_tab) / sizeof (hsfs_sig_tab[0]); /* * Local prototypes */ static struct hs_direct *readdir(struct dirstuff *); static uint_t parse_dir(fileid_t *, int, struct hs_direct *); static uint_t parse_susp(char *, uint_t *, struct hs_direct *); static ino_t dlook(char *, fileid_t *); static int opendir(ino_t, fileid_t *); static ino_t find(char *, fileid_t *); static int bhsfs_mountroot(char *str); static int bhsfs_unmountroot(void); static int bhsfs_open(char *str, int flags); static int bhsfs_close(int fd); static void bhsfs_closeall(void); static ssize_t bhsfs_read(int fdesc, char *buf, size_t count); static off_t bhsfs_lseek(int fdesc, off_t addr, int whence); static int bhsfs_fstat(int fdesc, struct bootstat *stp); static fileid_t * find_fp(int fd) { fileid_t *filep = head; if (fd >= 0) { while ((filep = filep->fi_forw) != head) if (fd == filep->fi_filedes) return (filep->fi_taken ? filep : 0); } return (0); } static int opendir(ino_t inode, fileid_t *filep) { struct hs_direct hsdep; if (bootrd_debug) kobj_printf("opendir: inode = %ld\n", inode); /* Set up the IO request */ filep->fi_offset = 0; filep->fi_blocknum = hdbtodb(inode); filep->fi_count = ISO_SECTOR_SIZE; filep->fi_memp = 0; if (diskread(filep)) return (0); filep->fi_offset = 0; filep->fi_blocknum = hdbtodb(inode); if (inode != root_ino) return (0); if (parse_dir(filep, 0, &hsdep) > 0) { struct inode *ip; ip = filep->fi_inode; if (ip == NULL) ip = filep->fi_inode = bkmem_alloc(sizeof (*ip)); ip->i_size = hsdep.hs_dir.ext_size; ip->i_smode = hsdep.hs_dir.mode; ip->i_number = inode; return (0); } return (1); } static ino_t find(char *path, fileid_t *filep) { char *q; char c; ino_t n; n = 0; if (bootrd_debug) kobj_printf("find: %s\n", path); if (path == NULL || *path == '\0') return (0); if (opendir(root_ino, filep)) return (0); while (*path) { while (*path == '/') path++; q = path; while (*q != '/' && *q != '\0') q++; c = *q; *q = '\0'; n = dlook(path, filep); *q = c; path = q; if (n != 0) { if (c == '\0') break; if (opendir(n, filep)) return (0); continue; } else { return (0); } } return ((ino_t)n); } static ino_t dlook(char *s, fileid_t *filep) { struct hs_direct *hsdep; struct direct *udp; struct inode *ip; struct dirstuff dirp; int len; if (bootrd_debug) kobj_printf("dlook: %s\n", s); ip = filep->fi_inode; if (s == NULL || *s == '\0') return (0); if ((ip->i_smode & IFMT) != IFDIR) { return (0); } if (ip->i_size == 0) { return (0); } len = strlen(s); dirp.loc = 0; dirp.filep = filep; for (hsdep = readdir(&dirp); hsdep != NULL; hsdep = readdir(&dirp)) { udp = &hsdep->hs_ufs_dir; if (udp->d_namlen == 1 && udp->d_name[0] == '.' && udp->d_name[1] == '\0') continue; if (udp->d_namlen == 2 && udp->d_name[0] == '.' && udp->d_name[1] == '.' && udp->d_name[2] == '\0') continue; if (udp->d_namlen == len && (strcmp(s, udp->d_name)) == 0) { struct inode *ip = filep->fi_inode; filep->fi_offset = 0; filep->fi_blocknum = hdbtodb(udp->d_ino); bzero(filep->fi_inode, sizeof (struct inode)); ip->i_size = hsdep->hs_dir.ext_size; ip->i_smode = hsdep->hs_dir.mode; ip->i_number = udp->d_ino; return (udp->d_ino); } } return (0); } /* * get next entry in a directory. */ static struct hs_direct * readdir(struct dirstuff *dirp) { static struct hs_direct hsdep; struct direct *udp = &hsdep.hs_ufs_dir; struct inode *ip; fileid_t *filep; daddr_t lbn; int off; if (bootrd_debug) kobj_printf("readdir: start\n"); filep = dirp->filep; ip = filep->fi_inode; for (;;) { if (dirp->loc >= ip->i_size) { return (NULL); } off = dirp->loc & ((1 << ISO_SECTOR_SHIFT) - 1); if (off == 0) { lbn = hdbtodb(dirp->loc >> ISO_SECTOR_SHIFT); filep->fi_blocknum = lbn + hdbtodb(ip->i_number); filep->fi_count = ISO_SECTOR_SIZE; filep->fi_memp = 0; if (diskread(filep)) { if (bootrd_debug) { kobj_printf( "readdir: diskread failed\n"); } return (NULL); } } dirp->loc += parse_dir(filep, off, &hsdep); if (udp->d_reclen == 0 && dirp->loc <= ip->i_size) { dirp->loc = roundup(dirp->loc, ISO_SECTOR_SIZE); continue; } return (&hsdep); } } static int getblock(fileid_t *filep) { struct inode *ip = filep->fi_inode; int off, size, diff; daddr_t lbn; if (bootrd_debug) kobj_printf("getblock: start\n"); diff = ip->i_size - filep->fi_offset; if (diff <= 0) return (-1); /* which block (or frag) in the file do we read? */ lbn = hdbtodb(filep->fi_offset >> ISO_SECTOR_SHIFT); filep->fi_blocknum = lbn + hdbtodb(ip->i_number); off = filep->fi_offset & ((1 << ISO_SECTOR_SHIFT) - 1); size = filep->fi_count = ISO_SECTOR_SIZE; filep->fi_memp = 0; if (diskread(filep)) /* Trap errors */ return (-1); if (filep->fi_offset - off + size >= ip->i_size) filep->fi_count = diff + off; filep->fi_count -= off; filep->fi_memp += off; if (bootrd_debug) kobj_printf("getblock: end\n"); return (0); } static ssize_t bhsfs_read(int fd, caddr_t buf, size_t count) { int i, j; fileid_t *filep; struct inode *ip; caddr_t n; if (bootrd_debug) kobj_printf("bhsfs_read %d, count 0x%lx\n", fd, count); filep = find_fp(fd); if (filep == NULL) return (-1); ip = filep->fi_inode; n = buf; if ((filep->fi_flags & FI_COMPRESSED) == 0 && filep->fi_offset + count > ip->i_size) count = ip->i_size - filep->fi_offset; if ((i = count) <= 0) return (0); while (i > 0) { if (filep->fi_flags & FI_COMPRESSED) { if ((j = cf_read(filep, buf, count)) < 0) return (0); /* encountered an error */ if (j < i) i = j; /* short read, must have hit EOF */ } else { if (filep->fi_count == 0) { if (getblock(filep) == -1) return (0); } j = MIN(i, filep->fi_count); bcopy(filep->fi_memp, buf, (uint_t)j); } filep->fi_memp += j; filep->fi_offset += j; filep->fi_count -= j; buf += j; i -= j; } if (bootrd_debug) kobj_printf("bhsfs_read: read 0x%x\n", (int)(buf - n)); return (buf - n); } static int bhsfs_mountroot(char *str __unused) { char *bufp; if (hsfsp != NULL) return (0); /* already mounted */ if (bootrd_debug) kobj_printf("mounting ramdisk as hsfs\n"); hsfsp = bkmem_alloc(sizeof (*hsfsp)); bzero(hsfsp, sizeof (*hsfsp)); head = bkmem_alloc(sizeof (*head)); bzero(head, sizeof (*head)); head->fi_back = head->fi_forw = head; /* now read the superblock. */ head->fi_blocknum = hdbtodb(ISO_VOLDESC_SEC); head->fi_offset = 0; head->fi_count = ISO_SECTOR_SIZE; head->fi_memp = head->fi_buf; if (diskread(head)) { kobj_printf("failed to read superblock\n"); bhsfs_closeall(); return (-1); } /* Since RRIP is based on ISO9660, that's where we start */ bufp = head->fi_buf; if ((ISO_DESC_TYPE(bufp) != ISO_VD_PVD) || (strncmp((const char *)ISO_std_id(bufp), ISO_ID_STRING, ISO_ID_STRLEN) != 0) || (ISO_STD_VER(bufp) != ISO_ID_VER)) { if (bootrd_debug) kobj_printf("volume type does not match\n"); bhsfs_closeall(); return (-1); } /* Now we fill in the volume descriptor */ hsfsp->vol_size = ISO_VOL_SIZE(bufp); hsfsp->lbn_size = ISO_BLK_SIZE(bufp); hsfsp->lbn_shift = ISO_SECTOR_SHIFT; hsfsp->lbn_secshift = ISO_SECTOR_SHIFT; hsfsp->vol_set_size = (ushort_t)ISO_SET_SIZE(bufp); hsfsp->vol_set_seq = (ushort_t)ISO_SET_SEQ(bufp); /* Make sure we have a valid logical block size */ if (hsfsp->lbn_size & ~(1 << hsfsp->lbn_shift)) { kobj_printf("%d invalid logical block size\n", hsfsp->lbn_size); bhsfs_closeall(); return (-1); } /* Since an HSFS root could be located anywhere on the media! */ root_ino = IDE_EXT_LBN(ISO_root_dir(bufp)); return (0); } static int bhsfs_unmountroot(void) { if (hsfsp == NULL) return (-1); bhsfs_closeall(); return (0); } /* * Open a file. */ int bhsfs_open(char *str, int flags __unused) { static int filedes = 1; fileid_t *filep; ino_t ino; if (bootrd_debug) kobj_printf("open %s\n", str); filep = (fileid_t *)bkmem_alloc(sizeof (fileid_t)); filep->fi_back = head->fi_back; filep->fi_forw = head; head->fi_back->fi_forw = filep; head->fi_back = filep; filep->fi_filedes = filedes++; filep->fi_taken = 1; filep->fi_path = (char *)bkmem_alloc(strlen(str) + 1); (void) strcpy(filep->fi_path, str); filep->fi_inode = NULL; bzero(filep->fi_buf, MAXBSIZE); filep->fi_getblock = getblock; filep->fi_flags = 0; ino = find(str, filep); if (ino == 0) { (void) bhsfs_close(filep->fi_filedes); return (-1); } filep->fi_blocknum = hdbtodb(ino); filep->fi_offset = 0; filep->fi_count = 0; filep->fi_memp = 0; if (cf_check_compressed(filep) != 0) return (-1); if (bootrd_debug) kobj_printf("open done\n"); return (filep->fi_filedes); } int bhsfs_close(int fd) { fileid_t *filep; if (bootrd_debug) kobj_printf("close %d\n", fd); if (!(filep = find_fp(fd))) return (-1); if (filep->fi_taken == 0 || filep == head) { kobj_printf("File descripter %d not allocated!\n", fd); return (-1); } cf_close(filep); /* unlink and deallocate node */ filep->fi_forw->fi_back = filep->fi_back; filep->fi_back->fi_forw = filep->fi_forw; if (filep->fi_inode) bkmem_free(filep->fi_inode, sizeof (struct inode)); bkmem_free(filep->fi_path, strlen(filep->fi_path) + 1); bkmem_free((char *)filep, sizeof (fileid_t)); if (bootrd_debug) kobj_printf("close done\n"); return (0); } static void bhsfs_closeall(void) { fileid_t *filep; while ((filep = head->fi_forw) != head) if (filep->fi_taken && bhsfs_close(filep->fi_filedes)) kobj_printf("Filesystem may be inconsistent.\n"); bkmem_free(hsfsp, sizeof (*hsfsp)); bkmem_free(head, sizeof (fileid_t)); hsfsp = NULL; head = NULL; } /* * This version of seek() only performs absolute seeks (whence == 0). */ static off_t bhsfs_lseek(int fd, off_t addr, int whence) { fileid_t *filep; if (bootrd_debug) kobj_printf("lseek %d, off = %lx\n", fd, addr); if (!(filep = find_fp(fd))) return (-1); if (filep->fi_flags & FI_COMPRESSED) { cf_seek(filep, addr, whence); } else { switch (whence) { case SEEK_CUR: filep->fi_offset += addr; break; case SEEK_SET: filep->fi_offset = addr; break; default: case SEEK_END: kobj_printf("lseek(): invalid whence value %d\n", whence); break; } filep->fi_blocknum = addr / DEV_BSIZE; } filep->fi_count = 0; return (0); } static int bhsfs_fstat(int fd, struct bootstat *stp) { fileid_t *filep; struct inode *ip; if (!(filep = find_fp(fd))) return (-1); ip = filep->fi_inode; stp->st_mode = 0; stp->st_size = 0; if (ip == NULL) return (0); switch (ip->i_smode & IFMT) { case IFDIR: stp->st_mode = S_IFDIR; break; case IFREG: stp->st_mode = S_IFREG; break; default: break; } /* * NOTE: this size will be the compressed size for a compressed file * This could confuse the caller since we decompress the file behind * the scenes when the file is read. */ stp->st_size = ip->i_size; /* file times */ stp->st_atim.tv_sec = ip->i_atime.tv_sec; stp->st_atim.tv_nsec = ip->i_atime.tv_usec * 1000; stp->st_mtim.tv_sec = ip->i_mtime.tv_sec; stp->st_mtim.tv_nsec = ip->i_mtime.tv_usec * 1000; stp->st_ctim.tv_sec = ip->i_ctime.tv_sec; stp->st_ctim.tv_nsec = ip->i_ctime.tv_usec * 1000; return (0); } /* * Parse a directory entry. * */ static uint_t parse_dir(fileid_t *filep, int offset, struct hs_direct *hsdep) { char *bufp = (char *)(filep->fi_memp + offset); struct direct *udp = &hsdep->hs_ufs_dir; /* ufs-style dir info */ struct hs_direntry *hdp = &hsdep->hs_dir; /* hsfs-style dir info */ uint_t ce_lbn; uint_t ce_len; uint_t nmlen; uint_t i; uchar_t c; if (bootrd_debug) kobj_printf("parse_dir: offset = %d\n", offset); /* a zero length dir entry terminates the dir block */ udp->d_reclen = IDE_DIR_LEN(bufp); if (udp->d_reclen == 0) return (0); /* fill in some basic hsfs info */ hdp->ext_lbn = IDE_EXT_LBN(bufp); hdp->ext_size = IDE_EXT_SIZE(bufp); hdp->xar_len = IDE_XAR_LEN(bufp); hdp->intlf_sz = IDE_INTRLV_SIZE(bufp); hdp->intlf_sk = IDE_INTRLV_SKIP(bufp); hdp->sym_link = NULL; /* we use lbn of data extent as an inode # equivalent */ udp->d_ino = hdp->ext_lbn; c = IDE_FLAGS(bufp); if (IDE_REGULAR_FILE(c)) { hdp->type = VREG; hdp->mode = IFREG; hdp->nlink = 1; } else if (IDE_REGULAR_DIR(c)) { hdp->type = VDIR; hdp->mode = IFDIR; hdp->nlink = 2; } else { kobj_printf("pd(): file type=0x%x unknown.\n", c); } /* * Massage hsfs name, recognizing special entries for . and .. * else lopping off version junk. */ /* Some initial conditions */ nmlen = IDE_NAME_LEN(bufp); c = *IDE_NAME(bufp); /* Special Case: Current Directory */ if (nmlen == 1 && c == '\0') { udp->d_name[0] = '.'; udp->d_name[1] = '\0'; udp->d_namlen = 1; /* Special Case: Parent Directory */ } else if (nmlen == 1 && c == '\001') { udp->d_name[0] = '.'; udp->d_name[1] = '.'; udp->d_name[2] = '\0'; udp->d_namlen = 2; /* Other file name */ } else { udp->d_namlen = 0; for (i = 0; i < nmlen; i++) { c = *(IDE_name(bufp)+i); if (c == ';') break; else if (c == ' ') continue; else udp->d_name[udp->d_namlen++] = c; } udp->d_name[udp->d_namlen] = '\0'; } /* System Use Fields */ ce_len = IDE_SUA_LEN(bufp); if (ce_len == 0) return (udp->d_reclen); /* there is an SUA for this dir entry; go parse it */ ce_lbn = parse_susp((char *)IDE_sys_use_area(bufp), &ce_len, hsdep); if (ce_lbn) { /* * store away current position in dir, * as we will be using the iobuf to reading SUA. */ daddr_t save_bn = filep->fi_blocknum; daddr_t save_offset = filep->fi_offset; caddr_t save_ma = filep->fi_memp; int save_cc = filep->fi_count; do { filep->fi_count = ISO_SECTOR_SIZE; filep->fi_offset = 0; filep->fi_blocknum = hdbtodb(ce_lbn); filep->fi_memp = 0; if (diskread(filep)) { kobj_printf("failed to read cont. area\n"); ce_len = 0; ce_lbn = 0; break; } ce_lbn = parse_susp(filep->fi_memp, &ce_len, hsdep); } while (ce_lbn); filep->fi_count = save_cc; filep->fi_offset = save_offset; filep->fi_blocknum = save_bn; filep->fi_memp = save_ma; } return (udp->d_reclen); } /* * Parse the System Use Fields in this System Use Area. * Return blk number of continuation/SUA, or 0 if no continuation/not a SUA. */ static uint_t parse_susp(char *bufp, uint_t *len, struct hs_direct *hsdep) { struct direct *udp = &hsdep->hs_ufs_dir; /* ufs-style info */ char *susp; uint_t cur_off = 0; uint_t blk_len = *len; uint_t susp_len = 0; uint_t ce_lbn = 0; uint_t i; if (bootrd_debug) kobj_printf("parse_susp: len = %d\n", *len); while (cur_off < blk_len) { susp = (char *)(bufp + cur_off); /* * A null entry, or an entry with zero length * terminates the SUSP. */ if (susp[0] == '\0' || susp[1] == '\0' || (susp_len = SUF_LEN(susp)) == 0) break; /* * Compare current entry to all known signatures. */ for (i = 0; i < hsfs_num_sig; i++) if (strncmp(hsfs_sig_tab[i], susp, SUF_SIG_LEN) == 0) break; switch (i) { case SUSP_CE_IX: /* * CE signature: continuation of SUSP. * will want to return new lbn, len. */ ce_lbn = CE_BLK_LOC(susp); *len = CE_CONT_LEN(susp); break; case RRIP_NM_IX: /* NM signature: POSIX-style file name */ if (!RRIP_NAME_FLAGS(susp)) { udp->d_namlen = RRIP_NAME_LEN(susp); bcopy((char *)RRIP_name(susp), udp->d_name, udp->d_namlen); udp->d_name[udp->d_namlen] = '\0'; } break; case HSFS_NUM_SIG: /* couldn't find a legit susp, terminate loop */ case SUSP_ST_IX: /* ST signature: terminates SUSP */ return (ce_lbn); case SUSP_SP_IX: case RRIP_RR_IX: default: break; } cur_off += susp_len; } return (ce_lbn); } struct boot_fs_ops bhsfs_ops = { "boot_hsfs", bhsfs_mountroot, bhsfs_unmountroot, bhsfs_open, bhsfs_close, bhsfs_read, bhsfs_lseek, bhsfs_fstat, NULL }; /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _PCFILEP_H #define _PCFILEP_H #ifdef __cplusplus extern "C" { #endif #define MAX_DOSMOUNT_RETRIES 3 #define TICKS_PER_SEC 18 /* It's really 18.2! */ #define SECSIZ 512 #define fat_bpc(i) (pi[(i)]->f_bpb.bs_spc * SECSIZ) /* * Access permissions for dosAccess(), dosOpen() * NOTE: These permission need to match those for the DOS compiler. */ #define FILE_EXISTS 1 #define FILE_READ 0x0000 #define FILE_WRITE 0x0001 #define FILE_RDWR 0x0002 #define FILE_APPEND 0x0008 #define FILE_CREATE 0x0100 #define FILE_TRUNC 0x0200 #define TYPE_EMPTY 0x00 /* undefined partition */ #define TYPE_DOS 0x13 /* causes fatInit() to search for */ /* active partition */ #define TYPE_DOS_12 0x01 /* partition with FAT12 filesys */ #define TYPE_DOS_16 0x04 /* partition with FAT16 filesys */ #define TYPE_DOS_EXT 0x05 /* not bootable, ignore */ #define TYPE_HUGH 0x06 /* HUGH partition */ #define TYPE_COMPAQ 0x12 /* Compaq's diag partition */ #define TYPE_SOLARIS 0x82 #define TYPE_SOLARIS_BOOT 0xBE /* For "boot hill" project */ #define FDISK_START 0x1be /* location in first sector where */ /* the fdisk starts. */ #define FDISK_PARTS 4 /* Number of partitions in a fdisk */ #define FDISK_ACTIVE 0x80 /* indicates partition is active */ #define FDISK_INACTIVE 0x00 /* " partition inactive */ #pragma pack(1) struct _fdisk_partition_ { uchar_t fd_active; uchar_t fd_b_head; uchar_t fd_b_sec; uchar_t fd_b_cyl; uchar_t fd_type; uchar_t fd_e_head; uchar_t fd_e_sec; uchar_t fd_e_cyl; union { long fd_start_sec_long; struct { ushort_t low; ushort_t high; } s; } u; long fd_part_len; }; #define fd_start_sec u.fd_start_sec_long #define fd_partition fd_type typedef struct _fdisk_partition_ _fdisk_t, *_fdisk_p; #pragma pack() #pragma pack(1) struct _boot_sector_ { uchar_t bs_jump_code[3]; uchar_t bs_oem_name[8]; uchar_t bs_bytes_sector[2]; uchar_t bs_spc; /* ... sectors per cluster */ uchar_t bs_resv_sectors[2]; uchar_t bs_num_fats; uchar_t bs_num_root_entries[2]; uchar_t bs_siv[2]; /* ... sectors in volume */ uchar_t bs_media; uchar_t bs_spf[2]; /* ... sectors per fat */ uchar_t bs_sectors_per_track[2]; uchar_t bs_heads[2]; /* * Byte offset at this point is 28 so we can declare the next * variable with the correct type and not worry about alignment. */ long bs_hidden_sectors; long bs_lsiv; /* ... logical sectors in volume */ uchar_t bs_phys_drive_num; uchar_t bs_reserved; uchar_t bs_ext_signature; char bs_volume_id[4]; char bs_volume_label[11]; char bs_type[8]; /* ---- ADDED BY SUNSOFT FOR MDBOOT ---- */ ushort_t bs_offset_high; ushort_t bs_offset_low; }; #pragma pack() typedef struct _boot_sector_ _boot_sector_t, *_boot_sector_p; /* * Cluster types */ #define CLUSTER_AVAIL 0x00 #define CLUSTER_RES_12_0 0x0ff0 /* 12bit fat, first reserved */ #define CLUSTER_RES_12_6 0x0ff6 /* 12bit fat, last reserved */ #define CLUSTER_RES_16_0 0xfff0 /* 16bit fat, first reserved */ #define CLUSTER_RES_16_6 0xfff6 /* 16bit fat, last reserved */ #define CLUSTER_BAD_12 0x0ff7 /* 12bit fat, bad entry */ #define CLUSTER_BAD_16 0xfff7 /* 16bit fat, bad entry */ #define CLUSTER_EOF CLUSTER_EOF_16_0 #define CLUSTER_MAX_12 0x0ff7 /* max clusters for 12bit fat */ #define CLUSTER_EOF_12_0 0x0ff8 /* 12bit fat, EOF first entry */ #define CLUSTER_EOF_12_8 0x0fff /* 12bit fat, EOF last entry */ #define CLUSTER_EOF_16_0 0xfff8 /* 16bit fat, EOF first entry */ #define CLUSTER_EOF_16_8 0xffff /* 16bit fat, EOF last entry */ /* * Cluster operations for allocation */ #define CLUSTER_NOOP 0x0001 /* ... just allocate cluster */ #define CLUSTER_ZEROFILL 0x0002 /* ... zero fill the alloc'd cluster */ #define CLUSTER_FIRST 0x0002 /* ... first cluster number to search */ #define CLUSTER_ROOTDIR 0x0000 /* ... root dir's cluster number */ /* * This structure is filled in by initFAT() */ struct _fat_controller_ { union { _boot_sector_t fu_bpb; /* boot parameter block */ uchar_t fu_sector[SECSIZ]; } fu; long f_adjust; /* starting sec for part. */ long f_rootsec; /* root dir starting sec. */ long f_rootlen; /* length of root in sectors */ long f_filesec; /* adjustment for clusters */ long f_dclust; /* cur dir cluster */ int f_nxtfree; /* next free cluster */ int f_ncluster; /* number of cluster in part */ char f_16bit:1, /* 1 if 16bit fat entries */ f_flush:1; /* flush the fat */ }; typedef struct _fat_controller_ _fat_controller_t, *_fat_controller_p; #define f_bpb fu.fu_bpb #define f_sector fu.fu_sector #define NAMESIZ 8 #define EXTSIZ 3 #pragma pack(1) struct _dir_entry_ { char d_name[NAMESIZ]; char d_ext[EXTSIZ]; uchar_t d_attr; char d_res[10]; short d_time; short d_date; ushort_t d_cluster; long d_size; }; #pragma pack() typedef struct _dir_entry_ _dir_entry_t, *_dir_entry_p; /* * Number of entries in one sector */ #define DIRENTS (SECSIZ / sizeof (_dir_entry_t)) /* * Directory entry attributes */ #define DE_READONLY 0x01 #define DE_HIDDEN 0x02 #define DE_SYSTEM 0x04 #define DE_LABEL 0x08 #define DE_DIRECTORY 0x10 #define DE_ARCHIVE 0x20 #define DE_RESERVED1 0x40 #define DE_RESERVED2 0x80 #define DE_IS_LFN (DE_READONLY | DE_HIDDEN | DE_SYSTEM | DE_LABEL) struct _file_descriptor_ { struct _file_descriptor_ *f_forw; /* link to next file descriptor */ int f_desc; /* descriptor number */ long f_startclust; /* starting cluster number */ long f_off; /* current offset */ long f_len; /* size of file */ long f_index; /* index into directory block */ uchar_t f_attr; /* attributes */ int f_volidx; /* Volume device index */ char *f_volname; /* Name of volume */ }; typedef struct _file_descriptor_ _file_desc_t, *_file_desc_p; #ifdef __cplusplus } #endif #endif /* _PCFILEP_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Basic file system reading code for standalone I/O system. * Simulates a primitive UNIX I/O system (read(), write(), open(), etc). * Does not support writes. */ /* * WARNING: * This is currently used by installgrub for creating bootable floppy. * The special part is diskread_callback/fileread_callback for gathering * fileblock list. */ #include #include #include #include #include #include #include "pcfilep.h" #if defined(_BOOT) #include "../common/util.h" #elif defined(_KERNEL) #include #else #include #include #include #endif #if defined(_BOOT) #define dprintf if (bootrd_debug) printf #elif defined(_KERNEL) #define printf kobj_printf #define dprintf if (bootrd_debug) kobj_printf /* PRINTLIKE */ extern void kobj_printf(char *, ...); #else #define dprintf if (bootrd_debug) printf #endif #define FI_STARTCLUST(fp) (*(ushort_t *)(fp)->fi_buf) #define FI_LENGTH(fp) (*(long *)((fp)->fi_buf + 4)) extern int bootrd_debug; extern void *bkmem_alloc(size_t); extern void bkmem_free(void *, size_t); /* * NOTE: The fileread_callback is set by the calling program * during a file read. diskread_callback is set to fileread_callback * only if reading a file block. It needs to be NULL while reading * cluster blocks. */ extern int (*diskread_callback)(int, int); extern int (*fileread_callback)(int, int); /* * Local prototypes */ static int lookuppn(char *, _dir_entry_p); static fileid_t *find_fp(int); static void *readblock(int, int); static int fat_map(int, int); static int cluster_valid(long, int); static int fat_ctodb(int, int); static int bpcfs_mountroot(char *str); static int bpcfs_unmountroot(void); static int bpcfs_open(char *str, int flags); static int bpcfs_close(int fd); static void bpcfs_closeall(void); static ssize_t bpcfs_read(int fdesc, char *buf, size_t count); static off_t bpcfs_lseek(int fdesc, off_t addr, int whence); static fileid_t *head; static _fat_controller_p pcfsp; /* cache the cluster */ static int nsec_cache; static int nsec_start; static char *cluster_cache; /*ARGSUSED*/ static int bpcfs_mountroot(char *str) { int ncluster; if (pcfsp != NULL) return (0); /* already mounted */ pcfsp = bkmem_alloc(sizeof (_fat_controller_t)); head = (fileid_t *)bkmem_alloc(sizeof (fileid_t)); head->fi_back = head->fi_forw = head; head->fi_filedes = 0; head->fi_taken = 0; /* read of first floppy sector */ head->fi_blocknum = 0; head->fi_count = SECSIZ; head->fi_memp = (caddr_t)pcfsp->f_sector; if (diskread(head)) { printf("failed to read first sector\n"); bkmem_free(pcfsp, sizeof (*pcfsp)); pcfsp = NULL; return (-1); } if (pcfsp->f_bpb.bs_spc == 0) { printf("invalid bios paramet block\n"); return (-1); } pcfsp->f_rootsec = (pcfsp->f_bpb.bs_num_fats * ltohs(pcfsp->f_bpb.bs_spf)) + ltohs(pcfsp->f_bpb.bs_resv_sectors); pcfsp->f_rootlen = ltohs(pcfsp->f_bpb.bs_num_root_entries) * sizeof (_dir_entry_t) / SECSIZ; pcfsp->f_adjust = 0; pcfsp->f_dclust = CLUSTER_ROOTDIR; pcfsp->f_filesec = pcfsp->f_rootsec + pcfsp->f_rootlen; pcfsp->f_nxtfree = CLUSTER_FIRST; /* figure out the number of clusters in this partition */ ncluster = (((ulong_t)ltohs(pcfsp->f_bpb.bs_siv) ? (ulong_t)ltohs(pcfsp->f_bpb.bs_siv) : (ulong_t)ltohi(pcfsp->f_bpb.bs_siv)) - pcfsp->f_filesec) / (ulong_t)pcfsp->f_bpb.bs_spc; pcfsp->f_16bit = ncluster >= CLUSTER_MAX_12; pcfsp->f_ncluster = ncluster; /* cache the cluster */ if (pcfsp->f_16bit) nsec_cache = (((ncluster << 1) + 511) >> 9); else nsec_cache = (ncluster + ((ncluster + 1) >> 1) + 511) >> 9; cluster_cache = bkmem_alloc(nsec_cache * SECSIZ); if (cluster_cache == NULL) { printf("bpcfs_mountroot: out of memory\n"); bkmem_free(pcfsp, sizeof (*pcfsp)); pcfsp = NULL; return (-1); } head->fi_blocknum = nsec_start = ltohs(pcfsp->f_bpb.bs_resv_sectors) + pcfsp->f_adjust; head->fi_count = nsec_cache * SECSIZ; head->fi_memp = cluster_cache; if (diskread(head)) { printf("bpcfs_mountroot: failed to read cluster\n"); bkmem_free(pcfsp, sizeof (*pcfsp)); pcfsp = NULL; return (-1); } dprintf("read cluster sectors %d starting at %d\n", nsec_cache, nsec_start); return (0); } static int bpcfs_unmountroot(void) { if (pcfsp == NULL) return (-1); (void) bpcfs_closeall(); return (0); } /* * Open a file. */ /*ARGSUSED*/ int bpcfs_open(char *str, int flags) { static int filedes = 1; fileid_t *filep; _dir_entry_t d; dprintf("open %s\n", str); filep = (fileid_t *)bkmem_alloc(sizeof (fileid_t)); filep->fi_back = head->fi_back; filep->fi_forw = head; head->fi_back->fi_forw = filep; head->fi_back = filep; filep->fi_filedes = filedes++; filep->fi_taken = 1; filep->fi_path = (char *)bkmem_alloc(strlen(str) + 1); (void) strcpy(filep->fi_path, str); if (lookuppn(str, &d)) { (void) bpcfs_close(filep->fi_filedes); return (-1); } filep->fi_offset = 0; FI_STARTCLUST(filep) = d.d_cluster; FI_LENGTH(filep) = d.d_size; dprintf("file %s size = %ld\n", str, d.d_size); return (filep->fi_filedes); } int bpcfs_close(int fd) { fileid_t *filep; dprintf("close %d\n", fd); if (!(filep = find_fp(fd))) return (-1); if (filep->fi_taken == 0 || filep == head) { printf("File descripter %d no allocated!\n", fd); return (-1); } /* unlink and deallocate node */ filep->fi_forw->fi_back = filep->fi_back; filep->fi_back->fi_forw = filep->fi_forw; bkmem_free(filep->fi_path, strlen(filep->fi_path) + 1); bkmem_free((char *)filep, sizeof (fileid_t)); dprintf("close done\n"); return (0); } static void bpcfs_closeall(void) { fileid_t *filep; while ((filep = head->fi_forw) != head) if (filep->fi_taken && bpcfs_close(filep->fi_filedes)) printf("Filesystem may be inconsistent.\n"); bkmem_free(pcfsp, sizeof (*pcfsp)); bkmem_free(head, sizeof (fileid_t)); pcfsp = NULL; head = NULL; } static ssize_t bpcfs_read(int fd, caddr_t b, size_t c) { ulong_t sector; uint_t count = 0, xfer, i; char *block; ulong_t off, blk; int rd, spc; fileid_t *fp; dprintf("bpcfs_read: fd = %d, buf = %p, size = %d\n", fd, (void *)b, c); fp = find_fp(fd); if (fp == NULL) { printf("invalid file descriptor %d\n", fd); return (-1); } spc = pcfsp->f_bpb.bs_spc; off = fp->fi_offset; blk = FI_STARTCLUST(fp); rd = blk == CLUSTER_ROOTDIR ? 1 : 0; spc = pcfsp->f_bpb.bs_spc; off = fp->fi_offset; blk = FI_STARTCLUST(fp); rd = (blk == CLUSTER_ROOTDIR) ? 1 : 0; if ((c = MIN(FI_LENGTH(fp) - off, c)) == 0) return (0); while (off >= pcfsp->f_bpb.bs_spc * SECSIZ) { blk = fat_map(blk, rd); off -= pcfsp->f_bpb.bs_spc * SECSIZ; if (!cluster_valid(blk, rd)) { printf("bpcfs_read: invalid cluster: %ld, %d\n", blk, rd); return (-1); } } while (count < c) { sector = fat_ctodb(blk, rd); diskread_callback = fileread_callback; for (i = ((off / SECSIZ) % pcfsp->f_bpb.bs_spc); i < spc; i++) { xfer = MIN(SECSIZ - (off % SECSIZ), c - count); if (xfer == 0) break; /* last sector done */ block = (char *)readblock(sector + i, 1); if (block == NULL) { return (-1); } dprintf("bpcfs_read: read %d bytes\n", xfer); if (diskread_callback == NULL) (void) bcopy(&block[off % SECSIZ], b, xfer); count += xfer; off += xfer; b += xfer; } diskread_callback = NULL; if (count < c) { blk = fat_map(blk, rd); if (!cluster_valid(blk, rd)) { printf("bpcfs_read: invalid cluster: %ld, %d\n", blk, rd); break; } } } fp->fi_offset += count; return (count); } /* * This version of seek() only performs absolute seeks (whence == 0). */ static off_t bpcfs_lseek(int fd, off_t addr, int whence) { fileid_t *filep; dprintf("lseek %d, off = %lx\n", fd, addr); if (!(filep = find_fp(fd))) return (-1); switch (whence) { case SEEK_CUR: filep->fi_offset += addr; break; case SEEK_SET: filep->fi_offset = addr; break; default: case SEEK_END: printf("lseek(): invalid whence value %d\n", whence); break; } filep->fi_blocknum = addr / DEV_BSIZE; filep->fi_count = 0; return (0); } static fileid_t * find_fp(int fd) { fileid_t *filep = head; if (fd >= 0) { while ((filep = filep->fi_forw) != head) if (fd == filep->fi_filedes) return (filep->fi_taken ? filep : 0); } return (0); } static int cluster_valid(long c, int rd) { return ((rd && (c == 0)) ? 1 : (c >= CLUSTER_RES_16_0 ? 0 : c)); } static int fat_ctodb(int blk, int r) { uint_t s; s = r ? blk + pcfsp->f_rootsec + pcfsp->f_adjust : ((blk - 2) * pcfsp->f_bpb.bs_spc) + pcfsp->f_filesec + pcfsp->f_adjust; return (s); } static int fat_map(int blk, int rootdir) { ulong_t sectn, fat_index; uchar_t *fp; if (rootdir) { return (blk > pcfsp->f_rootlen ? CLUSTER_EOF : blk + 1); } /* ---- Find out what sector this cluster is in ---- */ fat_index = (pcfsp->f_16bit) ? ((ulong_t)blk << 1) : ((ulong_t)blk + ((uint_t)blk >> 1)); sectn = (fat_index / SECSIZ) + ltohs(pcfsp->f_bpb.bs_resv_sectors) + pcfsp->f_adjust; /* * Read two sectors so that if our fat_index points at the last byte * byte we'll have the data needed. This is only a problem for fat12 * entries. */ if (!(fp = (uchar_t *)readblock(sectn, 2))) { printf("fat_map: bad cluster\n"); return (CLUSTER_BAD_16); } fp += (fat_index % SECSIZ); if (pcfsp->f_16bit) blk = fp[0] | (fp[1] << 8); else { if (blk & 1) blk = ((fp[0] >> 4) & 0xf) | (fp[1] << 4); else blk = ((fp[1] & 0xf) << 8) | fp[0]; /* * This makes compares easier because we can just compare * against one value instead of two. */ if (blk >= CLUSTER_RES_12_0) blk |= CLUSTER_RES_16_0; } return (blk); } static int namecmp(char *pn, char *dn, int cs) { dprintf("namecmp %s, %s, len = %d\n", pn, dn, cs); /* starting char must match */ while (*pn && *dn) { --cs; if (toupper(*pn++) != toupper(*dn++)) return (1); } dprintf("namecmp: cs = %d\n", cs); /* remainder should be either ~# or all spaces */ if (cs > 0 && *dn == '~') return (0); while (cs > 0) { if (*dn++ != ' ') return (1); --cs; } return (0); } static int dircmp(char *name, char *d_name, char *d_ext) { int ret; char *sep, *ext; sep = (char *)strchr(name, '.'); if (sep) { *sep = '\0'; ext = sep + 1; } else ext = " "; if (namecmp(name, d_name, NAMESIZ) || namecmp(ext, d_ext, EXTSIZ)) ret = 1; else ret = 0; if (sep) *sep = '.'; return (ret); } static int lookup(char *n, _dir_entry_p dp, ulong_t dir_blk) { int spc = pcfsp->f_bpb.bs_spc; int rd = (dir_blk == CLUSTER_ROOTDIR ? 1 : 0); _dir_entry_p dxp; int j, sector; dprintf("lookup: name = %s\n", n); while (cluster_valid(dir_blk, rd)) { sector = fat_ctodb(dir_blk, rd); dxp = readblock(sector, 1); /* read one sector */ if (dxp == NULL) return (0); for (j = 0; j < DIRENTS * spc; j++, dxp++) { dprintf("lookup: dir entry %s.%s;\n", dxp->d_name, dxp->d_ext); if (dxp->d_name[0] == 0) return (0); if ((uchar_t)dxp->d_name[0] != 0xE5 && (dxp->d_attr & (DE_LABEL|DE_HIDDEN)) == 0 && dircmp(n, dxp->d_name, dxp->d_ext) == 0) { dprintf("lookup: match found\n"); (void) bcopy(dxp, dp, sizeof (*dp)); return (1); } } /* next cluster */ dir_blk = fat_map(dir_blk, rd); } return (0); } static int lookuppn(char *n, _dir_entry_p dp) { long dir_blk; char name[8 + 1 + 3 + 1]; /* <8>.<3>'\0' */ char *p, *ep; _dir_entry_t dd; dprintf("lookuppn: path = %s\n", n); dir_blk = pcfsp->f_dclust; if ((*n == '\\') || (*n == '/')) { dir_blk = CLUSTER_ROOTDIR; while ((*n == '\\') || (*n == '/')) n++; if (*n == '\0') { (void) bzero(dp, sizeof (*dp)); dp->d_cluster = CLUSTER_ROOTDIR; dp->d_attr = DE_DIRECTORY; return (0); } } ep = &name[0] + sizeof (name); while (*n) { (void) bzero(name, sizeof (name)); p = &name[0]; while (*n && (*n != '\\') && (*n != '/')) if (p != ep) *p++ = *n++; else { dprintf("return, name %s is too long\n", name); return (-1); /* name is too long */ } while ((*n == '\\') || (*n == '/')) n++; if (lookup(name, &dd, dir_blk) == 0) { dprintf("return, name %s not found\n", name); return (-1); } dprintf("dd = %x:%x:%x attr = %x\n", *(int *)&dd, *(((int *)&dd) + 1), *(((int *)&dd) + 2), dd.d_attr); if (*n && ((dd.d_attr & DE_DIRECTORY) == 0)) { dprintf("return, not a directory\n"); return (-1); } dir_blk = dd.d_cluster; } (void) bcopy(&dd, dp, sizeof (dd)); return (0); } static void * readblock(int sector, int nsec) { if (sector >= nsec_start && sector + nsec <= nsec_start + nsec_cache) return (cluster_cache + (sector - nsec_start) * SECSIZ); /* read disk sectors */ head->fi_blocknum = sector; head->fi_count = nsec * SECSIZ; head->fi_memp = head->fi_buf; if (diskread(head)) { printf("failed to %d sectors at %d\n", nsec, sector); return (NULL); } return (head->fi_buf); } struct boot_fs_ops bpcfs_ops = { "boot_pcfs", bpcfs_mountroot, bpcfs_unmountroot, bpcfs_open, bpcfs_close, bpcfs_read, bpcfs_lseek, NULL }; /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016 by Delphix. All rights reserved. * Copyright 2022 Oxide Computer Company * Copyright 2025 MNX Cloud, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include extern void *bkmem_alloc(size_t); extern void bkmem_free(void *, size_t); extern int cf_check_compressed(fileid_t *); extern void cf_close(fileid_t *); extern void cf_seek(fileid_t *, off_t, int); extern int cf_read(fileid_t *, caddr_t, size_t); extern int bootrd_debug; /* * This fd is used when talking to the device file itself. */ static fileid_t *head; /* Only got one of these...ergo, only 1 fs open at once */ /* static */ devid_t *ufs_devp; struct dirinfo { int loc; fileid_t *fi; }; static int bufs_close(int); static void bufs_closeall(int); static ino_t find(fileid_t *filep, char *path); static ino_t dlook(fileid_t *filep, char *path); static daddr32_t sbmap(fileid_t *filep, daddr32_t bn); static struct direct *readdir(struct dirinfo *dstuff); static void set_cache(int, void *, uint_t); static void *get_cache(int); static void free_cache(); /* * There is only 1 open (mounted) device at any given time. * So we can keep a single, global devp file descriptor to * use to index into the di[] array. This is not true for the * fi[] array. We can have more than one file open at once, * so there is no global fd for the fi[]. * The user program must save the fd passed back from open() * and use it to do subsequent read()'s. */ static int openi(fileid_t *filep, ino_t inode) { struct dinode *dp; devid_t *devp = filep->fi_devp; filep->fi_inode = get_cache((int)inode); if (filep->fi_inode != 0) return (0); filep->fi_offset = 0; filep->fi_blocknum = fsbtodb(&devp->un_fs.di_fs, itod(&devp->un_fs.di_fs, inode)); /* never more than 1 disk block */ filep->fi_count = devp->un_fs.di_fs.fs_bsize; filep->fi_memp = 0; /* cached read */ if (diskread(filep) != 0) { return (0); } dp = (struct dinode *)filep->fi_memp; filep->fi_inode = (struct inode *) bkmem_alloc(sizeof (struct inode)); bzero((char *)filep->fi_inode, sizeof (struct inode)); filep->fi_inode->i_ic = dp[itoo(&devp->un_fs.di_fs, inode)].di_un.di_icom; filep->fi_inode->i_number = inode; set_cache((int)inode, (void *)filep->fi_inode, sizeof (struct inode)); return (0); } static fileid_t * find_fp(int fd) { fileid_t *filep = head; if (fd >= 0) { while ((filep = filep->fi_forw) != head) if (fd == filep->fi_filedes) return (filep->fi_taken ? filep : 0); } return (0); } static ino_t find(fileid_t *filep, char *path) { char *q; char c; ino_t inode; char lpath[MAXPATHLEN]; char *lpathp = lpath; int len, r; devid_t *devp; inode = 0; if (path == NULL || *path == '\0') { kobj_printf("null path\n"); return (inode); } if (bootrd_debug) kobj_printf("openi: %s\n", path); bzero(lpath, sizeof (lpath)); bcopy(path, lpath, strlen(path)); devp = filep->fi_devp; while (*lpathp) { /* if at the beginning of pathname get root inode */ r = (lpathp == lpath); if (r && openi(filep, (ino_t)UFSROOTINO)) return ((ino_t)0); while (*lpathp == '/') lpathp++; /* skip leading slashes */ q = lpathp; while (*q != '/' && *q != '\0') q++; /* find end of component */ c = *q; *q = '\0'; /* terminate component */ /* Bail out early if opening root */ if (r && (*lpathp == '\0')) return ((ino_t)UFSROOTINO); if ((inode = dlook(filep, lpathp)) != 0) { if (openi(filep, inode)) return ((ino_t)0); if ((filep->fi_inode->i_smode & IFMT) == IFLNK) { filep->fi_blocknum = fsbtodb(&devp->un_fs.di_fs, filep->fi_inode->i_db[0]); filep->fi_count = DEV_BSIZE; filep->fi_memp = 0; if (diskread(filep) != 0) return ((ino_t)0); len = strlen(filep->fi_memp); if (filep->fi_memp[0] == '/') /* absolute link */ lpathp = lpath; /* copy rest of unprocessed path up */ bcopy(q, lpathp + len, strlen(q + 1) + 2); /* point to unprocessed path */ *(lpathp + len) = c; /* prepend link in before unprocessed path */ bcopy(filep->fi_memp, lpathp, len); lpathp = lpath; continue; } else *q = c; if (c == '\0') break; lpathp = q; continue; } else { return ((ino_t)0); } } return (inode); } static daddr32_t sbmap(fileid_t *filep, daddr32_t bn) { struct inode *inodep; int i, j, sh; daddr32_t nb, *bap; daddr32_t *db; devid_t *devp; devp = filep->fi_devp; inodep = filep->fi_inode; db = inodep->i_db; /* * blocks 0..NDADDR are direct blocks */ if (bn < NDADDR) { nb = db[bn]; return (nb); } /* * addresses NIADDR have single and double indirect blocks. * the first step is to determine how many levels of indirection. */ sh = 1; bn -= NDADDR; for (j = NIADDR; j > 0; j--) { sh *= NINDIR(&devp->un_fs.di_fs); if (bn < sh) break; bn -= sh; } if (j == 0) { return ((daddr32_t)0); } /* * fetch the first indirect block address from the inode */ nb = inodep->i_ib[NIADDR - j]; if (nb == 0) { return ((daddr32_t)0); } /* * fetch through the indirect blocks */ for (; j <= NIADDR; j++) { filep->fi_blocknum = fsbtodb(&devp->un_fs.di_fs, nb); filep->fi_count = devp->un_fs.di_fs.fs_bsize; filep->fi_memp = 0; if (diskread(filep) != 0) return (0); bap = (daddr32_t *)filep->fi_memp; sh /= NINDIR(&devp->un_fs.di_fs); i = (bn / sh) % NINDIR(&devp->un_fs.di_fs); nb = bap[i]; if (nb == 0) { return ((daddr32_t)0); } } return (nb); } static ino_t dlook(fileid_t *filep, char *path) { struct direct *dp; struct inode *ip; struct dirinfo dirp; int len; ip = filep->fi_inode; if (path == NULL || *path == '\0') return (0); if (bootrd_debug) kobj_printf("dlook: %s\n", path); if ((ip->i_smode & IFMT) != IFDIR) { return (0); } if (ip->i_size == 0) { return (0); } len = strlen(path); dirp.loc = 0; dirp.fi = filep; for (dp = readdir(&dirp); dp != NULL; dp = readdir(&dirp)) { if (dp->d_ino == 0) continue; if (dp->d_namlen == len && strcmp(path, dp->d_name) == 0) { return (dp->d_ino); } /* Allow "*" to print all names at that level, w/out match */ if (strcmp(path, "*") == 0 && bootrd_debug) kobj_printf("%s\n", dp->d_name); } return (0); } /* * get next entry in a directory. */ struct direct * readdir(struct dirinfo *dstuff) { struct direct *dp; fileid_t *filep; daddr32_t lbn, d; int off; devid_t *devp; filep = dstuff->fi; devp = filep->fi_devp; for (;;) { if (dstuff->loc >= filep->fi_inode->i_size) { return (NULL); } off = blkoff(&devp->un_fs.di_fs, dstuff->loc); if (bootrd_debug) kobj_printf("readdir: off = 0x%x\n", off); if (off == 0) { lbn = lblkno(&devp->un_fs.di_fs, dstuff->loc); d = sbmap(filep, lbn); if (d == 0) return (NULL); filep->fi_blocknum = fsbtodb(&devp->un_fs.di_fs, d); filep->fi_count = blksize(&devp->un_fs.di_fs, filep->fi_inode, lbn); filep->fi_memp = 0; if (diskread(filep) != 0) { return (NULL); } } dp = (struct direct *)(filep->fi_memp + off); dstuff->loc += dp->d_reclen; if (dp->d_ino == 0) continue; if (bootrd_debug) kobj_printf("readdir: name = %s\n", dp->d_name); return (dp); } } /* * Get the next block of data from the file. If possible, dma right into * user's buffer */ static int getblock(fileid_t *filep, caddr_t buf, int count, int *rcount) { struct fs *fs; caddr_t p; int off, size, diff; daddr32_t lbn; devid_t *devp; if (bootrd_debug) kobj_printf("getblock: buf 0x%p, count 0x%x\n", (void *)buf, count); devp = filep->fi_devp; p = filep->fi_memp; if ((signed)filep->fi_count <= 0) { /* find the amt left to be read in the file */ diff = filep->fi_inode->i_size - filep->fi_offset; if (diff <= 0) { kobj_printf("Short read\n"); return (-1); } fs = &devp->un_fs.di_fs; /* which block (or frag) in the file do we read? */ lbn = lblkno(fs, filep->fi_offset); /* which physical block on the device do we read? */ filep->fi_blocknum = fsbtodb(fs, sbmap(filep, lbn)); off = blkoff(fs, filep->fi_offset); /* either blksize or fragsize */ size = blksize(fs, filep->fi_inode, lbn); filep->fi_count = size; filep->fi_memp = filep->fi_buf; /* * optimization if we are reading large blocks of data then * we can go directly to user's buffer */ *rcount = 0; if (off == 0 && count >= size) { filep->fi_memp = buf; if (diskread(filep)) { return (-1); } *rcount = size; filep->fi_count = 0; return (0); } else if (diskread(filep)) return (-1); if (filep->fi_offset - off + size >= filep->fi_inode->i_size) filep->fi_count = diff + off; filep->fi_count -= off; p = &filep->fi_memp[off]; } filep->fi_memp = p; return (0); } /* * Get the next block of data from the file. Don't attempt to go directly * to user's buffer. */ static int getblock_noopt(fileid_t *filep) { struct fs *fs; caddr_t p; int off, size, diff; daddr32_t lbn; devid_t *devp; if (bootrd_debug) kobj_printf("getblock_noopt: start\n"); devp = filep->fi_devp; p = filep->fi_memp; if ((signed)filep->fi_count <= 0) { /* find the amt left to be read in the file */ diff = filep->fi_inode->i_size - filep->fi_offset; if (diff <= 0) { kobj_printf("Short read\n"); return (-1); } fs = &devp->un_fs.di_fs; /* which block (or frag) in the file do we read? */ lbn = lblkno(fs, filep->fi_offset); /* which physical block on the device do we read? */ filep->fi_blocknum = fsbtodb(fs, sbmap(filep, lbn)); off = blkoff(fs, filep->fi_offset); /* either blksize or fragsize */ size = blksize(fs, filep->fi_inode, lbn); filep->fi_count = size; /* reading on a ramdisk, just get a pointer to the data */ filep->fi_memp = NULL; if (diskread(filep)) return (-1); if (filep->fi_offset - off + size >= filep->fi_inode->i_size) filep->fi_count = diff + off; filep->fi_count -= off; p = &filep->fi_memp[off]; } filep->fi_memp = p; return (0); } /* * This is the high-level read function. It works like this. * We assume that our IO device buffers up some amount of * data and that we can get a ptr to it. Thus we need * to actually call the device func about filesize/blocksize times * and this greatly increases our IO speed. When we already * have data in the buffer, we just return that data (with bcopy() ). */ static ssize_t bufs_read(int fd, caddr_t buf, size_t count) { size_t i, j; caddr_t n; int rcount; fileid_t *filep; if (!(filep = find_fp(fd))) { return (-1); } if ((filep->fi_flags & FI_COMPRESSED) == 0 && filep->fi_offset + count > filep->fi_inode->i_size) count = filep->fi_inode->i_size - filep->fi_offset; /* that was easy */ if ((i = count) == 0) return (0); n = buf; while (i > 0) { if (filep->fi_flags & FI_COMPRESSED) { int rval; if ((rval = cf_read(filep, buf, count)) < 0) return (0); /* encountered an error */ j = (size_t)rval; if (j < i) i = j; /* short read, must have hit EOF */ } else { /* If we need to reload the buffer, do so */ if ((j = filep->fi_count) == 0) { (void) getblock(filep, buf, i, &rcount); i -= rcount; buf += rcount; filep->fi_offset += rcount; continue; } else { /* else just bcopy from our buffer */ j = MIN(i, j); bcopy(filep->fi_memp, buf, (unsigned)j); } } buf += j; filep->fi_memp += j; filep->fi_offset += j; filep->fi_count -= j; i -= j; } return (buf - n); } /* * This routine will open a device as it is known by the V2 OBP. * Interface Defn: * err = mountroot(string); * err = 0 on success * err = -1 on failure * string: char string describing the properties of the device. * We must not dork with any fi[]'s here. Save that for later. */ static int bufs_mountroot(char *str) { if (ufs_devp) /* already mounted */ return (0); ufs_devp = (devid_t *)bkmem_alloc(sizeof (devid_t)); ufs_devp->di_taken = 1; ufs_devp->di_dcookie = 0; ufs_devp->di_desc = (char *)bkmem_alloc(strlen(str) + 1); (void) strcpy(ufs_devp->di_desc, str); bzero(ufs_devp->un_fs.dummy, SBSIZE); head = (fileid_t *)bkmem_alloc(sizeof (fileid_t)); head->fi_back = head->fi_forw = head; head->fi_filedes = 0; head->fi_taken = 0; /* Setup read of the superblock */ head->fi_devp = ufs_devp; head->fi_blocknum = SBLOCK; head->fi_count = (uint_t)SBSIZE; head->fi_memp = (caddr_t)&(ufs_devp->un_fs.di_fs); head->fi_offset = 0; if (diskread(head)) { kobj_printf("failed to read superblock\n"); (void) bufs_closeall(1); return (-1); } if (ufs_devp->un_fs.di_fs.fs_magic != FS_MAGIC) { if (bootrd_debug) kobj_printf("fs magic = 0x%x\n", ufs_devp->un_fs.di_fs.fs_magic); (void) bufs_closeall(1); return (-1); } if (bootrd_debug) kobj_printf("mountroot succeeded\n"); return (0); } /* * Unmount the currently mounted root fs. In practice, this means * closing all open files and releasing resources. All of this * is done by closeall(). */ static int bufs_unmountroot(void) { if (ufs_devp == NULL) return (-1); (void) bufs_closeall(1); return (0); } /* * We allocate an fd here for use when talking * to the file itself. */ static int bufs_open(char *filename, int flags __unused) { fileid_t *filep; ino_t inode; static int filedes = 1; if (bootrd_debug) kobj_printf("open: %s\n", filename); /* build and link a new file descriptor */ filep = (fileid_t *)bkmem_alloc(sizeof (fileid_t)); filep->fi_back = head->fi_back; filep->fi_forw = head; head->fi_back->fi_forw = filep; head->fi_back = filep; filep->fi_filedes = filedes++; filep->fi_taken = 1; filep->fi_path = (char *)bkmem_alloc(strlen(filename) + 1); (void) strcpy(filep->fi_path, filename); filep->fi_devp = ufs_devp; /* dev is already "mounted" */ filep->fi_inode = NULL; bzero(filep->fi_buf, MAXBSIZE); filep->fi_getblock = getblock_noopt; filep->fi_flags = 0; inode = find(filep, (char *)filename); if (inode == 0) { if (bootrd_debug) kobj_printf("open: cannot find %s\n", filename); (void) bufs_close(filep->fi_filedes); return (-1); } if (openi(filep, inode)) { kobj_printf("open: cannot open %s\n", filename); (void) bufs_close(filep->fi_filedes); return (-1); } filep->fi_offset = filep->fi_count = 0; if (cf_check_compressed(filep) != 0) return (-1); return (filep->fi_filedes); } /* * We don't do any IO here. * We just play games with the device pointers. */ static off_t bufs_lseek(int fd, off_t addr, int whence) { fileid_t *filep; /* Make sure user knows what file they are talking to */ if (!(filep = find_fp(fd))) return (-1); if (filep->fi_flags & FI_COMPRESSED) { cf_seek(filep, addr, whence); } else { switch (whence) { case SEEK_CUR: filep->fi_offset += addr; break; case SEEK_SET: filep->fi_offset = addr; break; default: case SEEK_END: kobj_printf("lseek(): invalid whence value %d\n", whence); break; } filep->fi_blocknum = addr / DEV_BSIZE; } filep->fi_count = 0; return (0); } int bufs_fstat(int fd, struct bootstat *stp) { fileid_t *filep; struct inode *ip; if (!(filep = find_fp(fd))) return (-1); ip = filep->fi_inode; stp->st_mode = 0; stp->st_size = 0; if (ip == NULL) return (0); switch (ip->i_smode & IFMT) { case IFLNK: stp->st_mode = S_IFLNK; break; case IFREG: stp->st_mode = S_IFREG; break; default: break; } /* * NOTE: this size will be the compressed size for a compressed file * This could confuse the caller since we decompress the file behind * the scenes when the file is read. */ stp->st_size = ip->i_size; stp->st_atim.tv_sec = ip->i_atime.tv_sec; stp->st_atim.tv_nsec = ip->i_atime.tv_usec * 1000; stp->st_mtim.tv_sec = ip->i_mtime.tv_sec; stp->st_mtim.tv_nsec = ip->i_mtime.tv_usec * 1000; stp->st_ctim.tv_sec = ip->i_ctime.tv_sec; stp->st_ctim.tv_nsec = ip->i_ctime.tv_usec * 1000; return (0); } static int bufs_close(int fd) { fileid_t *filep; /* Make sure user knows what file they are talking to */ if (!(filep = find_fp(fd))) return (-1); if (filep->fi_taken && (filep != head)) { /* Clear the ranks */ bkmem_free(filep->fi_path, strlen(filep->fi_path)+1); filep->fi_blocknum = filep->fi_count = filep->fi_offset = 0; filep->fi_memp = (caddr_t)0; filep->fi_devp = 0; filep->fi_taken = 0; /* unlink and deallocate node */ filep->fi_forw->fi_back = filep->fi_back; filep->fi_back->fi_forw = filep->fi_forw; cf_close(filep); bkmem_free((char *)filep, sizeof (fileid_t)); /* * Some files are opened and closed in early boot, for example * when doing a microcode update on the boot CPU. In that case * the inode cache will contain memory allocated from boot * pages, which will be invalid once kmem is initialised. * Until kmem is ready, clear the inode cache when closing a * file. */ if (kmem_ready == 0) free_cache(); return (0); } else { /* Big problem */ kobj_printf("\nFile descrip %d not allocated!", fd); return (-1); } } static void bufs_closeall(int flag __unused) { fileid_t *filep = head; while ((filep = filep->fi_forw) != head) if (filep->fi_taken) if (bufs_close(filep->fi_filedes)) kobj_printf( "Filesystem may be inconsistent.\n"); ufs_devp->di_taken = 0; bkmem_free((char *)ufs_devp, sizeof (devid_t)); bkmem_free((char *)head, sizeof (fileid_t)); ufs_devp = NULL; head = NULL; free_cache(); } static struct cache { struct cache *next; void *data; int key; uint_t size; } *icache; void set_cache(int key, void *data, uint_t size) { struct cache *entry = bkmem_alloc(sizeof (*entry)); entry->key = key; entry->data = data; entry->size = size; if (icache) { entry->next = icache; icache = entry; } else { icache = entry; entry->next = 0; } } void * get_cache(int key) { struct cache *entry = icache; while (entry) { if (entry->key == key) return (entry->data); entry = entry->next; } return (NULL); } void free_cache() { struct cache *next, *entry = icache; while (entry) { next = entry->next; bkmem_free(entry->data, entry->size); bkmem_free(entry, sizeof (*entry)); entry = next; } icache = 0; } struct boot_fs_ops bufs_ops = { "boot_ufs", bufs_mountroot, bufs_unmountroot, bufs_open, bufs_close, bufs_read, bufs_lseek, bufs_fstat, NULL };