diff options
Diffstat (limited to 'fs/ext')
-rw-r--r-- | fs/ext/Makefile | 31 | ||||
-rw-r--r-- | fs/ext/dir.c | 131 | ||||
-rw-r--r-- | fs/ext/file.c | 258 | ||||
-rw-r--r-- | fs/ext/freelists.c | 341 | ||||
-rw-r--r-- | fs/ext/fsync.c | 185 | ||||
-rw-r--r-- | fs/ext/inode.c | 444 | ||||
-rw-r--r-- | fs/ext/namei.c | 893 | ||||
-rw-r--r-- | fs/ext/symlink.c | 108 | ||||
-rw-r--r-- | fs/ext/truncate.c | 252 |
9 files changed, 2643 insertions, 0 deletions
diff --git a/fs/ext/Makefile b/fs/ext/Makefile new file mode 100644 index 000000000..5e23319c8 --- /dev/null +++ b/fs/ext/Makefile @@ -0,0 +1,31 @@ +# +# Makefile for the linux ext-filesystem routines. +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definitions are now in the main makefile... + +.c.s: + $(CC) $(CFLAGS) -S $< +.c.o: + $(CC) $(CFLAGS) -c $< +.s.o: + $(AS) -o $*.o $< + +OBJS= freelists.o truncate.o namei.o inode.o \ + file.o dir.o symlink.o fsync.o + +ext.o: $(OBJS) + $(LD) -r -o ext.o $(OBJS) + +dep: + $(CPP) -M *.c > .depend + +# +# include a dependency file if one exists +# +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/fs/ext/dir.c b/fs/ext/dir.c new file mode 100644 index 000000000..10e30fafa --- /dev/null +++ b/fs/ext/dir.c @@ -0,0 +1,131 @@ +/* + * linux/fs/ext/dir.c + * + * Copyright (C) 1992 Remy Card (card@masi.ibp.fr) + * + * from + * + * linux/fs/minix/dir.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + * + * ext directory handling functions + */ + +#include <asm/segment.h> + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/ext_fs.h> +#include <linux/stat.h> + +#define NAME_OFFSET(de) ((int) ((de)->d_name - (char *) (de))) +#define ROUND_UP(x) (((x)+3) & ~3) + +static int ext_dir_read(struct inode * inode, struct file * filp, char * buf, int count) +{ + return -EISDIR; +} + +static int ext_readdir(struct inode *, struct file *, struct dirent *, int); + +static struct file_operations ext_dir_operations = { + NULL, /* lseek - default */ + ext_dir_read, /* read */ + NULL, /* write - bad */ + ext_readdir, /* readdir */ + NULL, /* select - default */ + NULL, /* ioctl - default */ + NULL, /* mmap */ + NULL, /* no special open code */ + NULL, /* no special release code */ + file_fsync /* fsync */ +}; + +/* + * directories can handle most operations... + */ +struct inode_operations ext_dir_inode_operations = { + &ext_dir_operations, /* default directory file-ops */ + ext_create, /* create */ + ext_lookup, /* lookup */ + ext_link, /* link */ + ext_unlink, /* unlink */ + ext_symlink, /* symlink */ + ext_mkdir, /* mkdir */ + ext_rmdir, /* rmdir */ + ext_mknod, /* mknod */ + ext_rename, /* rename */ + NULL, /* readlink */ + NULL, /* follow_link */ + NULL, /* bmap */ + ext_truncate, /* truncate */ + NULL /* permission */ +}; + +static int ext_readdir(struct inode * inode, struct file * filp, + struct dirent * dirent, int count) +{ + unsigned int i; + unsigned int ret; + off_t offset; + char c; + struct buffer_head * bh; + struct ext_dir_entry * de; + + if (!inode || !S_ISDIR(inode->i_mode)) + return -EBADF; + if ((filp->f_pos & 7) != 0) + return -EBADF; + ret = 0; + while (!ret && filp->f_pos < inode->i_size) { + offset = filp->f_pos & 1023; + bh = ext_bread(inode,(filp->f_pos)>>BLOCK_SIZE_BITS,0); + if (!bh) { + filp->f_pos += 1024-offset; + continue; + } + for (i = 0; i < 1024 && i < offset; ) { + de = (struct ext_dir_entry *) (bh->b_data + i); + if (!de->rec_len) + break; + i += de->rec_len; + } + offset = i; + de = (struct ext_dir_entry *) (offset + bh->b_data); + while (!ret && offset < 1024 && filp->f_pos < inode->i_size) { + if (de->rec_len < 8 || de->rec_len % 8 != 0 || + de->rec_len < de->name_len + 8 || + (de->rec_len + (off_t) filp->f_pos - 1) / 1024 > ((off_t) filp->f_pos / 1024)) { + printk ("ext_readdir: bad dir entry, skipping\n"); + printk ("dev=%d, dir=%ld, offset=%ld, rec_len=%d, name_len=%d\n", + inode->i_dev, inode->i_ino, offset, de->rec_len, de->name_len); + filp->f_pos += 1024-offset; + if (filp->f_pos > inode->i_size) + filp->f_pos = inode->i_size; + continue; + } + offset += de->rec_len; + filp->f_pos += de->rec_len; + if (de->inode) { + for (i = 0; i < de->name_len; i++) + if ((c = de->name[i]) != 0) + put_fs_byte(c,i+dirent->d_name); + else + break; + if (i) { + put_fs_long(de->inode,&dirent->d_ino); + put_fs_byte(0,i+dirent->d_name); + put_fs_word(i,&dirent->d_reclen); + ret = ROUND_UP(NAME_OFFSET(dirent)+i+1); + break; + } + } + de = (struct ext_dir_entry *) ((char *) de + + de->rec_len); + } + brelse(bh); + } + return ret; +} diff --git a/fs/ext/file.c b/fs/ext/file.c new file mode 100644 index 000000000..f32cdd898 --- /dev/null +++ b/fs/ext/file.c @@ -0,0 +1,258 @@ +/* + * linux/fs/ext/file.c + * + * Copyright (C) 1992 Remy Card (card@masi.ibp.fr) + * + * from + * + * linux/fs/minix/file.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + * + * ext regular file handling primitives + */ + +#include <asm/segment.h> +#include <asm/system.h> + +#include <linux/sched.h> +#include <linux/ext_fs.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/fcntl.h> +#include <linux/stat.h> +#include <linux/locks.h> + +#define NBUF 32 + +#define MIN(a,b) (((a)<(b))?(a):(b)) +#define MAX(a,b) (((a)>(b))?(a):(b)) + +#include <linux/fs.h> +#include <linux/ext_fs.h> + +static int ext_file_read(struct inode *, struct file *, char *, int); +static int ext_file_write(struct inode *, struct file *, char *, int); + +/* + * We have mostly NULL's here: the current defaults are ok for + * the ext filesystem. + */ +static struct file_operations ext_file_operations = { + NULL, /* lseek - default */ + ext_file_read, /* read */ + ext_file_write, /* write */ + NULL, /* readdir - bad */ + NULL, /* select - default */ + NULL, /* ioctl - default */ + generic_mmap, /* mmap */ + NULL, /* no special open is needed */ + NULL, /* release */ + ext_sync_file /* fsync */ +}; + +struct inode_operations ext_file_inode_operations = { + &ext_file_operations, /* default file operations */ + NULL, /* create */ + NULL, /* lookup */ + NULL, /* link */ + NULL, /* unlink */ + NULL, /* symlink */ + NULL, /* mkdir */ + NULL, /* rmdir */ + NULL, /* mknod */ + NULL, /* rename */ + NULL, /* readlink */ + NULL, /* follow_link */ + ext_bmap, /* bmap */ + ext_truncate, /* truncate */ + NULL /* permission */ +}; + +static int ext_file_read(struct inode * inode, struct file * filp, char * buf, int count) +{ + int read,left,chars; + int block, blocks, offset; + int bhrequest, uptodate; + struct buffer_head ** bhb, ** bhe; + struct buffer_head * bhreq[NBUF]; + struct buffer_head * buflist[NBUF]; + unsigned int size; + + if (!inode) { + printk("ext_file_read: inode = NULL\n"); + return -EINVAL; + } + if (!S_ISREG(inode->i_mode)) { + printk("ext_file_read: mode = %07o\n",inode->i_mode); + return -EINVAL; + } + offset = filp->f_pos; + size = inode->i_size; + if (offset > size) + left = 0; + else + left = size - offset; + if (left > count) + left = count; + if (left <= 0) + return 0; + read = 0; + block = offset >> BLOCK_SIZE_BITS; + offset &= BLOCK_SIZE-1; + size = (size + (BLOCK_SIZE-1)) >> BLOCK_SIZE_BITS; + blocks = (left + offset + BLOCK_SIZE - 1) >> BLOCK_SIZE_BITS; + bhb = bhe = buflist; + if (filp->f_reada) { + if(blocks < read_ahead[MAJOR(inode->i_dev)] / (BLOCK_SIZE >> 9)) + blocks = read_ahead[MAJOR(inode->i_dev)] / (BLOCK_SIZE >> 9); + if (block + blocks > size) + blocks = size - block; + } + + /* We do this in a two stage process. We first try and request + as many blocks as we can, then we wait for the first one to + complete, and then we try and wrap up as many as are actually + done. This routine is rather generic, in that it can be used + in a filesystem by substituting the appropriate function in + for getblk. + + This routine is optimized to make maximum use of the various + buffers and caches. */ + + do { + bhrequest = 0; + uptodate = 1; + while (blocks) { + --blocks; + *bhb = ext_getblk(inode, block++, 0); + if (*bhb && !(*bhb)->b_uptodate) { + uptodate = 0; + bhreq[bhrequest++] = *bhb; + } + + if (++bhb == &buflist[NBUF]) + bhb = buflist; + + /* If the block we have on hand is uptodate, go ahead + and complete processing. */ + if (uptodate) + break; + if (bhb == bhe) + break; + } + + /* Now request them all */ + if (bhrequest) + ll_rw_block(READ, bhrequest, bhreq); + + do { /* Finish off all I/O that has actually completed */ + if (*bhe) { + wait_on_buffer(*bhe); + if (!(*bhe)->b_uptodate) { /* read error? */ + brelse(*bhe); + if (++bhe == &buflist[NBUF]) + bhe = buflist; + left = 0; + break; + } + } + if (left < BLOCK_SIZE - offset) + chars = left; + else + chars = BLOCK_SIZE - offset; + filp->f_pos += chars; + left -= chars; + read += chars; + if (*bhe) { + memcpy_tofs(buf,offset+(*bhe)->b_data,chars); + brelse(*bhe); + buf += chars; + } else { + while (chars-->0) + put_fs_byte(0,buf++); + } + offset = 0; + if (++bhe == &buflist[NBUF]) + bhe = buflist; + } while (left > 0 && bhe != bhb && (!*bhe || !(*bhe)->b_lock)); + } while (left > 0); + +/* Release the read-ahead blocks */ + while (bhe != bhb) { + brelse(*bhe); + if (++bhe == &buflist[NBUF]) + bhe = buflist; + }; + if (!read) + return -EIO; + filp->f_reada = 1; + if (!IS_RDONLY(inode)) { + inode->i_atime = CURRENT_TIME; + inode->i_dirt = 1; + } + return read; +} + +static int ext_file_write(struct inode * inode, struct file * filp, char * buf, int count) +{ + off_t pos; + int written,c; + struct buffer_head * bh; + char * p; + + if (!inode) { + printk("ext_file_write: inode = NULL\n"); + return -EINVAL; + } + if (!S_ISREG(inode->i_mode)) { + printk("ext_file_write: mode = %07o\n",inode->i_mode); + return -EINVAL; + } +/* + * ok, append may not work when many processes are writing at the same time + * but so what. That way leads to madness anyway. + */ + if (filp->f_flags & O_APPEND) + pos = inode->i_size; + else + pos = filp->f_pos; + written = 0; + while (written<count) { + bh = ext_getblk(inode,pos/BLOCK_SIZE,1); + if (!bh) { + if (!written) + written = -ENOSPC; + break; + } + c = BLOCK_SIZE - (pos % BLOCK_SIZE); + if (c > count-written) + c = count-written; + if (c != BLOCK_SIZE && !bh->b_uptodate) { + ll_rw_block(READ, 1, &bh); + wait_on_buffer(bh); + if (!bh->b_uptodate) { + brelse(bh); + if (!written) + written = -EIO; + break; + } + } + p = (pos % BLOCK_SIZE) + bh->b_data; + pos += c; + if (pos > inode->i_size) { + inode->i_size = pos; + inode->i_dirt = 1; + } + written += c; + memcpy_fromfs(p,buf,c); + buf += c; + bh->b_uptodate = 1; + mark_buffer_dirty(bh, 0); + brelse(bh); + } + inode->i_mtime = inode->i_ctime = CURRENT_TIME; + filp->f_pos = pos; + inode->i_dirt = 1; + return written; +} diff --git a/fs/ext/freelists.c b/fs/ext/freelists.c new file mode 100644 index 000000000..29c4c4289 --- /dev/null +++ b/fs/ext/freelists.c @@ -0,0 +1,341 @@ +/* + * linux/fs/ext/freelists.c + * + * Copyright (C) 1992 Remy Card (card@masi.ibp.fr) + * + */ + +/* freelists.c contains the code that handles the inode and block free lists */ + + +/* + + The free blocks are managed by a linked list. The super block contains the + number of the first free block. This block contains 254 numbers of other + free blocks and the number of the next block in the list. + + When an ext fs is mounted, the number of the first free block is stored + in s->u.ext_sb.s_firstfreeblocknumber and the block header is stored in + s->u.ext_sb.s_firstfreeblock. u.ext_sb.s_freeblockscount contains the count + of free blocks. + + The free inodes are also managed by a linked list in a similar way. The + super block contains the number of the first free inode. This inode contains + 14 numbers of other free inodes and the number of the next inode in the list. + + The number of the first free inode is stored in + s->u.ext_sb.s_firstfreeinodenumber and the header of the block containing + the inode is stored in s->u.ext_sb.s_firstfreeinodeblock. + u.ext_sb.s_freeinodescount contains the count of free inodes. + +*/ + +#include <linux/sched.h> +#include <linux/ext_fs.h> +#include <linux/stat.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/locks.h> + +void ext_free_block(struct super_block * sb, int block) +{ + struct buffer_head * bh; + struct ext_free_block * efb; + + if (!sb) { + printk("trying to free block on non-existent device\n"); + return; + } + lock_super (sb); + if (block < sb->u.ext_sb.s_firstdatazone || + block >= sb->u.ext_sb.s_nzones) { + printk("trying to free block not in datazone\n"); + return; + } + bh = get_hash_table(sb->s_dev, block, sb->s_blocksize); + if (bh) + bh->b_dirt=0; + brelse(bh); + if (sb->u.ext_sb.s_firstfreeblock) + efb = (struct ext_free_block *) sb->u.ext_sb.s_firstfreeblock->b_data; + if (!sb->u.ext_sb.s_firstfreeblock || efb->count == 254) { +#ifdef EXTFS_DEBUG +printk("ext_free_block: block full, skipping to %d\n", block); +#endif + if (sb->u.ext_sb.s_firstfreeblock) + brelse (sb->u.ext_sb.s_firstfreeblock); + if (!(sb->u.ext_sb.s_firstfreeblock = bread (sb->s_dev, + block, sb->s_blocksize))) + panic ("ext_free_block: unable to read block to free\n"); + efb = (struct ext_free_block *) sb->u.ext_sb.s_firstfreeblock->b_data; + efb->next = sb->u.ext_sb.s_firstfreeblocknumber; + efb->count = 0; + sb->u.ext_sb.s_firstfreeblocknumber = block; + } else { + efb->free[efb->count++] = block; + } + sb->u.ext_sb.s_freeblockscount ++; + sb->s_dirt = 1; + mark_buffer_dirty(sb->u.ext_sb.s_firstfreeblock, 1); + unlock_super (sb); + return; +} + +int ext_new_block(struct super_block * sb) +{ + struct buffer_head * bh; + struct ext_free_block * efb; + int j; + + if (!sb) { + printk("trying to get new block from non-existent device\n"); + return 0; + } + if (!sb->u.ext_sb.s_firstfreeblock) + return 0; + lock_super (sb); + efb = (struct ext_free_block *) sb->u.ext_sb.s_firstfreeblock->b_data; + if (efb->count) { + j = efb->free[--efb->count]; + mark_buffer_dirty(sb->u.ext_sb.s_firstfreeblock, 1); + } else { +#ifdef EXTFS_DEBUG +printk("ext_new_block: block empty, skipping to %d\n", efb->next); +#endif + j = sb->u.ext_sb.s_firstfreeblocknumber; + sb->u.ext_sb.s_firstfreeblocknumber = efb->next; + brelse (sb->u.ext_sb.s_firstfreeblock); + if (!sb->u.ext_sb.s_firstfreeblocknumber) { + sb->u.ext_sb.s_firstfreeblock = NULL; + } else { + if (!(sb->u.ext_sb.s_firstfreeblock = bread (sb->s_dev, + sb->u.ext_sb.s_firstfreeblocknumber, + sb->s_blocksize))) + panic ("ext_new_block: unable to read next free block\n"); + } + } + if (j < sb->u.ext_sb.s_firstdatazone || j > sb->u.ext_sb.s_nzones) { + printk ("ext_new_block: blk = %d\n", j); + printk("allocating block not in data zone\n"); + return 0; + } + sb->u.ext_sb.s_freeblockscount --; + sb->s_dirt = 1; + + if (!(bh=getblk(sb->s_dev, j, sb->s_blocksize))) { + printk("new_block: cannot get block"); + return 0; + } + memset(bh->b_data, 0, BLOCK_SIZE); + bh->b_uptodate = 1; + mark_buffer_dirty(bh, 1); + brelse(bh); +#ifdef EXTFS_DEBUG +printk("ext_new_block: allocating block %d\n", j); +#endif + unlock_super (sb); + return j; +} + +unsigned long ext_count_free_blocks(struct super_block *sb) +{ +#ifdef EXTFS_DEBUG + struct buffer_head * bh; + struct ext_free_block * efb; + unsigned long count, block; + + lock_super (sb); + if (!sb->u.ext_sb.s_firstfreeblock) + count = 0; + else { + efb = (struct ext_free_block *) sb->u.ext_sb.s_firstfreeblock->b_data; + count = efb->count + 1; + block = efb->next; + while (block) { + if (!(bh = bread (sb->s_dev, block, sb->s_blocksize))) { + printk ("ext_count_free: error while reading free blocks list\n"); + block = 0; + } else { + efb = (struct ext_free_block *) bh->b_data; + count += efb->count + 1; + block = efb->next; + brelse (bh); + } + } + } +printk("ext_count_free_blocks: stored = %d, computed = %d\n", + sb->u.ext_sb.s_freeblockscount, count); + unlock_super (sb); + return count; +#else + return sb->u.ext_sb.s_freeblockscount; +#endif +} + +void ext_free_inode(struct inode * inode) +{ + struct buffer_head * bh; + struct ext_free_inode * efi; + struct super_block * sb; + unsigned long block; + unsigned long ino; + dev_t dev; + + if (!inode) + return; + if (!inode->i_dev) { + printk("free_inode: inode has no device\n"); + return; + } + if (inode->i_count != 1) { + printk("free_inode: inode has count=%d\n",inode->i_count); + return; + } + if (inode->i_nlink) { + printk("free_inode: inode has nlink=%d\n",inode->i_nlink); + return; + } + if (!inode->i_sb) { + printk("free_inode: inode on non-existent device\n"); + return; + } + sb = inode->i_sb; + ino = inode->i_ino; + dev = inode->i_dev; + clear_inode(inode); + lock_super (sb); + if (ino < 1 || ino > sb->u.ext_sb.s_ninodes) { + printk("free_inode: inode 0 or non-existent inode\n"); + unlock_super (sb); + return; + } + if (sb->u.ext_sb.s_firstfreeinodeblock) + efi = ((struct ext_free_inode *) sb->u.ext_sb.s_firstfreeinodeblock->b_data) + + (sb->u.ext_sb.s_firstfreeinodenumber-1)%EXT_INODES_PER_BLOCK; + if (!sb->u.ext_sb.s_firstfreeinodeblock || efi->count == 14) { +#ifdef EXTFS_DEBUG +printk("ext_free_inode: inode full, skipping to %d\n", ino); +#endif + if (sb->u.ext_sb.s_firstfreeinodeblock) + brelse (sb->u.ext_sb.s_firstfreeinodeblock); + block = 2 + (ino - 1) / EXT_INODES_PER_BLOCK; + if (!(bh = bread(dev, block, sb->s_blocksize))) + panic("ext_free_inode: unable to read inode block\n"); + efi = ((struct ext_free_inode *) bh->b_data) + + (ino - 1) % EXT_INODES_PER_BLOCK; + efi->next = sb->u.ext_sb.s_firstfreeinodenumber; + efi->count = 0; + sb->u.ext_sb.s_firstfreeinodenumber = ino; + sb->u.ext_sb.s_firstfreeinodeblock = bh; + } else { + efi->free[efi->count++] = ino; + } + sb->u.ext_sb.s_freeinodescount ++; + sb->s_dirt = 1; + mark_buffer_dirty(sb->u.ext_sb.s_firstfreeinodeblock, 1); + unlock_super (sb); +} + +struct inode * ext_new_inode(const struct inode * dir) +{ + struct super_block * sb; + struct inode * inode; + struct ext_free_inode * efi; + unsigned long block; + int j; + + if (!dir || !(inode=get_empty_inode())) + return NULL; + sb = dir->i_sb; + inode->i_sb = sb; + inode->i_flags = sb->s_flags; + if (!sb->u.ext_sb.s_firstfreeinodeblock) + return 0; + lock_super (sb); + efi = ((struct ext_free_inode *) sb->u.ext_sb.s_firstfreeinodeblock->b_data) + + (sb->u.ext_sb.s_firstfreeinodenumber-1)%EXT_INODES_PER_BLOCK; + if (efi->count) { + j = efi->free[--efi->count]; + mark_buffer_dirty(sb->u.ext_sb.s_firstfreeinodeblock, 1); + } else { +#ifdef EXTFS_DEBUG +printk("ext_free_inode: inode empty, skipping to %d\n", efi->next); +#endif + j = sb->u.ext_sb.s_firstfreeinodenumber; + if (efi->next > sb->u.ext_sb.s_ninodes) { + printk ("efi->next = %ld\n", efi->next); + panic ("ext_new_inode: bad inode number in free list\n"); + } + sb->u.ext_sb.s_firstfreeinodenumber = efi->next; + block = 2 + (((unsigned long) efi->next) - 1) / EXT_INODES_PER_BLOCK; + brelse (sb->u.ext_sb.s_firstfreeinodeblock); + if (!sb->u.ext_sb.s_firstfreeinodenumber) { + sb->u.ext_sb.s_firstfreeinodeblock = NULL; + } else { + if (!(sb->u.ext_sb.s_firstfreeinodeblock = + bread(sb->s_dev, block, sb->s_blocksize))) + panic ("ext_new_inode: unable to read next free inode block\n"); + } + } + sb->u.ext_sb.s_freeinodescount --; + sb->s_dirt = 1; + inode->i_count = 1; + inode->i_nlink = 1; + inode->i_dev = sb->s_dev; + inode->i_uid = current->fsuid; + inode->i_gid = (dir->i_mode & S_ISGID) ? dir->i_gid : current->fsgid; + inode->i_dirt = 1; + inode->i_ino = j; + inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME; + inode->i_op = NULL; + inode->i_blocks = inode->i_blksize = 0; + insert_inode_hash(inode); +#ifdef EXTFS_DEBUG +printk("ext_new_inode : allocating inode %d\n", inode->i_ino); +#endif + unlock_super (sb); + return inode; +} + +unsigned long ext_count_free_inodes(struct super_block *sb) +{ +#ifdef EXTFS_DEBUG + struct buffer_head * bh; + struct ext_free_inode * efi; + unsigned long count, block, ino; + + lock_super (sb); + if (!sb->u.ext_sb.s_firstfreeinodeblock) + count = 0; + else { + efi = ((struct ext_free_inode *) sb->u.ext_sb.s_firstfreeinodeblock->b_data) + + ((sb->u.ext_sb.s_firstfreeinodenumber-1)%EXT_INODES_PER_BLOCK); + count = efi->count + 1; + ino = efi->next; + while (ino) { + if (ino < 1 || ino > sb->u.ext_sb.s_ninodes) { + printk ("u.ext_sb.s_firstfreeinodenumber = %d, ino = %d\n", + (int) sb->u.ext_sb.s_firstfreeinodenumber,ino); + panic ("ext_count_fre_inodes: bad inode number in free list\n"); + } + block = 2 + ((ino - 1) / EXT_INODES_PER_BLOCK); + if (!(bh = bread (sb->s_dev, block, sb->s_blocksize))) { + printk ("ext_count_free_inodes: error while reading free inodes list\n"); + block = 0; + } else { + efi = ((struct ext_free_inode *) bh->b_data) + + ((ino - 1) % EXT_INODES_PER_BLOCK); + count += efi->count + 1; + ino = efi->next; + brelse (bh); + } + } + } +printk("ext_count_free_inodes: stored = %d, computed = %d\n", + sb->u.ext_sb.s_freeinodescount, count); + unlock_super (sb); + return count; +#else + return sb->u.ext_sb.s_freeinodescount; +#endif +} diff --git a/fs/ext/fsync.c b/fs/ext/fsync.c new file mode 100644 index 000000000..bb20383cc --- /dev/null +++ b/fs/ext/fsync.c @@ -0,0 +1,185 @@ + +/* + * linux/fs/ext/fsync.c + * + * Copyright (C) 1993 Stephen Tweedie (sct@dcs.ed.ac.uk) + * from + * Copyright (C) 1992 Remy Card (card@masi.ibp.fr) + * from + * linux/fs/minix/truncate.c Copyright (C) 1991, 1992 Linus Torvalds + * + * extfs fsync primitive + */ + +#include <asm/segment.h> +#include <asm/system.h> + +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/stat.h> +#include <linux/fcntl.h> +#include <linux/locks.h> + +#include <linux/fs.h> +#include <linux/ext_fs.h> + + +#define blocksize BLOCK_SIZE +#define addr_per_block 256 + +static int sync_block (struct inode * inode, unsigned long * block, int wait) +{ + struct buffer_head * bh; + int tmp; + + if (!*block) + return 0; + tmp = *block; + bh = get_hash_table(inode->i_dev, *block, blocksize); + if (!bh) + return 0; + if (*block != tmp) { + brelse (bh); + return 1; + } + if (wait && bh->b_req && !bh->b_uptodate) { + brelse(bh); + return -1; + } + if (wait || !bh->b_uptodate || !bh->b_dirt) + { + brelse(bh); + return 0; + } + ll_rw_block(WRITE, 1, &bh); + bh->b_count--; + return 0; +} + +static int sync_iblock (struct inode * inode, unsigned long * iblock, + struct buffer_head **bh, int wait) +{ + int rc, tmp; + + *bh = NULL; + tmp = *iblock; + if (!tmp) + return 0; + rc = sync_block (inode, iblock, wait); + if (rc) + return rc; + *bh = bread(inode->i_dev, tmp, blocksize); + if (tmp != *iblock) { + brelse(*bh); + *bh = NULL; + return 1; + } + if (!*bh) + return -1; + return 0; +} + + +static int sync_direct(struct inode *inode, int wait) +{ + int i; + int rc, err = 0; + + for (i = 0; i < 9; i++) { + rc = sync_block (inode, inode->u.ext_i.i_data + i, wait); + if (rc > 0) + break; + if (rc) + err = rc; + } + return err; +} + +static int sync_indirect(struct inode *inode, unsigned long *iblock, int wait) +{ + int i; + struct buffer_head * ind_bh; + int rc, err = 0; + + rc = sync_iblock (inode, iblock, &ind_bh, wait); + if (rc || !ind_bh) + return rc; + + for (i = 0; i < addr_per_block; i++) { + rc = sync_block (inode, + ((unsigned long *) ind_bh->b_data) + i, + wait); + if (rc > 0) + break; + if (rc) + err = rc; + } + brelse(ind_bh); + return err; +} + +static int sync_dindirect(struct inode *inode, unsigned long *diblock, + int wait) +{ + int i; + struct buffer_head * dind_bh; + int rc, err = 0; + + rc = sync_iblock (inode, diblock, &dind_bh, wait); + if (rc || !dind_bh) + return rc; + + for (i = 0; i < addr_per_block; i++) { + rc = sync_indirect (inode, + ((unsigned long *) dind_bh->b_data) + i, + wait); + if (rc > 0) + break; + if (rc) + err = rc; + } + brelse(dind_bh); + return err; +} + +static int sync_tindirect(struct inode *inode, unsigned long *tiblock, + int wait) +{ + int i; + struct buffer_head * tind_bh; + int rc, err = 0; + + rc = sync_iblock (inode, tiblock, &tind_bh, wait); + if (rc || !tind_bh) + return rc; + + for (i = 0; i < addr_per_block; i++) { + rc = sync_dindirect (inode, + ((unsigned long *) tind_bh->b_data) + i, + wait); + if (rc > 0) + break; + if (rc) + err = rc; + } + brelse(tind_bh); + return err; +} + +int ext_sync_file(struct inode * inode, struct file *file) +{ + int wait, err = 0; + + if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || + S_ISLNK(inode->i_mode))) + return -EINVAL; + for (wait=0; wait<=1; wait++) + { + err |= sync_direct(inode, wait); + err |= sync_indirect(inode, inode->u.ext_i.i_data+9, wait); + err |= sync_dindirect(inode, inode->u.ext_i.i_data+10, wait); + err |= sync_tindirect(inode, inode->u.ext_i.i_data+11, wait); + } + err |= ext_sync_inode (inode); + return (err < 0) ? -EIO : 0; +} diff --git a/fs/ext/inode.c b/fs/ext/inode.c new file mode 100644 index 000000000..b3ca2e2cf --- /dev/null +++ b/fs/ext/inode.c @@ -0,0 +1,444 @@ +/* + * linux/fs/ext/inode.c + * + * Copyright (C) 1992 Remy Card (card@masi.ibp.fr) + * + * from + * + * linux/fs/minix/inode.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + */ + +#include <linux/sched.h> +#include <linux/ext_fs.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/stat.h> +#include <linux/locks.h> + +#include <asm/system.h> +#include <asm/segment.h> + +void ext_put_inode(struct inode *inode) +{ + if (inode->i_nlink) + return; + inode->i_size = 0; + ext_truncate(inode); + ext_free_inode(inode); +} + +void ext_put_super(struct super_block *sb) +{ + + lock_super(sb); + sb->s_dev = 0; + if (sb->u.ext_sb.s_firstfreeinodeblock) + brelse (sb->u.ext_sb.s_firstfreeinodeblock); + if (sb->u.ext_sb.s_firstfreeblock) + brelse (sb->u.ext_sb.s_firstfreeblock); + unlock_super(sb); + return; +} + +static struct super_operations ext_sops = { + ext_read_inode, + NULL, + ext_write_inode, + ext_put_inode, + ext_put_super, + ext_write_super, + ext_statfs, + NULL +}; + +struct super_block *ext_read_super(struct super_block *s,void *data, + int silent) +{ + struct buffer_head *bh; + struct ext_super_block *es; + int dev = s->s_dev,block; + + lock_super(s); + set_blocksize(dev, BLOCK_SIZE); + if (!(bh = bread(dev, 1, BLOCK_SIZE))) { + s->s_dev=0; + unlock_super(s); + printk("EXT-fs: unable to read superblock\n"); + return NULL; + } + es = (struct ext_super_block *) bh->b_data; + s->s_blocksize = 1024; + s->s_blocksize_bits = 10; + s->u.ext_sb.s_ninodes = es->s_ninodes; + s->u.ext_sb.s_nzones = es->s_nzones; + s->u.ext_sb.s_firstdatazone = es->s_firstdatazone; + s->u.ext_sb.s_log_zone_size = es->s_log_zone_size; + s->u.ext_sb.s_max_size = es->s_max_size; + s->s_magic = es->s_magic; + s->u.ext_sb.s_firstfreeblocknumber = es->s_firstfreeblock; + s->u.ext_sb.s_freeblockscount = es->s_freeblockscount; + s->u.ext_sb.s_firstfreeinodenumber = es->s_firstfreeinode; + s->u.ext_sb.s_freeinodescount = es->s_freeinodescount; + brelse(bh); + if (s->s_magic != EXT_SUPER_MAGIC) { + s->s_dev = 0; + unlock_super(s); + if (!silent) + printk("VFS: Can't find an extfs filesystem on dev 0x%04x.\n", + dev); + return NULL; + } + if (!s->u.ext_sb.s_firstfreeblocknumber) + s->u.ext_sb.s_firstfreeblock = NULL; + else + if (!(s->u.ext_sb.s_firstfreeblock = bread(dev, + s->u.ext_sb.s_firstfreeblocknumber, BLOCK_SIZE))) { + printk("ext_read_super: unable to read first free block\n"); + s->s_dev = 0; + unlock_super(s); + return NULL; + } + if (!s->u.ext_sb.s_firstfreeinodenumber) + s->u.ext_sb.s_firstfreeinodeblock = NULL; + else { + block = 2 + (s->u.ext_sb.s_firstfreeinodenumber - 1) / EXT_INODES_PER_BLOCK; + if (!(s->u.ext_sb.s_firstfreeinodeblock = bread(dev, block, BLOCK_SIZE))) { + printk("ext_read_super: unable to read first free inode block\n"); + brelse(s->u.ext_sb.s_firstfreeblock); + s->s_dev = 0; + unlock_super (s); + return NULL; + } + } + unlock_super(s); + /* set up enough so that it can read an inode */ + s->s_dev = dev; + s->s_op = &ext_sops; + if (!(s->s_mounted = iget(s,EXT_ROOT_INO))) { + s->s_dev=0; + printk("EXT-fs: get root inode failed\n"); + return NULL; + } + return s; +} + +void ext_write_super (struct super_block *sb) +{ + struct buffer_head * bh; + struct ext_super_block * es; + + if (!(bh = bread(sb->s_dev, 1, BLOCK_SIZE))) { + printk ("ext_write_super: bread failed\n"); + return; + } + es = (struct ext_super_block *) bh->b_data; + es->s_firstfreeblock = sb->u.ext_sb.s_firstfreeblocknumber; + es->s_freeblockscount = sb->u.ext_sb.s_freeblockscount; + es->s_firstfreeinode = sb->u.ext_sb.s_firstfreeinodenumber; + es->s_freeinodescount = sb->u.ext_sb.s_freeinodescount; + mark_buffer_dirty(bh, 1); + brelse (bh); + sb->s_dirt = 0; +} + +void ext_statfs (struct super_block *sb, struct statfs *buf) +{ + long tmp; + + put_fs_long(EXT_SUPER_MAGIC, &buf->f_type); + put_fs_long(1024, &buf->f_bsize); + put_fs_long(sb->u.ext_sb.s_nzones << sb->u.ext_sb.s_log_zone_size, + &buf->f_blocks); + tmp = ext_count_free_blocks(sb); + put_fs_long(tmp, &buf->f_bfree); + put_fs_long(tmp, &buf->f_bavail); + put_fs_long(sb->u.ext_sb.s_ninodes, &buf->f_files); + put_fs_long(ext_count_free_inodes(sb), &buf->f_ffree); + put_fs_long(EXT_NAME_LEN, &buf->f_namelen); + /* Don't know what value to put in buf->f_fsid */ +} + +#define inode_bmap(inode,nr) ((inode)->u.ext_i.i_data[(nr)]) + +static int block_bmap(struct buffer_head * bh, int nr) +{ + int tmp; + + if (!bh) + return 0; + tmp = ((unsigned long *) bh->b_data)[nr]; + brelse(bh); + return tmp; +} + +int ext_bmap(struct inode * inode,int block) +{ + int i; + + if (block<0) { + printk("ext_bmap: block<0"); + return 0; + } + if (block >= 9+256+256*256+256*256*256) { + printk("ext_bmap: block>big"); + return 0; + } + if (block<9) + return inode_bmap(inode,block); + block -= 9; + if (block<256) { + i = inode_bmap(inode,9); + if (!i) + return 0; + return block_bmap(bread(inode->i_dev,i,BLOCK_SIZE),block); + } + block -= 256; + if (block<256*256) { + i = inode_bmap(inode,10); + if (!i) + return 0; + i = block_bmap(bread(inode->i_dev,i,BLOCK_SIZE),block>>8); + if (!i) + return 0; + return block_bmap(bread(inode->i_dev,i,BLOCK_SIZE),block & 255); + } + block -= 256*256; + i = inode_bmap(inode,11); + if (!i) + return 0; + i = block_bmap(bread(inode->i_dev,i,BLOCK_SIZE),block>>16); + if (!i) + return 0; + i = block_bmap(bread(inode->i_dev,i,BLOCK_SIZE),(block>>8) & 255); + if (!i) + return 0; + return block_bmap(bread(inode->i_dev,i,BLOCK_SIZE),block & 255); +} + +static struct buffer_head * inode_getblk(struct inode * inode, int nr, int create) +{ + int tmp; + unsigned long * p; + struct buffer_head * result; + + p = inode->u.ext_i.i_data + nr; +repeat: + tmp = *p; + if (tmp) { + result = getblk(inode->i_dev, tmp, BLOCK_SIZE); + if (tmp == *p) + return result; + brelse(result); + goto repeat; + } + if (!create) + return NULL; + tmp = ext_new_block(inode->i_sb); + if (!tmp) + return NULL; + result = getblk(inode->i_dev, tmp, BLOCK_SIZE); + if (*p) { + ext_free_block(inode->i_sb,tmp); + brelse(result); + goto repeat; + } + *p = tmp; + inode->i_ctime = CURRENT_TIME; + inode->i_dirt = 1; + return result; +} + +static struct buffer_head * block_getblk(struct inode * inode, + struct buffer_head * bh, int nr, int create) +{ + int tmp; + unsigned long * p; + struct buffer_head * result; + + if (!bh) + return NULL; + if (!bh->b_uptodate) { + ll_rw_block(READ, 1, &bh); + wait_on_buffer(bh); + if (!bh->b_uptodate) { + brelse(bh); + return NULL; + } + } + p = nr + (unsigned long *) bh->b_data; +repeat: + tmp = *p; + if (tmp) { + result = getblk(bh->b_dev, tmp, BLOCK_SIZE); + if (tmp == *p) { + brelse(bh); + return result; + } + brelse(result); + goto repeat; + } + if (!create) { + brelse(bh); + return NULL; + } + tmp = ext_new_block(inode->i_sb); + if (!tmp) { + brelse(bh); + return NULL; + } + result = getblk(bh->b_dev, tmp, BLOCK_SIZE); + if (*p) { + ext_free_block(inode->i_sb,tmp); + brelse(result); + goto repeat; + } + *p = tmp; + mark_buffer_dirty(bh, 1); + brelse(bh); + return result; +} + +struct buffer_head * ext_getblk(struct inode * inode, int block, int create) +{ + struct buffer_head * bh; + + if (block<0) { + printk("ext_getblk: block<0\n"); + return NULL; + } + if (block >= 9+256+256*256+256*256*256) { + printk("ext_getblk: block>big\n"); + return NULL; + } + if (block<9) + return inode_getblk(inode,block,create); + block -= 9; + if (block<256) { + bh = inode_getblk(inode,9,create); + return block_getblk(inode,bh,block,create); + } + block -= 256; + if (block<256*256) { + bh = inode_getblk(inode,10,create); + bh = block_getblk(inode,bh,block>>8,create); + return block_getblk(inode,bh,block & 255,create); + } + block -= 256*256; + bh = inode_getblk(inode,11,create); + bh = block_getblk(inode,bh,block>>16,create); + bh = block_getblk(inode,bh,(block>>8) & 255,create); + return block_getblk(inode,bh,block & 255,create); +} + +struct buffer_head * ext_bread(struct inode * inode, int block, int create) +{ + struct buffer_head * bh; + + bh = ext_getblk(inode,block,create); + if (!bh || bh->b_uptodate) + return bh; + ll_rw_block(READ, 1, &bh); + wait_on_buffer(bh); + if (bh->b_uptodate) + return bh; + brelse(bh); + return NULL; +} + +void ext_read_inode(struct inode * inode) +{ + struct buffer_head * bh; + struct ext_inode * raw_inode; + int block; + + block = 2 + (inode->i_ino-1)/EXT_INODES_PER_BLOCK; + if (!(bh=bread(inode->i_dev, block, BLOCK_SIZE))) + panic("unable to read i-node block"); + raw_inode = ((struct ext_inode *) bh->b_data) + + (inode->i_ino-1)%EXT_INODES_PER_BLOCK; + inode->i_mode = raw_inode->i_mode; + inode->i_uid = raw_inode->i_uid; + inode->i_gid = raw_inode->i_gid; + inode->i_nlink = raw_inode->i_nlinks; + inode->i_size = raw_inode->i_size; + inode->i_mtime = inode->i_atime = inode->i_ctime = raw_inode->i_time; + inode->i_blocks = inode->i_blksize = 0; + if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode)) + inode->i_rdev = raw_inode->i_zone[0]; + else for (block = 0; block < 12; block++) + inode->u.ext_i.i_data[block] = raw_inode->i_zone[block]; + brelse(bh); + inode->i_op = NULL; + if (S_ISREG(inode->i_mode)) + inode->i_op = &ext_file_inode_operations; + else if (S_ISDIR(inode->i_mode)) + inode->i_op = &ext_dir_inode_operations; + else if (S_ISLNK(inode->i_mode)) + inode->i_op = &ext_symlink_inode_operations; + else if (S_ISCHR(inode->i_mode)) + inode->i_op = &chrdev_inode_operations; + else if (S_ISBLK(inode->i_mode)) + inode->i_op = &blkdev_inode_operations; + else if (S_ISFIFO(inode->i_mode)) + init_fifo(inode); +} + +static struct buffer_head * ext_update_inode(struct inode * inode) +{ + struct buffer_head * bh; + struct ext_inode * raw_inode; + int block; + + block = 2 + (inode->i_ino-1)/EXT_INODES_PER_BLOCK; + if (!(bh=bread(inode->i_dev, block, BLOCK_SIZE))) + panic("unable to read i-node block"); + raw_inode = ((struct ext_inode *)bh->b_data) + + (inode->i_ino-1)%EXT_INODES_PER_BLOCK; + raw_inode->i_mode = inode->i_mode; + raw_inode->i_uid = inode->i_uid; + raw_inode->i_gid = inode->i_gid; + raw_inode->i_nlinks = inode->i_nlink; + raw_inode->i_size = inode->i_size; + raw_inode->i_time = inode->i_mtime; + if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode)) + raw_inode->i_zone[0] = inode->i_rdev; + else for (block = 0; block < 12; block++) + raw_inode->i_zone[block] = inode->u.ext_i.i_data[block]; + mark_buffer_dirty(bh, 1); + inode->i_dirt=0; + return bh; +} + +void ext_write_inode(struct inode * inode) +{ + struct buffer_head *bh; + bh = ext_update_inode (inode); + brelse(bh); +} + +int ext_sync_inode (struct inode *inode) +{ + int err = 0; + struct buffer_head *bh; + + bh = ext_update_inode(inode); + if (bh && bh->b_dirt) + { + ll_rw_block(WRITE, 1, &bh); + wait_on_buffer(bh); + if (bh->b_req && !bh->b_uptodate) + { + printk ("IO error syncing ext inode [%04x:%08lx]\n", + inode->i_dev, inode->i_ino); + err = -1; + } + } + else if (!bh) + err = -1; + brelse (bh); + return err; +} + diff --git a/fs/ext/namei.c b/fs/ext/namei.c new file mode 100644 index 000000000..85a411e94 --- /dev/null +++ b/fs/ext/namei.c @@ -0,0 +1,893 @@ +/* + * linux/fs/ext/namei.c + * + * Copyright (C) 1992 Remy Card (card@masi.ibp.fr) + * + * from + * + * linux/fs/minix/namei.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + */ + +#include <linux/sched.h> +#include <linux/ext_fs.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/stat.h> +#include <linux/fcntl.h> +#include <linux/errno.h> + +#include <asm/segment.h> + +/* + * comment out this line if you want names > EXT_NAME_LEN chars to be + * truncated. Else they will be disallowed. + */ +/* #define NO_TRUNCATE */ + +/* + * EXT_DIR_PAD defines the directory entries boundaries + * + * NOTE: It must be a power of 2 and must be greater or equal than 8 + * because a directory entry needs 8 bytes for its fixed part + * (4 bytes for the inode, 2 bytes for the entry length and 2 bytes + * for the name length) + */ +#define EXT_DIR_PAD 8 + +/* + * + * EXT_DIR_MIN_SIZE is the minimal size of a directory entry + * + * During allocations, a directory entry is split into 2 ones + * *ONLY* if the size of the unused part is greater than or + * equal to EXT_DIR_MIN_SIZE + */ +#define EXT_DIR_MIN_SIZE 12 + +/* + * ok, we cannot use strncmp, as the name is not in our data space. + * Thus we'll have to use ext_match. No big problem. Match also makes + * some sanity tests. + * + * NOTE! unlike strncmp, ext_match returns 1 for success, 0 for failure. + */ +static int ext_match(int len,const char * name,struct ext_dir_entry * de) +{ + if (!de || !de->inode || len > EXT_NAME_LEN) + return 0; + /* "" means "." ---> so paths like "/usr/lib//libc.a" work */ + if (!len && (de->name[0]=='.') && (de->name[1]=='\0')) + return 1; + if (len != de->name_len) + return 0; + return !memcmp(name, de->name, len); +} + +/* + * ext_find_entry() + * + * finds an entry in the specified directory with the wanted name. It + * returns the cache buffer in which the entry was found, and the entry + * itself (as a parameter - res_dir). It does NOT read the inode of the + * entry - you'll have to do that yourself if you want to. + * + * addition for the ext file system : this function returns the previous + * and next directory entries in the parameters prev_dir and next_dir + */ +static struct buffer_head * ext_find_entry(struct inode * dir, + const char * name, int namelen, struct ext_dir_entry ** res_dir, + struct ext_dir_entry ** prev_dir, struct ext_dir_entry ** next_dir) +{ + long offset; + struct buffer_head * bh; + struct ext_dir_entry * de; + + *res_dir = NULL; + if (!dir) + return NULL; +#ifdef NO_TRUNCATE + if (namelen > EXT_NAME_LEN) + return NULL; +#else + if (namelen > EXT_NAME_LEN) + namelen = EXT_NAME_LEN; +#endif + bh = ext_bread(dir,0,0); + if (!bh) + return NULL; + if (prev_dir) + *prev_dir = NULL; + if (next_dir) + *next_dir = NULL; + offset = 0; + de = (struct ext_dir_entry *) bh->b_data; + while (offset < dir->i_size) { + if ((char *)de >= BLOCK_SIZE+bh->b_data) { + brelse(bh); + bh = NULL; + bh = ext_bread(dir,offset>>BLOCK_SIZE_BITS,0); + if (!bh) + continue; + de = (struct ext_dir_entry *) bh->b_data; + if (prev_dir) + *prev_dir = NULL; + } + if (de->rec_len < 8 || de->rec_len % 8 != 0 || + de->rec_len < de->name_len + 8 || + (((char *) de) + de->rec_len-1 >= BLOCK_SIZE+bh->b_data)) { + printk ("ext_find_entry: bad dir entry\n"); + printk ("dev=%d, dir=%ld, offset=%ld, rec_len=%d, name_len=%d\n", + dir->i_dev, dir->i_ino, offset, de->rec_len, de->name_len); + de = (struct ext_dir_entry *) (bh->b_data+BLOCK_SIZE); + offset = ((offset / BLOCK_SIZE) + 1) * BLOCK_SIZE; + continue; +/* brelse (bh); + return NULL; */ + } + if (ext_match(namelen,name,de)) { + *res_dir = de; + if (next_dir) + if (offset + de->rec_len < dir->i_size && + ((char *)de) + de->rec_len < BLOCK_SIZE+bh->b_data) + *next_dir = (struct ext_dir_entry *) + ((char *) de + de->rec_len); + else + *next_dir = NULL; + return bh; + } + offset += de->rec_len; + if (prev_dir) + *prev_dir = de; + de = (struct ext_dir_entry *) ((char *) de + de->rec_len); + } + brelse(bh); + return NULL; +} + +int ext_lookup(struct inode * dir,const char * name, int len, + struct inode ** result) +{ + int ino; + struct ext_dir_entry * de; + struct buffer_head * bh; + + *result = NULL; + if (!dir) + return -ENOENT; + if (!S_ISDIR(dir->i_mode)) { + iput(dir); + return -ENOENT; + } + if (!(bh = ext_find_entry(dir,name,len,&de,NULL,NULL))) { + iput(dir); + return -ENOENT; + } + ino = de->inode; + brelse(bh); + if (!(*result = iget(dir->i_sb,ino))) { + iput(dir); + return -EACCES; + } + iput(dir); + return 0; +} + +/* + * ext_add_entry() + * + * adds a file entry to the specified directory, using the same + * semantics as ext_find_entry(). It returns NULL if it failed. + * + * NOTE!! The inode part of 'de' is left at 0 - which means you + * may not sleep between calling this and putting something into + * the entry, as someone else might have used it while you slept. + */ +static struct buffer_head * ext_add_entry(struct inode * dir, + const char * name, int namelen, struct ext_dir_entry ** res_dir) +{ + int i; + long offset; + unsigned short rec_len; + struct buffer_head * bh; + struct ext_dir_entry * de, * de1; + + *res_dir = NULL; + if (!dir) + return NULL; +#ifdef NO_TRUNCATE + if (namelen > EXT_NAME_LEN) + return NULL; +#else + if (namelen > EXT_NAME_LEN) + namelen = EXT_NAME_LEN; +#endif + if (!namelen) + return NULL; + bh = ext_bread(dir,0,0); + if (!bh) + return NULL; + rec_len = ((8 + namelen + EXT_DIR_PAD - 1) / EXT_DIR_PAD) * EXT_DIR_PAD; + offset = 0; + de = (struct ext_dir_entry *) bh->b_data; + while (1) { + if ((char *)de >= BLOCK_SIZE+bh->b_data && offset < dir->i_size) { +#ifdef EXTFS_DEBUG +printk ("ext_add_entry: skipping to next block\n"); +#endif + brelse(bh); + bh = NULL; + bh = ext_bread(dir,offset>>BLOCK_SIZE_BITS,0); + if (!bh) + return NULL; + de = (struct ext_dir_entry *) bh->b_data; + } + if (offset >= dir->i_size) { + /* Check that the directory entry fits in the block */ + if (offset % BLOCK_SIZE == 0 || + (BLOCK_SIZE - (offset % BLOCK_SIZE)) < rec_len) { + if ((offset % BLOCK_SIZE) != 0) { + /* If the entry does not fit in the + block, the remainder of the block + becomes an unused entry */ + de->inode = 0; + de->rec_len = BLOCK_SIZE + - (offset & (BLOCK_SIZE - 1)); + de->name_len = 0; + offset += de->rec_len; + dir->i_size += de->rec_len; + dir->i_dirt = 1; +#if 0 + dir->i_ctime = CURRENT_TIME; +#endif + mark_buffer_dirty(bh, 1); + } + brelse (bh); + bh = NULL; +#ifdef EXTFS_DEBUG +printk ("ext_add_entry : creating next block\n"); +#endif + bh = ext_bread(dir,offset>>BLOCK_SIZE_BITS,1); + if (!bh) + return NULL; /* Other thing to do ??? */ + de = (struct ext_dir_entry *) bh->b_data; + } + /* Allocate the entry */ + de->inode=0; + de->rec_len = rec_len; + dir->i_size += de->rec_len; + dir->i_dirt = 1; +#if 0 + dir->i_ctime = CURRENT_TIME; +#endif + } + if (de->rec_len < 8 || de->rec_len % 4 != 0 || + de->rec_len < de->name_len + 8 || + (((char *) de) + de->rec_len-1 >= BLOCK_SIZE+bh->b_data)) { + printk ("ext_addr_entry: bad dir entry\n"); + printk ("dev=%d, dir=%ld, offset=%ld, rec_len=%d, name_len=%d\n", + dir->i_dev, dir->i_ino, offset, de->rec_len, de->name_len); + brelse (bh); + return NULL; + } + if (!de->inode && de->rec_len >= rec_len) { + if (de->rec_len > rec_len + && de->rec_len - rec_len >= EXT_DIR_MIN_SIZE) { + /* The found entry is too big : it is split + into 2 ones : + - the 1st one will be used to hold the name, + - the 2nd one is unused */ + de1 = (struct ext_dir_entry *) ((char *) de + rec_len); + de1->inode = 0; + de1->rec_len = de->rec_len - rec_len; + de1->name_len = 0; + de->rec_len = rec_len; + } + dir->i_mtime = dir->i_ctime = CURRENT_TIME; + de->name_len = namelen; + for (i=0; i < namelen ; i++) + de->name[i] = name[i]; + mark_buffer_dirty(bh, 1); + *res_dir = de; + return bh; + } + offset += de->rec_len; + de = (struct ext_dir_entry *) ((char *) de + de->rec_len); + } + brelse(bh); + return NULL; +} + +int ext_create(struct inode * dir,const char * name, int len, int mode, + struct inode ** result) +{ + struct inode * inode; + struct buffer_head * bh; + struct ext_dir_entry * de; + + *result = NULL; + if (!dir) + return -ENOENT; + inode = ext_new_inode(dir); + if (!inode) { + iput(dir); + return -ENOSPC; + } + inode->i_op = &ext_file_inode_operations; + inode->i_mode = mode; + inode->i_dirt = 1; + bh = ext_add_entry(dir,name,len,&de); + if (!bh) { + inode->i_nlink--; + inode->i_dirt = 1; + iput(inode); + iput(dir); + return -ENOSPC; + } + de->inode = inode->i_ino; + mark_buffer_dirty(bh, 1); + brelse(bh); + iput(dir); + *result = inode; + return 0; +} + +int ext_mknod(struct inode * dir, const char * name, int len, int mode, int rdev) +{ + struct inode * inode; + struct buffer_head * bh; + struct ext_dir_entry * de; + + if (!dir) + return -ENOENT; + bh = ext_find_entry(dir,name,len,&de,NULL,NULL); + if (bh) { + brelse(bh); + iput(dir); + return -EEXIST; + } + inode = ext_new_inode(dir); + if (!inode) { + iput(dir); + return -ENOSPC; + } + inode->i_uid = current->fsuid; + inode->i_mode = mode; + inode->i_op = NULL; + if (S_ISREG(inode->i_mode)) + inode->i_op = &ext_file_inode_operations; + else if (S_ISDIR(inode->i_mode)) { + inode->i_op = &ext_dir_inode_operations; + if (dir->i_mode & S_ISGID) + inode->i_mode |= S_ISGID; + } + else if (S_ISLNK(inode->i_mode)) + inode->i_op = &ext_symlink_inode_operations; + else if (S_ISCHR(inode->i_mode)) + inode->i_op = &chrdev_inode_operations; + else if (S_ISBLK(inode->i_mode)) + inode->i_op = &blkdev_inode_operations; + else if (S_ISFIFO(inode->i_mode)) + init_fifo(inode); + if (S_ISBLK(mode) || S_ISCHR(mode)) + inode->i_rdev = rdev; +#if 0 + inode->i_mtime = inode->i_atime = CURRENT_TIME; +#endif + inode->i_dirt = 1; + bh = ext_add_entry(dir,name,len,&de); + if (!bh) { + inode->i_nlink--; + inode->i_dirt = 1; + iput(inode); + iput(dir); + return -ENOSPC; + } + de->inode = inode->i_ino; + mark_buffer_dirty(bh, 1); + brelse(bh); + iput(dir); + iput(inode); + return 0; +} + +int ext_mkdir(struct inode * dir, const char * name, int len, int mode) +{ + struct inode * inode; + struct buffer_head * bh, *dir_block; + struct ext_dir_entry * de; + + bh = ext_find_entry(dir,name,len,&de,NULL,NULL); + if (bh) { + brelse(bh); + iput(dir); + return -EEXIST; + } + inode = ext_new_inode(dir); + if (!inode) { + iput(dir); + return -ENOSPC; + } + inode->i_op = &ext_dir_inode_operations; + inode->i_size = 2 * 16; /* Each entry is coded on 16 bytes for "." and ".." + - 4 bytes for the inode number, + - 2 bytes for the record length + - 2 bytes for the name length + - 8 bytes for the name */ +#if 0 + inode->i_mtime = inode->i_atime = CURRENT_TIME; +#endif + dir_block = ext_bread(inode,0,1); + if (!dir_block) { + iput(dir); + inode->i_nlink--; + inode->i_dirt = 1; + iput(inode); + return -ENOSPC; + } + de = (struct ext_dir_entry *) dir_block->b_data; + de->inode=inode->i_ino; + de->rec_len=16; + de->name_len=1; + strcpy(de->name,"."); + de = (struct ext_dir_entry *) ((char *) de + de->rec_len); + de->inode = dir->i_ino; + de->rec_len=16; + de->name_len=2; + strcpy(de->name,".."); + inode->i_nlink = 2; + mark_buffer_dirty(dir_block, 1); + brelse(dir_block); + inode->i_mode = S_IFDIR | (mode & 0777 & ~current->fs->umask); + if (dir->i_mode & S_ISGID) + inode->i_mode |= S_ISGID; + inode->i_dirt = 1; + bh = ext_add_entry(dir,name,len,&de); + if (!bh) { + iput(dir); + inode->i_nlink=0; + iput(inode); + return -ENOSPC; + } + de->inode = inode->i_ino; + mark_buffer_dirty(bh, 1); + dir->i_nlink++; + dir->i_dirt = 1; + iput(dir); + iput(inode); + brelse(bh); + return 0; +} + +/* + * routine to check that the specified directory is empty (for rmdir) + */ +static int empty_dir(struct inode * inode) +{ + unsigned long offset; + struct buffer_head * bh; + struct ext_dir_entry * de, * de1; + + if (inode->i_size < 2 * 12 || !(bh = ext_bread(inode,0,0))) { + printk("warning - bad directory on dev %04x\n",inode->i_dev); + return 1; + } + de = (struct ext_dir_entry *) bh->b_data; + de1 = (struct ext_dir_entry *) ((char *) de + de->rec_len); + if (de->inode != inode->i_ino || !de1->inode || + strcmp(".",de->name) || strcmp("..",de1->name)) { + printk("warning - bad directory on dev %04x\n",inode->i_dev); + return 1; + } + offset = de->rec_len + de1->rec_len; + de = (struct ext_dir_entry *) ((char *) de1 + de1->rec_len); + while (offset < inode->i_size ) { + if ((void *) de >= (void *) (bh->b_data+BLOCK_SIZE)) { + brelse(bh); + bh = ext_bread(inode, offset >> BLOCK_SIZE_BITS,1); + if (!bh) { + offset += BLOCK_SIZE; + continue; + } + de = (struct ext_dir_entry *) bh->b_data; + } + if (de->rec_len < 8 || de->rec_len %4 != 0 || + de->rec_len < de->name_len + 8) { + printk ("empty_dir: bad dir entry\n"); + printk ("dev=%d, dir=%ld, offset=%ld, rec_len=%d, name_len=%d\n", + inode->i_dev, inode->i_ino, offset, de->rec_len, de->name_len); + brelse (bh); + return 1; + } + if (de->inode) { + brelse(bh); + return 0; + } + offset += de->rec_len; + de = (struct ext_dir_entry *) ((char *) de + de->rec_len); + } + brelse(bh); + return 1; +} + +static inline void ext_merge_entries (struct ext_dir_entry * de, + struct ext_dir_entry * pde, struct ext_dir_entry * nde) +{ + if (nde && !nde->inode) + de->rec_len += nde->rec_len; + if (pde && !pde->inode) + pde->rec_len += de->rec_len; +} + +int ext_rmdir(struct inode * dir, const char * name, int len) +{ + int retval; + struct inode * inode; + struct buffer_head * bh; + struct ext_dir_entry * de, * pde, * nde; + + inode = NULL; + bh = ext_find_entry(dir,name,len,&de,&pde,&nde); + retval = -ENOENT; + if (!bh) + goto end_rmdir; + retval = -EPERM; + if (!(inode = iget(dir->i_sb, de->inode))) + goto end_rmdir; + if ((dir->i_mode & S_ISVTX) && !fsuser() && + current->fsuid != inode->i_uid && + current->fsuid != dir->i_uid) + goto end_rmdir; + if (inode->i_dev != dir->i_dev) + goto end_rmdir; + if (inode == dir) /* we may not delete ".", but "../dir" is ok */ + goto end_rmdir; + if (!S_ISDIR(inode->i_mode)) { + retval = -ENOTDIR; + goto end_rmdir; + } + if (!empty_dir(inode)) { + retval = -ENOTEMPTY; + goto end_rmdir; + } + if (inode->i_count > 1) { + retval = -EBUSY; + goto end_rmdir; + } + if (inode->i_nlink != 2) + printk("empty directory has nlink!=2 (%d)\n",inode->i_nlink); + de->inode = 0; + de->name_len = 0; + ext_merge_entries (de, pde, nde); + mark_buffer_dirty(bh, 1); + inode->i_nlink=0; + inode->i_dirt=1; + dir->i_nlink--; + inode->i_ctime = dir->i_ctime = dir->i_mtime = CURRENT_TIME; + dir->i_dirt=1; + retval = 0; +end_rmdir: + iput(dir); + iput(inode); + brelse(bh); + return retval; +} + +int ext_unlink(struct inode * dir, const char * name, int len) +{ + int retval; + struct inode * inode; + struct buffer_head * bh; + struct ext_dir_entry * de, * pde, * nde; + + retval = -ENOENT; + inode = NULL; + bh = ext_find_entry(dir,name,len,&de,&pde,&nde); + if (!bh) + goto end_unlink; + if (!(inode = iget(dir->i_sb, de->inode))) + goto end_unlink; + retval = -EPERM; + if ((dir->i_mode & S_ISVTX) && !fsuser() && + current->fsuid != inode->i_uid && + current->fsuid != dir->i_uid) + goto end_unlink; + if (S_ISDIR(inode->i_mode)) + goto end_unlink; + if (!inode->i_nlink) { + printk("Deleting nonexistent file (%04x:%ld), %d\n", + inode->i_dev,inode->i_ino,inode->i_nlink); + inode->i_nlink=1; + } + de->inode = 0; + de->name_len = 0; + ext_merge_entries (de, pde, nde); + mark_buffer_dirty(bh, 1); + inode->i_nlink--; + inode->i_dirt = 1; + inode->i_ctime = CURRENT_TIME; + dir->i_ctime = dir->i_mtime = inode->i_ctime; + dir->i_dirt = 1; + retval = 0; +end_unlink: + brelse(bh); + iput(inode); + iput(dir); + return retval; +} + +int ext_symlink(struct inode * dir, const char * name, int len, const char * symname) +{ + struct ext_dir_entry * de; + struct inode * inode = NULL; + struct buffer_head * bh = NULL, * name_block = NULL; + int i; + char c; + + if (!(inode = ext_new_inode(dir))) { + iput(dir); + return -ENOSPC; + } + inode->i_mode = S_IFLNK | 0777; + inode->i_op = &ext_symlink_inode_operations; + name_block = ext_bread(inode,0,1); + if (!name_block) { + iput(dir); + inode->i_nlink--; + inode->i_dirt = 1; + iput(inode); + return -ENOSPC; + } + i = 0; + while (i < 1023 && (c = *(symname++))) + name_block->b_data[i++] = c; + name_block->b_data[i] = 0; + mark_buffer_dirty(name_block, 1); + brelse(name_block); + inode->i_size = i; + inode->i_dirt = 1; + bh = ext_find_entry(dir,name,len,&de,NULL,NULL); + if (bh) { + inode->i_nlink--; + inode->i_dirt = 1; + iput(inode); + brelse(bh); + iput(dir); + return -EEXIST; + } + bh = ext_add_entry(dir,name,len,&de); + if (!bh) { + inode->i_nlink--; + inode->i_dirt = 1; + iput(inode); + iput(dir); + return -ENOSPC; + } + de->inode = inode->i_ino; + mark_buffer_dirty(bh, 1); + brelse(bh); + iput(dir); + iput(inode); + return 0; +} + +int ext_link(struct inode * oldinode, struct inode * dir, const char * name, int len) +{ + struct ext_dir_entry * de; + struct buffer_head * bh; + + if (S_ISDIR(oldinode->i_mode)) { + iput(oldinode); + iput(dir); + return -EPERM; + } + if (oldinode->i_nlink > 32000) { + iput(oldinode); + iput(dir); + return -EMLINK; + } + bh = ext_find_entry(dir,name,len,&de,NULL,NULL); + if (bh) { + brelse(bh); + iput(dir); + iput(oldinode); + return -EEXIST; + } + bh = ext_add_entry(dir,name,len,&de); + if (!bh) { + iput(dir); + iput(oldinode); + return -ENOSPC; + } + de->inode = oldinode->i_ino; + mark_buffer_dirty(bh, 1); + brelse(bh); + iput(dir); + oldinode->i_nlink++; + oldinode->i_ctime = CURRENT_TIME; + oldinode->i_dirt = 1; + iput(oldinode); + return 0; +} + +static int subdir(struct inode * new_inode, struct inode * old_inode) +{ + int ino; + int result; + + new_inode->i_count++; + result = 0; + for (;;) { + if (new_inode == old_inode) { + result = 1; + break; + } + if (new_inode->i_dev != old_inode->i_dev) + break; + ino = new_inode->i_ino; + if (ext_lookup(new_inode,"..",2,&new_inode)) + break; + if (new_inode->i_ino == ino) + break; + } + iput(new_inode); + return result; +} + +#define PARENT_INO(buffer) \ +((struct ext_dir_entry *) ((char *) buffer + \ +((struct ext_dir_entry *) buffer)->rec_len))->inode + +#define PARENT_NAME(buffer) \ +((struct ext_dir_entry *) ((char *) buffer + \ +((struct ext_dir_entry *) buffer)->rec_len))->name + +/* + * rename uses retrying to avoid race-conditions: at least they should be minimal. + * it tries to allocate all the blocks, then sanity-checks, and if the sanity- + * checks fail, it tries to restart itself again. Very practical - no changes + * are done until we know everything works ok.. and then all the changes can be + * done in one fell swoop when we have claimed all the buffers needed. + * + * Anybody can rename anything with this: the permission checks are left to the + * higher-level routines. + */ +static int do_ext_rename(struct inode * old_dir, const char * old_name, int old_len, + struct inode * new_dir, const char * new_name, int new_len) +{ + struct inode * old_inode, * new_inode; + struct buffer_head * old_bh, * new_bh, * dir_bh; + struct ext_dir_entry * old_de, * new_de, * pde, * nde; + int retval; + + goto start_up; +try_again: + brelse(old_bh); + brelse(new_bh); + brelse(dir_bh); + iput(old_inode); + iput(new_inode); + current->counter = 0; + schedule(); +start_up: + old_inode = new_inode = NULL; + old_bh = new_bh = dir_bh = NULL; + old_bh = ext_find_entry(old_dir,old_name,old_len,&old_de,&pde,&nde); + retval = -ENOENT; + if (!old_bh) + goto end_rename; + old_inode = __iget(old_dir->i_sb, old_de->inode,0); /* don't cross mnt-points */ + if (!old_inode) + goto end_rename; + retval = -EPERM; + if ((old_dir->i_mode & S_ISVTX) && + current->fsuid != old_inode->i_uid && + current->fsuid != old_dir->i_uid && !fsuser()) + goto end_rename; + new_bh = ext_find_entry(new_dir,new_name,new_len,&new_de,NULL,NULL); + if (new_bh) { + new_inode = __iget(new_dir->i_sb, new_de->inode,0); /* don't cross mnt-points */ + if (!new_inode) { + brelse(new_bh); + new_bh = NULL; + } + } + if (new_inode == old_inode) { + retval = 0; + goto end_rename; + } + if (new_inode && S_ISDIR(new_inode->i_mode)) { + retval = -EEXIST; + goto end_rename; + } + retval = -EPERM; + if (new_inode && (new_dir->i_mode & S_ISVTX) && + current->fsuid != new_inode->i_uid && + current->fsuid != new_dir->i_uid && !fsuser()) + goto end_rename; + if (S_ISDIR(old_inode->i_mode)) { + retval = -EEXIST; + if (new_bh) + goto end_rename; + retval = -EACCES; + if (!permission(old_inode, MAY_WRITE)) + goto end_rename; + retval = -EINVAL; + if (subdir(new_dir, old_inode)) + goto end_rename; + retval = -EIO; + dir_bh = ext_bread(old_inode,0,0); + if (!dir_bh) + goto end_rename; + if (PARENT_INO(dir_bh->b_data) != old_dir->i_ino) + goto end_rename; + } + if (!new_bh) + new_bh = ext_add_entry(new_dir,new_name,new_len,&new_de); + retval = -ENOSPC; + if (!new_bh) + goto end_rename; +/* sanity checking before doing the rename - avoid races */ + if (new_inode && (new_de->inode != new_inode->i_ino)) + goto try_again; + if (new_de->inode && !new_inode) + goto try_again; + if (old_de->inode != old_inode->i_ino) + goto try_again; +/* ok, that's it */ + old_de->inode = 0; + old_de->name_len = 0; + new_de->inode = old_inode->i_ino; + ext_merge_entries (old_de, pde, nde); + if (new_inode) { + new_inode->i_nlink--; + new_inode->i_dirt = 1; + } + mark_buffer_dirty(old_bh, 1); + mark_buffer_dirty(new_bh, 1); + if (dir_bh) { + PARENT_INO(dir_bh->b_data) = new_dir->i_ino; + mark_buffer_dirty(dir_bh, 1); + old_dir->i_nlink--; + new_dir->i_nlink++; + old_dir->i_dirt = 1; + new_dir->i_dirt = 1; + } + retval = 0; +end_rename: + brelse(dir_bh); + brelse(old_bh); + brelse(new_bh); + iput(old_inode); + iput(new_inode); + iput(old_dir); + iput(new_dir); + return retval; +} + +/* + * Ok, rename also locks out other renames, as they can change the parent of + * a directory, and we don't want any races. Other races are checked for by + * "do_rename()", which restarts if there are inconsistencies. + * + * Note that there is no race between different filesystems: it's only within + * the same device that races occur: many renames can happen at once, as long + * as they are on different partitions. + */ +int ext_rename(struct inode * old_dir, const char * old_name, int old_len, + struct inode * new_dir, const char * new_name, int new_len) +{ + static struct wait_queue * wait = NULL; + static int lock = 0; + int result; + + while (lock) + sleep_on(&wait); + lock = 1; + result = do_ext_rename(old_dir, old_name, old_len, + new_dir, new_name, new_len); + lock = 0; + wake_up(&wait); + return result; +} diff --git a/fs/ext/symlink.c b/fs/ext/symlink.c new file mode 100644 index 000000000..8c84bc622 --- /dev/null +++ b/fs/ext/symlink.c @@ -0,0 +1,108 @@ +/* + * linux/fs/ext/symlink.c + * + * Copyright (C) 1992 Remy Card (card@masi.ibp.fr) + * + * from + * + * linux/fs/minix/symlink.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + * + * ext symlink handling code + */ + +#include <asm/segment.h> + +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/ext_fs.h> +#include <linux/stat.h> + +static int ext_readlink(struct inode *, char *, int); +static int ext_follow_link(struct inode *, struct inode *, int, int, struct inode **); + +/* + * symlinks can't do much... + */ +struct inode_operations ext_symlink_inode_operations = { + NULL, /* no file-operations */ + NULL, /* create */ + NULL, /* lookup */ + NULL, /* link */ + NULL, /* unlink */ + NULL, /* symlink */ + NULL, /* mkdir */ + NULL, /* rmdir */ + NULL, /* mknod */ + NULL, /* rename */ + ext_readlink, /* readlink */ + ext_follow_link, /* follow_link */ + NULL, /* bmap */ + NULL, /* truncate */ + NULL /* permission */ +}; + +static int ext_follow_link(struct inode * dir, struct inode * inode, + int flag, int mode, struct inode ** res_inode) +{ + int error; + struct buffer_head * bh; + + *res_inode = NULL; + if (!dir) { + dir = current->fs->root; + dir->i_count++; + } + if (!inode) { + iput(dir); + return -ENOENT; + } + if (!S_ISLNK(inode->i_mode)) { + iput(dir); + *res_inode = inode; + return 0; + } + if (current->link_count > 5) { + iput(dir); + iput(inode); + return -ELOOP; + } + if (!(bh = ext_bread(inode, 0, 0))) { + iput(inode); + iput(dir); + return -EIO; + } + iput(inode); + current->link_count++; + error = open_namei(bh->b_data,flag,mode,res_inode,dir); + current->link_count--; + brelse(bh); + return error; +} + +static int ext_readlink(struct inode * inode, char * buffer, int buflen) +{ + struct buffer_head * bh; + int i; + char c; + + if (!S_ISLNK(inode->i_mode)) { + iput(inode); + return -EINVAL; + } + if (buflen > 1023) + buflen = 1023; + bh = ext_bread(inode, 0, 0); + iput(inode); + if (!bh) + return 0; + i = 0; + while (i<buflen && (c = bh->b_data[i])) { + i++; + put_fs_byte(c,buffer++); + } + brelse(bh); + return i; +} diff --git a/fs/ext/truncate.c b/fs/ext/truncate.c new file mode 100644 index 000000000..a2b485821 --- /dev/null +++ b/fs/ext/truncate.c @@ -0,0 +1,252 @@ +/* + * linux/fs/ext/truncate.c + * + * Copyright (C) 1992 Remy Card (card@masi.ibp.fr) + * + * from + * + * linux/fs/minix/truncate.c + * + * Copyright (C) 1991, 1992 Linus Torvalds + */ + +#include <linux/sched.h> +#include <linux/ext_fs.h> +#include <linux/stat.h> +#include <linux/fcntl.h> +#include <linux/errno.h> + +/* + * Truncate has the most races in the whole filesystem: coding it is + * a pain in the a**. Especially as I don't do any locking... + * + * The code may look a bit weird, but that's just because I've tried to + * handle things like file-size changes in a somewhat graceful manner. + * Anyway, truncating a file at the same time somebody else writes to it + * is likely to result in pretty weird behaviour... + * + * The new code handles normal truncates (size = 0) as well as the more + * general case (size = XXX). I hope. + */ + +static int trunc_direct(struct inode * inode) +{ + int i, tmp; + unsigned long * p; + struct buffer_head * bh; + int retry = 0; +#define DIRECT_BLOCK ((inode->i_size + 1023) >> 10) + +repeat: + for (i = DIRECT_BLOCK ; i < 9 ; i++) { + p = inode->u.ext_i.i_data+i; + if (!(tmp = *p)) + continue; + bh = getblk(inode->i_dev,tmp,BLOCK_SIZE); + if (i < DIRECT_BLOCK) { + brelse(bh); + goto repeat; + } + if ((bh && bh->b_count != 1) || tmp != *p) { + retry = 1; + brelse(bh); + continue; + } + *p = 0; + inode->i_dirt = 1; + brelse(bh); + ext_free_block(inode->i_sb,tmp); + } + return retry; +} + +static int trunc_indirect(struct inode * inode, int offset, unsigned long * p) +{ + int i, tmp; + struct buffer_head * bh; + struct buffer_head * ind_bh; + unsigned long * ind; + int retry = 0; +#define INDIRECT_BLOCK (DIRECT_BLOCK-offset) + + tmp = *p; + if (!tmp) + return 0; + ind_bh = bread(inode->i_dev, tmp, BLOCK_SIZE); + if (tmp != *p) { + brelse(ind_bh); + return 1; + } + if (!ind_bh) { + *p = 0; + return 0; + } +repeat: + for (i = INDIRECT_BLOCK ; i < 256 ; i++) { + if (i < 0) + i = 0; + if (i < INDIRECT_BLOCK) + goto repeat; + ind = i+(unsigned long *) ind_bh->b_data; + tmp = *ind; + if (!tmp) + continue; + bh = getblk(inode->i_dev,tmp,BLOCK_SIZE); + if (i < INDIRECT_BLOCK) { + brelse(bh); + goto repeat; + } + if ((bh && bh->b_count != 1) || tmp != *ind) { + retry = 1; + brelse(bh); + continue; + } + *ind = 0; + mark_buffer_dirty(ind_bh, 1); + brelse(bh); + ext_free_block(inode->i_sb,tmp); + } + ind = (unsigned long *) ind_bh->b_data; + for (i = 0; i < 256; i++) + if (*(ind++)) + break; + if (i >= 256) + if (ind_bh->b_count != 1) + retry = 1; + else { + tmp = *p; + *p = 0; + inode->i_dirt = 1; + ext_free_block(inode->i_sb,tmp); + } + brelse(ind_bh); + return retry; +} + +static int trunc_dindirect(struct inode * inode, int offset, unsigned long * p) +{ + int i,tmp; + struct buffer_head * dind_bh; + unsigned long * dind; + int retry = 0; +#define DINDIRECT_BLOCK ((DIRECT_BLOCK-offset)>>8) + + tmp = *p; + if (!tmp) + return 0; + dind_bh = bread(inode->i_dev, tmp, BLOCK_SIZE); + if (tmp != *p) { + brelse(dind_bh); + return 1; + } + if (!dind_bh) { + *p = 0; + return 0; + } +repeat: + for (i = DINDIRECT_BLOCK ; i < 256 ; i ++) { + if (i < 0) + i = 0; + if (i < DINDIRECT_BLOCK) + goto repeat; + dind = i+(unsigned long *) dind_bh->b_data; + tmp = *dind; + if (!tmp) + continue; + retry |= trunc_indirect(inode,offset+(i<<8),dind); + mark_buffer_dirty(dind_bh, 1); + } + dind = (unsigned long *) dind_bh->b_data; + for (i = 0; i < 256; i++) + if (*(dind++)) + break; + if (i >= 256) + if (dind_bh->b_count != 1) + retry = 1; + else { + tmp = *p; + *p = 0; + inode->i_dirt = 1; + ext_free_block(inode->i_sb,tmp); + } + brelse(dind_bh); + return retry; +} + +static int trunc_tindirect(struct inode * inode) +{ + int i,tmp; + struct buffer_head * tind_bh; + unsigned long * tind, * p; + int retry = 0; +#define TINDIRECT_BLOCK ((DIRECT_BLOCK-(256*256+256+9))>>16) + + p = inode->u.ext_i.i_data+11; + if (!(tmp = *p)) + return 0; + tind_bh = bread(inode->i_dev, tmp, BLOCK_SIZE); + if (tmp != *p) { + brelse(tind_bh); + return 1; + } + if (!tind_bh) { + *p = 0; + return 0; + } +repeat: + for (i = TINDIRECT_BLOCK ; i < 256 ; i ++) { + if (i < 0) + i = 0; + if (i < TINDIRECT_BLOCK) + goto repeat; + tind = i+(unsigned long *) tind_bh->b_data; + retry |= trunc_dindirect(inode,9+256+256*256+(i<<16),tind); + mark_buffer_dirty(tind_bh, 1); + } + tind = (unsigned long *) tind_bh->b_data; + for (i = 0; i < 256; i++) + if (*(tind++)) + break; + if (i >= 256) + if (tind_bh->b_count != 1) + retry = 1; + else { + tmp = *p; + *p = 0; + inode->i_dirt = 1; + ext_free_block(inode->i_sb,tmp); + } + brelse(tind_bh); + return retry; +} + +void ext_truncate(struct inode * inode) +{ + int retry; + + if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || + S_ISLNK(inode->i_mode))) + return; + while (1) { + retry = trunc_direct(inode); + retry |= trunc_indirect(inode,9,inode->u.ext_i.i_data+9); + retry |= trunc_dindirect(inode,9+256,inode->u.ext_i.i_data+10); + retry |= trunc_tindirect(inode); + if (!retry) + break; + current->counter = 0; + schedule(); + } + inode->i_mtime = inode->i_ctime = CURRENT_TIME; + inode->i_dirt = 1; +} + +/* + * Called when a inode is released. Note that this is different + * from ext_open: open gets called at every open, but release + * gets called only when /all/ the files are closed. + */ +void ext_release(struct inode * inode, struct file * filp) +{ + printk("ext_release not implemented\n"); +} |