diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1994-11-28 11:59:19 +0000 |
---|---|---|
committer | <ralf@linux-mips.org> | 1994-11-28 11:59:19 +0000 |
commit | 1513ff9b7899ab588401c89db0e99903dbf5f886 (patch) | |
tree | f69cc81a940a502ea23d664c3ffb2d215a479667 /fs/sysv |
Import of Linus's Linux 1.1.68
Diffstat (limited to 'fs/sysv')
-rw-r--r-- | fs/sysv/INTRO | 183 | ||||
-rw-r--r-- | fs/sysv/Makefile | 31 | ||||
-rw-r--r-- | fs/sysv/README | 37 | ||||
-rw-r--r-- | fs/sysv/balloc.c | 329 | ||||
-rw-r--r-- | fs/sysv/dir.c | 144 | ||||
-rw-r--r-- | fs/sysv/file.c | 263 | ||||
-rw-r--r-- | fs/sysv/fsync.c | 197 | ||||
-rw-r--r-- | fs/sysv/ialloc.c | 218 | ||||
-rw-r--r-- | fs/sysv/inode.c | 951 | ||||
-rw-r--r-- | fs/sysv/mmap.c | 85 | ||||
-rw-r--r-- | fs/sysv/namei.c | 822 | ||||
-rw-r--r-- | fs/sysv/symlink.c | 110 | ||||
-rw-r--r-- | fs/sysv/truncate.c | 283 |
13 files changed, 3653 insertions, 0 deletions
diff --git a/fs/sysv/INTRO b/fs/sysv/INTRO new file mode 100644 index 000000000..9e53cb317 --- /dev/null +++ b/fs/sysv/INTRO @@ -0,0 +1,183 @@ +This is the implementation of the SystemV/Coherent filesystem for Linux. +It grew out of separate filesystem implementations + + Xenix FS Doug Evans <dje@cygnus.com> June 1992 + SystemV FS Paul B. Monday <pmonday@eecs.wsu.edu> March-June 1993 + Coherent FS B. Haible <haible@ma2s2.mathematik.uni-karlsruhe.de> June 1993 + +and was merged together in July 1993. + +These filesystems are rather similar. Here is a comparison with Minix FS: + +* Linux fdisk reports on partitions + - Minix FS 0x81 Linux/Minix + - Xenix FS ?? + - SystemV FS ?? + - Coherent FS 0x08 AIX bootable + +* Size of a block or zone (data allocation unit on disk) + - Minix FS 1024 + - Xenix FS 1024 (also 512 ??) + - SystemV FS 1024 (also 512) + - Coherent FS 512 + +* General layout: all have one boot block, one super block and + separate areas for inodes and for directories/data. + On SystemV Release 2 FS (e.g. Microport) the first track is reserved and + all the block numbers (including the super block) are offset by one track. + +* Byte ordering of "short" (16 bit entities) on disk: + - Minix FS little endian 0 1 + - Xenix FS little endian 0 1 + - SystemV FS little endian 0 1 + - Coherent FS little endian 0 1 + Of course, this affects only the file system, not the data of files on it! + +* Byte ordering of "long" (32 bit entities) on disk: + - Minix FS little endian 0 1 2 3 + - Xenix FS little endian 0 1 2 3 + - SystemV FS little endian 0 1 2 3 + - Coherent FS PDP-11 2 3 0 1 + Of course, this affects only the file system, not the data of files on it! + +* Inode on disk: "short", 0 means non-existent, the root dir ino is: + - Minix FS 1 + - Xenix FS, SystemV FS, Coherent FS 2 + +* Maximum number of hard links to a file: + - Minix FS 250 + - Xenix FS ?? + - SystemV FS ?? + - Coherent FS >=10000 + +* Free inode management: + - Minix FS a bitmap + - Xenix FS, SystemV FS, Coherent FS + There is a cache of a certain number of free inodes in the super-block. + When it is exhausted, new free inodes are found using a linear search. + +* Free block management: + - Minix FS a bitmap + - Xenix FS, SystemV FS, Coherent FS + Free blocks are organized in a "free list". Maybe a misleading term, + since it is not true that every free block contains a pointer to + the next free block. Rather, the free blocks are organized in chunks + of limited size, and every now and then a free block contains pointers + to the free blocks pertaining to the next chunk; the first of these + contains pointers and so on. The list terminates with a "block number" + 0 on Xenix FS and SystemV FS, with a block zeroed out on Coherent FS. + +* Super-block location: + - Minix FS block 1 = bytes 1024..2047 + - Xenix FS block 1 = bytes 1024..2047 + - SystemV FS bytes 512..1023 + - Coherent FS block 1 = bytes 512..1023 + +* Super-block layout: + - Minix FS + unsigned short s_ninodes; + unsigned short s_nzones; + unsigned short s_imap_blocks; + unsigned short s_zmap_blocks; + unsigned short s_firstdatazone; + unsigned short s_log_zone_size; + unsigned long s_max_size; + unsigned short s_magic; + - Xenix FS, SystemV FS, Coherent FS + unsigned short s_firstdatazone; + unsigned long s_nzones; + unsigned short s_fzone_count; + unsigned long s_fzones[NICFREE]; + unsigned short s_finode_count; + unsigned short s_finodes[NICINOD]; + char s_flock; + char s_ilock; + char s_modified; + char s_rdonly; + unsigned long s_time; + short s_dinfo[4]; -- SystemV FS only + unsigned long s_free_zones; + unsigned short s_free_inodes; + short s_dinfo[4]; -- Xenix FS only + unsigned short s_interleave_m,s_interleave_n; -- Coherent FS only + char s_fname[6]; + char s_fpack[6]; + then they differ considerably: + Xenix FS + char s_clean; + char s_fill[371]; + long s_magic; + long s_type; + SystemV FS + long s_fill[12 or 14]; + long s_state; + long s_magic; + long s_type; + Coherent FS + unsigned long s_unique; + Note that Coherent FS has no magic. + +* Inode layout: + - Minix FS + unsigned short i_mode; + unsigned short i_uid; + unsigned long i_size; + unsigned long i_time; + unsigned char i_gid; + unsigned char i_nlinks; + unsigned short i_zone[7+1+1]; + - Xenix FS, SystemV FS, Coherent FS + unsigned short i_mode; + unsigned short i_nlink; + unsigned short i_uid; + unsigned short i_gid; + unsigned long i_size; + unsigned char i_zone[3*(10+1+1+1)]; + unsigned long i_atime; + unsigned long i_mtime; + unsigned long i_ctime; + +* Regular file data blocks are organized as + - Minix FS + 7 direct blocks + 1 indirect block (pointers to blocks) + 1 double-indirect block (pointer to pointers to blocks) + - Xenix FS, SystemV FS, Coherent FS + 10 direct blocks + 1 indirect block (pointers to blocks) + 1 double-indirect block (pointer to pointers to blocks) + 1 triple-indirect block (pointer to pointers to pointers to blocks) + +* Inode size, inodes per block + - Minix FS 32 32 + - Xenix FS 64 16 + - SystemV FS 64 16 + - Coherent FS 64 8 + +* Directory entry on disk + - Minix FS + unsigned short inode; + char name[14/30]; + - Xenix FS, SystemV FS, Coherent FS + unsigned short inode; + char name[14]; + +* Dir entry size, dir entries per block + - Minix FS 16/32 64/32 + - Xenix FS 16 64 + - SystemV FS 16 64 + - Coherent FS 16 32 + +* How to implement symbolic links such that the host fsck doesn't scream: + - Minix FS normal + - Xenix FS kludge: as regular files with chmod 1000 + - SystemV FS ?? + - Coherent FS kludge: as regular files with chmod 1000 + + +Notation: We often speak of a "block" but mean a zone (the allocation unit) +and not the disk driver's notion of "block". + + +Bruno Haible <haible@ma2s2.mathematik.uni-karlsruhe.de> + diff --git a/fs/sysv/Makefile b/fs/sysv/Makefile new file mode 100644 index 000000000..d4a6ecbd4 --- /dev/null +++ b/fs/sysv/Makefile @@ -0,0 +1,31 @@ +# +# Makefile for the Linux SystemV/Coherent-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= ialloc.o balloc.o inode.o file.o dir.o symlink.o namei.o \ + fsync.o truncate.o + +sysv.o: $(OBJS) + $(LD) -r -o sysv.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/sysv/README b/fs/sysv/README new file mode 100644 index 000000000..d318eb64b --- /dev/null +++ b/fs/sysv/README @@ -0,0 +1,37 @@ +This is the implementation of the SystemV/Coherent filesystem for Linux. +It implements all of + - Xenix FS, + - SystemV/386 FS, + - Coherent FS. + +This is version beta 4. + +To install: +* Answer the 'System V and Coherent filesystem support' question with 'y' + when configuring the kernel. +* To mount a disk or a partition, use + mount [-r] -t sysv device mountpoint + The file system type names + -t sysv + -t xenix + -t coherent + may be used interchangeably, but the last two will eventually disappear. + +Bugs in the present implementation: +- Coherent FS: + - The "free list interleave" n:m is currently ignored. + - Only file systems with no filesystem name and no pack name are recognized. + (See Coherent "man mkfs" for a description of these features.) +- SystemV Release 2 FS: + The superblock is only searched in the blocks 9, 15, 18, which corresponds to the + beginning of track 1 on floppy disks. No support for this FS on hard disk yet. + + +Please report any bugs and suggestions to + Bruno Haible <haible@ma2s2.mathematik.uni-karlsruhde.de> or + Pascal Haible <haible@izfm.uni-stuttgart.de> . + + +Bruno Haible +<haible@ma2s2.mathematik.uni-karlsruhe.de> + diff --git a/fs/sysv/balloc.c b/fs/sysv/balloc.c new file mode 100644 index 000000000..f0fb850be --- /dev/null +++ b/fs/sysv/balloc.c @@ -0,0 +1,329 @@ +/* + * linux/fs/sysv/balloc.c + * + * minix/bitmap.c + * Copyright (C) 1991, 1992 Linus Torvalds + * + * ext/freelists.c + * Copyright (C) 1992 Remy Card (card@masi.ibp.fr) + * + * xenix/alloc.c + * Copyright (C) 1992 Doug Evans + * + * coh/alloc.c + * Copyright (C) 1993 Pascal Haible, Bruno Haible + * + * sysv/balloc.c + * Copyright (C) 1993 Bruno Haible + * + * This file contains code for allocating/freeing blocks. + */ + +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/sysv_fs.h> +#include <linux/string.h> +#include <linux/locks.h> + +/* We don't trust the value of + sb->sv_sbd2->s_tfree = *sb->sv_sb_total_free_blocks + but we nevertheless keep it up to date. */ + +void sysv_free_block(struct super_block * sb, unsigned int block) +{ + struct buffer_head * bh; + char * bh_data; + + if (!sb) { + printk("sysv_free_block: trying to free block on nonexistent device\n"); + return; + } + if (block < sb->sv_firstdatazone || block >= sb->sv_nzones) { + printk("sysv_free_block: trying to free block not in datazone\n"); + return; + } + lock_super(sb); + if (*sb->sv_sb_flc_count > sb->sv_flc_size) { + printk("sysv_free_block: flc_count > flc_size\n"); + unlock_super(sb); + return; + } + /* If the free list head in super-block is full, it is copied + * into this block being freed: + */ + if (*sb->sv_sb_flc_count == sb->sv_flc_size) { + unsigned short * flc_count; + unsigned long * flc_blocks; + + bh = sv_getblk(sb, sb->s_dev, block); + if (!bh) { + printk("sysv_free_block: getblk() failed\n"); + unlock_super(sb); + return; + } + bh_data = bh->b_data; + switch (sb->sv_type) { + case FSTYPE_XENIX: + flc_count = &((struct xenix_freelist_chunk *) bh_data)->fl_nfree; + flc_blocks = &((struct xenix_freelist_chunk *) bh_data)->fl_free[0]; + break; + case FSTYPE_SYSV4: + flc_count = &((struct sysv4_freelist_chunk *) bh_data)->fl_nfree; + flc_blocks = &((struct sysv4_freelist_chunk *) bh_data)->fl_free[0]; + break; + case FSTYPE_SYSV2: + flc_count = &((struct sysv2_freelist_chunk *) bh_data)->fl_nfree; + flc_blocks = &((struct sysv2_freelist_chunk *) bh_data)->fl_free[0]; + break; + case FSTYPE_COH: + flc_count = &((struct coh_freelist_chunk *) bh_data)->fl_nfree; + flc_blocks = &((struct coh_freelist_chunk *) bh_data)->fl_free[0]; + break; + default: panic("sysv_free_block: invalid fs type\n"); + } + *flc_count = *sb->sv_sb_flc_count; /* = sb->sv_flc_size */ + memcpy(flc_blocks, sb->sv_sb_flc_blocks, *flc_count * sizeof(sysv_zone_t)); + mark_buffer_dirty(bh, 1); + bh->b_uptodate = 1; + brelse(bh); + *sb->sv_sb_flc_count = 0; + } else + /* If the free list head in super-block is empty, create a new head + * in this block being freed: + */ + if (*sb->sv_sb_flc_count == 0) { /* Applies only to Coherent FS */ + bh = sv_getblk(sb, sb->s_dev, block); + if (!bh) { + printk("sysv_free_block: getblk() failed\n"); + unlock_super(sb); + return; + } + memset(bh->b_data, 0, sb->sv_block_size); + /* this implies ((struct ..._freelist_chunk *) bh->b_data)->flc_count = 0; */ + mark_buffer_dirty(bh, 1); + bh->b_uptodate = 1; + brelse(bh); + /* still *sb->sv_sb_flc_count = 0 */ + } else { + /* Throw away block's contents */ + bh = sv_get_hash_table(sb, sb->s_dev, block); + if (bh) + bh->b_dirt = 0; + brelse(bh); + } + if (sb->sv_convert) + block = to_coh_ulong(block); + sb->sv_sb_flc_blocks[(*sb->sv_sb_flc_count)++] = block; + if (sb->sv_convert) + *sb->sv_sb_total_free_blocks = + to_coh_ulong(from_coh_ulong(*sb->sv_sb_total_free_blocks) + 1); + else + *sb->sv_sb_total_free_blocks = *sb->sv_sb_total_free_blocks + 1; + mark_buffer_dirty(sb->sv_bh1, 1); /* super-block has been modified */ + if (sb->sv_bh1 != sb->sv_bh2) mark_buffer_dirty(sb->sv_bh2, 1); + sb->s_dirt = 1; /* and needs time stamp */ + unlock_super(sb); +} + +int sysv_new_block(struct super_block * sb) +{ + unsigned int block; + struct buffer_head * bh; + char * bh_data; + + if (!sb) { + printk("sysv_new_block: trying to get new block from nonexistent device\n"); + return 0; + } + lock_super(sb); + if (*sb->sv_sb_flc_count == 0) { /* Applies only to Coherent FS */ + unlock_super(sb); + return 0; /* no blocks available */ + } + block = sb->sv_sb_flc_blocks[(*sb->sv_sb_flc_count)-1]; + if (sb->sv_convert) + block = from_coh_ulong(block); + if (block == 0) { /* Applies only to Xenix FS, SystemV FS */ + unlock_super(sb); + return 0; /* no blocks available */ + } + (*sb->sv_sb_flc_count)--; + if (block < sb->sv_firstdatazone || block >= sb->sv_nzones) { + printk("sysv_new_block: new block %d is not in data zone\n",block); + unlock_super(sb); + return 0; + } + if (*sb->sv_sb_flc_count == 0) { /* the last block continues the free list */ + unsigned short * flc_count; + unsigned long * flc_blocks; + + if (!(bh = sv_bread(sb, sb->s_dev, block))) { + printk("sysv_new_block: cannot read free-list block\n"); + /* retry this same block next time */ + (*sb->sv_sb_flc_count)++; + unlock_super(sb); + return 0; + } + bh_data = bh->b_data; + switch (sb->sv_type) { + case FSTYPE_XENIX: + flc_count = &((struct xenix_freelist_chunk *) bh_data)->fl_nfree; + flc_blocks = &((struct xenix_freelist_chunk *) bh_data)->fl_free[0]; + break; + case FSTYPE_SYSV4: + flc_count = &((struct sysv4_freelist_chunk *) bh_data)->fl_nfree; + flc_blocks = &((struct sysv4_freelist_chunk *) bh_data)->fl_free[0]; + break; + case FSTYPE_SYSV2: + flc_count = &((struct sysv2_freelist_chunk *) bh_data)->fl_nfree; + flc_blocks = &((struct sysv2_freelist_chunk *) bh_data)->fl_free[0]; + break; + case FSTYPE_COH: + flc_count = &((struct coh_freelist_chunk *) bh_data)->fl_nfree; + flc_blocks = &((struct coh_freelist_chunk *) bh_data)->fl_free[0]; + break; + default: panic("sysv_new_block: invalid fs type\n"); + } + if (*flc_count > sb->sv_flc_size) { + printk("sysv_new_block: free-list block with >flc_size entries\n"); + brelse(bh); + unlock_super(sb); + return 0; + } + *sb->sv_sb_flc_count = *flc_count; + memcpy(sb->sv_sb_flc_blocks, flc_blocks, *flc_count * sizeof(sysv_zone_t)); + brelse(bh); + } + /* Now the free list head in the superblock is valid again. */ + bh = sv_getblk(sb, sb->s_dev, block); + if (!bh) { + printk("sysv_new_block: getblk() failed\n"); + unlock_super(sb); + return 0; + } + if (bh->b_count != 1) { + printk("sysv_new_block: block already in use\n"); + unlock_super(sb); + return 0; + } + memset(bh->b_data, 0, sb->sv_block_size); + mark_buffer_dirty(bh, 1); + bh->b_uptodate = 1; + brelse(bh); + if (sb->sv_convert) + *sb->sv_sb_total_free_blocks = + to_coh_ulong(from_coh_ulong(*sb->sv_sb_total_free_blocks) - 1); + else + *sb->sv_sb_total_free_blocks = *sb->sv_sb_total_free_blocks - 1; + mark_buffer_dirty(sb->sv_bh1, 1); /* super-block has been modified */ + if (sb->sv_bh1 != sb->sv_bh2) mark_buffer_dirty(sb->sv_bh2, 1); + sb->s_dirt = 1; /* and needs time stamp */ + unlock_super(sb); + return block; +} + +unsigned long sysv_count_free_blocks(struct super_block * sb) +{ +#if 1 /* test */ + int count, old_count; + unsigned int block; + struct buffer_head * bh; + char * bh_data; + int i; + + /* this causes a lot of disk traffic ... */ + count = 0; + lock_super(sb); + if (*sb->sv_sb_flc_count > 0) { + for (i = *sb->sv_sb_flc_count ; /* i > 0 */ ; ) { + block = sb->sv_sb_flc_blocks[--i]; + if (sb->sv_convert) + block = from_coh_ulong(block); + if (block == 0) /* block 0 terminates list */ + goto done; + count++; + if (i == 0) + break; + } + /* block = sb->sv_sb_flc_blocks[0], the last block continues the free list */ + while (1) { + unsigned short * flc_count; + unsigned long * flc_blocks; + + if (block < sb->sv_firstdatazone || block >= sb->sv_nzones) { + printk("sysv_count_free_blocks: new block %d is not in data zone\n",block); + break; + } + if (!(bh = sv_bread(sb, sb->s_dev, block))) { + printk("sysv_count_free_blocks: cannot read free-list block\n"); + break; + } + bh_data = bh->b_data; + switch (sb->sv_type) { + case FSTYPE_XENIX: + flc_count = &((struct xenix_freelist_chunk *) bh_data)->fl_nfree; + flc_blocks = &((struct xenix_freelist_chunk *) bh_data)->fl_free[0]; + break; + case FSTYPE_SYSV4: + flc_count = &((struct sysv4_freelist_chunk *) bh_data)->fl_nfree; + flc_blocks = &((struct sysv4_freelist_chunk *) bh_data)->fl_free[0]; + break; + case FSTYPE_SYSV2: + flc_count = &((struct sysv2_freelist_chunk *) bh_data)->fl_nfree; + flc_blocks = &((struct sysv2_freelist_chunk *) bh_data)->fl_free[0]; + break; + case FSTYPE_COH: + flc_count = &((struct coh_freelist_chunk *) bh_data)->fl_nfree; + flc_blocks = &((struct coh_freelist_chunk *) bh_data)->fl_free[0]; + break; + default: panic("sysv_count_free_blocks: invalid fs type\n"); + } + if (*flc_count > sb->sv_flc_size) { + printk("sysv_count_free_blocks: free-list block with >flc_size entries\n"); + brelse(bh); + break; + } + if (*flc_count == 0) { /* Applies only to Coherent FS */ + brelse(bh); + break; + } + for (i = *flc_count ; /* i > 0 */ ; ) { + block = flc_blocks[--i]; + if (sb->sv_convert) + block = from_coh_ulong(block); + if (block == 0) /* block 0 terminates list */ + break; + count++; + if (i == 0) + break; + } + /* block = flc_blocks[0], the last block continues the free list */ + brelse(bh); + if (block == 0) /* Applies only to Xenix FS and SystemV FS */ + break; + } + done: ; + } + old_count = *sb->sv_sb_total_free_blocks; + if (sb->sv_convert) + old_count = from_coh_ulong(old_count); + if (count != old_count) { + printk("sysv_count_free_blocks: free block count was %d, correcting to %d\n",old_count,count); + if (!(sb->s_flags & MS_RDONLY)) { + *sb->sv_sb_total_free_blocks = (sb->sv_convert ? to_coh_ulong(count) : count); + mark_buffer_dirty(sb->sv_bh2, 1); /* super-block has been modified */ + sb->s_dirt = 1; /* and needs time stamp */ + } + } + unlock_super(sb); + return count; +#else + int count; + + count = *sb->sv_sb_total_free_blocks; + if (sb->sv_convert) + count = from_coh_ulong(count); + return count; +#endif +} + diff --git a/fs/sysv/dir.c b/fs/sysv/dir.c new file mode 100644 index 000000000..a4b019228 --- /dev/null +++ b/fs/sysv/dir.c @@ -0,0 +1,144 @@ +/* + * linux/fs/sysv/dir.c + * + * minix/dir.c + * Copyright (C) 1991, 1992 Linus Torvalds + * + * coh/dir.c + * Copyright (C) 1993 Pascal Haible, Bruno Haible + * + * sysv/dir.c + * Copyright (C) 1993 Bruno Haible + * + * SystemV/Coherent directory handling functions + */ + +#include <asm/segment.h> + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/sysv_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 sysv_dir_read(struct inode * inode, struct file * filp, char * buf, int count) +{ + return -EISDIR; +} + +static int sysv_readdir(struct inode *, struct file *, struct dirent *, int); + +static struct file_operations sysv_dir_operations = { + NULL, /* lseek - default */ + sysv_dir_read, /* read */ + NULL, /* write - bad */ + sysv_readdir, /* readdir */ + NULL, /* select - default */ + NULL, /* ioctl - default */ + NULL, /* mmap */ + NULL, /* no special open code */ + NULL, /* no special release code */ + file_fsync /* default fsync */ +}; + +/* + * directories can handle most operations... + */ +struct inode_operations sysv_dir_inode_operations = { + &sysv_dir_operations, /* default directory file-ops */ + sysv_create, /* create */ + sysv_lookup, /* lookup */ + sysv_link, /* link */ + sysv_unlink, /* unlink */ + sysv_symlink, /* symlink */ + sysv_mkdir, /* mkdir */ + sysv_rmdir, /* rmdir */ + sysv_mknod, /* mknod */ + sysv_rename, /* rename */ + NULL, /* readlink */ + NULL, /* follow_link */ + NULL, /* bmap */ + sysv_truncate, /* truncate */ + NULL /* permission */ +}; + +static int sysv_readdir1 (struct inode * inode, struct file * filp, + struct dirent * dirent) +{ + struct super_block * sb; + unsigned int offset,i; + char c; + struct buffer_head * bh; + char* bh_data; + struct sysv_dir_entry * de; + + if (!inode || !(sb = inode->i_sb) || !S_ISDIR(inode->i_mode)) + return -EBADF; + if ((unsigned long)(filp->f_pos) % SYSV_DIRSIZE) + return -EBADF; + while (filp->f_pos < inode->i_size) { + offset = filp->f_pos & sb->sv_block_size_1; + bh = sysv_file_bread(inode, filp->f_pos >> sb->sv_block_size_bits, 0); + if (!bh) { + filp->f_pos += sb->sv_block_size - offset; + continue; + } + bh_data = bh->b_data; + while (offset < sb->sv_block_size && filp->f_pos < inode->i_size) { + de = (struct sysv_dir_entry *) (offset + bh_data); + offset += SYSV_DIRSIZE; + filp->f_pos += SYSV_DIRSIZE; + if (de->inode) { + struct sysv_dir_entry sde; + + /* Copy the directory entry first, because the directory + * might be modified while we sleep in put_fs_byte... + */ + memcpy(&sde, de, sizeof(struct sysv_dir_entry)); + + for (i = 0; i < SYSV_NAMELEN; i++) + if ((c = sde.name[i]) != 0) + put_fs_byte(c,i+dirent->d_name); + else + break; + if (i) { + if (sde.inode > inode->i_sb->sv_ninodes) + printk("sysv_readdir: Bad inode number on dev 0x%04x, ino %ld, offset 0x%04lx: %d is out of range\n", + inode->i_dev, inode->i_ino, (off_t) filp->f_pos - SYSV_DIRSIZE, sde.inode); + put_fs_long(sde.inode,&dirent->d_ino); + put_fs_byte(0,i+dirent->d_name); + put_fs_word(i,&dirent->d_reclen); + brelse(bh); + return ROUND_UP(NAME_OFFSET(dirent)+i+1); + } + } + } + brelse(bh); + } + return 0; +} + +static int sysv_readdir(struct inode * inode, struct file * filp, + struct dirent * dirent, int count) +{ + int retval, stored; + + /* compatibility */ + if (count==1) + return sysv_readdir1(inode,filp,dirent); + + stored = 0; + while (count >= sizeof(struct dirent)) { + retval = sysv_readdir1(inode,filp,dirent); + if (retval < 0) + return retval; + if (!retval) + return stored; + dirent = (struct dirent *)((char *) dirent + retval); + stored += retval; + count -= retval; + } + return stored; +} diff --git a/fs/sysv/file.c b/fs/sysv/file.c new file mode 100644 index 000000000..27f82d51a --- /dev/null +++ b/fs/sysv/file.c @@ -0,0 +1,263 @@ +/* + * linux/fs/sysv/file.c + * + * minix/file.c + * Copyright (C) 1991, 1992 Linus Torvalds + * + * coh/file.c + * Copyright (C) 1993 Pascal Haible, Bruno Haible + * + * sysv/file.c + * Copyright (C) 1993 Bruno Haible + * + * SystemV/Coherent regular file handling primitives + */ + +#include <asm/segment.h> + +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/sysv_fs.h> +#include <linux/errno.h> +#include <linux/fcntl.h> +#include <linux/stat.h> +#include <linux/string.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/sysv_fs.h> + +static int sysv_file_write(struct inode *, struct file *, char *, int); + +/* + * We have mostly NULL's here: the current defaults are ok for + * the coh filesystem. + */ +static struct file_operations sysv_file_operations = { + NULL, /* lseek - default */ + sysv_file_read, /* read */ + sysv_file_write, /* write */ + NULL, /* readdir - bad */ + NULL, /* select - default */ + NULL, /* ioctl - default */ + generic_mmap, /* mmap */ + NULL, /* no special open is needed */ + NULL, /* release */ + sysv_sync_file /* fsync */ +}; + +struct inode_operations sysv_file_inode_operations = { + &sysv_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 */ + sysv_bmap, /* bmap */ + sysv_truncate, /* truncate */ + NULL /* permission */ +}; + +int sysv_file_read(struct inode * inode, struct file * filp, char * buf, int count) +{ + struct super_block * sb = inode->i_sb; + int read,left,chars; + unsigned int block; + int 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("sysv_file_read: inode = NULL\n"); + return -EINVAL; + } + if (!S_ISREG(inode->i_mode)) { + printk("sysv_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 >> sb->sv_block_size_bits; + offset &= sb->sv_block_size_1; + size = (size + sb->sv_block_size_1) >> sb->sv_block_size_bits; + blocks = (left + offset + sb->sv_block_size_1) >> sb->sv_block_size_bits; + bhb = bhe = buflist; + if (filp->f_reada) { + blocks += read_ahead[MAJOR(inode->i_dev)] >> (sb->sv_block_size_bits - 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 = sysv_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 < sb->sv_block_size - offset) + chars = left; + else + chars = sb->sv_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; + return read; +} + +static int sysv_file_write(struct inode * inode, struct file * filp, char * buf, int count) +{ + struct super_block * sb = inode->i_sb; + off_t pos; + int written,c; + struct buffer_head * bh; + char * p; + + if (!inode) { + printk("sysv_file_write: inode = NULL\n"); + return -EINVAL; + } + if (!S_ISREG(inode->i_mode)) { + printk("sysv_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. + * But we need to protect against simultaneous truncate as we may end up + * writing our data into blocks that have meanwhile been incorporated into + * the freelist, thereby trashing the freelist. + */ + if (filp->f_flags & O_APPEND) + pos = inode->i_size; + else + pos = filp->f_pos; + written = 0; + while (written<count) { + bh = sysv_getblk (inode, pos >> sb->sv_block_size_bits, 1); + if (!bh) { + if (!written) + written = -ENOSPC; + break; + } + c = sb->sv_block_size - (pos & sb->sv_block_size_1); + if (c > count-written) + c = count-written; + if (c != sb->sv_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; + } + } + /* now either c==sb->sv_block_size or bh->b_uptodate */ + p = (pos & sb->sv_block_size_1) + 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/sysv/fsync.c b/fs/sysv/fsync.c new file mode 100644 index 000000000..9e105077d --- /dev/null +++ b/fs/sysv/fsync.c @@ -0,0 +1,197 @@ +/* + * linux/fs/sysv/fsync.c + * + * minix/fsync.c + * Copyright (C) 1991, 1992 Linus Torvalds + * Copyright (C) 1993 Stephen Tweedie (sct@dcs.ed.ac.uk) + * + * coh/fsync.c + * Copyright (C) 1993 Pascal Haible, Bruno Haible + * + * sysv/fsync.c + * Copyright (C) 1993 Bruno Haible + * + * SystemV/Coherent fsync primitive + */ + +#include <linux/errno.h> +#include <linux/stat.h> + +#include <linux/fs.h> +#include <linux/sysv_fs.h> + + +/* return values: 0 means OK/done, 1 means redo, -1 means I/O error. */ + +/* Sync one block. The block number is + * from_coh_ulong(*blockp) if convert=1, *blockp if convert=0. + */ +static int sync_block (struct inode * inode, unsigned long * blockp, int convert, int wait) +{ + struct buffer_head * bh; + unsigned long tmp, block; + struct super_block * sb; + + block = tmp = *blockp; + if (convert) + block = from_coh_ulong(block); + if (!block) + return 0; + sb = inode->i_sb; + bh = sv_get_hash_table(sb, inode->i_dev, block); + if (!bh) + return 0; + if (*blockp != 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; +} + +/* Sync one block full of indirect pointers and read it because we'll need it. */ +static int sync_iblock (struct inode * inode, unsigned long * iblockp, int convert, + struct buffer_head * *bh, int wait) +{ + int rc; + unsigned long tmp, block; + + *bh = NULL; + block = tmp = *iblockp; + if (convert) + block = from_coh_ulong(block); + if (!block) + return 0; + rc = sync_block (inode, iblockp, convert, wait); + if (rc) + return rc; + *bh = sv_bread(inode->i_sb, inode->i_dev, block); + if (tmp != *iblockp) { + 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 < 10; i++) { + rc = sync_block (inode, inode->u.sysv_i.i_data + i, 0, wait); + if (rc > 0) + break; + if (rc) + err = rc; + } + return err; +} + +static int sync_indirect(struct inode *inode, unsigned long *iblockp, int convert, int wait) +{ + int i; + struct buffer_head * ind_bh; + int rc, err = 0; + struct super_block * sb; + + rc = sync_iblock (inode, iblockp, convert, &ind_bh, wait); + if (rc || !ind_bh) + return rc; + + sb = inode->i_sb; + for (i = 0; i < sb->sv_ind_per_block; i++) { + rc = sync_block (inode, + ((unsigned long *) ind_bh->b_data) + i, sb->sv_convert, + wait); + if (rc > 0) + break; + if (rc) + err = rc; + } + brelse(ind_bh); + return err; +} + +static int sync_dindirect(struct inode *inode, unsigned long *diblockp, int convert, + int wait) +{ + int i; + struct buffer_head * dind_bh; + int rc, err = 0; + struct super_block * sb; + + rc = sync_iblock (inode, diblockp, convert, &dind_bh, wait); + if (rc || !dind_bh) + return rc; + + sb = inode->i_sb; + for (i = 0; i < sb->sv_ind_per_block; i++) { + rc = sync_indirect (inode, + ((unsigned long *) dind_bh->b_data) + i, sb->sv_convert, + wait); + if (rc > 0) + break; + if (rc) + err = rc; + } + brelse(dind_bh); + return err; +} + +static int sync_tindirect(struct inode *inode, unsigned long *tiblockp, int convert, + int wait) +{ + int i; + struct buffer_head * tind_bh; + int rc, err = 0; + struct super_block * sb; + + rc = sync_iblock (inode, tiblockp, convert, &tind_bh, wait); + if (rc || !tind_bh) + return rc; + + sb = inode->i_sb; + for (i = 0; i < sb->sv_ind_per_block; i++) { + rc = sync_dindirect (inode, + ((unsigned long *) tind_bh->b_data) + i, sb->sv_convert, + wait); + if (rc > 0) + break; + if (rc) + err = rc; + } + brelse(tind_bh); + return err; +} + +int sysv_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.sysv_i.i_data+10, 0, wait); + err |= sync_dindirect(inode, inode->u.sysv_i.i_data+11, 0, wait); + err |= sync_tindirect(inode, inode->u.sysv_i.i_data+12, 0, wait); + } + err |= sysv_sync_inode (inode); + return (err < 0) ? -EIO : 0; +} diff --git a/fs/sysv/ialloc.c b/fs/sysv/ialloc.c new file mode 100644 index 000000000..f87009100 --- /dev/null +++ b/fs/sysv/ialloc.c @@ -0,0 +1,218 @@ +/* + * linux/fs/sysv/ialloc.c + * + * minix/bitmap.c + * Copyright (C) 1991, 1992 Linus Torvalds + * + * ext/freelists.c + * Copyright (C) 1992 Remy Card (card@masi.ibp.fr) + * + * xenix/alloc.c + * Copyright (C) 1992 Doug Evans + * + * coh/alloc.c + * Copyright (C) 1993 Pascal Haible, Bruno Haible + * + * sysv/ialloc.c + * Copyright (C) 1993 Bruno Haible + * + * This file contains code for allocating/freeing inodes. + */ + +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/sysv_fs.h> +#include <linux/stddef.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/locks.h> + +/* We don't trust the value of + sb->sv_sbd2->s_tinode = *sb->sv_sb_total_free_inodes + but we nevertheless keep it up to date. */ + +/* An inode on disk is considered free if both i_mode == 0 and i_nlink == 0. */ + +/* return &sb->sv_sb_fic_inodes[i] = &sbd->s_inode[i]; */ +static inline sysv_ino_t * sv_sb_fic_inode (struct super_block * sb, unsigned int i) +{ + if (sb->sv_bh1 == sb->sv_bh2) + return &sb->sv_sb_fic_inodes[i]; + else { + /* 512 byte Xenix FS */ + unsigned int offset = offsetof(struct xenix_super_block, s_inode[i]); + if (offset < 512) + return (sysv_ino_t*)(sb->sv_sbd1 + offset); + else + return (sysv_ino_t*)(sb->sv_sbd2 + offset); + } +} + +void sysv_free_inode(struct inode * inode) +{ + struct super_block * sb; + unsigned int ino; + struct buffer_head * bh; + struct sysv_inode * raw_inode; + + if (!inode) + return; + if (!inode->i_dev) { + printk("sysv_free_inode: inode has no device\n"); + return; + } + if (inode->i_count != 1) { + printk("sysv_free_inode: inode has count=%d\n", inode->i_count); + return; + } + if (inode->i_nlink) { + printk("sysv_free_inode: inode has nlink=%d\n", inode->i_nlink); + return; + } + if (!(sb = inode->i_sb)) { + printk("sysv_free_inode: inode on nonexistent device\n"); + return; + } + ino = inode->i_ino; + if (ino <= SYSV_ROOT_INO || ino > sb->sv_ninodes) { + printk("sysv_free_inode: inode 0,1,2 or nonexistent inode\n"); + return; + } + if (!(bh = sv_bread(sb, inode->i_dev, sb->sv_firstinodezone + ((ino-1) >> sb->sv_inodes_per_block_bits)))) { + printk("sysv_free_inode: unable to read inode block on device %d/%d\n",MAJOR(inode->i_dev),MINOR(inode->i_dev)); + clear_inode(inode); + return; + } + raw_inode = (struct sysv_inode *) bh->b_data + ((ino-1) & sb->sv_inodes_per_block_1); + lock_super(sb); + if (*sb->sv_sb_fic_count < sb->sv_fic_size) + *sv_sb_fic_inode(sb,(*sb->sv_sb_fic_count)++) = ino; + (*sb->sv_sb_total_free_inodes)++; + mark_buffer_dirty(sb->sv_bh1, 1); /* super-block has been modified */ + if (sb->sv_bh1 != sb->sv_bh2) mark_buffer_dirty(sb->sv_bh2, 1); + sb->s_dirt = 1; /* and needs time stamp */ + memset(raw_inode, 0, sizeof(struct sysv_inode)); + mark_buffer_dirty(bh, 1); + unlock_super(sb); + brelse(bh); + clear_inode(inode); +} + +struct inode * sysv_new_inode(const struct inode * dir) +{ + struct inode * inode; + struct super_block * sb; + struct buffer_head * bh; + struct sysv_inode * raw_inode; + int i,j,ino,block; + + if (!dir || !(inode = get_empty_inode())) + return NULL; + sb = dir->i_sb; + inode->i_sb = sb; + inode->i_flags = inode->i_sb->s_flags; + lock_super(sb); /* protect against task switches */ + if ((*sb->sv_sb_fic_count == 0) + || (*sv_sb_fic_inode(sb,(*sb->sv_sb_fic_count)-1) == 0) /* Applies only to SystemV2 FS */ + ) { + /* Rebuild cache of free inodes: */ + /* i : index into cache slot being filled */ + /* ino : inode we are trying */ + /* block : firstinodezone + (ino-1)/inodes_per_block */ + /* j : (ino-1)%inodes_per_block */ + /* bh : buffer for block */ + /* raw_inode : pointer to inode ino in the block */ + for (i = 0, ino = SYSV_ROOT_INO+1, block = sb->sv_firstinodezone, j = SYSV_ROOT_INO ; i < sb->sv_fic_size && block < sb->sv_firstdatazone ; block++, j = 0) { + if (!(bh = sv_bread(sb, sb->s_dev, block))) { + printk("sysv_new_inode: unable to read inode table\n"); + break; /* go with what we've got */ + /* FIXME: Perhaps try the next block? */ + } + raw_inode = (struct sysv_inode *) bh->b_data + j; + for (; j < sb->sv_inodes_per_block && i < sb->sv_fic_size; ino++, j++, raw_inode++) { + if (raw_inode->i_mode == 0 && raw_inode->i_nlink == 0) + *sv_sb_fic_inode(sb,i++) = ino; + } + brelse(bh); + } + if (i == 0) { + iput(inode); + unlock_super(sb); + return NULL; /* no inodes available */ + } + *sb->sv_sb_fic_count = i; + } + /* Now *sb->sv_sb_fic_count > 0. */ + ino = *sv_sb_fic_inode(sb,--(*sb->sv_sb_fic_count)); + mark_buffer_dirty(sb->sv_bh1, 1); /* super-block has been modified */ + if (sb->sv_bh1 != sb->sv_bh2) mark_buffer_dirty(sb->sv_bh2, 1); + sb->s_dirt = 1; /* and needs time stamp */ + 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 = ino; + 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); + /* Change directory entry: */ + inode->i_mode = 0; /* for sysv_write_inode() */ + inode->i_size = 0; /* ditto */ + sysv_write_inode(inode); /* ensure inode not allocated again */ + /* FIXME: caller may call this too. */ + inode->i_dirt = 1; /* cleared by sysv_write_inode() */ + /* That's it. */ + (*sb->sv_sb_total_free_inodes)--; + mark_buffer_dirty(sb->sv_bh2, 1); /* super-block has been modified again */ + sb->s_dirt = 1; /* and needs time stamp again */ + unlock_super(sb); + return inode; +} + +unsigned long sysv_count_free_inodes(struct super_block * sb) +{ +#if 1 /* test */ + struct buffer_head * bh; + struct sysv_inode * raw_inode; + int j,block,count; + + /* this causes a lot of disk traffic ... */ + count = 0; + lock_super(sb); + /* i : index into cache slot being filled */ + /* ino : inode we are trying */ + /* block : firstinodezone + (ino-1)/inodes_per_block */ + /* j : (ino-1)%inodes_per_block */ + /* bh : buffer for block */ + /* raw_inode : pointer to inode ino in the block */ + for (block = sb->sv_firstinodezone, j = SYSV_ROOT_INO ; block < sb->sv_firstdatazone ; block++, j = 0) { + if (!(bh = sv_bread(sb, sb->s_dev, block))) { + printk("sysv_count_free_inodes: unable to read inode table\n"); + break; /* go with what we've got */ + /* FIXME: Perhaps try the next block? */ + } + raw_inode = (struct sysv_inode *) bh->b_data + j; + for (; j < sb->sv_inodes_per_block ; j++, raw_inode++) + if (raw_inode->i_mode == 0 && raw_inode->i_nlink == 0) + count++; + brelse(bh); + } + if (count != *sb->sv_sb_total_free_inodes) { + printk("sysv_count_free_inodes: free inode count was %d, correcting to %d\n",(short)(*sb->sv_sb_total_free_inodes),count); + if (!(sb->s_flags & MS_RDONLY)) { + *sb->sv_sb_total_free_inodes = count; + mark_buffer_dirty(sb->sv_bh2, 1); /* super-block has been modified */ + sb->s_dirt = 1; /* and needs time stamp */ + } + } + unlock_super(sb); + return count; +#else + return *sb->sv_sb_total_free_inodes; +#endif +} + diff --git a/fs/sysv/inode.c b/fs/sysv/inode.c new file mode 100644 index 000000000..0b61ae7c7 --- /dev/null +++ b/fs/sysv/inode.c @@ -0,0 +1,951 @@ +/* + * linux/fs/sysv/inode.c + * + * minix/inode.c + * Copyright (C) 1991, 1992 Linus Torvalds + * + * xenix/inode.c + * Copyright (C) 1992 Doug Evans + * + * coh/inode.c + * Copyright (C) 1993 Pascal Haible, Bruno Haible + * + * sysv/inode.c + * Copyright (C) 1993 Paul B. Monday + * + * sysv/inode.c + * Copyright (C) 1993 Bruno Haible + * + * This file contains code for allocating/freeing inodes and for read/writing + * the superblock. + */ + +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/sysv_fs.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/locks.h> + +#include <asm/segment.h> + +void sysv_put_inode(struct inode *inode) +{ + if (inode->i_nlink) + return; + inode->i_size = 0; + sysv_truncate(inode); + sysv_free_inode(inode); +} + + +static struct super_operations sysv_sops = { + sysv_read_inode, + sysv_notify_change, + sysv_write_inode, + sysv_put_inode, + sysv_put_super, + sysv_write_super, + sysv_statfs, + NULL +}; + +/* The following functions try to recognize specific filesystems. + * We recognize: + * - Xenix FS by its magic number. + * - SystemV FS by its magic number. + * - Coherent FS by its funny fname/fpack field. + * We discriminate among SystemV4 and SystemV2 FS by the assumption that + * the time stamp is not < 01-01-1980. + */ + +static void detected_bs512 (struct super_block *sb) +{ + sb->sv_block_size = 512; + sb->sv_block_size_1 = 512-1; + sb->sv_block_size_bits = 9; + sb->sv_block_size_ratio = 2; + sb->sv_block_size_ratio_bits = 1; + sb->sv_inodes_per_block = 512/64; + sb->sv_inodes_per_block_1 = 512/64-1; + sb->sv_inodes_per_block_bits = 9-6; + sb->sv_toobig_block = 10 + + (sb->sv_ind_per_block = 512/4) + + (sb->sv_ind_per_block_2 = (512/4)*(512/4)) + + (sb->sv_ind_per_block_3 = (512/4)*(512/4)*(512/4)); + sb->sv_ind_per_block_1 = 512/4-1; + sb->sv_ind_per_block_2_1 = (512/4)*(512/4)-1; + sb->sv_ind_per_block_2_bits = 2 * + (sb->sv_ind_per_block_bits = 9-2); + sb->sv_ind_per_block_block_size_1 = (512/4)*512-1; + sb->sv_ind_per_block_block_size_bits = (9-2)+9; + sb->sv_ind_per_block_2_block_size_1 = (512/4)*(512/4)*512-1; + sb->sv_ind_per_block_2_block_size_bits = (9-2)+(9-2)+9; + sb->sv_ind0_size = 10 * 512; + sb->sv_ind1_size = (10 + (512/4))* 512; + sb->sv_ind2_size = (10 + (512/4) + (512/4)*(512/4)) * 512; +} + +static void detected_bs1024 (struct super_block *sb) +{ + sb->sv_block_size = 1024; + sb->sv_block_size_1 = 1024-1; + sb->sv_block_size_bits = 10; + sb->sv_block_size_ratio = 1; + sb->sv_block_size_ratio_bits = 0; + sb->sv_inodes_per_block = 1024/64; + sb->sv_inodes_per_block_1 = 1024/64-1; + sb->sv_inodes_per_block_bits = 10-6; + sb->sv_toobig_block = 10 + + (sb->sv_ind_per_block = 1024/4) + + (sb->sv_ind_per_block_2 = (1024/4)*(1024/4)) + + (sb->sv_ind_per_block_3 = (1024/4)*(1024/4)*(1024/4)); + sb->sv_ind_per_block_1 = 1024/4-1; + sb->sv_ind_per_block_2_1 = (1024/4)*(1024/4)-1; + sb->sv_ind_per_block_2_bits = 2 * + (sb->sv_ind_per_block_bits = 10-2); + sb->sv_ind_per_block_block_size_1 = (1024/4)*1024-1; + sb->sv_ind_per_block_block_size_bits = (10-2)+10; + sb->sv_ind_per_block_2_block_size_1 = (1024/4)*(1024/4)*1024-1; + sb->sv_ind_per_block_2_block_size_bits = (10-2)+(10-2)+10; + sb->sv_ind0_size = 10 * 1024; + sb->sv_ind1_size = (10 + (1024/4))* 1024; + sb->sv_ind2_size = (10 + (1024/4) + (1024/4)*(1024/4)) * 1024; +} + +static const char* detect_xenix (struct super_block *sb, struct buffer_head *bh) +{ + struct xenix_super_block * sbd; + + sbd = (struct xenix_super_block *) bh->b_data; + if (sbd->s_magic != 0x2b5544) + return NULL; + switch (sbd->s_type) { + case 1: detected_bs512(sb); break; + case 2: detected_bs1024(sb); break; + default: return NULL; + } + sb->sv_type = FSTYPE_XENIX; + return "Xenix"; +} +static struct super_block * detected_xenix (struct super_block *sb, struct buffer_head *bh1, struct buffer_head *bh2) +{ + struct xenix_super_block * sbd1; + struct xenix_super_block * sbd2; + + if (sb->sv_block_size == BLOCK_SIZE) + /* block size = 1024, so bh1 = bh2 */ + sbd1 = sbd2 = (struct xenix_super_block *) bh1->b_data; + else { + /* block size = 512, so bh1 != bh2 */ + sbd1 = (struct xenix_super_block *) bh1->b_data; + sbd2 = (struct xenix_super_block *) (bh2->b_data - BLOCK_SIZE/2); + /* sanity check */ + if (sbd2->s_magic != 0x2b5544) + return NULL; + } + + sb->sv_convert = 0; + sb->sv_kludge_symlinks = 1; + sb->sv_truncate = 1; + sb->sv_link_max = XENIX_LINK_MAX; + sb->sv_fic_size = XENIX_NICINOD; + sb->sv_flc_size = XENIX_NICFREE; + sb->sv_bh1 = bh1; + sb->sv_bh2 = bh2; + sb->sv_sbd1 = (char *) sbd1; + sb->sv_sbd2 = (char *) sbd2; + sb->sv_sb_fic_count = &sbd1->s_ninode; + sb->sv_sb_fic_inodes = &sbd1->s_inode[0]; + sb->sv_sb_total_free_inodes = &sbd2->s_tinode; + sb->sv_sb_flc_count = &sbd1->s_nfree; + sb->sv_sb_flc_blocks = &sbd1->s_free[0]; + sb->sv_sb_total_free_blocks = &sbd2->s_tfree; + sb->sv_sb_time = &sbd2->s_time; + sb->sv_block_base = 0; + sb->sv_firstinodezone = 2; + sb->sv_firstdatazone = sbd1->s_isize; + sb->sv_nzones = sbd1->s_fsize; + sb->sv_ndatazones = sb->sv_nzones - sb->sv_firstdatazone; + return sb; +} + +static const char* detect_sysv4 (struct super_block *sb, struct buffer_head *bh) +{ + struct sysv4_super_block * sbd; + + sbd = (struct sysv4_super_block *) (bh->b_data + BLOCK_SIZE/2); + if (sbd->s_magic != 0xfd187e20) + return NULL; + if (sbd->s_time < 315532800) /* this is likely to happen on SystemV2 FS */ + return NULL; + switch (sbd->s_type) { + case 1: detected_bs512(sb); break; + case 2: detected_bs1024(sb); break; + default: return NULL; + } + sb->sv_type = FSTYPE_SYSV4; + return "SystemV"; +} +static struct super_block * detected_sysv4 (struct super_block *sb, struct buffer_head *bh) +{ + struct sysv4_super_block * sbd; + + if (sb->sv_block_size == BLOCK_SIZE) + sbd = (struct sysv4_super_block *) (bh->b_data + BLOCK_SIZE/2); + else { + sbd = (struct sysv4_super_block *) bh->b_data; + /* sanity check */ + if (sbd->s_magic != 0xfd187e20) + return NULL; + if (sbd->s_time < 315532800) + return NULL; + } + + sb->sv_convert = 0; + sb->sv_kludge_symlinks = 0; /* ?? */ + sb->sv_truncate = 1; + sb->sv_link_max = SYSV_LINK_MAX; + sb->sv_fic_size = SYSV_NICINOD; + sb->sv_flc_size = SYSV_NICFREE; + sb->sv_bh1 = bh; + sb->sv_bh2 = bh; + sb->sv_sbd1 = (char *) sbd; + sb->sv_sbd2 = (char *) sbd; + sb->sv_sb_fic_count = &sbd->s_ninode; + sb->sv_sb_fic_inodes = &sbd->s_inode[0]; + sb->sv_sb_total_free_inodes = &sbd->s_tinode; + sb->sv_sb_flc_count = &sbd->s_nfree; + sb->sv_sb_flc_blocks = &sbd->s_free[0]; + sb->sv_sb_total_free_blocks = &sbd->s_tfree; + sb->sv_sb_time = &sbd->s_time; + sb->sv_block_base = 0; + sb->sv_firstinodezone = 2; + sb->sv_firstdatazone = sbd->s_isize; + sb->sv_nzones = sbd->s_fsize; + sb->sv_ndatazones = sb->sv_nzones - sb->sv_firstdatazone; + return sb; +} + +static const char* detect_sysv2 (struct super_block *sb, struct buffer_head *bh) +{ + struct sysv2_super_block * sbd; + + sbd = (struct sysv2_super_block *) (bh->b_data + BLOCK_SIZE/2); + if (sbd->s_magic != 0xfd187e20) + return NULL; + if (sbd->s_time < 315532800) /* this is likely to happen on SystemV4 FS */ + return NULL; + switch (sbd->s_type) { + case 1: detected_bs512(sb); break; + case 2: detected_bs1024(sb); break; + default: return NULL; + } + sb->sv_type = FSTYPE_SYSV2; + return "SystemV Release 2"; +} +static struct super_block * detected_sysv2 (struct super_block *sb, struct buffer_head *bh) +{ + struct sysv2_super_block * sbd; + + if (sb->sv_block_size == BLOCK_SIZE) + sbd = (struct sysv2_super_block *) (bh->b_data + BLOCK_SIZE/2); + else { + sbd = (struct sysv2_super_block *) bh->b_data; + /* sanity check */ + if (sbd->s_magic != 0xfd187e20) + return NULL; + if (sbd->s_time < 315532800) + return NULL; + } + + sb->sv_convert = 0; + sb->sv_kludge_symlinks = 0; /* ?? */ + sb->sv_truncate = 1; + sb->sv_link_max = SYSV_LINK_MAX; + sb->sv_fic_size = SYSV_NICINOD; + sb->sv_flc_size = SYSV_NICFREE; + sb->sv_bh1 = bh; + sb->sv_bh2 = bh; + sb->sv_sbd1 = (char *) sbd; + sb->sv_sbd2 = (char *) sbd; + sb->sv_sb_fic_count = &sbd->s_ninode; + sb->sv_sb_fic_inodes = &sbd->s_inode[0]; + sb->sv_sb_total_free_inodes = &sbd->s_tinode; + sb->sv_sb_flc_count = &sbd->s_nfree; + sb->sv_sb_flc_blocks = &sbd->s_free[0]; + sb->sv_sb_total_free_blocks = &sbd->s_tfree; + sb->sv_sb_time = &sbd->s_time; + sb->sv_block_base = 0; + sb->sv_firstinodezone = 2; + sb->sv_firstdatazone = sbd->s_isize; + sb->sv_nzones = sbd->s_fsize; + sb->sv_ndatazones = sb->sv_nzones - sb->sv_firstdatazone; + return sb; +} + +static const char* detect_coherent (struct super_block *sb, struct buffer_head *bh) +{ + struct coh_super_block * sbd; + + sbd = (struct coh_super_block *) (bh->b_data + BLOCK_SIZE/2); + if ((memcmp(sbd->s_fname,"noname",6) && memcmp(sbd->s_fname,"xxxxx ",6)) + || (memcmp(sbd->s_fpack,"nopack",6) && memcmp(sbd->s_fpack,"xxxxx\n",6))) + return NULL; + detected_bs512(sb); + sb->sv_type = FSTYPE_COH; + return "Coherent"; +} +static struct super_block * detected_coherent (struct super_block *sb, struct buffer_head *bh) +{ + struct coh_super_block * sbd; + + sbd = (struct coh_super_block *) bh->b_data; + /* sanity check */ + if ((memcmp(sbd->s_fname,"noname",6) && memcmp(sbd->s_fname,"xxxxx ",6)) + || (memcmp(sbd->s_fpack,"nopack",6) && memcmp(sbd->s_fpack,"xxxxx\n",6))) + return NULL; + + sb->sv_convert = 1; + sb->sv_kludge_symlinks = 1; + sb->sv_truncate = 1; + sb->sv_link_max = COH_LINK_MAX; + sb->sv_fic_size = COH_NICINOD; + sb->sv_flc_size = COH_NICFREE; + sb->sv_bh1 = bh; + sb->sv_bh2 = bh; + sb->sv_sbd1 = (char *) sbd; + sb->sv_sbd2 = (char *) sbd; + sb->sv_sb_fic_count = &sbd->s_ninode; + sb->sv_sb_fic_inodes = &sbd->s_inode[0]; + sb->sv_sb_total_free_inodes = &sbd->s_tinode; + sb->sv_sb_flc_count = &sbd->s_nfree; + sb->sv_sb_flc_blocks = &sbd->s_free[0]; + sb->sv_sb_total_free_blocks = &sbd->s_tfree; + sb->sv_sb_time = &sbd->s_time; + sb->sv_block_base = 0; + sb->sv_firstinodezone = 2; + sb->sv_firstdatazone = sbd->s_isize; + sb->sv_nzones = from_coh_ulong(sbd->s_fsize); + sb->sv_ndatazones = sb->sv_nzones - sb->sv_firstdatazone; + return sb; +} + +struct super_block *sysv_read_super(struct super_block *sb,void *data, + int silent) +{ + struct buffer_head *bh; + const char *found; + int dev = sb->s_dev; + + if (1024 != sizeof (struct xenix_super_block)) + panic("Xenix FS: bad super-block size"); + if ((512 != sizeof (struct sysv4_super_block)) + || (512 != sizeof (struct sysv2_super_block))) + panic("SystemV FS: bad super-block size"); + if (500 != sizeof (struct coh_super_block)) + panic("Coherent FS: bad super-block size"); + if (64 != sizeof (struct sysv_inode)) + panic("sysv fs: bad i-node size"); + lock_super(sb); + set_blocksize(dev,BLOCK_SIZE); + + /* Try to read Xenix superblock */ + if ((bh = bread(dev, 1, BLOCK_SIZE)) != NULL) { + if ((found = detect_xenix(sb,bh)) != NULL) + goto ok; + brelse(bh); + } + if ((bh = bread(dev, 0, BLOCK_SIZE)) != NULL) { + /* Try to recognize SystemV superblock */ + if ((found = detect_sysv4(sb,bh)) != NULL) + goto ok; + if ((found = detect_sysv2(sb,bh)) != NULL) + goto ok; + /* Try to recognize Coherent superblock */ + if ((found = detect_coherent(sb,bh)) != NULL) + goto ok; + brelse(bh); + } + /* Try to recognize SystemV superblock */ + /* Offset by 1 track, i.e. most probably 9, 15, or 18 kilobytes. */ + { static int offsets[] = { 9, 15, 18, }; + int i; + for (i = 0; i < sizeof(offsets)/sizeof(offsets[0]); i++) + if ((bh = bread(dev, offsets[i], BLOCK_SIZE)) != NULL) { + /* Try to recognize SystemV superblock */ + if ((found = detect_sysv4(sb,bh)) != NULL) { + sb->sv_block_base = offsets[i] << sb->sv_block_size_ratio_bits; + goto ok; + } + if ((found = detect_sysv2(sb,bh)) != NULL) { + sb->sv_block_base = offsets[i] << sb->sv_block_size_ratio_bits; + goto ok; + } + brelse(bh); + } + } + sb->s_dev=0; + unlock_super(sb); + if (!silent) + printk("VFS: unable to read Xenix/SystemV/Coherent superblock on device %d/%d\n",MAJOR(dev),MINOR(dev)); + return NULL; + + ok: + if (sb->sv_block_size == BLOCK_SIZE) { + switch (sb->sv_type) { + case FSTYPE_XENIX: + if (!detected_xenix(sb,bh,bh)) + goto bad_superblock; + break; + case FSTYPE_SYSV4: + if (!detected_sysv4(sb,bh)) + goto bad_superblock; + break; + case FSTYPE_SYSV2: + if (!detected_sysv2(sb,bh)) + goto bad_superblock; + break; + default: + bad_superblock: + brelse(bh); + sb->s_dev = 0; + unlock_super(sb); + printk("SysV FS: cannot read superblock in 1024 byte mode\n"); + return NULL; + } + } else { + /* Switch to another block size. Unfortunately, we have to + release the 1 KB block bh and read it in two parts again. */ + struct buffer_head *bh1, *bh2; + unsigned long blocknr = bh->b_blocknr << sb->sv_block_size_ratio_bits; + + brelse(bh); + set_blocksize(dev,sb->sv_block_size); + bh1 = NULL; bh2 = NULL; + switch (sb->sv_type) { + case FSTYPE_XENIX: + if ((bh1 = bread(dev, blocknr, sb->sv_block_size)) == NULL) + goto bad_superblock2; + if ((bh2 = bread(dev, blocknr+1, sb->sv_block_size)) == NULL) + goto bad_superblock2; + if (!detected_xenix(sb,bh1,bh2)) + goto bad_superblock2; + break; + case FSTYPE_SYSV4: + if ((bh2 = bread(dev, blocknr+1, sb->sv_block_size)) == NULL) + goto bad_superblock2; + if (!detected_sysv4(sb,bh2)) + goto bad_superblock2; + break; + case FSTYPE_SYSV2: + if ((bh2 = bread(dev, blocknr+1, sb->sv_block_size)) == NULL) + goto bad_superblock2; + if (!detected_sysv2(sb,bh2)) + goto bad_superblock2; + break; + case FSTYPE_COH: + if ((bh2 = bread(dev, blocknr+1, sb->sv_block_size)) == NULL) + goto bad_superblock2; + if (!detected_coherent(sb,bh2)) + goto bad_superblock2; + break; + default: + bad_superblock2: + brelse(bh1); + brelse(bh2); + set_blocksize(sb->s_dev,BLOCK_SIZE); + sb->s_dev = 0; + unlock_super(sb); + printk("SysV FS: cannot read superblock in 512 byte mode\n"); + return NULL; + } + } + sb->sv_ninodes = (sb->sv_firstdatazone - sb->sv_firstinodezone) << sb->sv_inodes_per_block_bits; + if (!silent) + printk("VFS: Found a %s FS (block size = %d) on device %d/%d\n",found,sb->sv_block_size,MAJOR(dev),MINOR(dev)); + sb->s_magic = SYSV_MAGIC_BASE + sb->sv_type; + /* The buffer code now supports block size 512 as well as 1024. */ + sb->s_blocksize = sb->sv_block_size; + sb->s_blocksize_bits = sb->sv_block_size_bits; + /* set up enough so that it can read an inode */ + sb->s_dev = dev; + sb->s_op = &sysv_sops; + sb->s_mounted = iget(sb,SYSV_ROOT_INO); + unlock_super(sb); + if (!sb->s_mounted) { + sysv_put_super(sb); + printk("SysV FS: get root inode failed\n"); + return NULL; + } + sb->s_dirt = 1; + /* brelse(bh); resp. brelse(bh1); brelse(bh2); + occurs when the disk is unmounted. */ + return sb; +} + +/* This is only called on sync() and umount(), when s_dirt=1. */ +void sysv_write_super (struct super_block *sb) +{ + lock_super(sb); + if (sb->sv_bh1->b_dirt || sb->sv_bh2->b_dirt) { + /* If we are going to write out the super block, + then attach current time stamp. */ + unsigned long time = CURRENT_TIME; + if (sb->sv_convert) + time = to_coh_ulong(time); + *sb->sv_sb_time = time; + mark_buffer_dirty(sb->sv_bh2, 1); + } + sb->s_dirt = 0; + unlock_super(sb); +} + +void sysv_put_super(struct super_block *sb) +{ + /* we can assume sysv_write_super() has already been called */ + lock_super(sb); + brelse(sb->sv_bh1); + if (sb->sv_bh1 != sb->sv_bh2) brelse(sb->sv_bh2); + /* switch back to default block size */ + if (sb->s_blocksize != BLOCK_SIZE) + set_blocksize(sb->s_dev,BLOCK_SIZE); + sb->s_dev = 0; + unlock_super(sb); +} + +void sysv_statfs(struct super_block *sb, struct statfs *buf) +{ + long tmp; + + put_fs_long(sb->s_magic, &buf->f_type); /* type of filesystem */ + put_fs_long(sb->sv_block_size, &buf->f_bsize); /* block size */ + put_fs_long(sb->sv_ndatazones, &buf->f_blocks); /* total data blocks in file system */ + tmp = sysv_count_free_blocks(sb); + put_fs_long(tmp, &buf->f_bfree); /* free blocks in fs */ + put_fs_long(tmp, &buf->f_bavail); /* free blocks available to non-superuser */ + put_fs_long(sb->sv_ninodes, &buf->f_files); /* total file nodes in file system */ + put_fs_long(sysv_count_free_inodes(sb), &buf->f_ffree); /* free file nodes in fs */ + put_fs_long(SYSV_NAMELEN, &buf->f_namelen); + /* Don't know what value to put in buf->f_fsid */ /* file system id */ +} + + +/* bmap support for running executables and shared libraries. */ + +static inline int inode_bmap(struct super_block * sb, struct inode * inode, int nr) +{ + int tmp = inode->u.sysv_i.i_data[nr]; + if (!tmp) + return 0; + return tmp + sb->sv_block_base; +} + +static int block_bmap(struct super_block * sb, struct buffer_head * bh, int nr, int convert) +{ + int tmp; + + if (!bh) + return 0; + tmp = ((sysv_zone_t *) bh->b_data) [nr]; + if (convert) + tmp = from_coh_ulong(tmp); + brelse(bh); + if (!tmp) + return 0; + return tmp + sb->sv_block_base; +} + +int sysv_bmap(struct inode * inode,int block_nr) +{ + unsigned int block = block_nr; + struct super_block * sb = inode->i_sb; + int convert; + int i; + struct buffer_head * bh; + + if (block < 10) + return inode_bmap(sb,inode,block); + block -= 10; + convert = sb->sv_convert; + if (block < sb->sv_ind_per_block) { + i = inode_bmap(sb,inode,10); + if (!i) + return 0; + bh = bread(inode->i_dev,i,sb->sv_block_size); + return block_bmap(sb, bh, block, convert); + } + block -= sb->sv_ind_per_block; + if (block < sb->sv_ind_per_block_2) { + i = inode_bmap(sb,inode,11); + if (!i) + return 0; + bh = bread(inode->i_dev,i,sb->sv_block_size); + i = block_bmap(sb, bh, block >> sb->sv_ind_per_block_bits, convert); + if (!i) + return 0; + bh = bread(inode->i_dev,i,sb->sv_block_size); + return block_bmap(sb, bh, block & sb->sv_ind_per_block_1, convert); + } + block -= sb->sv_ind_per_block_2; + if (block < sb->sv_ind_per_block_3) { + i = inode_bmap(sb,inode,12); + if (!i) + return 0; + bh = bread(inode->i_dev,i,sb->sv_block_size); + i = block_bmap(sb, bh, block >> sb->sv_ind_per_block_2_bits, convert); + if (!i) + return 0; + bh = bread(inode->i_dev,i,sb->sv_block_size); + i = block_bmap(sb, bh, (block >> sb->sv_ind_per_block_bits) & sb->sv_ind_per_block_1,convert); + if (!i) + return 0; + bh = bread(inode->i_dev,i,sb->sv_block_size); + return block_bmap(sb, bh, block & sb->sv_ind_per_block_1, convert); + } + if ((int)block<0) { + printk("sysv_bmap: block<0"); + return 0; + } + printk("sysv_bmap: block>big"); + return 0; +} + +/* End of bmap support. */ + + +/* Access selected blocks of regular files (or directories) */ + +static struct buffer_head * inode_getblk(struct inode * inode, int nr, int create) +{ + struct super_block *sb; + unsigned long tmp; + unsigned long *p; + struct buffer_head * result; + + sb = inode->i_sb; + p = inode->u.sysv_i.i_data + nr; +repeat: + tmp = *p; + if (tmp) { + result = sv_getblk(sb, inode->i_dev, tmp); + if (tmp == *p) + return result; + brelse(result); + goto repeat; + } + if (!create) + return NULL; + tmp = sysv_new_block(sb); + if (!tmp) + return NULL; + result = sv_getblk(sb, inode->i_dev, tmp); + if (*p) { + sysv_free_block(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) +{ + struct super_block *sb; + unsigned long tmp, block; + sysv_zone_t *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; + } + } + sb = inode->i_sb; + p = nr + (sysv_zone_t *) bh->b_data; +repeat: + block = tmp = *p; + if (sb->sv_convert) + block = from_coh_ulong(block); + if (tmp) { + result = sv_getblk(sb, bh->b_dev, block); + if (tmp == *p) { + brelse(bh); + return result; + } + brelse(result); + goto repeat; + } + if (!create) { + brelse(bh); + return NULL; + } + block = sysv_new_block(sb); + if (!block) { + brelse(bh); + return NULL; + } + result = sv_getblk(sb, bh->b_dev, block); + if (*p) { + sysv_free_block(sb,block); + brelse(result); + goto repeat; + } + *p = (sb->sv_convert ? to_coh_ulong(block) : block); + mark_buffer_dirty(bh, 1); + brelse(bh); + return result; +} + +struct buffer_head * sysv_getblk(struct inode * inode, unsigned int block, int create) +{ + struct super_block * sb = inode->i_sb; + struct buffer_head * bh; + + if (block < 10) + return inode_getblk(inode,block,create); + block -= 10; + if (block < sb->sv_ind_per_block) { + bh = inode_getblk(inode,10,create); + return block_getblk(inode, bh, block, create); + } + block -= sb->sv_ind_per_block; + if (block < sb->sv_ind_per_block_2) { + bh = inode_getblk(inode,11,create); + bh = block_getblk(inode, bh, block >> sb->sv_ind_per_block_bits, create); + return block_getblk(inode, bh, block & sb->sv_ind_per_block_1, create); + } + block -= sb->sv_ind_per_block_2; + if (block < sb->sv_ind_per_block_3) { + bh = inode_getblk(inode,12,create); + bh = block_getblk(inode, bh, block >> sb->sv_ind_per_block_2_bits, create); + bh = block_getblk(inode, bh, (block >> sb->sv_ind_per_block_bits) & sb->sv_ind_per_block_1, create); + return block_getblk(inode, bh, block & sb->sv_ind_per_block_1, create); + } + if ((int)block<0) { + printk("sysv_getblk: block<0"); + return NULL; + } + printk("sysv_getblk: block>big"); + return NULL; +} + +struct buffer_head * sysv_file_bread(struct inode * inode, int block, int create) +{ + struct buffer_head * bh; + + bh = sysv_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; +} + + +static inline unsigned long read3byte (char * p) +{ + return (unsigned long)(*(unsigned short *)p) + | (unsigned long)(*(unsigned char *)(p+2)) << 16; +} + +static inline void write3byte (char * p, unsigned long val) +{ + *(unsigned short *)p = (unsigned short) val; + *(unsigned char *)(p+2) = val >> 16; +} + +static inline unsigned long coh_read3byte (char * p) +{ + return (unsigned long)(*(unsigned char *)p) << 16 + | (unsigned long)(*(unsigned short *)(p+1)); +} + +static inline void coh_write3byte (char * p, unsigned long val) +{ + *(unsigned char *)p = val >> 16; + *(unsigned short *)(p+1) = (unsigned short) val; +} + +void sysv_read_inode(struct inode * inode) +{ + struct super_block * sb = inode->i_sb; + struct buffer_head * bh; + struct sysv_inode * raw_inode; + unsigned int block, ino; + umode_t mode; + + ino = inode->i_ino; + inode->i_op = NULL; + inode->i_mode = 0; + if (!ino || ino > sb->sv_ninodes) { + printk("Bad inode number on dev 0x%04x: %d is out of range\n", + inode->i_dev, ino); + return; + } + block = sb->sv_firstinodezone + ((ino-1) >> sb->sv_inodes_per_block_bits); + if (!(bh = sv_bread(sb,inode->i_dev,block))) { + printk("Major problem: unable to read inode from dev 0x%04x\n", + inode->i_dev); + return; + } + raw_inode = (struct sysv_inode *) bh->b_data + ((ino-1) & sb->sv_inodes_per_block_1); + mode = raw_inode->i_mode; + if (sb->sv_kludge_symlinks) + mode = from_coh_imode(mode); + /* SystemV FS: kludge permissions if ino==SYSV_ROOT_INO ?? */ + inode->i_mode = mode; + inode->i_uid = raw_inode->i_uid; + inode->i_gid = raw_inode->i_gid; + inode->i_nlink = raw_inode->i_nlink; + if (sb->sv_convert) { + inode->i_size = from_coh_ulong(raw_inode->i_size); + inode->i_atime = from_coh_ulong(raw_inode->i_atime); + inode->i_mtime = from_coh_ulong(raw_inode->i_mtime); + inode->i_ctime = from_coh_ulong(raw_inode->i_ctime); + } else { + inode->i_size = raw_inode->i_size; + inode->i_atime = raw_inode->i_atime; + inode->i_mtime = raw_inode->i_mtime; + inode->i_ctime = raw_inode->i_ctime; + } + inode->i_blocks = inode->i_blksize = 0; + if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode)) + inode->i_rdev = raw_inode->i_a.i_rdev; + else + if (sb->sv_convert) + for (block = 0; block < 10+1+1+1; block++) + inode->u.sysv_i.i_data[block] = + coh_read3byte(&raw_inode->i_a.i_addb[3*block]); + else + for (block = 0; block < 10+1+1+1; block++) + inode->u.sysv_i.i_data[block] = + read3byte(&raw_inode->i_a.i_addb[3*block]); + brelse(bh); + if (S_ISREG(inode->i_mode)) + inode->i_op = &sysv_file_inode_operations; + else if (S_ISDIR(inode->i_mode)) + inode->i_op = &sysv_dir_inode_operations; + else if (S_ISLNK(inode->i_mode)) + inode->i_op = &sysv_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); +} + +/* To avoid inconsistencies between inodes in memory and inodes on disk. */ +extern int sysv_notify_change(struct inode *inode, struct iattr *attr) +{ + int error; + + if ((error = inode_change_ok(inode, attr)) != 0) + return error; + + if (attr->ia_valid & ATTR_MODE) + if (inode->i_sb->sv_kludge_symlinks) + if (attr->ia_mode == COH_KLUDGE_SYMLINK_MODE) + attr->ia_mode = COH_KLUDGE_NOT_SYMLINK; + + inode_setattr(inode, attr); + + return 0; +} + +static struct buffer_head * sysv_update_inode(struct inode * inode) +{ + struct super_block * sb = inode->i_sb; + struct buffer_head * bh; + struct sysv_inode * raw_inode; + unsigned int ino, block; + umode_t mode; + + ino = inode->i_ino; + if (!ino || ino > sb->sv_ninodes) { + printk("Bad inode number on dev 0x%04x: %d is out of range\n", + inode->i_dev, ino); + inode->i_dirt = 0; + return 0; + } + block = sb->sv_firstinodezone + ((ino-1) >> sb->sv_inodes_per_block_bits); + if (!(bh = sv_bread(sb,inode->i_dev,block))) { + printk("unable to read i-node block\n"); + inode->i_dirt = 0; + return 0; + } + raw_inode = (struct sysv_inode *) bh->b_data + ((ino-1) & sb->sv_inodes_per_block_1); + mode = inode->i_mode; + if (sb->sv_kludge_symlinks) + mode = to_coh_imode(mode); + raw_inode->i_mode = mode; + raw_inode->i_uid = inode->i_uid; + raw_inode->i_gid = inode->i_gid; + raw_inode->i_nlink = inode->i_nlink; + if (sb->sv_convert) { + raw_inode->i_size = to_coh_ulong(inode->i_size); + raw_inode->i_atime = to_coh_ulong(inode->i_atime); + raw_inode->i_mtime = to_coh_ulong(inode->i_mtime); + raw_inode->i_ctime = to_coh_ulong(inode->i_ctime); + } else { + raw_inode->i_size = inode->i_size; + raw_inode->i_atime = inode->i_atime; + raw_inode->i_mtime = inode->i_mtime; + raw_inode->i_ctime = inode->i_ctime; + } + if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode)) + raw_inode->i_a.i_rdev = inode->i_rdev; /* write 2 or 3 bytes ?? */ + else + if (sb->sv_convert) + for (block = 0; block < 10+1+1+1; block++) + coh_write3byte(&raw_inode->i_a.i_addb[3*block],inode->u.sysv_i.i_data[block]); + else + for (block = 0; block < 10+1+1+1; block++) + write3byte(&raw_inode->i_a.i_addb[3*block],inode->u.sysv_i.i_data[block]); + inode->i_dirt=0; + mark_buffer_dirty(bh, 1); + return bh; +} + +void sysv_write_inode(struct inode * inode) +{ + struct buffer_head *bh; + bh = sysv_update_inode(inode); + brelse(bh); +} + +int sysv_sync_inode(struct inode * inode) +{ + int err = 0; + struct buffer_head *bh; + + bh = sysv_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 sysv 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/sysv/mmap.c b/fs/sysv/mmap.c new file mode 100644 index 000000000..3ec3867a9 --- /dev/null +++ b/fs/sysv/mmap.c @@ -0,0 +1,85 @@ +/* + * linux/fs/sysv/mmap.c + * + * mm/memory.c, mm/mmap.c + * Copyright (C) 1991, 1992, 1993 Linus Torvalds + * + * nfs/mmap.c + * Copyright (C) 1993 Jon Tombs + * + * fs/msdos/mmap.c + * Copyright (C) 1994 Jacques Gelinas + * + * fs/sysv/mmap.c + * Copyright (C) 1994 Bruno Haible + * + * SystemV/Coherent mmap handling + */ + +#include <asm/segment.h> + +#include <linux/fs.h> +#include <linux/sysv_fs.h> +#include <linux/mm.h> +#include <linux/string.h> +#include <linux/stat.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/malloc.h> + +/* + * Fill in the supplied page for mmap + */ +static unsigned long sysv_file_mmap_nopage (struct vm_area_struct * area, + unsigned long address, unsigned long page, int no_share) +{ + int remaining, count, old_fs; + struct file filp; + + address &= PAGE_MASK; + /* prepare a file pointer */ + filp.f_pos = address - area->vm_start + area->vm_offset; + filp.f_reada = 0; + remaining = area->vm_end - address; + if (remaining > PAGE_SIZE) + remaining = PAGE_SIZE; + /* read from the file. page is in kernel space, not user space. */ + old_fs = get_fs(); set_fs(get_ds()); + count = sysv_file_read (area->vm_inode, &filp, (char *)page, remaining); + set_fs(old_fs); + if (count < 0) + count = 0; /* do nothing on I/O error ?? */ + else + remaining -= count; + if (remaining > 0) + memset((char *)page + count, 0, remaining); + return page; +} + +static struct vm_operations_struct sysv_file_mmap = { + NULL, /* open */ + NULL, /* close */ + sysv_file_mmap_nopage, /* nopage */ + NULL, /* wppage */ + NULL, /* share */ + NULL, /* unmap */ +}; + +int sysv_mmap(struct inode * inode, struct file * file, struct vm_area_struct * vma) +{ + if (vma->vm_page_prot & PAGE_RW) /* only PAGE_COW or read-only supported right now */ + return -EINVAL; + if (vma->vm_offset & (inode->i_sb->s_blocksize - 1)) + return -EINVAL; + if (!inode->i_sb || !S_ISREG(inode->i_mode)) + return -EACCES; + if (!IS_RDONLY(inode)) { + inode->i_atime = CURRENT_TIME; + inode->i_dirt = 1; + } + + vma->vm_inode = inode; + inode->i_count++; + vma->vm_ops = &sysv_file_mmap; + return 0; +} diff --git a/fs/sysv/namei.c b/fs/sysv/namei.c new file mode 100644 index 000000000..c9fd77158 --- /dev/null +++ b/fs/sysv/namei.c @@ -0,0 +1,822 @@ +/* + * linux/fs/sysv/namei.c + * + * minix/namei.c + * Copyright (C) 1991, 1992 Linus Torvalds + * + * coh/namei.c + * Copyright (C) 1993 Pascal Haible, Bruno Haible + * + * sysv/namei.c + * Copyright (C) 1993 Bruno Haible + */ + +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/sysv_fs.h> +#include <linux/string.h> +#include <linux/stat.h> +#include <linux/errno.h> + +/* compare strings: name[0..len-1] (not zero-terminated) and + * buffer[0..] (filled with zeroes up to buffer[0..maxlen-1]) + */ +static inline int namecompare(int len, int maxlen, + const char * name, const char * buffer) +{ + if (len > maxlen) + return 0; + if (len < maxlen && buffer[len]) + return 0; + return !memcmp(name, buffer, len); +} + +/* + * ok, we cannot use strncmp, as the name is not in our data space. [Now it is!] + * Thus we'll have to use sysv_match. No big problem. Match also makes + * some sanity tests. + * + * NOTE! unlike strncmp, sysv_match returns 1 for success, 0 for failure. + */ +static int sysv_match(int len, const char * name, struct sysv_dir_entry * de) +{ + if (!de->inode || len > SYSV_NAMELEN) + return 0; + /* "" means "." ---> so paths like "/usr/lib//libc.a" work */ + if (!len && (de->name[0]=='.') && (de->name[1]=='\0')) + return 1; + return namecompare(len,SYSV_NAMELEN,name,de->name); +} + +/* + * sysv_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. + */ +static struct buffer_head * sysv_find_entry(struct inode * dir, + const char * name, int namelen, struct sysv_dir_entry ** res_dir) +{ + struct super_block * sb; + unsigned long pos, block, offset; /* pos = block * block_size + offset */ + struct buffer_head * bh; + + *res_dir = NULL; + if (!dir) + return NULL; + sb = dir->i_sb; + if (namelen > SYSV_NAMELEN) + if (sb->sv_truncate) + namelen = SYSV_NAMELEN; + else + return NULL; + bh = NULL; + pos = block = offset = 0; + while (pos < dir->i_size) { + if (!bh) { + bh = sysv_file_bread(dir,block,0); + if (!bh) { + /* offset = 0; */ block++; + pos += sb->sv_block_size; + continue; + } + } + if (sysv_match(namelen, name, + *res_dir = (struct sysv_dir_entry *) (bh->b_data + offset) )) + return bh; + pos += SYSV_DIRSIZE; + offset += SYSV_DIRSIZE; + if (offset < sb->sv_block_size) + continue; + brelse(bh); + bh = NULL; + offset = 0; block++; + } + brelse(bh); + *res_dir = NULL; + return NULL; +} + +int sysv_lookup(struct inode * dir,const char * name, int len, + struct inode ** result) +{ + int ino; + struct sysv_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 = sysv_find_entry(dir,name,len,&de))) { + iput(dir); + return -ENOENT; + } + ino = de->inode; + brelse(bh); + if (!(*result = iget(dir->i_sb,ino))) { + iput(dir); + return -EACCES; + } + iput(dir); + return 0; +} + +/* + * sysv_add_entry() + * + * adds a file entry to the specified directory, returning a possible + * error value if it fails. + * + * 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 int sysv_add_entry(struct inode * dir, + const char * name, int namelen, + struct buffer_head ** res_buf, + struct sysv_dir_entry ** res_dir) +{ + struct super_block * sb; + int i; + unsigned long pos, block, offset; /* pos = block * block_size + offset */ + struct buffer_head * bh; + struct sysv_dir_entry * de; + + *res_buf = NULL; + *res_dir = NULL; + if (!dir) + return -ENOENT; + sb = dir->i_sb; + if (namelen > SYSV_NAMELEN) + if (sb->sv_truncate) + namelen = SYSV_NAMELEN; + else + return -ENAMETOOLONG; + if (!namelen) + return -ENOENT; + bh = NULL; + pos = block = offset = 0; + while (1) { + if (!bh) { + bh = sysv_file_bread(dir,block,1); + if (!bh) + return -ENOSPC; + } + de = (struct sysv_dir_entry *) (bh->b_data + offset); + pos += SYSV_DIRSIZE; + offset += SYSV_DIRSIZE; + if (pos > dir->i_size) { + de->inode = 0; + dir->i_size = pos; + dir->i_dirt = 1; + } + if (de->inode) { + if (namecompare(namelen, SYSV_NAMELEN, name, de->name)) { + brelse(bh); + return -EEXIST; + } + } else { + dir->i_mtime = dir->i_ctime = CURRENT_TIME; + for (i = 0; i < SYSV_NAMELEN ; i++) + de->name[i] = (i < namelen) ? name[i] : 0; + mark_buffer_dirty(bh, 1); + *res_dir = de; + break; + } + if (offset < sb->sv_block_size) + continue; + brelse(bh); + bh = NULL; + offset = 0; block++; + } + *res_buf = bh; + return 0; +} + +int sysv_create(struct inode * dir,const char * name, int len, int mode, + struct inode ** result) +{ + int error; + struct inode * inode; + struct buffer_head * bh; + struct sysv_dir_entry * de; + + *result = NULL; + if (!dir) + return -ENOENT; + inode = sysv_new_inode(dir); + if (!inode) { + iput(dir); + return -ENOSPC; + } + inode->i_op = &sysv_file_inode_operations; + inode->i_mode = mode; + inode->i_dirt = 1; + error = sysv_add_entry(dir,name,len, &bh ,&de); + if (error) { + inode->i_nlink--; + inode->i_dirt = 1; + iput(inode); + iput(dir); + return error; + } + de->inode = inode->i_ino; + mark_buffer_dirty(bh, 1); + brelse(bh); + iput(dir); + *result = inode; + return 0; +} + +int sysv_mknod(struct inode * dir, const char * name, int len, int mode, int rdev) +{ + int error; + struct inode * inode; + struct buffer_head * bh; + struct sysv_dir_entry * de; + + if (!dir) + return -ENOENT; + bh = sysv_find_entry(dir,name,len,&de); + if (bh) { + brelse(bh); + iput(dir); + return -EEXIST; + } + inode = sysv_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 = &sysv_file_inode_operations; + else if (S_ISDIR(inode->i_mode)) { + inode->i_op = &sysv_dir_inode_operations; + if (dir->i_mode & S_ISGID) + inode->i_mode |= S_ISGID; + } + else if (S_ISLNK(inode->i_mode)) + inode->i_op = &sysv_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; + inode->i_dirt = 1; + error = sysv_add_entry(dir, name, len, &bh, &de); + if (error) { + inode->i_nlink--; + inode->i_dirt = 1; + iput(inode); + iput(dir); + return error; + } + de->inode = inode->i_ino; + mark_buffer_dirty(bh, 1); + brelse(bh); + iput(dir); + iput(inode); + return 0; +} + +int sysv_mkdir(struct inode * dir, const char * name, int len, int mode) +{ + int error; + struct inode * inode; + struct buffer_head * bh, *dir_block; + struct sysv_dir_entry * de; + + if (!dir) { + iput(dir); + return -EINVAL; + } + bh = sysv_find_entry(dir,name,len,&de); + if (bh) { + brelse(bh); + iput(dir); + return -EEXIST; + } + if (dir->i_nlink >= dir->i_sb->sv_link_max) { + iput(dir); + return -EMLINK; + } + inode = sysv_new_inode(dir); + if (!inode) { + iput(dir); + return -ENOSPC; + } + inode->i_op = &sysv_dir_inode_operations; + inode->i_size = 2 * SYSV_DIRSIZE; + dir_block = sysv_file_bread(inode,0,1); + if (!dir_block) { + iput(dir); + inode->i_nlink--; + inode->i_dirt = 1; + iput(inode); + return -ENOSPC; + } + de = (struct sysv_dir_entry *) (dir_block->b_data + 0*SYSV_DIRSIZE); + de->inode = inode->i_ino; + strcpy(de->name,"."); /* rest of de->name is zero, see sysv_new_block */ + de = (struct sysv_dir_entry *) (dir_block->b_data + 1*SYSV_DIRSIZE); + de->inode = dir->i_ino; + strcpy(de->name,".."); /* rest of de->name is zero, see sysv_new_block */ + 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; + error = sysv_add_entry(dir, name, len, &bh, &de); + if (error) { + iput(dir); + inode->i_nlink=0; + iput(inode); + return error; + } + 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) +{ + struct super_block * sb; + unsigned long pos, block, offset; /* pos = block * block_size + offset */ + struct buffer_head * bh; + struct sysv_dir_entry * de; + + if (!inode) + return 1; + block = 0; + bh = NULL; + pos = offset = 2*SYSV_DIRSIZE; + if (inode->i_size % SYSV_DIRSIZE) + goto bad_dir; + if (inode->i_size < pos) + goto bad_dir; + bh = sysv_file_bread(inode,0,0); + if (!bh) + goto bad_dir; + de = (struct sysv_dir_entry *) (bh->b_data + 0*SYSV_DIRSIZE); + if (!de->inode || strcmp(de->name,".")) + goto bad_dir; + de = (struct sysv_dir_entry *) (bh->b_data + 1*SYSV_DIRSIZE); + if (!de->inode || strcmp(de->name,"..")) + goto bad_dir; + sb = inode->i_sb; + while (pos < inode->i_size) { + if (!bh) { + bh = sysv_file_bread(inode,block,0); + if (!bh) { + /* offset = 0; */ block++; + pos += sb->sv_block_size; + continue; + } + } + de = (struct sysv_dir_entry *) (bh->b_data + offset); + pos += SYSV_DIRSIZE; + offset += SYSV_DIRSIZE; + if (de->inode) { + brelse(bh); + return 0; + } + if (offset < sb->sv_block_size) + continue; + brelse(bh); + bh = NULL; + offset = 0; block++; + } + brelse(bh); + return 1; +bad_dir: + brelse(bh); + printk("Bad directory on device %04x\n",inode->i_dev); + return 1; +} + +int sysv_rmdir(struct inode * dir, const char * name, int len) +{ + int retval; + struct inode * inode; + struct buffer_head * bh; + struct sysv_dir_entry * de; + + inode = NULL; + bh = sysv_find_entry(dir,name,len,&de); + 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 (de->inode != inode->i_ino) { + retval = -ENOENT; + 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; + 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 sysv_unlink(struct inode * dir, const char * name, int len) +{ + int retval; + struct inode * inode; + struct buffer_head * bh; + struct sysv_dir_entry * de; + +repeat: + retval = -ENOENT; + inode = NULL; + bh = sysv_find_entry(dir,name,len,&de); + if (!bh) + goto end_unlink; + if (!(inode = iget(dir->i_sb, de->inode))) + goto end_unlink; + retval = -EPERM; + if (S_ISDIR(inode->i_mode)) + goto end_unlink; + if (de->inode != inode->i_ino) { + iput(inode); + brelse(bh); + current->counter = 0; + schedule(); + goto repeat; + } + if ((dir->i_mode & S_ISVTX) && !fsuser() && + current->fsuid != inode->i_uid && + current->fsuid != dir->i_uid) + goto end_unlink; + if (de->inode != inode->i_ino) { + retval = -ENOENT; + goto end_unlink; + } + if (!inode->i_nlink) { + printk("Deleting nonexistent file (%04x:%lu), %d\n", + inode->i_dev,inode->i_ino,inode->i_nlink); + inode->i_nlink=1; + } + de->inode = 0; + mark_buffer_dirty(bh, 1); + dir->i_ctime = dir->i_mtime = CURRENT_TIME; + dir->i_dirt = 1; + inode->i_nlink--; + inode->i_ctime = dir->i_ctime; + inode->i_dirt = 1; + retval = 0; +end_unlink: + brelse(bh); + iput(inode); + iput(dir); + return retval; +} + +int sysv_symlink(struct inode * dir, const char * name, int len, const char * symname) +{ + struct sysv_dir_entry * de; + struct inode * inode; + struct buffer_head * name_block; + char * name_block_data; + struct super_block * sb; + int i; + char c; + struct buffer_head * bh; + + if (!(inode = sysv_new_inode(dir))) { + iput(dir); + return -ENOSPC; + } + inode->i_mode = S_IFLNK | 0777; + inode->i_op = &sysv_symlink_inode_operations; + name_block = sysv_file_bread(inode,0,1); + if (!name_block) { + iput(dir); + inode->i_nlink--; + inode->i_dirt = 1; + iput(inode); + return -ENOSPC; + } + sb = inode->i_sb; + name_block_data = name_block->b_data; + i = 0; + while (i < sb->sv_block_size_1 && (c = *(symname++))) + name_block_data[i++] = c; + name_block_data[i] = 0; + mark_buffer_dirty(name_block, 1); + brelse(name_block); + inode->i_size = i; + inode->i_dirt = 1; + bh = sysv_find_entry(dir,name,len,&de); + if (bh) { + inode->i_nlink--; + inode->i_dirt = 1; + iput(inode); + brelse(bh); + iput(dir); + return -EEXIST; + } + i = sysv_add_entry(dir, name, len, &bh, &de); + if (i) { + inode->i_nlink--; + inode->i_dirt = 1; + iput(inode); + iput(dir); + return i; + } + de->inode = inode->i_ino; + mark_buffer_dirty(bh, 1); + brelse(bh); + iput(dir); + iput(inode); + return 0; +} + +int sysv_link(struct inode * oldinode, struct inode * dir, const char * name, int len) +{ + int error; + struct sysv_dir_entry * de; + struct buffer_head * bh; + + if (S_ISDIR(oldinode->i_mode)) { + iput(oldinode); + iput(dir); + return -EPERM; + } + if (oldinode->i_nlink >= oldinode->i_sb->sv_link_max) { + iput(oldinode); + iput(dir); + return -EMLINK; + } + bh = sysv_find_entry(dir,name,len,&de); + if (bh) { + brelse(bh); + iput(dir); + iput(oldinode); + return -EEXIST; + } + error = sysv_add_entry(dir, name, len, &bh, &de); + if (error) { + iput(dir); + iput(oldinode); + return error; + } + 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; +} + +/* return 1 if `new' is a subdir of `old' on the same device */ +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 (sysv_lookup(new_inode,"..",2,&new_inode)) + break; + if (new_inode->i_ino == ino) /* root dir reached ? */ + break; + } + iput(new_inode); + return result; +} + +#define PARENT_INO(buffer) \ +(((struct sysv_dir_entry *) ((buffer) + 1*SYSV_DIRSIZE))->inode) + +/* + * 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_sysv_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 sysv_dir_entry * old_de, * new_de; + 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 = sysv_find_entry(old_dir,old_name,old_len,&old_de); + 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 = sysv_find_entry(new_dir,new_name,new_len,&new_de); + if (new_bh) { + new_inode = __iget(new_dir->i_sb, new_de->inode, 0); + 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 = -EISDIR; + if (!S_ISDIR(old_inode->i_mode)) + goto end_rename; + retval = -EINVAL; + if (subdir(new_dir, old_inode)) + goto end_rename; + retval = -ENOTEMPTY; + if (!empty_dir(new_inode)) + goto end_rename; + retval = -EBUSY; + if (new_inode->i_count > 1) + 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 = -ENOTDIR; + if (new_inode && !S_ISDIR(new_inode->i_mode)) + goto end_rename; + retval = -EINVAL; + if (subdir(new_dir, old_inode)) + goto end_rename; + retval = -EIO; + dir_bh = sysv_file_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; + retval = -EMLINK; + if (!new_inode && new_dir->i_nlink >= new_dir->i_sb->sv_link_max) + goto end_rename; + } + if (!new_bh) { + retval = sysv_add_entry(new_dir,new_name,new_len,&new_bh,&new_de); + if (retval) + 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; + new_de->inode = old_inode->i_ino; + old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME; + old_dir->i_dirt = 1; + new_dir->i_ctime = new_dir->i_mtime = CURRENT_TIME; + new_dir->i_dirt = 1; + if (new_inode) { + new_inode->i_nlink--; + new_inode->i_ctime = CURRENT_TIME; + 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--; + old_dir->i_dirt = 1; + if (new_inode) { + new_inode->i_nlink--; + new_inode->i_dirt = 1; + } else { + new_dir->i_nlink++; + 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 sysv_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_sysv_rename(old_dir, old_name, old_len, + new_dir, new_name, new_len); + lock = 0; + wake_up(&wait); + return result; +} diff --git a/fs/sysv/symlink.c b/fs/sysv/symlink.c new file mode 100644 index 000000000..d392816bc --- /dev/null +++ b/fs/sysv/symlink.c @@ -0,0 +1,110 @@ +/* + * linux/fs/sysv/symlink.c + * + * minix/symlink.c + * Copyright (C) 1991, 1992 Linus Torvalds + * + * coh/symlink.c + * Copyright (C) 1993 Pascal Haible, Bruno Haible + * + * sysv/symlink.c + * Copyright (C) 1993 Bruno Haible + * + * SystemV/Coherent symlink handling code + */ + +#include <asm/segment.h> + +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/sysv_fs.h> +#include <linux/stat.h> + +static int sysv_readlink(struct inode *, char *, int); +static int sysv_follow_link(struct inode *, struct inode *, int, int, struct inode **); + +/* + * symlinks can't do much... + */ +struct inode_operations sysv_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 */ + sysv_readlink, /* readlink */ + sysv_follow_link, /* follow_link */ + NULL, /* bmap */ + NULL, /* truncate */ + NULL /* permission */ +}; + +static int sysv_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(inode); + iput(dir); + return -ELOOP; + } + if (!(bh = sysv_file_bread(inode, 0, 0))) { /* is reading 1 block enough ?? */ + 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 sysv_readlink(struct inode * inode, char * buffer, int buflen) +{ + struct buffer_head * bh; + char * bh_data; + int i; + char c; + + if (!S_ISLNK(inode->i_mode)) { + iput(inode); + return -EINVAL; + } + if (buflen > inode->i_sb->sv_block_size_1) + buflen = inode->i_sb->sv_block_size_1; + bh = sysv_file_bread(inode, 0, 0); + iput(inode); + if (!bh) + return 0; + bh_data = bh->b_data; + i = 0; + while (i<buflen && (c = bh_data[i])) { + i++; + put_fs_byte(c,buffer++); + } + brelse(bh); + return i; +} diff --git a/fs/sysv/truncate.c b/fs/sysv/truncate.c new file mode 100644 index 000000000..21451e6dd --- /dev/null +++ b/fs/sysv/truncate.c @@ -0,0 +1,283 @@ +/* + * linux/fs/sysv/truncate.c + * + * minix/truncate.c + * Copyright (C) 1991, 1992 Linus Torvalds + * + * coh/truncate.c + * Copyright (C) 1993 Pascal Haible, Bruno Haible + * + * sysv/truncate.c + * Copyright (C) 1993 Bruno Haible + */ + +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/sysv_fs.h> +#include <linux/stat.h> + + +/* Linus' implementation of truncate. + * It doesn't need locking because it can tell from looking at bh->b_count + * whether a given block is in use elsewhere. + */ + +/* + * 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. + */ + +/* We throw away any data beyond inode->i_size. */ + +static int trunc_direct(struct inode * inode) +{ + struct super_block * sb; + unsigned int i; + unsigned long * p; + unsigned long block; + struct buffer_head * bh; + int retry = 0; + + sb = inode->i_sb; +repeat: + for (i = ((unsigned long) inode->i_size + sb->sv_block_size_1) >> sb->sv_block_size_bits; i < 10; i++) { + p = inode->u.sysv_i.i_data + i; + block = *p; + if (!block) + continue; + bh = sv_get_hash_table(sb, inode->i_dev, block); + if ((i << sb->sv_block_size_bits) < inode->i_size) { + brelse(bh); + goto repeat; + } + if ((bh && bh->b_count != 1) || (block != *p)) { + retry = 1; + brelse(bh); + continue; + } + *p = 0; + inode->i_dirt = 1; + brelse(bh); + sysv_free_block(sb,block); + } + return retry; +} + +static int trunc_indirect(struct inode * inode, unsigned long offset, unsigned long * p, int convert, unsigned char * dirt) +{ + unsigned long indtmp, indblock; + struct super_block * sb; + struct buffer_head * indbh; + unsigned int i; + sysv_zone_t * ind; + unsigned long tmp, block; + struct buffer_head * bh; + int retry = 0; + + indblock = indtmp = *p; + if (convert) + indblock = from_coh_ulong(indblock); + if (!indblock) + return 0; + sb = inode->i_sb; + indbh = sv_bread(sb, inode->i_dev, indblock); + if (indtmp != *p) { + brelse(indbh); + return 1; + } + if (!indbh) { + *p = 0; + *dirt = 1; + return 0; + } +repeat: + if (inode->i_size < offset) + i = 0; + else + i = (inode->i_size - offset + sb->sv_block_size_1) >> sb->sv_block_size_bits; + for (; i < sb->sv_ind_per_block; i++) { + ind = ((sysv_zone_t *) indbh->b_data) + i; + block = tmp = *ind; + if (sb->sv_convert) + block = from_coh_ulong(block); + if (!block) + continue; + bh = sv_get_hash_table(sb, inode->i_dev, block); + if ((i << sb->sv_block_size_bits) + offset < inode->i_size) { + brelse(bh); + goto repeat; + } + if ((bh && bh->b_count != 1) || (tmp != *ind)) { + retry = 1; + brelse(bh); + continue; + } + *ind = 0; + mark_buffer_dirty(indbh, 1); + brelse(bh); + sysv_free_block(sb,block); + } + for (i = 0; i < sb->sv_ind_per_block; i++) + if (((sysv_zone_t *) indbh->b_data)[i]) + goto done; + if ((indbh->b_count != 1) || (indtmp != *p)) { + brelse(indbh); + return 1; + } + *p = 0; + *dirt = 1; + sysv_free_block(sb,indblock); +done: + brelse(indbh); + return retry; +} + +static int trunc_dindirect(struct inode * inode, unsigned long offset, unsigned long * p, int convert, unsigned char * dirt) +{ + unsigned long indtmp, indblock; + struct super_block * sb; + struct buffer_head * indbh; + unsigned int i; + sysv_zone_t * ind; + unsigned long tmp, block; + int retry = 0; + + indblock = indtmp = *p; + if (convert) + indblock = from_coh_ulong(indblock); + if (!indblock) + return 0; + sb = inode->i_sb; + indbh = sv_bread(sb, inode->i_dev, indblock); + if (indtmp != *p) { + brelse(indbh); + return 1; + } + if (!indbh) { + *p = 0; + *dirt = 1; + return 0; + } + if (inode->i_size < offset) + i = 0; + else + i = (inode->i_size - offset + sb->sv_ind_per_block_block_size_1) >> sb->sv_ind_per_block_block_size_bits; + for (; i < sb->sv_ind_per_block; i++) { + ind = ((sysv_zone_t *) indbh->b_data) + i; + block = tmp = *ind; + if (sb->sv_convert) + block = from_coh_ulong(block); + if (!block) + continue; + retry |= trunc_indirect(inode,offset+(i<<sb->sv_ind_per_block_bits),ind,sb->sv_convert,&indbh->b_dirt); + } + for (i = 0; i < sb->sv_ind_per_block; i++) + if (((sysv_zone_t *) indbh->b_data)[i]) + goto done; + if ((indbh->b_count != 1) || (indtmp != *p)) { + brelse(indbh); + return 1; + } + *p = 0; + *dirt = 1; + sysv_free_block(sb,indblock); +done: + brelse(indbh); + return retry; +} + +static int trunc_tindirect(struct inode * inode, unsigned long offset, unsigned long * p, int convert, unsigned char * dirt) +{ + unsigned long indtmp, indblock; + struct super_block * sb; + struct buffer_head * indbh; + unsigned int i; + sysv_zone_t * ind; + unsigned long tmp, block; + int retry = 0; + + indblock = indtmp = *p; + if (convert) + indblock = from_coh_ulong(indblock); + if (!indblock) + return 0; + sb = inode->i_sb; + indbh = sv_bread(sb, inode->i_dev, indblock); + if (indtmp != *p) { + brelse(indbh); + return 1; + } + if (!indbh) { + *p = 0; + *dirt = 1; + return 0; + } + if (inode->i_size < offset) + i = 0; + else + i = (inode->i_size - offset + sb->sv_ind_per_block_2_block_size_1) >> sb->sv_ind_per_block_2_block_size_bits; + for (; i < sb->sv_ind_per_block; i++) { + ind = ((sysv_zone_t *) indbh->b_data) + i; + block = tmp = *ind; + if (sb->sv_convert) + block = from_coh_ulong(block); + if (!block) + continue; + retry |= trunc_dindirect(inode,offset+(i<<sb->sv_ind_per_block_2_bits),ind,sb->sv_convert,&indbh->b_dirt); + } + for (i = 0; i < sb->sv_ind_per_block; i++) + if (((sysv_zone_t *) indbh->b_data)[i]) + goto done; + if ((indbh->b_count != 1) || (indtmp != *p)) { + brelse(indbh); + return 1; + } + *p = 0; + *dirt = 1; + sysv_free_block(sb,indblock); +done: + brelse(indbh); + return retry; +} + +static int trunc_all(struct inode * inode) +{ + struct super_block * sb; + + sb = inode->i_sb; + return trunc_direct(inode) + | trunc_indirect(inode,sb->sv_ind0_size,&inode->u.sysv_i.i_data[10],0,&inode->i_dirt) + | trunc_dindirect(inode,sb->sv_ind1_size,&inode->u.sysv_i.i_data[11],0,&inode->i_dirt) + | trunc_tindirect(inode,sb->sv_ind2_size,&inode->u.sysv_i.i_data[12],0,&inode->i_dirt); +} + + +void sysv_truncate(struct inode * inode) +{ + /* If this is called from sysv_put_inode, we needn't worry about + * races as we are just losing the last reference to the inode. + * If this is called from another place, let's hope it's a regular + * file. + * Truncating symbolic links is strange. We assume we don't truncate + * a directory we are just modifying. We ensure we don't truncate + * a regular file we are just writing to, by use of a lock. + */ + if (S_ISLNK(inode->i_mode)) + printk("sysv_truncate: truncating symbolic link\n"); + else if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))) + return; + while (trunc_all(inode)) { + current->counter = 0; + schedule(); + } + inode->i_mtime = inode->i_ctime = CURRENT_TIME; + inode->i_dirt = 1; +} |