/* * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ /* * ext2subs.c version 0.20 * * Some strategic functions come from linux/fs/ext2 * kernel sources written by Remy Card. **/ #include <u.h>#include <libc.h>#include <bio.h>#include <fcall.h>#include <thread.h>#include <9p.h>#include "dat.h"#include "fns.h" #define putext2(e) putbuf((e).buf)#define dirtyext2(e) dirtybuf((e).buf) static Intmap *uidmap, *gidmap; static intgetnum(char *s, int *n){ char *r; *n = strtol(s, &r, 10); return (r != s);} static Intmap*idfile(char *f){ Biobuf *bin; Intmap *map; char *fields[3]; char *s; int nf, id; map = allocmap(0); bin = Bopen(f, OREAD); if (bin == 0) return 0; while ((s = Brdline(bin, '\n')) != 0) { s[Blinelen(bin)-1] = '\0'; nf = getfields(s, fields, 3, 0, ":"); if (nf == 3 && getnum(fields[2], &id)) insertkey(map, id, strdup(fields[0])); } Bterm(bin); return map;} voiduidfile(char *f){ uidmap = idfile(f);} voidgidfile(char *f){ gidmap = idfile(f);} static char*mapuid(int id){ static char s[12]; char *p; if (uidmap && (p = lookupkey(uidmap, id)) != 0) return p; sprint(s, "%d", id); return s;} static char*mapgid(int id){ static char s[12]; char *p; if (gidmap && (p = lookupkey(gidmap, id)) != 0) return p; sprint(s, "%d", id); return s;} intext2fs(Xfs *xf){ SuperBlock superblock; /* get the super block */ seek(xf->dev, OFFSET_SUPER_BLOCK, 0); if( sizeof(SuperBlock) != read(xf->dev, &superblock, sizeof(SuperBlock)) ){ chat("can't read super block %r...", xf->dev); errno = Eformat; return -1; } if( superblock.s_magic != EXT2_SUPER_MAGIC ){ chat("Bad super block..."); errno = Eformat; return -1; } if( !(superblock.s_state & EXT2_VALID_FS) ){ chat("fs not checked..."); errno = Enotclean; return -1; } xf->block_size = EXT2_MIN_BLOCK_SIZE << superblock.s_log_block_size; xf->desc_per_block = xf->block_size / sizeof (GroupDesc); xf->inodes_per_group = superblock.s_inodes_per_group; xf->inodes_per_block = xf->block_size / sizeof (Inode); xf->addr_per_block = xf->block_size / sizeof (uint); xf->blocks_per_group = superblock.s_blocks_per_group; if( xf->block_size == OFFSET_SUPER_BLOCK ) xf->superaddr = 1, xf->superoff = 0, xf->grpaddr = 2; else if( xf->block_size == 2*OFFSET_SUPER_BLOCK || xf->block_size == 4*OFFSET_SUPER_BLOCK ) xf->superaddr = 0, xf->superoff = OFFSET_SUPER_BLOCK, xf->grpaddr = 1; else { chat(" blocks of %d bytes are not supported...", xf->block_size); errno = Eformat; return -1; } chat("good super block..."); xf->ngroups = (superblock.s_blocks_count - superblock.s_first_data_block + superblock.s_blocks_per_group -1) / superblock.s_blocks_per_group; superblock.s_state &= ~EXT2_VALID_FS; superblock.s_mnt_count++; seek(xf->dev, OFFSET_SUPER_BLOCK, 0); if( !rdonly && sizeof(SuperBlock) != write(xf->dev, &superblock, sizeof(SuperBlock)) ){ chat("can't write super block..."); errno = Eio; return -1; } return 0;}Ext2getext2(Xfs *xf, char type, int n){ Iobuf *bd; Ext2 e; switch(type){ case EXT2_SUPER: e.buf = getbuf(xf, xf->superaddr); if( !e.buf ) goto error; e.u.sb = (SuperBlock *)(e.buf->iobuf + xf->superoff); e.type = EXT2_SUPER; break; case EXT2_DESC: e.buf = getbuf(xf, DESC_ADDR(xf, n)); if( !e.buf ) goto error; e.u.gd = DESC_OFFSET(xf, e.buf->iobuf, n); e.type = EXT2_DESC; break; case EXT2_BBLOCK: bd = getbuf(xf, DESC_ADDR(xf, n)); if( !bd ) goto error; e.buf = getbuf(xf, DESC_OFFSET(xf, bd->iobuf, n)->bg_block_bitmap); if( !e.buf ){ putbuf(bd); goto error; } putbuf(bd); e.u.bmp = (char *)e.buf->iobuf; e.type = EXT2_BBLOCK; break; case EXT2_BINODE: bd = getbuf(xf, DESC_ADDR(xf, n)); if( !bd ) goto error; e.buf = getbuf(xf, DESC_OFFSET(xf, bd->iobuf, n)->bg_inode_bitmap); if( !e.buf ){ putbuf(bd); goto error; } putbuf(bd); e.u.bmp = (char *)e.buf->iobuf; e.type = EXT2_BINODE; break; default: goto error; } return e;error: panic("getext2"); return e;}intget_inode( Xfile *file, uint nr ){ unsigned long block_group, block; Xfs *xf = file->xf; Ext2 ed, es; es = getext2(xf, EXT2_SUPER, 0); if(nr > es.u.sb->s_inodes_count ){ chat("inode number %d is too big...", nr); putext2(es); errno = Eio; return -1; } putext2(es); block_group = (nr - 1) / xf->inodes_per_group; if( block_group >= xf->ngroups ){ chat("block group (%d) > groups count...", block_group); errno = Eio; return -1; } ed = getext2(xf, EXT2_DESC, block_group); block = ed.u.gd->bg_inode_table + (((nr-1) % xf->inodes_per_group) / xf->inodes_per_block); putext2(ed); file->bufoffset = (nr-1) % xf->inodes_per_block; file->inbr = nr; file->bufaddr= block; return 1;}intget_file( Xfile *f, char *name){ uint offset, nr, i; Xfs *xf = f->xf; Inode *inode; int nblock; DirEntry *dir; Iobuf *buf, *ibuf; if( !S_ISDIR(getmode(f)) ) return -1; ibuf = getbuf(xf, f->bufaddr); if( !ibuf ) return -1; inode = ((Inode *)ibuf->iobuf) + f->bufoffset; nblock = (inode->i_blocks * 512) / xf->block_size; for(i=0 ; (i < nblock) && (i < EXT2_NDIR_BLOCKS) ; i++){ buf = getbuf(xf, inode->i_block[i]); if( !buf ){ putbuf(ibuf); return -1; } for(offset=0 ; offset < xf->block_size ; ){ dir = (DirEntry *)(buf->iobuf + offset); if( dir->name_len==strlen(name) && !strncmp(name, dir->name, dir->name_len) ){ nr = dir->inode; putbuf(buf); putbuf(ibuf); return nr; } offset += dir->rec_len; } putbuf(buf); } putbuf(ibuf); errno = Enonexist; return -1;}char *getname(Xfile *f, char *str){ Xfile ft; int offset, i, len; Xfs *xf = f->xf; Inode *inode; int nblock; DirEntry *dir; Iobuf *buf, *ibuf; ft = *f; if( get_inode(&ft, f->pinbr) < 0 ) return 0; if( !S_ISDIR(getmode(&ft)) ) return 0; ibuf = getbuf(xf, ft.bufaddr); if( !ibuf ) return 0; inode = ((Inode *)ibuf->iobuf) + ft.bufoffset; nblock = (inode->i_blocks * 512) / xf->block_size; for(i=0 ; (i < nblock) && (i < EXT2_NDIR_BLOCKS) ; i++){ buf = getbuf(xf, inode->i_block[i]); if( !buf ){ putbuf(ibuf); return 0; } for(offset=0 ; offset < xf->block_size ; ){ dir = (DirEntry *)(buf->iobuf + offset); if( f->inbr == dir->inode ){ len = (dir->name_len < EXT2_NAME_LEN) ? dir->name_len : EXT2_NAME_LEN; if (str == 0) str = malloc(len+1); strncpy(str, dir->name, len); str[len] = 0; putbuf(buf); putbuf(ibuf); return str; } offset += dir->rec_len; } putbuf(buf); } putbuf(ibuf); errno = Enonexist; return 0;}voiddostat(Qid qid, Xfile *f, Dir *dir ){ Inode *inode; Iobuf *ibuf; char *name; memset(dir, 0, sizeof(Dir)); if( f->inbr == EXT2_ROOT_INODE ){ dir->name = estrdup9p("/"); dir->qid = (Qid){0,0,QTDIR}; dir->mode = DMDIR | 0777; }else{ ibuf = getbuf(f->xf, f->bufaddr); if( !ibuf ) return; inode = ((Inode *)ibuf->iobuf) + f->bufoffset; dir->length = inode->i_size; dir->atime = inode->i_atime; dir->mtime = inode->i_mtime; putbuf(ibuf); name = getname(f, 0); dir->name = name; dir->uid = estrdup9p(mapuid(inode->i_uid)); dir->gid = estrdup9p(mapgid(inode->i_gid)); dir->qid = qid; dir->mode = getmode(f); if( qid.type & QTDIR ) dir->mode |= DMDIR; } }int dowstat(Xfile *f, Dir *stat){ Xfs *xf = f->xf; Inode *inode; Xfile fdir; Iobuf *ibuf; char name[EXT2_NAME_LEN+1]; /* change name */ getname(f, name); if( stat->name && stat->name[0] != 0 && strcmp(name, stat->name) ){ /* get dir */ fdir = *f; if( get_inode(&fdir, f->pinbr) < 0 ){ chat("can't get inode %d...", f->pinbr); return -1; } ibuf = getbuf(xf, fdir.bufaddr); if( !ibuf ) return -1; inode = ((Inode *)ibuf->iobuf) +fdir.bufoffset; /* Clean old dir entry */ if( delete_entry(xf, inode, f->inbr) < 0 ){ chat("delete entry failed..."); putbuf(ibuf); return -1; } putbuf(ibuf); /* add the new entry */ if( add_entry(&fdir, stat->name, f->inbr) < 0 ){ chat("add entry failed..."); return -1; } } ibuf = getbuf(xf, f->bufaddr); if( !ibuf ) return -1; inode = ((Inode *)ibuf->iobuf) + f->bufoffset; if (stat->mode != ~0) if( (getmode(f) & 0777) != (stat->mode & 0777) ){ inode->i_mode = (getmode(f) & ~0777) | (stat->mode & 0777); dirtybuf(ibuf); } if (stat->mtime != ~0) if( inode->i_mtime != stat->mtime ){ inode->i_mtime = stat->mtime; dirtybuf(ibuf); } putbuf(ibuf); return 1;}longreadfile(Xfile *f, void *vbuf, vlong offset, long count){ Xfs *xf = f->xf; Inode *inode; Iobuf *buffer, *ibuf; long rcount; int len, o, cur_block, baddr; uchar *buf; buf = vbuf; ibuf = getbuf(xf, f->bufaddr); if( !ibuf ) return -1; inode = ((Inode *)ibuf->iobuf) + f->bufoffset; if( offset >= inode->i_size ){ putbuf(ibuf); return 0; } if( offset + count > inode->i_size ) count = inode->i_size - offset; /* fast link */ if( S_ISLNK(getmode(f)) && (inode->i_size <= EXT2_N_BLOCKS<<2) ){ memcpy(&buf[0], ((char *)inode->i_block)+offset, count); putbuf(ibuf); return count; } chat("read block [ "); cur_block = offset / xf->block_size; o = offset % xf->block_size; rcount = 0; while( count > 0 ){ baddr = bmap(f, cur_block++); if( !baddr ){ putbuf(ibuf); return -1; } buffer = getbuf(xf, baddr); if( !buffer ){ putbuf(ibuf); return -1; } chat("%d ", baddr); len = xf->block_size - o; if( len > count ) len = count; memcpy(&buf[rcount], &buffer->iobuf[o], len); rcount += len; count -= len; o = 0; putbuf(buffer); } chat("] ..."); inode->i_atime = time(0); dirtybuf(ibuf); putbuf(ibuf); return rcount;}longreaddir(Xfile *f, void *vbuf, vlong offset, long count){ int off, i, len; long rcount; Xfs *xf = f->xf; Inode *inode, *tinode; int nblock; DirEntry *edir; Iobuf *buffer, *ibuf, *tbuf; Dir pdir; Xfile ft;Lines 1–500 of 1879. Load more
/* * This file is part of the UCB release of Plan 9. It is subject to the license * terms in the LICENSE file found in the top-level directory of this * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No * part of the UCB release of Plan 9, including this file, may be copied, * modified, propagated, or distributed except according to the terms contained * in the LICENSE file. */ /* * ext2subs.c version 0.20 * * Some strategic functions come from linux/fs/ext2 * kernel sources written by Remy Card. * */ #include <u.h> #include <libc.h> #include <bio.h> #include <fcall.h> #include <thread.h> #include <9p.h> #include "dat.h" #include "fns.h" #define putext2(e) putbuf((e).buf) #define dirtyext2(e) dirtybuf((e).buf) static Intmap *uidmap, *gidmap; static int getnum(char *s, int *n) { char *r; *n = strtol(s, &r, 10); return (r != s); } static Intmap* idfile(char *f) { Biobuf *bin; Intmap *map; char *fields[3]; char *s; int nf, id; map = allocmap(0); bin = Bopen(f, OREAD); if (bin == 0) return 0; while ((s = Brdline(bin, '\n')) != 0) { s[Blinelen(bin)-1] = '\0'; nf = getfields(s, fields, 3, 0, ":"); if (nf == 3 && getnum(fields[2], &id)) insertkey(map, id, strdup(fields[0])); } Bterm(bin); return map; } void uidfile(char *f) { uidmap = idfile(f); } void gidfile(char *f) { gidmap = idfile(f); } static char* mapuid(int id) { static char s[12]; char *p; if (uidmap && (p = lookupkey(uidmap, id)) != 0) return p; sprint(s, "%d", id); return s; } static char* mapgid(int id) { static char s[12]; char *p; if (gidmap && (p = lookupkey(gidmap, id)) != 0) return p; sprint(s, "%d", id); return s; } int ext2fs(Xfs *xf) { SuperBlock superblock; /* get the super block */ seek(xf->dev, OFFSET_SUPER_BLOCK, 0); if( sizeof(SuperBlock) != read(xf->dev, &superblock, sizeof(SuperBlock)) ){ chat("can't read super block %r...", xf->dev); errno = Eformat; return -1; } if( superblock.s_magic != EXT2_SUPER_MAGIC ){ chat("Bad super block..."); errno = Eformat; return -1; } if( !(superblock.s_state & EXT2_VALID_FS) ){ chat("fs not checked..."); errno = Enotclean; return -1; } xf->block_size = EXT2_MIN_BLOCK_SIZE << superblock.s_log_block_size; xf->desc_per_block = xf->block_size / sizeof (GroupDesc); xf->inodes_per_group = superblock.s_inodes_per_group; xf->inodes_per_block = xf->block_size / sizeof (Inode); xf->addr_per_block = xf->block_size / sizeof (uint); xf->blocks_per_group = superblock.s_blocks_per_group; if( xf->block_size == OFFSET_SUPER_BLOCK ) xf->superaddr = 1, xf->superoff = 0, xf->grpaddr = 2; else if( xf->block_size == 2*OFFSET_SUPER_BLOCK || xf->block_size == 4*OFFSET_SUPER_BLOCK ) xf->superaddr = 0, xf->superoff = OFFSET_SUPER_BLOCK, xf->grpaddr = 1; else { chat(" blocks of %d bytes are not supported...", xf->block_size); errno = Eformat; return -1; } chat("good super block..."); xf->ngroups = (superblock.s_blocks_count - superblock.s_first_data_block + superblock.s_blocks_per_group -1) / superblock.s_blocks_per_group; superblock.s_state &= ~EXT2_VALID_FS; superblock.s_mnt_count++; seek(xf->dev, OFFSET_SUPER_BLOCK, 0); if( !rdonly && sizeof(SuperBlock) != write(xf->dev, &superblock, sizeof(SuperBlock)) ){ chat("can't write super block..."); errno = Eio; return -1; } return 0; } Ext2 getext2(Xfs *xf, char type, int n) { Iobuf *bd; Ext2 e; switch(type){ case EXT2_SUPER: e.buf = getbuf(xf, xf->superaddr); if( !e.buf ) goto error; e.u.sb = (SuperBlock *)(e.buf->iobuf + xf->superoff); e.type = EXT2_SUPER; break; case EXT2_DESC: e.buf = getbuf(xf, DESC_ADDR(xf, n)); if( !e.buf ) goto error; e.u.gd = DESC_OFFSET(xf, e.buf->iobuf, n); e.type = EXT2_DESC; break; case EXT2_BBLOCK: bd = getbuf(xf, DESC_ADDR(xf, n)); if( !bd ) goto error; e.buf = getbuf(xf, DESC_OFFSET(xf, bd->iobuf, n)->bg_block_bitmap); if( !e.buf ){ putbuf(bd); goto error; } putbuf(bd); e.u.bmp = (char *)e.buf->iobuf; e.type = EXT2_BBLOCK; break; case EXT2_BINODE: bd = getbuf(xf, DESC_ADDR(xf, n)); if( !bd ) goto error; e.buf = getbuf(xf, DESC_OFFSET(xf, bd->iobuf, n)->bg_inode_bitmap); if( !e.buf ){ putbuf(bd); goto error; } putbuf(bd); e.u.bmp = (char *)e.buf->iobuf; e.type = EXT2_BINODE; break; default: goto error; } return e; error: panic("getext2"); return e; } int get_inode( Xfile *file, uint nr ) { unsigned long block_group, block; Xfs *xf = file->xf; Ext2 ed, es; es = getext2(xf, EXT2_SUPER, 0); if(nr > es.u.sb->s_inodes_count ){ chat("inode number %d is too big...", nr); putext2(es); errno = Eio; return -1; } putext2(es); block_group = (nr - 1) / xf->inodes_per_group; if( block_group >= xf->ngroups ){ chat("block group (%d) > groups count...", block_group); errno = Eio; return -1; } ed = getext2(xf, EXT2_DESC, block_group); block = ed.u.gd->bg_inode_table + (((nr-1) % xf->inodes_per_group) / xf->inodes_per_block); putext2(ed); file->bufoffset = (nr-1) % xf->inodes_per_block; file->inbr = nr; file->bufaddr= block; return 1; } int get_file( Xfile *f, char *name) { uint offset, nr, i; Xfs *xf = f->xf; Inode *inode; int nblock; DirEntry *dir; Iobuf *buf, *ibuf; if( !S_ISDIR(getmode(f)) ) return -1; ibuf = getbuf(xf, f->bufaddr); if( !ibuf ) return -1; inode = ((Inode *)ibuf->iobuf) + f->bufoffset; nblock = (inode->i_blocks * 512) / xf->block_size; for(i=0 ; (i < nblock) && (i < EXT2_NDIR_BLOCKS) ; i++){ buf = getbuf(xf, inode->i_block[i]); if( !buf ){ putbuf(ibuf); return -1; } for(offset=0 ; offset < xf->block_size ; ){ dir = (DirEntry *)(buf->iobuf + offset); if( dir->name_len==strlen(name) && !strncmp(name, dir->name, dir->name_len) ){ nr = dir->inode; putbuf(buf); putbuf(ibuf); return nr; } offset += dir->rec_len; } putbuf(buf); } putbuf(ibuf); errno = Enonexist; return -1; } char * getname(Xfile *f, char *str) { Xfile ft; int offset, i, len; Xfs *xf = f->xf; Inode *inode; int nblock; DirEntry *dir; Iobuf *buf, *ibuf; ft = *f; if( get_inode(&ft, f->pinbr) < 0 ) return 0; if( !S_ISDIR(getmode(&ft)) ) return 0; ibuf = getbuf(xf, ft.bufaddr); if( !ibuf ) return 0; inode = ((Inode *)ibuf->iobuf) + ft.bufoffset; nblock = (inode->i_blocks * 512) / xf->block_size; for(i=0 ; (i < nblock) && (i < EXT2_NDIR_BLOCKS) ; i++){ buf = getbuf(xf, inode->i_block[i]); if( !buf ){ putbuf(ibuf); return 0; } for(offset=0 ; offset < xf->block_size ; ){ dir = (DirEntry *)(buf->iobuf + offset); if( f->inbr == dir->inode ){ len = (dir->name_len < EXT2_NAME_LEN) ? dir->name_len : EXT2_NAME_LEN; if (str == 0) str = malloc(len+1); strncpy(str, dir->name, len); str[len] = 0; putbuf(buf); putbuf(ibuf); return str; } offset += dir->rec_len; } putbuf(buf); } putbuf(ibuf); errno = Enonexist; return 0; } void dostat(Qid qid, Xfile *f, Dir *dir ) { Inode *inode; Iobuf *ibuf; char *name; memset(dir, 0, sizeof(Dir)); if( f->inbr == EXT2_ROOT_INODE ){ dir->name = estrdup9p("/"); dir->qid = (Qid){0,0,QTDIR}; dir->mode = DMDIR | 0777; }else{ ibuf = getbuf(f->xf, f->bufaddr); if( !ibuf ) return; inode = ((Inode *)ibuf->iobuf) + f->bufoffset; dir->length = inode->i_size; dir->atime = inode->i_atime; dir->mtime = inode->i_mtime; putbuf(ibuf); name = getname(f, 0); dir->name = name; dir->uid = estrdup9p(mapuid(inode->i_uid)); dir->gid = estrdup9p(mapgid(inode->i_gid)); dir->qid = qid; dir->mode = getmode(f); if( qid.type & QTDIR ) dir->mode |= DMDIR; } } int dowstat(Xfile *f, Dir *stat) { Xfs *xf = f->xf; Inode *inode; Xfile fdir; Iobuf *ibuf; char name[EXT2_NAME_LEN+1]; /* change name */ getname(f, name); if( stat->name && stat->name[0] != 0 && strcmp(name, stat->name) ){ /* get dir */ fdir = *f; if( get_inode(&fdir, f->pinbr) < 0 ){ chat("can't get inode %d...", f->pinbr); return -1; } ibuf = getbuf(xf, fdir.bufaddr); if( !ibuf ) return -1; inode = ((Inode *)ibuf->iobuf) +fdir.bufoffset; /* Clean old dir entry */ if( delete_entry(xf, inode, f->inbr) < 0 ){ chat("delete entry failed..."); putbuf(ibuf); return -1; } putbuf(ibuf); /* add the new entry */ if( add_entry(&fdir, stat->name, f->inbr) < 0 ){ chat("add entry failed..."); return -1; } } ibuf = getbuf(xf, f->bufaddr); if( !ibuf ) return -1; inode = ((Inode *)ibuf->iobuf) + f->bufoffset; if (stat->mode != ~0) if( (getmode(f) & 0777) != (stat->mode & 0777) ){ inode->i_mode = (getmode(f) & ~0777) | (stat->mode & 0777); dirtybuf(ibuf); } if (stat->mtime != ~0) if( inode->i_mtime != stat->mtime ){ inode->i_mtime = stat->mtime; dirtybuf(ibuf); } putbuf(ibuf); return 1; } long readfile(Xfile *f, void *vbuf, vlong offset, long count) { Xfs *xf = f->xf; Inode *inode; Iobuf *buffer, *ibuf; long rcount; int len, o, cur_block, baddr; uchar *buf; buf = vbuf; ibuf = getbuf(xf, f->bufaddr); if( !ibuf ) return -1; inode = ((Inode *)ibuf->iobuf) + f->bufoffset; if( offset >= inode->i_size ){ putbuf(ibuf); return 0; } if( offset + count > inode->i_size ) count = inode->i_size - offset; /* fast link */ if( S_ISLNK(getmode(f)) && (inode->i_size <= EXT2_N_BLOCKS<<2) ){ memcpy(&buf[0], ((char *)inode->i_block)+offset, count); putbuf(ibuf); return count; } chat("read block [ "); cur_block = offset / xf->block_size; o = offset % xf->block_size; rcount = 0; while( count > 0 ){ baddr = bmap(f, cur_block++); if( !baddr ){ putbuf(ibuf); return -1; } buffer = getbuf(xf, baddr); if( !buffer ){ putbuf(ibuf); return -1; } chat("%d ", baddr); len = xf->block_size - o; if( len > count ) len = count; memcpy(&buf[rcount], &buffer->iobuf[o], len); rcount += len; count -= len; o = 0; putbuf(buffer); } chat("] ..."); inode->i_atime = time(0); dirtybuf(ibuf); putbuf(ibuf); return rcount; } long readdir(Xfile *f, void *vbuf, vlong offset, long count) { int off, i, len; long rcount; Xfs *xf = f->xf; Inode *inode, *tinode; int nblock; DirEntry *edir; Iobuf *buffer, *ibuf, *tbuf; Dir pdir; Xfile ft;