/*
       * Partition table and disk geometry handling
       *
       * This obsoletes the partition-handling code in genhd.c:
       * Userspace can look at a disk in arbitrary format and tell
       * the kernel what partitions there are on the disk, and how
       * these should be numbered.
       * It also allows one to repartition a disk that is being used.
       *
       * A single ioctl with lots of subfunctions:
       *
       * Device number stuff:
       *    get_whole_disk()          (given the device number of a partition, find
       *                               the device number of the encompassing disk)
       *    get_all_partitions()      (given the device number of a disk, return the
       *                               device numbers of all its known partitions)
       *
       * Partition stuff:
       *    add_partition()
       *    delete_partition()
       *    test_partition_in_use()   (also for test_disk_in_use)
       *
       * Geometry stuff:
       *    get_geometry()
       *    set_geometry()
       *    get_bios_drivedata()
       *
       * For today, only the partition stuff - aeb, 990515
       */
      
      #include <linux/errno.h>
      #include <linux/fs.h>			/* for BLKRASET, ... */
      #include <linux/sched.h>		/* for capable() */
      #include <linux/blk.h>			/* for set_device_ro() */
      #include <linux/blkpg.h>
      #include <linux/genhd.h>
      #include <linux/swap.h>			/* for is_swap_partition() */
      #include <linux/module.h>               /* for EXPORT_SYMBOL */
      
      #include <asm/uaccess.h>
      
      /*
       * What is the data describing a partition?
       *
       * 1. a device number (kdev_t)
       * 2. a starting sector and number of sectors (hd_struct)
       *    given in the part[] array of the gendisk structure for the drive.
       *
       * The number of sectors is replicated in the sizes[] array of
       * the gendisk structure for the major, which again is copied to
       * the blk_size[][] array.
       * (However, hd_struct has the number of 512-byte sectors,
       *  g->sizes[] and blk_size[][] have the number of 1024-byte blocks.)
       * Note that several drives may have the same major.
       */
      
      /* a linear search, superfluous when dev is a pointer */
  58  static struct gendisk *get_gendisk(kdev_t dev) {
      	struct gendisk *g;
      	int m = MAJOR(dev);
      
  62  	for (g = gendisk_head; g; g = g->next)
  63  		if (g->major == m)
  64  			break;
  65  	return g;
      }
      
      /*
       * Add a partition.
       *
       * returns: EINVAL: bad parameters
       *          ENXIO: cannot find drive
       *          EBUSY: proposed partition overlaps an existing one
       *                 or has the same number as an existing one
       *          0: all OK.
       */
  77  int add_partition(kdev_t dev, struct blkpg_partition *p) {
      	struct gendisk *g;
      	long long ppstart, pplength;
      	long pstart, plength;
      	int i, drive, first_minor, end_minor, minor;
      
      	/* convert bytes to sectors, check for fit in a hd_struct */
      	ppstart = (p->start >> 9);
      	pplength = (p->length >> 9);
      	pstart = ppstart;
      	plength = pplength;
      	if (pstart != ppstart || plength != pplength
  89  	    || pstart < 0 || plength < 0)
  90  		return -EINVAL;
      
      	/* find the drive major */
      	g = get_gendisk(dev);
  94  	if (!g)
  95  		return -ENXIO;
      
      	/* existing drive? */
      	drive = (MINOR(dev) >> g->minor_shift);
      	first_minor = (drive << g->minor_shift);
      	end_minor   = first_minor + g->max_p;
 101  	if (drive >= g->nr_real)
 102  		return -ENXIO;
      
      	/* drive and partition number OK? */
 105  	if (first_minor != MINOR(dev) || p->pno <= 0 || p->pno >= g->max_p)
 106  		return -EINVAL;
      
      	/* partition number in use? */
      	minor = first_minor + p->pno;
 110  	if (g->part[minor].nr_sects != 0)
 111  		return -EBUSY;
      
      	/* overlap? */
 114  	for (i=first_minor+1; i<end_minor; i++)
      		if (!(pstart+plength <= g->part[i].start_sect ||
 116  		      pstart >= g->part[i].start_sect + g->part[i].nr_sects))
 117  			return -EBUSY;
      
      	/* all seems OK */
      	g->part[minor].start_sect = pstart;
      	g->part[minor].nr_sects = plength;
 122  	if (g->sizes)
      		g->sizes[minor] = (plength >> (BLOCK_SIZE_BITS - 9));
 124  	return 0;
      }
      
      /*
       * Delete a partition given by partition number
       *
       * returns: EINVAL: bad parameters
       *          ENXIO: cannot find partition
       *          EBUSY: partition is busy
       *          0: all OK.
       *
       * Note that the dev argument refers to the entire disk, not the partition.
       */
 137  int del_partition(kdev_t dev, struct blkpg_partition *p) {
      	struct gendisk *g;
      	kdev_t devp;
      	int drive, first_minor, minor;
      
      	/* find the drive major */
      	g = get_gendisk(dev);
 144  	if (!g)
 145  		return -ENXIO;
      
      	/* drive and partition number OK? */
      	drive = (MINOR(dev) >> g->minor_shift);
      	first_minor = (drive << g->minor_shift);
 150  	if (first_minor != MINOR(dev) || p->pno <= 0 || p->pno >= g->max_p)
 151  		return -EINVAL;
      
      	/* existing drive and partition? */
      	minor = first_minor + p->pno;
 155  	if (drive >= g->nr_real || g->part[minor].nr_sects == 0)
 156  		return -ENXIO;
      
      	/* partition in use? Incomplete check for now. */
      	devp = MKDEV(MAJOR(dev), minor);
      	if (get_super(devp) ||		/* mounted? */
 161  	    is_swap_partition(devp))
 162  		return -EBUSY;
      
      	/* all seems OK */
      	fsync_dev(devp);
      	invalidate_buffers(devp);
      
      	g->part[minor].start_sect = 0;
      	g->part[minor].nr_sects = 0;
 170  	if (g->sizes)
      		g->sizes[minor] = 0;
      
 173  	return 0;
      }
      
 176  int blkpg_ioctl(kdev_t dev, struct blkpg_ioctl_arg *arg)
      {
      	struct blkpg_ioctl_arg a;
      	struct blkpg_partition p;
      	int len;
      
 182  	if (copy_from_user(&a, arg, sizeof(struct blkpg_ioctl_arg)))
 183  		return -EFAULT;
      
 185  	switch (a.op) {
 186  		case BLKPG_ADD_PARTITION:
 187  		case BLKPG_DEL_PARTITION:
      			len = a.datalen;
 189  			if (len < sizeof(struct blkpg_partition))
 190  				return -EINVAL;
 191  			if (copy_from_user(&p, a.data, sizeof(struct blkpg_partition)))
 192  				return -EFAULT;
 193  			if (!capable(CAP_SYS_ADMIN))
 194  				return -EACCES;
 195  			if (a.op == BLKPG_ADD_PARTITION)
 196  				return add_partition(dev, &p);
 197  			else
 198  				return del_partition(dev, &p);
 199  		default:
 200  			return -EINVAL;
      	}
      }
      
      /*
       * Common ioctl's for block devices
       */
      
 208  int blk_ioctl(kdev_t dev, unsigned int cmd, unsigned long arg)
      {
      	int intval;
      
 212  	switch (cmd) {
 213  		case BLKROSET:
 214  			if (!capable(CAP_SYS_ADMIN))
 215  				return -EACCES;
 216  			if (get_user(intval, (int *)(arg)))
 217  				return -EFAULT;
      			set_device_ro(dev, intval);
 219  			return 0;
 220  		case BLKROGET:
      			intval = (is_read_only(dev) != 0);
 222  			return put_user(intval, (int *)(arg));
      
 224  		case BLKRASET:
 225  			if(!capable(CAP_SYS_ADMIN))
 226  				return -EACCES;
 227  			if(!dev || arg > 0xff)
 228  				return -EINVAL;
      			read_ahead[MAJOR(dev)] = arg;
 230  			return 0;
 231  		case BLKRAGET:
 232  			if (!arg)
 233  				return -EINVAL;
 234  			return put_user(read_ahead[MAJOR(dev)], (long *) arg);
      
 236  		case BLKFLSBUF:
 237  			if(!capable(CAP_SYS_ADMIN))
 238  				return -EACCES;
 239  			if (!dev)
 240  				return -EINVAL;
      			fsync_dev(dev);
      			invalidate_buffers(dev);
 243  			return 0;
      
 245  		case BLKSSZGET:
      			/* get block device sector size as needed e.g. by fdisk */
      			intval = get_hardsect_size(dev);
 248  			return put_user(intval, (int *) arg);
      
      #if 0
      		case BLKGETSIZE:
      			/* Today get_gendisk() requires a linear scan;
      			   add this when dev has pointer type. */
      			g = get_gendisk(dev);
      			if (!g)
      				longval = 0;
      			else
      				longval = g->part[MINOR(dev)].nr_sects;
      			return put_user(longval, (long *) arg);
      #endif
      #if 0
      		case BLKRRPART: /* Re-read partition tables */
      			if (!capable(CAP_SYS_ADMIN)) 
      				return -EACCES;
      			return reread_partitions(dev, 1);
      #endif
      
 268  		case BLKPG:
 269  			return blkpg_ioctl(dev, (struct blkpg_ioctl_arg *) arg);
      			
 271  		case BLKELVGET:
      			return blkelvget_ioctl(&blk_get_queue(dev)->elevator,
 273  					       (blkelv_ioctl_arg_t *) arg);
 274  		case BLKELVSET:
      			return blkelvset_ioctl(&blk_get_queue(dev)->elevator,
 276  					       (blkelv_ioctl_arg_t *) arg);
      
 278  		default:
 279  			return -EINVAL;
      	}
      }
      
      EXPORT_SYMBOL(blk_ioctl);