Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#include        "u.h"
#include        "../port/lib.h"
#include        "mem.h"
#include        "dat.h"
#include        "fns.h"
#include        "io.h"
#include        "ureg.h"
#include        "pool.h"
#include        "../port/error.h"
#include        "../port/netif.h"
#include        "dosfs.h"

enum {
        Dosfilemax = 8,
        Dosextmax = 3,
};

/*
 *  predeclared
 */
static void     bootdump(Dosboot*);
static void     setname(Dosfile*, char*);

/*
 *  debugging
 */
#define chatty  0
#define chat    if(chatty)print

/*
 *  block io buffers
 */
enum
{
        Nbio=   16,
};
typedef struct  Clustbuf        Clustbuf;
struct Clustbuf
{
        int     age;
        long    sector;
        uchar   *iobuf;
        Dos     *dos;
        int     size;
};
Clustbuf        bio[Nbio];

/*
 *  get an io block from an io buffer
 */
Clustbuf*
getclust(Dos *dos, long sector)
{
        Bootfs *fs;
        Clustbuf *p, *oldest;
        int size;

        chat("getclust @ %ld\n", sector);

        /*
         *  if we have it, just return it
         */
        for(p = bio; p < &bio[Nbio]; p++){
                if(sector == p->sector && dos == p->dos){
                        p->age = m->ticks;
                        chat("getclust %ld in cache\n", sector);
                        return p;
                }
        }

        /*
         *  otherwise, reuse the oldest entry
         */
        oldest = bio;
        for(p = &bio[1]; p < &bio[Nbio]; p++){
                if(p->age <= oldest->age)
                        oldest = p;
        }
        p = oldest;

        /*
         *  make sure the buffer is big enough
         */
        size = dos->clustsize*dos->sectsize;
        if(p->iobuf==0 || p->size < size)
                p->iobuf = smalloc(size);
        p->size = size;

        /*
         *  read in the cluster
         */
        fs = (Bootfs*)dos;              /* assume dos is embedded at start of an Bootfs */
        chat("getclust addr %llud %p %s\n", ((sector+dos->start)*(vlong)dos->sectsize),
                fs, fs->disk);
        fs->devch->offset = (sector+dos->start) * (vlong)dos->sectsize;
        if(myreadn(fs->devch, p->iobuf, size) != size){
                chat("can't read block\n");
                return 0;
        }
        USED(fs);
        p->age = m->ticks;
        p->dos = dos;
        p->sector = sector;
        chat("getclust %ld read\n", sector);
        return p;
}

/*
 *  walk the fat one level ( n is a current cluster number ).
 *  return the new cluster number or -1 if no more.
 */
static long
fatwalk(Dos *dos, int n)
{
        ulong k, sect;
        Clustbuf *p;
        int o;

        chat("fatwalk %d\n", n);

        if(n < 2 || n >= dos->fatclusters)
                return -1;

        switch(dos->fatbits){
        case 12:
                k = (3*n)/2; break;
        case 16:
                k = 2*n; break;
        case 32:
                k = 4*n; break;
        default:
                return -1;
        }
        if(k >= dos->fatsize*dos->sectsize)
                panic("getfat");

        if (dos->sectsize == 0 || dos->clustsize == 0)
                panic("fatwalk: zero sector or cluster size");
        sect = (k/(dos->sectsize*dos->clustsize))*dos->clustsize + dos->fataddr;
        o = k%(dos->sectsize*dos->clustsize);
        p = getclust(dos, sect);
        k = p->iobuf[o++];
        if(o >= dos->sectsize*dos->clustsize){
                p = getclust(dos, sect+dos->clustsize);
                o = 0;
        }
        k |= p->iobuf[o++]<<8;
        if(dos->fatbits == 12){
                if(n&1)
                        k >>= 4;
                else
                        k &= 0xfff;
                if(k >= 0xff8)
                        k = -1;
        }
        else if (dos->fatbits == 32){
                if(o >= dos->sectsize*dos->clustsize){
                        p = getclust(dos, sect+dos->clustsize);
                        o = 0;
                }
                k |= p->iobuf[o++]<<16;
                k |= p->iobuf[o]<<24;
                if (k >= 0xfffffff8)
                        k = -1;
        }
        else
                k = k < 0xfff8 ? k : -1;
        chat("fatwalk %d -> %lud\n", n, k);
        return k;
}

