diff options
-rw-r--r-- | fs/aio.c | 115 | ||||
-rw-r--r-- | fs/direct-io.c | 1 | ||||
-rw-r--r-- | include/linux/aio.h | 54 | ||||
-rw-r--r-- | include/linux/aio_abi.h | 41 | ||||
-rw-r--r-- | include/linux/blk_types.h | 3 |
5 files changed, 212 insertions, 2 deletions
@@ -424,6 +424,8 @@ static struct kiocb *__aio_get_req(struct kioctx *ctx) req->private = NULL; req->ki_iovec = NULL; INIT_LIST_HEAD(&req->ki_run_list); + req->ki_attrs = NULL; + req->ki_attr_rets = NULL; req->ki_eventfd = NULL; return req; @@ -538,6 +540,7 @@ static inline void really_put_req(struct kioctx *ctx, struct kiocb *req) req->ki_dtor(req); if (req->ki_iovec != &req->ki_inline_vec) kfree(req->ki_iovec); + kfree(req->ki_attrs); kmem_cache_free(kiocb_cachep, req); ctx->reqs_active--; @@ -1504,6 +1507,109 @@ static ssize_t aio_setup_iocb(struct kiocb *kiocb, bool compat) return 0; } +static const size_t iocb_attr_sizes[] = { + [IOCB_ATTR_proxy_pid] = sizeof(struct iocb_attr_proxy_pid), +}; + +static const size_t iocb_attr_ret_sizes[] = { + [IOCB_ATTR_proxy_pid] = sizeof(struct iocb_attr_ret_proxy_pid), +}; + +static int aio_setup_attrs(struct iocb __user *user_iocb, + struct iocb *iocb, struct kiocb *req) +{ + struct iocb_attr_list __user *user_attrs; + struct iocb_attr_ret_list __user *user_attr_rets; + struct iocb_attr *attr, *end; + struct iocb_attr_ret *attr_ret; + u64 size, ret_size = sizeof(struct iocb_attr_ret_list); + int ret; + + user_attrs = (void *) &user_iocb[1]; + user_attr_rets = (void *) iocb->aio_attr_rets; + + if (!(iocb->aio_flags & IOCB_FLAG_ATTR)) + return 0; + + if (unlikely(get_user(size, &user_attrs->size))) + return -EFAULT; + + if (size == sizeof(struct iocb_attr_list)) { + /* + * We were passed an empty attribute list, so we can skip the + * rest of the setup but we still need to return an empty + * iocb_attr_ret_list: + */ + + struct iocb_attr_ret_list tmp; + tmp.size = sizeof(tmp); + + if (copy_to_user(user_attr_rets, &tmp, sizeof(tmp))) + return -EFAULT; + + return 0; + } + + if (unlikely(size > PAGE_SIZE)) + return -EFAULT; + + req->ki_attrs = kmalloc(size, GFP_KERNEL); + if (unlikely(!req->ki_attrs)) + return -ENOMEM; + + req->ki_attrs->size = size; + + ret = -EFAULT; + if (unlikely(copy_from_user(req->ki_attrs->attrs, + user_attrs->attrs, + size - sizeof(u64)))) + goto err_free; + + end = ((void *) req->ki_attrs) + req->ki_attrs->size; + + ret = -EINVAL; + for_each_iocb_attr(attr, req->ki_attrs) { + if (attr > end) + goto err_free; + + if (attr->size < sizeof(struct iocb_attr)) + goto err_free; + + if (attr->id >= IOCB_ATTR_MAX) + goto err_free; + + if (attr->size != iocb_attr_sizes[attr->id]) + goto err_free; + + ret_size += iocb_attr_ret_sizes[attr->id]; + } + + ret = -ENOMEM; + req->ki_attr_rets = kmalloc(ret_size, GFP_KERNEL); + if (!req->ki_attr_rets) + goto err_free; + + req->ki_attr_rets->size = ret_size; + + attr_ret = req->ki_attr_rets->rets; + + for_each_iocb_attr(attr, req->ki_attrs) { + attr_ret->cookie = attr->cookie; + attr_ret->size = iocb_attr_ret_sizes[attr->id]; + attr_ret->ret = -EINVAL; + + attr->cookie = (unsigned long) attr_ret; + + attr_ret = ((void *) attr_ret) + attr_ret->size; + } + + return 0; +err_free: + kfree(req->ki_attrs); + req->ki_attrs = NULL; + return ret; +} + static int io_submit_one(struct kioctx *ctx, struct iocb __user *user_iocb, struct iocb *iocb, struct kiocb_batch *batch, bool compat) @@ -1513,7 +1619,9 @@ static int io_submit_one(struct kioctx *ctx, struct iocb __user *user_iocb, ssize_t ret; /* enforce forwards compatibility on users */ - if (unlikely(iocb->aio_reserved1 || iocb->aio_reserved2)) { + if (unlikely(iocb->aio_reserved1 || + (iocb->aio_attr_rets && + !(iocb->aio_flags & IOCB_FLAG_ATTR)))) { pr_debug("EINVAL: io_submit: reserve field set\n"); return -EINVAL; } @@ -1538,6 +1646,11 @@ static int io_submit_one(struct kioctx *ctx, struct iocb __user *user_iocb, return -EAGAIN; } req->ki_filp = file; + + ret = aio_setup_attrs(user_iocb, iocb, req); + if (ret) + goto out_put_req; + if (iocb->aio_flags & IOCB_FLAG_RESFD) { /* * If the IOCB_FLAG_RESFD flag of aio_flags is set, get an diff --git a/fs/direct-io.c b/fs/direct-io.c index f86c720dba0e..f58f44fc2f1d 100644 --- a/fs/direct-io.c +++ b/fs/direct-io.c @@ -371,6 +371,7 @@ static inline void dio_bio_submit(struct dio *dio, struct dio_submit *sdio) unsigned long flags; bio->bi_private = dio; + bio->bi_attrs = dio->iocb->ki_attrs; spin_lock_irqsave(&dio->bio_lock, flags); dio->refcount++; diff --git a/include/linux/aio.h b/include/linux/aio.h index 31ff6dba4872..78af6199812b 100644 --- a/include/linux/aio.h +++ b/include/linux/aio.h @@ -119,6 +119,9 @@ struct kiocb { * for cancellation */ struct list_head ki_batch; /* batch allocation */ + struct iocb_attr_list *ki_attrs; + struct iocb_attr_ret_list *ki_attr_rets; + /* * If the aio_resfd field of the userspace iocb is not zero, * this is the underlying eventfd context to deliver events to. @@ -230,11 +233,62 @@ static inline long do_io_submit(aio_context_t ctx_id, long nr, bool compat) { return 0; } #endif /* CONFIG_AIO */ +#define for_each_iocb_attr(attr, attr_list) \ + for (attr = (attr_list)->attrs; \ + attr != ((void *) (attr_list)) + (attr_list)->size; \ + attr = ((void *) attr) + (attr)->size) + static inline struct kiocb *list_kiocb(struct list_head *h) { return list_entry(h, struct kiocb, ki_list); } +static inline struct iocb_attr *iocb_attr_next(struct iocb_attr_list *attrs, + struct iocb_attr *attr) +{ + void *end = ((void *) attrs) + attrs->size; + + if (attr) + attr = ((void *) attr) + attr->size; + else + attr = attrs->attrs; + + if (attr == end) + return attr; + + return attr; +} + +static inline void *__iocb_attr_lookup(struct iocb_attr_list *attrs, unsigned id) +{ + struct iocb_attr *attr = NULL; + + if (!attrs) + return NULL; + + while (1) { + attr = iocb_attr_next(attrs, attr); + if (!attr) + return NULL; + + if (attr->id == id) + return attr; + } + + return NULL; +} + +#define iocb_attr_lookup(attrs, id) \ +({ \ + struct iocb_attr *_attr; \ + \ + _attr = __iocb_attr_lookup((attrs), IOCB_ATTR_ ## id); \ + if (_attr->size != sizeof(struct iocb_attr_ ## id)) \ + _attr = NULL; \ + \ + (struct iocb_attr_ ## id *) _attr; \ +}) + /* for sysctl: */ extern unsigned long aio_nr; extern unsigned long aio_max_nr; diff --git a/include/linux/aio_abi.h b/include/linux/aio_abi.h index 86fa7a71336a..f86c862dfba3 100644 --- a/include/linux/aio_abi.h +++ b/include/linux/aio_abi.h @@ -53,6 +53,7 @@ enum { * is valid. */ #define IOCB_FLAG_RESFD (1 << 0) +#define IOCB_FLAG_ATTR (1 << 1) /* read() from /dev/aio returns these structures. */ struct io_event { @@ -92,7 +93,7 @@ struct iocb { __s64 aio_offset; /* extra parameters */ - __u64 aio_reserved2; /* TODO: use this for a (struct sigevent *) */ + __u64 aio_attr_rets; /* flags for the "struct iocb" */ __u32 aio_flags; @@ -104,6 +105,44 @@ struct iocb { __u32 aio_resfd; }; /* 64 bytes */ +struct iocb_attr { + __u64 cookie; + __u32 size; + __u32 id; + __u8 data[0]; +} __aligned(8); + +struct iocb_attr_list { + __u64 size; + struct iocb_attr attrs[]; +}; + +struct iocb_attr_ret { + __u64 cookie; + __u32 size; + __u32 ret; + __u8 data[0]; +} __aligned(8); + +struct iocb_attr_ret_list { + __u64 size; + struct iocb_attr_ret rets[]; +}; + +enum { + IOCB_ATTR_proxy_pid, + IOCB_ATTR_MAX +}; + +struct iocb_attr_proxy_pid { + struct iocb_attr attr; + __u64 pid; +}; + +struct iocb_attr_ret_proxy_pid { + struct iocb_attr_ret attr; +}; + #undef IFBIG #undef IFLITTLE diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h index 7b7ac9ccec7a..e2e37f783cbc 100644 --- a/include/linux/blk_types.h +++ b/include/linux/blk_types.h @@ -68,6 +68,9 @@ struct bio { bio_end_io_t *bi_end_io; void *bi_private; + + struct iocb_attr_list *bi_attrs; + #ifdef CONFIG_BLK_CGROUP /* * Optional ioc and css associated with this bio. Put on bio |