BUG?+FIX? Ramdisk erased when blocksize changes

Adam J. Richter (adam@adam.yggdrasil.com)
29 Sep 1996 07:45:03 GMT


I think I have found a bug in the new (kernel > 1.3.4x) ramdisk driver.
If you do something that changes the block size of the ramdisk device
(i.e., something that calls set_blocksize() in the kernel), then the
contents of the ramdisk are erased.

The reason for this appears to be the new ramdisk architecture that
stores the ramdisk's contents in the buffer cache. The problem is
that when the block size of the ramdisk device is changed,
set_blocksize() invalidates all buffer cache entries for that device
so that those entries can be read back in. I believe the solution is
to have set_blocksize() convert the buffer cache entries, although I
am just a little concerned about race conditions and whether the
kernel would get confused by having buffers of different block sizes
for the same device at the same time.

This situation came up because I decided to dust off some old code
that I was working on for a new filesystem that happened to use 4kB
blocks, and the following sequence of commands would not work the
first time I would try them, but would work on the second attempt:

mklogfs /dev/ramdisk
mount -t log /dev/ramdisk /mnt

Anyhow, the following patch seems to make the original problem go
away. I have tried it once for increasing the block size and once for
decreasing the block size. I did see a couple of messages from the
kernel saying "VFS: Wrong blocksize on device 01:01" when I tried
reducing the block size to 512 bytes, but I think that may come from
my own currently flakey filesystem code. Notice that the patch also
has some question marks around the manipulation of the b_state field
for the new buffer, so I think that somebody who is more familiar with
the buffer cache needs to look it over before incorporating it into the
kernel.

Adam
----------------------------------CUT HERE---------------------------------
*** linux/fs/buffer.c.orig Sat Sep 28 23:43:49 1996
--- linux/fs/buffer.c Sun Sep 29 00:30:28 1996
***************
*** 554,559 ****
--- 554,598 ----
+
+ /* copy_to_resized_buffers does not actually change the size of the buffer
+ passed to it, but rather copies the contents of this buffer
+ to one or more buffers of the new block size. It is used only
+ by set_blocksize(), and should not be used elsewhere, since that
+ would violate the kernel's expectations that, outside of
+ set_blocksize(), blocks in the cache have only one size.
+ */
+ static void
+ copy_to_resized_buffers (struct buffer_head *old_buf, int new_size) {
+ struct buffer_head *new_buf;
+
+ if (new_size > old_buf->b_size) {
+ const int ratio = new_size / old_buf->b_size;
+
+ new_buf = getblk (old_buf->b_dev,
+ old_buf->b_blocknr / ratio, new_size);
+ if (!new_buf) return; /* Fail silently. */
+ memcpy(new_buf->b_data + (old_buf->b_blocknr % ratio)*old_buf->b_size,
+ old_buf->b_data, old_buf->b_size);
+ new_buf->b_state |= old_buf->b_state; /* XXX? */
+ brelse(new_buf);
+ } else {
+ const int ratio = old_buf->b_size / new_size;
+ int i;
+
+ for (i = 0; i < ratio; i++ ) {
+ new_buf = getblk(old_buf->b_dev,
+ old_buf->b_blocknr * ratio + i, new_size);
+ if (!new_buf) continue; /* Fail silently. */
+ memcpy(new_buf->b_data,
+ old_buf->b_data + ratio * i,
+ new_size);
+ new_buf->b_state |= old_buf->b_state; /* XXX? */
+ brelse(new_buf);
+ }
+ }
+ }
+
void set_blocksize(kdev_t dev, int size)
{
int i, nlist;
***************
*** 594,599 ****
--- 633,641 ----

wait_on_buffer(bh);
if (bh->b_dev == dev && bh->b_size != size) {
+ if (test_bit(BH_Protected,&bh->b_state)) {
+ copy_to_resized_buffers(bh, size);
+ }
clear_bit(BH_Dirty, &bh->b_state);
clear_bit(BH_Uptodate, &bh->b_state);
clear_bit(BH_Req, &bh->b_state);

-- 
Adam J. Richter				  Yggdrasil Computing, Incorporated
(408) 261-6630				  "Free Software For The Rest of Us."