diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1998-03-17 22:05:47 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 1998-03-17 22:05:47 +0000 |
commit | 27cfca1ec98e91261b1a5355d10a8996464b63af (patch) | |
tree | 8e895a53e372fa682b4c0a585b9377d67ed70d0e /fs/hfs/file.c | |
parent | 6a76fb7214c477ccf6582bd79c5b4ccc4f9c41b1 (diff) |
Look Ma' what I found on my harddisk ...
o New faster syscalls for 2.1.x, too
o Upgrade to 2.1.89.
Don't try to run this. It's flaky as hell. But feel free to debug ...
Diffstat (limited to 'fs/hfs/file.c')
-rw-r--r-- | fs/hfs/file.c | 531 |
1 files changed, 531 insertions, 0 deletions
diff --git a/fs/hfs/file.c b/fs/hfs/file.c new file mode 100644 index 000000000..26f498305 --- /dev/null +++ b/fs/hfs/file.c @@ -0,0 +1,531 @@ +/* + * linux/fs/hfs/file.c + * + * Copyright (C) 1995, 1996 Paul H. Hargrove + * This file may be distributed under the terms of the GNU Public License. + * + * This file contains the file-related functions which are independent of + * which scheme is being used to represent forks. + * + * Based on the minix file system code, (C) 1991, 1992 by Linus Torvalds + * + * "XXX" in a comment is a note to myself to consider changing something. + * + * In function preconditions the term "valid" applied to a pointer to + * a structure means that the pointer is non-NULL and the structure it + * points to has all fields initialized to consistent values. + */ + +#include "hfs.h" +#include <linux/hfs_fs_sb.h> +#include <linux/hfs_fs_i.h> +#include <linux/hfs_fs.h> + +/*================ Forward declarations ================*/ + +static hfs_rwret_t hfs_file_read(struct file *, char *, hfs_rwarg_t, + loff_t *); +static hfs_rwret_t hfs_file_write(struct file *, const char *, hfs_rwarg_t, + loff_t *); +static void hfs_file_truncate(struct inode *); +static int hfs_bmap(struct inode *, int); + +/*================ Global variables ================*/ + +static struct file_operations hfs_file_operations = { + NULL, /* lseek - default */ + hfs_file_read, /* read */ + hfs_file_write, /* write */ + NULL, /* readdir - bad */ + NULL, /* select - default */ + NULL, /* ioctl - default */ + generic_file_mmap, /* mmap */ + NULL, /* open */ + NULL, /* release */ + file_fsync, /* fsync - default */ + NULL, /* fasync - default */ + NULL, /* check_media_change - none */ + NULL, /* revalidate - none */ + NULL /* lock - none */ +}; + +struct inode_operations hfs_file_inode_operations = { + &hfs_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 */ + generic_readpage, /* readpage */ + NULL, /* writepage */ + hfs_bmap, /* bmap */ + hfs_file_truncate, /* truncate */ + NULL, /* permission */ + NULL, /* smap */ + NULL, /* updatepage */ + NULL /* revalidate */ +}; + +/*================ Variable-like macros ================*/ + +/* maximum number of blocks to try to read in at once */ +#define NBUF 32 + +/*================ File-local functions ================*/ + +/* + * hfs_getblk() + * + * Given an hfs_fork and a block number return the buffer_head for + * that block from the fork. If 'create' is non-zero then allocate + * the necessary block(s) to the fork. + */ +struct buffer_head *hfs_getblk(struct hfs_fork *fork, int block, int create) +{ + int tmp; + kdev_t dev = fork->entry->mdb->sys_mdb->s_dev; + + tmp = hfs_extent_map(fork, block, create); + + if (create) { + /* If writing the block, then we have exclusive access + to the file until we return, so it can't have moved. + */ + if (tmp) { + hfs_cat_mark_dirty(fork->entry); + return getblk(dev, tmp, HFS_SECTOR_SIZE); + } + return NULL; + + } else { + /* If reading the block, then retry since the + location on disk could have changed while + we waited on the I/O in getblk to complete. + */ + do { + struct buffer_head *bh = + getblk(dev, tmp, HFS_SECTOR_SIZE); + int tmp2 = hfs_extent_map(fork, block, 0); + + if (tmp2 == tmp) { + return bh; + } else { + /* The block moved or no longer exists. */ + brelse(bh); + tmp = tmp2; + } + } while (tmp != 0); + + /* The block no longer exists. */ + return NULL; + } +} + +/* + * hfs_bmap() + * + * This is the bmap() field in the inode_operations structure for + * "regular" (non-header) files. The purpose is to translate an inode + * and a block number within the corresponding file into a physical + * block number. This function just calls hfs_extent_map() to do the + * real work. + */ +static int hfs_bmap(struct inode * inode, int block) +{ + return hfs_extent_map(HFS_I(inode)->fork, block, 0); +} + +/* + * hfs_file_read() + * + * This is the read field in the inode_operations structure for + * "regular" (non-header) files. The purpose is to transfer up to + * 'count' bytes from the file corresponding to 'inode', beginning at + * 'filp->offset' bytes into the file. The data is transfered to + * user-space at the address 'buf'. Returns the number of bytes + * successfully transfered. This function checks the arguments, does + * some setup and then calls hfs_do_read() to do the actual transfer. + */ +static hfs_rwret_t hfs_file_read(struct file * filp, char * buf, + hfs_rwarg_t count, loff_t *ppos) +{ + struct inode *inode = filp->f_dentry->d_inode; + hfs_s32 read, left, pos, size; + + if (!S_ISREG(inode->i_mode)) { + hfs_warn("hfs_file_read: mode = %07o\n",inode->i_mode); + return -EINVAL; + } + pos = *ppos; + if (pos >= HFS_FORK_MAX) { + return 0; + } + size = inode->i_size; + if (pos > size) { + left = 0; + } else { + left = size - pos; + } + if (left > count) { + left = count; + } + if (left <= 0) { + return 0; + } + if ((read = hfs_do_read(inode, HFS_I(inode)->fork, pos, + buf, left, filp->f_reada != 0)) > 0) { + *ppos += read; + filp->f_reada = 1; + } + + return read; +} + +/* + * hfs_file_write() + * + * This is the write() entry in the file_operations structure for + * "regular" files. The purpose is to transfer up to 'count' bytes + * to the file corresponding to 'inode' beginning at offset + * 'file->f_pos' from user-space at the address 'buf'. The return + * value is the number of bytes actually transferred. + */ +static hfs_rwret_t hfs_file_write(struct file * filp, const char * buf, + hfs_rwarg_t count, loff_t *ppos) +{ + struct inode *inode = filp->f_dentry->d_inode; + struct hfs_fork *fork = HFS_I(inode)->fork; + hfs_s32 written, pos; + + if (!S_ISREG(inode->i_mode)) { + hfs_warn("hfs_file_write: mode = %07o\n", inode->i_mode); + return -EINVAL; + } + + pos = (filp->f_flags & O_APPEND) ? inode->i_size : *ppos; + + if (pos >= HFS_FORK_MAX) { + return 0; + } + if (count > HFS_FORK_MAX) { + count = HFS_FORK_MAX; + } + if ((written = hfs_do_write(inode, fork, pos, buf, count)) > 0) + pos += written; + + *ppos = pos; + if (*ppos > inode->i_size) + inode->i_size = *ppos; + + return written; +} + +/* + * hfs_file_truncate() + * + * This is the truncate() entry in the file_operations structure for + * "regular" files. The purpose is to change the length of the file + * corresponding to the given inode. Changes can either lengthen or + * shorten the file. + */ +static void hfs_file_truncate(struct inode * inode) +{ + struct hfs_fork *fork = HFS_I(inode)->fork; + + fork->lsize = inode->i_size; + hfs_extent_adj(fork); + hfs_cat_mark_dirty(HFS_I(inode)->entry); + + inode->i_size = fork->lsize; + inode->i_blocks = fork->psize; +} + +/* + * xlate_to_user() + * + * Like copy_to_user() while translating CR->NL. + */ +static inline void xlate_to_user(char *buf, const char *data, int count) +{ + char ch; + + while (count--) { + ch = *(data++); + put_user((ch == '\r') ? '\n' : ch, buf++); + } +} + +/* + * xlate_from_user() + * + * Like copy_from_user() while translating NL->CR; + */ +static inline void xlate_from_user(char *data, const char *buf, int count) +{ + copy_from_user(data, buf, count); + while (count--) { + if (*data == '\n') { + *data = '\r'; + } + ++data; + } +} + +/*================ Global functions ================*/ + +/* + * hfs_do_read() + * + * This function transfers actual data from disk to user-space memory, + * returning the number of bytes successfully transfered. 'fork' tells + * which file on the disk to read from. 'pos' gives the offset into + * the Linux file at which to begin the transfer. Note that this will + * differ from 'filp->offset' in the case of an AppleDouble header file + * due to the block of metadata at the beginning of the file, which has + * no corresponding place in the HFS file. 'count' tells how many + * bytes to transfer. 'buf' gives an address in user-space to transfer + * the data to. + * + * This is based on Linus's minix_file_read(). + * It has been changed to take into account that HFS files have no holes. + */ +hfs_s32 hfs_do_read(struct inode *inode, struct hfs_fork * fork, hfs_u32 pos, + char * buf, hfs_u32 count, int reada) +{ + kdev_t dev = inode->i_dev; + hfs_s32 size, chars, offset, block, blocks, read = 0; + int bhrequest, uptodate; + int convert = HFS_I(inode)->convert; + struct buffer_head ** bhb, ** bhe; + struct buffer_head * bhreq[NBUF]; + struct buffer_head * buflist[NBUF]; + + /* split 'pos' in to block and (byte) offset components */ + block = pos >> HFS_SECTOR_SIZE_BITS; + offset = pos & (HFS_SECTOR_SIZE-1); + + /* compute the logical size of the fork in blocks */ + size = (fork->lsize + (HFS_SECTOR_SIZE-1)) >> HFS_SECTOR_SIZE_BITS; + + /* compute the number of physical blocks to be transferred */ + blocks = (count+offset+HFS_SECTOR_SIZE-1) >> HFS_SECTOR_SIZE_BITS; + + bhb = bhe = buflist; + if (reada) { + if (blocks < read_ahead[MAJOR(dev)] / (HFS_SECTOR_SIZE>>9)) { + blocks = read_ahead[MAJOR(dev)] / (HFS_SECTOR_SIZE>>9); + } + if (block + blocks > size) { + blocks = size - block; + } + } + + /* We do this in a two stage process. We first try and + request as many blocks as we can, then we wait for the + first one to complete, and then we try and wrap up as many + as are actually done. + + This routine is optimized to make maximum use of the + various buffers and caches. */ + + do { + bhrequest = 0; + uptodate = 1; + while (blocks) { + --blocks; + *bhb = hfs_getblk(fork, block++, 0); + + if (!(*bhb)) { + /* Since there are no holes in HFS files + we must have encountered an error. + So, stop adding blocks to the queue. */ + blocks = 0; + break; + } + + if (!buffer_uptodate(*bhb)) { + 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; + } + } + + /* If the only block in the queue is bad then quit */ + if (!(*bhe)) { + break; + } + + /* Now request them all */ + if (bhrequest) { + ll_rw_block(READ, bhrequest, bhreq); + } + + do { /* Finish off all I/O that has actually completed */ + char *p; + + wait_on_buffer(*bhe); + + if (!buffer_uptodate(*bhe)) { + /* read error? */ + brelse(*bhe); + if (++bhe == &buflist[NBUF]) { + bhe = buflist; + } + count = 0; + break; + } + + if (count < HFS_SECTOR_SIZE - offset) { + chars = count; + } else { + chars = HFS_SECTOR_SIZE - offset; + } + count -= chars; + read += chars; + p = (*bhe)->b_data + offset; + if (convert) { + xlate_to_user(buf, p, chars); + } else { + copy_to_user(buf, p, chars); + } + brelse(*bhe); + buf += chars; + offset = 0; + if (++bhe == &buflist[NBUF]) { + bhe = buflist; + } + } while (count && (bhe != bhb) && !buffer_locked(*bhe)); + } while (count); + + /* Release the read-ahead blocks */ + while (bhe != bhb) { + brelse(*bhe); + if (++bhe == &buflist[NBUF]) { + bhe = buflist; + } + } + if (!read) { + return -EIO; + } + return read; +} + +/* + * hfs_do_write() + * + * This function transfers actual data from user-space memory to disk, + * returning the number of bytes successfully transfered. 'fork' tells + * which file on the disk to write to. 'pos' gives the offset into + * the Linux file at which to begin the transfer. Note that this will + * differ from 'filp->offset' in the case of an AppleDouble header file + * due to the block of metadata at the beginning of the file, which has + * no corresponding place in the HFS file. 'count' tells how many + * bytes to transfer. 'buf' gives an address in user-space to transfer + * the data from. + * + * This is just a minor edit of Linus's minix_file_write(). + */ +hfs_s32 hfs_do_write(struct inode *inode, struct hfs_fork * fork, hfs_u32 pos, + const char * buf, hfs_u32 count) +{ + hfs_s32 written, c; + struct buffer_head * bh; + char * p; + int convert = HFS_I(inode)->convert; + + written = 0; + while (written < count) { + bh = hfs_getblk(fork, pos/HFS_SECTOR_SIZE, 1); + if (!bh) { + if (!written) { + written = -ENOSPC; + } + break; + } + c = HFS_SECTOR_SIZE - (pos % HFS_SECTOR_SIZE); + if (c > count - written) { + c = count - written; + } + if (c != HFS_SECTOR_SIZE && !buffer_uptodate(bh)) { + ll_rw_block(READ, 1, &bh); + wait_on_buffer(bh); + if (!buffer_uptodate(bh)) { + brelse(bh); + if (!written) { + written = -EIO; + } + break; + } + } + p = (pos % HFS_SECTOR_SIZE) + bh->b_data; + if (convert) { + xlate_from_user(p, buf, c); + } else { + copy_from_user(p, buf, c); + } + update_vm_cache(inode,pos,p,c); + pos += c; + written += c; + buf += c; + mark_buffer_uptodate(bh, 1); + mark_buffer_dirty(bh, 0); + brelse(bh); + } + if (written > 0) { + struct hfs_cat_entry *entry = fork->entry; + + inode->i_mtime = inode->i_ctime = CURRENT_TIME; + if (pos > fork->lsize) { + fork->lsize = pos; + } + entry->modify_date = hfs_u_to_mtime(CURRENT_TIME); + hfs_cat_mark_dirty(entry); + } + return written; +} + +/* + * hfs_file_fix_mode() + * + * Fixes up the permissions on a file after changing the write-inhibit bit. + */ +void hfs_file_fix_mode(struct hfs_cat_entry *entry) +{ + struct dentry **de = entry->sys_entry; + int i; + + if (entry->u.file.flags & HFS_FIL_LOCK) { + for (i = 0; i < 4; ++i) { + if (de[i]) { + de[i]->d_inode->i_mode &= ~S_IWUGO; + } + } + } else { + for (i = 0; i < 4; ++i) { + if (de[i]) { + struct inode *inode = de[i]->d_inode; + inode->i_mode |= S_IWUGO; + inode->i_mode &= + ~HFS_SB(inode->i_sb)->s_umask; + } + } + } +} |