xref: /linux/block/ioctl.c (revision c59ede7b78db329949d9cdcd7064e22d357560ef)
1*c59ede7bSRandy.Dunlap #include <linux/capability.h>
21da177e4SLinus Torvalds #include <linux/blkdev.h>
31da177e4SLinus Torvalds #include <linux/blkpg.h>
4a885c8c4SChristoph Hellwig #include <linux/hdreg.h>
51da177e4SLinus Torvalds #include <linux/backing-dev.h>
61da177e4SLinus Torvalds #include <linux/buffer_head.h>
71da177e4SLinus Torvalds #include <linux/smp_lock.h>
81da177e4SLinus Torvalds #include <asm/uaccess.h>
91da177e4SLinus Torvalds 
101da177e4SLinus Torvalds static int blkpg_ioctl(struct block_device *bdev, struct blkpg_ioctl_arg __user *arg)
111da177e4SLinus Torvalds {
121da177e4SLinus Torvalds 	struct block_device *bdevp;
131da177e4SLinus Torvalds 	struct gendisk *disk;
141da177e4SLinus Torvalds 	struct blkpg_ioctl_arg a;
151da177e4SLinus Torvalds 	struct blkpg_partition p;
161da177e4SLinus Torvalds 	long long start, length;
171da177e4SLinus Torvalds 	int part;
181da177e4SLinus Torvalds 	int i;
191da177e4SLinus Torvalds 
201da177e4SLinus Torvalds 	if (!capable(CAP_SYS_ADMIN))
211da177e4SLinus Torvalds 		return -EACCES;
221da177e4SLinus Torvalds 	if (copy_from_user(&a, arg, sizeof(struct blkpg_ioctl_arg)))
231da177e4SLinus Torvalds 		return -EFAULT;
241da177e4SLinus Torvalds 	if (copy_from_user(&p, a.data, sizeof(struct blkpg_partition)))
251da177e4SLinus Torvalds 		return -EFAULT;
261da177e4SLinus Torvalds 	disk = bdev->bd_disk;
271da177e4SLinus Torvalds 	if (bdev != bdev->bd_contains)
281da177e4SLinus Torvalds 		return -EINVAL;
291da177e4SLinus Torvalds 	part = p.pno;
301da177e4SLinus Torvalds 	if (part <= 0 || part >= disk->minors)
311da177e4SLinus Torvalds 		return -EINVAL;
321da177e4SLinus Torvalds 	switch (a.op) {
331da177e4SLinus Torvalds 		case BLKPG_ADD_PARTITION:
341da177e4SLinus Torvalds 			start = p.start >> 9;
351da177e4SLinus Torvalds 			length = p.length >> 9;
361da177e4SLinus Torvalds 			/* check for fit in a hd_struct */
371da177e4SLinus Torvalds 			if (sizeof(sector_t) == sizeof(long) &&
381da177e4SLinus Torvalds 			    sizeof(long long) > sizeof(long)) {
391da177e4SLinus Torvalds 				long pstart = start, plength = length;
401da177e4SLinus Torvalds 				if (pstart != start || plength != length
411da177e4SLinus Torvalds 				    || pstart < 0 || plength < 0)
421da177e4SLinus Torvalds 					return -EINVAL;
431da177e4SLinus Torvalds 			}
441da177e4SLinus Torvalds 			/* partition number in use? */
451da177e4SLinus Torvalds 			down(&bdev->bd_sem);
461da177e4SLinus Torvalds 			if (disk->part[part - 1]) {
471da177e4SLinus Torvalds 				up(&bdev->bd_sem);
481da177e4SLinus Torvalds 				return -EBUSY;
491da177e4SLinus Torvalds 			}
501da177e4SLinus Torvalds 			/* overlap? */
511da177e4SLinus Torvalds 			for (i = 0; i < disk->minors - 1; i++) {
521da177e4SLinus Torvalds 				struct hd_struct *s = disk->part[i];
531da177e4SLinus Torvalds 
541da177e4SLinus Torvalds 				if (!s)
551da177e4SLinus Torvalds 					continue;
561da177e4SLinus Torvalds 				if (!(start+length <= s->start_sect ||
571da177e4SLinus Torvalds 				      start >= s->start_sect + s->nr_sects)) {
581da177e4SLinus Torvalds 					up(&bdev->bd_sem);
591da177e4SLinus Torvalds 					return -EBUSY;
601da177e4SLinus Torvalds 				}
611da177e4SLinus Torvalds 			}
621da177e4SLinus Torvalds 			/* all seems OK */
631da177e4SLinus Torvalds 			add_partition(disk, part, start, length);
641da177e4SLinus Torvalds 			up(&bdev->bd_sem);
651da177e4SLinus Torvalds 			return 0;
661da177e4SLinus Torvalds 		case BLKPG_DEL_PARTITION:
671da177e4SLinus Torvalds 			if (!disk->part[part-1])
681da177e4SLinus Torvalds 				return -ENXIO;
691da177e4SLinus Torvalds 			if (disk->part[part - 1]->nr_sects == 0)
701da177e4SLinus Torvalds 				return -ENXIO;
711da177e4SLinus Torvalds 			bdevp = bdget_disk(disk, part);
721da177e4SLinus Torvalds 			if (!bdevp)
731da177e4SLinus Torvalds 				return -ENOMEM;
741da177e4SLinus Torvalds 			down(&bdevp->bd_sem);
751da177e4SLinus Torvalds 			if (bdevp->bd_openers) {
761da177e4SLinus Torvalds 				up(&bdevp->bd_sem);
771da177e4SLinus Torvalds 				bdput(bdevp);
781da177e4SLinus Torvalds 				return -EBUSY;
791da177e4SLinus Torvalds 			}
801da177e4SLinus Torvalds 			/* all seems OK */
811da177e4SLinus Torvalds 			fsync_bdev(bdevp);
821da177e4SLinus Torvalds 			invalidate_bdev(bdevp, 0);
831da177e4SLinus Torvalds 
841da177e4SLinus Torvalds 			down(&bdev->bd_sem);
851da177e4SLinus Torvalds 			delete_partition(disk, part);
861da177e4SLinus Torvalds 			up(&bdev->bd_sem);
871da177e4SLinus Torvalds 			up(&bdevp->bd_sem);
881da177e4SLinus Torvalds 			bdput(bdevp);
891da177e4SLinus Torvalds 
901da177e4SLinus Torvalds 			return 0;
911da177e4SLinus Torvalds 		default:
921da177e4SLinus Torvalds 			return -EINVAL;
931da177e4SLinus Torvalds 	}
941da177e4SLinus Torvalds }
951da177e4SLinus Torvalds 
961da177e4SLinus Torvalds static int blkdev_reread_part(struct block_device *bdev)
971da177e4SLinus Torvalds {
981da177e4SLinus Torvalds 	struct gendisk *disk = bdev->bd_disk;
991da177e4SLinus Torvalds 	int res;
1001da177e4SLinus Torvalds 
1011da177e4SLinus Torvalds 	if (disk->minors == 1 || bdev != bdev->bd_contains)
1021da177e4SLinus Torvalds 		return -EINVAL;
1031da177e4SLinus Torvalds 	if (!capable(CAP_SYS_ADMIN))
1041da177e4SLinus Torvalds 		return -EACCES;
1051da177e4SLinus Torvalds 	if (down_trylock(&bdev->bd_sem))
1061da177e4SLinus Torvalds 		return -EBUSY;
1071da177e4SLinus Torvalds 	res = rescan_partitions(disk, bdev);
1081da177e4SLinus Torvalds 	up(&bdev->bd_sem);
1091da177e4SLinus Torvalds 	return res;
1101da177e4SLinus Torvalds }
1111da177e4SLinus Torvalds 
1121da177e4SLinus Torvalds static int put_ushort(unsigned long arg, unsigned short val)
1131da177e4SLinus Torvalds {
1141da177e4SLinus Torvalds 	return put_user(val, (unsigned short __user *)arg);
1151da177e4SLinus Torvalds }
1161da177e4SLinus Torvalds 
1171da177e4SLinus Torvalds static int put_int(unsigned long arg, int val)
1181da177e4SLinus Torvalds {
1191da177e4SLinus Torvalds 	return put_user(val, (int __user *)arg);
1201da177e4SLinus Torvalds }
1211da177e4SLinus Torvalds 
1221da177e4SLinus Torvalds static int put_long(unsigned long arg, long val)
1231da177e4SLinus Torvalds {
1241da177e4SLinus Torvalds 	return put_user(val, (long __user *)arg);
1251da177e4SLinus Torvalds }
1261da177e4SLinus Torvalds 
1271da177e4SLinus Torvalds static int put_ulong(unsigned long arg, unsigned long val)
1281da177e4SLinus Torvalds {
1291da177e4SLinus Torvalds 	return put_user(val, (unsigned long __user *)arg);
1301da177e4SLinus Torvalds }
1311da177e4SLinus Torvalds 
1321da177e4SLinus Torvalds static int put_u64(unsigned long arg, u64 val)
1331da177e4SLinus Torvalds {
1341da177e4SLinus Torvalds 	return put_user(val, (u64 __user *)arg);
1351da177e4SLinus Torvalds }
1361da177e4SLinus Torvalds 
137bb93e3a5SArnd Bergmann static int blkdev_locked_ioctl(struct file *file, struct block_device *bdev,
138bb93e3a5SArnd Bergmann 				unsigned cmd, unsigned long arg)
1391da177e4SLinus Torvalds {
1401da177e4SLinus Torvalds 	struct backing_dev_info *bdi;
1411da177e4SLinus Torvalds 	int ret, n;
1421da177e4SLinus Torvalds 
1431da177e4SLinus Torvalds 	switch (cmd) {
1441da177e4SLinus Torvalds 	case BLKRAGET:
1451da177e4SLinus Torvalds 	case BLKFRAGET:
1461da177e4SLinus Torvalds 		if (!arg)
1471da177e4SLinus Torvalds 			return -EINVAL;
1481da177e4SLinus Torvalds 		bdi = blk_get_backing_dev_info(bdev);
1491da177e4SLinus Torvalds 		if (bdi == NULL)
1501da177e4SLinus Torvalds 			return -ENOTTY;
1511da177e4SLinus Torvalds 		return put_long(arg, (bdi->ra_pages * PAGE_CACHE_SIZE) / 512);
1521da177e4SLinus Torvalds 	case BLKROGET:
1531da177e4SLinus Torvalds 		return put_int(arg, bdev_read_only(bdev) != 0);
1541da177e4SLinus Torvalds 	case BLKBSZGET: /* get the logical block size (cf. BLKSSZGET) */
1551da177e4SLinus Torvalds 		return put_int(arg, block_size(bdev));
1561da177e4SLinus Torvalds 	case BLKSSZGET: /* get block device hardware sector size */
1571da177e4SLinus Torvalds 		return put_int(arg, bdev_hardsect_size(bdev));
1581da177e4SLinus Torvalds 	case BLKSECTGET:
1591da177e4SLinus Torvalds 		return put_ushort(arg, bdev_get_queue(bdev)->max_sectors);
1601da177e4SLinus Torvalds 	case BLKRASET:
1611da177e4SLinus Torvalds 	case BLKFRASET:
1621da177e4SLinus Torvalds 		if(!capable(CAP_SYS_ADMIN))
1631da177e4SLinus Torvalds 			return -EACCES;
1641da177e4SLinus Torvalds 		bdi = blk_get_backing_dev_info(bdev);
1651da177e4SLinus Torvalds 		if (bdi == NULL)
1661da177e4SLinus Torvalds 			return -ENOTTY;
1671da177e4SLinus Torvalds 		bdi->ra_pages = (arg * 512) / PAGE_CACHE_SIZE;
1681da177e4SLinus Torvalds 		return 0;
1691da177e4SLinus Torvalds 	case BLKBSZSET:
1701da177e4SLinus Torvalds 		/* set the logical block size */
1711da177e4SLinus Torvalds 		if (!capable(CAP_SYS_ADMIN))
1721da177e4SLinus Torvalds 			return -EACCES;
1731da177e4SLinus Torvalds 		if (!arg)
1741da177e4SLinus Torvalds 			return -EINVAL;
1751da177e4SLinus Torvalds 		if (get_user(n, (int __user *) arg))
1761da177e4SLinus Torvalds 			return -EFAULT;
1771da177e4SLinus Torvalds 		if (bd_claim(bdev, file) < 0)
1781da177e4SLinus Torvalds 			return -EBUSY;
1791da177e4SLinus Torvalds 		ret = set_blocksize(bdev, n);
1801da177e4SLinus Torvalds 		bd_release(bdev);
1811da177e4SLinus Torvalds 		return ret;
1821da177e4SLinus Torvalds 	case BLKPG:
1831da177e4SLinus Torvalds 		return blkpg_ioctl(bdev, (struct blkpg_ioctl_arg __user *) arg);
1841da177e4SLinus Torvalds 	case BLKRRPART:
1851da177e4SLinus Torvalds 		return blkdev_reread_part(bdev);
1861da177e4SLinus Torvalds 	case BLKGETSIZE:
1871da177e4SLinus Torvalds 		if ((bdev->bd_inode->i_size >> 9) > ~0UL)
1881da177e4SLinus Torvalds 			return -EFBIG;
1891da177e4SLinus Torvalds 		return put_ulong(arg, bdev->bd_inode->i_size >> 9);
1901da177e4SLinus Torvalds 	case BLKGETSIZE64:
1911da177e4SLinus Torvalds 		return put_u64(arg, bdev->bd_inode->i_size);
192bb93e3a5SArnd Bergmann 	}
193bb93e3a5SArnd Bergmann 	return -ENOIOCTLCMD;
194bb93e3a5SArnd Bergmann }
195bb93e3a5SArnd Bergmann 
196bb93e3a5SArnd Bergmann static int blkdev_driver_ioctl(struct inode *inode, struct file *file,
197bb93e3a5SArnd Bergmann 		struct gendisk *disk, unsigned cmd, unsigned long arg)
198bb93e3a5SArnd Bergmann {
199bb93e3a5SArnd Bergmann 	int ret;
200bb93e3a5SArnd Bergmann 	if (disk->fops->unlocked_ioctl)
201bb93e3a5SArnd Bergmann 		return disk->fops->unlocked_ioctl(file, cmd, arg);
202bb93e3a5SArnd Bergmann 
203bb93e3a5SArnd Bergmann 	if (disk->fops->ioctl) {
204bb93e3a5SArnd Bergmann 		lock_kernel();
205bb93e3a5SArnd Bergmann 		ret = disk->fops->ioctl(inode, file, cmd, arg);
206bb93e3a5SArnd Bergmann 		unlock_kernel();
207bb93e3a5SArnd Bergmann 		return ret;
208bb93e3a5SArnd Bergmann 	}
209bb93e3a5SArnd Bergmann 
210bb93e3a5SArnd Bergmann 	return -ENOTTY;
211bb93e3a5SArnd Bergmann }
212bb93e3a5SArnd Bergmann 
213bb93e3a5SArnd Bergmann int blkdev_ioctl(struct inode *inode, struct file *file, unsigned cmd,
214bb93e3a5SArnd Bergmann 			unsigned long arg)
215bb93e3a5SArnd Bergmann {
216bb93e3a5SArnd Bergmann 	struct block_device *bdev = inode->i_bdev;
217bb93e3a5SArnd Bergmann 	struct gendisk *disk = bdev->bd_disk;
218bb93e3a5SArnd Bergmann 	int ret, n;
219bb93e3a5SArnd Bergmann 
220bb93e3a5SArnd Bergmann 	switch(cmd) {
2211da177e4SLinus Torvalds 	case BLKFLSBUF:
2221da177e4SLinus Torvalds 		if (!capable(CAP_SYS_ADMIN))
2231da177e4SLinus Torvalds 			return -EACCES;
224bb93e3a5SArnd Bergmann 
225bb93e3a5SArnd Bergmann 		ret = blkdev_driver_ioctl(inode, file, disk, cmd, arg);
2261da177e4SLinus Torvalds 		/* -EINVAL to handle old uncorrected drivers */
2271da177e4SLinus Torvalds 		if (ret != -EINVAL && ret != -ENOTTY)
2281da177e4SLinus Torvalds 			return ret;
229bb93e3a5SArnd Bergmann 
230bb93e3a5SArnd Bergmann 		lock_kernel();
2311da177e4SLinus Torvalds 		fsync_bdev(bdev);
2321da177e4SLinus Torvalds 		invalidate_bdev(bdev, 0);
233bb93e3a5SArnd Bergmann 		unlock_kernel();
2341da177e4SLinus Torvalds 		return 0;
235bb93e3a5SArnd Bergmann 
2361da177e4SLinus Torvalds 	case BLKROSET:
237bb93e3a5SArnd Bergmann 		ret = blkdev_driver_ioctl(inode, file, disk, cmd, arg);
2381da177e4SLinus Torvalds 		/* -EINVAL to handle old uncorrected drivers */
2391da177e4SLinus Torvalds 		if (ret != -EINVAL && ret != -ENOTTY)
2401da177e4SLinus Torvalds 			return ret;
2411da177e4SLinus Torvalds 		if (!capable(CAP_SYS_ADMIN))
2421da177e4SLinus Torvalds 			return -EACCES;
2431da177e4SLinus Torvalds 		if (get_user(n, (int __user *)(arg)))
2441da177e4SLinus Torvalds 			return -EFAULT;
245bb93e3a5SArnd Bergmann 		lock_kernel();
2461da177e4SLinus Torvalds 		set_device_ro(bdev, n);
247bb93e3a5SArnd Bergmann 		unlock_kernel();
2481da177e4SLinus Torvalds 		return 0;
249a885c8c4SChristoph Hellwig 	case HDIO_GETGEO: {
250a885c8c4SChristoph Hellwig 		struct hd_geometry geo;
251a885c8c4SChristoph Hellwig 
252a885c8c4SChristoph Hellwig 		if (!arg)
253a885c8c4SChristoph Hellwig 			return -EINVAL;
254a885c8c4SChristoph Hellwig 		if (!disk->fops->getgeo)
255a885c8c4SChristoph Hellwig 			return -ENOTTY;
256a885c8c4SChristoph Hellwig 
257a885c8c4SChristoph Hellwig 		/*
258a885c8c4SChristoph Hellwig 		 * We need to set the startsect first, the driver may
259a885c8c4SChristoph Hellwig 		 * want to override it.
260a885c8c4SChristoph Hellwig 		 */
261a885c8c4SChristoph Hellwig 		geo.start = get_start_sect(bdev);
262a885c8c4SChristoph Hellwig 		ret = disk->fops->getgeo(bdev, &geo);
263a885c8c4SChristoph Hellwig 		if (ret)
264a885c8c4SChristoph Hellwig 			return ret;
265a885c8c4SChristoph Hellwig 		if (copy_to_user((struct hd_geometry __user *)arg, &geo,
266a885c8c4SChristoph Hellwig 					sizeof(geo)))
267a885c8c4SChristoph Hellwig 			return -EFAULT;
268a885c8c4SChristoph Hellwig 		return 0;
269a885c8c4SChristoph Hellwig 	}
2701da177e4SLinus Torvalds 	}
271bb93e3a5SArnd Bergmann 
272bb93e3a5SArnd Bergmann 	lock_kernel();
273bb93e3a5SArnd Bergmann 	ret = blkdev_locked_ioctl(file, bdev, cmd, arg);
274bb93e3a5SArnd Bergmann 	unlock_kernel();
275bb93e3a5SArnd Bergmann 	if (ret != -ENOIOCTLCMD)
276bb93e3a5SArnd Bergmann 		return ret;
277bb93e3a5SArnd Bergmann 
278bb93e3a5SArnd Bergmann 	return blkdev_driver_ioctl(inode, file, disk, cmd, arg);
2791da177e4SLinus Torvalds }
2801da177e4SLinus Torvalds 
2811da177e4SLinus Torvalds /* Most of the generic ioctls are handled in the normal fallback path.
2821da177e4SLinus Torvalds    This assumes the blkdev's low level compat_ioctl always returns
2831da177e4SLinus Torvalds    ENOIOCTLCMD for unknown ioctls. */
2841da177e4SLinus Torvalds long compat_blkdev_ioctl(struct file *file, unsigned cmd, unsigned long arg)
2851da177e4SLinus Torvalds {
2861da177e4SLinus Torvalds 	struct block_device *bdev = file->f_dentry->d_inode->i_bdev;
2871da177e4SLinus Torvalds 	struct gendisk *disk = bdev->bd_disk;
2881da177e4SLinus Torvalds 	int ret = -ENOIOCTLCMD;
2891da177e4SLinus Torvalds 	if (disk->fops->compat_ioctl) {
2901da177e4SLinus Torvalds 		lock_kernel();
2911da177e4SLinus Torvalds 		ret = disk->fops->compat_ioctl(file, cmd, arg);
2921da177e4SLinus Torvalds 		unlock_kernel();
2931da177e4SLinus Torvalds 	}
2941da177e4SLinus Torvalds 	return ret;
2951da177e4SLinus Torvalds }
29668f66febSStephen Tweedie 
29768f66febSStephen Tweedie EXPORT_SYMBOL_GPL(blkdev_ioctl);
298