/* This is a library of functions that allow user-level programs to * read and manipulate ext2 file systems. For convenience sake, * this library maintains a lot of state information in static * variables; therefore, it's not reentrant. We don't care for * our applications 8-) */ #include #include #include #include #include #include #include #include #include #define MAX_OPEN_FILES 8 int fd = -1; struct ext2_super_block sb; struct ext2_group_desc *gds; int ngroups = 0; int blocksize; /* Block size of this fs */ int directlim; /* Maximum direct blkno */ int ind1lim; /* Maximum single-indir blkno */ int ind2lim; /* Maximum double-indir blkno */ int ptrs_per_blk; /* ptrs/indirect block */ char filename[256]; int readonly; /* Is this FS read-only? */ int verbose = 0; int big_endian = 0; static void ext2_ifree(int ino); static void ext2_free_indirect(int indirect_blkno, int level); struct inode_table_entry { struct ext2_inode inode; int inumber; int free; unsigned short old_mode; } inode_table[MAX_OPEN_FILES]; /* Utility functions to byte-swap 16 and 32 bit quantities... */ unsigned short swap16 (unsigned short s) { return((unsigned short)( ((s << 8) & 0xff00) | ((s >> 8) & 0x00ff))); } unsigned int swap32 (unsigned int i) { return((unsigned int)( ((i << 24) & 0xff000000) | ((i << 8) & 0x00ff0000) | ((i >> 8) & 0x0000ff00) | ((i >> 24) & 0x000000ff)) ); } void ext2_swap_sb (struct ext2_super_block *sb) { sb->s_inodes_count = swap32(sb->s_inodes_count); sb->s_blocks_count = swap32(sb->s_blocks_count); sb->s_r_blocks_count = swap32(sb->s_r_blocks_count); sb->s_free_blocks_count = swap32(sb->s_free_blocks_count); sb->s_free_inodes_count = swap32(sb->s_free_inodes_count); sb->s_first_data_block = swap32(sb->s_first_data_block); sb->s_log_block_size = swap32(sb->s_log_block_size); sb->s_log_frag_size = swap32(sb->s_log_frag_size); sb->s_blocks_per_group = swap32(sb->s_blocks_per_group); sb->s_frags_per_group = swap32(sb->s_frags_per_group); sb->s_inodes_per_group = swap32(sb->s_inodes_per_group); sb->s_mtime = swap32(sb->s_mtime); sb->s_wtime = swap32(sb->s_wtime); sb->s_mnt_count = swap16(sb->s_mnt_count); sb->s_max_mnt_count = swap16(sb->s_max_mnt_count); sb->s_magic = swap16(sb->s_magic); sb->s_state = swap16(sb->s_state); sb->s_errors = swap16(sb->s_errors); sb->s_pad = swap16(sb->s_pad); sb->s_lastcheck = swap32(sb->s_lastcheck); sb->s_checkinterval = swap32(sb->s_checkinterval); } void ext2_swap_gd (struct ext2_group_desc *gd) { gd->bg_block_bitmap = swap32(gd->bg_block_bitmap); gd->bg_inode_bitmap = swap32(gd->bg_inode_bitmap); gd->bg_inode_table = swap32(gd->bg_inode_table); gd->bg_free_blocks_count = swap16(gd->bg_free_blocks_count); gd->bg_free_inodes_count = swap16(gd->bg_free_inodes_count); gd->bg_used_dirs_count = swap16(gd->bg_used_dirs_count); gd->bg_pad = swap16(gd->bg_pad); } void ext2_swap_inode (struct ext2_inode *ip) { int i; ip->i_mode = swap16(ip->i_mode); ip->i_uid = swap16(ip->i_uid); ip->i_size = swap32(ip->i_size); ip->i_atime = swap32(ip->i_atime); ip->i_ctime = swap32(ip->i_ctime); ip->i_mtime = swap32(ip->i_mtime); ip->i_dtime = swap32(ip->i_dtime); ip->i_gid = swap16(ip->i_gid); ip->i_links_count = swap16(ip->i_links_count); ip->i_blocks = swap32(ip->i_blocks); ip->i_flags = swap32(ip->i_flags); ip->i_reserved1 = swap32(ip->i_reserved1); for(i = 0; i < EXT2_N_BLOCKS; i++) { ip->i_block[i] = swap32(ip->i_block[i]); } ip->i_version = swap32(ip->i_version); ip->i_file_acl = swap32(ip->i_file_acl); ip->i_dir_acl = swap32(ip->i_dir_acl); ip->i_faddr = swap32(ip->i_faddr); ip->i_pad1 = swap16(ip->i_pad1); } /* Initialize an ext2 filesystem; this is sort-of the same idea as * "mounting" it. Read in the relevant control structures and * make them available to the user. Returns 0 if successful, -1 on * failure. */ int ext2_init (char * name, int access) { int i; /* Initialize the inode table */ for(i = 0; i < MAX_OPEN_FILES; i++) { inode_table[i].free = 1; inode_table[i].inumber = 0; } if((access != O_RDONLY) && (access != O_RDWR)) { fprintf(stderr, "ext2_init: Access must be O_RDONLY or O_RDWR, not %d\n", access); return(-1); } /* Open the device/file */ fd = open(name, access); if(fd < 0) { perror(filename); return(-1); } if(access == O_RDONLY) { readonly = 1; } /* Read in the first superblock */ lseek(fd, EXT2_MIN_BLOCK_SIZE, SEEK_SET); if(read(fd, &sb, sizeof(sb)) != sizeof(sb)) { perror("ext2 sb read"); close(fd); return(-1); } if((sb.s_magic != EXT2_SUPER_MAGIC) && (sb.s_magic != EXT2_SUPER_BIGMAGIC)) { fprintf(stderr, "ext2 bad magic 0x%x\n", sb.s_magic); close(fd); return(-1); } if(sb.s_magic == EXT2_SUPER_BIGMAGIC) { big_endian = 1; /* Byte-swap the fields in the superblock... */ ext2_swap_sb(&sb); } if(sb.s_first_data_block != 1) { fprintf(stderr, "Brain-damaged utils can't deal with a filesystem\nwhere s_first_data_block != 1.\nRe-initialize the filesystem\n"); close(fd); return(-1); } ngroups = (sb.s_blocks_count+sb.s_blocks_per_group-1)/sb.s_blocks_per_group; gds = (struct ext2_group_desc *) malloc((size_t)(ngroups * sizeof(struct ext2_group_desc))); /* Read in the group descriptors (immediately follows superblock) */ if ((size_t) read(fd, gds, ngroups * sizeof(struct ext2_group_desc)) != (ngroups * sizeof(struct ext2_group_desc))) { perror("ext2_init: group desc read error"); return(-1); } if(big_endian) { for(i = 0; i < ngroups; i++) { ext2_swap_gd(&(gds[i])); } } strcpy(filename, name); /* Calculate direct/indirect block limits for this file system * (blocksize dependent) */ blocksize = EXT2_BLOCK_SIZE(&sb); directlim = EXT2_NDIR_BLOCKS - 1; ptrs_per_blk = blocksize/sizeof(unsigned int); ind1lim = ptrs_per_blk + directlim; ind2lim = (ptrs_per_blk * ptrs_per_blk) + directlim; if(getenv("EXT2_VERBOSE")) { verbose = 1; } binit(fd, blocksize); if(verbose) { printf("Initialized filesystem %s\n", filename); printf(" %d blocks (%dKb), %d free (%dKb)\n", sb.s_blocks_count, (sb.s_blocks_count * blocksize)/1024, sb.s_free_blocks_count, (sb.s_free_blocks_count * blocksize)/1024); printf(" %d inodes, %d free\n", sb.s_inodes_count, sb.s_free_inodes_count); printf(" %d groups, %d blocks/group\n", ngroups, sb.s_blocks_per_group); } return(0); } int ext2_blocksize (void) { return blocksize; } int ext2_total_blocks (void) { return sb.s_blocks_count; } int ext2_free_blocks (void) { return sb.s_free_blocks_count; } int ext2_total_inodes (void) { return sb.s_inodes_count; } int ext2_free_inodes (void) { return sb.s_free_inodes_count; } /* Call this when we're all done with the file system. This will write * back any superblock and group changes to the file system. */ void ext2_close (void) { int i; int errors = 0; int blocks_per_group = sb.s_blocks_per_group; if(!readonly) { if(big_endian) { ext2_swap_sb(&sb); for(i = 0; i < ngroups; i++) { ext2_swap_gd(&(gds[i])); } } for(i = 0; i < ngroups; i++) { lseek(fd, ((i*blocks_per_group)+1)*blocksize, SEEK_SET); if(write(fd, &sb, sizeof(sb)) != sizeof(sb)) { perror("sb write"); errors = 1; } if ((size_t) write(fd, gds, ngroups*sizeof(struct ext2_group_desc)) != ngroups*sizeof(struct ext2_group_desc)) { perror("gds write"); errors = 1; } bflush(); } } close(fd); if(errors) { fprintf(stderr, "Errors encountered while updating %s\n", filename); fprintf(stderr, "e2fsck is STRONGLY recommended!\n"); } } /* Read the specified inode from the disk and return it to the user. * Returns NULL if the inode can't be read... */ struct ext2_inode * ext2_iget (int ino) { int i; struct ext2_inode * ip = NULL; struct inode_table_entry * itp = NULL; int group; int blkoffset; int byteoffset; char inobuf[EXT2_MAX_BLOCK_SIZE]; for(i = 0; i < MAX_OPEN_FILES; i++) { if(inode_table[i].free) { itp = &(inode_table[i]); ip = &(itp->inode); break; } } if(!ip) { fprintf(stderr, "ext2_iget: no free inodes\n"); return(NULL); } group = ino / sb.s_inodes_per_group; blkoffset = (gds[group].bg_inode_table * blocksize); byteoffset = ((ino-1) % sb.s_inodes_per_group) * sizeof(struct ext2_inode); blkoffset += ((byteoffset / blocksize) * blocksize); byteoffset = (byteoffset % blocksize); bread(blkoffset/blocksize, inobuf); memcpy(ip, &(inobuf[byteoffset]), sizeof(struct ext2_inode)); if(big_endian) { ext2_swap_inode(ip); } /* Yes, this is ugly, but it makes iput SOOO much easier 8-) */ itp->free = 0; itp->inumber = ino; itp->old_mode = ip->i_mode; return(ip); } /* Put the specified inode back on the disk where it came from. */ void ext2_iput (struct ext2_inode *ip) { int group; int blkoffset; int byteoffset; int ino; struct inode_table_entry *itp; char inobuf[EXT2_MAX_BLOCK_SIZE]; int inode_mode; itp = (struct inode_table_entry *)ip; ino = itp->inumber; if(ip->i_links_count == 0) { ext2_ifree(itp->inumber); } itp->inumber = 0; if(!readonly) { group = ino / sb.s_inodes_per_group; blkoffset = (gds[group].bg_inode_table * blocksize); byteoffset = ((ino-1) % sb.s_inodes_per_group) * sizeof(struct ext2_inode); blkoffset += (byteoffset / blocksize) * blocksize; byteoffset = byteoffset % blocksize; inode_mode = ip->i_mode; bread(blkoffset/blocksize, inobuf); if(big_endian) { ext2_swap_inode(ip); } memcpy(&(inobuf[byteoffset]), ip, sizeof(struct ext2_inode)); bwrite(blkoffset/blocksize, inobuf); if(S_ISDIR(itp->old_mode) && !S_ISDIR(inode_mode)) { /* We deleted a directory */ gds[group].bg_used_dirs_count--; } if(!S_ISDIR(itp->old_mode) && S_ISDIR(inode_mode)) { /* We created a directory */ gds[group].bg_used_dirs_count++; } } itp->free = 1; } #define BITS_PER_LONG (8*sizeof(int)) static int find_first_zero_bit (unsigned int * addr, unsigned size) { unsigned lwsize; unsigned int *ap = (unsigned int *)addr; unsigned int mask; unsigned int longword, bit; unsigned int lwval; if (!size) return 0; /* Convert "size" to a whole number of longwords... */ lwsize = (size + BITS_PER_LONG - 1) >> 5; for (longword = 0; longword < lwsize; longword++, ap++) { if(*ap != 0xffffffff) { lwval = big_endian ? swap32(*ap) : *ap; for (bit = 0, mask = 1; bit < BITS_PER_LONG; bit++, mask <<= 1) { if ((lwval & mask) == 0) { return (longword*BITS_PER_LONG) + bit; } } } } return size; } static void set_bit (unsigned int *addr, int bitno) { if(big_endian) { int lwval; lwval = swap32(addr[bitno/BITS_PER_LONG]); lwval |= (1 << (bitno % BITS_PER_LONG)); addr[bitno/BITS_PER_LONG] = swap32(lwval); } else { addr[bitno / BITS_PER_LONG] |= (1 << (bitno % BITS_PER_LONG)); } } static void clear_bit (unsigned int *addr, int bitno) { if(big_endian) { int lwval; lwval = swap32(addr[bitno/BITS_PER_LONG]); lwval &= ~((unsigned int)(1 << (bitno % BITS_PER_LONG))); addr[bitno/BITS_PER_LONG] = swap32(lwval); } else { addr[bitno / BITS_PER_LONG] &= ~((unsigned int)(1 << (bitno % BITS_PER_LONG))); } } /* Allocate a block from the file system. Brain-damaged implementation; * doesn't even TRY to do load-balancing among groups... just grabs the * first block it can find... */ int ext2_balloc (void) { unsigned int blk, blockmap[256]; int i; if(readonly) { fprintf(stderr, "ext2_balloc: readonly filesystem\n"); return(0); } for(i = 0; i < ngroups; i++) { if(gds[i].bg_free_blocks_count > 0) { bread(gds[i].bg_block_bitmap, blockmap); blk = find_first_zero_bit(blockmap, sb.s_blocks_per_group); if (blk == 0 || blk == sb.s_blocks_per_group) { fprintf(stderr, "group %d has %d blocks free but none in bitmap?\n", i, gds[i].bg_free_blocks_count); continue; } set_bit(blockmap, blk); bwrite(gds[i].bg_block_bitmap, blockmap); gds[i].bg_free_blocks_count--; sb.s_free_blocks_count--; blk = blk + (i*sb.s_blocks_per_group)+1; if(blk == 0) { fprintf(stderr, "ext2_balloc: blk == 0?\n"); } return(blk); } } if(verbose) { printf("ext2_balloc: can't find a free block\n"); } return(0); } /* Deallocate a block */ void ext2_bfree (int blk) { int i; unsigned int blockmap[256]; /* Find which group this block is in */ i = (blk-1) / sb.s_blocks_per_group; /* Read the block map */ bread(gds[i].bg_block_bitmap, blockmap); /* Clear the appropriate bit */ clear_bit(blockmap, (blk-1) % sb.s_blocks_per_group); /* Write the block map back out */ bwrite(gds[i].bg_block_bitmap, blockmap); /* Update free block counts. */ gds[i].bg_free_blocks_count++; sb.s_free_blocks_count++; } /* Allocate a contiguous range of blocks. This is used ONLY for * initializing the bootstrapper. It uses a simple-minded algorithm * that works best on a clean or nearly clean file system... we * chunk through the bitmap a longword at a time. Only if the whole * longword indicates free blocks do we use it. On a 32-bit system, * this means we allocate blocks only in units of 32. */ int ext2_contiguous_balloc (int nblocks) { int i, j; int firstlong, lastlong; int longs_needed; int longs_per_group; int blk; unsigned int blockmap[256]; if(readonly) { fprintf(stderr, "ext2_contiguous_balloc: readonly filesystem\n"); return(0); } /* Compute how many longwords we need to fulfill this request */ longs_needed = (nblocks + BITS_PER_LONG - 1) / BITS_PER_LONG; longs_per_group = sb.s_blocks_per_group/BITS_PER_LONG; for(i = 0; i < ngroups; i++) { /* Don't even bother if this group doesn't have enough blocks! */ if(gds[i].bg_free_blocks_count >= nblocks) { /* Get the block map. */ bread(gds[i].bg_block_bitmap, blockmap); /* Find a run of blocks */ firstlong = 0; do { for(; firstlong < longs_per_group; firstlong++) { if(blockmap[firstlong] == 0) break; } if(firstlong == longs_per_group) { /* No such thing in this group; try another! */ break; } for(lastlong = firstlong; lastlong < longs_per_group; lastlong++) { if(blockmap[lastlong] != 0) break; } if((lastlong-firstlong) < longs_needed) { firstlong = lastlong; } } while((lastlong-firstlong) < longs_needed); /* If we got all the way through the block map, * try another group. */ if(firstlong == longs_per_group) { continue; } /* If we get here, then we know that we have a run * that will fit our allocation. Allocate the *actual* * blocks that we need! */ blk = firstlong * BITS_PER_LONG; for(j = 0; j < nblocks; j++) { set_bit(blockmap, blk+j); } bwrite(gds[i].bg_block_bitmap, blockmap); gds[i].bg_free_blocks_count -= nblocks; sb.s_free_blocks_count -= nblocks; blk = blk + (i*sb.s_blocks_per_group)+1; if(verbose) { printf("ext2_contiguous_balloc: allocated %d blks @%d\n", nblocks, blk); } return(blk); } } if(verbose) { printf("ext2_contiguous_balloc: can't find %d contiguous free blocks\n", nblocks); } return(0); } /* Pre-allocate contiguous blocks to the specified inode. Note that the * DATA blocks must be contiguous; indirect blocks can come from anywhere. * This is for the benefit of the bootstrap loader. * If successful, this routine returns the block number of the first * data block of the file. Otherwise, it returns -1. */ int ext2_fill_contiguous (struct ext2_inode * ip, int nblocks) { int iblkno = 0; int firstblock; int i; unsigned int *lp = NULL; char blkbuf[EXT2_MAX_BLOCK_SIZE]; /* For simplicity's sake, we only allow single indirection * here. We shouldn't need more than this anyway! */ if(nblocks > ind1lim) { fprintf(stderr, "ext2_fill_contiguous: file size too big (%d); cannot exceed %d\n", nblocks, ind1lim); return(-1); } /* First, try to allocate the data blocks */ firstblock = ext2_contiguous_balloc(nblocks); if(firstblock == 0) { fprintf(stderr, "ext2_fill_contiguous: Cannot allocate %d contiguous blocks\n", nblocks); return(-1); } ip->i_blocks = nblocks * (blocksize/512); /* If we need the indirect block, then allocate it now. */ if(nblocks > directlim) { iblkno = ext2_balloc(); if(iblkno == 0) { /* Should rarely happen! */ fprintf(stderr, "ext2_fill_contiguous: cannot allocate indirect block\n"); for(i = 0; i < nblocks; i++) { ext2_bfree(i); } return(-1); } ip->i_blocks += (blocksize/512); /* Point to indirect block buffer, in case we need it! */ lp = (unsigned int *)blkbuf; for(i = 0; i < ptrs_per_blk; i++) { lp[i] = 0; } ip->i_block[EXT2_IND_BLOCK] = iblkno; } /* All set... let's roll! */ ip->i_size = nblocks * blocksize; for(i = 0; i < nblocks; i++) { if(i < EXT2_NDIR_BLOCKS) { ip->i_block[i] = firstblock+i; } else { *lp++ = big_endian ? swap32(firstblock+i) : firstblock+i; } } /* Write back the indirect block if necessary... */ if(iblkno) { bwrite(iblkno, blkbuf); } return(firstblock); } /* Write out a boot block for this file system. The caller * should have instantiated the block. */ void ext2_write_bootblock (char *bb) { bwrite(0, bb); } /* Allocate an inode from the file system. Brain-damaged implementation; * doesn't even TRY to do load-balancing among groups... just grabs the * first inode it can find... */ int ext2_ialloc (void) { unsigned int inodemap[256]; int i, ino; if(readonly) { return(0); } for(i = 0; i < ngroups; i++) { if(gds[i].bg_free_inodes_count > 4) { /* leave a few inodes in each group for slop... */ bread(gds[i].bg_inode_bitmap, inodemap); ino = find_first_zero_bit(inodemap, sb.s_inodes_per_group); if (ino == 0 || (unsigned) ino == sb.s_inodes_per_group) { fprintf(stderr, "group %d has %d inodes free but none in bitmap?\n", i, gds[i].bg_free_inodes_count); continue; } set_bit(inodemap, ino); bwrite(gds[i].bg_inode_bitmap, inodemap); gds[i].bg_free_inodes_count--; sb.s_free_inodes_count--; ino = ino + (i*sb.s_inodes_per_group) + 1; return ino; } } return 0; } /* Deallocate an inode */ static void ext2_ifree (int ino) { int i; unsigned int inodemap[256]; /* Find which group this inode is in */ i = (ino-1) / sb.s_inodes_per_group; /* Read the inode map */ bread(gds[i].bg_inode_bitmap, inodemap); /* Clear the appropriate bit */ clear_bit(inodemap, (ino-1) % sb.s_inodes_per_group); /* Write the inode map back out */ bwrite(gds[i].bg_inode_bitmap, inodemap); /* Update free inode counts. */ gds[i].bg_free_inodes_count++; sb.s_free_inodes_count++; } /* Map a block offset into a file into an absolute block number. * (traverse the indirect blocks if necessary). Note: Double-indirect * blocks allow us to map over 64Mb on a 1k file system. Therefore, for * our purposes, we will NOT bother with triple indirect blocks. * * The "allocate" argument is set if we want to *allocate* a block * and we don't already have one allocated. */ int ext2_blkno (struct ext2_inode *ip, int blkoff, int allocate) { unsigned int *lp; int blkno; int iblkno; int diblkno; char blkbuf[EXT2_MAX_BLOCK_SIZE]; if(allocate && readonly) { fprintf(stderr, "ext2_blkno: Cannot allocate on a readonly file system!\n"); return(0); } lp = (unsigned int *)blkbuf; /* If it's a direct block, it's easy! */ if(blkoff <= directlim) { if((ip->i_block[blkoff] == 0) && allocate) { ip->i_block[blkoff] = ext2_balloc(); if(verbose) { printf("Allocated data block %d\n", ip->i_block[blkoff]); } ip->i_blocks += (blocksize / 512); } return(ip->i_block[blkoff]); } /* Is it a single-indirect? */ if(blkoff <= ind1lim) { iblkno = ip->i_block[EXT2_IND_BLOCK]; if((iblkno == 0) && allocate) { /* No indirect block and we need one, so we allocate * one, zero it, and write it out. */ iblkno = ext2_balloc(); if(iblkno == 0) { return(0); } ip->i_block[EXT2_IND_BLOCK] = iblkno; if(verbose) { printf("Allocated indirect block %d\n", iblkno); } ip->i_blocks += (blocksize / 512); memset(blkbuf, 0, blocksize); bwrite(iblkno, blkbuf); } if(iblkno == 0) { return(0); } /* Read the indirect block */ bread(iblkno, blkbuf); if(big_endian) { blkno = swap32(lp[blkoff-(directlim+1)]); } else { blkno = lp[blkoff-(directlim+1)]; } if((blkno == 0) && allocate) { /* No block allocated but we need one. */ if(big_endian) { blkno = ext2_balloc(); lp[blkoff-(directlim+1)] = swap32(blkno); } else { blkno = lp[blkoff-(directlim+1)] = ext2_balloc(); } if(blkno == 0) { return(0); } ip->i_blocks += (blocksize / 512); if(verbose) { printf("Allocated data block %d\n", blkno); } bwrite(iblkno, blkbuf); } return(blkno); } /* Is it a double-indirect? */ if(blkoff <= ind2lim) { /* Find the double-indirect block */ diblkno = ip->i_block[EXT2_DIND_BLOCK]; if((diblkno == 0) && allocate) { /* No double-indirect block and we need one. Allocate one, * fill it with zeros, and write it out. */ diblkno = ext2_balloc(); if(diblkno == 0) { return(0); } ip->i_blocks += (blocksize / 512); if(verbose) { printf("Allocated double-indirect block %d\n", diblkno); } memset(blkbuf, 0, blocksize); bwrite(diblkno, blkbuf); ip->i_block[EXT2_DIND_BLOCK] = diblkno; } if(diblkno == 0) { return(0); } /* Read in the double-indirect block */ bread(diblkno, blkbuf); /* Find the single-indirect block pointer ... */ iblkno = lp[(blkoff - (ind1lim+1)) / ptrs_per_blk]; if(big_endian) { iblkno = swap32(iblkno); } if((iblkno == 0) && allocate) { /* No indirect block and we need one, so we allocate * one, zero it, and write it out. */ iblkno = ext2_balloc(); if(iblkno == 0) { return(0); } ip->i_blocks += (blocksize / 512); if(verbose) { printf("Allocated single-indirect block %d\n", iblkno); } lp[(blkoff-(ind1lim+1)) / ptrs_per_blk] = big_endian ? swap32(iblkno) : iblkno; bwrite(diblkno, blkbuf); memset(blkbuf, 0, blocksize); bwrite(iblkno, blkbuf); } if(iblkno == 0) { return(0); } /* Read the indirect block */ bread(iblkno, blkbuf); /* Find the block itself. */ blkno = lp[(blkoff-(ind1lim+1)) % ptrs_per_blk]; if(big_endian) { blkno = swap32(blkno); } if((blkno == 0) && allocate) { /* No block allocated but we need one. */ if(big_endian) { blkno = ext2_balloc(); lp[(blkoff-(ind1lim+1)) % ptrs_per_blk] = swap32(blkno); } else { blkno = lp[(blkoff-(ind1lim+1)) % ptrs_per_blk] = ext2_balloc(); } ip->i_blocks += (blocksize / 512); if(verbose) { printf("Allocated data block %d\n", blkno); } bwrite(iblkno, blkbuf); } return(blkno); } if(blkoff > ind2lim) { fprintf(stderr, "ext2_blkno: block number too large: %d\n", blkoff); return(0); } return 0; } /* Read block number "blkno" from the specified file */ void ext2_bread (struct ext2_inode *ip, int blkno, char * buffer) { int dev_blkno; dev_blkno = ext2_blkno(ip, blkno, 0); if(dev_blkno == 0) { /* This is a "hole" */ memset(buffer, 0, blocksize); } else { /* Read it for real */ bread(dev_blkno, buffer); } } /* Write block number "blkno" to the specified file */ void ext2_bwrite (struct ext2_inode *ip, int blkno, char * buffer) { int dev_blkno; if(readonly) { fprintf(stderr, "ext2_bwrite: Cannot write to a readonly filesystem!\n"); return; } dev_blkno = ext2_blkno(ip, blkno, 1); if(dev_blkno == 0) { fprintf(stderr, "%s: No space on ext2 device\n", filename); } else { /* Write it for real */ bwrite(dev_blkno, buffer); } } /* More convenient forms of ext2_bread/ext2_bwrite. These allow arbitrary * data alignment and buffer sizes... */ int ext2_seek_and_read (struct ext2_inode *ip, int offset, char *buffer, int count) { int blkno; int blkoffset; int bytesleft; int nread; int iosize; char *bufptr; char blkbuf[EXT2_MAX_BLOCK_SIZE]; bufptr = buffer; bytesleft = count; nread = 0; blkno = offset / blocksize; blkoffset = offset % blocksize; while(bytesleft > 0) { iosize = ((blocksize-blkoffset) > bytesleft) ? bytesleft : (blocksize-blkoffset); if((blkoffset == 0) && (iosize == blocksize)) { ext2_bread(ip, blkno, bufptr); } else { ext2_bread(ip, blkno, blkbuf); memcpy(bufptr, blkbuf+blkoffset, iosize); } bytesleft -= iosize; bufptr += iosize; nread += iosize; blkno++; blkoffset = 0; } return(nread); } int ext2_seek_and_write (struct ext2_inode *ip, int offset, char *buffer, int count) { int blkno; int blkoffset; int bytesleft; int nwritten; int iosize; char *bufptr; char blkbuf[EXT2_MAX_BLOCK_SIZE]; bufptr = buffer; bytesleft = count; nwritten = 0; blkno = offset / blocksize; blkoffset = offset % blocksize; while(bytesleft > 0) { iosize = ((blocksize-blkoffset) > bytesleft) ? bytesleft : (blocksize-blkoffset); if((blkoffset == 0) && (iosize == blocksize)) { ext2_bwrite(ip, blkno, bufptr); } else { ext2_bread(ip, blkno, blkbuf); memcpy(blkbuf+blkoffset, bufptr, iosize); ext2_bwrite(ip, blkno, blkbuf); } bytesleft -= iosize; bufptr += iosize; nwritten += iosize; blkno++; blkoffset = 0; } return(nwritten); } struct ext2_inode * ext2_namei (char *name) { char namebuf[256]; char dirbuf[EXT2_MAX_BLOCK_SIZE]; char * component; struct ext2_inode * dir_inode; struct ext2_dir_entry *dp; int next_ino; /* Squirrel away a copy of "namebuf" that we can molest */ strcpy(namebuf, name); /* Start at the root... */ dir_inode = ext2_iget(EXT2_ROOT_INO); component = strtok(namebuf, "/"); while(component) { unsigned diroffset; int component_length, blockoffset; /* Search for the specified component in the current directory * inode. */ next_ino = -1; component_length = strlen(component); diroffset = 0; while (diroffset < dir_inode->i_size) { blockoffset = 0; ext2_bread(dir_inode, diroffset / blocksize, dirbuf); while (blockoffset < blocksize) { int namelen; dp = (struct ext2_dir_entry *)(dirbuf+blockoffset); namelen = big_endian ? swap16(dp->name_len) : dp->name_len; if((namelen == component_length) && (strncmp(component, dp->name, component_length) == 0)) { /* Found it! */ next_ino = big_endian ? swap32(dp->inode) : dp->inode; break; } /* Go to next entry in this block */ blockoffset += (big_endian ? swap16(dp->rec_len) : dp->rec_len); } if(next_ino >= 0) { break; } /* If we got here, then we didn't find the component. * Try the next block in this directory... */ diroffset += blocksize; } /* At this point, we're done with this directory whether * we've succeeded or failed... */ ext2_iput(dir_inode); /* If next_ino is negative, then we've failed (gone all the * way through without finding anything) */ if(next_ino < 0) { return(NULL); } /* Otherwise, we can get this inode and find the next * component string... */ dir_inode = ext2_iget(next_ino); component = strtok(NULL, "/"); } /* If we get here, then we got through all the components. * Whatever we got must match up with the last one. */ return(dir_inode); } /* Create a new entry in the specified directory with the specified * name/inumber pair. This routine ASSUMES that the specified * entry does not already exist! Therefore, we MUST use namei * first to try and find the entry... */ void ext2_mknod (struct ext2_inode *dip, char * name, int ino) { unsigned diroffset; int blockoffset, namelen, new_reclen; struct ext2_dir_entry *dp; struct ext2_dir_entry *entry_dp; char dirbuf[EXT2_MAX_BLOCK_SIZE]; int dp_inode, dp_reclen, dp_namelen; namelen = strlen(name); /* Look for an empty directory entry that can hold this * item. */ diroffset = 0; entry_dp = NULL; while (diroffset < dip->i_size) { blockoffset = 0; ext2_bread(dip, diroffset / blocksize, dirbuf); while(blockoffset < blocksize) { dp = (struct ext2_dir_entry *)(dirbuf+blockoffset); dp_inode = big_endian ? swap32(dp->inode) : dp->inode; dp_reclen = big_endian ? swap16(dp->rec_len) : dp->rec_len; dp_namelen = big_endian ? swap16(dp->name_len) : dp->name_len; if((dp_inode == 0) && (dp_reclen >= EXT2_DIR_REC_LEN(namelen))) { /* Found an *empty* entry that can hold this name. */ entry_dp = dp; break; } /* If this entry is in use, see if it has space at the end * to hold the new entry anyway... */ if((dp_inode != 0) && ((dp_reclen - EXT2_DIR_REC_LEN(dp_namelen)) >= EXT2_DIR_REC_LEN(namelen))) { new_reclen = dp_reclen - EXT2_DIR_REC_LEN(dp_namelen); /* Chop the in-use entry down to size */ if(big_endian) { dp_reclen = EXT2_DIR_REC_LEN(swap16(dp->name_len)); } else { dp_reclen = EXT2_DIR_REC_LEN(dp->name_len); } dp->rec_len = big_endian ? swap16(dp_reclen) : dp_reclen; /* Point entry_dp to the end of this entry */ entry_dp = (struct ext2_dir_entry *)((char*)dp + dp_reclen); /* Set the record length for this entry */ entry_dp->rec_len = big_endian ? swap16(new_reclen) : new_reclen; /* all set! */ break; } /* No luck yet... go to next entry in this block */ blockoffset += dp_reclen; } if(entry_dp != NULL) { break; } /* If we got here, then we didn't find the component. * Try the next block in this directory... */ diroffset += blocksize; } /* By the time we get here, one of two things has happened: * * If entry_dp is non-NULL, then entry_dp points to the * place in dirbuf where the entry lives, and diroffset * is the directory offset of the beginning of dirbuf. * * If entry_dp is NULL, then we couldn't find an entry, * so we need to add a block to the directory file for * this entry... */ if(entry_dp) { entry_dp->inode = big_endian ? swap32(ino) : ino; entry_dp->name_len = big_endian ? swap16(namelen) : namelen; strncpy(entry_dp->name, name, namelen); ext2_bwrite(dip, diroffset/blocksize, dirbuf); } else { entry_dp = (struct ext2_dir_entry *)dirbuf; entry_dp->inode = big_endian ? swap32(ino) : ino; entry_dp->name_len = big_endian ? swap16(namelen) : namelen; strncpy(entry_dp->name, name, namelen); entry_dp->rec_len = big_endian ? swap16(blocksize) : blocksize; ext2_bwrite(dip, dip->i_size/blocksize, dirbuf); dip->i_size += blocksize; } } /* This is a close cousin to namei, only it *removes* the entry * in addition to finding it. This routine assumes that the specified * entry has already been found... */ void ext2_remove_entry (char *name) { char namebuf[256]; char dirbuf[EXT2_MAX_BLOCK_SIZE]; char * component; struct ext2_inode * dir_inode; struct ext2_dir_entry *dp; int next_ino; int dp_inode, dp_reclen, dp_namelen; /* Squirrel away a copy of "namebuf" that we can molest */ strcpy(namebuf, name); /* Start at the root... */ dir_inode = ext2_iget(EXT2_ROOT_INO); component = strtok(namebuf, "/"); while(component) { unsigned diroffset; int blockoffset, component_length; char *next_component; struct ext2_dir_entry * pdp; /* Search for the specified component in the current directory * inode. */ next_component = NULL; pdp = NULL; next_ino = -1; component_length = strlen(component); diroffset = 0; while (diroffset < dir_inode->i_size) { blockoffset = 0; ext2_bread(dir_inode, diroffset / blocksize, dirbuf); while(blockoffset < blocksize) { dp = (struct ext2_dir_entry *)(dirbuf+blockoffset); dp_inode = big_endian ? swap32(dp->inode) : dp->inode; dp_reclen = big_endian ? swap16(dp->rec_len) : dp->rec_len; dp_namelen = big_endian ? swap16(dp->name_len) : dp->name_len; if((dp_namelen == component_length) && (strncmp(component, dp->name, component_length) == 0)) { /* Found it! */ next_component = strtok(NULL, "/"); if(next_component == NULL) { /* We've found the entry that needs to be * zapped. If it's at the beginning of the * block, then zap it. Otherwise, coalesce * it with the previous entry. */ if(pdp) { if(big_endian) { pdp->rec_len = swap16(swap16(pdp->rec_len)+dp_reclen); } else { pdp->rec_len += dp_reclen; } } else { dp->inode = 0; dp->name_len = 0; } ext2_bwrite(dir_inode, diroffset / blocksize, dirbuf); return; } next_ino = dp_inode; break; } /* Go to next entry in this block */ pdp = dp; blockoffset += dp_reclen; } if(next_ino >= 0) { break; } /* If we got here, then we didn't find the component. * Try the next block in this directory... */ diroffset += blocksize; } /* At this point, we're done with this directory whether * we've succeeded or failed... */ ext2_iput(dir_inode); /* If next_ino is negative, then we've failed (gone all the * way through without finding anything) */ if(next_ino < 0) { return; } /* Otherwise, we can get this inode and find the next * component string... */ dir_inode = ext2_iget(next_ino); component = next_component; } ext2_iput(dir_inode); } void ext2_truncate (struct ext2_inode *ip) { int i; /* Deallocate all blocks associated with a particular file * and set its size to zero. */ /* Direct blocks */ for(i = 0; i < EXT2_NDIR_BLOCKS; i++) { if(ip->i_block[i]) { ext2_bfree(ip->i_block[i]); ip->i_block[i] = 0; } } /* First-level indirect blocks */ if(ip->i_block[EXT2_IND_BLOCK]) { ext2_free_indirect(ip->i_block[EXT2_IND_BLOCK], 0); ip->i_block[EXT2_IND_BLOCK] = 0; } /* Second-level indirect blocks */ if(ip->i_block[EXT2_DIND_BLOCK]) { ext2_free_indirect(ip->i_block[EXT2_DIND_BLOCK], 1); ip->i_block[EXT2_DIND_BLOCK] = 0; } /* Third-level indirect blocks */ if(ip->i_block[EXT2_TIND_BLOCK]) { ext2_free_indirect(ip->i_block[EXT2_TIND_BLOCK], 2); ip->i_block[EXT2_TIND_BLOCK] = 0; } ip->i_size = 0; } /* Recursive routine to free an indirect chain */ static void ext2_free_indirect (int indirect_blkno, int level) { int i, indirect_block[EXT2_MAX_BLOCK_SIZE/4]; /* Read the specified indirect block */ bread(indirect_blkno, indirect_block); for(i = 0; i < ptrs_per_blk; i++) { if(level == 0) { /* These are pointers to data blocks; just free them up */ if(indirect_block[i]) { if(big_endian) { ext2_bfree(swap32(indirect_block[i])); } else { ext2_bfree(indirect_block[i]); } indirect_block[i] = 0; } } else { /* These are pointers to *indirect* blocks. Go down the chain */ if(indirect_block[i]) { if(big_endian) { ext2_free_indirect(swap32(indirect_block[i]), level-1); } else { ext2_free_indirect(indirect_block[i], level-1); } indirect_block[i] = 0; } } } ext2_bfree(indirect_blkno); } int ext2_get_inumber (struct ext2_inode *ip) { struct inode_table_entry *itp; itp = (struct inode_table_entry *)ip; return(itp->inumber); }