[LWN Logo]
[LWN.net]
From:	 Michael E Brown <michael_e_brown@dell.com>
To:	 <linux-kernel@vger.kernel.org>
Subject: [RFC][PATCH] block ioctl to read/write last sector
Date:	 Tue, 6 Feb 2001 15:34:10 -0600 (CST)
Cc:	 "Domsch, Matt" <Matt_Domsch@dell.com>


Problem Summary:
  There is no function exported to userspace to read or write the last
512-byte sector of an odd-size disk.

  The block device uses 1K blocksize, and will prevent userspace from
seeing the odd-block at the end of the disk, if the disk is odd-size.

  IA-64 architecture defines a new partitioning scheme where there is a
backup of the partition table header in the last sector of the disk. While
we can read and write to this sector in the kernel partition code, we have
no way for userspace to update this partition block.

Solution:
  As an interim solution, I propose the following IOCTLs for the block
device layer: BLKGETLASTSECT and BLKSETLASTSECT.  These ioctls will take a
userspace pointer to a char[512] and read/write the last sector. Below is
a patch to do this.

I have attached the patch as well, because I've heard that Pine will eat
patches. :-(

--
Michael Brown
Linux System Group
Dell Computer Corp


diff -ruP linux/drivers/block/blkpg.c linux-meb-clean/drivers/block/blkpg.c
--- linux/drivers/block/blkpg.c	Fri Oct 27 01:35:47 2000
+++ linux-meb-clean/drivers/block/blkpg.c	Mon Jan 22 10:00:04 2001
@@ -39,6 +39,9 @@

 #include <asm/uaccess.h>

+static int set_last_sector( kdev_t dev, char *sect );
+static int get_last_sector( kdev_t dev, char *sect );
+
 /*
  * What is the data describing a partition?
  *
@@ -208,8 +211,19 @@
 int blk_ioctl(kdev_t dev, unsigned int cmd, unsigned long arg)
 {
 	int intval;
+        unsigned long longval;

 	switch (cmd) {
+		case BLKGETLASTSECT:
+			return get_last_sector(dev, (char *)(arg));
+
+		case BLKSETLASTSECT:
+			if( is_read_only(dev) )
+				return -EACCES;
+			if (!capable(CAP_SYS_ADMIN))
+				return -EACCES;
+			return set_last_sector(dev, (char *)(arg));
+
 		case BLKROSET:
 			if (!capable(CAP_SYS_ADMIN))
 				return -EACCES;
@@ -281,3 +295,140 @@
 }

 EXPORT_SYMBOL(blk_ioctl);
+
+ /*********************
+  * get_last_sector()
+  *
+  * Description: This function will return the first 512 bytes of the last sector of
+  *    a block device.
+  * Why: Normal read/write calls through the block layer will not read the last sector
+  *    of an odd-size disk.
+  * parameters:
+  *    dev: a kdev_t that represents the device for which we want the last sector
+  *    sect: a userspace pointer, should be at least char[512] to hold the last sector contents
+  * return:
+  *    0 on success
+  *   -ERRVAL on error.
+  *********************/
+int get_last_sector( kdev_t dev, char *sect )
+{
+        struct buffer_head *bh;
+        struct gendisk *g;
+        int m, rc = 0;
+        unsigned int lba;
+        int orig_blksize = BLOCK_SIZE;
+        int hardblocksize;
+
+        if( !dev ) return -EINVAL;
+
+        m = MAJOR(dev);
+        for (g = gendisk_head; g; g = g->next)
+                if (g->major == m)
+                        break;
+
+        if( !g ) return -EINVAL;
+
+        lba = g->part[MINOR(dev)].nr_sects - 1;
+
+        if( !lba ) return -EINVAL;
+
+        hardblocksize = get_hardblocksize(dev);
+        if( ! hardblocksize ) hardblocksize = 512;
+
+         /* Need to change the block size that the block layer uses */
+        if (blksize_size[MAJOR(dev)]){
+                orig_blksize = blksize_size[MAJOR(dev)][MINOR(dev)];
+        }
+        if (orig_blksize != hardblocksize)
+                   set_blocksize(dev, hardblocksize);
+
+        bh =  bread(dev, lba, hardblocksize);
+        if (!bh) {
+                       /* We hit the end of the disk */
+                       printk(KERN_WARNING
+                              "get_last_sector ioctl: bread returned NULL.\n");
+                       return -1;
+        }
+
+        rc = copy_to_user(sect, bh->b_data, (bh->b_size > 512) ? 512 : bh->b_size );
+
+        brelse(bh);
+
+        /* change block size back */
+        if (orig_blksize != hardblocksize)
+                   set_blocksize(dev, orig_blksize);
+
+        return rc;
+}
+
+
+ /*********************
+  * set_last_sector()
+  *
+  * Description: This function will write the first 512 bytes of the last sector of
+  *    a block device.
+  * Why: Normal read/write calls through the block layer will not read the last sector
+  *    of an odd-size disk.
+  * parameters:
+  *    dev: a kdev_t that represents the device for which we want the last sector
+  *    sect: a userspace pointer, should be at least char[512] to hold the last sector contents
+  * return:
+  *    0 on success
+  *   -ERRVAL on error.
+  *********************/
+int set_last_sector( kdev_t dev, char *sect )
+{
+        struct buffer_head *bh;
+        struct gendisk *g;
+        int m, rc = 0;
+        unsigned int lba;
+        int orig_blksize = BLOCK_SIZE;
+        int hardblocksize;
+
+        if( !dev ) return -EINVAL;
+
+        m = MAJOR(dev);
+        for (g = gendisk_head; g; g = g->next)
+                    if (g->major == m)
+                            break;
+
+        if( !g ) return -EINVAL;
+
+        lba = g->part[MINOR(dev)].nr_sects - 1;
+
+        if( !lba ) return -EINVAL;
+
+        hardblocksize = get_hardblocksize(dev);
+        if( ! hardblocksize ) hardblocksize = 512;
+
+         /* Need to change the block size that the block layer uses */
+        if (blksize_size[MAJOR(dev)]){
+                orig_blksize = blksize_size[MAJOR(dev)][MINOR(dev)];
+        }
+        if (orig_blksize != hardblocksize)
+                 set_blocksize(dev, hardblocksize);
+
+        bh =  getblk(dev, lba, hardblocksize);
+        if (!bh) {
+                         /* We hit the end of the disk */
+                         printk(KERN_WARNING
+                                "get_last_sector ioctl: getblk returned NULL.\n");
+                         return -1;
+        }
+
+        copy_from_user(bh->b_data, sect, (bh->b_size > 512) ? 512 : bh->b_size);
+
+        mark_buffer_dirty(bh);
+        ll_rw_block (WRITE, 1, &bh);
+        wait_on_buffer (bh);
+        if (!buffer_uptodate(bh))
+          rc=-1;
+
+        brelse(bh);
+
+        /* change block size back */
+        if (orig_blksize != hardblocksize)
+                 set_blocksize(dev, orig_blksize);
+
+       return rc;
+}
diff -ruP linux/drivers/ide/ide.c linux-meb-clean/drivers/ide/ide.c
--- linux/drivers/ide/ide.c	Wed Dec  6 14:06:19 2000
+++ linux-meb-clean/drivers/ide/ide.c	Fri Jan 19 16:18:51 2001
@@ -2665,6 +2665,8 @@
 			}
 			return 0;

