summaryrefslogtreecommitdiffstats
path: root/fs/hfs/file.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/hfs/file.c')
-rw-r--r--fs/hfs/file.c531
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;
+ }
+ }
+ }
+}