#include #include #include #include #include #include #include "ext2.h" static void parsedirent(Dirent*, uchar*); static void parseinode(Inode*, uchar*); static void parsegroup(Group*, uchar*); static void parsesuper(Super*, uchar*); #define debug 0 static int ext2sync(Fsys*); static void ext2close(Fsys*); static Block* ext2blockread(Fsys*, u64int); static Nfs3Status ext2root(Fsys*, Nfs3Handle*); static Nfs3Status ext2getattr(Fsys*, SunAuthUnix *au, Nfs3Handle*, Nfs3Attr*); static Nfs3Status ext2lookup(Fsys*, SunAuthUnix *au, Nfs3Handle*, char*, Nfs3Handle*); static Nfs3Status ext2readfile(Fsys*, SunAuthUnix *au, Nfs3Handle*, u32int, u64int, uchar**, u32int*, u1int*); static Nfs3Status ext2readlink(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, char **link); static Nfs3Status ext2readdir(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, u32int, u64int, uchar**, u32int*, u1int*); static Nfs3Status ext2access(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, u32int want, u32int *got, Nfs3Attr *attr); Fsys* fsysopenext2(Disk *disk) { Ext2 *fs; Fsys *fsys; fsys = emalloc(sizeof(Fsys)); fs = emalloc(sizeof(Ext2)); fs->disk = disk; fsys->priv = fs; fs->fsys = fsys; fsys->type = "ext2"; fsys->_readblock = ext2blockread; fsys->_sync = ext2sync; fsys->_root = ext2root; fsys->_getattr = ext2getattr; fsys->_access = ext2access; fsys->_lookup = ext2lookup; fsys->_readfile = ext2readfile; fsys->_readlink = ext2readlink; fsys->_readdir = ext2readdir; fsys->_close = ext2close; if(ext2sync(fsys) < 0) goto error; return fsys; error: ext2close(fsys); return nil; } static void ext2close(Fsys *fsys) { Ext2 *fs; fs = fsys->priv; free(fs); free(fsys); } static int ext2group(Ext2 *fs, u32int i, Group *g) { Block *b; u64int addr; if(i >= fs->ngroup) return -1; addr = fs->groupaddr + i/fs->descperblock; b = diskread(fs->disk, fs->blocksize, addr*fs->blocksize); if(b == nil) return -1; parsegroup(g, b->data+i%fs->descperblock*GroupSize); blockput(b); return 0; } static Block* ext2blockread(Fsys *fsys, u64int vbno) { Block *bitb; Group g; uchar *bits; u32int bno, boff, bitblock; u64int bitpos; Ext2 *fs; fs = fsys->priv; if(vbno >= fs->nblock) return nil; bno = vbno; if(bno != vbno) return nil; /* if(bno < fs->firstblock) return diskread(fs->disk, fs->blocksize, (u64int)bno*fs->blocksize); */ if(bno < fs->firstblock) return nil; bno -= fs->firstblock; if(ext2group(fs, bno/fs->blockspergroup, &g) < 0){ if(debug) fprint(2, "loading group: %r..."); return nil; } /* if(debug) fprint(2, "ext2 group %d: bitblock=%ud inodebitblock=%ud inodeaddr=%ud freeblocks=%ud freeinodes=%ud useddirs=%ud\n", (int)(bno/fs->blockspergroup), g.bitblock, g.inodebitblock, g.inodeaddr, g.freeblockscount, g.freeinodescount, g.useddirscount); if(debug) fprint(2, "group %d bitblock=%d...", bno/fs->blockspergroup, g.bitblock); */ bitblock = g.bitblock; bitpos = (u64int)bitblock*fs->blocksize; if((bitb = diskread(fs->disk, fs->blocksize, bitpos)) == nil){ if(debug) fprint(2, "loading bitblock: %r..."); return nil; } bits = bitb->data; boff = bno%fs->blockspergroup; if((bits[boff>>3] & (1<<(boff&7))) == 0){ if(debug) fprint(2, "block %d not allocated in group %d: bitblock %d/%lld bits[%d] = %#x\n", boff, bno/fs->blockspergroup, (int)bitblock, bitpos, boff>>3, bits[boff>>3]); blockput(bitb); return nil; } blockput(bitb); bno += fs->firstblock; return diskread(fs->disk, fs->blocksize, (u64int)bno*fs->blocksize); } static Block* ext2datablock(Ext2 *fs, u32int bno, int size) { USED(size); return ext2blockread(fs->fsys, bno); } static Block* ext2fileblock(Ext2 *fs, Inode *ino, u32int bno, int size) { int ppb; Block *b; u32int *a; u32int obno, pbno; obno = bno; if(bno < NDIRBLOCKS){ if(debug) fprint(2, "fileblock %d -> %d...", bno, ino->block[bno]); return ext2datablock(fs, ino->block[bno], size); } bno -= NDIRBLOCKS; ppb = fs->blocksize/4; /* one indirect */ if(bno < ppb){ b = ext2datablock(fs, ino->block[INDBLOCK], fs->blocksize); if(b == nil) return nil; a = (u32int*)b->data; bno = a[bno]; blockput(b); return ext2datablock(fs, bno, size); } bno -= ppb; /* one double indirect */ if(bno < ppb*ppb){ b = ext2datablock(fs, ino->block[DINDBLOCK], fs->blocksize); if(b == nil) return nil; a = (u32int*)b->data; pbno = a[bno/ppb]; bno = bno%ppb; blockput(b); b = ext2datablock(fs, pbno, fs->blocksize); if(b == nil) return nil; a = (u32int*)b->data; bno = a[bno]; blockput(b); return ext2datablock(fs, bno, size); } bno -= ppb*ppb; /* one triple indirect */ if(bno < ppb*ppb*ppb){ b = ext2datablock(fs, ino->block[TINDBLOCK], fs->blocksize); if(b == nil) return nil; a = (u32int*)b->data; pbno = a[bno/(ppb*ppb)]; bno = bno%(ppb*ppb); blockput(b); b = ext2datablock(fs, pbno, fs->blocksize); if(b == nil) return nil; a = (u32int*)b->data; pbno = a[bno/ppb]; bno = bno%ppb; blockput(b); b = ext2datablock(fs, pbno, fs->blocksize); if(b == nil) return nil; a = (u32int*)b->data; bno = a[bno]; blockput(b); return ext2datablock(fs, bno, size); } fprint(2, "ext2fileblock %ud: too big\n", obno); return nil; } static int checksuper(Super *super) { if(super->magic != SUPERMAGIC){ werrstr("bad magic 0x%ux wanted 0x%ux", super->magic, SUPERMAGIC); return -1; } return 0; } static int ext2sync(Fsys *fsys) { int i; Group g; Block *b; Super super; Ext2 *fs; Disk *disk; fs = fsys->priv; disk = fs->disk; if((b = diskread(disk, SBSIZE, SBOFF)) == nil) return -1; parsesuper(&super, b->data); blockput(b); if(checksuper(&super) < 0) return -1; fs->blocksize = MINBLOCKSIZE<nblock = super.nblock; fs->ngroup = (super.nblock+super.blockspergroup-1) / super.blockspergroup; fs->inospergroup = super.inospergroup; fs->blockspergroup = super.blockspergroup; if(super.revlevel >= 1) fs->inosize = super.inosize; else fs->inosize = 128; fs->inosperblock = fs->blocksize / fs->inosize; if(fs->blocksize == SBOFF) fs->groupaddr = 2; else fs->groupaddr = 1; fs->descperblock = fs->blocksize / GroupSize; fs->firstblock = super.firstdatablock; fsys->blocksize = fs->blocksize; fsys->nblock = fs->nblock; if(debug) fprint(2, "ext2 %d %d-byte blocks, first data block %d, %d groups of %d\n", fs->nblock, fs->blocksize, fs->firstblock, fs->ngroup, fs->blockspergroup); if(0){ for(i=0; ingroup; i++) if(ext2group(fs, i, &g) >= 0) fprint(2, "grp %d: bitblock=%d\n", i, g.bitblock); } return 0; } static void mkhandle(Nfs3Handle *h, u64int ino) { h->h[0] = ino>>24; h->h[1] = ino>>16; h->h[2] = ino>>8; h->h[3] = ino; h->len = 4; } static u32int byte2u32(uchar *p) { return (p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3]; } static Nfs3Status handle2ino(Ext2 *fs, Nfs3Handle *h, u32int *pinum, Inode *ino) { int i; uint ioff; u32int inum; u32int addr; Block *b; Group g; if(h->len != 4) return Nfs3ErrBadHandle; inum = byte2u32(h->h); if(pinum) *pinum = inum; i = (inum-1) / fs->inospergroup; if(i >= fs->ngroup) return Nfs3ErrBadHandle; ioff = (inum-1) % fs->inospergroup; if(ext2group(fs, i, &g) < 0) return Nfs3ErrIo; addr = g.inodeaddr + ioff/fs->inosperblock; if((b = diskread(fs->disk, fs->blocksize, (u64int)addr*fs->blocksize)) == nil) return Nfs3ErrIo; parseinode(ino, b->data+fs->inosize*(ioff%fs->inosperblock)); blockput(b); return Nfs3Ok; } static Nfs3Status ext2root(Fsys *fsys, Nfs3Handle *h) { USED(fsys); mkhandle(h, ROOTINODE); return Nfs3Ok; } static u64int inosize(Inode* ino) { u64int size; size = ino->size; if((ino->mode&IFMT)==IFREG) size |= (u64int)ino->diracl << 32; return size; } static Nfs3Status ino2attr(Ext2 *fs, Inode *ino, u32int inum, Nfs3Attr *attr) { u32int rdev; attr->type = -1; switch(ino->mode&IFMT){ case IFIFO: attr->type = Nfs3FileFifo; break; case IFCHR: attr->type = Nfs3FileChar; break; case IFDIR: attr->type = Nfs3FileDir; break; case IFBLK: attr->type = Nfs3FileBlock; break; case IFREG: attr->type = Nfs3FileReg; break; case IFLNK: attr->type = Nfs3FileSymlink; break; case IFSOCK: attr->type = Nfs3FileSocket; break; case IFWHT: default: return Nfs3ErrBadHandle; } attr->mode = ino->mode&07777; attr->nlink = ino->nlink; attr->uid = ino->uid; attr->gid = ino->gid; attr->size = inosize(ino); attr->used = (u64int)ino->nblock*fs->blocksize; if(attr->type==Nfs3FileBlock || attr->type==Nfs3FileChar){ rdev = ino->block[0]; attr->major = (rdev>>8)&0xFF; attr->minor = rdev & 0xFFFF00FF; }else{ attr->major = 0; attr->minor = 0; } attr->fsid = 0; attr->fileid = inum; attr->atime.sec = ino->atime; attr->atime.nsec = 0; attr->mtime.sec = ino->mtime; attr->mtime.nsec = 0; attr->ctime.sec = ino->ctime; attr->ctime.nsec = 0; return Nfs3Ok; } static int ingroup(SunAuthUnix *au, uint gid) { int i; for(i=0; ing; i++) if(au->g[i] == gid) return 1; return 0; } static Nfs3Status inoperm(Inode *ino, SunAuthUnix *au, int need) { int have; if(allowall) return Nfs3Ok; have = ino->mode&0777; if(ino->uid == au->uid) have >>= 6; else if(ino->gid == au->gid || ingroup(au, ino->gid)) have >>= 3; if((have&need) != need) return Nfs3ErrNotOwner; /* really EPERM */ return Nfs3Ok; } static Nfs3Status ext2getattr(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, Nfs3Attr *attr) { Inode ino; u32int inum; Ext2 *fs; Nfs3Status ok; fs = fsys->priv; if((ok = handle2ino(fs, h, &inum, &ino)) != Nfs3Ok) return ok; USED(au); /* anyone can getattr */ return ino2attr(fs, &ino, inum, attr); } static Nfs3Status ext2access(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, u32int want, u32int *got, Nfs3Attr *attr) { int have; Inode ino; u32int inum; Ext2 *fs; Nfs3Status ok; fs = fsys->priv; if((ok = handle2ino(fs, h, &inum, &ino)) != Nfs3Ok) return ok; have = ino.mode&0777; if(ino.uid == au->uid) have >>= 6; else if(ino.gid == au->gid || ingroup(au, ino.gid)) have >>= 3; *got = 0; if((want&Nfs3AccessRead) && (have&AREAD)) *got |= Nfs3AccessRead; if((want&Nfs3AccessLookup) && (ino.mode&IFMT)==IFDIR && (have&AEXEC)) *got |= Nfs3AccessLookup; if((want&Nfs3AccessExecute) && (ino.mode&IFMT)!=IFDIR && (have&AEXEC)) *got |= Nfs3AccessExecute; return ino2attr(fs, &ino, inum, attr); } static Nfs3Status ext2lookup(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, char *name, Nfs3Handle *nh) { u32int nblock; u32int i; uchar *p, *ep; Dirent de; Inode ino; Block *b; Ext2 *fs; Nfs3Status ok; int len, want; fs = fsys->priv; if((ok = handle2ino(fs, h, nil, &ino)) != Nfs3Ok) return ok; if((ino.mode&IFMT) != IFDIR) return Nfs3ErrNotDir; if((ok = inoperm(&ino, au, AEXEC)) != Nfs3Ok) return ok; len = strlen(name); nblock = (ino.size+fs->blocksize-1) / fs->blocksize; if(debug) fprint(2, "%d blocks in dir...", nblock); for(i=0; iblocksize; else want = fs->blocksize; b = ext2fileblock(fs, &ino, i, want); if(b == nil){ if(debug) fprint(2, "empty block..."); continue; } p = b->data; ep = p+b->len; while(p < ep){ parsedirent(&de, p); if(de.reclen == 0){ if(debug) fprint(2, "reclen 0 at offset %d of %d\n", (int)(p-b->data), b->len); break; } p += de.reclen; if(p > ep){ if(debug) fprint(2, "bad len %d at offset %d of %d\n", de.reclen, (int)(p-b->data), b->len); break; } if(de.ino == 0) continue; if(4+2+2+de.namlen > de.reclen){ if(debug) fprint(2, "bad namelen %d at offset %d of %d\n", de.namlen, (int)(p-b->data), b->len); break; } if(de.namlen == len && memcmp(de.name, name, len) == 0){ mkhandle(nh, de.ino); blockput(b); return Nfs3Ok; } } blockput(b); } return Nfs3ErrNoEnt; } static Nfs3Status ext2readdir(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, u32int count, u64int cookie, uchar **pdata, u32int *pcount, u1int *peof) { u32int nblock; u32int i; int off, outofspace; uchar *data, *dp, *dep, *p, *ep, *ndp; Dirent de; Inode ino; Block *b; Ext2 *fs; Nfs3Status ok; Nfs3Entry e; int want; fs = fsys->priv; if((ok = handle2ino(fs, h, nil, &ino)) != Nfs3Ok) return ok; if((ino.mode&IFMT) != IFDIR) return Nfs3ErrNotDir; if((ok = inoperm(&ino, au, AREAD)) != Nfs3Ok) return ok; if(debug) print("readdir cookie %#llux ino.size %#llux\n", (u64int)cookie, (u64int)ino.size); if(cookie >= ino.size){ *peof = 1; *pcount = 0; *pdata = 0; return Nfs3Ok; } dp = malloc(count); data = dp; if(dp == nil) return Nfs3ErrNoMem; dep = dp+count; *peof = 0; nblock = (ino.size+fs->blocksize-1) / fs->blocksize; i = cookie/fs->blocksize; off = cookie%fs->blocksize; outofspace = 0; for(; iblocksize; else want = fs->blocksize; b = ext2fileblock(fs, &ino, i, want); if(b == nil) continue; p = b->data; ep = p+b->len; memset(&e, 0, sizeof e); while(p < ep){ parsedirent(&de, p); if(de.reclen == 0){ if(debug) fprint(2, "reclen 0 at offset %d of %d\n", (int)(p-b->data), b->len); break; } p += de.reclen; if(p > ep){ if(debug) fprint(2, "reclen %d at offset %d of %d\n", de.reclen, (int)(p-b->data), b->len); break; } if(de.ino == 0){ if(debug) fprint(2, "zero inode\n"); continue; } if(4+2+2+de.namlen > de.reclen){ if(debug) fprint(2, "bad namlen %d reclen %d at offset %d of %d\n", de.namlen, de.reclen, (int)(p-b->data), b->len); break; } if(debug) print("%.*s/%d ", de.namlen, de.name, (int)de.ino); if(p-de.reclen - b->data < off) continue; e.fileid = de.ino; e.name = de.name; e.namelen = de.namlen; e.cookie = (u64int)i*fs->blocksize + (p - b->data); if(debug) print("%.*s %#llux\n", utfnlen(e.name, e.namelen), e.name, (u64int)e.cookie); if(nfs3entrypack(dp, dep, &ndp, &e) < 0){ outofspace = 1; break; } dp = ndp; } blockput(b); } if(i==nblock && !outofspace) *peof = 1; *pcount = dp - data; *pdata = data; return Nfs3Ok; } static Nfs3Status ext2readfile(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, u32int count, u64int offset, uchar **pdata, u32int *pcount, u1int *peof) { uchar *data; Block *b; Ext2 *fs; int skip1, tot, want, fragcount; Inode ino; Nfs3Status ok; u64int size; fs = fsys->priv; if((ok = handle2ino(fs, h, nil, &ino)) != Nfs3Ok) return ok; if((ok = inoperm(&ino, au, AREAD)) != Nfs3Ok) return ok; size = inosize(&ino); if(offset >= size){ *pdata = 0; *pcount = 0; *peof = 1; return Nfs3Ok; } if(offset+count > size) count = size-offset; data = malloc(count); if(data == nil) return Nfs3ErrNoMem; memset(data, 0, count); skip1 = offset%fs->blocksize; offset -= skip1; want = skip1+count; /* * have to read multiple blocks if we get asked for a big read. * Linux NFS client assumes that if you ask for 8k and only get 4k * back, the remaining 4k is zeros. */ for(tot=0; totblocksize, fs->blocksize); fragcount = fs->blocksize; if(b == nil) continue; if(tot+fragcount > want) fragcount = want - tot; if(tot == 0) memmove(data, b->data+skip1, fragcount-skip1); else memmove(data+tot-skip1, b->data, fragcount); blockput(b); } count = tot - skip1; *peof = (offset+count == size); *pcount = count; *pdata = data; return Nfs3Ok; } static Nfs3Status ext2readlink(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, char **link) { Ext2 *fs; Nfs3Status ok; int len; Inode ino; Block *b; fs = fsys->priv; if((ok = handle2ino(fs, h, nil, &ino)) != Nfs3Ok) return ok; if((ok = inoperm(&ino, au, AREAD)) != Nfs3Ok) return ok; if(ino.size > 1024) return Nfs3ErrIo; len = ino.size; if(ino.nblock != 0){ /* BUG: assumes symlink fits in one block */ b = ext2fileblock(fs, &ino, 0, len); if(b == nil) return Nfs3ErrIo; if(memchr(b->data, 0, len) != nil){ blockput(b); return Nfs3ErrIo; } *link = malloc(len+1); if(*link == 0){ blockput(b); return Nfs3ErrNoMem; } memmove(*link, b->data, len); (*link)[len] = 0; blockput(b); return Nfs3Ok; } if(len > sizeof ino.block) return Nfs3ErrIo; *link = malloc(len+1); if(*link == 0) return Nfs3ErrNoMem; memmove(*link, ino.block, ino.size); (*link)[len] = 0; return Nfs3Ok; } /* * Ext2 is always little-endian, even on big-endian machines. */ static u32int l32(uchar *p) { return p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24); } static u16int l16(uchar *p) { return p[0] | (p[1]<<8); } static u8int l8(uchar *p) { return p[0]; } static void parsedirent(Dirent *de, uchar *p) { de->ino = l32(p); de->reclen = l16(p+4); de->namlen = l8(p+6); /* 1 byte pad */ de->name = (char*)p+8; } static void parseinode(Inode *ino, uchar *p) { int i; ino->mode = l16(p); ino->uid = l16(p+2); ino->size = l32(p+4); ino->atime = l32(p+8); ino->ctime = l32(p+12); ino->mtime = l32(p+16); ino->dtime = l32(p+20); ino->gid = l16(p+24); ino->nlink = l16(p+26); ino->nblock = l32(p+28); ino->flags = l32(p+32); /* 4 byte osd1 */ for(i=0; iblock[i] = l32(p+40+i*4); ino->version = l32(p+100); ino->fileacl = l32(p+104); ino->diracl = l32(p+108); ino->faddr = l32(p+112); /* 12 byte osd2 */ } static void parsegroup(Group *g, uchar *p) { g->bitblock = l32(p); g->inodebitblock = l32(p+4); g->inodeaddr = l32(p+8); g->freeblockscount = l16(p+12); g->freeinodescount = l16(p+14); g->useddirscount = l16(p+16); /* 2 byte pad */ /* 12 byte reserved */ } static void parsesuper(Super *s, uchar *p) { s->ninode = l32(p); s->nblock = l32(p+4); s->rblockcount = l32(p+8); s->freeblockcount = l32(p+12); s->freeinodecount = l32(p+16); s->firstdatablock = l32(p+20); s->logblocksize = l32(p+24); s->logfragsize = l32(p+28); s->blockspergroup = l32(p+32); s->fragpergroup = l32(p+36); s->inospergroup = l32(p+40); s->mtime = l32(p+44); s->wtime = l32(p+48); s->mntcount = l16(p+52); s->maxmntcount = l16(p+54); s->magic = l16(p+56); s->state = l16(p+58); s->errors = l16(p+60); /* 2 byte pad */ s->lastcheck = l32(p+64); s->checkinterval = l32(p+68); s->creatoros = l32(p+72); s->revlevel = l32(p+76); s->defresuid = l16(p+80); s->defresgid = l16(p+82); s->firstino = l32(p+84); s->inosize = l32(p+88); s->blockgroupnr = l16(p+60); /* 932 byte reserved */ }