2005-04-17 00:20:36 +02:00
|
|
|
/*
|
|
|
|
* 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>
|
[PATCH] inotify
inotify is intended to correct the deficiencies of dnotify, particularly
its inability to scale and its terrible user interface:
* dnotify requires the opening of one fd per each directory
that you intend to watch. This quickly results in too many
open files and pins removable media, preventing unmount.
* dnotify is directory-based. You only learn about changes to
directories. Sure, a change to a file in a directory affects
the directory, but you are then forced to keep a cache of
stat structures.
* dnotify's interface to user-space is awful. Signals?
inotify provides a more usable, simple, powerful solution to file change
notification:
* inotify's interface is a system call that returns a fd, not SIGIO.
You get a single fd, which is select()-able.
* inotify has an event that says "the filesystem that the item
you were watching is on was unmounted."
* inotify can watch directories or files.
Inotify is currently used by Beagle (a desktop search infrastructure),
Gamin (a FAM replacement), and other projects.
See Documentation/filesystems/inotify.txt.
Signed-off-by: Robert Love <rml@novell.com>
Cc: John McCutchan <ttb@tentacle.dhs.org>
Cc: Christoph Hellwig <hch@lst.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-07-12 23:06:03 +02:00
|
|
|
#include <linux/fsnotify.h>
|
2005-04-17 00:20:36 +02:00
|
|
|
#include <linux/security.h>
|
2011-11-17 05:57:37 +01:00
|
|
|
#include <linux/export.h>
|
2005-04-17 00:20:36 +02:00
|
|
|
#include <linux/syscalls.h>
|
2006-01-05 01:20:40 +01:00
|
|
|
#include <linux/pagemap.h>
|
2007-06-04 09:59:47 +02:00
|
|
|
#include <linux/splice.h>
|
2013-02-24 16:52:26 +01:00
|
|
|
#include <linux/compat.h>
|
2015-11-10 22:53:30 +01:00
|
|
|
#include <linux/mount.h>
|
2016-02-16 22:20:59 +01:00
|
|
|
#include <linux/fs.h>
|
2013-03-20 18:19:30 +01:00
|
|
|
#include "internal.h"
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
#include <asm/uaccess.h>
|
|
|
|
#include <asm/unistd.h>
|
|
|
|
|
2013-05-04 21:00:54 +02:00
|
|
|
typedef ssize_t (*io_fn_t)(struct file *, char __user *, size_t, loff_t *);
|
2014-02-12 00:37:41 +01:00
|
|
|
typedef ssize_t (*iter_fn_t)(struct kiocb *, struct iov_iter *);
|
2013-05-04 21:00:54 +02:00
|
|
|
|
2006-03-28 11:56:42 +02:00
|
|
|
const struct file_operations generic_ro_fops = {
|
2005-04-17 00:20:36 +02:00
|
|
|
.llseek = generic_file_llseek,
|
2014-04-02 20:33:16 +02:00
|
|
|
.read_iter = generic_file_read_iter,
|
2005-04-17 00:20:36 +02:00
|
|
|
.mmap = generic_file_readonly_mmap,
|
2007-06-01 14:52:37 +02:00
|
|
|
.splice_read = generic_file_splice_read,
|
2005-04-17 00:20:36 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
EXPORT_SYMBOL(generic_ro_fops);
|
|
|
|
|
2010-12-17 13:44:05 +01:00
|
|
|
static inline int unsigned_offsets(struct file *file)
|
2010-10-01 23:20:22 +02:00
|
|
|
{
|
2010-12-17 13:44:05 +01:00
|
|
|
return file->f_mode & FMODE_UNSIGNED_OFFSET;
|
2010-10-01 23:20:22 +02:00
|
|
|
}
|
|
|
|
|
2013-06-25 06:02:13 +02:00
|
|
|
/**
|
|
|
|
* vfs_setpos - update the file offset for lseek
|
|
|
|
* @file: file structure in question
|
|
|
|
* @offset: file offset to seek to
|
|
|
|
* @maxsize: maximum file size
|
|
|
|
*
|
|
|
|
* This is a low-level filesystem helper for updating the file offset to
|
|
|
|
* the value specified by @offset if the given offset is valid and it is
|
|
|
|
* not equal to the current file offset.
|
|
|
|
*
|
|
|
|
* Return the specified offset on success and -EINVAL on invalid offset.
|
|
|
|
*/
|
|
|
|
loff_t vfs_setpos(struct file *file, loff_t offset, loff_t maxsize)
|
2011-09-16 01:06:48 +02:00
|
|
|
{
|
|
|
|
if (offset < 0 && !unsigned_offsets(file))
|
|
|
|
return -EINVAL;
|
|
|
|
if (offset > maxsize)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (offset != file->f_pos) {
|
|
|
|
file->f_pos = offset;
|
|
|
|
file->f_version = 0;
|
|
|
|
}
|
|
|
|
return offset;
|
|
|
|
}
|
2013-06-25 06:02:13 +02:00
|
|
|
EXPORT_SYMBOL(vfs_setpos);
|
2011-09-16 01:06:48 +02:00
|
|
|
|
2008-08-11 15:37:17 +02:00
|
|
|
/**
|
2011-09-16 01:06:50 +02:00
|
|
|
* generic_file_llseek_size - generic llseek implementation for regular files
|
2008-08-11 15:37:17 +02:00
|
|
|
* @file: file structure to seek on
|
|
|
|
* @offset: file offset to seek to
|
2012-12-18 00:59:39 +01:00
|
|
|
* @whence: type of seek
|
vfs: allow custom EOF in generic_file_llseek code
For ext3/4 htree directories, using the vfs llseek function with
SEEK_END goes to i_size like for any other file, but in reality
we want the maximum possible hash value. Recent changes
in ext4 have cut & pasted generic_file_llseek() back into fs/ext4/dir.c,
but replicating this core code seems like a bad idea, especially
since the copy has already diverged from the vfs.
This patch updates generic_file_llseek_size to accept
both a custom maximum offset, and a custom EOF position. With this
in place, ext4_dir_llseek can pass in the appropriate maximum hash
position for both maxsize and eof, and get what it wants.
As far as I know, this does not fix any bugs - nfs in the kernel
doesn't use SEEK_END, and I don't know of any user who does. But
some ext4 folks seem keen on doing the right thing here, and I can't
really argue.
(Patch also fixes up some comments slightly)
Signed-off-by: Eric Sandeen <sandeen@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2012-04-30 20:11:29 +02:00
|
|
|
* @size: max size of this file in file system
|
|
|
|
* @eof: offset used for SEEK_END position
|
2008-08-11 15:37:17 +02:00
|
|
|
*
|
2011-09-16 01:06:50 +02:00
|
|
|
* This is a variant of generic_file_llseek that allows passing in a custom
|
vfs: allow custom EOF in generic_file_llseek code
For ext3/4 htree directories, using the vfs llseek function with
SEEK_END goes to i_size like for any other file, but in reality
we want the maximum possible hash value. Recent changes
in ext4 have cut & pasted generic_file_llseek() back into fs/ext4/dir.c,
but replicating this core code seems like a bad idea, especially
since the copy has already diverged from the vfs.
This patch updates generic_file_llseek_size to accept
both a custom maximum offset, and a custom EOF position. With this
in place, ext4_dir_llseek can pass in the appropriate maximum hash
position for both maxsize and eof, and get what it wants.
As far as I know, this does not fix any bugs - nfs in the kernel
doesn't use SEEK_END, and I don't know of any user who does. But
some ext4 folks seem keen on doing the right thing here, and I can't
really argue.
(Patch also fixes up some comments slightly)
Signed-off-by: Eric Sandeen <sandeen@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2012-04-30 20:11:29 +02:00
|
|
|
* maximum file size and a custom EOF position, for e.g. hashed directories
|
2011-09-16 01:06:48 +02:00
|
|
|
*
|
|
|
|
* Synchronization:
|
2011-09-16 01:06:50 +02:00
|
|
|
* SEEK_SET and SEEK_END are unsynchronized (but atomic on 64bit platforms)
|
2011-09-16 01:06:48 +02:00
|
|
|
* SEEK_CUR is synchronized against other SEEK_CURs, but not read/writes.
|
|
|
|
* read/writes behave like SEEK_SET against seeks.
|
2008-08-11 15:37:17 +02:00
|
|
|
*/
|
2008-06-27 11:05:24 +02:00
|
|
|
loff_t
|
2012-12-18 00:59:39 +01:00
|
|
|
generic_file_llseek_size(struct file *file, loff_t offset, int whence,
|
vfs: allow custom EOF in generic_file_llseek code
For ext3/4 htree directories, using the vfs llseek function with
SEEK_END goes to i_size like for any other file, but in reality
we want the maximum possible hash value. Recent changes
in ext4 have cut & pasted generic_file_llseek() back into fs/ext4/dir.c,
but replicating this core code seems like a bad idea, especially
since the copy has already diverged from the vfs.
This patch updates generic_file_llseek_size to accept
both a custom maximum offset, and a custom EOF position. With this
in place, ext4_dir_llseek can pass in the appropriate maximum hash
position for both maxsize and eof, and get what it wants.
As far as I know, this does not fix any bugs - nfs in the kernel
doesn't use SEEK_END, and I don't know of any user who does. But
some ext4 folks seem keen on doing the right thing here, and I can't
really argue.
(Patch also fixes up some comments slightly)
Signed-off-by: Eric Sandeen <sandeen@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2012-04-30 20:11:29 +02:00
|
|
|
loff_t maxsize, loff_t eof)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2012-12-18 00:59:39 +01:00
|
|
|
switch (whence) {
|
2008-08-11 15:37:17 +02:00
|
|
|
case SEEK_END:
|
vfs: allow custom EOF in generic_file_llseek code
For ext3/4 htree directories, using the vfs llseek function with
SEEK_END goes to i_size like for any other file, but in reality
we want the maximum possible hash value. Recent changes
in ext4 have cut & pasted generic_file_llseek() back into fs/ext4/dir.c,
but replicating this core code seems like a bad idea, especially
since the copy has already diverged from the vfs.
This patch updates generic_file_llseek_size to accept
both a custom maximum offset, and a custom EOF position. With this
in place, ext4_dir_llseek can pass in the appropriate maximum hash
position for both maxsize and eof, and get what it wants.
As far as I know, this does not fix any bugs - nfs in the kernel
doesn't use SEEK_END, and I don't know of any user who does. But
some ext4 folks seem keen on doing the right thing here, and I can't
really argue.
(Patch also fixes up some comments slightly)
Signed-off-by: Eric Sandeen <sandeen@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2012-04-30 20:11:29 +02:00
|
|
|
offset += eof;
|
2008-08-11 15:37:17 +02:00
|
|
|
break;
|
|
|
|
case SEEK_CUR:
|
2008-11-11 02:08:08 +01:00
|
|
|
/*
|
|
|
|
* Here we special-case the lseek(fd, 0, SEEK_CUR)
|
|
|
|
* position-querying operation. Avoid rewriting the "same"
|
|
|
|
* f_pos value back to the file because a concurrent read(),
|
|
|
|
* write() or lseek() might have altered it
|
|
|
|
*/
|
|
|
|
if (offset == 0)
|
|
|
|
return file->f_pos;
|
2011-09-16 01:06:48 +02:00
|
|
|
/*
|
|
|
|
* f_lock protects against read/modify/write race with other
|
|
|
|
* SEEK_CURs. Note that parallel writes and reads behave
|
|
|
|
* like SEEK_SET.
|
|
|
|
*/
|
|
|
|
spin_lock(&file->f_lock);
|
2013-06-25 06:02:13 +02:00
|
|
|
offset = vfs_setpos(file, file->f_pos + offset, maxsize);
|
2011-09-16 01:06:48 +02:00
|
|
|
spin_unlock(&file->f_lock);
|
|
|
|
return offset;
|
2011-07-18 19:21:35 +02:00
|
|
|
case SEEK_DATA:
|
|
|
|
/*
|
|
|
|
* In the generic case the entire file is data, so as long as
|
|
|
|
* offset isn't at the end of the file then the offset is data.
|
|
|
|
*/
|
vfs: allow custom EOF in generic_file_llseek code
For ext3/4 htree directories, using the vfs llseek function with
SEEK_END goes to i_size like for any other file, but in reality
we want the maximum possible hash value. Recent changes
in ext4 have cut & pasted generic_file_llseek() back into fs/ext4/dir.c,
but replicating this core code seems like a bad idea, especially
since the copy has already diverged from the vfs.
This patch updates generic_file_llseek_size to accept
both a custom maximum offset, and a custom EOF position. With this
in place, ext4_dir_llseek can pass in the appropriate maximum hash
position for both maxsize and eof, and get what it wants.
As far as I know, this does not fix any bugs - nfs in the kernel
doesn't use SEEK_END, and I don't know of any user who does. But
some ext4 folks seem keen on doing the right thing here, and I can't
really argue.
(Patch also fixes up some comments slightly)
Signed-off-by: Eric Sandeen <sandeen@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2012-04-30 20:11:29 +02:00
|
|
|
if (offset >= eof)
|
2011-07-18 19:21:35 +02:00
|
|
|
return -ENXIO;
|
|
|
|
break;
|
|
|
|
case SEEK_HOLE:
|
|
|
|
/*
|
|
|
|
* There is a virtual hole at the end of the file, so as long as
|
|
|
|
* offset isn't i_size or larger, return i_size.
|
|
|
|
*/
|
vfs: allow custom EOF in generic_file_llseek code
For ext3/4 htree directories, using the vfs llseek function with
SEEK_END goes to i_size like for any other file, but in reality
we want the maximum possible hash value. Recent changes
in ext4 have cut & pasted generic_file_llseek() back into fs/ext4/dir.c,
but replicating this core code seems like a bad idea, especially
since the copy has already diverged from the vfs.
This patch updates generic_file_llseek_size to accept
both a custom maximum offset, and a custom EOF position. With this
in place, ext4_dir_llseek can pass in the appropriate maximum hash
position for both maxsize and eof, and get what it wants.
As far as I know, this does not fix any bugs - nfs in the kernel
doesn't use SEEK_END, and I don't know of any user who does. But
some ext4 folks seem keen on doing the right thing here, and I can't
really argue.
(Patch also fixes up some comments slightly)
Signed-off-by: Eric Sandeen <sandeen@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2012-04-30 20:11:29 +02:00
|
|
|
if (offset >= eof)
|
2011-07-18 19:21:35 +02:00
|
|
|
return -ENXIO;
|
vfs: allow custom EOF in generic_file_llseek code
For ext3/4 htree directories, using the vfs llseek function with
SEEK_END goes to i_size like for any other file, but in reality
we want the maximum possible hash value. Recent changes
in ext4 have cut & pasted generic_file_llseek() back into fs/ext4/dir.c,
but replicating this core code seems like a bad idea, especially
since the copy has already diverged from the vfs.
This patch updates generic_file_llseek_size to accept
both a custom maximum offset, and a custom EOF position. With this
in place, ext4_dir_llseek can pass in the appropriate maximum hash
position for both maxsize and eof, and get what it wants.
As far as I know, this does not fix any bugs - nfs in the kernel
doesn't use SEEK_END, and I don't know of any user who does. But
some ext4 folks seem keen on doing the right thing here, and I can't
really argue.
(Patch also fixes up some comments slightly)
Signed-off-by: Eric Sandeen <sandeen@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2012-04-30 20:11:29 +02:00
|
|
|
offset = eof;
|
2011-07-18 19:21:35 +02:00
|
|
|
break;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
2008-08-11 15:37:17 +02:00
|
|
|
|
2013-06-25 06:02:13 +02:00
|
|
|
return vfs_setpos(file, offset, maxsize);
|
2011-09-16 01:06:50 +02:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(generic_file_llseek_size);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* generic_file_llseek - generic llseek implementation for regular files
|
|
|
|
* @file: file structure to seek on
|
|
|
|
* @offset: file offset to seek to
|
2012-12-18 00:59:39 +01:00
|
|
|
* @whence: type of seek
|
2011-09-16 01:06:50 +02:00
|
|
|
*
|
|
|
|
* This is a generic implemenation of ->llseek useable for all normal local
|
|
|
|
* filesystems. It just updates the file offset to the value specified by
|
2013-04-30 00:06:07 +02:00
|
|
|
* @offset and @whence.
|
2011-09-16 01:06:50 +02:00
|
|
|
*/
|
2012-12-18 00:59:39 +01:00
|
|
|
loff_t generic_file_llseek(struct file *file, loff_t offset, int whence)
|
2011-09-16 01:06:50 +02:00
|
|
|
{
|
|
|
|
struct inode *inode = file->f_mapping->host;
|
|
|
|
|
2012-12-18 00:59:39 +01:00
|
|
|
return generic_file_llseek_size(file, offset, whence,
|
vfs: allow custom EOF in generic_file_llseek code
For ext3/4 htree directories, using the vfs llseek function with
SEEK_END goes to i_size like for any other file, but in reality
we want the maximum possible hash value. Recent changes
in ext4 have cut & pasted generic_file_llseek() back into fs/ext4/dir.c,
but replicating this core code seems like a bad idea, especially
since the copy has already diverged from the vfs.
This patch updates generic_file_llseek_size to accept
both a custom maximum offset, and a custom EOF position. With this
in place, ext4_dir_llseek can pass in the appropriate maximum hash
position for both maxsize and eof, and get what it wants.
As far as I know, this does not fix any bugs - nfs in the kernel
doesn't use SEEK_END, and I don't know of any user who does. But
some ext4 folks seem keen on doing the right thing here, and I can't
really argue.
(Patch also fixes up some comments slightly)
Signed-off-by: Eric Sandeen <sandeen@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2012-04-30 20:11:29 +02:00
|
|
|
inode->i_sb->s_maxbytes,
|
|
|
|
i_size_read(inode));
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
2008-06-27 11:05:24 +02:00
|
|
|
EXPORT_SYMBOL(generic_file_llseek);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2013-06-16 18:27:42 +02:00
|
|
|
/**
|
|
|
|
* fixed_size_llseek - llseek implementation for fixed-sized devices
|
|
|
|
* @file: file structure to seek on
|
|
|
|
* @offset: file offset to seek to
|
|
|
|
* @whence: type of seek
|
|
|
|
* @size: size of the file
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
loff_t fixed_size_llseek(struct file *file, loff_t offset, int whence, loff_t size)
|
|
|
|
{
|
|
|
|
switch (whence) {
|
|
|
|
case SEEK_SET: case SEEK_CUR: case SEEK_END:
|
|
|
|
return generic_file_llseek_size(file, offset, whence,
|
|
|
|
size, size);
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(fixed_size_llseek);
|
|
|
|
|
2015-12-06 04:04:48 +01:00
|
|
|
/**
|
|
|
|
* no_seek_end_llseek - llseek implementation for fixed-sized devices
|
|
|
|
* @file: file structure to seek on
|
|
|
|
* @offset: file offset to seek to
|
|
|
|
* @whence: type of seek
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
loff_t no_seek_end_llseek(struct file *file, loff_t offset, int whence)
|
|
|
|
{
|
|
|
|
switch (whence) {
|
|
|
|
case SEEK_SET: case SEEK_CUR:
|
|
|
|
return generic_file_llseek_size(file, offset, whence,
|
2016-02-16 22:20:59 +01:00
|
|
|
OFFSET_MAX, 0);
|
2015-12-06 04:04:48 +01:00
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(no_seek_end_llseek);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* no_seek_end_llseek_size - llseek implementation for fixed-sized devices
|
|
|
|
* @file: file structure to seek on
|
|
|
|
* @offset: file offset to seek to
|
|
|
|
* @whence: type of seek
|
|
|
|
* @size: maximal offset allowed
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
loff_t no_seek_end_llseek_size(struct file *file, loff_t offset, int whence, loff_t size)
|
|
|
|
{
|
|
|
|
switch (whence) {
|
|
|
|
case SEEK_SET: case SEEK_CUR:
|
|
|
|
return generic_file_llseek_size(file, offset, whence,
|
|
|
|
size, 0);
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(no_seek_end_llseek_size);
|
|
|
|
|
2010-05-26 23:44:48 +02:00
|
|
|
/**
|
|
|
|
* noop_llseek - No Operation Performed llseek implementation
|
|
|
|
* @file: file structure to seek on
|
|
|
|
* @offset: file offset to seek to
|
2012-12-18 00:59:39 +01:00
|
|
|
* @whence: type of seek
|
2010-05-26 23:44:48 +02:00
|
|
|
*
|
|
|
|
* This is an implementation of ->llseek useable for the rare special case when
|
|
|
|
* userspace expects the seek to succeed but the (device) file is actually not
|
|
|
|
* able to perform the seek. In this case you use noop_llseek() instead of
|
|
|
|
* falling back to the default implementation of ->llseek.
|
|
|
|
*/
|
2012-12-18 00:59:39 +01:00
|
|
|
loff_t noop_llseek(struct file *file, loff_t offset, int whence)
|
2010-05-26 23:44:48 +02:00
|
|
|
{
|
|
|
|
return file->f_pos;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(noop_llseek);
|
|
|
|
|
2012-12-18 00:59:39 +01:00
|
|
|
loff_t no_llseek(struct file *file, loff_t offset, int whence)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
return -ESPIPE;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(no_llseek);
|
|
|
|
|
2012-12-18 00:59:39 +01:00
|
|
|
loff_t default_llseek(struct file *file, loff_t offset, int whence)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2013-01-23 23:07:38 +01:00
|
|
|
struct inode *inode = file_inode(file);
|
2008-04-22 15:09:22 +02:00
|
|
|
loff_t retval;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2016-01-22 21:40:57 +01:00
|
|
|
inode_lock(inode);
|
2012-12-18 00:59:39 +01:00
|
|
|
switch (whence) {
|
2007-05-08 09:24:13 +02:00
|
|
|
case SEEK_END:
|
2011-07-18 19:21:35 +02:00
|
|
|
offset += i_size_read(inode);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
2007-05-08 09:24:13 +02:00
|
|
|
case SEEK_CUR:
|
2008-11-11 02:08:08 +01:00
|
|
|
if (offset == 0) {
|
|
|
|
retval = file->f_pos;
|
|
|
|
goto out;
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
offset += file->f_pos;
|
2011-07-18 19:21:35 +02:00
|
|
|
break;
|
|
|
|
case SEEK_DATA:
|
|
|
|
/*
|
|
|
|
* In the generic case the entire file is data, so as
|
|
|
|
* long as offset isn't at the end of the file then the
|
|
|
|
* offset is data.
|
|
|
|
*/
|
2011-07-26 16:25:20 +02:00
|
|
|
if (offset >= inode->i_size) {
|
|
|
|
retval = -ENXIO;
|
|
|
|
goto out;
|
|
|
|
}
|
2011-07-18 19:21:35 +02:00
|
|
|
break;
|
|
|
|
case SEEK_HOLE:
|
|
|
|
/*
|
|
|
|
* There is a virtual hole at the end of the file, so
|
|
|
|
* as long as offset isn't i_size or larger, return
|
|
|
|
* i_size.
|
|
|
|
*/
|
2011-07-26 16:25:20 +02:00
|
|
|
if (offset >= inode->i_size) {
|
|
|
|
retval = -ENXIO;
|
|
|
|
goto out;
|
|
|
|
}
|
2011-07-18 19:21:35 +02:00
|
|
|
offset = inode->i_size;
|
|
|
|
break;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
retval = -EINVAL;
|
2010-12-17 13:44:05 +01:00
|
|
|
if (offset >= 0 || unsigned_offsets(file)) {
|
2005-04-17 00:20:36 +02:00
|
|
|
if (offset != file->f_pos) {
|
|
|
|
file->f_pos = offset;
|
|
|
|
file->f_version = 0;
|
|
|
|
}
|
|
|
|
retval = offset;
|
|
|
|
}
|
2008-11-11 02:08:08 +01:00
|
|
|
out:
|
2016-01-22 21:40:57 +01:00
|
|
|
inode_unlock(inode);
|
2005-04-17 00:20:36 +02:00
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(default_llseek);
|
|
|
|
|
2012-12-18 00:59:39 +01:00
|
|
|
loff_t vfs_llseek(struct file *file, loff_t offset, int whence)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
loff_t (*fn)(struct file *, loff_t, int);
|
|
|
|
|
|
|
|
fn = no_llseek;
|
|
|
|
if (file->f_mode & FMODE_LSEEK) {
|
2013-09-22 22:27:52 +02:00
|
|
|
if (file->f_op->llseek)
|
2005-04-17 00:20:36 +02:00
|
|
|
fn = file->f_op->llseek;
|
|
|
|
}
|
2012-12-18 00:59:39 +01:00
|
|
|
return fn(file, offset, whence);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(vfs_llseek);
|
|
|
|
|
2012-12-18 00:59:39 +01:00
|
|
|
SYSCALL_DEFINE3(lseek, unsigned int, fd, off_t, offset, unsigned int, whence)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
off_t retval;
|
2014-03-03 18:36:58 +01:00
|
|
|
struct fd f = fdget_pos(fd);
|
2012-08-28 18:52:22 +02:00
|
|
|
if (!f.file)
|
|
|
|
return -EBADF;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
retval = -EINVAL;
|
2012-12-18 00:59:39 +01:00
|
|
|
if (whence <= SEEK_MAX) {
|
|
|
|
loff_t res = vfs_llseek(f.file, offset, whence);
|
2005-04-17 00:20:36 +02:00
|
|
|
retval = res;
|
|
|
|
if (res != (loff_t)retval)
|
|
|
|
retval = -EOVERFLOW; /* LFS: should only happen on 32 bit platforms */
|
|
|
|
}
|
2014-03-03 18:36:58 +01:00
|
|
|
fdput_pos(f);
|
2005-04-17 00:20:36 +02:00
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2013-02-24 16:52:26 +01:00
|
|
|
#ifdef CONFIG_COMPAT
|
|
|
|
COMPAT_SYSCALL_DEFINE3(lseek, unsigned int, fd, compat_off_t, offset, unsigned int, whence)
|
|
|
|
{
|
|
|
|
return sys_lseek(fd, offset, whence);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
#ifdef __ARCH_WANT_SYS_LLSEEK
|
2009-01-14 14:14:21 +01:00
|
|
|
SYSCALL_DEFINE5(llseek, unsigned int, fd, unsigned long, offset_high,
|
|
|
|
unsigned long, offset_low, loff_t __user *, result,
|
2012-12-18 00:59:39 +01:00
|
|
|
unsigned int, whence)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
int retval;
|
2014-03-16 20:24:08 +01:00
|
|
|
struct fd f = fdget_pos(fd);
|
2005-04-17 00:20:36 +02:00
|
|
|
loff_t offset;
|
|
|
|
|
2012-08-28 18:52:22 +02:00
|
|
|
if (!f.file)
|
|
|
|
return -EBADF;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
retval = -EINVAL;
|
2012-12-18 00:59:39 +01:00
|
|
|
if (whence > SEEK_MAX)
|
2005-04-17 00:20:36 +02:00
|
|
|
goto out_putf;
|
|
|
|
|
2012-08-28 18:52:22 +02:00
|
|
|
offset = vfs_llseek(f.file, ((loff_t) offset_high << 32) | offset_low,
|
2012-12-18 00:59:39 +01:00
|
|
|
whence);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
retval = (int)offset;
|
|
|
|
if (offset >= 0) {
|
|
|
|
retval = -EFAULT;
|
|
|
|
if (!copy_to_user(result, &offset, sizeof(offset)))
|
|
|
|
retval = 0;
|
|
|
|
}
|
|
|
|
out_putf:
|
2014-03-16 20:24:08 +01:00
|
|
|
fdput_pos(f);
|
2005-04-17 00:20:36 +02:00
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2015-01-25 21:11:59 +01:00
|
|
|
ssize_t vfs_iter_read(struct file *file, struct iov_iter *iter, loff_t *ppos)
|
|
|
|
{
|
|
|
|
struct kiocb kiocb;
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
if (!file->f_op->read_iter)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
init_sync_kiocb(&kiocb, file);
|
|
|
|
kiocb.ki_pos = *ppos;
|
|
|
|
|
|
|
|
iter->type |= READ;
|
|
|
|
ret = file->f_op->read_iter(&kiocb, iter);
|
2015-02-11 19:59:44 +01:00
|
|
|
BUG_ON(ret == -EIOCBQUEUED);
|
2015-01-25 21:11:59 +01:00
|
|
|
if (ret > 0)
|
|
|
|
*ppos = kiocb.ki_pos;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(vfs_iter_read);
|
|
|
|
|
|
|
|
ssize_t vfs_iter_write(struct file *file, struct iov_iter *iter, loff_t *ppos)
|
|
|
|
{
|
|
|
|
struct kiocb kiocb;
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
if (!file->f_op->write_iter)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
init_sync_kiocb(&kiocb, file);
|
|
|
|
kiocb.ki_pos = *ppos;
|
|
|
|
|
|
|
|
iter->type |= WRITE;
|
|
|
|
ret = file->f_op->write_iter(&kiocb, iter);
|
2015-02-11 19:59:44 +01:00
|
|
|
BUG_ON(ret == -EIOCBQUEUED);
|
2015-01-25 21:11:59 +01:00
|
|
|
if (ret > 0)
|
|
|
|
*ppos = kiocb.ki_pos;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(vfs_iter_write);
|
|
|
|
|
2013-06-19 13:26:04 +02:00
|
|
|
int rw_verify_area(int read_write, struct file *file, const loff_t *ppos, size_t count)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
struct inode *inode;
|
|
|
|
loff_t pos;
|
2008-01-12 12:05:48 +01:00
|
|
|
int retval = -EINVAL;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2013-01-23 23:07:38 +01:00
|
|
|
inode = file_inode(file);
|
2006-01-05 01:20:40 +01:00
|
|
|
if (unlikely((ssize_t) count < 0))
|
2008-01-12 12:05:48 +01:00
|
|
|
return retval;
|
2005-04-17 00:20:36 +02:00
|
|
|
pos = *ppos;
|
2010-12-17 13:44:05 +01:00
|
|
|
if (unlikely(pos < 0)) {
|
|
|
|
if (!unsigned_offsets(file))
|
|
|
|
return retval;
|
|
|
|
if (count >= -pos) /* both values are in 0..LLONG_MAX */
|
|
|
|
return -EOVERFLOW;
|
|
|
|
} else if (unlikely((loff_t) (pos + count) < 0)) {
|
|
|
|
if (!unsigned_offsets(file))
|
2010-10-01 23:20:22 +02:00
|
|
|
return retval;
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2015-01-16 21:05:55 +01:00
|
|
|
if (unlikely(inode->i_flctx && mandatory_lock(inode))) {
|
2015-12-03 12:59:49 +01:00
|
|
|
retval = locks_mandatory_area(inode, file, pos, pos + count - 1,
|
|
|
|
read_write == READ ? F_RDLCK : F_WRLCK);
|
2006-01-05 01:20:40 +01:00
|
|
|
if (retval < 0)
|
|
|
|
return retval;
|
|
|
|
}
|
2016-04-01 03:48:20 +02:00
|
|
|
return security_file_permission(file,
|
2008-01-12 12:05:48 +01:00
|
|
|
read_write == READ ? MAY_READ : MAY_WRITE);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2015-04-03 21:41:18 +02:00
|
|
|
static ssize_t new_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
|
2014-02-12 00:37:41 +01:00
|
|
|
{
|
|
|
|
struct iovec iov = { .iov_base = buf, .iov_len = len };
|
|
|
|
struct kiocb kiocb;
|
|
|
|
struct iov_iter iter;
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
init_sync_kiocb(&kiocb, filp);
|
|
|
|
kiocb.ki_pos = *ppos;
|
|
|
|
iov_iter_init(&iter, READ, &iov, 1, len);
|
|
|
|
|
|
|
|
ret = filp->f_op->read_iter(&kiocb, &iter);
|
2015-02-11 19:59:44 +01:00
|
|
|
BUG_ON(ret == -EIOCBQUEUED);
|
2014-02-12 00:37:41 +01:00
|
|
|
*ppos = kiocb.ki_pos;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-11-05 16:01:17 +01:00
|
|
|
ssize_t __vfs_read(struct file *file, char __user *buf, size_t count,
|
|
|
|
loff_t *pos)
|
|
|
|
{
|
|
|
|
if (file->f_op->read)
|
2015-04-03 21:09:18 +02:00
|
|
|
return file->f_op->read(file, buf, count, pos);
|
2014-11-05 16:01:17 +01:00
|
|
|
else if (file->f_op->read_iter)
|
2015-04-03 21:09:18 +02:00
|
|
|
return new_sync_read(file, buf, count, pos);
|
2014-11-05 16:01:17 +01:00
|
|
|
else
|
2015-04-03 21:09:18 +02:00
|
|
|
return -EINVAL;
|
2014-11-05 16:01:17 +01:00
|
|
|
}
|
2015-04-03 21:09:18 +02:00
|
|
|
EXPORT_SYMBOL(__vfs_read);
|
2014-11-05 16:01:17 +01:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
|
|
|
|
{
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
if (!(file->f_mode & FMODE_READ))
|
|
|
|
return -EBADF;
|
2014-02-11 23:49:24 +01:00
|
|
|
if (!(file->f_mode & FMODE_CAN_READ))
|
2005-04-17 00:20:36 +02:00
|
|
|
return -EINVAL;
|
|
|
|
if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
ret = rw_verify_area(READ, file, pos, count);
|
2016-04-01 03:48:20 +02:00
|
|
|
if (!ret) {
|
|
|
|
if (count > MAX_RW_COUNT)
|
|
|
|
count = MAX_RW_COUNT;
|
2014-11-05 16:01:17 +01:00
|
|
|
ret = __vfs_read(file, buf, count, pos);
|
2008-01-12 12:05:48 +01:00
|
|
|
if (ret > 0) {
|
2009-12-18 03:24:21 +01:00
|
|
|
fsnotify_access(file);
|
2008-01-12 12:05:48 +01:00
|
|
|
add_rchar(current, ret);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
2008-01-12 12:05:48 +01:00
|
|
|
inc_syscr(current);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT_SYMBOL(vfs_read);
|
|
|
|
|
2015-04-03 21:41:18 +02:00
|
|
|
static ssize_t new_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
|
2014-02-12 00:37:41 +01:00
|
|
|
{
|
|
|
|
struct iovec iov = { .iov_base = (void __user *)buf, .iov_len = len };
|
|
|
|
struct kiocb kiocb;
|
|
|
|
struct iov_iter iter;
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
init_sync_kiocb(&kiocb, filp);
|
|
|
|
kiocb.ki_pos = *ppos;
|
|
|
|
iov_iter_init(&iter, WRITE, &iov, 1, len);
|
|
|
|
|
|
|
|
ret = filp->f_op->write_iter(&kiocb, &iter);
|
2015-02-11 19:59:44 +01:00
|
|
|
BUG_ON(ret == -EIOCBQUEUED);
|
2015-04-07 02:50:38 +02:00
|
|
|
if (ret > 0)
|
|
|
|
*ppos = kiocb.ki_pos;
|
2014-02-12 00:37:41 +01:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-04-03 21:06:43 +02:00
|
|
|
ssize_t __vfs_write(struct file *file, const char __user *p, size_t count,
|
|
|
|
loff_t *pos)
|
|
|
|
{
|
|
|
|
if (file->f_op->write)
|
|
|
|
return file->f_op->write(file, p, count, pos);
|
|
|
|
else if (file->f_op->write_iter)
|
|
|
|
return new_sync_write(file, p, count, pos);
|
|
|
|
else
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(__vfs_write);
|
|
|
|
|
2013-03-20 18:19:30 +01:00
|
|
|
ssize_t __kernel_write(struct file *file, const char *buf, size_t count, loff_t *pos)
|
|
|
|
{
|
|
|
|
mm_segment_t old_fs;
|
|
|
|
const char __user *p;
|
|
|
|
ssize_t ret;
|
|
|
|
|
2014-02-11 23:49:24 +01:00
|
|
|
if (!(file->f_mode & FMODE_CAN_WRITE))
|
2013-03-27 16:20:30 +01:00
|
|
|
return -EINVAL;
|
|
|
|
|
2013-03-20 18:19:30 +01:00
|
|
|
old_fs = get_fs();
|
|
|
|
set_fs(get_ds());
|
|
|
|
p = (__force const char __user *)buf;
|
|
|
|
if (count > MAX_RW_COUNT)
|
|
|
|
count = MAX_RW_COUNT;
|
2015-04-03 21:06:43 +02:00
|
|
|
ret = __vfs_write(file, p, count, pos);
|
2013-03-20 18:19:30 +01:00
|
|
|
set_fs(old_fs);
|
|
|
|
if (ret > 0) {
|
|
|
|
fsnotify_modify(file);
|
|
|
|
add_wchar(current, ret);
|
|
|
|
}
|
|
|
|
inc_syscw(current);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-08-19 17:48:09 +02:00
|
|
|
EXPORT_SYMBOL(__kernel_write);
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
|
|
|
|
{
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
if (!(file->f_mode & FMODE_WRITE))
|
|
|
|
return -EBADF;
|
2014-02-11 23:49:24 +01:00
|
|
|
if (!(file->f_mode & FMODE_CAN_WRITE))
|
2005-04-17 00:20:36 +02:00
|
|
|
return -EINVAL;
|
|
|
|
if (unlikely(!access_ok(VERIFY_READ, buf, count)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
ret = rw_verify_area(WRITE, file, pos, count);
|
2016-04-01 03:48:20 +02:00
|
|
|
if (!ret) {
|
|
|
|
if (count > MAX_RW_COUNT)
|
|
|
|
count = MAX_RW_COUNT;
|
2013-03-20 18:04:20 +01:00
|
|
|
file_start_write(file);
|
2015-04-03 21:06:43 +02:00
|
|
|
ret = __vfs_write(file, buf, count, pos);
|
2008-01-12 12:05:48 +01:00
|
|
|
if (ret > 0) {
|
2009-12-18 03:24:21 +01:00
|
|
|
fsnotify_modify(file);
|
2008-01-12 12:05:48 +01:00
|
|
|
add_wchar(current, ret);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
2008-01-12 12:05:48 +01:00
|
|
|
inc_syscw(current);
|
2013-03-20 18:04:20 +01:00
|
|
|
file_end_write(file);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT_SYMBOL(vfs_write);
|
|
|
|
|
|
|
|
static inline loff_t file_pos_read(struct file *file)
|
|
|
|
{
|
|
|
|
return file->f_pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void file_pos_write(struct file *file, loff_t pos)
|
|
|
|
{
|
|
|
|
file->f_pos = pos;
|
|
|
|
}
|
|
|
|
|
2009-01-14 14:14:22 +01:00
|
|
|
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2014-03-03 18:36:58 +01:00
|
|
|
struct fd f = fdget_pos(fd);
|
2005-04-17 00:20:36 +02:00
|
|
|
ssize_t ret = -EBADF;
|
|
|
|
|
2012-08-28 18:52:22 +02:00
|
|
|
if (f.file) {
|
|
|
|
loff_t pos = file_pos_read(f.file);
|
|
|
|
ret = vfs_read(f.file, buf, count, &pos);
|
2013-06-15 03:49:36 +02:00
|
|
|
if (ret >= 0)
|
|
|
|
file_pos_write(f.file, pos);
|
2014-03-03 18:36:58 +01:00
|
|
|
fdput_pos(f);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2009-01-14 14:14:22 +01:00
|
|
|
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
|
|
|
|
size_t, count)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2014-03-03 18:36:58 +01:00
|
|
|
struct fd f = fdget_pos(fd);
|
2005-04-17 00:20:36 +02:00
|
|
|
ssize_t ret = -EBADF;
|
|
|
|
|
2012-08-28 18:52:22 +02:00
|
|
|
if (f.file) {
|
|
|
|
loff_t pos = file_pos_read(f.file);
|
|
|
|
ret = vfs_write(f.file, buf, count, &pos);
|
2013-06-15 03:49:36 +02:00
|
|
|
if (ret >= 0)
|
|
|
|
file_pos_write(f.file, pos);
|
2014-03-03 18:36:58 +01:00
|
|
|
fdput_pos(f);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2013-01-21 21:16:58 +01:00
|
|
|
SYSCALL_DEFINE4(pread64, unsigned int, fd, char __user *, buf,
|
|
|
|
size_t, count, loff_t, pos)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2012-08-28 18:52:22 +02:00
|
|
|
struct fd f;
|
2005-04-17 00:20:36 +02:00
|
|
|
ssize_t ret = -EBADF;
|
|
|
|
|
|
|
|
if (pos < 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2012-08-28 18:52:22 +02:00
|
|
|
f = fdget(fd);
|
|
|
|
if (f.file) {
|
2005-04-17 00:20:36 +02:00
|
|
|
ret = -ESPIPE;
|
2012-08-28 18:52:22 +02:00
|
|
|
if (f.file->f_mode & FMODE_PREAD)
|
|
|
|
ret = vfs_read(f.file, buf, count, &pos);
|
|
|
|
fdput(f);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2013-01-21 21:16:58 +01:00
|
|
|
SYSCALL_DEFINE4(pwrite64, unsigned int, fd, const char __user *, buf,
|
|
|
|
size_t, count, loff_t, pos)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2012-08-28 18:52:22 +02:00
|
|
|
struct fd f;
|
2005-04-17 00:20:36 +02:00
|
|
|
ssize_t ret = -EBADF;
|
|
|
|
|
|
|
|
if (pos < 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2012-08-28 18:52:22 +02:00
|
|
|
f = fdget(fd);
|
|
|
|
if (f.file) {
|
2005-04-17 00:20:36 +02:00
|
|
|
ret = -ESPIPE;
|
2012-08-28 18:52:22 +02:00
|
|
|
if (f.file->f_mode & FMODE_PWRITE)
|
|
|
|
ret = vfs_write(f.file, buf, count, &pos);
|
|
|
|
fdput(f);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Reduce an iovec's length in-place. Return the resulting number of segments
|
|
|
|
*/
|
|
|
|
unsigned long iov_shorten(struct iovec *iov, unsigned long nr_segs, size_t to)
|
|
|
|
{
|
|
|
|
unsigned long seg = 0;
|
|
|
|
size_t len = 0;
|
|
|
|
|
|
|
|
while (seg < nr_segs) {
|
|
|
|
seg++;
|
|
|
|
if (len + iov->iov_len >= to) {
|
|
|
|
iov->iov_len = to - len;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
len += iov->iov_len;
|
|
|
|
iov++;
|
|
|
|
}
|
|
|
|
return seg;
|
|
|
|
}
|
2008-01-29 05:58:27 +01:00
|
|
|
EXPORT_SYMBOL(iov_shorten);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2015-03-21 01:10:21 +01:00
|
|
|
static ssize_t do_iter_readv_writev(struct file *filp, struct iov_iter *iter,
|
2016-03-03 16:03:58 +01:00
|
|
|
loff_t *ppos, iter_fn_t fn, int flags)
|
2014-02-12 00:37:41 +01:00
|
|
|
{
|
|
|
|
struct kiocb kiocb;
|
|
|
|
ssize_t ret;
|
|
|
|
|
2016-04-07 17:52:03 +02:00
|
|
|
if (flags & ~(RWF_HIPRI | RWF_DSYNC | RWF_SYNC))
|
2016-03-03 16:03:58 +01:00
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
2014-02-12 00:37:41 +01:00
|
|
|
init_sync_kiocb(&kiocb, filp);
|
2016-03-03 16:04:01 +01:00
|
|
|
if (flags & RWF_HIPRI)
|
|
|
|
kiocb.ki_flags |= IOCB_HIPRI;
|
2016-04-07 17:52:03 +02:00
|
|
|
if (flags & RWF_DSYNC)
|
|
|
|
kiocb.ki_flags |= IOCB_DSYNC;
|
|
|
|
if (flags & RWF_SYNC)
|
|
|
|
kiocb.ki_flags |= (IOCB_DSYNC | IOCB_SYNC);
|
2014-02-12 00:37:41 +01:00
|
|
|
kiocb.ki_pos = *ppos;
|
|
|
|
|
2015-03-21 01:10:21 +01:00
|
|
|
ret = fn(&kiocb, iter);
|
2015-02-11 19:59:44 +01:00
|
|
|
BUG_ON(ret == -EIOCBQUEUED);
|
2014-02-12 00:37:41 +01:00
|
|
|
*ppos = kiocb.ki_pos;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2006-10-01 08:28:47 +02:00
|
|
|
/* Do it by hand, with file-ops */
|
2015-03-21 01:10:21 +01:00
|
|
|
static ssize_t do_loop_readv_writev(struct file *filp, struct iov_iter *iter,
|
2016-03-03 16:03:58 +01:00
|
|
|
loff_t *ppos, io_fn_t fn, int flags)
|
2006-10-01 08:28:47 +02:00
|
|
|
{
|
|
|
|
ssize_t ret = 0;
|
|
|
|
|
2016-03-03 16:04:01 +01:00
|
|
|
if (flags & ~RWF_HIPRI)
|
2016-03-03 16:03:58 +01:00
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
2015-03-21 01:10:21 +01:00
|
|
|
while (iov_iter_count(iter)) {
|
|
|
|
struct iovec iovec = iov_iter_iovec(iter);
|
2006-10-01 08:28:47 +02:00
|
|
|
ssize_t nr;
|
|
|
|
|
2015-03-21 01:10:21 +01:00
|
|
|
nr = fn(filp, iovec.iov_base, iovec.iov_len, ppos);
|
2006-10-01 08:28:47 +02:00
|
|
|
|
|
|
|
if (nr < 0) {
|
|
|
|
if (!ret)
|
|
|
|
ret = nr;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ret += nr;
|
2015-03-21 01:10:21 +01:00
|
|
|
if (nr != iovec.iov_len)
|
2006-10-01 08:28:47 +02:00
|
|
|
break;
|
2015-03-21 01:10:21 +01:00
|
|
|
iov_iter_advance(iter, nr);
|
2006-10-01 08:28:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* A write operation does a read from user space and vice versa */
|
|
|
|
#define vrfy_dir(type) ((type) == READ ? VERIFY_WRITE : VERIFY_READ)
|
|
|
|
|
2006-10-01 08:28:49 +02:00
|
|
|
ssize_t rw_copy_check_uvector(int type, const struct iovec __user * uvector,
|
|
|
|
unsigned long nr_segs, unsigned long fast_segs,
|
|
|
|
struct iovec *fast_pointer,
|
2012-06-01 01:26:42 +02:00
|
|
|
struct iovec **ret_pointer)
|
2010-10-29 19:36:49 +02:00
|
|
|
{
|
2006-10-01 08:28:49 +02:00
|
|
|
unsigned long seg;
|
2010-10-29 19:36:49 +02:00
|
|
|
ssize_t ret;
|
2006-10-01 08:28:49 +02:00
|
|
|
struct iovec *iov = fast_pointer;
|
|
|
|
|
2010-10-29 19:36:49 +02:00
|
|
|
/*
|
|
|
|
* SuS says "The readv() function *may* fail if the iovcnt argument
|
|
|
|
* was less than or equal to 0, or greater than {IOV_MAX}. Linux has
|
|
|
|
* traditionally returned zero for zero segments, so...
|
|
|
|
*/
|
2006-10-01 08:28:49 +02:00
|
|
|
if (nr_segs == 0) {
|
|
|
|
ret = 0;
|
2010-10-29 19:36:49 +02:00
|
|
|
goto out;
|
2006-10-01 08:28:49 +02:00
|
|
|
}
|
|
|
|
|
2010-10-29 19:36:49 +02:00
|
|
|
/*
|
|
|
|
* First get the "struct iovec" from user memory and
|
|
|
|
* verify all the pointers
|
|
|
|
*/
|
2006-10-01 08:28:49 +02:00
|
|
|
if (nr_segs > UIO_MAXIOV) {
|
|
|
|
ret = -EINVAL;
|
2010-10-29 19:36:49 +02:00
|
|
|
goto out;
|
2006-10-01 08:28:49 +02:00
|
|
|
}
|
|
|
|
if (nr_segs > fast_segs) {
|
2010-10-29 19:36:49 +02:00
|
|
|
iov = kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL);
|
2006-10-01 08:28:49 +02:00
|
|
|
if (iov == NULL) {
|
|
|
|
ret = -ENOMEM;
|
2010-10-29 19:36:49 +02:00
|
|
|
goto out;
|
2006-10-01 08:28:49 +02:00
|
|
|
}
|
2010-10-29 19:36:49 +02:00
|
|
|
}
|
2006-10-01 08:28:49 +02:00
|
|
|
if (copy_from_user(iov, uvector, nr_segs*sizeof(*uvector))) {
|
|
|
|
ret = -EFAULT;
|
2010-10-29 19:36:49 +02:00
|
|
|
goto out;
|
2006-10-01 08:28:49 +02:00
|
|
|
}
|
|
|
|
|
2010-10-29 19:36:49 +02:00
|
|
|
/*
|
2006-10-01 08:28:49 +02:00
|
|
|
* According to the Single Unix Specification we should return EINVAL
|
|
|
|
* if an element length is < 0 when cast to ssize_t or if the
|
|
|
|
* total length would overflow the ssize_t return value of the
|
|
|
|
* system call.
|
2010-10-29 19:36:49 +02:00
|
|
|
*
|
|
|
|
* Linux caps all read/write calls to MAX_RW_COUNT, and avoids the
|
|
|
|
* overflow case.
|
|
|
|
*/
|
2006-10-01 08:28:49 +02:00
|
|
|
ret = 0;
|
2010-10-29 19:36:49 +02:00
|
|
|
for (seg = 0; seg < nr_segs; seg++) {
|
|
|
|
void __user *buf = iov[seg].iov_base;
|
|
|
|
ssize_t len = (ssize_t)iov[seg].iov_len;
|
2006-10-01 08:28:49 +02:00
|
|
|
|
|
|
|
/* see if we we're about to use an invalid len or if
|
|
|
|
* it's about to overflow ssize_t */
|
2010-10-29 19:36:49 +02:00
|
|
|
if (len < 0) {
|
2006-10-01 08:28:49 +02:00
|
|
|
ret = -EINVAL;
|
2010-10-29 19:36:49 +02:00
|
|
|
goto out;
|
2006-10-01 08:28:49 +02:00
|
|
|
}
|
2012-06-01 01:26:42 +02:00
|
|
|
if (type >= 0
|
2011-11-01 01:06:39 +01:00
|
|
|
&& unlikely(!access_ok(vrfy_dir(type), buf, len))) {
|
2006-10-01 08:28:49 +02:00
|
|
|
ret = -EFAULT;
|
2010-10-29 19:36:49 +02:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
if (len > MAX_RW_COUNT - ret) {
|
|
|
|
len = MAX_RW_COUNT - ret;
|
|
|
|
iov[seg].iov_len = len;
|
2006-10-01 08:28:49 +02:00
|
|
|
}
|
|
|
|
ret += len;
|
2010-10-29 19:36:49 +02:00
|
|
|
}
|
2006-10-01 08:28:49 +02:00
|
|
|
out:
|
|
|
|
*ret_pointer = iov;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
static ssize_t do_readv_writev(int type, struct file *file,
|
|
|
|
const struct iovec __user * uvector,
|
2016-03-03 16:03:58 +01:00
|
|
|
unsigned long nr_segs, loff_t *pos,
|
|
|
|
int flags)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
size_t tot_len;
|
|
|
|
struct iovec iovstack[UIO_FASTIOV];
|
2006-10-01 08:28:47 +02:00
|
|
|
struct iovec *iov = iovstack;
|
2015-03-21 01:10:21 +01:00
|
|
|
struct iov_iter iter;
|
2005-04-17 00:20:36 +02:00
|
|
|
ssize_t ret;
|
|
|
|
io_fn_t fn;
|
2014-02-12 00:37:41 +01:00
|
|
|
iter_fn_t iter_fn;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2015-03-22 00:40:11 +01:00
|
|
|
ret = import_iovec(type, uvector, nr_segs,
|
|
|
|
ARRAY_SIZE(iovstack), &iov, &iter);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2015-03-22 00:40:11 +01:00
|
|
|
tot_len = iov_iter_count(&iter);
|
|
|
|
if (!tot_len)
|
|
|
|
goto out;
|
2005-04-17 00:20:36 +02:00
|
|
|
ret = rw_verify_area(type, file, pos, tot_len);
|
2006-01-05 01:20:40 +01:00
|
|
|
if (ret < 0)
|
2005-09-28 17:21:28 +02:00
|
|
|
goto out;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if (type == READ) {
|
|
|
|
fn = file->f_op->read;
|
2014-02-12 00:37:41 +01:00
|
|
|
iter_fn = file->f_op->read_iter;
|
2005-04-17 00:20:36 +02:00
|
|
|
} else {
|
|
|
|
fn = (io_fn_t)file->f_op->write;
|
2014-02-12 00:37:41 +01:00
|
|
|
iter_fn = file->f_op->write_iter;
|
2013-03-20 18:04:20 +01:00
|
|
|
file_start_write(file);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2014-02-12 00:37:41 +01:00
|
|
|
if (iter_fn)
|
2016-03-03 16:03:58 +01:00
|
|
|
ret = do_iter_readv_writev(file, &iter, pos, iter_fn, flags);
|
2006-10-01 08:28:47 +02:00
|
|
|
else
|
2016-03-03 16:03:58 +01:00
|
|
|
ret = do_loop_readv_writev(file, &iter, pos, fn, flags);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2013-03-20 18:04:20 +01:00
|
|
|
if (type != READ)
|
|
|
|
file_end_write(file);
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
out:
|
2015-03-22 00:40:11 +01:00
|
|
|
kfree(iov);
|
[PATCH] inotify
inotify is intended to correct the deficiencies of dnotify, particularly
its inability to scale and its terrible user interface:
* dnotify requires the opening of one fd per each directory
that you intend to watch. This quickly results in too many
open files and pins removable media, preventing unmount.
* dnotify is directory-based. You only learn about changes to
directories. Sure, a change to a file in a directory affects
the directory, but you are then forced to keep a cache of
stat structures.
* dnotify's interface to user-space is awful. Signals?
inotify provides a more usable, simple, powerful solution to file change
notification:
* inotify's interface is a system call that returns a fd, not SIGIO.
You get a single fd, which is select()-able.
* inotify has an event that says "the filesystem that the item
you were watching is on was unmounted."
* inotify can watch directories or files.
Inotify is currently used by Beagle (a desktop search infrastructure),
Gamin (a FAM replacement), and other projects.
See Documentation/filesystems/inotify.txt.
Signed-off-by: Robert Love <rml@novell.com>
Cc: John McCutchan <ttb@tentacle.dhs.org>
Cc: Christoph Hellwig <hch@lst.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-07-12 23:06:03 +02:00
|
|
|
if ((ret + (type == READ)) > 0) {
|
|
|
|
if (type == READ)
|
2009-12-18 03:24:21 +01:00
|
|
|
fsnotify_access(file);
|
[PATCH] inotify
inotify is intended to correct the deficiencies of dnotify, particularly
its inability to scale and its terrible user interface:
* dnotify requires the opening of one fd per each directory
that you intend to watch. This quickly results in too many
open files and pins removable media, preventing unmount.
* dnotify is directory-based. You only learn about changes to
directories. Sure, a change to a file in a directory affects
the directory, but you are then forced to keep a cache of
stat structures.
* dnotify's interface to user-space is awful. Signals?
inotify provides a more usable, simple, powerful solution to file change
notification:
* inotify's interface is a system call that returns a fd, not SIGIO.
You get a single fd, which is select()-able.
* inotify has an event that says "the filesystem that the item
you were watching is on was unmounted."
* inotify can watch directories or files.
Inotify is currently used by Beagle (a desktop search infrastructure),
Gamin (a FAM replacement), and other projects.
See Documentation/filesystems/inotify.txt.
Signed-off-by: Robert Love <rml@novell.com>
Cc: John McCutchan <ttb@tentacle.dhs.org>
Cc: Christoph Hellwig <hch@lst.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-07-12 23:06:03 +02:00
|
|
|
else
|
2009-12-18 03:24:21 +01:00
|
|
|
fsnotify_modify(file);
|
[PATCH] inotify
inotify is intended to correct the deficiencies of dnotify, particularly
its inability to scale and its terrible user interface:
* dnotify requires the opening of one fd per each directory
that you intend to watch. This quickly results in too many
open files and pins removable media, preventing unmount.
* dnotify is directory-based. You only learn about changes to
directories. Sure, a change to a file in a directory affects
the directory, but you are then forced to keep a cache of
stat structures.
* dnotify's interface to user-space is awful. Signals?
inotify provides a more usable, simple, powerful solution to file change
notification:
* inotify's interface is a system call that returns a fd, not SIGIO.
You get a single fd, which is select()-able.
* inotify has an event that says "the filesystem that the item
you were watching is on was unmounted."
* inotify can watch directories or files.
Inotify is currently used by Beagle (a desktop search infrastructure),
Gamin (a FAM replacement), and other projects.
See Documentation/filesystems/inotify.txt.
Signed-off-by: Robert Love <rml@novell.com>
Cc: John McCutchan <ttb@tentacle.dhs.org>
Cc: Christoph Hellwig <hch@lst.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-07-12 23:06:03 +02:00
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ssize_t vfs_readv(struct file *file, const struct iovec __user *vec,
|
2016-03-03 16:03:58 +01:00
|
|
|
unsigned long vlen, loff_t *pos, int flags)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
if (!(file->f_mode & FMODE_READ))
|
|
|
|
return -EBADF;
|
2014-02-11 23:49:24 +01:00
|
|
|
if (!(file->f_mode & FMODE_CAN_READ))
|
2005-04-17 00:20:36 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
2016-03-03 16:03:58 +01:00
|
|
|
return do_readv_writev(READ, file, vec, vlen, pos, flags);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT_SYMBOL(vfs_readv);
|
|
|
|
|
|
|
|
ssize_t vfs_writev(struct file *file, const struct iovec __user *vec,
|
2016-03-03 16:03:58 +01:00
|
|
|
unsigned long vlen, loff_t *pos, int flags)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
if (!(file->f_mode & FMODE_WRITE))
|
|
|
|
return -EBADF;
|
2014-02-11 23:49:24 +01:00
|
|
|
if (!(file->f_mode & FMODE_CAN_WRITE))
|
2005-04-17 00:20:36 +02:00
|
|
|
return -EINVAL;
|
|
|
|
|
2016-03-03 16:03:58 +01:00
|
|
|
return do_readv_writev(WRITE, file, vec, vlen, pos, flags);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT_SYMBOL(vfs_writev);
|
|
|
|
|
2016-03-03 16:03:59 +01:00
|
|
|
static ssize_t do_readv(unsigned long fd, const struct iovec __user *vec,
|
|
|
|
unsigned long vlen, int flags)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2014-03-03 18:36:58 +01:00
|
|
|
struct fd f = fdget_pos(fd);
|
2005-04-17 00:20:36 +02:00
|
|
|
ssize_t ret = -EBADF;
|
|
|
|
|
2012-08-28 18:52:22 +02:00
|
|
|
if (f.file) {
|
|
|
|
loff_t pos = file_pos_read(f.file);
|
2016-03-03 16:03:59 +01:00
|
|
|
ret = vfs_readv(f.file, vec, vlen, &pos, flags);
|
2013-06-15 03:49:36 +02:00
|
|
|
if (ret >= 0)
|
|
|
|
file_pos_write(f.file, pos);
|
2014-03-03 18:36:58 +01:00
|
|
|
fdput_pos(f);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ret > 0)
|
[PATCH] ifdef ->rchar, ->wchar, ->syscr, ->syscw from task_struct
They are fat: 4x8 bytes in task_struct.
They are uncoditionally updated in every fork, read, write and sendfile.
They are used only if you have some "extended acct fields feature".
And please, please, please, read(2) knows about bytes, not characters,
why it is called "rchar"?
Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
Cc: Jay Lan <jlan@engr.sgi.com>
Cc: Balbir Singh <balbir@in.ibm.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-02-10 10:46:45 +01:00
|
|
|
add_rchar(current, ret);
|
|
|
|
inc_syscr(current);
|
2005-04-17 00:20:36 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-03-03 16:03:59 +01:00
|
|
|
static ssize_t do_writev(unsigned long fd, const struct iovec __user *vec,
|
|
|
|
unsigned long vlen, int flags)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2014-03-03 18:36:58 +01:00
|
|
|
struct fd f = fdget_pos(fd);
|
2005-04-17 00:20:36 +02:00
|
|
|
ssize_t ret = -EBADF;
|
|
|
|
|
2012-08-28 18:52:22 +02:00
|
|
|
if (f.file) {
|
|
|
|
loff_t pos = file_pos_read(f.file);
|
2016-03-03 16:03:59 +01:00
|
|
|
ret = vfs_writev(f.file, vec, vlen, &pos, flags);
|
2013-06-15 03:49:36 +02:00
|
|
|
if (ret >= 0)
|
|
|
|
file_pos_write(f.file, pos);
|
2014-03-03 18:36:58 +01:00
|
|
|
fdput_pos(f);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ret > 0)
|
[PATCH] ifdef ->rchar, ->wchar, ->syscr, ->syscw from task_struct
They are fat: 4x8 bytes in task_struct.
They are uncoditionally updated in every fork, read, write and sendfile.
They are used only if you have some "extended acct fields feature".
And please, please, please, read(2) knows about bytes, not characters,
why it is called "rchar"?
Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
Cc: Jay Lan <jlan@engr.sgi.com>
Cc: Balbir Singh <balbir@in.ibm.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-02-10 10:46:45 +01:00
|
|
|
add_wchar(current, ret);
|
|
|
|
inc_syscw(current);
|
2005-04-17 00:20:36 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
Make non-compat preadv/pwritev use native register size
Instead of always splitting the file offset into 32-bit 'high' and 'low'
parts, just split them into the largest natural word-size - which in C
terms is 'unsigned long'.
This allows 64-bit architectures to avoid the unnecessary 32-bit
shifting and masking for native format (while the compat interfaces will
obviously always have to do it).
This also changes the order of 'high' and 'low' to be "low first". Why?
Because when we have it like this, the 64-bit system calls now don't use
the "pos_high" argument at all, and it makes more sense for the native
system call to simply match the user-mode prototype.
This results in a much more natural calling convention, and allows the
compiler to generate much more straightforward code. On x86-64, we now
generate
testq %rcx, %rcx # pos_l
js .L122 #,
movq %rcx, -48(%rbp) # pos_l, pos
from the C source
loff_t pos = pos_from_hilo(pos_h, pos_l);
...
if (pos < 0)
return -EINVAL;
and the 'pos_h' register isn't even touched. It used to generate code
like
mov %r8d, %r8d # pos_low, pos_low
salq $32, %rcx #, tmp71
movq %r8, %rax # pos_low, pos.386
orq %rcx, %rax # tmp71, pos.386
js .L122 #,
movq %rax, -48(%rbp) # pos.386, pos
which isn't _that_ horrible, but it does show how the natural word size
is just a more sensible interface (same arguments will hold in the user
level glibc wrapper function, of course, so the kernel side is just half
of the equation!)
Note: in all cases the user code wrapper can again be the same. You can
just do
#define HALF_BITS (sizeof(unsigned long)*4)
__syscall(PWRITEV, fd, iov, count, offset, (offset >> HALF_BITS) >> HALF_BITS);
or something like that. That way the user mode wrapper will also be
nicely passing in a zero (it won't actually have to do the shifts, the
compiler will understand what is going on) for the last argument.
And that is a good idea, even if nobody will necessarily ever care: if
we ever do move to a 128-bit lloff_t, this particular system call might
be left alone. Of course, that will be the least of our worries if we
really ever need to care, so this may not be worth really caring about.
[ Fixed for lost 'loff_t' cast noticed by Andrew Morton ]
Acked-by: Gerd Hoffmann <kraxel@redhat.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: linux-api@vger.kernel.org
Cc: linux-arch@vger.kernel.org
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Ralf Baechle <ralf@linux-mips.org>>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2009-04-03 17:03:22 +02:00
|
|
|
static inline loff_t pos_from_hilo(unsigned long high, unsigned long low)
|
|
|
|
{
|
|
|
|
#define HALF_LONG_BITS (BITS_PER_LONG / 2)
|
|
|
|
return (((loff_t)high << HALF_LONG_BITS) << HALF_LONG_BITS) | low;
|
|
|
|
}
|
|
|
|
|
2016-03-03 16:03:59 +01:00
|
|
|
static ssize_t do_preadv(unsigned long fd, const struct iovec __user *vec,
|
|
|
|
unsigned long vlen, loff_t pos, int flags)
|
2009-04-03 01:59:23 +02:00
|
|
|
{
|
2012-08-28 18:52:22 +02:00
|
|
|
struct fd f;
|
2009-04-03 01:59:23 +02:00
|
|
|
ssize_t ret = -EBADF;
|
|
|
|
|
|
|
|
if (pos < 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2012-08-28 18:52:22 +02:00
|
|
|
f = fdget(fd);
|
|
|
|
if (f.file) {
|
2009-04-03 01:59:23 +02:00
|
|
|
ret = -ESPIPE;
|
2012-08-28 18:52:22 +02:00
|
|
|
if (f.file->f_mode & FMODE_PREAD)
|
2016-03-03 16:03:59 +01:00
|
|
|
ret = vfs_readv(f.file, vec, vlen, &pos, flags);
|
2012-08-28 18:52:22 +02:00
|
|
|
fdput(f);
|
2009-04-03 01:59:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ret > 0)
|
|
|
|
add_rchar(current, ret);
|
|
|
|
inc_syscr(current);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-03-03 16:03:59 +01:00
|
|
|
static ssize_t do_pwritev(unsigned long fd, const struct iovec __user *vec,
|
|
|
|
unsigned long vlen, loff_t pos, int flags)
|
2009-04-03 01:59:23 +02:00
|
|
|
{
|
2012-08-28 18:52:22 +02:00
|
|
|
struct fd f;
|
2009-04-03 01:59:23 +02:00
|
|
|
ssize_t ret = -EBADF;
|
|
|
|
|
|
|
|
if (pos < 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2012-08-28 18:52:22 +02:00
|
|
|
f = fdget(fd);
|
|
|
|
if (f.file) {
|
2009-04-03 01:59:23 +02:00
|
|
|
ret = -ESPIPE;
|
2012-08-28 18:52:22 +02:00
|
|
|
if (f.file->f_mode & FMODE_PWRITE)
|
2016-03-03 16:03:59 +01:00
|
|
|
ret = vfs_writev(f.file, vec, vlen, &pos, flags);
|
2012-08-28 18:52:22 +02:00
|
|
|
fdput(f);
|
2009-04-03 01:59:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ret > 0)
|
|
|
|
add_wchar(current, ret);
|
|
|
|
inc_syscw(current);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-03-03 16:03:59 +01:00
|
|
|
SYSCALL_DEFINE3(readv, unsigned long, fd, const struct iovec __user *, vec,
|
|
|
|
unsigned long, vlen)
|
|
|
|
{
|
|
|
|
return do_readv(fd, vec, vlen, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
SYSCALL_DEFINE3(writev, unsigned long, fd, const struct iovec __user *, vec,
|
|
|
|
unsigned long, vlen)
|
|
|
|
{
|
|
|
|
return do_writev(fd, vec, vlen, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
SYSCALL_DEFINE5(preadv, unsigned long, fd, const struct iovec __user *, vec,
|
|
|
|
unsigned long, vlen, unsigned long, pos_l, unsigned long, pos_h)
|
|
|
|
{
|
|
|
|
loff_t pos = pos_from_hilo(pos_h, pos_l);
|
|
|
|
|
|
|
|
return do_preadv(fd, vec, vlen, pos, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
SYSCALL_DEFINE6(preadv2, unsigned long, fd, const struct iovec __user *, vec,
|
|
|
|
unsigned long, vlen, unsigned long, pos_l, unsigned long, pos_h,
|
|
|
|
int, flags)
|
|
|
|
{
|
|
|
|
loff_t pos = pos_from_hilo(pos_h, pos_l);
|
|
|
|
|
|
|
|
if (pos == -1)
|
|
|
|
return do_readv(fd, vec, vlen, flags);
|
|
|
|
|
|
|
|
return do_preadv(fd, vec, vlen, pos, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
SYSCALL_DEFINE5(pwritev, unsigned long, fd, const struct iovec __user *, vec,
|
|
|
|
unsigned long, vlen, unsigned long, pos_l, unsigned long, pos_h)
|
|
|
|
{
|
|
|
|
loff_t pos = pos_from_hilo(pos_h, pos_l);
|
|
|
|
|
|
|
|
return do_pwritev(fd, vec, vlen, pos, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
SYSCALL_DEFINE6(pwritev2, unsigned long, fd, const struct iovec __user *, vec,
|
|
|
|
unsigned long, vlen, unsigned long, pos_l, unsigned long, pos_h,
|
|
|
|
int, flags)
|
|
|
|
{
|
|
|
|
loff_t pos = pos_from_hilo(pos_h, pos_l);
|
|
|
|
|
|
|
|
if (pos == -1)
|
|
|
|
return do_writev(fd, vec, vlen, flags);
|
|
|
|
|
|
|
|
return do_pwritev(fd, vec, vlen, pos, flags);
|
|
|
|
}
|
|
|
|
|
2013-03-20 15:42:10 +01:00
|
|
|
#ifdef CONFIG_COMPAT
|
|
|
|
|
|
|
|
static ssize_t compat_do_readv_writev(int type, struct file *file,
|
|
|
|
const struct compat_iovec __user *uvector,
|
2016-03-03 16:03:58 +01:00
|
|
|
unsigned long nr_segs, loff_t *pos,
|
|
|
|
int flags)
|
2013-03-20 15:42:10 +01:00
|
|
|
{
|
|
|
|
compat_ssize_t tot_len;
|
|
|
|
struct iovec iovstack[UIO_FASTIOV];
|
|
|
|
struct iovec *iov = iovstack;
|
2015-03-21 01:10:21 +01:00
|
|
|
struct iov_iter iter;
|
2013-03-20 15:42:10 +01:00
|
|
|
ssize_t ret;
|
|
|
|
io_fn_t fn;
|
2014-02-12 00:37:41 +01:00
|
|
|
iter_fn_t iter_fn;
|
2013-03-20 15:42:10 +01:00
|
|
|
|
2015-03-22 00:40:11 +01:00
|
|
|
ret = compat_import_iovec(type, uvector, nr_segs,
|
|
|
|
UIO_FASTIOV, &iov, &iter);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2013-03-20 15:42:10 +01:00
|
|
|
|
2015-03-22 00:40:11 +01:00
|
|
|
tot_len = iov_iter_count(&iter);
|
|
|
|
if (!tot_len)
|
|
|
|
goto out;
|
2013-03-20 15:42:10 +01:00
|
|
|
ret = rw_verify_area(type, file, pos, tot_len);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
if (type == READ) {
|
|
|
|
fn = file->f_op->read;
|
2014-02-12 00:37:41 +01:00
|
|
|
iter_fn = file->f_op->read_iter;
|
2013-03-20 15:42:10 +01:00
|
|
|
} else {
|
|
|
|
fn = (io_fn_t)file->f_op->write;
|
2014-02-12 00:37:41 +01:00
|
|
|
iter_fn = file->f_op->write_iter;
|
2013-03-20 18:04:20 +01:00
|
|
|
file_start_write(file);
|
2013-03-20 15:42:10 +01:00
|
|
|
}
|
|
|
|
|
2014-02-12 00:37:41 +01:00
|
|
|
if (iter_fn)
|
2016-03-03 16:03:58 +01:00
|
|
|
ret = do_iter_readv_writev(file, &iter, pos, iter_fn, flags);
|
2013-03-20 18:04:20 +01:00
|
|
|
else
|
2016-03-03 16:03:58 +01:00
|
|
|
ret = do_loop_readv_writev(file, &iter, pos, fn, flags);
|
2013-03-20 15:42:10 +01:00
|
|
|
|
2013-03-20 18:04:20 +01:00
|
|
|
if (type != READ)
|
|
|
|
file_end_write(file);
|
|
|
|
|
2013-03-20 15:42:10 +01:00
|
|
|
out:
|
2015-03-22 00:40:11 +01:00
|
|
|
kfree(iov);
|
2013-03-20 15:42:10 +01:00
|
|
|
if ((ret + (type == READ)) > 0) {
|
|
|
|
if (type == READ)
|
|
|
|
fsnotify_access(file);
|
|
|
|
else
|
|
|
|
fsnotify_modify(file);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t compat_readv(struct file *file,
|
|
|
|
const struct compat_iovec __user *vec,
|
2016-03-03 16:03:59 +01:00
|
|
|
unsigned long vlen, loff_t *pos, int flags)
|
2013-03-20 15:42:10 +01:00
|
|
|
{
|
|
|
|
ssize_t ret = -EBADF;
|
|
|
|
|
|
|
|
if (!(file->f_mode & FMODE_READ))
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
ret = -EINVAL;
|
2014-02-11 23:49:24 +01:00
|
|
|
if (!(file->f_mode & FMODE_CAN_READ))
|
2013-03-20 15:42:10 +01:00
|
|
|
goto out;
|
|
|
|
|
2016-03-03 16:03:59 +01:00
|
|
|
ret = compat_do_readv_writev(READ, file, vec, vlen, pos, flags);
|
2013-03-20 15:42:10 +01:00
|
|
|
|
|
|
|
out:
|
|
|
|
if (ret > 0)
|
|
|
|
add_rchar(current, ret);
|
|
|
|
inc_syscr(current);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-03-03 16:03:59 +01:00
|
|
|
static size_t do_compat_readv(compat_ulong_t fd,
|
|
|
|
const struct compat_iovec __user *vec,
|
|
|
|
compat_ulong_t vlen, int flags)
|
2013-03-20 15:42:10 +01:00
|
|
|
{
|
2014-03-03 18:36:58 +01:00
|
|
|
struct fd f = fdget_pos(fd);
|
2013-03-20 15:42:10 +01:00
|
|
|
ssize_t ret;
|
|
|
|
loff_t pos;
|
|
|
|
|
|
|
|
if (!f.file)
|
|
|
|
return -EBADF;
|
|
|
|
pos = f.file->f_pos;
|
2016-03-03 16:03:59 +01:00
|
|
|
ret = compat_readv(f.file, vec, vlen, &pos, flags);
|
2013-06-15 03:49:36 +02:00
|
|
|
if (ret >= 0)
|
|
|
|
f.file->f_pos = pos;
|
2014-03-03 18:36:58 +01:00
|
|
|
fdput_pos(f);
|
2013-03-20 15:42:10 +01:00
|
|
|
return ret;
|
2016-03-03 16:03:59 +01:00
|
|
|
|
2013-03-20 15:42:10 +01:00
|
|
|
}
|
|
|
|
|
2016-03-03 16:03:59 +01:00
|
|
|
COMPAT_SYSCALL_DEFINE3(readv, compat_ulong_t, fd,
|
|
|
|
const struct compat_iovec __user *,vec,
|
|
|
|
compat_ulong_t, vlen)
|
|
|
|
{
|
|
|
|
return do_compat_readv(fd, vec, vlen, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static long do_compat_preadv64(unsigned long fd,
|
2014-03-05 10:43:51 +01:00
|
|
|
const struct compat_iovec __user *vec,
|
2016-03-03 16:03:59 +01:00
|
|
|
unsigned long vlen, loff_t pos, int flags)
|
2013-03-20 15:42:10 +01:00
|
|
|
{
|
|
|
|
struct fd f;
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
if (pos < 0)
|
|
|
|
return -EINVAL;
|
|
|
|
f = fdget(fd);
|
|
|
|
if (!f.file)
|
|
|
|
return -EBADF;
|
|
|
|
ret = -ESPIPE;
|
|
|
|
if (f.file->f_mode & FMODE_PREAD)
|
2016-03-03 16:03:59 +01:00
|
|
|
ret = compat_readv(f.file, vec, vlen, &pos, flags);
|
2013-03-20 15:42:10 +01:00
|
|
|
fdput(f);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-05 10:43:51 +01:00
|
|
|
#ifdef __ARCH_WANT_COMPAT_SYS_PREADV64
|
|
|
|
COMPAT_SYSCALL_DEFINE4(preadv64, unsigned long, fd,
|
|
|
|
const struct compat_iovec __user *,vec,
|
|
|
|
unsigned long, vlen, loff_t, pos)
|
|
|
|
{
|
2016-03-03 16:03:59 +01:00
|
|
|
return do_compat_preadv64(fd, vec, vlen, pos, 0);
|
2014-03-05 10:43:51 +01:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2014-01-29 23:05:44 +01:00
|
|
|
COMPAT_SYSCALL_DEFINE5(preadv, compat_ulong_t, fd,
|
2013-03-20 15:42:10 +01:00
|
|
|
const struct compat_iovec __user *,vec,
|
2014-01-29 23:05:44 +01:00
|
|
|
compat_ulong_t, vlen, u32, pos_low, u32, pos_high)
|
2013-03-20 15:42:10 +01:00
|
|
|
{
|
|
|
|
loff_t pos = ((loff_t)pos_high << 32) | pos_low;
|
2014-03-05 10:43:51 +01:00
|
|
|
|
2016-03-03 16:03:59 +01:00
|
|
|
return do_compat_preadv64(fd, vec, vlen, pos, 0);
|
|
|
|
}
|
|
|
|
|
2016-07-14 21:31:53 +02:00
|
|
|
#ifdef __ARCH_WANT_COMPAT_SYS_PREADV64V2
|
|
|
|
COMPAT_SYSCALL_DEFINE5(preadv64v2, unsigned long, fd,
|
|
|
|
const struct compat_iovec __user *,vec,
|
|
|
|
unsigned long, vlen, loff_t, pos, int, flags)
|
|
|
|
{
|
|
|
|
return do_compat_preadv64(fd, vec, vlen, pos, flags);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2016-03-03 16:03:59 +01:00
|
|
|
COMPAT_SYSCALL_DEFINE6(preadv2, compat_ulong_t, fd,
|
|
|
|
const struct compat_iovec __user *,vec,
|
|
|
|
compat_ulong_t, vlen, u32, pos_low, u32, pos_high,
|
|
|
|
int, flags)
|
|
|
|
{
|
|
|
|
loff_t pos = ((loff_t)pos_high << 32) | pos_low;
|
|
|
|
|
|
|
|
if (pos == -1)
|
|
|
|
return do_compat_readv(fd, vec, vlen, flags);
|
|
|
|
|
|
|
|
return do_compat_preadv64(fd, vec, vlen, pos, flags);
|
2013-03-20 15:42:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static size_t compat_writev(struct file *file,
|
|
|
|
const struct compat_iovec __user *vec,
|
2016-03-03 16:03:59 +01:00
|
|
|
unsigned long vlen, loff_t *pos, int flags)
|
2013-03-20 15:42:10 +01:00
|
|
|
{
|
|
|
|
ssize_t ret = -EBADF;
|
|
|
|
|
|
|
|
if (!(file->f_mode & FMODE_WRITE))
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
ret = -EINVAL;
|
2014-02-11 23:49:24 +01:00
|
|
|
if (!(file->f_mode & FMODE_CAN_WRITE))
|
2013-03-20 15:42:10 +01:00
|
|
|
goto out;
|
|
|
|
|
2016-03-03 16:03:58 +01:00
|
|
|
ret = compat_do_readv_writev(WRITE, file, vec, vlen, pos, 0);
|
2013-03-20 15:42:10 +01:00
|
|
|
|
|
|
|
out:
|
|
|
|
if (ret > 0)
|
|
|
|
add_wchar(current, ret);
|
|
|
|
inc_syscw(current);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-03-03 16:03:59 +01:00
|
|
|
static size_t do_compat_writev(compat_ulong_t fd,
|
|
|
|
const struct compat_iovec __user* vec,
|
|
|
|
compat_ulong_t vlen, int flags)
|
2013-03-20 15:42:10 +01:00
|
|
|
{
|
2014-03-03 18:36:58 +01:00
|
|
|
struct fd f = fdget_pos(fd);
|
2013-03-20 15:42:10 +01:00
|
|
|
ssize_t ret;
|
|
|
|
loff_t pos;
|
|
|
|
|
|
|
|
if (!f.file)
|
|
|
|
return -EBADF;
|
|
|
|
pos = f.file->f_pos;
|
2016-03-03 16:03:59 +01:00
|
|
|
ret = compat_writev(f.file, vec, vlen, &pos, flags);
|
2013-06-15 03:49:36 +02:00
|
|
|
if (ret >= 0)
|
|
|
|
f.file->f_pos = pos;
|
2014-03-03 18:36:58 +01:00
|
|
|
fdput_pos(f);
|
2013-03-20 15:42:10 +01:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-03-03 16:03:59 +01:00
|
|
|
COMPAT_SYSCALL_DEFINE3(writev, compat_ulong_t, fd,
|
|
|
|
const struct compat_iovec __user *, vec,
|
|
|
|
compat_ulong_t, vlen)
|
|
|
|
{
|
|
|
|
return do_compat_writev(fd, vec, vlen, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static long do_compat_pwritev64(unsigned long fd,
|
2014-03-05 10:43:51 +01:00
|
|
|
const struct compat_iovec __user *vec,
|
2016-03-03 16:03:59 +01:00
|
|
|
unsigned long vlen, loff_t pos, int flags)
|
2013-03-20 15:42:10 +01:00
|
|
|
{
|
|
|
|
struct fd f;
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
if (pos < 0)
|
|
|
|
return -EINVAL;
|
|
|
|
f = fdget(fd);
|
|
|
|
if (!f.file)
|
|
|
|
return -EBADF;
|
|
|
|
ret = -ESPIPE;
|
|
|
|
if (f.file->f_mode & FMODE_PWRITE)
|
2016-03-03 16:03:59 +01:00
|
|
|
ret = compat_writev(f.file, vec, vlen, &pos, flags);
|
2013-03-20 15:42:10 +01:00
|
|
|
fdput(f);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-05 10:43:51 +01:00
|
|
|
#ifdef __ARCH_WANT_COMPAT_SYS_PWRITEV64
|
|
|
|
COMPAT_SYSCALL_DEFINE4(pwritev64, unsigned long, fd,
|
|
|
|
const struct compat_iovec __user *,vec,
|
|
|
|
unsigned long, vlen, loff_t, pos)
|
|
|
|
{
|
2016-03-03 16:03:59 +01:00
|
|
|
return do_compat_pwritev64(fd, vec, vlen, pos, 0);
|
2014-03-05 10:43:51 +01:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2014-01-29 23:05:44 +01:00
|
|
|
COMPAT_SYSCALL_DEFINE5(pwritev, compat_ulong_t, fd,
|
2013-03-20 15:42:10 +01:00
|
|
|
const struct compat_iovec __user *,vec,
|
2014-01-29 23:05:44 +01:00
|
|
|
compat_ulong_t, vlen, u32, pos_low, u32, pos_high)
|
2013-03-20 15:42:10 +01:00
|
|
|
{
|
|
|
|
loff_t pos = ((loff_t)pos_high << 32) | pos_low;
|
2014-03-05 10:43:51 +01:00
|
|
|
|
2016-03-03 16:03:59 +01:00
|
|
|
return do_compat_pwritev64(fd, vec, vlen, pos, 0);
|
2013-03-20 15:42:10 +01:00
|
|
|
}
|
2016-03-03 16:03:59 +01:00
|
|
|
|
2016-07-14 21:31:53 +02:00
|
|
|
#ifdef __ARCH_WANT_COMPAT_SYS_PWRITEV64V2
|
|
|
|
COMPAT_SYSCALL_DEFINE5(pwritev64v2, unsigned long, fd,
|
|
|
|
const struct compat_iovec __user *,vec,
|
|
|
|
unsigned long, vlen, loff_t, pos, int, flags)
|
|
|
|
{
|
|
|
|
return do_compat_pwritev64(fd, vec, vlen, pos, flags);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2016-03-03 16:03:59 +01:00
|
|
|
COMPAT_SYSCALL_DEFINE6(pwritev2, compat_ulong_t, fd,
|
|
|
|
const struct compat_iovec __user *,vec,
|
|
|
|
compat_ulong_t, vlen, u32, pos_low, u32, pos_high, int, flags)
|
|
|
|
{
|
|
|
|
loff_t pos = ((loff_t)pos_high << 32) | pos_low;
|
|
|
|
|
|
|
|
if (pos == -1)
|
|
|
|
return do_compat_writev(fd, vec, vlen, flags);
|
|
|
|
|
|
|
|
return do_compat_pwritev64(fd, vec, vlen, pos, flags);
|
2013-03-20 15:42:10 +01:00
|
|
|
}
|
2016-03-03 16:03:59 +01:00
|
|
|
|
2013-03-20 15:42:10 +01:00
|
|
|
#endif
|
|
|
|
|
2013-02-24 08:17:03 +01:00
|
|
|
static ssize_t do_sendfile(int out_fd, int in_fd, loff_t *ppos,
|
|
|
|
size_t count, loff_t max)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
2012-08-28 18:52:22 +02:00
|
|
|
struct fd in, out;
|
|
|
|
struct inode *in_inode, *out_inode;
|
2005-04-17 00:20:36 +02:00
|
|
|
loff_t pos;
|
2013-06-20 16:58:36 +02:00
|
|
|
loff_t out_pos;
|
2005-04-17 00:20:36 +02:00
|
|
|
ssize_t retval;
|
2012-08-28 18:52:22 +02:00
|
|
|
int fl;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Get input file, and verify that it is ok..
|
|
|
|
*/
|
|
|
|
retval = -EBADF;
|
2012-08-28 18:52:22 +02:00
|
|
|
in = fdget(in_fd);
|
|
|
|
if (!in.file)
|
2005-04-17 00:20:36 +02:00
|
|
|
goto out;
|
2012-08-28 18:52:22 +02:00
|
|
|
if (!(in.file->f_mode & FMODE_READ))
|
2005-04-17 00:20:36 +02:00
|
|
|
goto fput_in;
|
|
|
|
retval = -ESPIPE;
|
2013-06-20 16:58:36 +02:00
|
|
|
if (!ppos) {
|
|
|
|
pos = in.file->f_pos;
|
|
|
|
} else {
|
|
|
|
pos = *ppos;
|
2012-08-28 18:52:22 +02:00
|
|
|
if (!(in.file->f_mode & FMODE_PREAD))
|
2005-04-17 00:20:36 +02:00
|
|
|
goto fput_in;
|
2013-06-20 16:58:36 +02:00
|
|
|
}
|
|
|
|
retval = rw_verify_area(READ, in.file, &pos, count);
|
2006-01-05 01:20:40 +01:00
|
|
|
if (retval < 0)
|
2005-04-17 00:20:36 +02:00
|
|
|
goto fput_in;
|
2016-04-01 03:48:20 +02:00
|
|
|
if (count > MAX_RW_COUNT)
|
|
|
|
count = MAX_RW_COUNT;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Get output file, and verify that it is ok..
|
|
|
|
*/
|
|
|
|
retval = -EBADF;
|
2012-08-28 18:52:22 +02:00
|
|
|
out = fdget(out_fd);
|
|
|
|
if (!out.file)
|
2005-04-17 00:20:36 +02:00
|
|
|
goto fput_in;
|
2012-08-28 18:52:22 +02:00
|
|
|
if (!(out.file->f_mode & FMODE_WRITE))
|
2005-04-17 00:20:36 +02:00
|
|
|
goto fput_out;
|
|
|
|
retval = -EINVAL;
|
2013-01-23 23:07:38 +01:00
|
|
|
in_inode = file_inode(in.file);
|
|
|
|
out_inode = file_inode(out.file);
|
2013-06-20 16:58:36 +02:00
|
|
|
out_pos = out.file->f_pos;
|
|
|
|
retval = rw_verify_area(WRITE, out.file, &out_pos, count);
|
2006-01-05 01:20:40 +01:00
|
|
|
if (retval < 0)
|
2005-04-17 00:20:36 +02:00
|
|
|
goto fput_out;
|
|
|
|
|
|
|
|
if (!max)
|
|
|
|
max = min(in_inode->i_sb->s_maxbytes, out_inode->i_sb->s_maxbytes);
|
|
|
|
|
|
|
|
if (unlikely(pos + count > max)) {
|
|
|
|
retval = -EOVERFLOW;
|
|
|
|
if (pos >= max)
|
|
|
|
goto fput_out;
|
|
|
|
count = max - pos;
|
|
|
|
}
|
|
|
|
|
2007-06-11 12:18:52 +02:00
|
|
|
fl = 0;
|
2007-06-01 14:52:37 +02:00
|
|
|
#if 0
|
2007-06-11 12:18:52 +02:00
|
|
|
/*
|
|
|
|
* We need to debate whether we can enable this or not. The
|
|
|
|
* man page documents EAGAIN return for the output at least,
|
|
|
|
* and the application is arguably buggy if it doesn't expect
|
|
|
|
* EAGAIN on a non-blocking file descriptor.
|
|
|
|
*/
|
2012-08-28 18:52:22 +02:00
|
|
|
if (in.file->f_flags & O_NONBLOCK)
|
2007-06-11 12:18:52 +02:00
|
|
|
fl = SPLICE_F_NONBLOCK;
|
2007-06-01 14:52:37 +02:00
|
|
|
#endif
|
2013-05-24 02:10:34 +02:00
|
|
|
file_start_write(out.file);
|
2013-06-20 16:58:36 +02:00
|
|
|
retval = do_splice_direct(in.file, &pos, out.file, &out_pos, count, fl);
|
2013-05-24 02:10:34 +02:00
|
|
|
file_end_write(out.file);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if (retval > 0) {
|
[PATCH] ifdef ->rchar, ->wchar, ->syscr, ->syscw from task_struct
They are fat: 4x8 bytes in task_struct.
They are uncoditionally updated in every fork, read, write and sendfile.
They are used only if you have some "extended acct fields feature".
And please, please, please, read(2) knows about bytes, not characters,
why it is called "rchar"?
Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
Cc: Jay Lan <jlan@engr.sgi.com>
Cc: Balbir Singh <balbir@in.ibm.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-02-10 10:46:45 +01:00
|
|
|
add_rchar(current, retval);
|
|
|
|
add_wchar(current, retval);
|
2012-12-21 00:05:52 +01:00
|
|
|
fsnotify_access(in.file);
|
|
|
|
fsnotify_modify(out.file);
|
2013-06-20 16:58:36 +02:00
|
|
|
out.file->f_pos = out_pos;
|
|
|
|
if (ppos)
|
|
|
|
*ppos = pos;
|
|
|
|
else
|
|
|
|
in.file->f_pos = pos;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
[PATCH] ifdef ->rchar, ->wchar, ->syscr, ->syscw from task_struct
They are fat: 4x8 bytes in task_struct.
They are uncoditionally updated in every fork, read, write and sendfile.
They are used only if you have some "extended acct fields feature".
And please, please, please, read(2) knows about bytes, not characters,
why it is called "rchar"?
Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
Cc: Jay Lan <jlan@engr.sgi.com>
Cc: Balbir Singh <balbir@in.ibm.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-02-10 10:46:45 +01:00
|
|
|
inc_syscr(current);
|
|
|
|
inc_syscw(current);
|
2013-06-20 16:58:36 +02:00
|
|
|
if (pos > max)
|
2005-04-17 00:20:36 +02:00
|
|
|
retval = -EOVERFLOW;
|
|
|
|
|
|
|
|
fput_out:
|
2012-08-28 18:52:22 +02:00
|
|
|
fdput(out);
|
2005-04-17 00:20:36 +02:00
|
|
|
fput_in:
|
2012-08-28 18:52:22 +02:00
|
|
|
fdput(in);
|
2005-04-17 00:20:36 +02:00
|
|
|
out:
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2009-01-14 14:14:18 +01:00
|
|
|
SYSCALL_DEFINE4(sendfile, int, out_fd, int, in_fd, off_t __user *, offset, size_t, count)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
loff_t pos;
|
|
|
|
off_t off;
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
if (offset) {
|
|
|
|
if (unlikely(get_user(off, offset)))
|
|
|
|
return -EFAULT;
|
|
|
|
pos = off;
|
|
|
|
ret = do_sendfile(out_fd, in_fd, &pos, count, MAX_NON_LFS);
|
|
|
|
if (unlikely(put_user(pos, offset)))
|
|
|
|
return -EFAULT;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return do_sendfile(out_fd, in_fd, NULL, count, 0);
|
|
|
|
}
|
|
|
|
|
2009-01-14 14:14:18 +01:00
|
|
|
SYSCALL_DEFINE4(sendfile64, int, out_fd, int, in_fd, loff_t __user *, offset, size_t, count)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
loff_t pos;
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
if (offset) {
|
|
|
|
if (unlikely(copy_from_user(&pos, offset, sizeof(loff_t))))
|
|
|
|
return -EFAULT;
|
|
|
|
ret = do_sendfile(out_fd, in_fd, &pos, count, 0);
|
|
|
|
if (unlikely(put_user(pos, offset)))
|
|
|
|
return -EFAULT;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return do_sendfile(out_fd, in_fd, NULL, count, 0);
|
|
|
|
}
|
2013-02-24 08:17:03 +01:00
|
|
|
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
|
|
COMPAT_SYSCALL_DEFINE4(sendfile, int, out_fd, int, in_fd,
|
|
|
|
compat_off_t __user *, offset, compat_size_t, count)
|
|
|
|
{
|
|
|
|
loff_t pos;
|
|
|
|
off_t off;
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
if (offset) {
|
|
|
|
if (unlikely(get_user(off, offset)))
|
|
|
|
return -EFAULT;
|
|
|
|
pos = off;
|
|
|
|
ret = do_sendfile(out_fd, in_fd, &pos, count, MAX_NON_LFS);
|
|
|
|
if (unlikely(put_user(pos, offset)))
|
|
|
|
return -EFAULT;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return do_sendfile(out_fd, in_fd, NULL, count, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
COMPAT_SYSCALL_DEFINE4(sendfile64, int, out_fd, int, in_fd,
|
|
|
|
compat_loff_t __user *, offset, compat_size_t, count)
|
|
|
|
{
|
|
|
|
loff_t pos;
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
if (offset) {
|
|
|
|
if (unlikely(copy_from_user(&pos, offset, sizeof(loff_t))))
|
|
|
|
return -EFAULT;
|
|
|
|
ret = do_sendfile(out_fd, in_fd, &pos, count, 0);
|
|
|
|
if (unlikely(put_user(pos, offset)))
|
|
|
|
return -EFAULT;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return do_sendfile(out_fd, in_fd, NULL, count, 0);
|
|
|
|
}
|
|
|
|
#endif
|
2015-11-10 22:53:30 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* copy_file_range() differs from regular file read and write in that it
|
|
|
|
* specifically allows return partial success. When it does so is up to
|
|
|
|
* the copy_file_range method.
|
|
|
|
*/
|
|
|
|
ssize_t vfs_copy_file_range(struct file *file_in, loff_t pos_in,
|
|
|
|
struct file *file_out, loff_t pos_out,
|
|
|
|
size_t len, unsigned int flags)
|
|
|
|
{
|
|
|
|
struct inode *inode_in = file_inode(file_in);
|
|
|
|
struct inode *inode_out = file_inode(file_out);
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
if (flags != 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
ret = rw_verify_area(READ, file_in, &pos_in, len);
|
2016-04-01 03:48:20 +02:00
|
|
|
if (unlikely(ret))
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = rw_verify_area(WRITE, file_out, &pos_out, len);
|
|
|
|
if (unlikely(ret))
|
2015-11-10 22:53:30 +01:00
|
|
|
return ret;
|
|
|
|
|
|
|
|
if (!(file_in->f_mode & FMODE_READ) ||
|
|
|
|
!(file_out->f_mode & FMODE_WRITE) ||
|
2015-11-10 22:53:33 +01:00
|
|
|
(file_out->f_flags & O_APPEND))
|
2015-11-10 22:53:30 +01:00
|
|
|
return -EBADF;
|
|
|
|
|
|
|
|
/* this could be relaxed once a method supports cross-fs copies */
|
|
|
|
if (inode_in->i_sb != inode_out->i_sb)
|
|
|
|
return -EXDEV;
|
|
|
|
|
|
|
|
if (len == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
ret = mnt_want_write_file(file_out);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2015-11-10 22:53:33 +01:00
|
|
|
ret = -EOPNOTSUPP;
|
|
|
|
if (file_out->f_op->copy_file_range)
|
|
|
|
ret = file_out->f_op->copy_file_range(file_in, pos_in, file_out,
|
|
|
|
pos_out, len, flags);
|
|
|
|
if (ret == -EOPNOTSUPP)
|
|
|
|
ret = do_splice_direct(file_in, &pos_in, file_out, &pos_out,
|
|
|
|
len > MAX_RW_COUNT ? MAX_RW_COUNT : len, 0);
|
|
|
|
|
2015-11-10 22:53:30 +01:00
|
|
|
if (ret > 0) {
|
|
|
|
fsnotify_access(file_in);
|
|
|
|
add_rchar(current, ret);
|
|
|
|
fsnotify_modify(file_out);
|
|
|
|
add_wchar(current, ret);
|
|
|
|
}
|
|
|
|
inc_syscr(current);
|
|
|
|
inc_syscw(current);
|
|
|
|
|
|
|
|
mnt_drop_write_file(file_out);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(vfs_copy_file_range);
|
|
|
|
|
|
|
|
SYSCALL_DEFINE6(copy_file_range, int, fd_in, loff_t __user *, off_in,
|
|
|
|
int, fd_out, loff_t __user *, off_out,
|
|
|
|
size_t, len, unsigned int, flags)
|
|
|
|
{
|
|
|
|
loff_t pos_in;
|
|
|
|
loff_t pos_out;
|
|
|
|
struct fd f_in;
|
|
|
|
struct fd f_out;
|
|
|
|
ssize_t ret = -EBADF;
|
|
|
|
|
|
|
|
f_in = fdget(fd_in);
|
|
|
|
if (!f_in.file)
|
|
|
|
goto out2;
|
|
|
|
|
|
|
|
f_out = fdget(fd_out);
|
|
|
|
if (!f_out.file)
|
|
|
|
goto out1;
|
|
|
|
|
|
|
|
ret = -EFAULT;
|
|
|
|
if (off_in) {
|
|
|
|
if (copy_from_user(&pos_in, off_in, sizeof(loff_t)))
|
|
|
|
goto out;
|
|
|
|
} else {
|
|
|
|
pos_in = f_in.file->f_pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (off_out) {
|
|
|
|
if (copy_from_user(&pos_out, off_out, sizeof(loff_t)))
|
|
|
|
goto out;
|
|
|
|
} else {
|
|
|
|
pos_out = f_out.file->f_pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = vfs_copy_file_range(f_in.file, pos_in, f_out.file, pos_out, len,
|
|
|
|
flags);
|
|
|
|
if (ret > 0) {
|
|
|
|
pos_in += ret;
|
|
|
|
pos_out += ret;
|
|
|
|
|
|
|
|
if (off_in) {
|
|
|
|
if (copy_to_user(off_in, &pos_in, sizeof(loff_t)))
|
|
|
|
ret = -EFAULT;
|
|
|
|
} else {
|
|
|
|
f_in.file->f_pos = pos_in;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (off_out) {
|
|
|
|
if (copy_to_user(off_out, &pos_out, sizeof(loff_t)))
|
|
|
|
ret = -EFAULT;
|
|
|
|
} else {
|
|
|
|
f_out.file->f_pos = pos_out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
fdput(f_out);
|
|
|
|
out1:
|
|
|
|
fdput(f_in);
|
|
|
|
out2:
|
|
|
|
return ret;
|
|
|
|
}
|
2015-12-03 12:59:50 +01:00
|
|
|
|
|
|
|
static int clone_verify_area(struct file *file, loff_t pos, u64 len, bool write)
|
|
|
|
{
|
|
|
|
struct inode *inode = file_inode(file);
|
|
|
|
|
|
|
|
if (unlikely(pos < 0))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (unlikely((loff_t) (pos + len) < 0))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (unlikely(inode->i_flctx && mandatory_lock(inode))) {
|
|
|
|
loff_t end = len ? pos + len - 1 : OFFSET_MAX;
|
|
|
|
int retval;
|
|
|
|
|
|
|
|
retval = locks_mandatory_area(inode, file, pos, end,
|
|
|
|
write ? F_WRLCK : F_RDLCK);
|
|
|
|
if (retval < 0)
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
return security_file_permission(file, write ? MAY_WRITE : MAY_READ);
|
|
|
|
}
|
|
|
|
|
|
|
|
int vfs_clone_file_range(struct file *file_in, loff_t pos_in,
|
|
|
|
struct file *file_out, loff_t pos_out, u64 len)
|
|
|
|
{
|
|
|
|
struct inode *inode_in = file_inode(file_in);
|
|
|
|
struct inode *inode_out = file_inode(file_out);
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (inode_in->i_sb != inode_out->i_sb ||
|
|
|
|
file_in->f_path.mnt != file_out->f_path.mnt)
|
|
|
|
return -EXDEV;
|
|
|
|
|
|
|
|
if (S_ISDIR(inode_in->i_mode) || S_ISDIR(inode_out->i_mode))
|
|
|
|
return -EISDIR;
|
|
|
|
if (!S_ISREG(inode_in->i_mode) || !S_ISREG(inode_out->i_mode))
|
2015-12-19 09:55:52 +01:00
|
|
|
return -EINVAL;
|
2015-12-03 12:59:50 +01:00
|
|
|
|
|
|
|
if (!(file_in->f_mode & FMODE_READ) ||
|
|
|
|
!(file_out->f_mode & FMODE_WRITE) ||
|
2016-02-26 18:53:12 +01:00
|
|
|
(file_out->f_flags & O_APPEND))
|
2015-12-03 12:59:50 +01:00
|
|
|
return -EBADF;
|
|
|
|
|
2016-02-26 18:53:12 +01:00
|
|
|
if (!file_in->f_op->clone_file_range)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
2015-12-03 12:59:50 +01:00
|
|
|
ret = clone_verify_area(file_in, pos_in, len, false);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = clone_verify_area(file_out, pos_out, len, true);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if (pos_in + len > i_size_read(inode_in))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
ret = mnt_want_write_file(file_out);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = file_in->f_op->clone_file_range(file_in, pos_in,
|
|
|
|
file_out, pos_out, len);
|
|
|
|
if (!ret) {
|
|
|
|
fsnotify_access(file_in);
|
|
|
|
fsnotify_modify(file_out);
|
|
|
|
}
|
|
|
|
|
|
|
|
mnt_drop_write_file(file_out);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(vfs_clone_file_range);
|
2015-12-19 09:55:59 +01:00
|
|
|
|
|
|
|
int vfs_dedupe_file_range(struct file *file, struct file_dedupe_range *same)
|
|
|
|
{
|
|
|
|
struct file_dedupe_range_info *info;
|
|
|
|
struct inode *src = file_inode(file);
|
|
|
|
u64 off;
|
|
|
|
u64 len;
|
|
|
|
int i;
|
|
|
|
int ret;
|
|
|
|
bool is_admin = capable(CAP_SYS_ADMIN);
|
|
|
|
u16 count = same->dest_count;
|
|
|
|
struct file *dst_file;
|
|
|
|
loff_t dst_off;
|
|
|
|
ssize_t deduped;
|
|
|
|
|
|
|
|
if (!(file->f_mode & FMODE_READ))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (same->reserved1 || same->reserved2)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
off = same->src_offset;
|
|
|
|
len = same->src_length;
|
|
|
|
|
|
|
|
ret = -EISDIR;
|
|
|
|
if (S_ISDIR(src->i_mode))
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
ret = -EINVAL;
|
|
|
|
if (!S_ISREG(src->i_mode))
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
ret = clone_verify_area(file, off, len, false);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out;
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
/* pre-format output fields to sane values */
|
|
|
|
for (i = 0; i < count; i++) {
|
|
|
|
same->info[i].bytes_deduped = 0ULL;
|
|
|
|
same->info[i].status = FILE_DEDUPE_RANGE_SAME;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0, info = same->info; i < count; i++, info++) {
|
|
|
|
struct inode *dst;
|
|
|
|
struct fd dst_fd = fdget(info->dest_fd);
|
|
|
|
|
|
|
|
dst_file = dst_fd.file;
|
|
|
|
if (!dst_file) {
|
|
|
|
info->status = -EBADF;
|
|
|
|
goto next_loop;
|
|
|
|
}
|
|
|
|
dst = file_inode(dst_file);
|
|
|
|
|
|
|
|
ret = mnt_want_write_file(dst_file);
|
|
|
|
if (ret) {
|
|
|
|
info->status = ret;
|
|
|
|
goto next_loop;
|
|
|
|
}
|
|
|
|
|
|
|
|
dst_off = info->dest_offset;
|
|
|
|
ret = clone_verify_area(dst_file, dst_off, len, true);
|
|
|
|
if (ret < 0) {
|
|
|
|
info->status = ret;
|
|
|
|
goto next_file;
|
|
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
if (info->reserved) {
|
|
|
|
info->status = -EINVAL;
|
|
|
|
} else if (!(is_admin || (dst_file->f_mode & FMODE_WRITE))) {
|
|
|
|
info->status = -EINVAL;
|
|
|
|
} else if (file->f_path.mnt != dst_file->f_path.mnt) {
|
|
|
|
info->status = -EXDEV;
|
|
|
|
} else if (S_ISDIR(dst->i_mode)) {
|
|
|
|
info->status = -EISDIR;
|
|
|
|
} else if (dst_file->f_op->dedupe_file_range == NULL) {
|
|
|
|
info->status = -EINVAL;
|
|
|
|
} else {
|
|
|
|
deduped = dst_file->f_op->dedupe_file_range(file, off,
|
|
|
|
len, dst_file,
|
|
|
|
info->dest_offset);
|
|
|
|
if (deduped == -EBADE)
|
|
|
|
info->status = FILE_DEDUPE_RANGE_DIFFERS;
|
|
|
|
else if (deduped < 0)
|
|
|
|
info->status = deduped;
|
|
|
|
else
|
|
|
|
info->bytes_deduped += deduped;
|
|
|
|
}
|
|
|
|
|
|
|
|
next_file:
|
|
|
|
mnt_drop_write_file(dst_file);
|
|
|
|
next_loop:
|
|
|
|
fdput(dst_fd);
|
2016-01-23 01:58:28 +01:00
|
|
|
|
|
|
|
if (fatal_signal_pending(current))
|
|
|
|
goto out;
|
2015-12-19 09:55:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(vfs_dedupe_file_range);
|