/* * ext2subs.c version 0.20 * * Some strategic functions come from linux/fs/ext2 * kernel sources written by Remy Card. * */ #include #include #include #include #include #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; uchar *buf; char name[EXT2_NAME_LEN+1]; unsigned int dirlen; int index; buf = vbuf; if (offset == 0) f->dirindex = 0; 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; ft = *f; chat("read block [ "); index = 0; for(i=0, rcount=0 ; (i < nblock) && (i < EXT2_NDIR_BLOCKS) ; i++){ buffer = getbuf(xf, inode->i_block[i]); if( !buffer ){ putbuf(ibuf); return -1; } chat("%d, ", buffer->addr); for(off=0 ; off < xf->block_size ; ){ edir = (DirEntry *)(buffer->iobuf + off); off += edir->rec_len; if( (edir->name[0] == '.' ) && (edir->name_len == 1)) continue; if(edir->name[0] == '.' && edir->name[1] == '.' && edir->name_len == 2) continue; if( edir->inode == 0 ) /* for lost+found dir ... */ continue; if( index++ < f->dirindex ) continue; if( get_inode(&ft, edir->inode) < 0 ){ chat("can't find ino no %d ] ...", edir->inode); error: putbuf(buffer); putbuf(ibuf); return -1; } tbuf = getbuf(xf, ft.bufaddr); if( !tbuf ) goto error; tinode = ((Inode *)tbuf->iobuf) + ft.bufoffset; memset(&pdir, 0, sizeof(Dir)); /* fill plan9 dir struct */ pdir.name = name; len = (edir->name_len < EXT2_NAME_LEN) ? edir->name_len : EXT2_NAME_LEN; strncpy(pdir.name, edir->name, len); pdir.name[len] = 0; // chat("name %s len %d\n", pdir.name, edir->name_len); pdir.uid = mapuid(tinode->i_uid); pdir.gid = mapgid(tinode->i_gid); pdir.qid.path = edir->inode; pdir.mode = tinode->i_mode; if( edir->inode == EXT2_ROOT_INODE ) pdir.qid.path = f->xf->rootqid.path; else if( S_ISDIR( tinode->i_mode) ) pdir.qid.type |= QTDIR; if( pdir.qid.type & QTDIR ) pdir.mode |= DMDIR; pdir.length = tinode->i_size; pdir.atime = tinode->i_atime; pdir.mtime = tinode->i_mtime; putbuf(tbuf); dirlen = convD2M(&pdir, &buf[rcount], count-rcount); if ( dirlen <= BIT16SZ ) { chat("] ..."); putbuf(buffer); putbuf(ibuf); return rcount; } rcount += dirlen; f->dirindex++; } putbuf(buffer); } chat("] ..."); putbuf(ibuf); return rcount; } int bmap( Xfile *f, int block ) { Xfs *xf = f->xf; Inode *inode; Iobuf *buf, *ibuf; int addr; int addr_per_block = xf->addr_per_block; int addr_per_block_bits = ffz(~addr_per_block); if(block < 0) { chat("bmap() block < 0 ..."); return 0; } if(block >= EXT2_NDIR_BLOCKS + addr_per_block + (1 << (addr_per_block_bits * 2)) + ((1 << (addr_per_block_bits * 2)) << addr_per_block_bits)) { chat("bmap() block > big..."); return 0; } ibuf = getbuf(xf, f->bufaddr); if( !ibuf ) return 0; inode = ((Inode *)ibuf->iobuf) + f->bufoffset; /* direct blocks */ if(block < EXT2_NDIR_BLOCKS){ putbuf(ibuf); return inode->i_block[block]; } block -= EXT2_NDIR_BLOCKS; /* indirect blocks*/ if(block < addr_per_block) { addr = inode->i_block[EXT2_IND_BLOCK]; if (!addr) goto error; buf = getbuf(xf, addr); if( !buf ) goto error; addr = *(((uint *)buf->iobuf) + block); putbuf(buf); putbuf(ibuf); return addr; } block -= addr_per_block; /* double indirect blocks */ if(block < (1 << (addr_per_block_bits * 2))) { addr = inode->i_block[EXT2_DIND_BLOCK]; if (!addr) goto error; buf = getbuf(xf, addr); if( !buf ) goto error; addr = *(((uint *)buf->iobuf) + (block >> addr_per_block_bits)); putbuf(buf); buf = getbuf(xf, addr); if( !buf ) goto error; addr = *(((uint *)buf->iobuf) + (block & (addr_per_block - 1))); putbuf(buf); putbuf(ibuf); return addr; } block -= (1 << (addr_per_block_bits * 2)); /* triple indirect blocks */ addr = inode->i_block[EXT2_TIND_BLOCK]; if(!addr) goto error; buf = getbuf(xf, addr); if( !buf ) goto error; addr = *(((uint *)buf->iobuf) + (block >> (addr_per_block_bits * 2))); putbuf(buf); if(!addr) goto error; buf = getbuf(xf, addr); if( !buf ) goto error; addr = *(((uint *)buf->iobuf) + ((block >> addr_per_block_bits) & (addr_per_block - 1))); putbuf(buf); if(!addr) goto error; buf = getbuf(xf, addr); if( !buf ) goto error; addr = *(((uint *)buf->iobuf) + (block & (addr_per_block - 1))); putbuf(buf); putbuf(ibuf); return addr; error: putbuf(ibuf); return 0; } long writefile(Xfile *f, void *vbuf, vlong offset, long count) { Xfs *xf = f->xf; Inode *inode; Iobuf *buffer, *ibuf; long w; int len, o, cur_block, baddr; char *buf; buf = vbuf; ibuf = getbuf(xf, f->bufaddr); if( !ibuf ) return -1; inode = ((Inode *)ibuf->iobuf) + f->bufoffset; chat("write block [ "); cur_block = offset / xf->block_size; o = offset % xf->block_size; w = 0; while( count > 0 ){ baddr = getblk(f, cur_block++); if( baddr <= 0 ) goto end; buffer = getbuf(xf, baddr); if( !buffer ) goto end; chat("%d ", baddr); len = xf->block_size - o; if( len > count ) len = count; memcpy(&buffer->iobuf[o], &buf[w], len); dirtybuf(buffer); w += len; count -= len; o = 0; putbuf(buffer); } end: if( inode->i_size < offset + w ) inode->i_size = offset + w; inode->i_atime = inode->i_mtime = time(0); dirtybuf(ibuf); putbuf(ibuf); chat("]..."); if( errno ) return -1; return w; } int new_block( Xfile *f, int goal ) { Xfs *xf= f->xf; int group, block, baddr, k, redo; ulong lmap; char *p, *r; Iobuf *buf; Ext2 ed, es, eb; es = getext2(xf, EXT2_SUPER, 0); redo = 0; repeat: if( goal < es.u.sb->s_first_data_block || goal >= es.u.sb->s_blocks_count ) goal = es.u.sb->s_first_data_block; group = (goal - es.u.sb->s_first_data_block) / xf->blocks_per_group; ed = getext2(xf, EXT2_DESC, group); eb = getext2(xf, EXT2_BBLOCK, group); /* * First, test if goal block is free */ if( ed.u.gd->bg_free_blocks_count > 0 ){ block = (goal - es.u.sb->s_first_data_block) % xf->blocks_per_group; if( !test_bit(block, eb.u.bmp) ) goto got_block; if( block ){ /* * goal wasn't free ; search foward for a free * block within the next 32 blocks */ lmap = (((ulong *)eb.u.bmp)[block>>5]) >> ((block & 31) + 1); if( block < xf->blocks_per_group - 32 ) lmap |= (((ulong *)eb.u.bmp)[(block>>5)+1]) << ( 31-(block & 31) ); else lmap |= 0xffffffff << ( 31-(block & 31) ); if( lmap != 0xffffffffl ){ k = ffz(lmap) + 1; if( (block + k) < xf->blocks_per_group ){ block += k; goto got_block; } } } /* * Search in the remaider of the group */ p = eb.u.bmp + (block>>3); r = memscan(p, 0, (xf->blocks_per_group - block + 7) >>3); k = ( r - eb.u.bmp )<<3; if( k < xf->blocks_per_group ){ block = k; goto search_back; } k = find_next_zero_bit((unsigned long *)eb.u.bmp, xf->blocks_per_group>>3, block); if( k < xf->blocks_per_group ){ block = k; goto got_block; } } /* * Search the rest of groups */ putext2(ed); putext2(eb); for(k=0 ; k < xf->ngroups ; k++){ group++; if( group >= xf->ngroups ) group = 0; ed = getext2(xf, EXT2_DESC, group); if( ed.u.gd->bg_free_blocks_count > 0 ) break; putext2(ed); } if( redo && group == xf->ngroups-1 ){ putext2(ed); goto full; } if( k >=xf->ngroups ){ /* * All groups are full or * we have retry (because the last block) and all other * groups are also full. */ full: chat("no free blocks ..."); putext2(es); errno = Enospace; return 0; } eb = getext2(xf, EXT2_BBLOCK, group); r = memscan(eb.u.bmp, 0, xf->blocks_per_group>>3); block = (r - eb.u.bmp) <<3; if( block < xf->blocks_per_group ) goto search_back; else block = find_first_zero_bit((ulong *)eb.u.bmp, xf->blocks_per_group>>3); if( block >= xf->blocks_per_group ){ chat("Free block count courupted for block group %d...", group); putext2(ed); putext2(eb); putext2(es); errno = Ecorrupt; return 0; } search_back: /* * A free byte was found in the block. Now search backwards up * to 7 bits to find the start of this group of free block. */ for(k=0 ; k < 7 && block > 0 && !test_bit(block-1, eb.u.bmp) ; k++, block--); got_block: baddr = block + (group * xf->blocks_per_group) + es.u.sb->s_first_data_block; if( baddr == ed.u.gd->bg_block_bitmap || baddr == ed.u.gd->bg_inode_bitmap ){ chat("Allocating block in system zone..."); putext2(ed); putext2(eb); putext2(es); errno = Eintern; return 0; } if( set_bit(block, eb.u.bmp) ){ chat("bit already set (%d)...", block); putext2(ed); putext2(eb); putext2(es); errno = Ecorrupt; return 0; } dirtyext2(eb); if( baddr >= es.u.sb->s_blocks_count ){ chat("block >= blocks count..."); errno = Eintern; error: clear_bit(block, eb.u.bmp); putext2(eb); putext2(ed); putext2(es); return 0; } buf = getbuf(xf, baddr); if( !buf ){ if( !redo ){ /* * It's perhaps the last block of the disk and * it can't be acceded because the last sector. * Therefore, we try one more time with goal at 0 * to force scanning all groups. */ clear_bit(block, eb.u.bmp); putext2(eb); putext2(ed); goal = 0; errno = 0; redo++; goto repeat; } goto error; } memset(&buf->iobuf[0], 0, xf->block_size); dirtybuf(buf); putbuf(buf); es.u.sb->s_free_blocks_count--; dirtyext2(es); ed.u.gd->bg_free_blocks_count--; dirtyext2(ed); putext2(eb); putext2(ed); putext2(es); chat("new "); return baddr; } int getblk(Xfile *f, int block) { Xfs *xf = f->xf; int baddr; int addr_per_block = xf->addr_per_block; if (block < 0) { chat("getblk() block < 0 ..."); return 0; } if(block > EXT2_NDIR_BLOCKS + addr_per_block + addr_per_block * addr_per_block + addr_per_block * addr_per_block * addr_per_block ){ chat("getblk() block > big..."); errno = Eintern; return 0; } if( block < EXT2_NDIR_BLOCKS ) return inode_getblk(f, block); block -= EXT2_NDIR_BLOCKS; if( block < addr_per_block ){ baddr = inode_getblk(f, EXT2_IND_BLOCK); baddr = block_getblk(f, baddr, block); return baddr; } block -= addr_per_block; if( block < addr_per_block * addr_per_block ){ baddr = inode_getblk(f, EXT2_DIND_BLOCK); baddr = block_getblk(f, baddr, block / addr_per_block); baddr = block_getblk(f, baddr, block & ( addr_per_block-1)); return baddr; } block -= addr_per_block * addr_per_block; baddr = inode_getblk(f, EXT2_TIND_BLOCK); baddr = block_getblk(f, baddr, block / (addr_per_block * addr_per_block)); baddr = block_getblk(f, baddr, (block / addr_per_block) & ( addr_per_block-1)); return block_getblk(f, baddr, block & ( addr_per_block-1)); } int block_getblk(Xfile *f, int rb, int nr) { Xfs *xf = f->xf; Inode *inode; int tmp, goal = 0; int blocks = xf->block_size / 512; Iobuf *buf, *ibuf; uint *p; Ext2 es; if( !rb ) return 0; buf = getbuf(xf, rb); if( !buf ) return 0; p = (uint *)(buf->iobuf) + nr; if( *p ){ tmp = *p; putbuf(buf); return tmp; } for(tmp=nr - 1 ; tmp >= 0 ; tmp--){ if( ((uint *)(buf->iobuf))[tmp] ){ goal = ((uint *)(buf->iobuf))[tmp]; break; } } if( !goal ){ es = getext2(xf, EXT2_SUPER, 0); goal = (((f->inbr -1) / xf->inodes_per_group) * xf->blocks_per_group) + es.u.sb->s_first_data_block; putext2(es); } tmp = new_block(f, goal); if( !tmp ){ putbuf(buf); return 0; } *p = tmp; dirtybuf(buf); putbuf(buf); ibuf = getbuf(xf, f->bufaddr); if( !ibuf ) return -1; inode = ((Inode *)ibuf->iobuf) + f->bufoffset; inode->i_blocks += blocks; dirtybuf(ibuf); putbuf(ibuf); return tmp; } int inode_getblk(Xfile *f, int block) { Xfs *xf = f->xf; Inode *inode; Iobuf *ibuf; int tmp, goal = 0; int blocks = xf->block_size / 512; Ext2 es; ibuf = getbuf(xf, f->bufaddr); if( !ibuf ) return -1; inode = ((Inode *)ibuf->iobuf) + f->bufoffset; if( inode->i_block[block] ){ putbuf(ibuf); return inode->i_block[block]; } for(tmp=block - 1 ; tmp >= 0 ; tmp--){ if( inode->i_block[tmp] ){ goal = inode->i_block[tmp]; break; } } if( !goal ){ es = getext2(xf, EXT2_SUPER, 0); goal = (((f->inbr -1) / xf->inodes_per_group) * xf->blocks_per_group) + es.u.sb->s_first_data_block; putext2(es); } tmp = new_block(f, goal); if( !tmp ){ putbuf(ibuf); return 0; } inode->i_block[block] = tmp; inode->i_blocks += blocks; dirtybuf(ibuf); putbuf(ibuf); return tmp; } int new_inode(Xfile *f, int mode) { Xfs *xf = f->xf; Inode *inode, *finode; Iobuf *buf, *ibuf; int ave,group, i, j; Ext2 ed, es, eb; group = -1; es = getext2(xf, EXT2_SUPER, 0); if( S_ISDIR(mode) ){ /* create directory inode */ ave = es.u.sb->s_free_inodes_count / xf->ngroups; for(i=0 ; i < xf->ngroups ; i++){ ed = getext2(xf, EXT2_DESC, i); if( ed.u.gd->bg_free_inodes_count && ed.u.gd->bg_free_inodes_count >= ave ){ if( group<0 || ed.u.gd->bg_free_inodes_count > ed.u.gd->bg_free_inodes_count ) group = i; } putext2(ed); } }else{ /* create file inode */ /* Try to put inode in its parent directory */ i = (f->inbr -1) / xf->inodes_per_group; ed = getext2(xf, EXT2_DESC, i); if( ed.u.gd->bg_free_inodes_count ){ group = i; putext2(ed); }else{ /* * Use a quadratic hash to find a group whith * a free inode */ putext2(ed); for( j=1 ; j < xf->ngroups ; j <<= 1){ i += j; if( i >= xf->ngroups ) i -= xf->ngroups; ed = getext2(xf, EXT2_DESC, i); if( ed.u.gd->bg_free_inodes_count ){ group = i; putext2(ed); break; } putext2(ed); } } if( group < 0 ){ /* try a linear search */ i = ((f->inbr -1) / xf->inodes_per_group) + 1; for(j=2 ; j < xf->ngroups ; j++){ if( ++i >= xf->ngroups ) i = 0; ed = getext2(xf, EXT2_DESC, i); if( ed.u.gd->bg_free_inodes_count ){ group = i; putext2(ed); break; } putext2(ed); } } } if( group < 0 ){ chat("group < 0..."); putext2(es); return 0; } ed = getext2(xf, EXT2_DESC, group); eb = getext2(xf, EXT2_BINODE, group); if( (j = find_first_zero_bit(eb.u.bmp, xf->inodes_per_group>>3)) < xf->inodes_per_group){ if( set_bit(j, eb.u.bmp) ){ chat("inode %d of group %d is already allocated...", j, group); putext2(ed); putext2(eb); putext2(es); errno = Ecorrupt; return 0; } dirtyext2(eb); }else if( ed.u.gd->bg_free_inodes_count != 0 ){ chat("free inodes count corrupted for group %d...", group); putext2(ed); putext2(eb); putext2(es); errno = Ecorrupt; return 0; } i = j; j += group * xf->inodes_per_group + 1; if( j < EXT2_FIRST_INO || j >= es.u.sb->s_inodes_count ){ chat("reserved inode or inode > inodes count..."); errno = Ecorrupt; error: clear_bit(i, eb.u.bmp); putext2(eb); putext2(ed); putext2(es); return 0; } buf = getbuf(xf, ed.u.gd->bg_inode_table + (((j-1) % xf->inodes_per_group) / xf->inodes_per_block)); if( !buf ) goto error; inode = ((struct Inode *) buf->iobuf) + ((j-1) % xf->inodes_per_block); memset(inode, 0, sizeof(Inode)); inode->i_mode = mode; inode->i_links_count = 1; inode->i_uid = DEFAULT_UID; inode->i_gid = DEFAULT_GID; inode->i_mtime = inode->i_atime = inode->i_ctime = time(0); dirtybuf(buf); ibuf = getbuf(xf, f->bufaddr); if( !ibuf ){ putbuf(buf); goto error; } finode = ((Inode *)ibuf->iobuf) + f->bufoffset; inode->i_flags = finode->i_flags; inode->i_uid = finode->i_uid; inode->i_gid = finode->i_gid; dirtybuf(ibuf); putbuf(ibuf); putbuf(buf); ed.u.gd->bg_free_inodes_count--; if( S_ISDIR(mode) ) ed.u.gd->bg_used_dirs_count++; dirtyext2(ed); es.u.sb->s_free_inodes_count--; dirtyext2(es); putext2(eb); putext2(ed); putext2(es); return j; } int create_file(Xfile *fdir, char *name, int mode) { int inr; inr = new_inode(fdir, mode); if( !inr ){ chat("create one new inode failed..."); return -1; } if( add_entry(fdir, name, inr) < 0 ){ chat("add entry failed..."); free_inode(fdir->xf, inr); return -1; } return inr; } void free_inode( Xfs *xf, int inr) { Inode *inode; ulong b, bg; Iobuf *buf; Ext2 ed, es, eb; bg = (inr -1) / xf->inodes_per_group; b = (inr -1) % xf->inodes_per_group; ed = getext2(xf, EXT2_DESC, bg); buf = getbuf(xf, ed.u.gd->bg_inode_table + (b / xf->inodes_per_block)); if( !buf ){ putext2(ed); return; } inode = ((struct Inode *) buf->iobuf) + ((inr-1) % xf->inodes_per_block); if( S_ISDIR(inode->i_mode) ) ed.u.gd->bg_used_dirs_count--; memset(inode, 0, sizeof(Inode)); inode->i_dtime = time(0); dirtybuf(buf); putbuf(buf); ed.u.gd->bg_free_inodes_count++; dirtyext2(ed); putext2(ed); eb = getext2(xf, EXT2_BINODE, bg); clear_bit(b, eb.u.bmp); dirtyext2(eb); putext2(eb); es = getext2(xf, EXT2_SUPER, 0); es.u.sb->s_free_inodes_count++; dirtyext2(es); putext2(es); } int create_dir(Xfile *fdir, char *name, int mode) { Xfs *xf = fdir->xf; DirEntry *de; Inode *inode; Iobuf *buf, *ibuf; Xfile tf; int inr, baddr; inr = new_inode(fdir, mode); if( inr == 0 ){ chat("create one new inode failed..."); return -1; } if( add_entry(fdir, name, inr) < 0 ){ chat("add entry failed..."); free_inode(fdir->xf, inr); return -1; } /* create the empty dir */ tf = *fdir; if( get_inode(&tf, inr) < 0 ){ chat("can't get inode %d...", inr); free_inode(fdir->xf, inr); return -1; } ibuf = getbuf(xf, tf.bufaddr); if( !ibuf ){ free_inode(fdir->xf, inr); return -1; } inode = ((Inode *)ibuf->iobuf) + tf.bufoffset; baddr = inode_getblk(&tf, 0); if( !baddr ){ putbuf(ibuf); ibuf = getbuf(xf, fdir->bufaddr); if( !ibuf ){ free_inode(fdir->xf, inr); return -1; } inode = ((Inode *)ibuf->iobuf) + fdir->bufoffset; delete_entry(fdir->xf, inode, inr); putbuf(ibuf); free_inode(fdir->xf, inr); return -1; } inode->i_size = xf->block_size; buf = getbuf(xf, baddr); de = (DirEntry *)buf->iobuf; de->inode = inr; de->name_len = 1; de->rec_len = DIR_REC_LEN(de->name_len); strcpy(de->name, "."); de = (DirEntry *)( (char *)de + de->rec_len); de->inode = fdir->inbr; de->name_len = 2; de->rec_len = xf->block_size - DIR_REC_LEN(1); strcpy(de->name, ".."); dirtybuf(buf); putbuf(buf); inode->i_links_count = 2; dirtybuf(ibuf); putbuf(ibuf); ibuf = getbuf(xf, fdir->bufaddr); if( !ibuf ) return -1; inode = ((Inode *)ibuf->iobuf) + fdir->bufoffset; inode->i_links_count++; dirtybuf(ibuf); putbuf(ibuf); return inr; } int add_entry(Xfile *f, char *name, int inr) { Xfs *xf = f->xf; DirEntry *de, *de1; int offset, baddr; int rec_len, cur_block; int namelen = strlen(name); Inode *inode; Iobuf *buf, *ibuf; ibuf = getbuf(xf, f->bufaddr); if( !ibuf ) return -1; inode = ((Inode *)ibuf->iobuf) + f->bufoffset; if( inode->i_size == 0 ){ chat("add_entry() no entry !!!..."); putbuf(ibuf); return -1; } cur_block = offset = 0; rec_len = DIR_REC_LEN(namelen); buf = getbuf(xf, inode->i_block[cur_block++]); if( !buf ){ putbuf(ibuf); return -1; } de = (DirEntry *)buf->iobuf; for(;;){ if( ((char *)de) >= (xf->block_size + buf->iobuf) ){ putbuf(buf); if( cur_block >= EXT2_NDIR_BLOCKS ){ errno = Enospace; putbuf(ibuf); return -1; } if( (baddr = inode_getblk(f, cur_block++)) == 0 ){ putbuf(ibuf); return -1; } buf = getbuf(xf, baddr); if( !buf ){ putbuf(ibuf); return -1; } if( inode->i_size <= offset ){ de = (DirEntry *)buf->iobuf; de->inode = 0; de->rec_len = xf->block_size; dirtybuf(buf); inode->i_size = offset + xf->block_size; dirtybuf(ibuf); }else{ de = (DirEntry *)buf->iobuf; } } if( de->inode != 0 && de->name_len == namelen && !strncmp(name, de->name, namelen) ){ errno = Eexist; putbuf(ibuf); putbuf(buf); return -1; } offset += de->rec_len; if( (de->inode == 0 && de->rec_len >= rec_len) || (de->rec_len >= DIR_REC_LEN(de->name_len) + rec_len) ){ if( de->inode ){ de1 = (DirEntry *) ((char *)de + DIR_REC_LEN(de->name_len)); de1->rec_len = de->rec_len - DIR_REC_LEN(de->name_len); de->rec_len = DIR_REC_LEN(de->name_len); de = de1; } de->inode = inr; de->name_len = namelen; memcpy(de->name, name, namelen); dirtybuf(buf); putbuf(buf); inode->i_mtime = inode->i_ctime = time(0); dirtybuf(ibuf); putbuf(ibuf); return 0; } de = (DirEntry *)((char *)de + de->rec_len); } /* not reached */ } int unlink( Xfile *file ) { Xfs *xf = file->xf; Inode *dir; int bg, b; Inode *inode; Iobuf *buf, *ibuf; Ext2 ed, es, eb; if( S_ISDIR(getmode(file)) && !empty_dir(file) ){ chat("non empty directory..."); errno = Eperm; return -1; } es = getext2(xf, EXT2_SUPER, 0); /* get dir inode */ if( file->pinbr >= es.u.sb->s_inodes_count ){ chat("inode number %d is too big...", file->pinbr); putext2(es); errno = Eintern; return -1; } bg = (file->pinbr - 1) / xf->inodes_per_group; if( bg >= xf->ngroups ){ chat("block group (%d) > groups count...", bg); putext2(es); errno = Eintern; return -1; } ed = getext2(xf, EXT2_DESC, bg); b = ed.u.gd->bg_inode_table + (((file->pinbr-1) % xf->inodes_per_group) / xf->inodes_per_block); putext2(ed); buf = getbuf(xf, b); if( !buf ){ putext2(es); return -1; } dir = ((struct Inode *) buf->iobuf) + ((file->pinbr-1) % xf->inodes_per_block); /* Clean dir entry */ if( delete_entry(xf, dir, file->inbr) < 0 ){ putbuf(buf); putext2(es); return -1; } if( S_ISDIR(getmode(file)) ){ dir->i_links_count--; dirtybuf(buf); } putbuf(buf); /* clean blocks */ ibuf = getbuf(xf, file->bufaddr); if( !ibuf ){ putext2(es); return -1; } inode = ((Inode *)ibuf->iobuf) + file->bufoffset; if( !S_ISLNK(getmode(file)) || (S_ISLNK(getmode(file)) && (inode->i_size > EXT2_N_BLOCKS<<2)) ) if( free_block_inode(file) < 0 ){ chat("error while freeing blocks..."); putext2(es); putbuf(ibuf); return -1; } /* clean inode */ bg = (file->inbr -1) / xf->inodes_per_group; b = (file->inbr -1) % xf->inodes_per_group; eb = getext2(xf, EXT2_BINODE, bg); clear_bit(b, eb.u.bmp); dirtyext2(eb); putext2(eb); inode->i_dtime = time(0); inode->i_links_count--; if( S_ISDIR(getmode(file)) ) inode->i_links_count = 0; es.u.sb->s_free_inodes_count++; dirtyext2(es); putext2(es); ed = getext2(xf, EXT2_DESC, bg); ed.u.gd->bg_free_inodes_count++; if( S_ISDIR(getmode(file)) ) ed.u.gd->bg_used_dirs_count--; dirtyext2(ed); putext2(ed); dirtybuf(ibuf); putbuf(ibuf); return 1; } int empty_dir(Xfile *dir) { Xfs *xf = dir->xf; int nblock; uint offset, i,count; DirEntry *de; Inode *inode; Iobuf *buf, *ibuf; if( !S_ISDIR(getmode(dir)) ) return 0; ibuf = getbuf(xf, dir->bufaddr); if( !ibuf ) return -1; inode = ((Inode *)ibuf->iobuf) + dir->bufoffset; nblock = (inode->i_blocks * 512) / xf->block_size; for(i=0, count=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 ; ){ de = (DirEntry *)(buf->iobuf + offset); if(de->inode) count++; offset += de->rec_len; } putbuf(buf); if( count > 2 ){ putbuf(ibuf); return 0; } } putbuf(ibuf); return 1; } int free_block_inode(Xfile *file) { Xfs *xf = file->xf; int i, j, k; ulong b, *y, *z; uint *x; int naddr; Inode *inode; Iobuf *buf, *buf1, *buf2, *ibuf; ibuf = getbuf(xf, file->bufaddr); if( !ibuf ) return -1; inode = ((Inode *)ibuf->iobuf) + file->bufoffset; for(i=0 ; i < EXT2_IND_BLOCK ; i++){ x = inode->i_block + i; if( *x == 0 ){ putbuf(ibuf); return 0; } free_block(xf, *x); } naddr = xf->addr_per_block; /* indirect blocks */ if( (b=inode->i_block[EXT2_IND_BLOCK]) ){ buf = getbuf(xf, b); if( !buf ){ putbuf(ibuf); return -1; } for(i=0 ; i < naddr ; i++){ x = ((uint *)buf->iobuf) + i; if( *x == 0 ) break; free_block(xf, *x); } free_block(xf, b); putbuf(buf); } /* double indirect block */ if( (b=inode->i_block[EXT2_DIND_BLOCK]) ){ buf = getbuf(xf, b); if( !buf ){ putbuf(ibuf); return -1; } for(i=0 ; i < naddr ; i++){ x = ((uint *)buf->iobuf) + i; if( *x== 0 ) break; buf1 = getbuf(xf, *x); if( !buf1 ){ putbuf(buf); putbuf(ibuf); return -1; } for(j=0 ; j < naddr ; j++){ y = ((ulong *)buf1->iobuf) + j; if( *y == 0 ) break; free_block(xf, *y); } free_block(xf, *x); putbuf(buf1); } free_block(xf, b); putbuf(buf); } /* triple indirect block */ if( (b=inode->i_block[EXT2_TIND_BLOCK]) ){ buf = getbuf(xf, b); if( !buf ){ putbuf(ibuf); return -1; } for(i=0 ; i < naddr ; i++){ x = ((uint *)buf->iobuf) + i; if( *x == 0 ) break; buf1 = getbuf(xf, *x); if( !buf1 ){ putbuf(buf); putbuf(ibuf); return -1; } for(j=0 ; j < naddr ; j++){ y = ((ulong *)buf1->iobuf) + j; if( *y == 0 ) break; buf2 = getbuf(xf, *y); if( !buf2 ){ putbuf(buf); putbuf(buf1); putbuf(ibuf); return -1; } for(k=0 ; k < naddr ; k++){ z = ((ulong *)buf2->iobuf) + k; if( *z == 0 ) break; free_block(xf, *z); } free_block(xf, *y); putbuf(buf2); } free_block(xf, *x); putbuf(buf1); } free_block(xf, b); putbuf(buf); } putbuf(ibuf); return 0; } void free_block( Xfs *xf, ulong block ) { ulong bg; Ext2 ed, es, eb; es = getext2(xf, EXT2_SUPER, 0); bg = (block - es.u.sb->s_first_data_block) / xf->blocks_per_group; block = (block - es.u.sb->s_first_data_block) % xf->blocks_per_group; eb = getext2(xf, EXT2_BBLOCK, bg); clear_bit(block, eb.u.bmp); dirtyext2(eb); putext2(eb); es.u.sb->s_free_blocks_count++; dirtyext2(es); putext2(es); ed = getext2(xf, EXT2_DESC, bg); ed.u.gd->bg_free_blocks_count++; dirtyext2(ed); putext2(ed); } int delete_entry(Xfs *xf, Inode *inode, int inbr) { int nblock = (inode->i_blocks * 512) / xf->block_size; uint offset, i; DirEntry *de, *pde; Iobuf *buf; if( !S_ISDIR(inode->i_mode) ) return -1; for(i=0 ; (i < nblock) && (i < EXT2_NDIR_BLOCKS) ; i++){ buf = getbuf(xf, inode->i_block[i]); if( !buf ) return -1; pde = 0; for(offset=0 ; offset < xf->block_size ; ){ de = (DirEntry *)(buf->iobuf + offset); if( de->inode == inbr ){ if( pde ) pde->rec_len += de->rec_len; de->inode = 0; dirtybuf(buf); putbuf(buf); return 1; } offset += de->rec_len; pde = de; } putbuf(buf); } errno = Enonexist; return -1; } int truncfile(Xfile *f) { Inode *inode; Iobuf *ibuf; chat("trunc(fid=%d) ...", f->fid); ibuf = getbuf(f->xf, f->bufaddr); if( !ibuf ) return -1; inode = ((Inode *)ibuf->iobuf) + f->bufoffset; if( free_block_inode(f) < 0 ){ chat("error while freeing blocks..."); putbuf(ibuf); return -1; } inode->i_atime = inode->i_mtime = time(0); inode->i_blocks = 0; inode->i_size = 0; memset(inode->i_block, 0, EXT2_N_BLOCKS*sizeof(ulong)); dirtybuf(ibuf); putbuf(ibuf); chat("trunc ok..."); return 0; } long getmode(Xfile *f) { Iobuf *ibuf; long mode; ibuf = getbuf(f->xf, f->bufaddr); if( !ibuf ) return -1; mode = (((Inode *)ibuf->iobuf) + f->bufoffset)->i_mode; putbuf(ibuf); return mode; } void CleanSuper(Xfs *xf) { Ext2 es; es = getext2(xf, EXT2_SUPER, 0); es.u.sb->s_state = EXT2_VALID_FS; dirtyext2(es); putext2(es); } int test_bit(int i, void *data) { char *pt = (char *)data; return pt[i>>3] & (0x01 << (i&7)); } int set_bit(int i, void *data) { char *pt; if( test_bit(i, data) ) return 1; /* bit already set !!! */ pt = (char *)data; pt[i>>3] |= (0x01 << (i&7)); return 0; } int clear_bit(int i, void *data) { char *pt; if( !test_bit(i, data) ) return 1; /* bit already clear !!! */ pt = (char *)data; pt[i>>3] &= ~(0x01 << (i&7)); return 0; } void * memscan( void *data, int c, int count ) { char *pt = (char *)data; while( count ){ if( *pt == c ) return (void *)pt; count--; pt++; } return (void *)pt; } int find_first_zero_bit( void *data, int count /* in byte */) { char *pt = (char *)data; int n, i; n = 0; while( n < count ){ for(i=0 ; i < 8 ; i++) if( !(*pt & (0x01 << (i&7))) ) return (n<<3) + i; n++; pt++; } return n << 3; } int find_next_zero_bit( void *data, int count /* in byte */, int where) { char *pt = (((char *)data) + (where >> 3)); int n, i; n = where >> 3; i = where & 7; while( n < count ){ for(; i < 8 ; i++) if( !(*pt & (0x01 << (i&7))) ) return (n<<3) + i; n++; pt++; i=0; } return n << 3; } int ffz( int x ) { int c = 0; while( x&1 ){ c++; x >>= 1; } return c; }