blob: 0bf8f39c277347408a8ad14528659c0e751a68b1 [file] [log] [blame]
/*
* linux/fs/msdos/file.c
*
* Written 1992,1993 by Werner Almesberger
*
* MS-DOS regular file handling primitives
*/
#include <asm/segment.h>
#include <asm/system.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/msdos_fs.h>
#include <linux/errno.h>
#include <linux/fcntl.h>
#include <linux/stat.h>
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
static int msdos_file_read(struct inode *inode,struct file *filp,char *buf,
int count);
static int msdos_file_write(struct inode *inode,struct file *filp,char *buf,
int count);
static struct file_operations msdos_file_operations = {
NULL, /* lseek - default */
msdos_file_read, /* read */
msdos_file_write, /* write */
NULL, /* readdir - bad */
NULL, /* select - default */
NULL, /* ioctl - default */
NULL, /* mmap */
NULL, /* no special open is needed */
NULL, /* release */
file_fsync /* fsync */
};
struct inode_operations msdos_file_inode_operations = {
&msdos_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 */
msdos_bmap, /* bmap */
msdos_truncate, /* truncate */
NULL /* permission */
};
/* No bmap for MS-DOS FS' that don't align data at kByte boundaries. */
struct inode_operations msdos_file_inode_operations_no_bmap = {
&msdos_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 */
NULL, /* bmap */
msdos_truncate, /* truncate */
NULL /* permission */
};
static int msdos_file_read(struct inode *inode,struct file *filp,char *buf,
int count)
{
char *start;
int left,offset,size,sector,cnt;
char ch;
struct buffer_head *bh;
void *data;
/* printk("msdos_file_read\n"); */
if (!inode) {
printk("msdos_file_read: inode = NULL\n");
return -EINVAL;
}
if (!S_ISREG(inode->i_mode)) {
printk("msdos_file_read: mode = %07o\n",inode->i_mode);
return -EINVAL;
}
if (filp->f_pos >= inode->i_size || count <= 0) return 0;
start = buf;
while ((left = MIN(inode->i_size-filp->f_pos,count-(buf-start))) > 0){
if (!(sector = msdos_smap(inode,filp->f_pos >> SECTOR_BITS)))
break;
offset = filp->f_pos & (SECTOR_SIZE-1);
if (!(bh = msdos_sread(inode->i_dev,sector,&data))) break;
filp->f_pos += (size = MIN(SECTOR_SIZE-offset,left));
if (MSDOS_I(inode)->i_binary) {
memcpy_tofs(buf,data+offset,size);
buf += size;
}
else for (cnt = size; cnt; cnt--) {
if ((ch = *((char *) data+offset++)) == '\r')
size--;
else {
if (ch != 26) put_fs_byte(ch,buf++);
else {
filp->f_pos = inode->i_size;
brelse(bh);
return buf-start;
}
}
}
brelse(bh);
}
if (start == buf) return -EIO;
return buf-start;
}
static int msdos_file_write(struct inode *inode,struct file *filp,char *buf,
int count)
{
int sector,offset,size,left,written;
int error,carry;
char *start,*to,ch;
struct buffer_head *bh;
void *data;
if (!inode) {
printk("msdos_file_write: inode = NULL\n");
return -EINVAL;
}
if (!S_ISREG(inode->i_mode)) {
printk("msdos_file_write: mode = %07o\n",inode->i_mode);
return -EINVAL;
}
/*
* ok, append may not work when many processes are writing at the same time
* but so what. That way leads to madness anyway.
*/
if (filp->f_flags & O_APPEND) filp->f_pos = inode->i_size;
if (count <= 0) return 0;
error = carry = 0;
for (start = buf; count || carry; count -= size) {
while (!(sector = msdos_smap(inode,filp->f_pos >> SECTOR_BITS)))
if ((error = msdos_add_cluster(inode)) < 0) break;
if (error) {
msdos_truncate(inode);
break;
}
offset = filp->f_pos & (SECTOR_SIZE-1);
size = MIN(SECTOR_SIZE-offset,MAX(carry,count));
if (!(bh = msdos_sread(inode->i_dev,sector,&data))) {
error = -EIO;
break;
}
if (MSDOS_I(inode)->i_binary) {
memcpy_fromfs(data+(filp->f_pos & (SECTOR_SIZE-1)),
buf,written = size);
buf += size;
}
else {
written = left = SECTOR_SIZE-offset;
to = (char *) data+(filp->f_pos & (SECTOR_SIZE-1));
if (carry) {
*to++ = '\n';
left--;
carry = 0;
}
for (size = 0; size < count && left; size++) {
if ((ch = get_fs_byte(buf++)) == '\n') {
*to++ = '\r';
left--;
}
if (!left) carry = 1;
else {
*to++ = ch;
left--;
}
}
written -= left;
}
filp->f_pos += written;
if (filp->f_pos > inode->i_size) {
inode->i_size = filp->f_pos;
inode->i_dirt = 1;
}
bh->b_dirt = 1;
brelse(bh);
}
inode->i_mtime = inode->i_ctime = CURRENT_TIME;
MSDOS_I(inode)->i_attrs |= ATTR_ARCH;
inode->i_dirt = 1;
return start == buf ? error : buf-start;
}
void msdos_truncate(struct inode *inode)
{
int cluster;
cluster = SECTOR_SIZE*MSDOS_SB(inode->i_sb)->cluster_size;
(void) fat_free(inode,(inode->i_size+(cluster-1))/cluster);
MSDOS_I(inode)->i_attrs |= ATTR_ARCH;
inode->i_dirt = 1;
}