/*
       *  linux/fs/read_write.c
       *
       *  Copyright (C) 1991, 1992  Linus Torvalds
       */
      
      #include <linux/malloc.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 = {
      	read:		generic_file_read,
      	mmap:		generic_file_mmap,
      };
      
  22  ssize_t generic_read_dir(struct file *filp, char *buf, size_t siz, loff_t *ppos)
      {
  24  	return -EISDIR;
      }
      
  27  loff_t default_llseek(struct file *file, loff_t offset, int origin)
      {
      	long long retval;
      
  31  	switch (origin) {
  32  		case 2:
      			offset += file->f_dentry->d_inode->i_size;
  34  			break;
  35  		case 1:
      			offset += file->f_pos;
      	}
      	retval = -EINVAL;
  39  	if (offset >= 0) {
  40  		if (offset != file->f_pos) {
      			file->f_pos = offset;
      			file->f_reada = 0;
      			file->f_version = ++event;
      		}
      		retval = offset;
      	}
  47  	return retval;
      }
      
  50  static inline loff_t llseek(struct file *file, loff_t offset, int origin)
      {
      	loff_t (*fn)(struct file *, loff_t, int);
      	loff_t retval;
      
      	fn = default_llseek;
  56  	if (file->f_op && file->f_op->llseek)
      		fn = file->f_op->llseek;
  58  	lock_kernel();
      	retval = fn(file, offset, origin);
  60  	unlock_kernel();
  61  	return retval;
      }
      
  64  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);
  71  	if (!file)
  72  		goto bad;
      	retval = -EINVAL;
  74  	if (origin <= 2) {
      		loff_t res = llseek(file, offset, origin);
      		retval = res;
  77  		if (res != (loff_t)retval)
      			retval = -EOVERFLOW;	/* LFS: should only happen on 32 bit platforms */
      	}
      	fput(file);
      bad:
  82  	return retval;
      }
      
      #if !defined(__alpha__)
  86  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);
  96  	if (!file)
  97  		goto bad;
      	retval = -EINVAL;
  99  	if (origin > 2)
 100  		goto out_putf;
      
      	offset = llseek(file, ((loff_t) offset_high << 32) | offset_low,
      			origin);
      
      	retval = (int)offset;
 106  	if (offset >= 0) {
      		retval = -EFAULT;
 108  		if (!copy_to_user(result, &offset, sizeof(offset)))
      			retval = 0;
      	}
      out_putf:
      	fput(file);
      bad:
 114  	return retval;
      }
      #endif
      
 118  asmlinkage ssize_t sys_read(unsigned int fd, char * buf, size_t count)
      {
      	ssize_t ret;
      	struct file * file;
      
      	ret = -EBADF;
      	file = fget(fd);
 125  	if (file) {
 126  		if (file->f_mode & FMODE_READ) {
      			ret = locks_verify_area(FLOCK_VERIFY_READ, file->f_dentry->d_inode,
      						file, file->f_pos, count);
 129  			if (!ret) {
      				ssize_t (*read)(struct file *, char *, size_t, loff_t *);
      				ret = -EINVAL;
 132  				if (file->f_op && (read = file->f_op->read) != NULL)
      					ret = read(file, buf, count, &file->f_pos);
      			}
      		}
 136  		if (ret > 0)
      			inode_dir_notify(file->f_dentry->d_parent->d_inode,
      				DN_ACCESS);
      		fput(file);
      	}
 141  	return ret;
      }
      
 144  asmlinkage ssize_t sys_write(unsigned int fd, const char * buf, size_t count)
      {
      	ssize_t ret;
      	struct file * file;
      
      	ret = -EBADF;
      	file = fget(fd);
 151  	if (file) {
 152  		if (file->f_mode & FMODE_WRITE) {
      			struct inode *inode = file->f_dentry->d_inode;
      			ret = locks_verify_area(FLOCK_VERIFY_WRITE, inode, file,
      				file->f_pos, count);
 156  			if (!ret) {
      				ssize_t (*write)(struct file *, const char *, size_t, loff_t *);
      				ret = -EINVAL;
 159  				if (file->f_op && (write = file->f_op->write) != NULL)
      					ret = write(file, buf, count, &file->f_pos);
      			}
      		}
 163  		if (ret > 0)
      			inode_dir_notify(file->f_dentry->d_parent->d_inode,
      				DN_MODIFY);
      		fput(file);
      	}
 168  	return ret;
      }
      
      
 172  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;
 192  	if (!count)
 193  		goto out_nofree;
      	ret = -EINVAL;
 195  	if (count > UIO_MAXIOV)
 196  		goto out_nofree;
 197  	if (!file->f_op)
 198  		goto out_nofree;
 199  	if (count > UIO_FASTIOV) {
      		ret = -ENOMEM;
      		iov = kmalloc(count*sizeof(struct iovec), GFP_KERNEL);
 202  		if (!iov)
 203  			goto out_nofree;
      	}
      	ret = -EFAULT;
 206  	if (copy_from_user(iov, vector, count*sizeof(*vector)))
 207  		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;
 213  	for (i = 0 ; i < count ; i++) {
      		size_t tmp = tot_len;
      		int len = iov[i].iov_len;
 216  		if (len < 0)
 217  			goto out;
      		(u32)tot_len += len;
 219  		if (tot_len < tmp || tot_len < (u32)len)
 220  			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);
 228  	if (ret) goto out;
      
      	fnv = (type == VERIFY_WRITE ? file->f_op->readv : file->f_op->writev);
 231  	if (fnv) {
      		ret = fnv(file, iov, count, &file->f_pos);
 233  		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;
 242  	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);
      
 254  		if (nr < 0) {
 255  			if (!ret) ret = nr;
 256  			break;
      		}
      		ret += nr;
 259  		if (nr != len)
 260  			break;
      	}
      
      out:
 264  	if (iov != iovstack)
      		kfree(iov);
      out_nofree:
      	/* VERIFY_WRITE actually means a read, as we write to user space */
 268  	if ((ret + (type == VERIFY_WRITE)) > 0)
      		inode_dir_notify(file->f_dentry->d_parent->d_inode,
      			(type == VERIFY_WRITE) ? DN_MODIFY : DN_ACCESS);
 271  	return ret;
      }
      
 274  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);
 283  	if (!file)
 284  		goto bad_file;
      	if (file->f_op && (file->f_mode & FMODE_READ) &&
 286  	    (file->f_op->readv || file->f_op->read))
      		ret = do_readv_writev(VERIFY_WRITE, file, vector, count);
      	fput(file);
      
      bad_file:
 291  	return ret;
      }
      
 294  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);
 303  	if (!file)
 304  		goto bad_file;
      	if (file->f_op && (file->f_mode & FMODE_WRITE) &&
 306  	    (file->f_op->writev || file->f_op->write))
      		ret = do_readv_writev(VERIFY_READ, file, vector, count);
      	fput(file);
      
      bad_file:
 311  	return ret;
      }
      
      /* From the Single Unix Spec: pread & pwrite act like lseek to pos + op +
         lseek back to original location.  They fail just like lseek does on
         non-seekable files.  */
      
 318  asmlinkage ssize_t sys_pread(unsigned int fd, char * buf,
      			     size_t count, loff_t pos)
      {
      	ssize_t ret;
      	struct file * file;
      	ssize_t (*read)(struct file *, char *, size_t, loff_t *);
      
      	ret = -EBADF;
      	file = fget(fd);
 327  	if (!file)
 328  		goto bad_file;
 329  	if (!(file->f_mode & FMODE_READ))
 330  		goto out;
      	ret = locks_verify_area(FLOCK_VERIFY_READ, file->f_dentry->d_inode,
      				file, pos, count);
 333  	if (ret)
 334  		goto out;
      	ret = -EINVAL;
 336  	if (!file->f_op || !(read = file->f_op->read))
 337  		goto out;
 338  	if (pos < 0)
 339  		goto out;
      	ret = read(file, buf, count, &pos);
 341  	if (ret > 0)
      		inode_dir_notify(file->f_dentry->d_parent->d_inode, DN_ACCESS);
      out:
      	fput(file);
      bad_file:
 346  	return ret;
      }
      
 349  asmlinkage ssize_t sys_pwrite(unsigned int fd, const char * buf,
      			      size_t count, loff_t pos)
      {
      	ssize_t ret;
      	struct file * file;
      	ssize_t (*write)(struct file *, const char *, size_t, loff_t *);
      
      	ret = -EBADF;
      	file = fget(fd);
 358  	if (!file)
 359  		goto bad_file;
 360  	if (!(file->f_mode & FMODE_WRITE))
 361  		goto out;
      	ret = locks_verify_area(FLOCK_VERIFY_WRITE, file->f_dentry->d_inode,
      				file, pos, count);
 364  	if (ret)
 365  		goto out;
      	ret = -EINVAL;
 367  	if (!file->f_op || !(write = file->f_op->write))
 368  		goto out;
 369  	if (pos < 0)
 370  		goto out;
      
      	ret = write(file, buf, count, &pos);
 373  	if (ret > 0)
      		inode_dir_notify(file->f_dentry->d_parent->d_inode, DN_MODIFY);
      out:
      	fput(file);
      bad_file:
 378  	return ret;
      }