| // SPDX-License-Identifier: GPL-2.0 | |
| #include <linux/kernel.h> | |
| #include <linux/errno.h> | |
| #include <linux/file.h> | |
| #include <linux/mm.h> | |
| #include <linux/slab.h> | |
| #include <linux/nospec.h> | |
| #include <linux/io_uring.h> | |
| #include <uapi/linux/io_uring.h> | |
| #include "io_uring.h" | |
| #include "rsrc.h" | |
| #include "filetable.h" | |
| static int io_file_bitmap_get(struct io_ring_ctx *ctx) | |
| { | |
| struct io_file_table *table = &ctx->file_table; | |
| unsigned long nr = ctx->file_alloc_end; | |
| int ret; | |
| if (!table->bitmap) | |
| return -ENFILE; | |
| if (table->alloc_hint < ctx->file_alloc_start || | |
| table->alloc_hint >= ctx->file_alloc_end) | |
| table->alloc_hint = ctx->file_alloc_start; | |
| do { | |
| ret = find_next_zero_bit(table->bitmap, nr, table->alloc_hint); | |
| if (ret != nr) | |
| return ret; | |
| if (table->alloc_hint == ctx->file_alloc_start) | |
| break; | |
| nr = table->alloc_hint; | |
| table->alloc_hint = ctx->file_alloc_start; | |
| } while (1); | |
| return -ENFILE; | |
| } | |
| bool io_alloc_file_tables(struct io_ring_ctx *ctx, struct io_file_table *table, | |
| unsigned nr_files) | |
| { | |
| if (io_rsrc_data_alloc(&table->data, nr_files)) | |
| return false; | |
| table->bitmap = bitmap_zalloc(nr_files, GFP_KERNEL_ACCOUNT); | |
| if (table->bitmap) | |
| return true; | |
| io_rsrc_data_free(ctx, &table->data); | |
| return false; | |
| } | |
| void io_free_file_tables(struct io_ring_ctx *ctx, struct io_file_table *table) | |
| { | |
| io_rsrc_data_free(ctx, &table->data); | |
| bitmap_free(table->bitmap); | |
| table->bitmap = NULL; | |
| } | |
| static int io_install_fixed_file(struct io_ring_ctx *ctx, struct file *file, | |
| u32 slot_index) | |
| __must_hold(&ctx->uring_lock) | |
| { | |
| struct io_rsrc_node *node; | |
| if (io_is_uring_fops(file)) | |
| return -EBADF; | |
| if (!ctx->file_table.data.nr) | |
| return -ENXIO; | |
| if (slot_index >= ctx->file_table.data.nr) | |
| return -EINVAL; | |
| node = io_rsrc_node_alloc(ctx, IORING_RSRC_FILE); | |
| if (!node) | |
| return -ENOMEM; | |
| if (!io_reset_rsrc_node(ctx, &ctx->file_table.data, slot_index)) | |
| io_file_bitmap_set(&ctx->file_table, slot_index); | |
| ctx->file_table.data.nodes[slot_index] = node; | |
| io_fixed_file_set(node, file); | |
| return 0; | |
| } | |
| int __io_fixed_fd_install(struct io_ring_ctx *ctx, struct file *file, | |
| unsigned int file_slot) | |
| { | |
| bool alloc_slot = file_slot == IORING_FILE_INDEX_ALLOC; | |
| int ret; | |
| if (alloc_slot) { | |
| ret = io_file_bitmap_get(ctx); | |
| if (unlikely(ret < 0)) | |
| return ret; | |
| file_slot = ret; | |
| } else { | |
| file_slot--; | |
| } | |
| ret = io_install_fixed_file(ctx, file, file_slot); | |
| if (!ret && alloc_slot) | |
| ret = file_slot; | |
| return ret; | |
| } | |
| /* | |
| * Note when io_fixed_fd_install() returns error value, it will ensure | |
| * fput() is called correspondingly. | |
| */ | |
| int io_fixed_fd_install(struct io_kiocb *req, unsigned int issue_flags, | |
| struct file *file, unsigned int file_slot) | |
| { | |
| struct io_ring_ctx *ctx = req->ctx; | |
| int ret; | |
| io_ring_submit_lock(ctx, issue_flags); | |
| ret = __io_fixed_fd_install(ctx, file, file_slot); | |
| io_ring_submit_unlock(ctx, issue_flags); | |
| if (unlikely(ret < 0)) | |
| fput(file); | |
| return ret; | |
| } | |
| int io_fixed_fd_remove(struct io_ring_ctx *ctx, unsigned int offset) | |
| { | |
| struct io_rsrc_node *node; | |
| if (unlikely(!ctx->file_table.data.nr)) | |
| return -ENXIO; | |
| if (offset >= ctx->file_table.data.nr) | |
| return -EINVAL; | |
| node = io_rsrc_node_lookup(&ctx->file_table.data, offset); | |
| if (!node) | |
| return -EBADF; | |
| io_reset_rsrc_node(ctx, &ctx->file_table.data, offset); | |
| io_file_bitmap_clear(&ctx->file_table, offset); | |
| return 0; | |
| } | |
| int io_register_file_alloc_range(struct io_ring_ctx *ctx, | |
| struct io_uring_file_index_range __user *arg) | |
| { | |
| struct io_uring_file_index_range range; | |
| u32 end; | |
| if (copy_from_user(&range, arg, sizeof(range))) | |
| return -EFAULT; | |
| if (check_add_overflow(range.off, range.len, &end)) | |
| return -EOVERFLOW; | |
| if (range.resv || end > ctx->file_table.data.nr) | |
| return -EINVAL; | |
| io_file_table_set_alloc_range(ctx, range.off, range.len); | |
| return 0; | |
| } |