/*
 *  map a file's logical cluster address to a physical sector address
 */
static long
fileaddr(Dosfile *fp, long ltarget)
{
        Dos *dos = fp->dos;
        long l;
        long p;

        chat("fileaddr %8.8s %ld\n", fp->name, ltarget);
        /*
         *  root directory is contiguous and easy (unless FAT32)
         */
        if(fp->pstart == 0 && dos->rootsize != 0) {
                if(ltarget*dos->sectsize*dos->clustsize >= dos->rootsize*sizeof(Dosdir))
                        return -1;
                l = dos->rootaddr + ltarget*dos->clustsize;
                chat("fileaddr %ld -> %ld\n", ltarget, l);
                return l;
        }

        /*
         *  anything else requires a walk through the fat
         */
        if(ltarget >= fp->lcurrent && fp->pcurrent){
                /* start at the currrent point */
                l = fp->lcurrent;
                p = fp->pcurrent;
        } else {
                /* go back to the beginning */
                l = 0;
                p = fp->pstart;
        }
        while(l != ltarget){
                /* walk the fat */
                p = fatwalk(dos, p);
                if(p < 0)
                        return -1;
                l++;
        }
        fp->lcurrent = l;
        fp->pcurrent = p;

        /*
         *  clusters start at 2 instead of 0 (why? - presotto)
         */
        l =  dos->dataaddr + (p-2)*dos->clustsize;
        chat("fileaddr %ld -> %ld\n", ltarget, l);
        return l;
}

/*
 *  read from a dos file
 */
long
dosread(Dosfile *fp, void *a, long n)
{
        long addr;
        long rv;
        int i;
        int off;
        Clustbuf *p;
        uchar *from, *to;

        if((fp->attr & DOSDIR) == 0){
                if(fp->offset >= fp->length)
                        return 0;
                if(fp->offset+n > fp->length)
                        n = fp->length - fp->offset;
        }

        to = a;
        for(rv = 0; rv < n; rv+=i){
                /*
                 *  read the cluster
                 */
                addr = fileaddr(fp, fp->offset/fp->dos->clustbytes);
                if(addr < 0)
                        return -1;
                p = getclust(fp->dos, addr);
                if(p == 0)
                        return -1;

                /*
                 *  copy the bytes we need
                 */
                off = fp->offset % fp->dos->clustbytes;
                from = &p->iobuf[off];
                i = n - rv;
                if(i > fp->dos->clustbytes - off)
                        i = fp->dos->clustbytes - off;
                memmove(to, from, i);
                to += i;
                fp->offset += i;
        }

        return rv;
}

/*
 *  walk a directory returns
 *      -1 if something went wrong
 *       0 if not found
 *       1 if found
 */
int
doswalk(File *f, char *name)
{
        Dosdir d;
        long n;
        Dosfile *file;

        chat("doswalk %s\n", name);

        file = &f->dos;

        if((file->attr & DOSDIR) == 0){
                chat("walking non-directory!\n");
                return -1;
        }

        setname(file, name);

        file->offset = 0;       /* start at the beginning */
        while((n = dosread(file, &d, sizeof(d))) == sizeof(d)){
                chat("comparing to %8.8s.%3.3s\n", (char*)d.name, (char*)d.ext);
                if(memcmp(file->name, d.name, sizeof(d.name)) != 0)
                        continue;
                if(memcmp(file->ext, d.ext, sizeof(d.ext)) != 0)
                        continue;
                if(d.attr & DOSVLABEL){
                        chat("%8.8s.%3.3s is a LABEL\n", (char*)d.name, (char*)d.ext);
                        continue;
                }
                file->attr = d.attr;
                file->pstart = GSHORT(d.start);
                if (file->dos->fatbits == 32)
                        file->pstart |= GSHORT(d.highstart) << 16;
                file->length = GLONG(d.length);
                file->pcurrent = 0;
                file->lcurrent = 0;
                file->offset = 0;
                return 1;
        }
        return n >= 0 ? 0 : -1;
}

void
lowercase(char *s)
{
        for (; *s != '\0'; s++)
                if (*s >= 'A' && *s <= 'Z')
                        *s -= 'A' - 'a';
}

