patch-2.1.78 linux/fs/hfs/file.c

Next file: linux/fs/hfs/file_cap.c
Previous file: linux/fs/hfs/extent.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.77/linux/fs/hfs/file.c linux/fs/hfs/file.c
@@ -0,0 +1,525 @@
+/*
+ * 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.
+		*/
+		return tmp ? getblk(dev, tmp, HFS_SECTOR_SIZE) : 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);
+
+	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);
+		entry->dirt = 1;
+	}
+	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;
+			}
+		}
+	}
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov