diff options
author | Martin K. Petersen <martin.petersen@oracle.com> | 2020-01-10 00:14:46 -0500 |
---|---|---|
committer | Martin K. Petersen <martin.petersen@oracle.com> | 2020-01-10 00:14:46 -0500 |
commit | 1c46a2cf2dbd3146ae4a804d058679600cf6f0b9 (patch) | |
tree | fa8ab7fe4fc9a14e89d2dd000ffabc9000223046 /block | |
parent | c53cf10ef6d9faeee9baa1fab824139c6f10a134 (diff) | |
parent | 8ce156deca71808de5884d6cf951688cbf4f4a67 (diff) | |
download | lwn-1c46a2cf2dbd3146ae4a804d058679600cf6f0b9.tar.gz lwn-1c46a2cf2dbd3146ae4a804d058679600cf6f0b9.zip |
Merge tag 'block-ioctl-cleanup-5.6' into 5.6/scsi-queue
Pull compat_ioctl cleanup from Arnd. Here's his description:
This series concludes the work I did for linux-5.5 on the compat_ioctl()
cleanup, killing off fs/compat_ioctl.c and block/compat_ioctl.c by moving
everything into drivers.
Overall this would be a reduction both in complexity and line count, but
as I'm also adding documentation the overall number of lines increases
in the end.
My plan was originally to keep the SCSI and block parts separate.
This did not work easily because of interdependencies: I cannot
do the final SCSI cleanup in a good way without first addressing the
CDROM ioctls, so this is one series that I hope could be merged through
either the block or the scsi git trees, or possibly both if you can
pull in the same branch.
The series comes in these steps:
1. clean up the sg v3 interface as suggested by Linus. I have
talked about this with Doug Gilbert as well, and he would
rebase his sg v4 patches on top of "compat: scsi: sg: fix v3
compat read/write interface"
2. Actually moving handlers out of block/compat_ioctl.c and
block/scsi_ioctl.c into drivers, mixed in with cleanup
patches
3. Document how to do this right. I keep getting asked about this,
and it helps to point to some documentation file.
The branch is based on another one that fixes a couple of bugs found
during the creation of this series.
Changes since v3:
https://lore.kernel.org/lkml/20200102145552.1853992-1-arnd@arndb.de/
- Move sr_compat_ioctl fixup to correct patch (Ben Hutchings)
- Add Reviewed-by tags
Changes since v2:
https://lore.kernel.org/lkml/20191217221708.3730997-1-arnd@arndb.de/
- Rebase to v5.5-rc4, which contains the earlier bugfixes
- Fix sr_block_compat_ioctl() error handling bug found by
Ben Hutchings
- Fix idecd_locked_compat_ioctl() compat_ptr() bug
- Don't try to handle HDIO_DRIVE_TASKFILE in drivers/ide
- More documentation improvements
Changes since v1:
https://lore.kernel.org/lkml/20191211204306.1207817-1-arnd@arndb.de/
- move out the bugfixes into a branch for itself
- clean up scsi sg driver further as suggested by Christoph Hellwig
- avoid some ifdefs by moving compat_ptr() out of asm/compat.h
- split out the blkdev_compat_ptr_ioctl function; bug spotted by
Ben Hutchings
- Improve formatting of documentation
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
Diffstat (limited to 'block')
-rw-r--r-- | block/Makefile | 1 | ||||
-rw-r--r-- | block/blk-core.c | 11 | ||||
-rw-r--r-- | block/blk-flush.c | 5 | ||||
-rw-r--r-- | block/blk-iocost.c | 13 | ||||
-rw-r--r-- | block/blk-map.c | 2 | ||||
-rw-r--r-- | block/blk.h | 1 | ||||
-rw-r--r-- | block/bsg-lib.c | 2 | ||||
-rw-r--r-- | block/bsg.c | 1 | ||||
-rw-r--r-- | block/compat_ioctl.c | 411 | ||||
-rw-r--r-- | block/ioctl.c | 319 | ||||
-rw-r--r-- | block/scsi_ioctl.c | 214 |
11 files changed, 420 insertions, 560 deletions
diff --git a/block/Makefile b/block/Makefile index 205a5f2fef17..1f70c73ea83d 100644 --- a/block/Makefile +++ b/block/Makefile @@ -25,7 +25,6 @@ obj-$(CONFIG_MQ_IOSCHED_KYBER) += kyber-iosched.o bfq-y := bfq-iosched.o bfq-wf2q.o bfq-cgroup.o obj-$(CONFIG_IOSCHED_BFQ) += bfq.o -obj-$(CONFIG_BLOCK_COMPAT) += compat_ioctl.o obj-$(CONFIG_BLK_CMDLINE_PARSER) += cmdline-parser.o obj-$(CONFIG_BLK_DEV_INTEGRITY) += bio-integrity.o blk-integrity.o t10-pi.o obj-$(CONFIG_BLK_MQ_PCI) += blk-mq-pci.o diff --git a/block/blk-core.c b/block/blk-core.c index e0a094fddee5..089e890ab208 100644 --- a/block/blk-core.c +++ b/block/blk-core.c @@ -885,11 +885,14 @@ generic_make_request_checks(struct bio *bio) } /* - * For a REQ_NOWAIT based request, return -EOPNOTSUPP - * if queue is not a request based queue. + * Non-mq queues do not honor REQ_NOWAIT, so complete a bio + * with BLK_STS_AGAIN status in order to catch -EAGAIN and + * to give a chance to the caller to repeat request gracefully. */ - if ((bio->bi_opf & REQ_NOWAIT) && !queue_is_mq(q)) - goto not_supported; + if ((bio->bi_opf & REQ_NOWAIT) && !queue_is_mq(q)) { + status = BLK_STS_AGAIN; + goto end_io; + } if (should_fail_bio(bio)) goto end_io; diff --git a/block/blk-flush.c b/block/blk-flush.c index 1777346baf06..3f977c517960 100644 --- a/block/blk-flush.c +++ b/block/blk-flush.c @@ -69,6 +69,7 @@ #include <linux/blkdev.h> #include <linux/gfp.h> #include <linux/blk-mq.h> +#include <linux/lockdep.h> #include "blk.h" #include "blk-mq.h" @@ -505,6 +506,9 @@ struct blk_flush_queue *blk_alloc_flush_queue(struct request_queue *q, INIT_LIST_HEAD(&fq->flush_queue[1]); INIT_LIST_HEAD(&fq->flush_data_in_flight); + lockdep_register_key(&fq->key); + lockdep_set_class(&fq->mq_flush_lock, &fq->key); + return fq; fail_rq: @@ -519,6 +523,7 @@ void blk_free_flush_queue(struct blk_flush_queue *fq) if (!fq) return; + lockdep_unregister_key(&fq->key); kfree(fq->flush_rq); kfree(fq); } diff --git a/block/blk-iocost.c b/block/blk-iocost.c index e01267f99183..27ca68621137 100644 --- a/block/blk-iocost.c +++ b/block/blk-iocost.c @@ -1212,7 +1212,7 @@ static enum hrtimer_restart iocg_waitq_timer_fn(struct hrtimer *timer) return HRTIMER_NORESTART; } -static void iocg_kick_delay(struct ioc_gq *iocg, struct ioc_now *now, u64 cost) +static bool iocg_kick_delay(struct ioc_gq *iocg, struct ioc_now *now, u64 cost) { struct ioc *ioc = iocg->ioc; struct blkcg_gq *blkg = iocg_to_blkg(iocg); @@ -1229,11 +1229,11 @@ static void iocg_kick_delay(struct ioc_gq *iocg, struct ioc_now *now, u64 cost) /* clear or maintain depending on the overage */ if (time_before_eq64(vtime, now->vnow)) { blkcg_clear_delay(blkg); - return; + return false; } if (!atomic_read(&blkg->use_delay) && time_before_eq64(vtime, now->vnow + vmargin)) - return; + return false; /* use delay */ if (cost) { @@ -1250,10 +1250,11 @@ static void iocg_kick_delay(struct ioc_gq *iocg, struct ioc_now *now, u64 cost) oexpires = ktime_to_ns(hrtimer_get_softexpires(&iocg->delay_timer)); if (hrtimer_is_queued(&iocg->delay_timer) && abs(oexpires - expires) <= margin_ns / 4) - return; + return true; hrtimer_start_range_ns(&iocg->delay_timer, ns_to_ktime(expires), margin_ns / 4, HRTIMER_MODE_ABS); + return true; } static enum hrtimer_restart iocg_delay_timer_fn(struct hrtimer *timer) @@ -1739,7 +1740,9 @@ static void ioc_rqos_throttle(struct rq_qos *rqos, struct bio *bio) */ if (bio_issue_as_root_blkg(bio) || fatal_signal_pending(current)) { atomic64_add(abs_cost, &iocg->abs_vdebt); - iocg_kick_delay(iocg, &now, cost); + if (iocg_kick_delay(iocg, &now, cost)) + blkcg_schedule_throttle(rqos->q, + (bio->bi_opf & REQ_SWAP) == REQ_SWAP); return; } diff --git a/block/blk-map.c b/block/blk-map.c index 3a62e471d81b..b0790268ed9d 100644 --- a/block/blk-map.c +++ b/block/blk-map.c @@ -151,7 +151,7 @@ int blk_rq_map_user_iov(struct request_queue *q, struct request *rq, return 0; unmap_rq: - __blk_rq_unmap_user(bio); + blk_rq_unmap_user(bio); fail: rq->bio = NULL; return ret; diff --git a/block/blk.h b/block/blk.h index 6842f28c033e..0b8884353f6b 100644 --- a/block/blk.h +++ b/block/blk.h @@ -30,6 +30,7 @@ struct blk_flush_queue { * at the same time */ struct request *orig_rq; + struct lock_class_key key; spinlock_t mq_flush_lock; }; diff --git a/block/bsg-lib.c b/block/bsg-lib.c index 347dda16c2f4..6cbb7926534c 100644 --- a/block/bsg-lib.c +++ b/block/bsg-lib.c @@ -266,7 +266,7 @@ static blk_status_t bsg_queue_rq(struct blk_mq_hw_ctx *hctx, struct request *req = bd->rq; struct bsg_set *bset = container_of(q->tag_set, struct bsg_set, tag_set); - int sts = BLK_STS_IOERR; + blk_status_t sts = BLK_STS_IOERR; int ret; blk_mq_start_request(req); diff --git a/block/bsg.c b/block/bsg.c index 833c44b3d458..d7bae94b64d9 100644 --- a/block/bsg.c +++ b/block/bsg.c @@ -382,6 +382,7 @@ static const struct file_operations bsg_fops = { .open = bsg_open, .release = bsg_release, .unlocked_ioctl = bsg_ioctl, + .compat_ioctl = compat_ptr_ioctl, .owner = THIS_MODULE, .llseek = default_llseek, }; diff --git a/block/compat_ioctl.c b/block/compat_ioctl.c deleted file mode 100644 index 6ca015f92766..000000000000 --- a/block/compat_ioctl.c +++ /dev/null @@ -1,411 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include <linux/blkdev.h> -#include <linux/blkpg.h> -#include <linux/blktrace_api.h> -#include <linux/cdrom.h> -#include <linux/compat.h> -#include <linux/elevator.h> -#include <linux/hdreg.h> -#include <linux/slab.h> -#include <linux/syscalls.h> -#include <linux/types.h> -#include <linux/uaccess.h> - -static int compat_put_ushort(unsigned long arg, unsigned short val) -{ - return put_user(val, (unsigned short __user *)compat_ptr(arg)); -} - -static int compat_put_int(unsigned long arg, int val) -{ - return put_user(val, (compat_int_t __user *)compat_ptr(arg)); -} - -static int compat_put_uint(unsigned long arg, unsigned int val) -{ - return put_user(val, (compat_uint_t __user *)compat_ptr(arg)); -} - -static int compat_put_long(unsigned long arg, long val) -{ - return put_user(val, (compat_long_t __user *)compat_ptr(arg)); -} - -static int compat_put_ulong(unsigned long arg, compat_ulong_t val) -{ - return put_user(val, (compat_ulong_t __user *)compat_ptr(arg)); -} - -static int compat_put_u64(unsigned long arg, u64 val) -{ - return put_user(val, (compat_u64 __user *)compat_ptr(arg)); -} - -struct compat_hd_geometry { - unsigned char heads; - unsigned char sectors; - unsigned short cylinders; - u32 start; -}; - -static int compat_hdio_getgeo(struct gendisk *disk, struct block_device *bdev, - struct compat_hd_geometry __user *ugeo) -{ - struct hd_geometry geo; - int ret; - - if (!ugeo) - return -EINVAL; - if (!disk->fops->getgeo) - return -ENOTTY; - - memset(&geo, 0, sizeof(geo)); - /* - * We need to set the startsect first, the driver may - * want to override it. - */ - geo.start = get_start_sect(bdev); - ret = disk->fops->getgeo(bdev, &geo); - if (ret) - return ret; - - ret = copy_to_user(ugeo, &geo, 4); - ret |= put_user(geo.start, &ugeo->start); - if (ret) - ret = -EFAULT; - - return ret; -} - -static int compat_hdio_ioctl(struct block_device *bdev, fmode_t mode, - unsigned int cmd, unsigned long arg) -{ - unsigned long __user *p; - int error; - - p = compat_alloc_user_space(sizeof(unsigned long)); - error = __blkdev_driver_ioctl(bdev, mode, - cmd, (unsigned long)p); - if (error == 0) { - unsigned int __user *uvp = compat_ptr(arg); - unsigned long v; - if (get_user(v, p) || put_user(v, uvp)) - error = -EFAULT; - } - return error; -} - -struct compat_cdrom_read_audio { - union cdrom_addr addr; - u8 addr_format; - compat_int_t nframes; - compat_caddr_t buf; -}; - -struct compat_cdrom_generic_command { - unsigned char cmd[CDROM_PACKET_SIZE]; - compat_caddr_t buffer; - compat_uint_t buflen; - compat_int_t stat; - compat_caddr_t sense; - unsigned char data_direction; - compat_int_t quiet; - compat_int_t timeout; - compat_caddr_t reserved[1]; -}; - -static int compat_cdrom_read_audio(struct block_device *bdev, fmode_t mode, - unsigned int cmd, unsigned long arg) -{ - struct cdrom_read_audio __user *cdread_audio; - struct compat_cdrom_read_audio __user *cdread_audio32; - __u32 data; - void __user *datap; - - cdread_audio = compat_alloc_user_space(sizeof(*cdread_audio)); - cdread_audio32 = compat_ptr(arg); - - if (copy_in_user(&cdread_audio->addr, - &cdread_audio32->addr, - (sizeof(*cdread_audio32) - - sizeof(compat_caddr_t)))) - return -EFAULT; - - if (get_user(data, &cdread_audio32->buf)) - return -EFAULT; - datap = compat_ptr(data); - if (put_user(datap, &cdread_audio->buf)) - return -EFAULT; - - return __blkdev_driver_ioctl(bdev, mode, cmd, - (unsigned long)cdread_audio); -} - -static int compat_cdrom_generic_command(struct block_device *bdev, fmode_t mode, - unsigned int cmd, unsigned long arg) -{ - struct cdrom_generic_command __user *cgc; - struct compat_cdrom_generic_command __user *cgc32; - u32 data; - unsigned char dir; - int itmp; - - cgc = compat_alloc_user_space(sizeof(*cgc)); - cgc32 = compat_ptr(arg); - - if (copy_in_user(&cgc->cmd, &cgc32->cmd, sizeof(cgc->cmd)) || - get_user(data, &cgc32->buffer) || - put_user(compat_ptr(data), &cgc->buffer) || - copy_in_user(&cgc->buflen, &cgc32->buflen, - (sizeof(unsigned int) + sizeof(int))) || - get_user(data, &cgc32->sense) || - put_user(compat_ptr(data), &cgc->sense) || - get_user(dir, &cgc32->data_direction) || - put_user(dir, &cgc->data_direction) || - get_user(itmp, &cgc32->quiet) || - put_user(itmp, &cgc->quiet) || - get_user(itmp, &cgc32->timeout) || - put_user(itmp, &cgc->timeout) || - get_user(data, &cgc32->reserved[0]) || - put_user(compat_ptr(data), &cgc->reserved[0])) - return -EFAULT; - - return __blkdev_driver_ioctl(bdev, mode, cmd, (unsigned long)cgc); -} - -struct compat_blkpg_ioctl_arg { - compat_int_t op; - compat_int_t flags; - compat_int_t datalen; - compat_caddr_t data; -}; - -static int compat_blkpg_ioctl(struct block_device *bdev, fmode_t mode, - unsigned int cmd, struct compat_blkpg_ioctl_arg __user *ua32) -{ - struct blkpg_ioctl_arg __user *a = compat_alloc_user_space(sizeof(*a)); - compat_caddr_t udata; - compat_int_t n; - int err; - - err = get_user(n, &ua32->op); - err |= put_user(n, &a->op); - err |= get_user(n, &ua32->flags); - err |= put_user(n, &a->flags); - err |= get_user(n, &ua32->datalen); - err |= put_user(n, &a->datalen); - err |= get_user(udata, &ua32->data); - err |= put_user(compat_ptr(udata), &a->data); - if (err) - return err; - - return blkdev_ioctl(bdev, mode, cmd, (unsigned long)a); -} - -#define BLKBSZGET_32 _IOR(0x12, 112, int) -#define BLKBSZSET_32 _IOW(0x12, 113, int) -#define BLKGETSIZE64_32 _IOR(0x12, 114, int) - -static int compat_blkdev_driver_ioctl(struct block_device *bdev, fmode_t mode, - unsigned cmd, unsigned long arg) -{ - switch (cmd) { - case HDIO_GET_UNMASKINTR: - case HDIO_GET_MULTCOUNT: - case HDIO_GET_KEEPSETTINGS: - case HDIO_GET_32BIT: - case HDIO_GET_NOWERR: - case HDIO_GET_DMA: - case HDIO_GET_NICE: - case HDIO_GET_WCACHE: - case HDIO_GET_ACOUSTIC: - case HDIO_GET_ADDRESS: - case HDIO_GET_BUSSTATE: - return compat_hdio_ioctl(bdev, mode, cmd, arg); - case CDROMREADAUDIO: - return compat_cdrom_read_audio(bdev, mode, cmd, arg); - case CDROM_SEND_PACKET: - return compat_cdrom_generic_command(bdev, mode, cmd, arg); - - /* - * No handler required for the ones below, we just need to - * convert arg to a 64 bit pointer. - */ - case BLKSECTSET: - /* - * 0x03 -- HD/IDE ioctl's used by hdparm and friends. - * Some need translations, these do not. - */ - case HDIO_GET_IDENTITY: - case HDIO_DRIVE_TASK: - case HDIO_DRIVE_CMD: - /* 0x330 is reserved -- it used to be HDIO_GETGEO_BIG */ - case 0x330: - /* CDROM stuff */ - case CDROMPAUSE: - case CDROMRESUME: - case CDROMPLAYMSF: - case CDROMPLAYTRKIND: - case CDROMREADTOCHDR: - case CDROMREADTOCENTRY: - case CDROMSTOP: - case CDROMSTART: - case CDROMEJECT: - case CDROMVOLCTRL: - case CDROMSUBCHNL: - case CDROMMULTISESSION: - case CDROM_GET_MCN: - case CDROMRESET: - case CDROMVOLREAD: - case CDROMSEEK: - case CDROMPLAYBLK: - case CDROMCLOSETRAY: - case CDROM_DISC_STATUS: - case CDROM_CHANGER_NSLOTS: - case CDROM_GET_CAPABILITY: - /* Ignore cdrom.h about these next 5 ioctls, they absolutely do - * not take a struct cdrom_read, instead they take a struct cdrom_msf - * which is compatible. - */ - case CDROMREADMODE2: - case CDROMREADMODE1: - case CDROMREADRAW: - case CDROMREADCOOKED: - case CDROMREADALL: - /* DVD ioctls */ - case DVD_READ_STRUCT: - case DVD_WRITE_STRUCT: - case DVD_AUTH: - arg = (unsigned long)compat_ptr(arg); - /* These intepret arg as an unsigned long, not as a pointer, - * so we must not do compat_ptr() conversion. */ - case HDIO_SET_MULTCOUNT: - case HDIO_SET_UNMASKINTR: - case HDIO_SET_KEEPSETTINGS: - case HDIO_SET_32BIT: - case HDIO_SET_NOWERR: - case HDIO_SET_DMA: - case HDIO_SET_PIO_MODE: - case HDIO_SET_NICE: - case HDIO_SET_WCACHE: - case HDIO_SET_ACOUSTIC: - case HDIO_SET_BUSSTATE: - case HDIO_SET_ADDRESS: - case CDROMEJECT_SW: - case CDROM_SET_OPTIONS: - case CDROM_CLEAR_OPTIONS: - case CDROM_SELECT_SPEED: - case CDROM_SELECT_DISC: - case CDROM_MEDIA_CHANGED: - case CDROM_DRIVE_STATUS: - case CDROM_LOCKDOOR: - case CDROM_DEBUG: - break; - default: - /* unknown ioctl number */ - return -ENOIOCTLCMD; - } - - return __blkdev_driver_ioctl(bdev, mode, cmd, arg); -} - -/* Most of the generic ioctls are handled in the normal fallback path. - This assumes the blkdev's low level compat_ioctl always returns - ENOIOCTLCMD for unknown ioctls. */ -long compat_blkdev_ioctl(struct file *file, unsigned cmd, unsigned long arg) -{ - int ret = -ENOIOCTLCMD; - struct inode *inode = file->f_mapping->host; - struct block_device *bdev = inode->i_bdev; - struct gendisk *disk = bdev->bd_disk; - fmode_t mode = file->f_mode; - loff_t size; - unsigned int max_sectors; - - /* - * O_NDELAY can be altered using fcntl(.., F_SETFL, ..), so we have - * to updated it before every ioctl. - */ - if (file->f_flags & O_NDELAY) - mode |= FMODE_NDELAY; - else - mode &= ~FMODE_NDELAY; - - switch (cmd) { - case HDIO_GETGEO: - return compat_hdio_getgeo(disk, bdev, compat_ptr(arg)); - case BLKPBSZGET: - return compat_put_uint(arg, bdev_physical_block_size(bdev)); - case BLKIOMIN: - return compat_put_uint(arg, bdev_io_min(bdev)); - case BLKIOOPT: - return compat_put_uint(arg, bdev_io_opt(bdev)); - case BLKALIGNOFF: - return compat_put_int(arg, bdev_alignment_offset(bdev)); - case BLKDISCARDZEROES: - return compat_put_uint(arg, 0); - case BLKFLSBUF: - case BLKROSET: - case BLKDISCARD: - case BLKSECDISCARD: - case BLKZEROOUT: - /* - * the ones below are implemented in blkdev_locked_ioctl, - * but we call blkdev_ioctl, which gets the lock for us - */ - case BLKRRPART: - return blkdev_ioctl(bdev, mode, cmd, - (unsigned long)compat_ptr(arg)); - case BLKBSZSET_32: - return blkdev_ioctl(bdev, mode, BLKBSZSET, - (unsigned long)compat_ptr(arg)); - case BLKPG: - return compat_blkpg_ioctl(bdev, mode, cmd, compat_ptr(arg)); - case BLKRAGET: - case BLKFRAGET: - if (!arg) - return -EINVAL; - return compat_put_long(arg, - (bdev->bd_bdi->ra_pages * PAGE_SIZE) / 512); - case BLKROGET: /* compatible */ - return compat_put_int(arg, bdev_read_only(bdev) != 0); - case BLKBSZGET_32: /* get the logical block size (cf. BLKSSZGET) */ - return compat_put_int(arg, block_size(bdev)); - case BLKSSZGET: /* get block device hardware sector size */ - return compat_put_int(arg, bdev_logical_block_size(bdev)); - case BLKSECTGET: - max_sectors = min_t(unsigned int, USHRT_MAX, - queue_max_sectors(bdev_get_queue(bdev))); - return compat_put_ushort(arg, max_sectors); - case BLKROTATIONAL: - return compat_put_ushort(arg, - !blk_queue_nonrot(bdev_get_queue(bdev))); - case BLKRASET: /* compatible, but no compat_ptr (!) */ - case BLKFRASET: - if (!capable(CAP_SYS_ADMIN)) - return -EACCES; - bdev->bd_bdi->ra_pages = (arg * 512) / PAGE_SIZE; - return 0; - case BLKGETSIZE: - size = i_size_read(bdev->bd_inode); - if ((size >> 9) > ~0UL) - return -EFBIG; - return compat_put_ulong(arg, size >> 9); - - case BLKGETSIZE64_32: - return compat_put_u64(arg, i_size_read(bdev->bd_inode)); - - case BLKTRACESETUP32: - case BLKTRACESTART: /* compatible */ - case BLKTRACESTOP: /* compatible */ - case BLKTRACETEARDOWN: /* compatible */ - ret = blk_trace_ioctl(bdev, cmd, compat_ptr(arg)); - return ret; - default: - if (disk->fops->compat_ioctl) - ret = disk->fops->compat_ioctl(bdev, mode, cmd, arg); - if (ret == -ENOIOCTLCMD) - ret = compat_blkdev_driver_ioctl(bdev, mode, cmd, arg); - return ret; - } -} diff --git a/block/ioctl.c b/block/ioctl.c index 5de98b97af2a..127194b9f9bd 100644 --- a/block/ioctl.c +++ b/block/ioctl.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 #include <linux/capability.h> +#include <linux/compat.h> #include <linux/blkdev.h> #include <linux/export.h> #include <linux/gfp.h> @@ -11,12 +12,12 @@ #include <linux/pr.h> #include <linux/uaccess.h> -static int blkpg_ioctl(struct block_device *bdev, struct blkpg_ioctl_arg __user *arg) +static int blkpg_do_ioctl(struct block_device *bdev, + struct blkpg_partition __user *upart, int op) { struct block_device *bdevp; struct gendisk *disk; struct hd_struct *part, *lpart; - struct blkpg_ioctl_arg a; struct blkpg_partition p; struct disk_part_iter piter; long long start, length; @@ -24,9 +25,7 @@ static int blkpg_ioctl(struct block_device *bdev, struct blkpg_ioctl_arg __user if (!capable(CAP_SYS_ADMIN)) return -EACCES; - if (copy_from_user(&a, arg, sizeof(struct blkpg_ioctl_arg))) - return -EFAULT; - if (copy_from_user(&p, a.data, sizeof(struct blkpg_partition))) + if (copy_from_user(&p, upart, sizeof(struct blkpg_partition))) return -EFAULT; disk = bdev->bd_disk; if (bdev != bdev->bd_contains) @@ -34,7 +33,7 @@ static int blkpg_ioctl(struct block_device *bdev, struct blkpg_ioctl_arg __user partno = p.pno; if (partno <= 0) return -EINVAL; - switch (a.op) { + switch (op) { case BLKPG_ADD_PARTITION: start = p.start >> 9; length = p.length >> 9; @@ -155,6 +154,39 @@ static int blkpg_ioctl(struct block_device *bdev, struct blkpg_ioctl_arg __user } } +static int blkpg_ioctl(struct block_device *bdev, + struct blkpg_ioctl_arg __user *arg) +{ + struct blkpg_partition __user *udata; + int op; + + if (get_user(op, &arg->op) || get_user(udata, &arg->data)) + return -EFAULT; + + return blkpg_do_ioctl(bdev, udata, op); +} + +#ifdef CONFIG_COMPAT +struct compat_blkpg_ioctl_arg { + compat_int_t op; + compat_int_t flags; + compat_int_t datalen; + compat_caddr_t data; +}; + +static int compat_blkpg_ioctl(struct block_device *bdev, + struct compat_blkpg_ioctl_arg __user *arg) +{ + compat_caddr_t udata; + int op; + + if (get_user(op, &arg->op) || get_user(udata, &arg->data)) + return -EFAULT; + + return blkpg_do_ioctl(bdev, compat_ptr(udata), op); +} +#endif + static int blkdev_reread_part(struct block_device *bdev) { int ret; @@ -238,36 +270,48 @@ static int blk_ioctl_zeroout(struct block_device *bdev, fmode_t mode, BLKDEV_ZERO_NOUNMAP); } -static int put_ushort(unsigned long arg, unsigned short val) +static int put_ushort(unsigned short __user *argp, unsigned short val) { - return put_user(val, (unsigned short __user *)arg); + return put_user(val, argp); } -static int put_int(unsigned long arg, int val) +static int put_int(int __user *argp, int val) { - return put_user(val, (int __user *)arg); + return put_user(val, argp); } -static int put_uint(unsigned long arg, unsigned int val) +static int put_uint(unsigned int __user *argp, unsigned int val) { - return put_user(val, (unsigned int __user *)arg); + return put_user(val, argp); } -static int put_long(unsigned long arg, long val) +static int put_long(long __user *argp, long val) { - return put_user(val, (long __user *)arg); + return put_user(val, argp); } -static int put_ulong(unsigned long arg, unsigned long val) +static int put_ulong(unsigned long __user *argp, unsigned long val) { - return put_user(val, (unsigned long __user *)arg); + return put_user(val, argp); } -static int put_u64(unsigned long arg, u64 val) +static int put_u64(u64 __user *argp, u64 val) { - return put_user(val, (u64 __user *)arg); + return put_user(val, argp); } +#ifdef CONFIG_COMPAT +static int compat_put_long(compat_long_t *argp, long val) +{ + return put_user(val, argp); +} + +static int compat_put_ulong(compat_ulong_t *argp, compat_ulong_t val) +{ + return put_user(val, argp); +} +#endif + int __blkdev_driver_ioctl(struct block_device *bdev, fmode_t mode, unsigned cmd, unsigned long arg) { @@ -285,6 +329,26 @@ int __blkdev_driver_ioctl(struct block_device *bdev, fmode_t mode, */ EXPORT_SYMBOL_GPL(__blkdev_driver_ioctl); +#ifdef CONFIG_COMPAT +/* + * This is the equivalent of compat_ptr_ioctl(), to be used by block + * drivers that implement only commands that are completely compatible + * between 32-bit and 64-bit user space + */ +int blkdev_compat_ptr_ioctl(struct block_device *bdev, fmode_t mode, + unsigned cmd, unsigned long arg) +{ + struct gendisk *disk = bdev->bd_disk; + + if (disk->fops->ioctl) + return disk->fops->ioctl(bdev, mode, cmd, + (unsigned long)compat_ptr(arg)); + + return -ENOIOCTLCMD; +} +EXPORT_SYMBOL(blkdev_compat_ptr_ioctl); +#endif + static int blkdev_pr_register(struct block_device *bdev, struct pr_registration __user *arg) { @@ -455,6 +519,45 @@ static int blkdev_getgeo(struct block_device *bdev, return 0; } +#ifdef CONFIG_COMPAT +struct compat_hd_geometry { + unsigned char heads; + unsigned char sectors; + unsigned short cylinders; + u32 start; +}; + +static int compat_hdio_getgeo(struct block_device *bdev, + struct compat_hd_geometry __user *ugeo) +{ + struct gendisk *disk = bdev->bd_disk; + struct hd_geometry geo; + int ret; + + if (!ugeo) + return -EINVAL; + if (!disk->fops->getgeo) + return -ENOTTY; + + memset(&geo, 0, sizeof(geo)); + /* + * We need to set the startsect first, the driver may + * want to override it. + */ + geo.start = get_start_sect(bdev); + ret = disk->fops->getgeo(bdev, &geo); + if (ret) + return ret; + + ret = copy_to_user(ugeo, &geo, 4); + ret |= put_user(geo.start, &ugeo->start); + if (ret) + ret = -EFAULT; + + return ret; +} +#endif + /* set the logical block size */ static int blkdev_bszset(struct block_device *bdev, fmode_t mode, int __user *argp) @@ -481,13 +584,13 @@ static int blkdev_bszset(struct block_device *bdev, fmode_t mode, } /* - * always keep this in sync with compat_blkdev_ioctl() + * Common commands that are handled the same way on native and compat + * user space. Note the separate arg/argp parameters that are needed + * to deal with the compat_ptr() conversion. */ -int blkdev_ioctl(struct block_device *bdev, fmode_t mode, unsigned cmd, - unsigned long arg) +static int blkdev_common_ioctl(struct block_device *bdev, fmode_t mode, + unsigned cmd, unsigned long arg, void __user *argp) { - void __user *argp = (void __user *)arg; - loff_t size; unsigned int max_sectors; switch (cmd) { @@ -510,60 +613,39 @@ int blkdev_ioctl(struct block_device *bdev, fmode_t mode, unsigned cmd, case BLKFINISHZONE: return blkdev_zone_mgmt_ioctl(bdev, mode, cmd, arg); case BLKGETZONESZ: - return put_uint(arg, bdev_zone_sectors(bdev)); + return put_uint(argp, bdev_zone_sectors(bdev)); case BLKGETNRZONES: - return put_uint(arg, blkdev_nr_zones(bdev->bd_disk)); - case HDIO_GETGEO: - return blkdev_getgeo(bdev, argp); - case BLKRAGET: - case BLKFRAGET: - if (!arg) - return -EINVAL; - return put_long(arg, (bdev->bd_bdi->ra_pages*PAGE_SIZE) / 512); + return put_uint(argp, blkdev_nr_zones(bdev->bd_disk)); case BLKROGET: - return put_int(arg, bdev_read_only(bdev) != 0); - case BLKBSZGET: /* get block device soft block size (cf. BLKSSZGET) */ - return put_int(arg, block_size(bdev)); + return put_int(argp, bdev_read_only(bdev) != 0); case BLKSSZGET: /* get block device logical block size */ - return put_int(arg, bdev_logical_block_size(bdev)); + return put_int(argp, bdev_logical_block_size(bdev)); case BLKPBSZGET: /* get block device physical block size */ - return put_uint(arg, bdev_physical_block_size(bdev)); + return put_uint(argp, bdev_physical_block_size(bdev)); case BLKIOMIN: - return put_uint(arg, bdev_io_min(bdev)); + return put_uint(argp, bdev_io_min(bdev)); case BLKIOOPT: - return put_uint(arg, bdev_io_opt(bdev)); + return put_uint(argp, bdev_io_opt(bdev)); case BLKALIGNOFF: - return put_int(arg, bdev_alignment_offset(bdev)); + return put_int(argp, bdev_alignment_offset(bdev)); case BLKDISCARDZEROES: - return put_uint(arg, 0); + return put_uint(argp, 0); case BLKSECTGET: max_sectors = min_t(unsigned int, USHRT_MAX, queue_max_sectors(bdev_get_queue(bdev))); - return put_ushort(arg, max_sectors); + return put_ushort(argp, max_sectors); case BLKROTATIONAL: - return put_ushort(arg, !blk_queue_nonrot(bdev_get_queue(bdev))); + return put_ushort(argp, !blk_queue_nonrot(bdev_get_queue(bdev))); case BLKRASET: case BLKFRASET: if(!capable(CAP_SYS_ADMIN)) return -EACCES; bdev->bd_bdi->ra_pages = (arg * 512) / PAGE_SIZE; return 0; - case BLKBSZSET: - return blkdev_bszset(bdev, mode, argp); - case BLKPG: - return blkpg_ioctl(bdev, argp); case BLKRRPART: return blkdev_reread_part(bdev); - case BLKGETSIZE: - size = i_size_read(bdev->bd_inode); - if ((size >> 9) > ~0UL) - return -EFBIG; - return put_ulong(arg, size >> 9); - case BLKGETSIZE64: - return put_u64(arg, i_size_read(bdev->bd_inode)); case BLKTRACESTART: case BLKTRACESTOP: - case BLKTRACESETUP: case BLKTRACETEARDOWN: return blk_trace_ioctl(bdev, cmd, argp); case IOC_PR_REGISTER: @@ -579,7 +661,132 @@ int blkdev_ioctl(struct block_device *bdev, fmode_t mode, unsigned cmd, case IOC_PR_CLEAR: return blkdev_pr_clear(bdev, argp); default: + return -ENOIOCTLCMD; + } +} + +/* + * Always keep this in sync with compat_blkdev_ioctl() + * to handle all incompatible commands in both functions. + * + * New commands must be compatible and go into blkdev_common_ioctl + */ +int blkdev_ioctl(struct block_device *bdev, fmode_t mode, unsigned cmd, + unsigned long arg) +{ + int ret; + loff_t size; + void __user *argp = (void __user *)arg; + + switch (cmd) { + /* These need separate implementations for the data structure */ + case HDIO_GETGEO: + return blkdev_getgeo(bdev, argp); + case BLKPG: + return blkpg_ioctl(bdev, argp); + + /* Compat mode returns 32-bit data instead of 'long' */ + case BLKRAGET: + case BLKFRAGET: + if (!argp) + return -EINVAL; + return put_long(argp, (bdev->bd_bdi->ra_pages*PAGE_SIZE) / 512); + case BLKGETSIZE: + size = i_size_read(bdev->bd_inode); + if ((size >> 9) > ~0UL) + return -EFBIG; + return put_ulong(argp, size >> 9); + + /* The data is compatible, but the command number is different */ + case BLKBSZGET: /* get block device soft block size (cf. BLKSSZGET) */ + return put_int(argp, block_size(bdev)); + case BLKBSZSET: + return blkdev_bszset(bdev, mode, argp); + case BLKGETSIZE64: + return put_u64(argp, i_size_read(bdev->bd_inode)); + + /* Incompatible alignment on i386 */ + case BLKTRACESETUP: + return blk_trace_ioctl(bdev, cmd, argp); + default: + break; + } + + ret = blkdev_common_ioctl(bdev, mode, cmd, arg, argp); + if (ret == -ENOIOCTLCMD) return __blkdev_driver_ioctl(bdev, mode, cmd, arg); + + return ret; +} +EXPORT_SYMBOL_GPL(blkdev_ioctl); /* for /dev/raw */ + +#ifdef CONFIG_COMPAT + +#define BLKBSZGET_32 _IOR(0x12, 112, int) +#define BLKBSZSET_32 _IOW(0x12, 113, int) +#define BLKGETSIZE64_32 _IOR(0x12, 114, int) + +/* Most of the generic ioctls are handled in the normal fallback path. + This assumes the blkdev's low level compat_ioctl always returns + ENOIOCTLCMD for unknown ioctls. */ +long compat_blkdev_ioctl(struct file *file, unsigned cmd, unsigned long arg) +{ + int ret; + void __user *argp = compat_ptr(arg); + struct inode *inode = file->f_mapping->host; + struct block_device *bdev = inode->i_bdev; + struct gendisk *disk = bdev->bd_disk; + fmode_t mode = file->f_mode; + loff_t size; + + /* + * O_NDELAY can be altered using fcntl(.., F_SETFL, ..), so we have + * to updated it before every ioctl. + */ + if (file->f_flags & O_NDELAY) + mode |= FMODE_NDELAY; + else + mode &= ~FMODE_NDELAY; + + switch (cmd) { + /* These need separate implementations for the data structure */ + case HDIO_GETGEO: + return compat_hdio_getgeo(bdev, argp); + case BLKPG: + return compat_blkpg_ioctl(bdev, argp); + + /* Compat mode returns 32-bit data instead of 'long' */ + case BLKRAGET: + case BLKFRAGET: + if (!argp) + return -EINVAL; + return compat_put_long(argp, + (bdev->bd_bdi->ra_pages * PAGE_SIZE) / 512); + case BLKGETSIZE: + size = i_size_read(bdev->bd_inode); + if ((size >> 9) > ~0UL) + return -EFBIG; + return compat_put_ulong(argp, size >> 9); + + /* The data is compatible, but the command number is different */ + case BLKBSZGET_32: /* get the logical block size (cf. BLKSSZGET) */ + return put_int(argp, bdev_logical_block_size(bdev)); + case BLKBSZSET_32: + return blkdev_bszset(bdev, mode, argp); + case BLKGETSIZE64_32: + return put_u64(argp, i_size_read(bdev->bd_inode)); + + /* Incompatible alignment on i386 */ + case BLKTRACESETUP32: + return blk_trace_ioctl(bdev, cmd, argp); + default: + break; } + + ret = blkdev_common_ioctl(bdev, mode, cmd, arg, argp); + if (ret == -ENOIOCTLCMD && disk->fops->compat_ioctl) + ret = disk->fops->compat_ioctl(bdev, mode, cmd, arg); + + return ret; } -EXPORT_SYMBOL_GPL(blkdev_ioctl); +#endif diff --git a/block/scsi_ioctl.c b/block/scsi_ioctl.c index 650bade5ea5a..b4e73d5dd5c2 100644 --- a/block/scsi_ioctl.c +++ b/block/scsi_ioctl.c @@ -20,6 +20,7 @@ #include <scsi/scsi.h> #include <scsi/scsi_ioctl.h> #include <scsi/scsi_cmnd.h> +#include <scsi/sg.h> struct blk_cmd_filter { unsigned long read_ok[BLK_SCSI_CMD_PER_LONG]; @@ -550,34 +551,6 @@ static inline int blk_send_start_stop(struct request_queue *q, return __blk_send_generic(q, bd_disk, GPCMD_START_STOP_UNIT, data); } -#ifdef CONFIG_COMPAT -struct compat_sg_io_hdr { - compat_int_t interface_id; /* [i] 'S' for SCSI generic (required) */ - compat_int_t dxfer_direction; /* [i] data transfer direction */ - unsigned char cmd_len; /* [i] SCSI command length ( <= 16 bytes) */ - unsigned char mx_sb_len; /* [i] max length to write to sbp */ - unsigned short iovec_count; /* [i] 0 implies no scatter gather */ - compat_uint_t dxfer_len; /* [i] byte count of data transfer */ - compat_uint_t dxferp; /* [i], [*io] points to data transfer memory - or scatter gather list */ - compat_uptr_t cmdp; /* [i], [*i] points to command to perform */ - compat_uptr_t sbp; /* [i], [*o] points to sense_buffer memory */ - compat_uint_t timeout; /* [i] MAX_UINT->no timeout (unit: millisec) */ - compat_uint_t flags; /* [i] 0 -> default, see SG_FLAG... */ - compat_int_t pack_id; /* [i->o] unused internally (normally) */ - compat_uptr_t usr_ptr; /* [i->o] unused internally */ - unsigned char status; /* [o] scsi status */ - unsigned char masked_status; /* [o] shifted, masked scsi status */ - unsigned char msg_status; /* [o] messaging level data (optional) */ - unsigned char sb_len_wr; /* [o] byte count actually written to sbp */ - unsigned short host_status; /* [o] errors from host adapter */ - unsigned short driver_status; /* [o] errors from software driver */ - compat_int_t resid; /* [o] dxfer_len - actual_transferred */ - compat_uint_t duration; /* [o] time taken by cmd (unit: millisec) */ - compat_uint_t info; /* [o] auxiliary information */ -}; -#endif - int put_sg_io_hdr(const struct sg_io_hdr *hdr, void __user *argp) { #ifdef CONFIG_COMPAT @@ -666,6 +639,136 @@ int get_sg_io_hdr(struct sg_io_hdr *hdr, const void __user *argp) } EXPORT_SYMBOL(get_sg_io_hdr); +#ifdef CONFIG_COMPAT +struct compat_cdrom_generic_command { + unsigned char cmd[CDROM_PACKET_SIZE]; + compat_caddr_t buffer; + compat_uint_t buflen; + compat_int_t stat; + compat_caddr_t sense; + unsigned char data_direction; + compat_int_t quiet; + compat_int_t timeout; + compat_caddr_t reserved[1]; +}; +#endif + +static int scsi_get_cdrom_generic_arg(struct cdrom_generic_command *cgc, + const void __user *arg) +{ +#ifdef CONFIG_COMPAT + if (in_compat_syscall()) { + struct compat_cdrom_generic_command cgc32; + + if (copy_from_user(&cgc32, arg, sizeof(cgc32))) + return -EFAULT; + + *cgc = (struct cdrom_generic_command) { + .buffer = compat_ptr(cgc32.buffer), + .buflen = cgc32.buflen, + .stat = cgc32.stat, + .sense = compat_ptr(cgc32.sense), + .data_direction = cgc32.data_direction, + .quiet = cgc32.quiet, + .timeout = cgc32.timeout, + .reserved[0] = compat_ptr(cgc32.reserved[0]), + }; + memcpy(&cgc->cmd, &cgc32.cmd, CDROM_PACKET_SIZE); + return 0; + } +#endif + if (copy_from_user(cgc, arg, sizeof(*cgc))) + return -EFAULT; + + return 0; +} + +static int scsi_put_cdrom_generic_arg(const struct cdrom_generic_command *cgc, + void __user *arg) +{ +#ifdef CONFIG_COMPAT + if (in_compat_syscall()) { + struct compat_cdrom_generic_command cgc32 = { + .buffer = (uintptr_t)(cgc->buffer), + .buflen = cgc->buflen, + .stat = cgc->stat, + .sense = (uintptr_t)(cgc->sense), + .data_direction = cgc->data_direction, + .quiet = cgc->quiet, + .timeout = cgc->timeout, + .reserved[0] = (uintptr_t)(cgc->reserved[0]), + }; + memcpy(&cgc32.cmd, &cgc->cmd, CDROM_PACKET_SIZE); + + if (copy_to_user(arg, &cgc32, sizeof(cgc32))) + return -EFAULT; + + return 0; + } +#endif + if (copy_to_user(arg, cgc, sizeof(*cgc))) + return -EFAULT; + + return 0; +} + +static int scsi_cdrom_send_packet(struct request_queue *q, + struct gendisk *bd_disk, + fmode_t mode, void __user *arg) +{ + struct cdrom_generic_command cgc; + struct sg_io_hdr hdr; + int err; + + err = scsi_get_cdrom_generic_arg(&cgc, arg); + if (err) + return err; + + cgc.timeout = clock_t_to_jiffies(cgc.timeout); + memset(&hdr, 0, sizeof(hdr)); + hdr.interface_id = 'S'; + hdr.cmd_len = sizeof(cgc.cmd); + hdr.dxfer_len = cgc.buflen; + switch (cgc.data_direction) { + case CGC_DATA_UNKNOWN: + hdr.dxfer_direction = SG_DXFER_UNKNOWN; + break; + case CGC_DATA_WRITE: + hdr.dxfer_direction = SG_DXFER_TO_DEV; + break; + case CGC_DATA_READ: + hdr.dxfer_direction = SG_DXFER_FROM_DEV; + break; + case CGC_DATA_NONE: + hdr.dxfer_direction = SG_DXFER_NONE; + break; + default: + return -EINVAL; + } + + hdr.dxferp = cgc.buffer; + hdr.sbp = cgc.sense; + if (hdr.sbp) + hdr.mx_sb_len = sizeof(struct request_sense); + hdr.timeout = jiffies_to_msecs(cgc.timeout); + hdr.cmdp = ((struct cdrom_generic_command __user*) arg)->cmd; + hdr.cmd_len = sizeof(cgc.cmd); + + err = sg_io(q, bd_disk, &hdr, mode); + if (err == -EFAULT) + return -EFAULT; + + if (hdr.status) + return -EIO; + + cgc.stat = err; + cgc.buflen = hdr.resid; + if (scsi_put_cdrom_generic_arg(&cgc, arg)) + return -EFAULT; + + return err; +} + int scsi_cmd_ioctl(struct request_queue *q, struct gendisk *bd_disk, fmode_t mode, unsigned int cmd, void __user *arg) { @@ -716,60 +819,9 @@ int scsi_cmd_ioctl(struct request_queue *q, struct gendisk *bd_disk, fmode_t mod err = -EFAULT; break; } - case CDROM_SEND_PACKET: { - struct cdrom_generic_command cgc; - struct sg_io_hdr hdr; - - err = -EFAULT; - if (copy_from_user(&cgc, arg, sizeof(cgc))) - break; - cgc.timeout = clock_t_to_jiffies(cgc.timeout); - memset(&hdr, 0, sizeof(hdr)); - hdr.interface_id = 'S'; - hdr.cmd_len = sizeof(cgc.cmd); - hdr.dxfer_len = cgc.buflen; - err = 0; - switch (cgc.data_direction) { - case CGC_DATA_UNKNOWN: - hdr.dxfer_direction = SG_DXFER_UNKNOWN; - break; - case CGC_DATA_WRITE: - hdr.dxfer_direction = SG_DXFER_TO_DEV; - break; - case CGC_DATA_READ: - hdr.dxfer_direction = SG_DXFER_FROM_DEV; - break; - case CGC_DATA_NONE: - hdr.dxfer_direction = SG_DXFER_NONE; - break; - default: - err = -EINVAL; - } - if (err) - break; - - hdr.dxferp = cgc.buffer; - hdr.sbp = cgc.sense; - if (hdr.sbp) - hdr.mx_sb_len = sizeof(struct request_sense); - hdr.timeout = jiffies_to_msecs(cgc.timeout); - hdr.cmdp = ((struct cdrom_generic_command __user*) arg)->cmd; - hdr.cmd_len = sizeof(cgc.cmd); - - err = sg_io(q, bd_disk, &hdr, mode); - if (err == -EFAULT) - break; - - if (hdr.status) - err = -EIO; - - cgc.stat = err; - cgc.buflen = hdr.resid; - if (copy_to_user(arg, &cgc, sizeof(cgc))) - err = -EFAULT; - + case CDROM_SEND_PACKET: + err = scsi_cdrom_send_packet(q, bd_disk, mode, arg); break; - } /* * old junk scsi send command ioctl |