+		case BLKGETLASTSECT:
+		case BLKSETLASTSECT:
 		case BLKROSET:
 		case BLKROGET:
 		case BLKFLSBUF:
diff -ruP linux/drivers/scsi/sd.c linux-meb-clean/drivers/scsi/sd.c
--- linux/drivers/scsi/sd.c	Fri Oct 27 01:35:48 2000
+++ linux-meb-clean/drivers/scsi/sd.c	Fri Jan 19 11:13:03 2001
@@ -225,6 +225,8 @@
 				return -EINVAL;
 			return put_user(sd[SD_PARTITION(inode->i_rdev)].nr_sects, (long *) arg);

+		case BLKGETLASTSECT:
+		case BLKSETLASTSECT:
 		case BLKROSET:
 		case BLKROGET:
 		case BLKRASET:
diff -ruP linux/include/linux/fs.h linux-meb-clean/include/linux/fs.h
--- linux/include/linux/fs.h	Thu Jan  4 16:50:47 2001
+++ linux-meb-clean/include/linux/fs.h	Fri Jan 19 22:23:48 2001
@@ -180,6 +180,8 @@
 /* This was here just to show that the number is taken -
    probably all these _IO(0x12,*) ioctls should be moved to blkpg.h. */
 #endif
+#define BLKGETLASTSECT  _IO(0x12,108) /* get last sector of block device */
+#define BLKSETLASTSECT  _IO(0x12,109) /* get last sector of block device */


 #define BMAP_IOCTL 1		/* obsolete - kept for compatibility */

diff -ruP linux/drivers/block/blkpg.c linux-meb-clean/drivers/block/blkpg.c
--- linux/drivers/block/blkpg.c	Fri Oct 27 01:35:47 2000
+++ linux-meb-clean/drivers/block/blkpg.c	Mon Jan 22 10:00:04 2001
@@ -39,6 +39,9 @@
 
 #include <asm/uaccess.h>
 
+static int set_last_sector( kdev_t dev, char *sect );
+static int get_last_sector( kdev_t dev, char *sect );
+
 /*
  * What is the data describing a partition?
  *
@@ -208,8 +211,19 @@
 int blk_ioctl(kdev_t dev, unsigned int cmd, unsigned long arg)
 {
 	int intval;
+        unsigned long longval;
 
 	switch (cmd) {
+		case BLKGETLASTSECT:
+			return get_last_sector(dev, (char *)(arg));
+
+		case BLKSETLASTSECT:
+			if( is_read_only(dev) )
+				return -EACCES;
+			if (!capable(CAP_SYS_ADMIN))
+				return -EACCES;
+			return set_last_sector(dev, (char *)(arg));
+
 		case BLKROSET:
 			if (!capable(CAP_SYS_ADMIN))
 				return -EACCES;
@@ -281,3 +295,140 @@
 }
 
 EXPORT_SYMBOL(blk_ioctl);
+
+ /*********************
+  * get_last_sector()
+  *  
+  * Description: This function will return the first 512 bytes of the last sector of 
+  *    a block device.
+  * Why: Normal read/write calls through the block layer will not read the last sector 
+  *    of an odd-size disk. 
+  * parameters: 
+  *    dev: a kdev_t that represents the device for which we want the last sector
+  *    sect: a userspace pointer, should be at least char[512] to hold the last sector contents
+  * return: 
+  *    0 on success
+  *   -ERRVAL on error.
+  *********************/
+int get_last_sector( kdev_t dev, char *sect )
+{   
+        struct buffer_head *bh;
+        struct gendisk *g;
+        int m, rc = 0;
+        unsigned int lba;
+        int orig_blksize = BLOCK_SIZE;
+        int hardblocksize;
+
+        if( !dev ) return -EINVAL;
+
+        m = MAJOR(dev);
+        for (g = gendisk_head; g; g = g->next)
+                if (g->major == m)
+                        break;
+
+        if( !g ) return -EINVAL;
+
+        lba = g->part[MINOR(dev)].nr_sects - 1;
+
+        if( !lba ) return -EINVAL;
+
+        hardblocksize = get_hardblocksize(dev);
+        if( ! hardblocksize ) hardblocksize = 512;
+
+         /* Need to change the block size that the block layer uses */
+        if (blksize_size[MAJOR(dev)]){
+                orig_blksize = blksize_size[MAJOR(dev)][MINOR(dev)];
+        }
+        if (orig_blksize != hardblocksize)
+                   set_blocksize(dev, hardblocksize);
+
+        bh =  bread(dev, lba, hardblocksize);
+        if (!bh) {
+                       /* We hit the end of the disk */
+                       printk(KERN_WARNING
+                              "get_last_sector ioctl: bread returned NULL.\n");
+                       return -1;
+        }
+
+        rc = copy_to_user(sect, bh->b_data, (bh->b_size > 512) ? 512 : bh->b_size );
+
+        brelse(bh);
+
+        /* change block size back */
+        if (orig_blksize != hardblocksize)
+                   set_blocksize(dev, orig_blksize);
+   
+        return rc;
+}
+
+
+ /*********************
+  * set_last_sector()
+  *  
+  * Description: This function will write the first 512 bytes of the last sector of 
+  *    a block device.
+  * Why: Normal read/write calls through the block layer will not read the last sector 
+  *    of an odd-size disk. 
+  * parameters: 
+  *    dev: a kdev_t that represents the device for which we want the last sector
+  *    sect: a userspace pointer, should be at least char[512] to hold the last sector contents
+  * return: 
+  *    0 on success
+  *   -ERRVAL on error.
+  *********************/
+int set_last_sector( kdev_t dev, char *sect ) 
+{
+        struct buffer_head *bh;
+        struct gendisk *g;
+        int m, rc = 0;
+        unsigned int lba;
+        int orig_blksize = BLOCK_SIZE;
+        int hardblocksize;
+
+        if( !dev ) return -EINVAL;
+
+        m = MAJOR(dev);
+        for (g = gendisk_head; g; g = g->next)
+                    if (g->major == m)
+                            break;
+
+        if( !g ) return -EINVAL;
+    
+        lba = g->part[MINOR(dev)].nr_sects - 1;
+    
+        if( !lba ) return -EINVAL;
+    
+        hardblocksize = get_hardblocksize(dev);
+        if( ! hardblocksize ) hardblocksize = 512;
+    
+         /* Need to change the block size that the block layer uses */
+        if (blksize_size[MAJOR(dev)]){
+                orig_blksize = blksize_size[MAJOR(dev)][MINOR(dev)];
+        }
+        if (orig_blksize != hardblocksize)
+                 set_blocksize(dev, hardblocksize);
+    
+        bh =  getblk(dev, lba, hardblocksize);
+        if (!bh) {
+                         /* We hit the end of the disk */
+                         printk(KERN_WARNING
+                                "get_last_sector ioctl: getblk returned NULL.\n");
+                         return -1;
+        }
+    
+        copy_from_user(bh->b_data, sect, (bh->b_size > 512) ? 512 : bh->b_size);
+    
+        mark_buffer_dirty(bh);
+        ll_rw_block (WRITE, 1, &bh);
+        wait_on_buffer (bh);
+        if (!buffer_uptodate(bh))
+          rc=-1; 
+    
+        brelse(bh);
+    
+        /* change block size back */
+        if (orig_blksize != hardblocksize)
+                 set_blocksize(dev, orig_blksize);
+       
+       return rc;
+}
diff -ruP linux/drivers/ide/ide.c linux-meb-clean/drivers/ide/ide.c
--- linux/drivers/ide/ide.c	Wed Dec  6 14:06:19 2000
+++ linux-meb-clean/drivers/ide/ide.c	Fri Jan 19 16:18:51 2001
@@ -2665,6 +2665,8 @@
 			}
 			return 0;
 
+		case BLKGETLASTSECT:
+		case BLKSETLASTSECT:
 		case BLKROSET:
 		case BLKROGET:
 		case BLKFLSBUF:
diff -ruP linux/drivers/scsi/sd.c linux-meb-clean/drivers/scsi/sd.c
--- linux/drivers/scsi/sd.c	Fri Oct 27 01:35:48 2000
+++ linux-meb-clean/drivers/scsi/sd.c	Fri Jan 19 11:13:03 2001
@@ -225,6 +225,8 @@
 				return -EINVAL;
 			return put_user(sd[SD_PARTITION(inode->i_rdev)].nr_sects, (long *) arg);
 
+		case BLKGETLASTSECT:
+		case BLKSETLASTSECT:
 		case BLKROSET:
 		case BLKROGET:
 		case BLKRASET:
diff -ruP linux/include/linux/fs.h linux-meb-clean/include/linux/fs.h
--- linux/include/linux/fs.h	Thu Jan  4 16:50:47 2001
+++ linux-meb-clean/include/linux/fs.h	Fri Jan 19 22:23:48 2001
@@ -180,6 +180,8 @@
 /* This was here just to show that the number is taken -
    probably all these _IO(0x12,*) ioctls should be moved to blkpg.h. */
 #endif
+#define BLKGETLASTSECT  _IO(0x12,108) /* get last sector of block device */
+#define BLKSETLASTSECT  _IO(0x12,109) /* get last sector of block device */
 
 
 #define BMAP_IOCTL 1		/* obsolete - kept for compatibility */