void
trim(char *s, int len)
{
        while(len > 0 && s[len-1] == ' ')
                s[--len] = '\0';
}

/*
 *  read a directory and return the file names in a malloced
 *      array whose address is stored through nmarray.
 *      -1 if something went wrong
 *      else number of dir. entries
 */
int
dosdirread(File *f, char ***nmarray)
{
        int entries;
        long i;
        char buf[Dosfilemax+1+Dosextmax+1];
        char **nms;
        Dosdir d;
        Dosfile *file;

        chat("dosdirread\n");
        file = &f->dos;
        if((file->attr & DOSDIR) == 0){
                chat("walking non-directory!\n");
                return -1;
        }

        /* allocate the array of char*s */
        file->offset = 0;               /* start at the beginning */
        for(entries = 0; dosread(file, &d, sizeof d) == sizeof d; entries++)
                ;
        nms = smalloc(sizeof(char *) * (entries + 1));

        /* populate the array */
        file->offset = 0;               /* rewind */
        for(i = 0; i < entries && dosread(file, &d, sizeof d) == sizeof d; ){
                trim((char *)d.name, Dosfilemax);
                trim((char *)d.ext, Dosextmax);
                if (d.name[0] == '\0')
                        continue;
                if (d.ext[0] == '\0')
                        kstrdup(&nms[i], (char *)d.name);
                else {
                        snprint(buf, sizeof buf, "%.*s.%.*s",
                                Dosfilemax, (char *)d.name,
                                Dosextmax, (char *)d.ext);
                        kstrdup(&nms[i], buf);
                }
                lowercase(nms[i++]);
        }
        *nmarray = nms;
        return 0;
}

/*
 *  instructions that boot blocks can start with
 */
#define JMPSHORT        0xeb
#define JMPNEAR         0xe9

/*
 *  read in a segment
 */
long
dosreadseg(File *f, void *va, long len)
{
        char *a;
        long n, sofar;
        Dosfile *fp;

        fp = &f->dos;
        a = va;
        for(sofar = 0; sofar < len; sofar += n){
                n = 8*1024;
                if(len - sofar < n)
                        n = len - sofar;
                n = dosread(fp, a + sofar, n);
                if(n <= 0)
                        break;
                print(".");
        }
        return sofar;
}

int
dosinit(Bootfs *fs, char *disk)
{
        Clustbuf *p;
        Dosboot *b;
        int i;
        Dos *dos;
        Dosfile *root;

chat("dosinit0 %p %s\n", fs, fs->disk);

        fs->disk = disk;
        fs->devch = namecopen(disk, OREAD);
        if (fs->devch == nil) {
                print("dosinit: can't open %s\n", disk);
                return -1;
        }

        dos = &fs->dos;
        /* defaults till we know better */
        dos->sectsize = 512;
        dos->clustsize = 1;

        /* get first sector */
        p = getclust(dos, 0);
        if(p == 0){
                chat("can't read boot block\n");
                return -1;
        }

chat("dosinit0a\n");

        p->dos = 0;                             /* don't cache this block */
        b = (Dosboot *)p->iobuf;
        if(b->magic[0] != JMPNEAR && (b->magic[0] != JMPSHORT || b->magic[2] != 0x90)){
                chat("no dos file system %x %x %x %x\n",
                        b->magic[0], b->magic[1], b->magic[2], b->magic[3]);
                return -1;
        }

        if(chatty)
                bootdump(b);

        if(b->clustsize == 0) {
unreasonable:
                if(chatty){
                        print("unreasonable FAT BPB: ");
                        for(i=0; i<3+8+2+1; i++)
                                print(" %.2ux", p->iobuf[i]);
                        print("\n");
                }
                return -1;
        }

chat("dosinit1\n");

        /*
         * Determine the systems' wondrous properties.
         * There are heuristics here, but there's no real way
         * of knowing if this is a reasonable FAT.
         */
        dos->fatbits = 0;
        dos->sectsize = GSHORT(b->sectsize);
        if(dos->sectsize & 0xFF)
                goto unreasonable;
        dos->clustsize = b->clustsize;
        dos->clustbytes = dos->sectsize*dos->clustsize;
        dos->nresrv = GSHORT(b->nresrv);
        dos->nfats = b->nfats;
        dos->fatsize = GSHORT(b->fatsize);
        dos->rootsize = GSHORT(b->rootsize);
        dos->volsize = GSHORT(b->volsize);
        if(dos->volsize == 0)
                dos->volsize = GLONG(b->bigvolsize);
        dos->mediadesc = b->mediadesc;
        if(dos->fatsize == 0) {
                chat("fat32\n");
                dos->rootsize = 0;
                dos->fatsize = GLONG(b->bigfatsize);
                dos->fatbits = 32;
        }
        dos->fataddr = dos->nresrv;
        if (dos->rootsize == 0) {
                dos->rootaddr = 0;
                dos->rootclust = GLONG(b->rootdirstartclust);
                dos->dataaddr = dos->fataddr + dos->nfats*dos->fatsize;
        } else {
                dos->rootaddr = dos->fataddr + dos->nfats*dos->fatsize;
                i = dos->rootsize*sizeof(Dosdir) + dos->sectsize - 1;
                i = i/dos->sectsize;
                dos->dataaddr = dos->rootaddr + i;
        }
        dos->fatclusters = 2+(dos->volsize - dos->dataaddr)/dos->clustsize;
        if(dos->fatbits != 32) {
                if(dos->fatclusters < 4087)
                        dos->fatbits = 12;
                else
                        dos->fatbits = 16;
        }
        dos->freeptr = 2;

        if(dos->clustbytes < 512 || dos->clustbytes > 64*1024)
                goto unreasonable;

chat("dosinit2\n");

        /*
         *  set up the root
         */

        fs->root.fs = fs;
        root = &fs->root.dos;
        root->dos = dos;
        root->pstart = dos->rootsize == 0 ? dos->rootclust : 0;
        root->pcurrent = root->lcurrent = 0;
        root->offset = 0;
        root->attr = DOSDIR;
        root->length = dos->rootsize*sizeof(Dosdir);

chat("dosinit3\n");

        fs->read = dosreadseg;
        fs->walk = doswalk;
        return 0;
}

