/* * 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 };