/* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * 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 <sys/param.h> #include <sys/sysmacros.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/fs/hsfs_spec.h> #include <sys/fs/hsfs_isospec.h> #include <sys/fs/hsfs_node.h> #include <sys/fs/hsfs_susp.h> #include <sys/fs/hsfs_rrip.h> #include <sys/bootvfs.h> #include <sys/filep.h> #ifdef _BOOT #include "../common/util.h" #else #include <sys/sunddi.h> #endif #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 #ifdef _BOOT #define dprintf if (bootrd_debug) printf #else #define printf kobj_printf #define dprintf if (bootrd_debug) kobj_printf /* PRINTFLIKE1 */ extern void kobj_printf(char *, ...); #endif extern int bootrd_debug; extern void *bkmem_alloc(size_t); extern void bkmem_free(void *, 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; dprintf("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; dprintf("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; dprintf("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; dprintf("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)) { dprintf("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; dprintf("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; dprintf("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; dprintf("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_offset + count > ip->i_size) count = ip->i_size - filep->fi_offset; if ((i = count) <= 0) return (0); while (i > 0) { 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); buf += j; filep->fi_memp += j; filep->fi_offset += j; filep->fi_count -= j; i -= j; } dprintf("bhsfs_read: read 0x%x\n", (int)(buf - n)); return (buf - n); } /*ARGSUSED*/ static int bhsfs_mountroot(char *str) { char *bufp; if (hsfsp != NULL) return (0); /* already mounted */ dprintf("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)) { 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)) { dprintf("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)) { 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. */ /*ARGSUSED*/ int bhsfs_open(char *str, int flags) { static int filedes = 1; fileid_t *filep; ino_t ino; 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); filep->fi_inode = NULL; bzero(filep->fi_buf, MAXBSIZE); 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; dprintf("open done\n"); return (filep->fi_filedes); } int bhsfs_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; 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)); dprintf("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)) 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; 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 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; } 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; dprintf("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 { 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)) { 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; dprintf("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 };