summaryrefslogtreecommitdiffstats
path: root/fs/read_write.c
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>1997-01-07 02:33:00 +0000
committer <ralf@linux-mips.org>1997-01-07 02:33:00 +0000
commitbeb116954b9b7f3bb56412b2494b562f02b864b1 (patch)
tree120e997879884e1b9d93b265221b939d2ef1ade1 /fs/read_write.c
parent908d4681a1dc3792ecafbe64265783a86c4cccb6 (diff)
Import of Linux/MIPS 2.1.14
Diffstat (limited to 'fs/read_write.c')
-rw-r--r--fs/read_write.c324
1 files changed, 236 insertions, 88 deletions
diff --git a/fs/read_write.c b/fs/read_write.c
index 1cd19d57a..6c28b8f59 100644
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -9,133 +9,281 @@
#include <linux/stat.h>
#include <linux/kernel.h>
#include <linux/sched.h>
+#include <linux/fcntl.h>
+#include <linux/file.h>
#include <linux/mm.h>
+#include <linux/uio.h>
-#include <asm/segment.h>
+#include <asm/uaccess.h>
-asmlinkage int sys_lseek(unsigned int fd, off_t offset, unsigned int origin)
+static long long default_llseek(struct inode *inode,
+ struct file *file,
+ long long offset,
+ int origin)
{
- struct file * file;
- int tmp = -1;
-
- if (fd >= NR_OPEN || !(file=current->files->fd[fd]) || !(file->f_inode))
- return -EBADF;
- if (origin > 2)
- return -EINVAL;
- if (file->f_op && file->f_op->lseek)
- return file->f_op->lseek(file->f_inode,file,offset,origin);
+ long long retval;
-/* this is the default handler if no lseek handler is present */
switch (origin) {
- case 0:
- tmp = offset;
- break;
- case 1:
- tmp = file->f_pos + offset;
- break;
case 2:
- if (!file->f_inode)
- return -EINVAL;
- tmp = file->f_inode->i_size + offset;
+ offset += inode->i_size;
break;
+ case 1:
+ offset += file->f_pos;
}
- if (tmp < 0)
- return -EINVAL;
- if (tmp != file->f_pos) {
- file->f_pos = tmp;
- file->f_reada = 0;
- file->f_version = ++event;
+ retval = -EINVAL;
+ if (offset >= 0) {
+ if (offset != file->f_pos) {
+ file->f_pos = offset;
+ file->f_reada = 0;
+ file->f_version = ++event;
+ }
+ retval = offset;
}
- return file->f_pos;
+ return retval;
+}
+
+static inline long long llseek(struct inode * inode, struct file *file,
+ long long offset, unsigned int origin)
+{
+ long long (*fn)(struct inode *, struct file *, long long, int);
+
+ fn = default_llseek;
+ if (file->f_op && file->f_op->llseek)
+ fn = file->f_op->llseek;
+ return fn(inode, file, offset, origin);
+}
+
+asmlinkage long sys_lseek(unsigned int fd, off_t offset, unsigned int origin)
+{
+ long retval;
+ struct file * file;
+ struct inode * inode;
+
+ retval = -EBADF;
+ if (fd >= NR_OPEN ||
+ !(file = current->files->fd[fd]) ||
+ !(inode = file->f_inode))
+ goto bad;
+ retval = -EINVAL;
+ if (origin > 2)
+ goto bad;
+ retval = llseek(inode, file, offset, origin);
+bad:
+ return retval;
}
asmlinkage int sys_llseek(unsigned int fd, unsigned long offset_high,
unsigned long offset_low, loff_t * result,
unsigned int origin)
{
+ long retval;
struct file * file;
- loff_t tmp = -1;
- loff_t offset;
- int err;
+ struct inode * inode;
+ long long offset;
- if (fd >= NR_OPEN || !(file=current->files->fd[fd]) || !(file->f_inode))
- return -EBADF;
+ retval = -EBADF;
+ if (fd >= NR_OPEN ||
+ !(file = current->files->fd[fd]) ||
+ !(inode = file->f_inode))
+ goto bad;
+ retval = -EINVAL;
if (origin > 2)
- return -EINVAL;
- if ((err = verify_area(VERIFY_WRITE, result, sizeof(loff_t))))
- return err;
- offset = (loff_t) (((unsigned long long) offset_high << 32) | offset_low);
-/* there is no fs specific llseek handler */
- switch (origin) {
- case 0:
- tmp = offset;
- break;
- case 1:
- tmp = file->f_pos + offset;
- break;
- case 2:
- if (!file->f_inode)
- return -EINVAL;
- tmp = file->f_inode->i_size + offset;
- break;
+ goto bad;
+
+ offset = llseek(inode, file,
+ (((unsigned long long) offset_high << 32) | offset_low),
+ origin);
+
+ retval = offset;
+ if (offset >= 0) {
+ retval = copy_to_user(result, &offset, sizeof(offset));
+ if (retval)
+ retval = -EFAULT;
}
- if (tmp < 0)
- return -EINVAL;
- file->f_pos = tmp;
- file->f_reada = 0;
- file->f_version = ++event;
- memcpy_tofs(result, &file->f_pos, sizeof(loff_t));
- return 0;
+
+bad:
+ return retval;
}
-asmlinkage int sys_read(unsigned int fd,char * buf,unsigned int count)
+asmlinkage long sys_read(unsigned int fd, char * buf, unsigned long count)
{
int error;
struct file * file;
struct inode * inode;
+ long (*read)(struct inode *, struct file *, char *, unsigned long);
- if (fd>=NR_OPEN || !(file=current->files->fd[fd]) || !(inode=file->f_inode))
- return -EBADF;
+ error = -EBADF;
+ file = fget(fd);
+ if (!file)
+ goto bad_file;
+ inode = file->f_inode;
+ if (!inode)
+ goto out;
+ error = -EBADF;
if (!(file->f_mode & 1))
- return -EBADF;
- if (!file->f_op || !file->f_op->read)
- return -EINVAL;
- if (!count)
- return 0;
- error = verify_area(VERIFY_WRITE,buf,count);
+ goto out;
+ error = locks_verify_area(FLOCK_VERIFY_READ,inode,file,file->f_pos,count);
if (error)
- return error;
- return file->f_op->read(inode,file,buf,count);
+ goto out;
+ error = -EINVAL;
+ if (!file->f_op || !(read = file->f_op->read))
+ goto out;
+ error = read(inode,file,buf,count);
+out:
+ fput(file, inode);
+bad_file:
+ return error;
}
-asmlinkage int sys_write(unsigned int fd,char * buf,unsigned int count)
+asmlinkage long sys_write(unsigned int fd, const char * buf, unsigned long count)
{
int error;
struct file * file;
struct inode * inode;
- int written;
-
- if (fd>=NR_OPEN || !(file=current->files->fd[fd]) || !(inode=file->f_inode))
- return -EBADF;
+ long (*write)(struct inode *, struct file *, const char *, unsigned long);
+
+ error = -EBADF;
+ file = fget(fd);
+ if (!file)
+ goto bad_file;
+ inode = file->f_inode;
+ if (!inode)
+ goto out;
if (!(file->f_mode & 2))
- return -EBADF;
- if (!file->f_op || !file->f_op->write)
- return -EINVAL;
+ goto out;
+ error = locks_verify_area(FLOCK_VERIFY_WRITE,inode,file,file->f_pos,count);
+ if (error)
+ goto out;
+ error = -EINVAL;
+ if (!file->f_op || !(write = file->f_op->write))
+ goto out;
+ down(&inode->i_sem);
+ error = write(inode,file,buf,count);
+ up(&inode->i_sem);
+out:
+ fput(file, inode);
+bad_file:
+ return error;
+}
+
+static long sock_readv_writev(int type, struct inode * inode, struct file * file,
+ const struct iovec * iov, long count, long size)
+{
+ struct msghdr msg;
+ struct socket *sock;
+
+ sock = &inode->u.socket_i;
+ if (!sock->ops)
+ return -EOPNOTSUPP;
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_control = NULL;
+ msg.msg_iov = (struct iovec *) iov;
+ msg.msg_iovlen = count;
+
+ /* read() does a VERIFY_WRITE */
+ if (type == VERIFY_WRITE) {
+ if (!sock->ops->recvmsg)
+ return -EOPNOTSUPP;
+ return sock->ops->recvmsg(sock, &msg, size,
+ (file->f_flags & O_NONBLOCK), 0, NULL);
+ }
+ if (!sock->ops->sendmsg)
+ return -EOPNOTSUPP;
+ return sock->ops->sendmsg(sock, &msg, size,
+ (file->f_flags & O_NONBLOCK), 0);
+}
+
+typedef long (*IO_fn_t)(struct inode *, struct file *, char *, unsigned long);
+
+static long do_readv_writev(int type, struct inode * inode, struct file * file,
+ const struct iovec * vector, unsigned long count)
+{
+ unsigned long tot_len;
+ struct iovec iov[UIO_MAXIOV];
+ long retval, i;
+ IO_fn_t fn;
+
+ /*
+ * First get the "struct iovec" from user memory and
+ * verify all the pointers
+ */
if (!count)
return 0;
- error = verify_area(VERIFY_READ,buf,count);
- if (error)
- return error;
- written = file->f_op->write(inode,file,buf,count);
+ if (count > UIO_MAXIOV)
+ return -EINVAL;
+ if (copy_from_user(iov, vector, count*sizeof(*vector)))
+ return -EFAULT;
+ tot_len = 0;
+ for (i = 0 ; i < count ; i++)
+ tot_len += iov[i].iov_len;
+
+ retval = locks_verify_area(type == VERIFY_READ ? FLOCK_VERIFY_READ : FLOCK_VERIFY_WRITE,
+ inode, file, file->f_pos, tot_len);
+ if (retval)
+ return retval;
+
/*
- * If data has been written to the file, remove the setuid and
- * the setgid bits
+ * Then do the actual IO. Note that sockets need to be handled
+ * specially as they have atomicity guarantees and can handle
+ * iovec's natively
*/
- if (written > 0 && !suser() && (inode->i_mode & (S_ISUID | S_ISGID))) {
- struct iattr newattrs;
- newattrs.ia_mode = inode->i_mode & ~(S_ISUID | S_ISGID);
- newattrs.ia_valid = ATTR_MODE;
- notify_change(inode, &newattrs);
+ if (inode->i_sock)
+ return sock_readv_writev(type, inode, file, iov, count, tot_len);
+
+ if (!file->f_op)
+ return -EINVAL;
+ /* VERIFY_WRITE actually means a read, as we write to user space */
+ fn = file->f_op->read;
+ if (type == VERIFY_READ)
+ fn = (IO_fn_t) file->f_op->write;
+ vector = iov;
+ while (count > 0) {
+ void * base;
+ int len, nr;
+
+ base = vector->iov_base;
+ len = vector->iov_len;
+ vector++;
+ count--;
+ nr = fn(inode, file, base, len);
+ if (nr < 0) {
+ if (retval)
+ break;
+ retval = nr;
+ break;
+ }
+ retval += nr;
+ if (nr != len)
+ break;
}
- return written;
+ return retval;
+}
+
+asmlinkage long sys_readv(unsigned long fd, const struct iovec * vector, unsigned long count)
+{
+ struct file * file;
+ struct inode * inode;
+
+ if (fd >= NR_OPEN || !(file = current->files->fd[fd]) || !(inode = file->f_inode))
+ return -EBADF;
+ if (!(file->f_mode & 1))
+ return -EBADF;
+ return do_readv_writev(VERIFY_WRITE, inode, file, vector, count);
+}
+
+asmlinkage long sys_writev(unsigned long fd, const struct iovec * vector, unsigned long count)
+{
+ int error;
+ struct file * file;
+ struct inode * inode;
+
+ if (fd >= NR_OPEN || !(file = current->files->fd[fd]) || !(inode = file->f_inode))
+ return -EBADF;
+ if (!(file->f_mode & 2))
+ return -EBADF;
+ down(&inode->i_sem);
+ error = do_readv_writev(VERIFY_READ, inode, file, vector, count);
+ up(&inode->i_sem);
+ return error;
}