diff -uNr linux.orig/drivers/block/loop.c linux/drivers/block/loop.c --- linux.orig/drivers/block/loop.c 2002-12-05 13:35:48.000000000 -0700 +++ linux/drivers/block/loop.c 2002-12-30 16:31:01.000000000 -0700 @@ -39,6 +39,10 @@ * Support up to 256 loop devices * Heinz Mauelshagen , Feb 2002 * + * Changing of backing file/device during operation for read-only loopback. + * Jakub Jelinek , Mar 31, 2000 + * AV: ported the above to Jens' variant, Jan 2001 + * * Still To Fix: * - Advisory locking is ignored here. * - Should use an own CAP_* category instead of CAP_SYS_ADMIN @@ -78,6 +82,8 @@ #define MAJOR_NR LOOP_MAJOR +#define LOOP_BH_Set 7 /* it's ugly, but... */ + static int max_loop = 8; static struct loop_device *loop_dev; static int *loop_sizes; @@ -266,12 +272,23 @@ return size; } +static inline void lo_drop(struct loop_device *lo, int set) +{ + spin_lock_irq(&lo->lo_lock); + if (!--lo->lo_pending_reads[set] && set !=lo->lo_current && + lo->lo_change) + up(lo->lo_change); + spin_unlock_irq(&lo->lo_lock); +} + + static int lo_receive(struct loop_device *lo, struct buffer_head *bh, int bsize, loff_t pos) { struct lo_read_data cookie; read_descriptor_t desc; struct file *file; + int lo_current; cookie.lo = lo; cookie.data = bh->b_data; @@ -281,9 +298,12 @@ desc.buf = (char*)&cookie; desc.error = 0; spin_lock_irq(&lo->lo_lock); + lo_current = lo->lo_current; + lo->lo_pending_reads[lo_current]++; file = lo->lo_backing_file; spin_unlock_irq(&lo->lo_lock); do_generic_file_read(file, &pos, &desc, lo_read_actor); + lo_drop(lo, lo_current); return desc.error; } @@ -400,9 +420,11 @@ } static struct buffer_head *loop_get_buffer(struct loop_device *lo, - struct buffer_head *rbh) + struct buffer_head *rbh, + int rw) { struct buffer_head *bh; + int lo_current; /* * for xfer_funcs that can operate on the same bh, do that @@ -423,8 +445,13 @@ memset(bh, 0, sizeof(*bh)); bh->b_size = rbh->b_size; + lo_current = lo->lo_current; + if (rw != WRITE) + lo->lo_pending_reads[lo_current]++; + bh->b_dev = rbh->b_rdev; bh->b_state = (1 << BH_Req) | (1 << BH_Mapped) | (1 << BH_Lock); + bh->b_state |= (lo_current << LOOP_BH_Set); /* * easy way out, although it does waste some memory for < PAGE_SIZE @@ -502,7 +529,7 @@ /* * piggy old buffer on original, and submit for I/O */ - bh = loop_get_buffer(lo, rbh); + bh = loop_get_buffer(lo, rbh,rw); IV = loop_get_iv(lo, rbh->b_rsector); if (rw == WRITE) { set_bit(BH_Dirty, &bh->b_state); @@ -546,6 +573,7 @@ bh->b_size, IV); rbh->b_end_io(rbh, !ret); + lo_drop(lo, (bh->b_state >> LOOP_BH_Set) & 1); loop_put_buffer(bh); } } @@ -614,8 +642,7 @@ return 0; } -static int loop_set_fd(struct loop_device *lo, struct file *lo_file, kdev_t dev, - unsigned int arg) +static int loop_change_fd(struct loop_device *lo, struct file *lo_file, kdev_t dev, unsigned int arg, int set) { struct file *file; struct inode *inode; @@ -624,12 +651,17 @@ int error; int bs; - MOD_INC_USE_COUNT; + if (set) { + MOD_INC_USE_COUNT; - error = -EBUSY; - if (lo->lo_state != Lo_unbound) + error = -EBUSY; + if (lo->lo_state != Lo_unbound) + goto out; + } else { + error = -EINVAL; + if (lo->lo_state != Lo_bound || !(lo->lo_flags & LO_FLAGS_READ_ONLY)) goto out; - + } error = -EBADF; file = fget(arg); if (!file) @@ -667,43 +699,85 @@ get_file(file); - if (IS_RDONLY (inode) || is_read_only(lo_device) - || !(lo_file->f_mode & FMODE_WRITE)) - lo_flags |= LO_FLAGS_READ_ONLY; - - set_device_ro(dev, (lo_flags & LO_FLAGS_READ_ONLY) != 0); - - lo->lo_device = lo_device; - lo->lo_flags = lo_flags; - lo->lo_backing_file = file; - lo->transfer = NULL; - lo->ioctl = NULL; - figure_loop_size(lo); - lo->old_gfp_mask = inode->i_mapping->gfp_mask; - inode->i_mapping->gfp_mask = GFP_NOIO; - - bs = 0; - if (blksize_size[MAJOR(lo_device)]) - bs = blksize_size[MAJOR(lo_device)][MINOR(lo_device)]; - if (!bs) - bs = BLOCK_SIZE; - - set_blocksize(dev, bs); - - lo->lo_bh = lo->lo_bhtail = NULL; - kernel_thread(loop_thread, lo, CLONE_FS | CLONE_FILES | CLONE_SIGHAND); - down(&lo->lo_sem); - + if (set) { + if (IS_RDONLY (inode) || is_read_only(lo_device) + || !(lo_file->f_mode & FMODE_WRITE)) + lo_flags |= LO_FLAGS_READ_ONLY; + + set_device_ro(dev, (lo_flags & LO_FLAGS_READ_ONLY)!=0); + + lo->lo_current = 0; + lo->lo_pending_reads [0] = 0; + lo->lo_pending_reads [1] = 0; + lo->lo_device = lo_device; + lo->lo_flags = lo_flags; + lo->lo_backing_file = file; + lo->lo_change = NULL; + lo->transfer = NULL; + lo->ioctl = NULL; + figure_loop_size(lo); + lo->old_gfp_mask = inode->i_mapping->gfp_mask; + inode->i_mapping->gfp_mask = GFP_NOIO; + + bs = 0; + if (blksize_size[MAJOR(lo_device)]) + bs = blksize_size[MAJOR(lo_device)][MINOR(lo_device)]; + if (!bs) + bs = BLOCK_SIZE; + + lo->lo_blksize = bs; + set_blocksize(dev, bs); + + lo->lo_bh = lo->lo_bhtail = NULL; + kernel_thread(loop_thread, lo, CLONE_FS | CLONE_FILES | CLONE_SIGHAND); + down(&lo->lo_sem); + } else { + int size = compute_loop_size(lo, file->f_dentry, lo_device); + struct semaphore sem; + kdev_t old_device; + struct file *old_backing_file; + + error = -EINVAL; + if (size != loop_sizes[lo->lo_number]) + /* Loop size has changed. Don't allow it. */ + goto out_put_all; + + init_MUTEX(&sem); + + spin_lock_irq(&lo->lo_lock); + if (lo->lo_pending_reads [lo->lo_current]) { + lo->lo_current ^= 1; + lo->lo_change = &sem; + down(&sem); + } + old_device = lo->lo_device; + old_backing_file = lo->lo_backing_file; + lo->lo_device = lo_device; + lo->lo_backing_file = file; + lo->lo_flags = lo_flags | LO_FLAGS_READ_ONLY; + spin_unlock_irq(&lo->lo_lock); + + /* Wait until there are no more outstanding requests to the + old backing file/device */ + down(&sem); + lo->lo_change = NULL; + fput(old_backing_file); + error = 0; + } + +out_put_all: fput(file); - return 0; - - out_putf: + return 0; + +out_putf: fput(file); - out: - MOD_DEC_USE_COUNT; + +out: + if (set) + MOD_DEC_USE_COUNT; return error; } - + static int loop_release_xfer(struct loop_device *lo) { int err = 0; @@ -864,7 +938,10 @@ down(&lo->lo_ctl_mutex); switch (cmd) { case LOOP_SET_FD: - err = loop_set_fd(lo, file, inode->i_rdev, arg); + err = loop_change_fd(lo, file, inode->i_rdev, arg, 1); + break; + case LOOP_CHANGE_FD: + err = loop_change_fd(lo, file, inode->i_rdev, arg, 0); break; case LOOP_CLR_FD: err = loop_clr_fd(lo, inode->i_bdev); diff -uNr linux.orig/include/linux/loop.h linux/include/linux/loop.h --- linux.orig/include/linux/loop.h 2002-12-05 13:35:46.000000000 -0700 +++ linux/include/linux/loop.h 2002-12-30 16:29:56.000000000 -0700 @@ -48,6 +48,10 @@ int old_gfp_mask; + int lo_blksize; + int lo_current; + unsigned long lo_pending_reads[2]; + struct semaphore *lo_change; spinlock_t lo_lock; struct buffer_head *lo_bh; struct buffer_head *lo_bhtail; @@ -151,5 +155,6 @@ #define LOOP_CLR_FD 0x4C01 #define LOOP_SET_STATUS 0x4C02 #define LOOP_GET_STATUS 0x4C03 +#define LOOP_CHANGE_FD 0x4C04 #endif