aboutsummaryrefslogtreecommitdiff
path: root/src/libdiskfs/ffs.c
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2005-07-13 03:48:35 +0000
committerrsc <devnull@localhost>2005-07-13 03:48:35 +0000
commit0c98da8bf8ea51d0288222f6c6ba3c125cf20f46 (patch)
treed249da5fdda43c001a6a99f7354084a5cbfbacef /src/libdiskfs/ffs.c
parentbe7cbb4ef2cb02aa9ac48c02dc1ee585a8e49043 (diff)
downloadplan9port-0c98da8bf8ea51d0288222f6c6ba3c125cf20f46.tar.gz
plan9port-0c98da8bf8ea51d0288222f6c6ba3c125cf20f46.tar.bz2
plan9port-0c98da8bf8ea51d0288222f6c6ba3c125cf20f46.zip
File system access library.
Diffstat (limited to 'src/libdiskfs/ffs.c')
-rw-r--r--src/libdiskfs/ffs.c791
1 files changed, 791 insertions, 0 deletions
diff --git a/src/libdiskfs/ffs.c b/src/libdiskfs/ffs.c
new file mode 100644
index 00000000..2342171f
--- /dev/null
+++ b/src/libdiskfs/ffs.c
@@ -0,0 +1,791 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <sunrpc.h>
+#include <nfs3.h>
+#include <diskfs.h>
+#include "ffs.h"
+
+#define checkcg 0
+#define debug 0
+
+static int checkfsblk(Fsblk*);
+static int checkcgblk(Cgblk*);
+static Block *ffsblockread(Fsys*, u64int);
+static int ffssync(Fsys*);
+static void ffsclose(Fsys*);
+
+static Nfs3Status ffsroot(Fsys*, Nfs3Handle*);
+static Nfs3Status ffsgetattr(Fsys*, SunAuthUnix *au, Nfs3Handle*, Nfs3Attr*);
+static Nfs3Status ffslookup(Fsys*, SunAuthUnix *au, Nfs3Handle*, char*, Nfs3Handle*);
+static Nfs3Status ffsreadfile(Fsys*, SunAuthUnix *au, Nfs3Handle*, u32int, u64int, uchar**, u32int*, u1int*);
+static Nfs3Status ffsreadlink(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, char **link);
+static Nfs3Status ffsreaddir(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, u32int, u64int, uchar**, u32int*, u1int*);
+static Nfs3Status ffsaccess(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, u32int want, u32int *got, Nfs3Attr *attr);
+
+Fsys*
+fsysopenffs(Disk *disk)
+{
+ Ffs *fs;
+ Fsys *fsys;
+
+ fsys = emalloc(sizeof(Fsys));
+ fs = emalloc(sizeof(Ffs));
+ fs->disk = disk;
+ fsys->priv = fs;
+ fsys->type = "ffs";
+ fsys->_readblock = ffsblockread;
+ fsys->_sync = ffssync;
+ fsys->_root = ffsroot;
+ fsys->_getattr = ffsgetattr;
+ fsys->_access = ffsaccess;
+ fsys->_lookup = ffslookup;
+ fsys->_readfile = ffsreadfile;
+ fsys->_readlink = ffsreadlink;
+ fsys->_readdir = ffsreaddir;
+
+ if(ffssync(fsys) < 0)
+ goto error;
+
+ return fsys;
+
+error:
+ ffsclose(fsys);
+ return nil;
+}
+
+static Cgblk*
+ffscylgrp(Ffs *fs, int i, Block **pb)
+{
+ Block *b;
+ Cgblk *cg;
+
+ if(i >= fs->ncg)
+ return nil;
+
+ b = diskread(fs->disk, fs->blocksize, (u64int)fs->cg[i].cgblkno*fs->blocksize);
+ if(b == nil)
+ return nil;
+ cg = (Cgblk*)b->data;
+ if(checkcgblk(cg) < 0){
+fprint(2, "checkcgblk %d %lud: %r\n", i, (ulong)fs->cg[i].cgblkno);
+ blockput(b);
+ return nil;
+ }
+ *pb = b;
+ return cg;
+}
+
+static int
+ffssync(Fsys *fsys)
+{
+ int i;
+ Block *b, *cgb;
+ Cgblk *cgblk;
+ Cylgrp *cg;
+ Disk *disk;
+ Ffs *fs;
+ Fsblk *fsblk;
+
+ fs = fsys->priv;
+ disk = fs->disk;
+
+ /*
+ * Read super block.
+ */
+ if((b = diskread(disk, SBSIZE, SBOFF)) == nil)
+ goto error;
+ fsblk = (Fsblk*)b->data;
+ if(checkfsblk(fsblk) < 0)
+ goto error;
+
+ fs->blocksize = fsblk->blocksize;
+ fs->nblock = (fsblk->nfrag+fsblk->fragsperblock-1) / fsblk->fragsperblock;
+ fs->fragsize = fsblk->fragsize;
+ fs->fragspergroup = fsblk->fragspergroup;
+ fs->fragsperblock = fsblk->fragsperblock;
+ fs->inosperblock = fsblk->inosperblock;
+ fs->inospergroup = fsblk->inospergroup;
+
+ fs->nfrag = fsblk->nfrag;
+ fs->ndfrag = fsblk->ndfrag;
+ fs->blockspergroup = (u64int)fsblk->cylspergroup *
+ fsblk->secspercyl * BYTESPERSEC / fsblk->blocksize;
+ fs->ncg = fsblk->ncg;
+
+ fsys->blocksize = fs->blocksize;
+ fsys->nblock = fs->nblock;
+
+ if(0) fprint(2, "ffs %d %d-byte blocks, %d cylinder groups\n",
+ fs->nblock, fs->blocksize, fs->ncg);
+
+ if(fs->cg == nil)
+ fs->cg = emalloc(fs->ncg*sizeof(Cylgrp));
+ for(i=0; i<fs->ncg; i++){
+ cg = &fs->cg[i];
+ cg->bno = fs->blockspergroup*i + fsblk->cgoffset * (i & ~fsblk->cgmask);
+ cg->cgblkno = cg->bno + fsblk->cfragno/fs->fragsperblock;
+ cg->ibno = cg->bno + fsblk->ifragno/fs->fragsperblock;
+ cg->dbno = cg->bno + fsblk->dfragno/fs->fragsperblock;
+
+ if(checkcg){
+ if((cgb = diskread(disk, fs->blocksize, (u64int)cg->cgblkno*fs->blocksize)) == nil)
+ goto error;
+
+ cgblk = (Cgblk*)cgb->data;
+ if(checkcgblk(cgblk) < 0){
+ blockput(cgb);
+ goto error;
+ }
+ if(cgblk->nfrag % fs->fragsperblock && i != fs->ncg-1){
+ werrstr("fractional number of blocks in non-last cylinder group %d", cgblk->nfrag);
+ blockput(cgb);
+ goto error;
+ }
+ // cg->nfrag = cgblk->nfrag;
+ // cg->nblock = (cgblk->nfrag+fs->fragsperblock-1) / fs->fragsperblock;
+ // fprint(2, "cg #%d: cgblk %lud, %d blocks, %d inodes\n", cgblk->num, (ulong)cg->cgblkno, cg->nblock, cg->nino);
+ }
+ }
+ blockput(b);
+ return 0;
+
+error:
+ blockput(b);
+ return -1;
+}
+
+static void
+ffsclose(Fsys *fsys)
+{
+ Ffs *fs;
+
+ fs = fsys->priv;
+ if(fs->cg)
+ free(fs->cg);
+ free(fs);
+ free(fsys);
+}
+
+static int
+checkfsblk(Fsblk *super)
+{
+ if(super->magic != FSMAGIC){
+ werrstr("bad super block");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+checkcgblk(Cgblk *cg)
+{
+ if(cg->magic != CGMAGIC){
+ werrstr("bad cylinder group block");
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Read block #bno from the disk, zeroing unused data.
+ * If there is no data whatsoever, it's okay to return nil.
+ */
+int nskipx;
+static Block*
+ffsblockread(Fsys *fsys, u64int bno)
+{
+ u32int i, o;
+ u8int *fmap;
+ int frag, fsize, avail;
+ Block *b;
+// Cylgrp *cg;
+ Cgblk *cgblk;
+ Ffs *fs;
+
+ fs = fsys->priv;
+ i = bno / fs->blockspergroup;
+ o = bno % fs->blockspergroup;
+ if(i >= fs->ncg)
+ return nil;
+// cg = &fs->cg[i];
+
+// if(o >= cg->nblock)
+// return nil;
+
+ if((cgblk = ffscylgrp(fs, i, &b)) == nil)
+ return nil;
+
+ fmap = (u8int*)cgblk+cgblk->fmapoff;
+ frag = fs->fragsperblock;
+ switch(frag){
+ default:
+ sysfatal("bad frag");
+ case 8:
+ avail = fmap[o];
+ break;
+ case 4:
+ avail = (fmap[o>>1] >> ((o&1)*4)) & 0xF;
+ break;
+ case 2:
+ avail = (fmap[o>>2] >> ((o&3)*2)) & 0x3;
+ break;
+ case 1:
+ avail = (fmap[o>>3] >> (o&7)) & 0x1;
+ break;
+ }
+ blockput(b);
+
+ if(avail == ((1<<frag)-1))
+{
+nskipx++;
+ return nil;
+}
+ if((b = diskread(fs->disk, fs->blocksize, bno*fs->blocksize)) == nil){
+ fprint(2, "diskread failed!!!\n");
+ return nil;
+ }
+
+ fsize = fs->fragsize;
+ for(i=0; i<frag; i++)
+ if(avail & (1<<i))
+ memset(b->data + fsize*i, 0, fsize);
+ return b;
+}
+
+static Block*
+ffsdatablock(Ffs *fs, u32int bno, int size)
+{
+ int fsize;
+ u64int diskaddr;
+ Block *b;
+
+ if(bno == 0)
+ return nil;
+
+ fsize = size;
+ if(fsize < fs->fragsize)
+ fsize = fs->fragsize;
+
+ if(bno >= fs->nfrag){
+ fprint(2, "ffs: request for block %#lux; nfrag %#x\n", (ulong)bno, fs->nfrag);
+ return nil;
+ }
+ diskaddr = (u64int)bno*fs->fragsize;
+ b = diskread(fs->disk, fsize, diskaddr);
+ if(b == nil){
+ fprint(2, "ffs: disk i/o at %#llux for %#ux: %r\n", diskaddr, fsize);
+ return nil;
+ }
+ if(b->len < fsize){
+ fprint(2, "ffs: disk i/o at %#llux for %#ux got %#ux\n", diskaddr, fsize,
+ b->len);
+ blockput(b);
+ return nil;
+ }
+
+ return b;
+}
+
+static Block*
+ffsfileblock(Ffs *fs, Inode *ino, u32int bno, int size)
+{
+ int ppb;
+ Block *b;
+ u32int *a;
+
+ if(bno < NDADDR){
+ if(debug) fprint(2, "ffsfileblock %lud: direct %#lux\n", (ulong)bno, (ulong)ino->db[bno]);
+ return ffsdatablock(fs, ino->db[bno], size);
+ }
+ bno -= NDADDR;
+ ppb = fs->blocksize/4;
+
+ if(bno/ppb < NIADDR){
+ if(debug) fprint(2, "ffsfileblock %lud: indirect %#lux\n", (ulong)(bno+NDADDR),
+ (ulong)ino->ib[bno/ppb]);
+ b = ffsdatablock(fs, ino->ib[bno/ppb], fs->blocksize);
+ if(b == nil)
+ return nil;
+ a = (u32int*)b->data;
+ bno = a[bno%ppb];
+ if(debug) fprint(2, "ffsfileblock: indirect fetch %#lux size %d\n", (ulong)bno, size);
+ blockput(b);
+ return ffsdatablock(fs, bno, size);
+ }
+
+ fprint(2, "ffsfileblock %lud: too big\n", (ulong)bno+NDADDR);
+ return nil;
+}
+
+/*
+ * NFS handles are 4-byte inode number.
+ */
+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(Ffs *fs, Nfs3Handle *h, u32int *pinum, Inode *ino)
+{
+ int i;
+ u32int ioff;
+ u32int inum;
+ Block *b;
+ Cylgrp *cg;
+
+ if(h->len != 4)
+ return Nfs3ErrBadHandle;
+ inum = byte2u32(h->h);
+ if(pinum)
+ *pinum = inum;
+ if(debug) print("inum %d...", (int)inum);
+
+ /* fetch inode from disk */
+ i = inum / fs->inospergroup;
+ ioff = inum % fs->inospergroup;
+ if(debug)print("cg %d off %d...", i, (int)ioff);
+ if(i >= fs->ncg)
+ return Nfs3ErrBadHandle;
+ cg = &fs->cg[i];
+/*
+ if(ioff >= cg->nino)
+ return Nfs3ErrBadHandle;
+*/
+
+ if(debug) print("cg->ibno %d...", cg->ibno);
+ if((b = diskread(fs->disk, fs->blocksize,
+ (cg->ibno+ioff/fs->inosperblock)*(vlong)fs->blocksize)) == nil)
+ return Nfs3ErrIo;
+ *ino = ((Inode*)b->data)[ioff%fs->inosperblock];
+ blockput(b);
+
+ return Nfs3Ok;
+}
+
+static Nfs3Status
+ffsroot(Fsys *fsys, Nfs3Handle *h)
+{
+ USED(fsys);
+ mkhandle(h, 2);
+ return Nfs3Ok;
+}
+
+static Nfs3Status
+ino2attr(Ffs *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 = ino->size;
+ attr->used = ino->nblock*fs->blocksize;
+ if(attr->type==Nfs3FileBlock || attr->type==Nfs3FileChar){
+ rdev = ino->db[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 = ino->atimensec;
+ attr->mtime.sec = ino->mtime;
+ attr->mtime.nsec = ino->mtimensec;
+ attr->ctime.sec = ino->ctime;
+ attr->ctime.nsec = ino->ctimensec;
+ return Nfs3Ok;
+}
+
+static int
+ingroup(SunAuthUnix *au, uint gid)
+{
+ int i;
+
+ for(i=0; i<au->ng; i++)
+ if(au->g[i] == gid)
+ return 1;
+ return 0;
+}
+
+static Nfs3Status
+inoperm(Inode *ino, SunAuthUnix *au, int need)
+{
+ int have;
+
+ 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
+ffsgetattr(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, Nfs3Attr *attr)
+{
+ Inode ino;
+ u32int inum;
+ Ffs *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
+ffsaccess(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, u32int want, u32int *got, Nfs3Attr *attr)
+{
+ int have;
+ Inode ino;
+ u32int inum;
+ Ffs *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
+ffslookup(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, char *name, Nfs3Handle *nh)
+{
+ u32int nblock;
+ u32int i;
+ uchar *p, *ep;
+ Dirent *de;
+ Inode ino;
+ Block *b;
+ Ffs *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;
+ for(i=0; i<nblock; i++){
+ if(i==nblock-1)
+ want = ino.size % fs->blocksize;
+ else
+ want = fs->blocksize;
+ b = ffsfileblock(fs, &ino, i, want);
+ if(b == nil)
+ continue;
+ p = b->data;
+ ep = p+b->len;
+ while(p < ep){
+ de = (Dirent*)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
+ffsreaddir(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, u32int count, u64int cookie, uchar **pdata, u32int *pcount, u1int *peof)
+{
+ u32int nblock;
+ u32int i;
+ int off, done;
+ uchar *data, *dp, *dep, *p, *ep, *ndp;
+ Dirent *de;
+ Inode ino;
+ Block *b;
+ Ffs *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(cookie >= ino.size){
+ *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;
+ done = 0;
+ for(; i<nblock && !done; i++){
+ if(i==nblock-1)
+ want = ino.size % fs->blocksize;
+ else
+ want = fs->blocksize;
+ b = ffsfileblock(fs, &ino, i, want);
+ if(b == nil)
+ continue;
+ p = b->data;
+ ep = p+b->len;
+ memset(&e, 0, sizeof e);
+ while(p < ep){
+ de = (Dirent*)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(de->name[de->namlen] != 0){
+ if(debug) fprint(2, "bad name %d %.*s\n", de->namlen, de->namlen, de->name);
+ continue;
+ }
+ if(debug) print("%s/%d ", de->name, (int)de->ino);
+ if((uchar*)de - b->data < off)
+ continue;
+ e.fileid = de->ino;
+ e.name = de->name;
+ e.cookie = (u64int)i*fs->blocksize + (p - b->data);
+ if(nfs3entrypack(dp, dep, &ndp, &e) < 0){
+ done = 1;
+ break;
+ }
+ dp = ndp;
+ }
+ off = 0;
+ blockput(b);
+ }
+ if(i==nblock)
+ *peof = 1;
+
+ *pcount = dp - data;
+ *pdata = data;
+ return Nfs3Ok;
+}
+
+static Nfs3Status
+ffsreadfile(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, u32int count,
+ u64int offset, uchar **pdata, u32int *pcount, u1int *peof)
+{
+ uchar *data;
+ Block *b;
+ Ffs *fs;
+ int off, want, fragcount;
+ Inode ino;
+ Nfs3Status ok;
+
+ fs = fsys->priv;
+ if((ok = handle2ino(fs, h, nil, &ino)) != Nfs3Ok)
+ return ok;
+
+ if((ok = inoperm(&ino, au, AREAD)) != Nfs3Ok)
+ return ok;
+
+ if(offset >= ino.size){
+ *pdata = 0;
+ *pcount = 0;
+ *peof = 1;
+ return Nfs3Ok;
+ }
+ if(offset+count > ino.size)
+ count = ino.size-offset;
+ if(offset/fs->blocksize != (offset+count-1)/fs->blocksize)
+ count = fs->blocksize - offset%fs->blocksize;
+
+ data = malloc(count);
+ if(data == nil)
+ return Nfs3ErrNoMem;
+
+ want = offset%fs->blocksize+count;
+ if(want%fs->fragsize)
+ want += fs->fragsize - want%fs->fragsize;
+
+ b = ffsfileblock(fs, &ino, offset/fs->blocksize, want);
+ if(b == nil){
+ /* BUG: distinguish sparse file from I/O error */
+ memset(data, 0, count);
+ }else{
+ off = offset%fs->blocksize;
+ fragcount = count; /* need signed variable */
+ if(off+fragcount > b->len){
+ fragcount = b->len - off;
+ if(fragcount < 0)
+ fragcount = 0;
+ }
+ if(fragcount > 0)
+ memmove(data, b->data+off, fragcount);
+ count = fragcount;
+ blockput(b);
+ }
+ *peof = (offset+count == ino.size);
+ *pcount = count;
+ *pdata = data;
+ return Nfs3Ok;
+}
+
+static Nfs3Status
+ffsreadlink(Fsys *fsys, SunAuthUnix *au, Nfs3Handle *h, char **link)
+{
+ Ffs *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 = ffsfileblock(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.db + sizeof ino.ib)
+ return Nfs3ErrIo;
+
+ *link = malloc(len+1);
+ if(*link == 0)
+ return Nfs3ErrNoMem;
+ memmove(*link, ino.db, ino.size);
+ (*link)[len] = 0;
+ return Nfs3Ok;
+}