2001-02-06 21:34:52

by Michael E Brown

[permalink] [raw]
Subject: [RFC][PATCH] block ioctl to read/write last sector


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 */


Attachments:
patch-getlastsector-20010122 (6.66 kB)

2001-02-07 14:35:24

by Andi Kleen

[permalink] [raw]
Subject: Re: [RFC][PATCH] block ioctl to read/write last sector

Michael E Brown <[email protected]> writes:

> 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.

But what happens when you e.g. run a software blocksize of 4096 and the device
has >1 inaccessible 512 byte sector at the end?
I think it would be better to pass in a offset in 512 byte units to a special
ioctl (and do error checking in the driver for impossible requests)

-Andi

2001-02-07 15:36:06

by Michael E Brown

[permalink] [raw]
Subject: Re: [RFC][PATCH] block ioctl to read/write last sector

On 7 Feb 2001, Andi Kleen wrote:

> But what happens when you e.g. run a software blocksize of 4096 and the device
> has >1 inaccessible 512 byte sector at the end?
> I think it would be better to pass in a offset in 512 byte units to a special
> ioctl (and do error checking in the driver for impossible requests)

This is a valid point.

Can you tell me how it would come about that we would have a blocksize !=
1024?

Can you show the proposed interface to the new ioctl?

I was limited in that I could only figure out how to get one userspace
char* into/out of the ioctl. How would you propose to pass in the offset?
I had problems finding documentation on the more complicated IOCTL calls,
and since I am a kernel hacking novice, I went the easiest and most direct
route.

If you tell me the proposed interface and some sample code, I can code,
test and resubmit it. Thank you for the feedback.

Michael Brown
Linux System Group
Dell Computer Corp