PATCH: linux-2.4.0-test5-pre4: set_blocksize() destroyed ramdisks

From: Adam J. Richter (adam@yggdrasil.com)
Date: Mon Jul 24 2000 - 05:31:13 EST


        This patch fixes a long standing bug in the Linux kernel
where set_blocksize() destroys the contents of a ramdisk if it
actually needs to change the blocksize. This is why cramfs could
not be used an initial ramdisk (cramfs changes the ramdisk blocksize
from 1kB to 4kB).

Adam J. Richter __ ______________ 4880 Stevens Creek Blvd, Suite 104
adam@yggdrasil.com \ / San Jose, California 95129-1034
+1 408 261-6630 | g g d r a s i l United States of America
fax +1 408 261-6631 "Free Software For The Rest Of Us."
---------------------------CUT HERE---------------------------------------

--- linux-2.4.0-test5-pre4/fs/buffer.c Mon Jul 24 03:24:57 2000
+++ linux/fs/buffer.c Sat Jul 22 20:06:43 2000
@@ -132,6 +132,10 @@
 int bdflush_min[N_PARAM] = { 0, 10, 5, 25, 0, 1*HZ, 1*HZ, 1, 1};
 int bdflush_max[N_PARAM] = {100,50000, 20000, 20000,600*HZ, 6000*HZ, 6000*HZ, 2047, 5};
 
+#ifndef min
+# define min(a,b) (((a)<(b))?(a):(b))
+#endif
+
 /*
  * Rewrote the wait-routines to use the "new" wait-queue functionality,
  * and getting rid of the cli-sti pairs. The wait-queue routines still
@@ -625,11 +629,66 @@
                 goto retry;
 }
 
+/* Copy all protected ramdisk blocks into new blocks. */
+static void
+resize_protected_blocks(kdev_t dev, int oldsize, int newsize)
+{
+ int i;
+ struct buffer_head *bh, *bh_new;
+ int remaining;
+ int iterations = 0;
+ const int minsize = min(oldsize, newsize);
+
+ again:
+ iterations++;
+ spin_lock(&lru_list_lock);
+ for (bh = lru_list[BUF_PROTECTED],
+ remaining = nr_buffers_type[BUF_PROTECTED];
+ remaining != 0 && bh != NULL;
+ bh = bh->b_next_free, remaining--) {
+ if (bh->b_dev == dev && bh->b_size == oldsize) {
+ const int oldblk = bh->b_blocknr;
+ const int newblk = (oldblk * oldsize) / newsize;
+ spin_unlock(&lru_list_lock);
+ bh = bread(dev, oldblk, oldsize);
+ if (!bh) {
+ printk ("ramdisk_resize_save: cannot find existing block. Data lost.\n");
+ return; /* If we were to do "goto again",
+ we would risk an infinite loop. */
+ }
+ for(i = 0; i < oldsize/newsize || i==0; i++) {
+ bh_new = getblk(dev,newblk+i,newsize);
+ if (!bh_new) {
+ printk ("ramdisk_resize_save: memory allocation failure. Data lost.\n");
+ continue;
+ }
+ if (!buffer_protected(bh_new)) {
+ mark_buffer_protected(bh_new);
+ if (newsize > oldsize)
+ memset(bh_new->b_data,
+ 0, newsize);
+ }
+ memcpy(bh_new->b_data +
+ ((oldblk*oldsize)%newsize),
+ bh->b_data + (i * newsize),
+ minsize);
+ brelse(bh_new);
+ }
+ test_and_clear_bit(BH_Protected, &bh->b_state);
+ refile_buffer(bh);
+ __bforget(bh);
+ goto again;
+ }
+ }
+ spin_unlock(&lru_list_lock);
+}
+
 void set_blocksize(kdev_t dev, int size)
 {
         extern int *blksize_size[];
         int i, nlist, slept;
         struct buffer_head * bh, * bh_next;
+ int oldsize;
 
         if (!blksize_size[MAJOR(dev)])
                 return;
@@ -638,13 +697,16 @@
         if (size > PAGE_SIZE || size < 512 || (size & (size-1)))
                 panic("Invalid blocksize passed to set_blocksize");
 
- if (blksize_size[MAJOR(dev)][MINOR(dev)] == 0 && size == BLOCK_SIZE) {
+ oldsize = blksize_size[MAJOR(dev)][MINOR(dev)];
+ if (oldsize == 0 && size == BLOCK_SIZE) {
                 blksize_size[MAJOR(dev)][MINOR(dev)] = size;
                 return;
         }
         if (blksize_size[MAJOR(dev)][MINOR(dev)] == size)
                 return;
         sync_buffers(dev, 2);
+ if (MAJOR(dev) == RAMDISK_MAJOR)
+ resize_protected_blocks(dev, oldsize, size);
         blksize_size[MAJOR(dev)][MINOR(dev)] = size;
 
  retry:

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Mon Jul 31 2000 - 21:00:16 EST