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:
Linus Torvalds 2006-05-01 18:33:40 -07:00
commit 9817d207dc
4 changed files with 258 additions and 129 deletions

View File

@ -1104,6 +1104,8 @@ const struct file_operations def_blk_fops = {
.readv = generic_file_readv,
.writev = generic_file_write_nolock,
.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)

190
fs/pipe.c
View File

@ -55,7 +55,8 @@ void pipe_wait(struct pipe_inode_info *pipe)
}
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;
@ -64,8 +65,13 @@ pipe_iov_copy_from_user(void *to, struct iovec *iov, unsigned long len)
iov++;
copy = min_t(unsigned long, len, iov->iov_len);
if (copy_from_user(to, iov->iov_base, copy))
return -EFAULT;
if (atomic) {
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;
len -= copy;
iov->iov_base += copy;
@ -75,7 +81,8 @@ pipe_iov_copy_from_user(void *to, struct iovec *iov, unsigned long len)
}
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;
@ -84,8 +91,13 @@ pipe_iov_copy_to_user(struct iovec *iov, const void *from, unsigned long len)
iov++;
copy = min_t(unsigned long, len, iov->iov_len);
if (copy_to_user(iov->iov_base, from, copy))
return -EFAULT;
if (atomic) {
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;
len -= 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;
}
/*
* 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,
struct pipe_buffer *buf)
{
struct page *page = buf->page;
buf->flags &= ~PIPE_BUF_FLAG_STOLEN;
/*
* 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
@ -112,38 +163,58 @@ static void anon_pipe_buf_release(struct pipe_inode_info *pipe,
page_cache_release(page);
}
static void * anon_pipe_buf_map(struct file *file, struct pipe_inode_info *pipe,
struct pipe_buffer *buf)
void *generic_pipe_buf_map(struct pipe_inode_info *pipe,
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);
}
static void anon_pipe_buf_unmap(struct pipe_inode_info *pipe,
struct pipe_buffer *buf)
void generic_pipe_buf_unmap(struct pipe_inode_info *pipe,
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,
struct pipe_buffer *buf)
{
buf->flags |= PIPE_BUF_FLAG_STOLEN;
return 0;
struct page *page = buf->page;
if (page_count(page) == 1) {
lock_page(page);
return 0;
}
return 1;
}
static void anon_pipe_buf_get(struct pipe_inode_info *info,
struct pipe_buffer *buf)
void generic_pipe_buf_get(struct pipe_inode_info *info, struct pipe_buffer *buf)
{
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 = {
.can_merge = 1,
.map = anon_pipe_buf_map,
.unmap = anon_pipe_buf_unmap,
.map = generic_pipe_buf_map,
.unmap = generic_pipe_buf_unmap,
.pin = generic_pipe_buf_pin,
.release = anon_pipe_buf_release,
.steal = anon_pipe_buf_steal,
.get = anon_pipe_buf_get,
.get = generic_pipe_buf_get,
};
static ssize_t
@ -174,22 +245,33 @@ pipe_readv(struct file *filp, const struct iovec *_iov,
struct pipe_buf_operations *ops = buf->ops;
void *addr;
size_t chars = buf->len;
int error;
int error, atomic;
if (chars > total_len)
chars = total_len;
addr = ops->map(filp, pipe, buf);
if (IS_ERR(addr)) {
error = ops->pin(pipe, buf);
if (error) {
if (!ret)
ret = PTR_ERR(addr);
error = ret;
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)) {
/*
* Just retry with the slow path if we failed.
*/
if (atomic) {
atomic = 0;
goto redo;
}
if (!ret)
ret = -EFAULT;
ret = error;
break;
}
ret += chars;
@ -293,21 +375,28 @@ pipe_writev(struct file *filp, const struct iovec *_iov,
int offset = buf->offset + buf->len;
if (ops->can_merge && offset + chars <= PAGE_SIZE) {
int error, atomic = 1;
void *addr;
int error;
addr = ops->map(filp, 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;
error = ops->pin(pipe, buf);
if (error)
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;
total_len -= chars;
ret = chars;
@ -330,7 +419,8 @@ pipe_writev(struct file *filp, const struct iovec *_iov,
int newbuf = (pipe->curbuf + bufs) & (PIPE_BUFFERS-1);
struct pipe_buffer *buf = pipe->bufs + newbuf;
struct page *page = pipe->tmp_page;
int error;
char *src;
int error, atomic = 1;
if (!page) {
page = alloc_page(GFP_HIGHUSER);
@ -350,11 +440,27 @@ pipe_writev(struct file *filp, const struct iovec *_iov,
if (chars > total_len)
chars = total_len;
error = pipe_iov_copy_from_user(kmap(page), iov, chars);
kunmap(page);
iov_fault_in_pages_read(iov, chars);
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 (atomic) {
atomic = 0;
goto redo2;
}
if (!ret)
ret = -EFAULT;
ret = error;
break;
}
ret += chars;

View File

@ -78,7 +78,7 @@ static int page_cache_pipe_buf_steal(struct pipe_inode_info *info,
return 1;
}
buf->flags |= PIPE_BUF_FLAG_STOLEN | PIPE_BUF_FLAG_LRU;
buf->flags |= PIPE_BUF_FLAG_LRU;
return 0;
}
@ -87,12 +87,11 @@ static void page_cache_pipe_buf_release(struct pipe_inode_info *info,
{
page_cache_release(buf->page);
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,
struct pipe_inode_info *info,
struct pipe_buffer *buf)
static int page_cache_pipe_buf_pin(struct pipe_inode_info *info,
struct pipe_buffer *buf)
{
struct page *page = buf->page;
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);
}
return kmap(page);
return 0;
error:
unlock_page(page);
return ERR_PTR(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);
return err;
}
static struct pipe_buf_operations page_cache_pipe_buf_ops = {
.can_merge = 0,
.map = page_cache_pipe_buf_map,
.unmap = page_cache_pipe_buf_unmap,
.map = generic_pipe_buf_map,
.unmap = generic_pipe_buf_unmap,
.pin = page_cache_pipe_buf_pin,
.release = page_cache_pipe_buf_release,
.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,
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 = {
.can_merge = 0,
.map = user_page_pipe_buf_map,
.unmap = user_page_pipe_buf_unmap,
.map = generic_pipe_buf_map,
.unmap = generic_pipe_buf_unmap,
.pin = generic_pipe_buf_pin,
.release = page_cache_pipe_buf_release,
.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->len = spd->partial[page_nr].len;
buf->ops = spd->ops;
if (spd->flags & SPLICE_F_GIFT)
buf->flags |= PIPE_BUF_FLAG_GIFT;
pipe->nrbufs++;
page_nr++;
ret += buf->len;
@ -325,6 +307,12 @@ __generic_file_splice_read(struct file *in, loff_t *ppos,
*/
page = find_get_page(mapping, index);
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.
*/
@ -517,26 +505,16 @@ static int pipe_to_sendpage(struct pipe_inode_info *info,
{
struct file *file = sd->file;
loff_t pos = sd->pos;
ssize_t ret;
void *ptr;
int more;
int ret, more;
/*
* Sub-optimal, but we are limited by the pipe ->map. We don't
* need a kmap'ed buffer here, we just want to make sure we
* 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);
ret = buf->ops->pin(info, buf);
if (!ret) {
more = (sd->flags & SPLICE_F_MORE) || sd->len < sd->total_len;
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;
}
@ -569,15 +547,14 @@ static int pipe_to_file(struct pipe_inode_info *info, struct pipe_buffer *buf,
unsigned int offset, this_len;
struct page *page;
pgoff_t index;
char *src;
int ret;
/*
* make sure the data in this buffer is uptodate
*/
src = buf->ops->map(file, info, buf);
if (IS_ERR(src))
return PTR_ERR(src);
ret = buf->ops->pin(info, buf);
if (unlikely(ret))
return ret;
index = sd->pos >> PAGE_CACHE_SHIFT;
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;
/*
* 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
* 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;
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;
}
page_cache_get(page);
if (!(buf->flags & PIPE_BUF_FLAG_LRU))
lru_cache_add(page);
@ -660,34 +642,36 @@ find_page:
} else if (ret)
goto out;
if (!(buf->flags & PIPE_BUF_FLAG_STOLEN)) {
char *dst = kmap_atomic(page, KM_USER0);
if (buf->page != page) {
/*
* 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);
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);
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);
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:
if (!(buf->flags & PIPE_BUF_FLAG_STOLEN))
page_cache_release(page);
page_cache_release(page);
unlock_page(page);
out_nomem:
buf->ops->unmap(info, buf);
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,
unsigned int nr_vecs, struct page **pages,
struct partial_page *partial)
struct partial_page *partial, int aligned)
{
int buffers = 0, error = 0;
@ -1135,6 +1119,15 @@ static int get_iovec_page_array(const struct iovec __user *iov,
* in the user pages.
*/
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;
if (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))
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)
return spd.nr_pages;
@ -1336,6 +1330,12 @@ static int link_pipe(struct pipe_inode_info *ipipe,
obuf = opipe->bufs + nbuf;
*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)
obuf->len = len;

View File

@ -5,8 +5,9 @@
#define PIPE_BUFFERS (16)
#define PIPE_BUF_FLAG_STOLEN 0x01
#define PIPE_BUF_FLAG_LRU 0x02
#define PIPE_BUF_FLAG_LRU 0x01 /* page is on the LRU */
#define PIPE_BUF_FLAG_ATOMIC 0x02 /* was atomically mapped */
#define PIPE_BUF_FLAG_GIFT 0x04 /* page is a gift */
struct pipe_buffer {
struct page *page;
@ -15,10 +16,23 @@ struct pipe_buffer {
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 {
int can_merge;
void * (*map)(struct file *, struct pipe_inode_info *, struct pipe_buffer *);
void (*unmap)(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 *);
int (*pin)(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 *);
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 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
* 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 */
/* from/to, of course */
#define SPLICE_F_MORE (0x04) /* expect more data */
#define SPLICE_F_GIFT (0x08) /* pages passed in are a gift */
/*
* Passed to the actors