diff options
author | Russell King <rmk+kernel@arm.linux.org.uk> | 2009-09-22 19:54:53 (GMT) |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2009-09-22 20:01:40 (GMT) |
commit | ae19ffbadc1b2100285a5b5b3d0a4e0a11390904 (patch) | |
tree | 3c2086ab67398a019089a47ca3f362a4bc6db74f /drivers/staging/cowloop/cowloop.c | |
parent | 34e84f39a27d059a3e6ec6e8b94aafa702e6f220 (diff) | |
parent | 9173a8ef24a6b1b8031507b35b8ffe5f85a87692 (diff) | |
download | linux-fsl-qoriq-ae19ffbadc1b2100285a5b5b3d0a4e0a11390904.tar.xz |
Merge branch 'master' into for-linus
Diffstat (limited to 'drivers/staging/cowloop/cowloop.c')
-rw-r--r-- | drivers/staging/cowloop/cowloop.c | 2842 |
1 files changed, 2842 insertions, 0 deletions
diff --git a/drivers/staging/cowloop/cowloop.c b/drivers/staging/cowloop/cowloop.c new file mode 100644 index 0000000..a71c743 --- /dev/null +++ b/drivers/staging/cowloop/cowloop.c @@ -0,0 +1,2842 @@ +/* +** COWLOOP block device driver (2.6 kernel compliant) +** ======================================================================= +** Read-write loop-driver with copy-on-write functionality. +** +** Synopsis: +** +** modprobe cowloop [maxcows=..] [rdofile=..... cowfile=.... [option=r]] +** +** Definition of number of configured cowdevices: +** maxcows= number of configured cowdevices (default: 16) +** (do not confuse this with MAXCOWS: absolute maximum as compiled) +** +** One pair of filenames can be supplied during insmod/modprobe to open +** the first cowdevice: +** rdofile= read-only file (or filesystem) +** cowfile= storage-space for modified blocks of read-only file(system) +** option=r repair cowfile automatically if it appears to be dirty +** +** Other cowdevices can be activated via the command "cowdev" +** whenever the cowloop-driver is loaded. +** +** The read-only file may be of type 'regular' or 'block-device'. +** +** The cowfile must be of type 'regular'. +** If an existing regular file is used as cowfile, its contents will be +** used again for the current read-only file. When the cowfile has not been +** closed properly during a previous session (i.e. rmmod cowloop), the +** cowloop-driver refuses to open it unless the parameter "option=r" is +** specified. +** +** Layout of cowfile: +** +** +-----------------------------+ +** | cow head block | MAPUNIT bytes +** |-----------------------------| +** | | MAPUNIT bytes +** |--- ---| +** | | MAPUNIT bytes +** |--- ---| +** | used-block bitmap | MAPUNIT bytes +** |-----------------------------| +** | gap to align start-offset | +** | to 4K multiple | +** |-----------------------------| <---- start-offset cow blocks +** | | +** | written cow blocks | MAPUNIT bytes +** | ..... | +** +** cowhead block: +** - contains general info about the rdofile which is related +** to this cowfile +** +** used-block bitmap: +** - contains one bit per block with a size of MAPUNIT bytes +** - bit-value '1' = block has been written on cow +** '0' = block unused on cow +** - total bitmap rounded to multiples of MAPUNIT +** +** ============================================================================ +** Author: Gerlof Langeveld - AT Computing (March 2003) +** Current maintainer: Hendrik-Jan Thomassen - AT Computing (Summer 2006) +** Email: hjt@ATComputing.nl +** ---------------------------------------------------------------------------- +** Copyright (C) 2003-2009 AT Consultancy +** +** This program is free software; you can redistribute it and/or modify it +** under the terms of the GNU General Public License as published by the +** Free Software Foundation; either version 2, or (at your option) any +** later version. +** +** This program is distributed in the hope that it will be useful, but +** WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +** See the GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +** ---------------------------------------------------------------------------- +** +** Major modifications: +** +** 200405 Ported to kernel-version 2.6 Hendrik-Jan Thomassen +** 200405 Added cowhead to cowfile to garantee +** consistency with read-only file Gerlof Langeveld +** 200405 Postponed flushing of bitmaps to improve +** performance. Gerlof Langeveld +** 200405 Inline recovery for dirty cowfiles. Gerlof Langeveld +** 200502 Redesign to support more cowdevices. Gerlof Langeveld +** 200502 Support devices/file > 2 Gbytes. Gerlof Langeveld +** 200507 Check for free space to expand cowfile. Gerlof Langeveld +** 200902 Upgrade for kernel 2.6.28 Hendrik-Jan Thomassen +** +** Inspired by +** loop.c by Theodore Ts'o and +** cloop.c by Paul `Rusty' Russell & Klaus Knopper. +** +** Design-considerations: +** +** For the first experiments with the cowloop-driver, the request-queue +** made use of the do_generic_file_read() which worked fine except +** in combination with the cloop-driver; that combination +** resulted in a non-interruptible hangup of the system during +** heavy load. Other experiments using the `make_request' interface also +** resulted in unpredictable system hangups (with proper use of spinlocks). +** +** To overcome these problems, the cowloop-driver starts a kernel-thread +** for every active cowdevice. +** All read- and write-request on the read-only file and copy-on-write file +** are handled in the context of that thread. +** A scheme has been designed to wakeup the kernel-thread as +** soon as I/O-requests are available in the request-queue; this thread +** handles the requests one-by-one by calling the proper read- or +** write-function related to the open read-only file or copy-on-write file. +** When all pending requests have been handled, the kernel-thread goes +** back to sleep-state. +** This approach requires some additional context-switches; however the +** performance loss during heavy I/O is less than 3%. +** +** -------------------------------------------------------------------------*/ +/* The following is the cowloop package version number. It must be + identical to the content of the include-file "version.h" that is + used in all supporting utilities: */ +char revision[] = "$Revision: 3.1 $"; /* cowlo_init_module() has + assumptions about this string's format */ + +/* Note that the following numbers are *not* the cowloop package version + numbers, but separate revision history numbers to track the + modifications of this particular source file: */ +/* $Log: cowloop.c,v $ +** +** Revision 1.30 2009/02/08 hjt +** Integrated earlier fixes +** Upgraded to kernel 2.6.28 (thanks Jerome Poulin) +** +** Revision 1.29 2006/12/03 22:12:00 hjt +** changed 'cowdevlock' from spinlock to semaphore, to avoid +** "scheduling while atomic". Contributed by Juergen Christ. +** Added version.h again +** +** Revision 1.28 2006/08/16 16:00:00 hjt +** malloc each individual cowloopdevice struct separately +** +** Revision 1.27 2006/03/14 14:57:03 root +** Removed include version.h +** +** Revision 1.26 2005/08/08 11:22:48 root +** Implement possibility to close a cow file or reopen a cowfile read-only. +** +** Revision 1.25 2005/08/03 14:00:39 root +** Added modinfo info to driver. +** +** Revision 1.24 2005/07/21 06:14:53 root +** Cosmetic changes source code. +** +** Revision 1.23 2005/07/20 13:07:32 root +** Supply ioctl to write watchdog program to react on lack of cowfile space. +** +** Revision 1.22 2005/07/20 07:53:34 root +** Regular verification of free space in filesystem holding the cowfile +** (give warnings whenever space is almost exhausted). +** Terminology change: checksum renamed to fingerprint. +** +** Revision 1.21 2005/07/19 09:21:52 root +** Removing maximum limit of 16 Gb per cowdevice. +** +** Revision 1.20 2005/07/19 07:50:33 root +** Minor bugfixes and cosmetic changes. +** +** Revision 1.19 2005/06/10 12:29:55 root +** Removed lock/unlock operation from cowlo_open(). +** +** Revision 1.18 2005/05/09 12:56:26 root +** Allow a cowdevice to be open more than once +** (needed for support of ReiserFS and XFS). +** +** Revision 1.17 2005/03/17 14:36:16 root +** Fixed some license issues. +** +** Revision 1.16 2005/03/07 14:42:05 root +** Only allow one parallel open per cowdevice. +** +** Revision 1.15 2005/02/18 11:52:04 gerlof +** Redesign to support more than one cowdevice > 2 Gb space. +** +** Revision 1.14 2004/08/17 14:19:16 gerlof +** Modified output of /proc/cowloop. +** +** Revision 1.13 2004/08/16 07:21:10 gerlof +** Separate statistical counter for read on rdofile and cowfile. +** +** Revision 1.12 2004/08/11 06:52:11 gerlof +** Modified messages. +** +** Revision 1.11 2004/08/11 06:44:11 gerlof +** Modified log messages. +** +** Revision 1.10 2004/08/10 12:27:27 gerlof +** Cosmetic changes. +** +** Revision 1.9 2004/08/09 11:43:37 gerlof +** Removed double definition of major number (COWMAJOR). +** +** Revision 1.8 2004/08/09 08:03:39 gerlof +** Cleanup of messages. +** +** Revision 1.7 2004/05/27 06:37:33 gerlof +** Modified /proc message. +** +** Revision 1.6 2004/05/26 21:23:28 gerlof +** Modified /proc output. +** +** Revision 1.5 2004/05/26 13:23:34 gerlof +** Support cowsync to force flushing the bitmaps and cowhead. +** +** Revision 1.4 2004/05/26 11:11:10 gerlof +** Updated the comment to the actual situation. +** +** Revision 1.3 2004/05/26 10:50:00 gerlof +** Implemented recovery-option. +** +** Revision 1.2 2004/05/25 15:14:41 gerlof +** Modified bitmap flushing strategy. +** +*/ + +#define COWMAJOR 241 + +// #define COWDEBUG + +#ifdef COWDEBUG +#define DEBUGP printk +#define DCOW KERN_ALERT +#else +#define DEBUGP(format, x...) +#endif + +#include <linux/types.h> +#include <linux/autoconf.h> +#ifndef AUTOCONF_INCLUDED +#include <linux/config.h> +#endif +#include <linux/module.h> +#include <linux/version.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/stat.h> +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/semaphore.h> +#include <asm/uaccess.h> +#include <linux/proc_fs.h> +#include <linux/blkdev.h> +#include <linux/buffer_head.h> +#include <linux/hdreg.h> +#include <linux/genhd.h> +#include <linux/statfs.h> + +#include "cowloop.h" + +MODULE_LICENSE("GPL"); +/* MODULE_AUTHOR("Gerlof Langeveld <gerlof@ATComputing.nl>"); obsolete address */ +MODULE_AUTHOR("Hendrik-Jan Thomassen <hjt@ATComputing.nl>"); /* current maintainer */ +MODULE_DESCRIPTION("Copy-on-write loop driver"); +MODULE_PARM_DESC(maxcows, " Number of configured cowdevices (default 16)"); +MODULE_PARM_DESC(rdofile, " Read-only file for /dev/cow/0"); +MODULE_PARM_DESC(cowfile, " Cowfile for /dev/cow/0"); +MODULE_PARM_DESC(option, " Repair cowfile if inconsistent: option=r"); + +#define DEVICE_NAME "cow" + +#define DFLCOWS 16 /* default cowloop devices */ + +static int maxcows = DFLCOWS; +module_param(maxcows, int, 0); +static char *rdofile = ""; +module_param(rdofile, charp, 0); +static char *cowfile = ""; +module_param(cowfile, charp, 0); +static char *option = ""; +module_param(option, charp, 0); + +/* +** per cowdevice several bitmap chunks are allowed of MAPCHUNKSZ each +** +** each bitmap chunk can describe MAPCHUNKSZ * 8 * MAPUNIT bytes of data +** suppose: +** MAPCHUNKSZ 4096 and MAPUNIT 1024 --> 4096 * 8 * 1024 = 32 Mb per chunk +*/ +#define MAPCHUNKSZ 4096 /* #bytes per bitmap chunk (do not change) */ + +#define SPCMINBLK 100 /* space threshold to give warning messages */ +#define SPCDFLINTVL 16 /* once every SPCDFLINTVL writes to cowfile, */ + /* available space in filesystem is checked */ + +#define CALCMAP(x) ((x)/(MAPCHUNKSZ*8)) +#define CALCBYTE(x) (((x)%(MAPCHUNKSZ*8))>>3) +#define CALCBIT(x) ((x)&7) + +#define ALLCOW 1 +#define ALLRDO 2 +#define MIXEDUP 3 + +static char allzeroes[MAPUNIT]; + +/* +** administration per cowdevice (pair of cowfile/rdofile) +*/ + +/* bit-values for state */ +#define COWDEVOPEN 0x01 /* cowdevice opened */ +#define COWRWCOWOPEN 0x02 /* cowfile opened read-write */ +#define COWRDCOWOPEN 0x04 /* cowfile opened read-only */ +#define COWWATCHDOG 0x08 /* ioctl for watchdog cowfile space active */ + +#define COWCOWOPEN (COWRWCOWOPEN|COWRDCOWOPEN) + +struct cowloop_device +{ + /* + ** current status + */ + int state; /* bit-values (see above) */ + int opencnt; /* # opens for cowdevice */ + + /* + ** open file pointers + */ + struct file *rdofp, *cowfp; /* open file pointers */ + char *rdoname, *cowname; /* file names */ + + /* + ** request queue administration + */ + struct request_queue *rqueue; + spinlock_t rqlock; + struct gendisk *gd; + + /* + ** administration about read-only file + */ + unsigned int numblocks; /* # blocks input file in MAPUNIT */ + unsigned int blocksz; /* minimum unit to access this dev */ + unsigned long fingerprint; /* fingerprint of current rdofile */ + struct block_device *belowdev; /* block device below us */ + struct gendisk *belowgd; /* gendisk for blk dev below us */ + struct request_queue *belowq; /* req. queue of blk dev below us */ + + /* + ** bitmap administration to register which blocks are modified + */ + long int mapsize; /* total size of bitmap (bytes) */ + long int mapremain; /* remaining bytes in last bitmap */ + int mapcount; /* number of bitmaps in use */ + char **mapcache; /* area with pointers to bitmaps */ + + char *iobuf; /* databuffer of MAPUNIT bytes */ + struct cowhead *cowhead; /* buffer containing cowhead */ + + /* + ** administration for interface with the kernel-thread + */ + int pid; /* pid==0: no thread available */ + struct request *req; /* request to be handled now */ + wait_queue_head_t waitq; /* wait-Q: thread waits for work */ + char closedown; /* boolean: thread exit required */ + char qfilled; /* boolean: I/O request pending */ + char iobusy; /* boolean: req under treatment */ + + /* + ** administration to keep track of free space in cowfile filesystem + */ + unsigned long blksize; /* block size of fs (bytes) */ + unsigned long blktotal; /* recent total space in fs (blocks) */ + unsigned long blkavail; /* recent free space in fs (blocks) */ + + wait_queue_head_t watchq; /* wait-Q: watcher awaits threshold */ + unsigned long watchthresh; /* threshold of watcher (blocks) */ + + /* + ** statistical counters + */ + unsigned long rdoreads; /* number of read-actions rdo */ + unsigned long cowreads; /* number of read-actions cow */ + unsigned long cowwrites; /* number of write-actions */ + unsigned long nrcowblocks; /* number of blocks in use on cow */ +}; + +static struct cowloop_device **cowdevall; /* ptr to ptrs to all cowdevices */ +static struct semaphore cowdevlock; /* generic lock for cowdevs */ + +static struct gendisk *cowctlgd; /* gendisk control channel */ +static spinlock_t cowctlrqlock; /* for req.q. of ctrl. channel */ + +/* +** private directory /proc/cow +*/ +struct proc_dir_entry *cowlo_procdir; + +/* +** function prototypes +*/ +static long int cowlo_do_request (struct request *req); +static void cowlo_sync (void); +static int cowlo_checkio (struct cowloop_device *, int, loff_t); +static int cowlo_readmix (struct cowloop_device *, void *, int, loff_t); +static int cowlo_writemix (struct cowloop_device *, void *, int, loff_t); +static long int cowlo_readrdo (struct cowloop_device *, void *, int, loff_t); +static long int cowlo_readcow (struct cowloop_device *, void *, int, loff_t); +static long int cowlo_readcowraw (struct cowloop_device *, void *, int, loff_t); +static long int cowlo_writecow (struct cowloop_device *, void *, int, loff_t); +static long int cowlo_writecowraw(struct cowloop_device *, void *, int, loff_t); +static int cowlo_ioctl (struct block_device *, fmode_t, + unsigned int, unsigned long); +static int cowlo_makepair (struct cowpair __user *); +static int cowlo_removepair (unsigned long __user *); +static int cowlo_watch (struct cowpair __user *); +static int cowlo_cowctl (unsigned long __user *, int); +static int cowlo_openpair (char *, char *, int, int); +static int cowlo_closepair (struct cowloop_device *); +static int cowlo_openrdo (struct cowloop_device *, char *); +static int cowlo_opencow (struct cowloop_device *, char *, int); +static void cowlo_undo_openrdo(struct cowloop_device *); +static void cowlo_undo_opencow(struct cowloop_device *); + +/*****************************************************************************/ +/* System call handling */ +/*****************************************************************************/ + +/* +** handle system call open()/mount() +** +** returns: +** 0 - okay +** < 0 - error value +*/ +static int cowlo_open(struct block_device *bdev, fmode_t mode) +{ + struct inode *inode = bdev->bd_inode; + + if (!inode) + return -EINVAL; + + if (imajor(inode) != COWMAJOR) { + printk(KERN_WARNING + "cowloop - unexpected major %d\n", imajor(inode)); + return -ENODEV; + } + + switch (iminor(inode)) { + case COWCTL: + DEBUGP(DCOW"cowloop - open %d control\n", COWCTL); + break; + + default: + DEBUGP(DCOW"cowloop - open minor %d\n", iminor(inode)); + + if ( iminor(inode) >= maxcows ) + return -ENODEV; + + if ( !((cowdevall[iminor(inode)])->state & COWDEVOPEN) ) + return -ENODEV; + + (cowdevall[iminor(inode)])->opencnt++; + } + + return 0; +} + +/* +** handle system call close()/umount() +** +** returns: +** 0 - okay +*/ +static int cowlo_release(struct gendisk *gd, fmode_t mode) +{ + struct block_device *bdev; + struct inode *inode; + + bdev = bdget_disk(gd, 0); + inode = bdev->bd_inode; + if (!inode) + return 0; + + DEBUGP(DCOW"cowloop - release (close) minor %d\n", iminor(inode)); + + if ( iminor(inode) != COWCTL) + (cowdevall[iminor(inode)])->opencnt--; + + return 0; +} + +/* +** handle system call ioctl() +** +** returns: +** 0 - okay +** < 0 - error value +*/ +static int cowlo_ioctl(struct block_device *bdev, fmode_t mode, + unsigned int cmd, unsigned long arg) +{ + struct hd_geometry geo; + struct inode *inode = bdev->bd_inode; + + DEBUGP(DCOW "cowloop - ioctl cmd %x\n", cmd); + + switch ( iminor(inode) ) { + + /* + ** allowed via control device only + */ + case COWCTL: + switch (cmd) { + /* + ** write all bitmap chunks and cowheaders to cowfiles + */ + case COWSYNC: + down(&cowdevlock); + cowlo_sync(); + up(&cowdevlock); + return 0; + + /* + ** open a new cowdevice (pair of rdofile/cowfile) + */ + case COWMKPAIR: + return cowlo_makepair((void __user *)arg); + + /* + ** close a cowdevice (pair of rdofile/cowfile) + */ + case COWRMPAIR: + return cowlo_removepair((void __user *)arg); + + /* + ** watch free space of filesystem containing cowfile + */ + case COWWATCH: + return cowlo_watch((void __user *)arg); + + /* + ** close cowfile for active device + */ + case COWCLOSE: + return cowlo_cowctl((void __user *)arg, COWCLOSE); + + /* + ** reopen cowfile read-only for active device + */ + case COWRDOPEN: + return cowlo_cowctl((void __user *)arg, COWRDOPEN); + + default: + return -EINVAL; + } /* end of switch on command */ + + /* + ** allowed for any other cowdevice + */ + default: + switch (cmd) { + /* + ** HDIO_GETGEO must be supported for fdisk, etc + */ + case HDIO_GETGEO: + geo.cylinders = 0; + geo.heads = 0; + geo.sectors = 0; + + if (copy_to_user((void __user *)arg, &geo, sizeof geo)) + return -EFAULT; + return 0; + + default: + return -EINVAL; + } /* end of switch on ioctl-cmd code parameter */ + } /* end of switch on minor number */ +} + +static struct block_device_operations cowlo_fops = +{ + .owner = THIS_MODULE, + .open = cowlo_open, /* called upon open */ + .release = cowlo_release, /* called upon close */ + .ioctl = cowlo_ioctl, /* called upon ioctl */ +}; + +/* +** handle ioctl-command COWMKPAIR: +** open a new cowdevice (pair of rdofile/cowfile) on-the-fly +** +** returns: +** 0 - okay +** < 0 - error value +*/ +static int +cowlo_makepair(struct cowpair __user *arg) +{ + int i, rv=0; + struct cowpair cowpair; + unsigned char *cowpath; + unsigned char *rdopath; + + /* + ** retrieve info about pathnames + */ + if ( copy_from_user(&cowpair, arg, sizeof cowpair) ) + return -EFAULT; + + if ( (MAJOR(cowpair.device) != COWMAJOR) && (cowpair.device != ANYDEV) ) + return -EINVAL; + + if ( (MINOR(cowpair.device) >= maxcows) && (cowpair.device != ANYDEV) ) + return -EINVAL; + + /* + ** retrieve pathname strings + */ + if ( (cowpair.cowflen > PATH_MAX) || (cowpair.rdoflen > PATH_MAX) ) + return -ENAMETOOLONG; + + if ( !(cowpath = kmalloc(cowpair.cowflen+1, GFP_KERNEL)) ) + return -ENOMEM; + + if ( copy_from_user(cowpath, (void __user *)cowpair.cowfile, + cowpair.cowflen) ) { + kfree(cowpath); + return -EFAULT; + } + *(cowpath+cowpair.cowflen) = 0; + + if ( !(rdopath = kmalloc(cowpair.rdoflen+1, GFP_KERNEL)) ) { + kfree(cowpath); + return -ENOMEM; + } + + if ( copy_from_user(rdopath, (void __user *)cowpair.rdofile, + cowpair.rdoflen) ) { + kfree(rdopath); + kfree(cowpath); + return -EFAULT; + } + *(rdopath+cowpair.rdoflen) = 0; + + /* + ** open new cowdevice + */ + if ( cowpair.device == ANYDEV) { + /* + ** search first unused minor + */ + for (i=0, rv=-EBUSY; i < maxcows; i++) { + if ( !((cowdevall[i])->state & COWDEVOPEN) ) { + rv = cowlo_openpair(rdopath, cowpath, 0, i); + break; + } + } + + if (rv) { /* open failed? */ + kfree(rdopath); + kfree(cowpath); + return rv; + } + + /* + ** return newly allocated cowdevice to user space + */ + cowpair.device = MKDEV(COWMAJOR, i); + + if ( copy_to_user(arg, &cowpair, sizeof cowpair)) { + kfree(rdopath); + kfree(cowpath); + return -EFAULT; + } + } else { /* specific minor requested */ + if ( (rv = cowlo_openpair(rdopath, cowpath, 0, + MINOR(cowpair.device)))) { + kfree(rdopath); + kfree(cowpath); + return rv; + } + } + + return 0; +} + +/* +** handle ioctl-command COWRMPAIR: +** deactivate an existing cowdevice (pair of rdofile/cowfile) on-the-fly +** +** returns: +** 0 - okay +** < 0 - error value +*/ +static int +cowlo_removepair(unsigned long __user *arg) +{ + unsigned long cowdevice; + struct cowloop_device *cowdev; + + /* + ** retrieve info about device to be removed + */ + if ( copy_from_user(&cowdevice, arg, sizeof cowdevice)) + return -EFAULT; + + /* + ** verify major-minor number + */ + if ( MAJOR(cowdevice) != COWMAJOR) + return -EINVAL; + + if ( MINOR(cowdevice) >= maxcows) + return -EINVAL; + + cowdev = cowdevall[MINOR(cowdevice)]; + + if ( !(cowdev->state & COWDEVOPEN) ) + return -ENODEV; + + /* + ** synchronize bitmaps and close cowdevice + */ + if (cowdev->state & COWRWCOWOPEN) { + down(&cowdevlock); + cowlo_sync(); + up(&cowdevlock); + } + + return cowlo_closepair(cowdev); +} + +/* +** handle ioctl-command COWWATCH: +** watch the free space of the filesystem containing a cowfile +** of an open cowdevice +** +** returns: +** 0 - okay +** < 0 - error value +*/ +static int +cowlo_watch(struct cowpair __user *arg) +{ + struct cowloop_device *cowdev; + struct cowwatch cowwatch; + + /* + ** retrieve structure holding info + */ + if ( copy_from_user(&cowwatch, arg, sizeof cowwatch)) + return -EFAULT; + + /* + ** verify if cowdevice exists and is currently open + */ + if ( MINOR(cowwatch.device) >= maxcows) + return -EINVAL; + + cowdev = cowdevall[MINOR(cowwatch.device)]; + + if ( !(cowdev->state & COWDEVOPEN) ) + return -ENODEV; + + /* + ** if the WATCHWAIT-option is set, wait until the indicated + ** threshold is reached (only one waiter allowed) + */ + if (cowwatch.flags & WATCHWAIT) { + /* + ** check if already another waiter active + ** for this cowdevice + */ + if (cowdev->state & COWWATCHDOG) + return -EAGAIN; + + cowdev->state |= COWWATCHDOG; + + cowdev->watchthresh = (unsigned long long) + cowwatch.threshold / + (cowdev->blksize / 1024); + + if (wait_event_interruptible(cowdev->watchq, + cowdev->watchthresh >= cowdev->blkavail)) { + cowdev->state &= ~COWWATCHDOG; + return EINTR; + } + + cowdev->state &= ~COWWATCHDOG; + } + + cowwatch.totalkb = (unsigned long long)cowdev->blktotal * + cowdev->blksize / 1024; + cowwatch.availkb = (unsigned long long)cowdev->blkavail * + cowdev->blksize / 1024; + + if ( copy_to_user(arg, &cowwatch, sizeof cowwatch)) + return -EFAULT; + + return 0; +} + +/* +** handle ioctl-commands COWCLOSE and COWRDOPEN: +** COWCLOSE - close the cowfile while the cowdevice remains open; +** this allows an unmount of the filesystem on which +** the cowfile resides +** COWRDOPEN - close the cowfile and reopen it for read-only; +** this allows a remount read-ony of the filesystem +** on which the cowfile resides +** +** returns: +** 0 - okay +** < 0 - error value +*/ +static int +cowlo_cowctl(unsigned long __user *arg, int cmd) +{ + struct cowloop_device *cowdev; + unsigned long cowdevice; + + /* + ** retrieve info about device to be removed + */ + if ( copy_from_user(&cowdevice, arg, sizeof cowdevice)) + return -EFAULT; + + /* + ** verify major-minor number + */ + if ( MAJOR(cowdevice) != COWMAJOR) + return -EINVAL; + + if ( MINOR(cowdevice) >= maxcows) + return -EINVAL; + + cowdev = cowdevall[MINOR(cowdevice)]; + + if ( !(cowdev->state & COWDEVOPEN) ) + return -ENODEV; + + /* + ** synchronize bitmaps and close cowfile + */ + if (cowdev->state & COWRWCOWOPEN) { + down(&cowdevlock); + cowlo_sync(); + up(&cowdevlock); + } + + /* + ** handle specific ioctl-command + */ + switch (cmd) { + case COWRDOPEN: + /* + ** if the cowfile is still opened read-write + */ + if (cowdev->state & COWRWCOWOPEN) { + /* + ** close the cowfile + */ + if (cowdev->cowfp) + filp_close(cowdev->cowfp, 0); + + cowdev->state &= ~COWRWCOWOPEN; + + /* + ** open again for read-only + */ + cowdev->cowfp = filp_open(cowdev->cowname, + O_RDONLY|O_LARGEFILE, 0600); + + if ( (cowdev->cowfp == NULL) || IS_ERR(cowdev->cowfp) ) { + printk(KERN_ERR + "cowloop - failed to reopen cowfile %s\n", + cowdev->cowname); + return -EINVAL; + } + + /* + ** mark cowfile open for read-only + */ + cowdev->state |= COWRDCOWOPEN; + } else { + return -EINVAL; + } + break; + + case COWCLOSE: + /* + ** if the cowfile is still open + */ + if (cowdev->state & COWCOWOPEN) { + /* + ** close the cowfile + */ + if (cowdev->cowfp) + filp_close(cowdev->cowfp, 0); + + cowdev->state &= ~COWCOWOPEN; + } + } + + return 0; +} + + +/*****************************************************************************/ +/* Handling of I/O-requests for a cowdevice */ +/*****************************************************************************/ + +/* +** function to be called by core-kernel to handle the I/O-requests +** in the queue +*/ +static void cowlo_request(struct request_queue *q) +{ + struct request *req; + struct cowloop_device *cowdev; + + DEBUGP(DCOW "cowloop - request function called....\n"); + + while((req = blk_peek_request(q)) != NULL) { + DEBUGP(DCOW "cowloop - got next request\n"); + + if (! blk_fs_request(req)) { + /* this is not a normal file system request */ + __blk_end_request_cur(req, -EIO); + continue; + } + cowdev = req->rq_disk->private_data; + + if (cowdev->iobusy) + return; + else + cowdev->iobusy = 1; + + /* + ** when no kernel-thread is available, the request will + ** produce an I/O-error + */ + if (!cowdev->pid) { + printk(KERN_ERR"cowloop - no thread available\n"); + __blk_end_request_cur(req, -EIO); /* request failed */ + cowdev->iobusy = 0; + continue; + } + + /* + ** handle I/O-request in the context of the kernel-thread + */ + cowdev->req = req; + cowdev->qfilled = 1; + + wake_up_interruptible_sync(&cowdev->waitq); + + /* + ** get out of this function now while the I/O-request is + ** under treatment of the kernel-thread; this function + ** will be called again after the current I/O-request has + ** been finished by the thread + */ + return; + } +} + +/* +** daemon-process (kernel-thread) executes this function +*/ +static int +cowlo_daemon(struct cowloop_device *cowdev) +{ + int rv; + int minor; + char myname[16]; + + for (minor = 0; minor < maxcows; minor++) { + if (cowdev == cowdevall[minor]) break; + } + sprintf(myname, "cowloopd%d", minor); + + daemonize(myname); + + while (!cowdev->closedown) { + /* + ** sleep while waiting for an I/O request; + ** note that no non-interruptible wait has been used + ** because the non-interruptible version of + ** a *synchronous* wake_up does not exist (any more) + */ + if (wait_event_interruptible(cowdev->waitq, cowdev->qfilled)){ + flush_signals(current); /* ignore signal-based wakeup */ + continue; + } + + if (cowdev->closedown) /* module will be unloaded ? */{ + cowdev->pid = 0; + return 0; + } + + /* + ** woken up by the I/O-request handler: treat requested I/O + */ + cowdev->qfilled = 0; + + rv = cowlo_do_request(cowdev->req); + + /* + ** reacquire the queue-spinlock for manipulating + ** the request-queue and dequeue the request + */ + spin_lock_irq(&cowdev->rqlock); + + __blk_end_request_cur(cowdev->req, rv); + cowdev->iobusy = 0; + + /* + ** initiate the next request from the queue + */ + cowlo_request(cowdev->rqueue); + + spin_unlock_irq(&cowdev->rqlock); + } + return 0; +} + +/* +** function to be called in the context of the kernel thread +** to handle the queued I/O-requests +** +** returns: +** 0 - fail +** 1 - success +*/ +static long int +cowlo_do_request(struct request *req) +{ + unsigned long len; + long int rv; + loff_t offset; + struct cowloop_device *cowdev = req->rq_disk->private_data; + + /* + ** calculate some variables which are needed later on + */ + len = blk_rq_cur_sectors(req) << 9; + offset = (loff_t) blk_rq_pos(req) << 9; + + DEBUGP(DCOW"cowloop - req cmd=%d offset=%lld len=%lu addr=%p\n", + *(req->cmd), offset, len, req->buffer); + + /* + ** handle READ- or WRITE-request + */ + switch (rq_data_dir(req)) { + /**********************************************************/ + case READ: + switch ( cowlo_checkio(cowdev, len, offset) ) { + case ALLCOW: + rv = cowlo_readcow(cowdev, req->buffer, len, offset); + break; + + case ALLRDO: + rv = cowlo_readrdo(cowdev, req->buffer, len, offset); + break; + + case MIXEDUP: + rv = cowlo_readmix(cowdev, req->buffer, len, offset); + break; + + default: + rv = 0; /* never happens */ + } + break; + + /**********************************************************/ + case WRITE: + switch ( cowlo_checkio(cowdev, len, offset) ) { + case ALLCOW: + /* + ** straight-forward write will do... + */ + DEBUGP(DCOW"cowloop - write straight "); + + rv = cowlo_writecow(cowdev, req->buffer, len, offset); + break; /* from switch */ + + case ALLRDO: + if ( (len & MUMASK) == 0) { + DEBUGP(DCOW"cowloop - write straight "); + + rv = cowlo_writecow(cowdev, req->buffer, + len, offset); + break; + } + + case MIXEDUP: + rv = cowlo_writemix(cowdev, req->buffer, len, offset); + break; + + default: + rv = 0; /* never happens */ + } + break; + + default: + printk(KERN_ERR + "cowloop - unrecognized command %d\n", *(req->cmd)); + rv = 0; + } + + return (rv <= 0 ? 0 : 1); +} + +/* +** check for a given I/O-request if all underlying blocks +** (with size MAPUNIT) are either in the read-only file or in +** the cowfile (or a combination of the two) +** +** returns: +** ALLRDO - all underlying blocks in rdofile +** ALLCOW - all underlying blocks in cowfile +** MIXEDUP - underlying blocks partly in rdofile and partly in cowfile +*/ +static int +cowlo_checkio(struct cowloop_device *cowdev, int len, loff_t offset) +{ + unsigned long mapnum, bytenum, bitnum, blocknr, partlen; + long int totcnt, cowcnt; + char *mc; + + /* + ** notice that the requested block might cross + ** a blocksize boundary while one of the concerned + ** blocks resides in the read-only file and another + ** one in the copy-on-write file; in that case the + ** request will be broken up into pieces + */ + if ( (len <= MAPUNIT) && + (MAPUNIT - (offset & MUMASK) <= len) ) { + /* + ** easy situation: + ** requested data-block entirely fits within + ** the mapunit used for the bitmap + ** check if that block is located in rdofile or + ** cowfile + */ + blocknr = offset >> MUSHIFT; + + mapnum = CALCMAP (blocknr); + bytenum = CALCBYTE(blocknr); + bitnum = CALCBIT (blocknr); + + if (*(*(cowdev->mapcache+mapnum)+bytenum)&(1<<bitnum)) + return ALLCOW; + else + return ALLRDO; + } + + /* + ** less easy situation: + ** the requested data-block does not fit within the mapunit + ** used for the bitmap + ** check if *all* underlying blocks involved reside on the rdofile + ** or the cowfile (so still no breakup required) + */ + for (cowcnt=totcnt=0; len > 0; len-=partlen, offset+=partlen, totcnt++){ + /* + ** calculate blocknr of involved block + */ + blocknr = offset >> MUSHIFT; + + /* + ** calculate partial length for this transfer + */ + partlen = MAPUNIT - (offset & MUMASK); + if (partlen > len) + partlen = len; + + /* + ** is this block located in the cowfile + */ + mapnum = CALCMAP (blocknr); + bytenum = CALCBYTE(blocknr); + bitnum = CALCBIT (blocknr); + + mc = *(cowdev->mapcache+mapnum); + + if (*(mc+bytenum)&(1<<bitnum)) + cowcnt++;; + + DEBUGP(DCOW + "cowloop - check %lu - map %lu, byte %lu, bit %lu, " + "cowcnt %ld, totcnt %ld %02x %p\n", + blocknr, mapnum, bytenum, bitnum, cowcnt, totcnt, + *(mc+bytenum), mc); + } + + if (cowcnt == 0) /* all involved blocks on rdofile? */ + return ALLRDO; + + if (cowcnt == totcnt) /* all involved blocks on cowfile? */ + return ALLCOW; + + /* + ** situation somewhat more complicated: + ** involved underlying blocks spread over both files + */ + return MIXEDUP; +} + +/* +** read requested chunk partly from rdofile and partly from cowfile +** +** returns: +** 0 - fail +** 1 - success +*/ +static int +cowlo_readmix(struct cowloop_device *cowdev, void *buf, int len, loff_t offset) +{ + unsigned long mapnum, bytenum, bitnum, blocknr, partlen; + long int rv; + char *mc; + + /* + ** complicated approach: breakup required of read-request + */ + for (rv=1; len > 0; len-=partlen, buf+=partlen, offset+=partlen) { + /* + ** calculate blocknr of entire block + */ + blocknr = offset >> MUSHIFT; + + /* + ** calculate partial length for this transfer + */ + partlen = MAPUNIT - (offset & MUMASK); + if (partlen > len) + partlen = len; + + /* + ** is this block located in the cowfile + */ + mapnum = CALCMAP (blocknr); + bytenum = CALCBYTE(blocknr); + bitnum = CALCBIT (blocknr); + mc = *(cowdev->mapcache+mapnum); + + if (*(mc+bytenum)&(1<<bitnum)) { + /* + ** read (partial) block from cowfile + */ + DEBUGP(DCOW"cowloop - split read " + "cow partlen=%ld off=%lld\n", partlen, offset); + + if (cowlo_readcow(cowdev, buf, partlen, offset) <= 0) + rv = 0; + } else { + /* + ** read (partial) block from rdofile + */ + DEBUGP(DCOW"cowloop - split read " + "rdo partlen=%ld off=%lld\n", partlen, offset); + + if (cowlo_readrdo(cowdev, buf, partlen, offset) <= 0) + rv = 0; + } + } + + return rv; +} + +/* +** chunk to be written to the cowfile needs pieces to be +** read from the rdofile +** +** returns: +** 0 - fail +** 1 - success +*/ +static int +cowlo_writemix(struct cowloop_device *cowdev, void *buf, int len, loff_t offset) +{ + unsigned long mapnum, bytenum, bitnum, blocknr, partlen; + long int rv; + char *mc; + + /* + ** somewhat more complicated stuff is required: + ** if the request is larger than one underlying + ** block or is spread over two underlying blocks, + ** split the request into pieces; if a block does not + ** start at a block boundary, take care that + ** surrounding data is read first (if needed), + ** fit the new data in and write it as a full block + */ + for (rv=1; len > 0; len-=partlen, buf+=partlen, offset+=partlen) { + /* + ** calculate partial length for this transfer + */ + partlen = MAPUNIT - (offset & MUMASK); + if (partlen > len) + partlen = len; + + /* + ** calculate blocknr of entire block + */ + blocknr = offset >> MUSHIFT; + + /* + ** has this block been written before? + */ + mapnum = CALCMAP (blocknr); + bytenum = CALCBYTE(blocknr); + bitnum = CALCBIT (blocknr); + mc = *(cowdev->mapcache+mapnum); + + if (*(mc+bytenum)&(1<<bitnum)) { + /* + ** block has been written before; + ** write transparantly to cowfile + */ + DEBUGP(DCOW + "cowloop - splitwr transp\n"); + + if (cowlo_writecow(cowdev, buf, partlen, offset) <= 0) + rv = 0; + } else { + /* + ** block has never been written before, + ** so read entire block from + ** read-only file first, unless + ** a full block is requested to + ** be written + */ + if (partlen < MAPUNIT) { + if (cowlo_readrdo(cowdev, cowdev->iobuf, + MAPUNIT, (loff_t)blocknr << MUSHIFT) <= 0) + rv = 0; + } + + /* + ** transfer modified part into + ** the block just read + */ + memcpy(cowdev->iobuf + (offset & MUMASK), buf, partlen); + + /* + ** write entire block to cowfile + */ + DEBUGP(DCOW"cowloop - split " + "partlen=%ld off=%lld\n", + partlen, (loff_t)blocknr << MUSHIFT); + + if (cowlo_writecow(cowdev, cowdev->iobuf, MAPUNIT, + (loff_t)blocknr << MUSHIFT) <= 0) + rv = 0; + } + } + + return rv; +} + +/*****************************************************************************/ +/* I/O-support for read-only file and copy-on-write file */ +/*****************************************************************************/ + +/* +** read data from the read-only file +** +** return-value: similar to user-mode read +*/ +static long int +cowlo_readrdo(struct cowloop_device *cowdev, void *buf, int len, loff_t offset) +{ + long int rv; + mm_segment_t old_fs; + loff_t saveoffset = offset; + + DEBUGP(DCOW"cowloop - readrdo called\n"); + + old_fs = get_fs(); + set_fs( get_ds() ); + rv = cowdev->rdofp->f_op->read(cowdev->rdofp, buf, len, &offset); + set_fs(old_fs); + + if (rv < len) { + printk(KERN_WARNING "cowloop - read-failure %ld on rdofile" + "- offset=%lld len=%d\n", + rv, saveoffset, len); + } + + cowdev->rdoreads++; + return rv; +} + +/* +** read cowfile from a modified offset, i.e. skipping the bitmap and cowhead +** +** return-value: similar to user-mode read +*/ +static long int +cowlo_readcow(struct cowloop_device *cowdev, void *buf, int len, loff_t offset) +{ + DEBUGP(DCOW"cowloop - readcow called\n"); + + offset += cowdev->cowhead->doffset; + + return cowlo_readcowraw(cowdev, buf, len, offset); +} + +/* +** read cowfile from an absolute offset +** +** return-value: similar to user-mode read +*/ +static long int +cowlo_readcowraw(struct cowloop_device *cowdev, + void *buf, int len, loff_t offset) +{ + long int rv; + mm_segment_t old_fs; + loff_t saveoffset = offset; + + DEBUGP(DCOW"cowloop - readcowraw called\n"); + + /* + ** be sure that cowfile is opened for read-write + */ + if ( !(cowdev->state & COWCOWOPEN) ) { + printk(KERN_WARNING + "cowloop - read request from cowfile refused\n"); + + return -EBADF; + } + + /* + ** issue low level read + */ + old_fs = get_fs(); + set_fs( get_ds() ); + rv = cowdev->cowfp->f_op->read(cowdev->cowfp, buf, len, &offset); + set_fs(old_fs); + + if (rv < len) { + printk(KERN_WARNING + "cowloop - read-failure %ld on cowfile" + "- offset=%lld len=%d\n", rv, saveoffset, len); + } + + cowdev->cowreads++; + return rv; +} + +/* +** write cowfile from a modified offset, i.e. skipping the bitmap and cowhead +** +** if a block is written for the first time while its contents consists +** of binary zeroes only, the concerning bitmap is flushed to the cowfile +** +** return-value: similar to user-mode write +*/ +static long int +cowlo_writecow(struct cowloop_device *cowdev, void *buf, int len, loff_t offset) +{ + long int rv; + unsigned long mapnum=0, mapbyte=0, mapbit=0, cowblock=0, partlen; + char *tmpptr, *mapptr = NULL; + loff_t tmpoffset, mapoffset = 0; + + DEBUGP(DCOW"cowloop - writecow called\n"); + + /* + ** be sure that cowfile is opened for read-write + */ + if ( !(cowdev->state & COWRWCOWOPEN) ) { + printk(KERN_WARNING + "cowloop - Write request to cowfile refused\n"); + + return -EBADF; + } + + /* + ** write the entire block to the cowfile + */ + tmpoffset = offset + cowdev->cowhead->doffset; + + rv = cowlo_writecowraw(cowdev, buf, len, tmpoffset); + + /* + ** verify if enough space available on filesystem holding + ** the cowfile + ** - when the last write failed (might be caused by lack of space) + ** - when a watcher is active (to react adequatly) + ** - when the previous check indicated fs was almost full + ** - with regular intervals + */ + if ( (rv <= 0) || + (cowdev->state & COWWATCHDOG) || + (cowdev->blkavail / 2 < SPCDFLINTVL) || + (cowdev->cowwrites % SPCDFLINTVL == 0) ) { + struct kstatfs ks; + + if (vfs_statfs(cowdev->cowfp->f_dentry, &ks)==0){ + if (ks.f_bavail <= SPCMINBLK) { + switch (ks.f_bavail) { + case 0: + case 1: + case 2: + case 3: + printk(KERN_ALERT + "cowloop - " + "ALERT: cowfile full!\n"); + break; + + default: + printk(KERN_WARNING + "cowloop - cowfile almost " + "full (only %llu Kb free)\n", + (unsigned long long) + ks.f_bsize * ks.f_bavail /1024); + } + } + + cowdev->blktotal = ks.f_blocks; + cowdev->blkavail = ks.f_bavail; + + /* + ** wakeup watcher if threshold has been reached + */ + if ( (cowdev->state & COWWATCHDOG) && + (cowdev->watchthresh >= cowdev->blkavail) ) { + wake_up_interruptible(&cowdev->watchq); + } + } + } + + if (rv <= 0) + return rv; + + DEBUGP(DCOW"cowloop - block written\n"); + + /* + ** check if block(s) is/are written to the cowfile + ** for the first time; if so, adapt the bitmap + */ + for (; len > 0; len-=partlen, offset+=partlen, buf+=partlen) { + /* + ** calculate partial length for this transfer + */ + partlen = MAPUNIT - (offset & MUMASK); + if (partlen > len) + partlen = len; + + /* + ** calculate bitnr of written chunk of cowblock + */ + cowblock = offset >> MUSHIFT; + + mapnum = CALCMAP (cowblock); + mapbyte = CALCBYTE(cowblock); + mapbit = CALCBIT (cowblock); + + if (*(*(cowdev->mapcache+mapnum)+mapbyte) & (1<<mapbit)) + continue; /* already written before */ + + /* + ** if the block is written for the first time, + ** the corresponding bit should be set in the bitmap + */ + *(*(cowdev->mapcache+mapnum)+mapbyte) |= (1<<mapbit); + + cowdev->nrcowblocks++; + + DEBUGP(DCOW"cowloop - bitupdate blk=%ld map=%ld " + "byte=%ld bit=%ld\n", + cowblock, mapnum, mapbyte, mapbit); + + /* + ** check if the cowhead in the cowfile is currently + ** marked clean; if so, mark it dirty and flush it + */ + if ( !(cowdev->cowhead->flags &= COWDIRTY)) { + cowdev->cowhead->flags |= COWDIRTY; + + cowlo_writecowraw(cowdev, cowdev->cowhead, + MAPUNIT, (loff_t)0); + } + + /* + ** if the written datablock contained binary zeroes, + ** the bitmap block should be marked to be flushed to disk + ** (blocks containing all zeroes cannot be recovered by + ** the cowrepair-program later on if cowloop is not properly + ** removed via rmmod) + */ + if ( memcmp(buf, allzeroes, partlen) ) /* not all zeroes? */ + continue; /* no flush needed */ + + /* + ** calculate positions of bitmap block to be flushed + ** - pointer of bitmap block in memory + ** - offset of bitmap block in cowfile + */ + tmpptr = *(cowdev->mapcache+mapnum) + (mapbyte & (~MUMASK)); + tmpoffset = (loff_t) MAPUNIT + mapnum * MAPCHUNKSZ + + (mapbyte & (~MUMASK)); + + /* + ** flush a bitmap block at the moment that all bits have + ** been set in that block, i.e. at the moment that we + ** switch to another bitmap block + */ + if ( (mapoffset != 0) && (mapoffset != tmpoffset) ) { + if (cowlo_writecowraw(cowdev, mapptr, MAPUNIT, + mapoffset) < 0) { + printk(KERN_WARNING + "cowloop - write-failure on bitmap - " + "blk=%ld map=%ld byte=%ld bit=%ld\n", + cowblock, mapnum, mapbyte, mapbit); + } + + DEBUGP(DCOW"cowloop - bitmap blk written %lld\n", + mapoffset); + } + + /* + ** remember offset in cowfile and offset in memory + ** for bitmap to be flushed; flushing will be done + ** as soon as all updates in this bitmap block have + ** been done + */ + mapoffset = tmpoffset; + mapptr = tmpptr; + } + + /* + ** any new block written containing binary zeroes? + */ + if (mapoffset) { + if (cowlo_writecowraw(cowdev, mapptr, MAPUNIT, mapoffset) < 0) { + printk(KERN_WARNING + "cowloop - write-failure on bitmap - " + "blk=%ld map=%ld byte=%ld bit=%ld\n", + cowblock, mapnum, mapbyte, mapbit); + } + + DEBUGP(DCOW"cowloop - bitmap block written %lld\n", mapoffset); + } + + return rv; +} + +/* +** write cowfile from an absolute offset +** +** return-value: similar to user-mode write +*/ +static long int +cowlo_writecowraw(struct cowloop_device *cowdev, + void *buf, int len, loff_t offset) +{ + long int rv; + mm_segment_t old_fs; + loff_t saveoffset = offset; + + DEBUGP(DCOW"cowloop - writecowraw called\n"); + + /* + ** be sure that cowfile is opened for read-write + */ + if ( !(cowdev->state & COWRWCOWOPEN) ) { + printk(KERN_WARNING + "cowloop - write request to cowfile refused\n"); + + return -EBADF; + } + + /* + ** issue low level write + */ + old_fs = get_fs(); + set_fs( get_ds() ); + rv = cowdev->cowfp->f_op->write(cowdev->cowfp, buf, len, &offset); + set_fs(old_fs); + + if (rv < len) { + printk(KERN_WARNING + "cowloop - write-failure %ld on cowfile" + "- offset=%lld len=%d\n", rv, saveoffset, len); + } + + cowdev->cowwrites++; + return rv; +} + + +/* +** readproc-function: called when the corresponding /proc-file is read +*/ +static int +cowlo_readproc(char *buf, char **start, off_t pos, int cnt, int *eof, void *p) +{ + struct cowloop_device *cowdev = p; + + revision[sizeof revision - 3] = '\0'; + + return sprintf(buf, + " cowloop version: %9s\n\n" + " device state: %s%s%s%s\n" + " number of opens: %9d\n" + " pid of thread: %9d\n\n" + " read-only file: %9s\n" + " rdoreads: %9lu\n\n" + "copy-on-write file: %9s\n" + " state cowfile: %9s\n" + " bitmap-blocks: %9lu (of %d bytes)\n" + " cowblocks in use: %9lu (of %d bytes)\n" + " cowreads: %9lu\n" + " cowwrites: %9lu\n", + &revision[11], + + cowdev->state & COWDEVOPEN ? "devopen " : "", + cowdev->state & COWRWCOWOPEN ? "cowopenrw " : "", + cowdev->state & COWRDCOWOPEN ? "cowopenro " : "", + cowdev->state & COWWATCHDOG ? "watchdog " : "", + + cowdev->opencnt, + cowdev->pid, + cowdev->rdoname, + cowdev->rdoreads, + cowdev->cowname, + cowdev->cowhead->flags & COWDIRTY ? "dirty":"clean", + cowdev->mapsize >> MUSHIFT, MAPUNIT, + cowdev->nrcowblocks, MAPUNIT, + cowdev->cowreads, + cowdev->cowwrites); +} + +/*****************************************************************************/ +/* Setup and destroy cowdevices */ +/*****************************************************************************/ + +/* +** open and prepare a cowdevice (rdofile and cowfile) and allocate bitmaps +** +** returns: +** 0 - okay +** < 0 - error value +*/ +static int +cowlo_openpair(char *rdof, char *cowf, int autorecover, int minor) +{ + long int rv; + struct cowloop_device *cowdev = cowdevall[minor]; + struct kstatfs ks; + + down(&cowdevlock); + + /* + ** requested device exists? + */ + if (minor >= maxcows) { + up(&cowdevlock); + return -ENODEV; + } + + /* + ** requested device already assigned to cowdevice? + */ + if (cowdev->state & COWDEVOPEN) { + up(&cowdevlock); + return -EBUSY; + } + + /* + ** initialize administration + */ + memset(cowdev, 0, sizeof *cowdev); + + spin_lock_init (&cowdev->rqlock); + init_waitqueue_head(&cowdev->waitq); + init_waitqueue_head(&cowdev->watchq); + + /* + ** open the read-only file + */ + DEBUGP(DCOW"cowloop - call openrdo....\n"); + + if ( (rv = cowlo_openrdo(cowdev, rdof)) ) { + cowlo_undo_openrdo(cowdev); + up(&cowdevlock); + return rv; + } + + /* + ** open the cowfile + */ + DEBUGP(DCOW"cowloop - call opencow....\n"); + + if ( (rv = cowlo_opencow(cowdev, cowf, autorecover)) ) { + cowlo_undo_openrdo(cowdev); + cowlo_undo_opencow(cowdev); + up(&cowdevlock); + return rv; + } + + /* + ** administer total and available size of filesystem holding cowfile + */ + if (vfs_statfs(cowdev->cowfp->f_dentry, &ks)==0) { + cowdev->blksize = ks.f_bsize; + cowdev->blktotal = ks.f_blocks; + cowdev->blkavail = ks.f_bavail; + } else { + cowdev->blksize = 1024; /* avoid division by zero */ + } + + /* + ** flush the (recovered) bitmaps and cowhead to the cowfile + */ + DEBUGP(DCOW"cowloop - call cowsync....\n"); + + cowlo_sync(); + + /* + ** allocate gendisk for the cow device + */ + DEBUGP(DCOW"cowloop - alloc disk....\n"); + + if ((cowdev->gd = alloc_disk(1)) == NULL) { + printk(KERN_WARNING + "cowloop - unable to alloc_disk for cowloop\n"); + + cowlo_undo_openrdo(cowdev); + cowlo_undo_opencow(cowdev); + up(&cowdevlock); + return -ENOMEM; + } + + cowdev->gd->major = COWMAJOR; + cowdev->gd->first_minor = minor; + cowdev->gd->minors = 1; + cowdev->gd->fops = &cowlo_fops; + cowdev->gd->private_data = cowdev; + sprintf(cowdev->gd->disk_name, "%s%d", DEVICE_NAME, minor); + + /* in .5 Kb units */ + set_capacity(cowdev->gd, (cowdev->numblocks*(MAPUNIT/512))); + + DEBUGP(DCOW"cowloop - init request queue....\n"); + + if ((cowdev->rqueue = blk_init_queue(cowlo_request, &cowdev->rqlock)) + == NULL) { + printk(KERN_WARNING + "cowloop - unable to get request queue for cowloop\n"); + + del_gendisk(cowdev->gd); + cowlo_undo_openrdo(cowdev); + cowlo_undo_opencow(cowdev); + up(&cowdevlock); + return -EINVAL; + } + + blk_queue_logical_block_size(cowdev->rqueue, cowdev->blocksz); + cowdev->gd->queue = cowdev->rqueue; + + /* + ** start kernel thread to handle requests + */ + DEBUGP(DCOW"cowloop - kickoff daemon....\n"); + + cowdev->pid = kernel_thread((int (*)(void *))cowlo_daemon, cowdev, 0); + + /* + ** create a file below directory /proc/cow for this new cowdevice + */ + if (cowlo_procdir) { + char tmpname[64]; + + sprintf(tmpname, "%d", minor); + + create_proc_read_entry(tmpname, 0 , cowlo_procdir, + cowlo_readproc, cowdev); + } + + cowdev->state |= COWDEVOPEN; + + cowdev->rdoname = rdof; + cowdev->cowname = cowf; + + /* + ** enable the new disk; this triggers the first request! + */ + DEBUGP(DCOW"cowloop - call add_disk....\n"); + + add_disk(cowdev->gd); + + up(&cowdevlock); + return 0; +} + +/* +** close a cowdevice (pair of rdofile/cowfile) and release memory +** +** returns: +** 0 - okay +** < 0 - error value +*/ +static int +cowlo_closepair(struct cowloop_device *cowdev) +{ + int minor; + + down(&cowdevlock); + + /* + ** if cowdevice is not activated at all, refuse + */ + if ( !(cowdev->state & COWDEVOPEN) ) { + up(&cowdevlock); + return -ENODEV; + } + + /* + ** if this cowdevice is still open, refuse + */ + if (cowdev->opencnt > 0) { + up(&cowdevlock); + return -EBUSY; + } + + up(&cowdevlock); + + /* + ** wakeup watcher (if any) + */ + if (cowdev->state & COWWATCHDOG) { + cowdev->watchthresh = cowdev->blkavail; + wake_up_interruptible(&cowdev->watchq); + } + + /* + ** wakeup kernel-thread to be able to exit + ** and wait until it has exited + */ + cowdev->closedown = 1; + cowdev->qfilled = 1; + wake_up_interruptible(&cowdev->waitq); + + while (cowdev->pid) + schedule(); + + del_gendisk(cowdev->gd); /* revert the alloc_disk() */ + put_disk(cowdev->gd); /* revert the add_disk() */ + + if (cowlo_procdir) { + char tmpname[64]; + + for (minor = 0; minor < maxcows; minor++) { + if (cowdev == cowdevall[minor]) break; + } + sprintf(tmpname, "%d", minor); + + remove_proc_entry(tmpname, cowlo_procdir); + } + + blk_cleanup_queue(cowdev->rqueue); + + /* + ** release memory for filenames if these names have + ** been allocated dynamically + */ + if ( (cowdev->cowname) && (cowdev->cowname != cowfile)) + kfree(cowdev->cowname); + + if ( (cowdev->rdoname) && (cowdev->rdoname != rdofile)) + kfree(cowdev->rdoname); + + cowlo_undo_openrdo(cowdev); + cowlo_undo_opencow(cowdev); + + cowdev->state &= ~COWDEVOPEN; + + return 0; +} + +/* +** open the read-only file +** +** returns: +** 0 - okay +** < 0 - error value +*/ +static int +cowlo_openrdo(struct cowloop_device *cowdev, char *rdof) +{ + struct file *f; + struct inode *inode; + long int i, nrval; + + DEBUGP(DCOW"cowloop - openrdo called\n"); + + /* + ** open the read-only file + */ + if(*rdof == '\0') { + printk(KERN_ERR + "cowloop - specify name for read-only file\n\n"); + return -EINVAL; + } + + f = filp_open(rdof, O_RDONLY|O_LARGEFILE, 0); + + if ( (f == NULL) || IS_ERR(f) ) { + printk(KERN_ERR + "cowloop - open of rdofile %s failed\n", rdof); + return -EINVAL; + } + + cowdev->rdofp = f; + + inode = f->f_dentry->d_inode; + + if ( !S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode) ) { + printk(KERN_ERR + "cowloop - %s not regular file or blockdev\n", rdof); + return -EINVAL; + } + + DEBUGP(DCOW"cowloop - determine size rdo....\n"); + + /* + ** determine block-size and total size of read-only file + */ + if (S_ISREG(inode->i_mode)) { + /* + ** read-only file is a regular file + */ + cowdev->blocksz = 512; /* other value fails */ + cowdev->numblocks = inode->i_size >> MUSHIFT; + + if (inode->i_size & MUMASK) { + printk(KERN_WARNING + "cowloop - rdofile %s truncated to multiple " + "of %d bytes\n", rdof, MAPUNIT); + } + + DEBUGP(DCOW"cowloop - RO=regular: numblocks=%d, blocksz=%d\n", + cowdev->numblocks, cowdev->blocksz); + } else { + /* + ** read-only file is a block device + */ + cowdev->belowdev = inode->i_bdev; + cowdev->belowgd = cowdev->belowdev->bd_disk; /* gendisk */ + + if (cowdev->belowdev->bd_part) { + cowdev->numblocks = cowdev->belowdev->bd_part->nr_sects + / (MAPUNIT/512); + } + + if (cowdev->belowgd) { + cowdev->belowq = cowdev->belowgd->queue; + + if (cowdev->numblocks == 0) { + cowdev->numblocks = get_capacity(cowdev->belowgd) + / (MAPUNIT/512); + } + } + + + if (cowdev->belowq) + cowdev->blocksz = queue_logical_block_size(cowdev->belowq); + + if (cowdev->blocksz == 0) + cowdev->blocksz = BLOCK_SIZE; /* default 2^10 */ + + DEBUGP(DCOW"cowloop - numblocks=%d, " + "blocksz=%d, belowgd=%p, belowq=%p\n", + cowdev->numblocks, cowdev->blocksz, + cowdev->belowgd, cowdev->belowq); + + DEBUGP(DCOW"cowloop - belowdev.bd_block_size=%d\n", + cowdev->belowdev->bd_block_size); + } + + if (cowdev->numblocks == 0) { + printk(KERN_ERR "cowloop - %s has no contents\n", rdof); + return -EINVAL; + } + + /* + ** reserve space in memory as generic I/O buffer + */ + cowdev->iobuf = kmalloc(MAPUNIT, GFP_KERNEL); + + if (!cowdev->iobuf) { + printk(KERN_ERR + "cowloop - cannot get space for buffer %d\n", MAPUNIT); + return -ENOMEM; + } + + DEBUGP(DCOW"cowloop - determine fingerprint rdo....\n"); + + /* + ** determine fingerprint for read-only file + ** calculate fingerprint from first four datablocks + ** which do not contain binary zeroes + */ + for (i=0, cowdev->fingerprint=0, nrval=0; + (nrval < 4)&&(i < cowdev->numblocks); i++) { + int j; + unsigned char cs; + + /* + ** read next block + */ + if (cowlo_readrdo(cowdev, cowdev->iobuf, MAPUNIT, + (loff_t)i << MUSHIFT) < 1) + break; + + /* + ** calculate fingerprint by adding all byte-values + */ + for (j=0, cs=0; j < MAPUNIT; j++) + cs += *(cowdev->iobuf+j); + + if (cs == 0) /* block probably contained zeroes */ + continue; + + /* + ** shift byte-value to proper place in final fingerprint + */ + cowdev->fingerprint |= cs << (nrval*8); + nrval++; + } + + return 0; +} + +/* +** undo memory allocs and file opens issued so far +** related to the read-only file +*/ +static void +cowlo_undo_openrdo(struct cowloop_device *cowdev) +{ + if(cowdev->iobuf); + kfree(cowdev->iobuf); + + if (cowdev->rdofp) + filp_close(cowdev->rdofp, 0); +} + +/* +** open the cowfile +** +** returns: +** 0 - okay +** < 0 - error value +*/ +static int +cowlo_opencow(struct cowloop_device *cowdev, char *cowf, int autorecover) +{ + long int i, rv; + int minor; + unsigned long nb; + struct file *f; + struct inode *inode; + loff_t offset; + struct cowloop_device *cowtmp; + + DEBUGP(DCOW"cowloop - opencow called\n"); + + /* + ** open copy-on-write file (read-write) + */ + if (cowf[0] == '\0') { + printk(KERN_ERR + "cowloop - specify name of copy-on-write file\n\n"); + return -EINVAL; + } + + f = filp_open(cowf, O_RDWR|O_LARGEFILE, 0600); + + if ( (f == NULL) || IS_ERR(f) ) { + /* + ** non-existing cowfile: try to create + */ + f = filp_open(cowf, O_RDWR|O_CREAT|O_LARGEFILE, 0600); + + if ( (f == NULL) || IS_ERR(f) ) { + printk(KERN_ERR + "cowloop - failed to open file %s for read-write\n\n", + cowf); + return -EINVAL; + } + } + + cowdev->cowfp = f; + + inode = f->f_dentry->d_inode; + + if (!S_ISREG(inode->i_mode)) { + printk(KERN_ERR "cowloop - %s is not regular file\n", cowf); + return -EINVAL; + } + + /* + ** check if this cowfile is already in use for another cowdevice + */ + for (minor = 0; minor < maxcows; minor++) { + + cowtmp = cowdevall[minor]; + + if ( !(cowtmp->state & COWDEVOPEN) ) + continue; + + if (cowtmp == cowdev) + continue; + + if (cowtmp->cowfp->f_dentry->d_inode == f->f_dentry->d_inode) { + printk(KERN_ERR + "cowloop - %s: already in use as cow\n", cowf); + return -EBUSY; + } + } + + /* + ** mark cowfile open for read-write + */ + cowdev->state |= COWRWCOWOPEN; + + /* + ** calculate size (in bytes) for total bitmap in cowfile; + ** when the size of the cowhead block is added, the start-offset + ** for the modified data blocks can be found + */ + nb = cowdev->numblocks; + + if (nb%8) /* transform #bits to #bytes */ + nb+=8; /* rounded if necessary */ + nb /= 8; + + if (nb & MUMASK) /* round up #bytes to MAPUNIT chunks */ + cowdev->mapsize = ( (nb>>MUSHIFT) +1) << MUSHIFT; + else + cowdev->mapsize = nb; + + /* + ** reserve space in memory for the cowhead + */ + cowdev->cowhead = kmalloc(MAPUNIT, GFP_KERNEL); + + if (!cowdev->cowhead) { + printk(KERN_ERR "cowloop - cannot get space for cowhead %d\n", + MAPUNIT); + return -ENOMEM; + } + + memset(cowdev->cowhead, 0, MAPUNIT); + + DEBUGP(DCOW"cowloop - prepare cowhead....\n"); + + /* + ** check if the cowfile exists or should be created + */ + if (inode->i_size != 0) { + /* + ** existing cowfile: read the cow head + */ + if (inode->i_size < MAPUNIT) { + printk(KERN_ERR + "cowloop - existing cowfile %s too small\n", + cowf); + return -EINVAL; + } + + cowlo_readcowraw(cowdev, cowdev->cowhead, MAPUNIT, (loff_t) 0); + + /* + ** verify if the existing file is really a cowfile + */ + if (cowdev->cowhead->magic != COWMAGIC) { + printk(KERN_ERR + "cowloop - cowfile %s has incorrect format\n", + cowf); + return -EINVAL; + } + + /* + ** verify the cowhead version of the cowfile + */ + if (cowdev->cowhead->version > COWVERSION) { + printk(KERN_ERR + "cowloop - cowfile %s newer than this driver\n", + cowf); + return -EINVAL; + } + + /* + ** make sure that this is not a packed cowfile + */ + if (cowdev->cowhead->flags & COWPACKED) { + printk(KERN_ERR + "cowloop - packed cowfile %s not accepted\n", cowf); + return -EINVAL; + } + + /* + ** verify if the cowfile has been properly closed + */ + if (cowdev->cowhead->flags & COWDIRTY) { + /* + ** cowfile was not properly closed; + ** check if automatic recovery is required + ** (actual recovery will be done later on) + */ + if (!autorecover) { + printk(KERN_ERR + "cowloop - cowfile %s is dirty " + "(not properly closed by rmmod?)\n", + cowf); + printk(KERN_ERR + "cowloop - run cowrepair or specify " + "'option=r' to recover\n"); + return -EINVAL; + } + } + + /* + ** verify if the cowfile is really related to this rdofile + */ + if (cowdev->cowhead->rdoblocks != cowdev->numblocks) { + printk(KERN_ERR + "cowloop - cowfile %s (size %lld) not related " + "to rdofile (size %lld)\n", + cowf, + (long long)cowdev->cowhead->rdoblocks <<MUSHIFT, + (long long)cowdev->numblocks <<MUSHIFT); + return -EINVAL; + } + + if (cowdev->cowhead->rdofingerprint != cowdev->fingerprint) { + printk(KERN_ERR + "cowloop - cowfile %s not related to rdofile " + " (fingerprint err - rdofile modified?)\n", cowf); + return -EINVAL; + } + } else { + /* + ** new cowfile: determine the minimal size (cowhead+bitmap) + */ + offset = (loff_t) MAPUNIT + cowdev->mapsize - 1; + + if ( cowlo_writecowraw(cowdev, "", 1, offset) < 1) { + printk(KERN_ERR + "cowloop - cannot set cowfile to size %lld\n", + offset+1); + return -EINVAL; + } + + /* + ** prepare new cowhead + */ + cowdev->cowhead->magic = COWMAGIC; + cowdev->cowhead->version = COWVERSION; + cowdev->cowhead->mapunit = MAPUNIT; + cowdev->cowhead->mapsize = cowdev->mapsize; + cowdev->cowhead->rdoblocks = cowdev->numblocks; + cowdev->cowhead->rdofingerprint = cowdev->fingerprint; + cowdev->cowhead->cowused = 0; + + /* + ** calculate start offset of data in cowfile, + ** rounded up to multiple of 4K to avoid + ** unnecessary disk-usage for written datablocks in + ** the sparsed cowfile on e.g. 4K filesystems + */ + cowdev->cowhead->doffset = + ((MAPUNIT+cowdev->mapsize+4095)>>12)<<12; + } + + cowdev->cowhead->flags = 0; + + DEBUGP(DCOW"cowloop - reserve space bitmap....\n"); + + /* + ** reserve space in memory for the entire bitmap and + ** fill it with the bitmap-data from disk; the entire + ** bitmap is allocated in several chunks because kmalloc + ** has restrictions regarding the allowed size per kmalloc + */ + cowdev->mapcount = (cowdev->mapsize+MAPCHUNKSZ-1)/MAPCHUNKSZ; + + /* + ** the size of every bitmap chunk will be MAPCHUNKSZ bytes, except for + ** the last bitmap chunk: calculate remaining size for this chunk + */ + if (cowdev->mapsize % MAPCHUNKSZ == 0) + cowdev->mapremain = MAPCHUNKSZ; + else + cowdev->mapremain = cowdev->mapsize % MAPCHUNKSZ; + + /* + ** allocate space to store all pointers for the bitmap-chunks + ** (initialize area with zeroes to allow proper undo) + */ + cowdev->mapcache = kmalloc(cowdev->mapcount * sizeof(char *), + GFP_KERNEL); + if (!cowdev->mapcache) { + printk(KERN_ERR + "cowloop - can not allocate space for bitmap ptrs\n"); + return -ENOMEM; + } + + memset(cowdev->mapcache, 0, cowdev->mapcount * sizeof(char *)); + + /* + ** allocate space to store the bitmap-chunks themselves + */ + for (i=0; i < cowdev->mapcount; i++) { + if (i < (cowdev->mapcount-1)) + *(cowdev->mapcache+i) = kmalloc(MAPCHUNKSZ, GFP_KERNEL); + else + *(cowdev->mapcache+i) = kmalloc(cowdev->mapremain, + GFP_KERNEL); + + if (*(cowdev->mapcache+i) == NULL) { + printk(KERN_ERR "cowloop - no space for bitmapchunk %ld" + " totmapsz=%ld, mapcnt=%d mapunit=%d\n", + i, cowdev->mapsize, cowdev->mapcount, + MAPUNIT); + return -ENOMEM; + } + } + + DEBUGP(DCOW"cowloop - read bitmap from cow....\n"); + + /* + ** read the entire bitmap from the cowfile into the in-memory cache; + ** count the number of blocks that are in use already + ** (statistical purposes) + */ + for (i=0, offset=MAPUNIT; i < cowdev->mapcount; + i++, offset+=MAPCHUNKSZ) { + unsigned long numbytes; + + if (i < (cowdev->mapcount-1)) + /* + ** full bitmap chunk + */ + numbytes = MAPCHUNKSZ; + else + /* + ** last bitmap chunk: might be partly filled + */ + numbytes = cowdev->mapremain; + + cowlo_readcowraw(cowdev, *(cowdev->mapcache+i), + numbytes, offset); + } + + /* + ** if the cowfile was dirty and automatic recovery is required, + ** reconstruct a proper bitmap in memory now + */ + if (cowdev->cowhead->flags & COWDIRTY) { + unsigned long long blocknum; + char databuf[MAPUNIT]; + unsigned long mapnum, mapbyte, mapbit; + + printk(KERN_NOTICE "cowloop - recover dirty cowfile %s....\n", + cowf); + + /* + ** read all data blocks + */ + for (blocknum=0, rv=1, offset=0; + cowlo_readcow(cowdev, databuf, MAPUNIT, offset) > 0; + blocknum++, offset += MAPUNIT) { + + /* + ** if this datablock contains real data (not binary + ** zeroes), set the corresponding bit in the bitmap + */ + if ( memcmp(databuf, allzeroes, MAPUNIT) == 0) + continue; + + mapnum = CALCMAP (blocknum); + mapbyte = CALCBYTE(blocknum); + mapbit = CALCBIT (blocknum); + + *(*(cowdev->mapcache+mapnum)+mapbyte) |= (1<<mapbit); + } + + printk(KERN_NOTICE "cowloop - cowfile recovery completed\n"); + } + + /* + ** count all bits set in the bitmaps for statistical purposes + */ + for (i=0, cowdev->nrcowblocks = 0; i < cowdev->mapcount; i++) { + long numbytes; + char *p; + + if (i < (cowdev->mapcount-1)) + numbytes = MAPCHUNKSZ; + else + numbytes = cowdev->mapremain; + + p = *(cowdev->mapcache+i); + + for (numbytes--; numbytes >= 0; numbytes--, p++) { + /* + ** for only eight checks the following construction + ** is faster than a loop-construction + */ + if ((*p) & 0x01) cowdev->nrcowblocks++; + if ((*p) & 0x02) cowdev->nrcowblocks++; + if ((*p) & 0x04) cowdev->nrcowblocks++; + if ((*p) & 0x08) cowdev->nrcowblocks++; + if ((*p) & 0x10) cowdev->nrcowblocks++; + if ((*p) & 0x20) cowdev->nrcowblocks++; + if ((*p) & 0x40) cowdev->nrcowblocks++; + if ((*p) & 0x80) cowdev->nrcowblocks++; + } + } + + /* + ** consistency-check for number of bits set in bitmap + */ + if ( !(cowdev->cowhead->flags & COWDIRTY) && + (cowdev->cowhead->cowused != cowdev->nrcowblocks) ) { + printk(KERN_ERR "cowloop - inconsistent cowfile admi\n"); + return -EINVAL; + } + + return 0; +} + +/* +** undo memory allocs and file opens issued so far +** related to the cowfile +*/ +static void +cowlo_undo_opencow(struct cowloop_device *cowdev) +{ + int i; + + if (cowdev->mapcache) { + for (i=0; i < cowdev->mapcount; i++) { + if (*(cowdev->mapcache+i) != NULL) + kfree( *(cowdev->mapcache+i) ); + } + + kfree(cowdev->mapcache); + } + + if (cowdev->cowhead) + kfree(cowdev->cowhead); + + if ( (cowdev->state & COWCOWOPEN) && (cowdev->cowfp) ) + filp_close(cowdev->cowfp, 0); + + /* + ** mark cowfile closed + */ + cowdev->state &= ~COWCOWOPEN; +} + +/* +** flush the entire bitmap and the cowhead (clean) to the cowfile +** +** must be called with the cowdevices-lock set +*/ +static void +cowlo_sync(void) +{ + int i, minor; + loff_t offset; + struct cowloop_device *cowdev; + + for (minor=0; minor < maxcows; minor++) { + cowdev = cowdevall[minor]; + if ( ! (cowdev->state & COWRWCOWOPEN) ) + continue; + + for (i=0, offset=MAPUNIT; i < cowdev->mapcount; + i++, offset += MAPCHUNKSZ) { + unsigned long numbytes; + + if (i < (cowdev->mapcount-1)) + /* + ** full bitmap chunk + */ + numbytes = MAPCHUNKSZ; + else + /* + ** last bitmap chunk: might be partly filled + */ + numbytes = cowdev->mapremain; + + DEBUGP(DCOW + "cowloop - flushing bitmap %2d (%3ld Kb)\n", + i, numbytes/1024); + + if (cowlo_writecowraw(cowdev, *(cowdev->mapcache+i), + numbytes, offset) < numbytes) { + break; + } + } + + /* + ** flush clean up-to-date cowhead to cowfile + */ + cowdev->cowhead->cowused = cowdev->nrcowblocks; + cowdev->cowhead->flags &= ~COWDIRTY; + + DEBUGP(DCOW "cowloop - flushing cowhead (%3d Kb)\n", + MAPUNIT/1024); + + cowlo_writecowraw(cowdev, cowdev->cowhead, MAPUNIT, (loff_t) 0); + } +} + +/*****************************************************************************/ +/* Module loading/unloading */ +/*****************************************************************************/ + +/* +** called during insmod/modprobe +*/ +static int __init +cowlo_init_module(void) +{ + int rv; + int minor, uptocows; + + revision[sizeof revision - 3] = '\0'; + + printk(KERN_NOTICE "cowloop - (C) 2009 ATComputing.nl - version: %s\n", &revision[11]); + printk(KERN_NOTICE "cowloop - info: www.ATComputing.nl/cowloop\n"); + + memset(allzeroes, 0, MAPUNIT); + + /* + ** Setup administration for all possible cowdevices. + ** Note that their minor numbers go from 0 to MAXCOWS-1 inclusive + ** and minor == MAXCOWS-1 is reserved for the control device. + */ + if ((maxcows < 1) || (maxcows > MAXCOWS)) { + printk(KERN_WARNING + "cowloop - maxcows exceeds maximum of %d\n", MAXCOWS); + + maxcows = DFLCOWS; + } + + /* allocate room for a table with a pointer to each cowloop_device: */ + if ( (cowdevall = kmalloc(maxcows * sizeof(struct cowloop_device *), + GFP_KERNEL)) == NULL) { + printk(KERN_WARNING + "cowloop - can not alloc table for %d devs\n", maxcows); + uptocows = 0; + rv = -ENOMEM; + goto error_out; + } + memset(cowdevall, 0, maxcows * sizeof(struct cowloop_device *)); + /* then hook an actual cowloop_device struct to each pointer: */ + for (minor=0; minor < maxcows; minor++) { + if ((cowdevall[minor] = kmalloc(sizeof(struct cowloop_device), + GFP_KERNEL)) == NULL) { + printk(KERN_WARNING + "cowloop - can not alloc admin-struct for dev no %d\n", minor); + + uptocows = minor; /* this is how far we got.... */ + rv = -ENOMEM; + goto error_out; + } + memset(cowdevall[minor], 0, sizeof(struct cowloop_device)); + } + uptocows = maxcows; /* we got all devices */ + + sema_init(&cowdevlock, 1); + + /* + ** register cowloop module + */ + if ( register_blkdev(COWMAJOR, DEVICE_NAME) < 0) { + printk(KERN_WARNING + "cowloop - unable to get major %d for cowloop\n", COWMAJOR); + rv = -EIO; + goto error_out; + } + + /* + ** create a directory below /proc to allocate a file + ** for each cowdevice that is allocated later on + */ + cowlo_procdir = proc_mkdir("cow", NULL); + + /* + ** check if a cowdevice has to be opened during insmod/modprobe; + ** two parameters should be specified then: rdofile= and cowfile= + */ + if( (rdofile[0] != '\0') && (cowfile[0] != '\0') ) { + char *po = option; + int wantrecover = 0; + + /* + ** check if automatic recovery is wanted + */ + while (*po) { + if (*po == 'r') { + wantrecover = 1; + break; + } + po++; + } + + /* + ** open new cowdevice with minor number 0 + */ + if ( (rv = cowlo_openpair(rdofile, cowfile, wantrecover, 0))) { + remove_proc_entry("cow", NULL); + unregister_blkdev(COWMAJOR, DEVICE_NAME); + goto error_out; + } + } else { + /* + ** check if only one parameter has been specified + */ + if( (rdofile[0] != '\0') || (cowfile[0] != '\0') ) { + printk(KERN_ERR + "cowloop - only one filename specified\n"); + remove_proc_entry("cow", NULL); + unregister_blkdev(COWMAJOR, DEVICE_NAME); + rv = -EINVAL; + goto error_out; + } + } + + /* + ** allocate fake disk as control channel to handle the requests + ** to activate and deactivate cowdevices dynamically + */ + if (!(cowctlgd = alloc_disk(1))) { + printk(KERN_WARNING + "cowloop - unable to alloc_disk for cowctl\n"); + + remove_proc_entry("cow", NULL); + (void) cowlo_closepair(cowdevall[0]); + unregister_blkdev(COWMAJOR, DEVICE_NAME); + rv = -ENOMEM; + goto error_out; + } + + spin_lock_init(&cowctlrqlock); + cowctlgd->major = COWMAJOR; + cowctlgd->first_minor = COWCTL; + cowctlgd->minors = 1; + cowctlgd->fops = &cowlo_fops; + cowctlgd->private_data = NULL; + /* the device has capacity 0, so there will be no q-requests */ + cowctlgd->queue = blk_init_queue(NULL, &cowctlrqlock); + sprintf(cowctlgd->disk_name, "cowctl"); + set_capacity(cowctlgd, 0); + + add_disk(cowctlgd); + + printk(KERN_NOTICE "cowloop - number of configured cowdevices: %d\n", + maxcows); + if (rdofile[0] != '\0') { + printk(KERN_NOTICE "cowloop - initialized on rdofile=%s\n", + rdofile); + } else { + printk(KERN_NOTICE "cowloop - initialized without rdofile yet\n"); + } + return 0; + +error_out: + for (minor=0; minor < uptocows ; minor++) { + kfree(cowdevall[minor]); + } + kfree(cowdevall); + return rv; +} + +/* +** called during rmmod +*/ +static void __exit +cowlo_cleanup_module(void) +{ + int minor; + + /* + ** flush bitmaps and cowheads to the cowfiles + */ + down(&cowdevlock); + cowlo_sync(); + up(&cowdevlock); + + /* + ** close all cowdevices + */ + for (minor=0; minor < maxcows; minor++) + (void) cowlo_closepair(cowdevall[minor]); + + unregister_blkdev(COWMAJOR, DEVICE_NAME); + + /* + ** get rid of /proc/cow and unregister the driver + */ + remove_proc_entry("cow", NULL); + + for (minor = 0; minor < maxcows; minor++) { + kfree(cowdevall[minor]); + } + kfree(cowdevall); + + del_gendisk(cowctlgd); /* revert the alloc_disk() */ + put_disk (cowctlgd); /* revert the add_disk() */ + blk_cleanup_queue(cowctlgd->queue); /* cleanup the empty queue */ + + printk(KERN_NOTICE "cowloop - unloaded\n"); +} + +module_init(cowlo_init_module); +module_exit(cowlo_cleanup_module); |