static void
bootdump(Dosboot *b)
{
        if(chatty == 0)
                return;
        print("magic: 0x%2.2x 0x%2.2x 0x%2.2x ",
                b->magic[0], b->magic[1], b->magic[2]);
        print("version: \"%8.8s\"\n", (char*)b->version);
        print("sectsize: %d ", GSHORT(b->sectsize));
        print("allocsize: %d ", b->clustsize);
        print("nresrv: %d ", GSHORT(b->nresrv));
        print("nfats: %d\n", b->nfats);
        print("rootsize: %d ", GSHORT(b->rootsize));
        print("volsize: %d ", GSHORT(b->volsize));
        print("mediadesc: 0x%2.2x\n", b->mediadesc);
        print("fatsize: %d ", GSHORT(b->fatsize));
        print("trksize: %d ", GSHORT(b->trksize));
        print("nheads: %d ", GSHORT(b->nheads));
        print("nhidden: %d ", GLONG(b->nhidden));
        print("bigvolsize: %d\n", GLONG(b->bigvolsize));
/*
        print("driveno: %d\n", b->driveno);
        print("reserved0: 0x%2.2x\n", b->reserved0);
        print("bootsig: 0x%2.2x\n", b->bootsig);
        print("volid: 0x%8.8x\n", GLONG(b->volid));
        print("label: \"%11.11s\"\n", b->label);
*/
}


/*
 *  set up a dos file name
 */
static void
setname(Dosfile *fp, char *from)
{
        char *to;

        to = fp->name;
        for(; *from && to-fp->name < 8; from++, to++){
                if(*from == '.'){
                        from++;
                        break;
                }
                if(*from >= 'a' && *from <= 'z')
                        *to = *from + 'A' - 'a';
                else
                        *to = *from;
        }
        while(to - fp->name < 8)
                *to++ = ' ';
        
        /* from might be 12345678.123: don't save the '.' in ext */
        if(*from == '.')
                from++;

        to = fp->ext;
        for(; *from && to-fp->ext < 3; from++, to++){
                if(*from >= 'a' && *from <= 'z')
                        *to = *from + 'A' - 'a';
                else
                        *to = *from;
        }
        while(to-fp->ext < 3)
                *to++ = ' ';

        chat("name is %8.8s.%3.3s\n", fp->name, fp->ext);
}