mirror of
https://github.com/torvalds/linux.git
synced 2024-11-05 03:21:32 +00:00
Merge branch 'splice' of git://brick.kernel.dk/data/git/linux-2.6-block
* 'splice' of git://brick.kernel.dk/data/git/linux-2.6-block: [PATCH] vmsplice: allow user to pass in gift pages [PATCH] pipe: enable atomic copying of pipe data to/from user space [PATCH] splice: call handle_ra_miss() on failure to lookup page [PATCH] Add ->splice_read/splice_write to def_blk_fops [PATCH] pipe: introduce ->pin() buffer operation [PATCH] splice: fix bugs in pipe_to_file() [PATCH] splice: fix bugs with stealing regular pipe pages
This commit is contained in:
commit
9817d207dc
@ -1104,6 +1104,8 @@ const struct file_operations def_blk_fops = {
|
|||||||
.readv = generic_file_readv,
|
.readv = generic_file_readv,
|
||||||
.writev = generic_file_write_nolock,
|
.writev = generic_file_write_nolock,
|
||||||
.sendfile = generic_file_sendfile,
|
.sendfile = generic_file_sendfile,
|
||||||
|
.splice_read = generic_file_splice_read,
|
||||||
|
.splice_write = generic_file_splice_write,
|
||||||
};
|
};
|
||||||
|
|
||||||
int ioctl_by_bdev(struct block_device *bdev, unsigned cmd, unsigned long arg)
|
int ioctl_by_bdev(struct block_device *bdev, unsigned cmd, unsigned long arg)
|
||||||
|
190
fs/pipe.c
190
fs/pipe.c
@ -55,7 +55,8 @@ void pipe_wait(struct pipe_inode_info *pipe)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
pipe_iov_copy_from_user(void *to, struct iovec *iov, unsigned long len)
|
pipe_iov_copy_from_user(void *to, struct iovec *iov, unsigned long len,
|
||||||
|
int atomic)
|
||||||
{
|
{
|
||||||
unsigned long copy;
|
unsigned long copy;
|
||||||
|
|
||||||
@ -64,8 +65,13 @@ pipe_iov_copy_from_user(void *to, struct iovec *iov, unsigned long len)
|
|||||||
iov++;
|
iov++;
|
||||||
copy = min_t(unsigned long, len, iov->iov_len);
|
copy = min_t(unsigned long, len, iov->iov_len);
|
||||||
|
|
||||||
if (copy_from_user(to, iov->iov_base, copy))
|
if (atomic) {
|
||||||
return -EFAULT;
|
if (__copy_from_user_inatomic(to, iov->iov_base, copy))
|
||||||
|
return -EFAULT;
|
||||||
|
} else {
|
||||||
|
if (copy_from_user(to, iov->iov_base, copy))
|
||||||
|
return -EFAULT;
|
||||||
|
}
|
||||||
to += copy;
|
to += copy;
|
||||||
len -= copy;
|
len -= copy;
|
||||||
iov->iov_base += copy;
|
iov->iov_base += copy;
|
||||||
@ -75,7 +81,8 @@ pipe_iov_copy_from_user(void *to, struct iovec *iov, unsigned long len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
pipe_iov_copy_to_user(struct iovec *iov, const void *from, unsigned long len)
|
pipe_iov_copy_to_user(struct iovec *iov, const void *from, unsigned long len,
|
||||||
|
int atomic)
|
||||||
{
|
{
|
||||||
unsigned long copy;
|
unsigned long copy;
|
||||||
|
|
||||||
@ -84,8 +91,13 @@ pipe_iov_copy_to_user(struct iovec *iov, const void *from, unsigned long len)
|
|||||||
iov++;
|
iov++;
|
||||||
copy = min_t(unsigned long, len, iov->iov_len);
|
copy = min_t(unsigned long, len, iov->iov_len);
|
||||||
|
|
||||||
if (copy_to_user(iov->iov_base, from, copy))
|
if (atomic) {
|
||||||
return -EFAULT;
|
if (__copy_to_user_inatomic(iov->iov_base, from, copy))
|
||||||
|
return -EFAULT;
|
||||||
|
} else {
|
||||||
|
if (copy_to_user(iov->iov_base, from, copy))
|
||||||
|
return -EFAULT;
|
||||||
|
}
|
||||||
from += copy;
|
from += copy;
|
||||||
len -= copy;
|
len -= copy;
|
||||||
iov->iov_base += copy;
|
iov->iov_base += copy;
|
||||||
@ -94,13 +106,52 @@ pipe_iov_copy_to_user(struct iovec *iov, const void *from, unsigned long len)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Attempt to pre-fault in the user memory, so we can use atomic copies.
|
||||||
|
* Returns the number of bytes not faulted in.
|
||||||
|
*/
|
||||||
|
static int iov_fault_in_pages_write(struct iovec *iov, unsigned long len)
|
||||||
|
{
|
||||||
|
while (!iov->iov_len)
|
||||||
|
iov++;
|
||||||
|
|
||||||
|
while (len > 0) {
|
||||||
|
unsigned long this_len;
|
||||||
|
|
||||||
|
this_len = min_t(unsigned long, len, iov->iov_len);
|
||||||
|
if (fault_in_pages_writeable(iov->iov_base, this_len))
|
||||||
|
break;
|
||||||
|
|
||||||
|
len -= this_len;
|
||||||
|
iov++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pre-fault in the user memory, so we can use atomic copies.
|
||||||
|
*/
|
||||||
|
static void iov_fault_in_pages_read(struct iovec *iov, unsigned long len)
|
||||||
|
{
|
||||||
|
while (!iov->iov_len)
|
||||||
|
iov++;
|
||||||
|
|
||||||
|
while (len > 0) {
|
||||||
|
unsigned long this_len;
|
||||||
|
|
||||||
|
this_len = min_t(unsigned long, len, iov->iov_len);
|
||||||
|
fault_in_pages_readable(iov->iov_base, this_len);
|
||||||
|
len -= this_len;
|
||||||
|
iov++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void anon_pipe_buf_release(struct pipe_inode_info *pipe,
|
static void anon_pipe_buf_release(struct pipe_inode_info *pipe,
|
||||||
struct pipe_buffer *buf)
|
struct pipe_buffer *buf)
|
||||||
{
|
{
|
||||||
struct page *page = buf->page;
|
struct page *page = buf->page;
|
||||||
|
|
||||||
buf->flags &= ~PIPE_BUF_FLAG_STOLEN;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If nobody else uses this page, and we don't already have a
|
* If nobody else uses this page, and we don't already have a
|
||||||
* temporary page, let's keep track of it as a one-deep
|
* temporary page, let's keep track of it as a one-deep
|
||||||
@ -112,38 +163,58 @@ static void anon_pipe_buf_release(struct pipe_inode_info *pipe,
|
|||||||
page_cache_release(page);
|
page_cache_release(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void * anon_pipe_buf_map(struct file *file, struct pipe_inode_info *pipe,
|
void *generic_pipe_buf_map(struct pipe_inode_info *pipe,
|
||||||
struct pipe_buffer *buf)
|
struct pipe_buffer *buf, int atomic)
|
||||||
{
|
{
|
||||||
|
if (atomic) {
|
||||||
|
buf->flags |= PIPE_BUF_FLAG_ATOMIC;
|
||||||
|
return kmap_atomic(buf->page, KM_USER0);
|
||||||
|
}
|
||||||
|
|
||||||
return kmap(buf->page);
|
return kmap(buf->page);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void anon_pipe_buf_unmap(struct pipe_inode_info *pipe,
|
void generic_pipe_buf_unmap(struct pipe_inode_info *pipe,
|
||||||
struct pipe_buffer *buf)
|
struct pipe_buffer *buf, void *map_data)
|
||||||
{
|
{
|
||||||
kunmap(buf->page);
|
if (buf->flags & PIPE_BUF_FLAG_ATOMIC) {
|
||||||
|
buf->flags &= ~PIPE_BUF_FLAG_ATOMIC;
|
||||||
|
kunmap_atomic(map_data, KM_USER0);
|
||||||
|
} else
|
||||||
|
kunmap(buf->page);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int anon_pipe_buf_steal(struct pipe_inode_info *pipe,
|
static int anon_pipe_buf_steal(struct pipe_inode_info *pipe,
|
||||||
struct pipe_buffer *buf)
|
struct pipe_buffer *buf)
|
||||||
{
|
{
|
||||||
buf->flags |= PIPE_BUF_FLAG_STOLEN;
|
struct page *page = buf->page;
|
||||||
return 0;
|
|
||||||
|
if (page_count(page) == 1) {
|
||||||
|
lock_page(page);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void anon_pipe_buf_get(struct pipe_inode_info *info,
|
void generic_pipe_buf_get(struct pipe_inode_info *info, struct pipe_buffer *buf)
|
||||||
struct pipe_buffer *buf)
|
|
||||||
{
|
{
|
||||||
page_cache_get(buf->page);
|
page_cache_get(buf->page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int generic_pipe_buf_pin(struct pipe_inode_info *info, struct pipe_buffer *buf)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static struct pipe_buf_operations anon_pipe_buf_ops = {
|
static struct pipe_buf_operations anon_pipe_buf_ops = {
|
||||||
.can_merge = 1,
|
.can_merge = 1,
|
||||||
.map = anon_pipe_buf_map,
|
.map = generic_pipe_buf_map,
|
||||||
.unmap = anon_pipe_buf_unmap,
|
.unmap = generic_pipe_buf_unmap,
|
||||||
|
.pin = generic_pipe_buf_pin,
|
||||||
.release = anon_pipe_buf_release,
|
.release = anon_pipe_buf_release,
|
||||||
.steal = anon_pipe_buf_steal,
|
.steal = anon_pipe_buf_steal,
|
||||||
.get = anon_pipe_buf_get,
|
.get = generic_pipe_buf_get,
|
||||||
};
|
};
|
||||||
|
|
||||||
static ssize_t
|
static ssize_t
|
||||||
@ -174,22 +245,33 @@ pipe_readv(struct file *filp, const struct iovec *_iov,
|
|||||||
struct pipe_buf_operations *ops = buf->ops;
|
struct pipe_buf_operations *ops = buf->ops;
|
||||||
void *addr;
|
void *addr;
|
||||||
size_t chars = buf->len;
|
size_t chars = buf->len;
|
||||||
int error;
|
int error, atomic;
|
||||||
|
|
||||||
if (chars > total_len)
|
if (chars > total_len)
|
||||||
chars = total_len;
|
chars = total_len;
|
||||||
|
|
||||||
addr = ops->map(filp, pipe, buf);
|
error = ops->pin(pipe, buf);
|
||||||
if (IS_ERR(addr)) {
|
if (error) {
|
||||||
if (!ret)
|
if (!ret)
|
||||||
ret = PTR_ERR(addr);
|
error = ret;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
error = pipe_iov_copy_to_user(iov, addr + buf->offset, chars);
|
|
||||||
ops->unmap(pipe, buf);
|
atomic = !iov_fault_in_pages_write(iov, chars);
|
||||||
|
redo:
|
||||||
|
addr = ops->map(pipe, buf, atomic);
|
||||||
|
error = pipe_iov_copy_to_user(iov, addr + buf->offset, chars, atomic);
|
||||||
|
ops->unmap(pipe, buf, addr);
|
||||||
if (unlikely(error)) {
|
if (unlikely(error)) {
|
||||||
|
/*
|
||||||
|
* Just retry with the slow path if we failed.
|
||||||
|
*/
|
||||||
|
if (atomic) {
|
||||||
|
atomic = 0;
|
||||||
|
goto redo;
|
||||||
|
}
|
||||||
if (!ret)
|
if (!ret)
|
||||||
ret = -EFAULT;
|
ret = error;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ret += chars;
|
ret += chars;
|
||||||
@ -293,21 +375,28 @@ pipe_writev(struct file *filp, const struct iovec *_iov,
|
|||||||
int offset = buf->offset + buf->len;
|
int offset = buf->offset + buf->len;
|
||||||
|
|
||||||
if (ops->can_merge && offset + chars <= PAGE_SIZE) {
|
if (ops->can_merge && offset + chars <= PAGE_SIZE) {
|
||||||
|
int error, atomic = 1;
|
||||||
void *addr;
|
void *addr;
|
||||||
int error;
|
|
||||||
|
|
||||||
addr = ops->map(filp, pipe, buf);
|
error = ops->pin(pipe, buf);
|
||||||
if (IS_ERR(addr)) {
|
|
||||||
error = PTR_ERR(addr);
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
error = pipe_iov_copy_from_user(offset + addr, iov,
|
|
||||||
chars);
|
|
||||||
ops->unmap(pipe, buf);
|
|
||||||
ret = error;
|
|
||||||
do_wakeup = 1;
|
|
||||||
if (error)
|
if (error)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
|
iov_fault_in_pages_read(iov, chars);
|
||||||
|
redo1:
|
||||||
|
addr = ops->map(pipe, buf, atomic);
|
||||||
|
error = pipe_iov_copy_from_user(offset + addr, iov,
|
||||||
|
chars, atomic);
|
||||||
|
ops->unmap(pipe, buf, addr);
|
||||||
|
ret = error;
|
||||||
|
do_wakeup = 1;
|
||||||
|
if (error) {
|
||||||
|
if (atomic) {
|
||||||
|
atomic = 0;
|
||||||
|
goto redo1;
|
||||||
|
}
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
buf->len += chars;
|
buf->len += chars;
|
||||||
total_len -= chars;
|
total_len -= chars;
|
||||||
ret = chars;
|
ret = chars;
|
||||||
@ -330,7 +419,8 @@ pipe_writev(struct file *filp, const struct iovec *_iov,
|
|||||||
int newbuf = (pipe->curbuf + bufs) & (PIPE_BUFFERS-1);
|
int newbuf = (pipe->curbuf + bufs) & (PIPE_BUFFERS-1);
|
||||||
struct pipe_buffer *buf = pipe->bufs + newbuf;
|
struct pipe_buffer *buf = pipe->bufs + newbuf;
|
||||||
struct page *page = pipe->tmp_page;
|
struct page *page = pipe->tmp_page;
|
||||||
int error;
|
char *src;
|
||||||
|
int error, atomic = 1;
|
||||||
|
|
||||||
if (!page) {
|
if (!page) {
|
||||||
page = alloc_page(GFP_HIGHUSER);
|
page = alloc_page(GFP_HIGHUSER);
|
||||||
@ -350,11 +440,27 @@ pipe_writev(struct file *filp, const struct iovec *_iov,
|
|||||||
if (chars > total_len)
|
if (chars > total_len)
|
||||||
chars = total_len;
|
chars = total_len;
|
||||||
|
|
||||||
error = pipe_iov_copy_from_user(kmap(page), iov, chars);
|
iov_fault_in_pages_read(iov, chars);
|
||||||
kunmap(page);
|
redo2:
|
||||||
|
if (atomic)
|
||||||
|
src = kmap_atomic(page, KM_USER0);
|
||||||
|
else
|
||||||
|
src = kmap(page);
|
||||||
|
|
||||||
|
error = pipe_iov_copy_from_user(src, iov, chars,
|
||||||
|
atomic);
|
||||||
|
if (atomic)
|
||||||
|
kunmap_atomic(src, KM_USER0);
|
||||||
|
else
|
||||||
|
kunmap(page);
|
||||||
|
|
||||||
if (unlikely(error)) {
|
if (unlikely(error)) {
|
||||||
|
if (atomic) {
|
||||||
|
atomic = 0;
|
||||||
|
goto redo2;
|
||||||
|
}
|
||||||
if (!ret)
|
if (!ret)
|
||||||
ret = -EFAULT;
|
ret = error;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ret += chars;
|
ret += chars;
|
||||||
|
166
fs/splice.c
166
fs/splice.c
@ -78,7 +78,7 @@ static int page_cache_pipe_buf_steal(struct pipe_inode_info *info,
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
buf->flags |= PIPE_BUF_FLAG_STOLEN | PIPE_BUF_FLAG_LRU;
|
buf->flags |= PIPE_BUF_FLAG_LRU;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,12 +87,11 @@ static void page_cache_pipe_buf_release(struct pipe_inode_info *info,
|
|||||||
{
|
{
|
||||||
page_cache_release(buf->page);
|
page_cache_release(buf->page);
|
||||||
buf->page = NULL;
|
buf->page = NULL;
|
||||||
buf->flags &= ~(PIPE_BUF_FLAG_STOLEN | PIPE_BUF_FLAG_LRU);
|
buf->flags &= ~PIPE_BUF_FLAG_LRU;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *page_cache_pipe_buf_map(struct file *file,
|
static int page_cache_pipe_buf_pin(struct pipe_inode_info *info,
|
||||||
struct pipe_inode_info *info,
|
struct pipe_buffer *buf)
|
||||||
struct pipe_buffer *buf)
|
|
||||||
{
|
{
|
||||||
struct page *page = buf->page;
|
struct page *page = buf->page;
|
||||||
int err;
|
int err;
|
||||||
@ -118,64 +117,44 @@ static void *page_cache_pipe_buf_map(struct file *file,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Page is ok afterall, fall through to mapping.
|
* Page is ok afterall, we are done.
|
||||||
*/
|
*/
|
||||||
unlock_page(page);
|
unlock_page(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
return kmap(page);
|
return 0;
|
||||||
error:
|
error:
|
||||||
unlock_page(page);
|
unlock_page(page);
|
||||||
return ERR_PTR(err);
|
return err;
|
||||||
}
|
|
||||||
|
|
||||||
static void page_cache_pipe_buf_unmap(struct pipe_inode_info *info,
|
|
||||||
struct pipe_buffer *buf)
|
|
||||||
{
|
|
||||||
kunmap(buf->page);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *user_page_pipe_buf_map(struct file *file,
|
|
||||||
struct pipe_inode_info *pipe,
|
|
||||||
struct pipe_buffer *buf)
|
|
||||||
{
|
|
||||||
return kmap(buf->page);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void user_page_pipe_buf_unmap(struct pipe_inode_info *pipe,
|
|
||||||
struct pipe_buffer *buf)
|
|
||||||
{
|
|
||||||
kunmap(buf->page);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void page_cache_pipe_buf_get(struct pipe_inode_info *info,
|
|
||||||
struct pipe_buffer *buf)
|
|
||||||
{
|
|
||||||
page_cache_get(buf->page);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct pipe_buf_operations page_cache_pipe_buf_ops = {
|
static struct pipe_buf_operations page_cache_pipe_buf_ops = {
|
||||||
.can_merge = 0,
|
.can_merge = 0,
|
||||||
.map = page_cache_pipe_buf_map,
|
.map = generic_pipe_buf_map,
|
||||||
.unmap = page_cache_pipe_buf_unmap,
|
.unmap = generic_pipe_buf_unmap,
|
||||||
|
.pin = page_cache_pipe_buf_pin,
|
||||||
.release = page_cache_pipe_buf_release,
|
.release = page_cache_pipe_buf_release,
|
||||||
.steal = page_cache_pipe_buf_steal,
|
.steal = page_cache_pipe_buf_steal,
|
||||||
.get = page_cache_pipe_buf_get,
|
.get = generic_pipe_buf_get,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int user_page_pipe_buf_steal(struct pipe_inode_info *pipe,
|
static int user_page_pipe_buf_steal(struct pipe_inode_info *pipe,
|
||||||
struct pipe_buffer *buf)
|
struct pipe_buffer *buf)
|
||||||
{
|
{
|
||||||
return 1;
|
if (!(buf->flags & PIPE_BUF_FLAG_GIFT))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct pipe_buf_operations user_page_pipe_buf_ops = {
|
static struct pipe_buf_operations user_page_pipe_buf_ops = {
|
||||||
.can_merge = 0,
|
.can_merge = 0,
|
||||||
.map = user_page_pipe_buf_map,
|
.map = generic_pipe_buf_map,
|
||||||
.unmap = user_page_pipe_buf_unmap,
|
.unmap = generic_pipe_buf_unmap,
|
||||||
|
.pin = generic_pipe_buf_pin,
|
||||||
.release = page_cache_pipe_buf_release,
|
.release = page_cache_pipe_buf_release,
|
||||||
.steal = user_page_pipe_buf_steal,
|
.steal = user_page_pipe_buf_steal,
|
||||||
.get = page_cache_pipe_buf_get,
|
.get = generic_pipe_buf_get,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -210,6 +189,9 @@ static ssize_t splice_to_pipe(struct pipe_inode_info *pipe,
|
|||||||
buf->offset = spd->partial[page_nr].offset;
|
buf->offset = spd->partial[page_nr].offset;
|
||||||
buf->len = spd->partial[page_nr].len;
|
buf->len = spd->partial[page_nr].len;
|
||||||
buf->ops = spd->ops;
|
buf->ops = spd->ops;
|
||||||
|
if (spd->flags & SPLICE_F_GIFT)
|
||||||
|
buf->flags |= PIPE_BUF_FLAG_GIFT;
|
||||||
|
|
||||||
pipe->nrbufs++;
|
pipe->nrbufs++;
|
||||||
page_nr++;
|
page_nr++;
|
||||||
ret += buf->len;
|
ret += buf->len;
|
||||||
@ -325,6 +307,12 @@ __generic_file_splice_read(struct file *in, loff_t *ppos,
|
|||||||
*/
|
*/
|
||||||
page = find_get_page(mapping, index);
|
page = find_get_page(mapping, index);
|
||||||
if (!page) {
|
if (!page) {
|
||||||
|
/*
|
||||||
|
* Make sure the read-ahead engine is notified
|
||||||
|
* about this failure.
|
||||||
|
*/
|
||||||
|
handle_ra_miss(mapping, &in->f_ra, index);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* page didn't exist, allocate one.
|
* page didn't exist, allocate one.
|
||||||
*/
|
*/
|
||||||
@ -517,26 +505,16 @@ static int pipe_to_sendpage(struct pipe_inode_info *info,
|
|||||||
{
|
{
|
||||||
struct file *file = sd->file;
|
struct file *file = sd->file;
|
||||||
loff_t pos = sd->pos;
|
loff_t pos = sd->pos;
|
||||||
ssize_t ret;
|
int ret, more;
|
||||||
void *ptr;
|
|
||||||
int more;
|
|
||||||
|
|
||||||
/*
|
ret = buf->ops->pin(info, buf);
|
||||||
* Sub-optimal, but we are limited by the pipe ->map. We don't
|
if (!ret) {
|
||||||
* need a kmap'ed buffer here, we just want to make sure we
|
more = (sd->flags & SPLICE_F_MORE) || sd->len < sd->total_len;
|
||||||
* have the page pinned if the pipe page originates from the
|
|
||||||
* page cache.
|
|
||||||
*/
|
|
||||||
ptr = buf->ops->map(file, info, buf);
|
|
||||||
if (IS_ERR(ptr))
|
|
||||||
return PTR_ERR(ptr);
|
|
||||||
|
|
||||||
more = (sd->flags & SPLICE_F_MORE) || sd->len < sd->total_len;
|
ret = file->f_op->sendpage(file, buf->page, buf->offset,
|
||||||
|
sd->len, &pos, more);
|
||||||
|
}
|
||||||
|
|
||||||
ret = file->f_op->sendpage(file, buf->page, buf->offset, sd->len,
|
|
||||||
&pos, more);
|
|
||||||
|
|
||||||
buf->ops->unmap(info, buf);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -569,15 +547,14 @@ static int pipe_to_file(struct pipe_inode_info *info, struct pipe_buffer *buf,
|
|||||||
unsigned int offset, this_len;
|
unsigned int offset, this_len;
|
||||||
struct page *page;
|
struct page *page;
|
||||||
pgoff_t index;
|
pgoff_t index;
|
||||||
char *src;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* make sure the data in this buffer is uptodate
|
* make sure the data in this buffer is uptodate
|
||||||
*/
|
*/
|
||||||
src = buf->ops->map(file, info, buf);
|
ret = buf->ops->pin(info, buf);
|
||||||
if (IS_ERR(src))
|
if (unlikely(ret))
|
||||||
return PTR_ERR(src);
|
return ret;
|
||||||
|
|
||||||
index = sd->pos >> PAGE_CACHE_SHIFT;
|
index = sd->pos >> PAGE_CACHE_SHIFT;
|
||||||
offset = sd->pos & ~PAGE_CACHE_MASK;
|
offset = sd->pos & ~PAGE_CACHE_MASK;
|
||||||
@ -587,9 +564,10 @@ static int pipe_to_file(struct pipe_inode_info *info, struct pipe_buffer *buf,
|
|||||||
this_len = PAGE_CACHE_SIZE - offset;
|
this_len = PAGE_CACHE_SIZE - offset;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Reuse buf page, if SPLICE_F_MOVE is set.
|
* Reuse buf page, if SPLICE_F_MOVE is set and we are doing a full
|
||||||
|
* page.
|
||||||
*/
|
*/
|
||||||
if (sd->flags & SPLICE_F_MOVE) {
|
if ((sd->flags & SPLICE_F_MOVE) && this_len == PAGE_CACHE_SIZE) {
|
||||||
/*
|
/*
|
||||||
* If steal succeeds, buf->page is now pruned from the vm
|
* If steal succeeds, buf->page is now pruned from the vm
|
||||||
* side (LRU and page cache) and we can reuse it. The page
|
* side (LRU and page cache) and we can reuse it. The page
|
||||||
@ -599,8 +577,12 @@ static int pipe_to_file(struct pipe_inode_info *info, struct pipe_buffer *buf,
|
|||||||
goto find_page;
|
goto find_page;
|
||||||
|
|
||||||
page = buf->page;
|
page = buf->page;
|
||||||
if (add_to_page_cache(page, mapping, index, gfp_mask))
|
if (add_to_page_cache(page, mapping, index, gfp_mask)) {
|
||||||
|
unlock_page(page);
|
||||||
goto find_page;
|
goto find_page;
|
||||||
|
}
|
||||||
|
|
||||||
|
page_cache_get(page);
|
||||||
|
|
||||||
if (!(buf->flags & PIPE_BUF_FLAG_LRU))
|
if (!(buf->flags & PIPE_BUF_FLAG_LRU))
|
||||||
lru_cache_add(page);
|
lru_cache_add(page);
|
||||||
@ -660,34 +642,36 @@ find_page:
|
|||||||
} else if (ret)
|
} else if (ret)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
if (!(buf->flags & PIPE_BUF_FLAG_STOLEN)) {
|
if (buf->page != page) {
|
||||||
char *dst = kmap_atomic(page, KM_USER0);
|
/*
|
||||||
|
* Careful, ->map() uses KM_USER0!
|
||||||
|
*/
|
||||||
|
char *src = buf->ops->map(info, buf, 1);
|
||||||
|
char *dst = kmap_atomic(page, KM_USER1);
|
||||||
|
|
||||||
memcpy(dst + offset, src + buf->offset, this_len);
|
memcpy(dst + offset, src + buf->offset, this_len);
|
||||||
flush_dcache_page(page);
|
flush_dcache_page(page);
|
||||||
kunmap_atomic(dst, KM_USER0);
|
kunmap_atomic(dst, KM_USER1);
|
||||||
|
buf->ops->unmap(info, buf, src);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = mapping->a_ops->commit_write(file, page, offset, offset+this_len);
|
ret = mapping->a_ops->commit_write(file, page, offset, offset+this_len);
|
||||||
if (ret == AOP_TRUNCATED_PAGE) {
|
if (!ret) {
|
||||||
|
/*
|
||||||
|
* Return the number of bytes written and mark page as
|
||||||
|
* accessed, we are now done!
|
||||||
|
*/
|
||||||
|
ret = this_len;
|
||||||
|
mark_page_accessed(page);
|
||||||
|
balance_dirty_pages_ratelimited(mapping);
|
||||||
|
} else if (ret == AOP_TRUNCATED_PAGE) {
|
||||||
page_cache_release(page);
|
page_cache_release(page);
|
||||||
goto find_page;
|
goto find_page;
|
||||||
} else if (ret)
|
}
|
||||||
goto out;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return the number of bytes written.
|
|
||||||
*/
|
|
||||||
ret = this_len;
|
|
||||||
mark_page_accessed(page);
|
|
||||||
balance_dirty_pages_ratelimited(mapping);
|
|
||||||
out:
|
out:
|
||||||
if (!(buf->flags & PIPE_BUF_FLAG_STOLEN))
|
page_cache_release(page);
|
||||||
page_cache_release(page);
|
|
||||||
|
|
||||||
unlock_page(page);
|
unlock_page(page);
|
||||||
out_nomem:
|
out_nomem:
|
||||||
buf->ops->unmap(info, buf);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1095,7 +1079,7 @@ static long do_splice(struct file *in, loff_t __user *off_in,
|
|||||||
*/
|
*/
|
||||||
static int get_iovec_page_array(const struct iovec __user *iov,
|
static int get_iovec_page_array(const struct iovec __user *iov,
|
||||||
unsigned int nr_vecs, struct page **pages,
|
unsigned int nr_vecs, struct page **pages,
|
||||||
struct partial_page *partial)
|
struct partial_page *partial, int aligned)
|
||||||
{
|
{
|
||||||
int buffers = 0, error = 0;
|
int buffers = 0, error = 0;
|
||||||
|
|
||||||
@ -1135,6 +1119,15 @@ static int get_iovec_page_array(const struct iovec __user *iov,
|
|||||||
* in the user pages.
|
* in the user pages.
|
||||||
*/
|
*/
|
||||||
off = (unsigned long) base & ~PAGE_MASK;
|
off = (unsigned long) base & ~PAGE_MASK;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If asked for alignment, the offset must be zero and the
|
||||||
|
* length a multiple of the PAGE_SIZE.
|
||||||
|
*/
|
||||||
|
error = -EINVAL;
|
||||||
|
if (aligned && (off || len & ~PAGE_MASK))
|
||||||
|
break;
|
||||||
|
|
||||||
npages = (off + len + PAGE_SIZE - 1) >> PAGE_SHIFT;
|
npages = (off + len + PAGE_SIZE - 1) >> PAGE_SHIFT;
|
||||||
if (npages > PIPE_BUFFERS - buffers)
|
if (npages > PIPE_BUFFERS - buffers)
|
||||||
npages = PIPE_BUFFERS - buffers;
|
npages = PIPE_BUFFERS - buffers;
|
||||||
@ -1228,7 +1221,8 @@ static long do_vmsplice(struct file *file, const struct iovec __user *iov,
|
|||||||
else if (unlikely(!nr_segs))
|
else if (unlikely(!nr_segs))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
spd.nr_pages = get_iovec_page_array(iov, nr_segs, pages, partial);
|
spd.nr_pages = get_iovec_page_array(iov, nr_segs, pages, partial,
|
||||||
|
flags & SPLICE_F_GIFT);
|
||||||
if (spd.nr_pages <= 0)
|
if (spd.nr_pages <= 0)
|
||||||
return spd.nr_pages;
|
return spd.nr_pages;
|
||||||
|
|
||||||
@ -1336,6 +1330,12 @@ static int link_pipe(struct pipe_inode_info *ipipe,
|
|||||||
obuf = opipe->bufs + nbuf;
|
obuf = opipe->bufs + nbuf;
|
||||||
*obuf = *ibuf;
|
*obuf = *ibuf;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Don't inherit the gift flag, we need to
|
||||||
|
* prevent multiple steals of this page.
|
||||||
|
*/
|
||||||
|
obuf->flags &= ~PIPE_BUF_FLAG_GIFT;
|
||||||
|
|
||||||
if (obuf->len > len)
|
if (obuf->len > len)
|
||||||
obuf->len = len;
|
obuf->len = len;
|
||||||
|
|
||||||
|
@ -5,8 +5,9 @@
|
|||||||
|
|
||||||
#define PIPE_BUFFERS (16)
|
#define PIPE_BUFFERS (16)
|
||||||
|
|
||||||
#define PIPE_BUF_FLAG_STOLEN 0x01
|
#define PIPE_BUF_FLAG_LRU 0x01 /* page is on the LRU */
|
||||||
#define PIPE_BUF_FLAG_LRU 0x02
|
#define PIPE_BUF_FLAG_ATOMIC 0x02 /* was atomically mapped */
|
||||||
|
#define PIPE_BUF_FLAG_GIFT 0x04 /* page is a gift */
|
||||||
|
|
||||||
struct pipe_buffer {
|
struct pipe_buffer {
|
||||||
struct page *page;
|
struct page *page;
|
||||||
@ -15,10 +16,23 @@ struct pipe_buffer {
|
|||||||
unsigned int flags;
|
unsigned int flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note on the nesting of these functions:
|
||||||
|
*
|
||||||
|
* ->pin()
|
||||||
|
* ->steal()
|
||||||
|
* ...
|
||||||
|
* ->map()
|
||||||
|
* ...
|
||||||
|
* ->unmap()
|
||||||
|
*
|
||||||
|
* That is, ->map() must be called on a pinned buffer, same goes for ->steal().
|
||||||
|
*/
|
||||||
struct pipe_buf_operations {
|
struct pipe_buf_operations {
|
||||||
int can_merge;
|
int can_merge;
|
||||||
void * (*map)(struct file *, struct pipe_inode_info *, struct pipe_buffer *);
|
void * (*map)(struct pipe_inode_info *, struct pipe_buffer *, int);
|
||||||
void (*unmap)(struct pipe_inode_info *, struct pipe_buffer *);
|
void (*unmap)(struct pipe_inode_info *, struct pipe_buffer *, void *);
|
||||||
|
int (*pin)(struct pipe_inode_info *, struct pipe_buffer *);
|
||||||
void (*release)(struct pipe_inode_info *, struct pipe_buffer *);
|
void (*release)(struct pipe_inode_info *, struct pipe_buffer *);
|
||||||
int (*steal)(struct pipe_inode_info *, struct pipe_buffer *);
|
int (*steal)(struct pipe_inode_info *, struct pipe_buffer *);
|
||||||
void (*get)(struct pipe_inode_info *, struct pipe_buffer *);
|
void (*get)(struct pipe_inode_info *, struct pipe_buffer *);
|
||||||
@ -51,6 +65,12 @@ struct pipe_inode_info * alloc_pipe_info(struct inode * inode);
|
|||||||
void free_pipe_info(struct inode * inode);
|
void free_pipe_info(struct inode * inode);
|
||||||
void __free_pipe_info(struct pipe_inode_info *);
|
void __free_pipe_info(struct pipe_inode_info *);
|
||||||
|
|
||||||
|
/* Generic pipe buffer ops functions */
|
||||||
|
void *generic_pipe_buf_map(struct pipe_inode_info *, struct pipe_buffer *, int);
|
||||||
|
void generic_pipe_buf_unmap(struct pipe_inode_info *, struct pipe_buffer *, void *);
|
||||||
|
void generic_pipe_buf_get(struct pipe_inode_info *, struct pipe_buffer *);
|
||||||
|
int generic_pipe_buf_pin(struct pipe_inode_info *, struct pipe_buffer *);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* splice is tied to pipes as a transport (at least for now), so we'll just
|
* splice is tied to pipes as a transport (at least for now), so we'll just
|
||||||
* add the splice flags here.
|
* add the splice flags here.
|
||||||
@ -60,6 +80,7 @@ void __free_pipe_info(struct pipe_inode_info *);
|
|||||||
/* we may still block on the fd we splice */
|
/* we may still block on the fd we splice */
|
||||||
/* from/to, of course */
|
/* from/to, of course */
|
||||||
#define SPLICE_F_MORE (0x04) /* expect more data */
|
#define SPLICE_F_MORE (0x04) /* expect more data */
|
||||||
|
#define SPLICE_F_GIFT (0x08) /* pages passed in are a gift */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Passed to the actors
|
* Passed to the actors
|
||||||
|
Loading…
Reference in New Issue
Block a user