blob: 4657734e4355e806fa1c973103f3291733dbb886 [file] [log] [blame]
/*
* linux/fs/read_write.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
*/
#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/fcntl.h>
#include <linux/file.h>
#include <linux/uio.h>
#include <linux/smp_lock.h>
#include <linux/dnotify.h>
#include <asm/uaccess.h>
struct file_operations generic_ro_fops = {
llseek: generic_file_llseek,
read: generic_file_read,
mmap: generic_file_mmap,
};
loff_t generic_file_llseek(struct file *file, loff_t offset, int origin)
{
long long retval;
struct inode *inode = file->f_dentry->d_inode->i_mapping->host;
down(&inode->i_sem);
switch (origin) {
case 2:
offset += inode->i_size;
break;
case 1:
offset += file->f_pos;
}
retval = -EINVAL;
if (offset>=0 && offset<=inode->i_sb->s_maxbytes) {
if (offset != file->f_pos) {
file->f_pos = offset;
file->f_version = ++event;
}
retval = offset;
}
up(&inode->i_sem);
return retval;
}
loff_t remote_llseek(struct file *file, loff_t offset, int origin)
{
long long retval;
lock_kernel();
switch (origin) {
case 2:
offset += file->f_dentry->d_inode->i_size;
break;
case 1:
offset += file->f_pos;
}
retval = -EINVAL;
if (offset>=0 && offset<=file->f_dentry->d_inode->i_sb->s_maxbytes) {
if (offset != file->f_pos) {
file->f_pos = offset;
file->f_version = ++event;
}
retval = offset;
}
unlock_kernel();
return retval;
}
loff_t no_llseek(struct file *file, loff_t offset, int origin)
{
return -ESPIPE;
}
loff_t default_llseek(struct file *file, loff_t offset, int origin)
{
long long retval;
lock_kernel();
switch (origin) {
case 2:
offset += file->f_dentry->d_inode->i_size;
break;
case 1:
offset += file->f_pos;
}
retval = -EINVAL;
if (offset >= 0) {
if (offset != file->f_pos) {
file->f_pos = offset;
file->f_version = ++event;
}
retval = offset;
}
unlock_kernel();
return retval;
}
static inline loff_t llseek(struct file *file, loff_t offset, int origin)
{
loff_t (*fn)(struct file *, loff_t, int);
fn = default_llseek;
if (file->f_op && file->f_op->llseek)
fn = file->f_op->llseek;
return fn(file, offset, origin);
}
asmlinkage off_t sys_lseek(unsigned int fd, off_t offset, unsigned int origin)
{
off_t retval;
struct file * file;
retval = -EBADF;
file = fget(fd);
if (!file)
goto bad;
retval = -EINVAL;
if (origin <= 2) {
loff_t res = llseek(file, offset, origin);
retval = res;
if (res != (loff_t)retval)
retval = -EOVERFLOW; /* LFS: should only happen on 32 bit platforms */
}
fput(file);
bad:
return retval;
}
#if !defined(__alpha__)
asmlinkage long sys_llseek(unsigned int fd, unsigned long offset_high,
unsigned long offset_low, loff_t * result,
unsigned int origin)
{
int retval;
struct file * file;
loff_t offset;
retval = -EBADF;
file = fget(fd);
if (!file)
goto bad;
retval = -EINVAL;
if (origin > 2)
goto out_putf;
offset = llseek(file, ((loff_t) offset_high << 32) | offset_low,
origin);
retval = (int)offset;
if (offset >= 0) {
retval = -EFAULT;
if (!copy_to_user(result, &offset, sizeof(offset)))
retval = 0;
}
out_putf:
fput(file);
bad:
return retval;
}
#endif
ssize_t vfs_read(struct file *file, char *buf, size_t count, loff_t *pos)
{
struct inode *inode = file->f_dentry->d_inode;
ssize_t ret;
if (!(file->f_mode & FMODE_READ))
return -EBADF;
if (!file->f_op || !file->f_op->read)
return -EINVAL;
if (pos < 0)
return -EINVAL;
ret = locks_verify_area(FLOCK_VERIFY_READ, inode, file, *pos, count);
if (!ret) {
ret = file->f_op->read(file, buf, count, pos);
if (ret > 0)
dnotify_parent(file->f_dentry, DN_ACCESS);
}
return ret;
}
ssize_t vfs_write(struct file *file, const char *buf, size_t count, loff_t *pos)
{
struct inode *inode = file->f_dentry->d_inode;
ssize_t ret;
if (!(file->f_mode & FMODE_WRITE))
return -EBADF;
if (!file->f_op || !file->f_op->write)
return -EINVAL;
if (pos < 0)
return -EINVAL;
ret = locks_verify_area(FLOCK_VERIFY_WRITE, inode, file, *pos, count);
if (!ret) {
ret = file->f_op->write(file, buf, count, pos);
if (ret > 0)
dnotify_parent(file->f_dentry, DN_MODIFY);
}
return ret;
}
asmlinkage ssize_t sys_read(unsigned int fd, char * buf, size_t count)
{
struct file *file;
ssize_t ret = -EBADF;
file = fget(fd);
if (file) {
ret = vfs_read(file, buf, count, &file->f_pos);
fput(file);
}
return ret;
}
asmlinkage ssize_t sys_write(unsigned int fd, const char * buf, size_t count)
{
struct file *file;
ssize_t ret = -EBADF;
file = fget(fd);
if (file) {
ret = vfs_write(file, buf, count, &file->f_pos);
fput(file);
}
return ret;
}
asmlinkage ssize_t sys_pread(unsigned int fd, char *buf,
size_t count, loff_t pos)
{
struct file *file;
ssize_t ret = -EBADF;
file = fget(fd);
if (file) {
ret = vfs_read(file, buf, count, &pos);
fput(file);
}
return ret;
}
asmlinkage ssize_t sys_pwrite(unsigned int fd, const char *buf,
size_t count, loff_t pos)
{
struct file *file;
ssize_t ret = -EBADF;
file = fget(fd);
if (file) {
ret = vfs_write(file, buf, count, &pos);
fput(file);
}
return ret;
}
static ssize_t do_readv_writev(int type, struct file *file,
const struct iovec * vector,
unsigned long count)
{
typedef ssize_t (*io_fn_t)(struct file *, char *, size_t, loff_t *);
typedef ssize_t (*iov_fn_t)(struct file *, const struct iovec *, unsigned long, loff_t *);
size_t tot_len;
struct iovec iovstack[UIO_FASTIOV];
struct iovec *iov=iovstack;
ssize_t ret, i;
io_fn_t fn;
iov_fn_t fnv;
struct inode *inode;
/*
* First get the "struct iovec" from user memory and
* verify all the pointers
*/
ret = 0;
if (!count)
goto out_nofree;
ret = -EINVAL;
if (count > UIO_MAXIOV)
goto out_nofree;
if (!file->f_op)
goto out_nofree;
if (count > UIO_FASTIOV) {
ret = -ENOMEM;
iov = kmalloc(count*sizeof(struct iovec), GFP_KERNEL);
if (!iov)
goto out_nofree;
}
ret = -EFAULT;
if (copy_from_user(iov, vector, count*sizeof(*vector)))
goto out;
/* BSD readv/writev returns EINVAL if one of the iov_len
values < 0 or tot_len overflowed a 32-bit integer. -ink */
tot_len = 0;
ret = -EINVAL;
for (i = 0 ; i < count ; i++) {
size_t tmp = tot_len;
int len = iov[i].iov_len;
if (len < 0)
goto out;
(u32)tot_len += len;
if (tot_len < tmp || tot_len < (u32)len)
goto out;
}
inode = file->f_dentry->d_inode;
/* VERIFY_WRITE actually means a read, as we write to user space */
ret = locks_verify_area((type == VERIFY_WRITE
? FLOCK_VERIFY_READ : FLOCK_VERIFY_WRITE),
inode, file, file->f_pos, tot_len);
if (ret) goto out;
fnv = (type == VERIFY_WRITE ? file->f_op->readv : file->f_op->writev);
if (fnv) {
ret = fnv(file, iov, count, &file->f_pos);
goto out;
}
/* VERIFY_WRITE actually means a read, as we write to user space */
fn = (type == VERIFY_WRITE ? file->f_op->read :
(io_fn_t) file->f_op->write);
ret = 0;
vector = iov;
while (count > 0) {
void * base;
size_t len;
ssize_t nr;
base = vector->iov_base;
len = vector->iov_len;
vector++;
count--;
nr = fn(file, base, len, &file->f_pos);
if (nr < 0) {
if (!ret) ret = nr;
break;
}
ret += nr;
if (nr != len)
break;
}
out:
if (iov != iovstack)
kfree(iov);
out_nofree:
/* VERIFY_WRITE actually means a read, as we write to user space */
if ((ret + (type == VERIFY_WRITE)) > 0)
dnotify_parent(file->f_dentry,
(type == VERIFY_WRITE) ? DN_MODIFY : DN_ACCESS);
return ret;
}
asmlinkage ssize_t sys_readv(unsigned long fd, const struct iovec * vector,
unsigned long count)
{
struct file * file;
ssize_t ret;
ret = -EBADF;
file = fget(fd);
if (!file)
goto bad_file;
if (file->f_op && (file->f_mode & FMODE_READ) &&
(file->f_op->readv || file->f_op->read))
ret = do_readv_writev(VERIFY_WRITE, file, vector, count);
fput(file);
bad_file:
return ret;
}
asmlinkage ssize_t sys_writev(unsigned long fd, const struct iovec * vector,
unsigned long count)
{
struct file * file;
ssize_t ret;
ret = -EBADF;
file = fget(fd);
if (!file)
goto bad_file;
if (file->f_op && (file->f_mode & FMODE_WRITE) &&
(file->f_op->writev || file->f_op->write))
ret = do_readv_writev(VERIFY_READ, file, vector, count);
fput(file);
bad_file:
return ret;
}