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