diff -Nur linux-2.6.8.1_supermount/Documentation/filesystems/00-INDEX linux-2.6.8.1/Documentation/filesystems/00-INDEX --- linux-2.6.8.1_supermount/Documentation/filesystems/00-INDEX 2004-08-14 12:56:24.000000000 +0200 +++ linux-2.6.8.1/Documentation/filesystems/00-INDEX 2004-08-17 03:16:00.000000000 +0200 @@ -36,6 +36,8 @@ - Description of the ROMFS filesystem. smbfs.txt - info on using filesystems with the SMB protocol (Windows 3.11 and NT) +supermount.txt + - info on using supermount for removable media. sysv-fs.txt - info on the SystemV/V7/Xenix/Coherent filesystem. udf.txt diff -Nur linux-2.6.8.1_supermount/Documentation/filesystems/supermount.txt linux-2.6.8.1/Documentation/filesystems/supermount.txt --- linux-2.6.8.1_supermount/Documentation/filesystems/supermount.txt 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.8.1/Documentation/filesystems/supermount.txt 2004-08-17 03:16:00.000000000 +0200 @@ -0,0 +1,250 @@ +Supermount README +================= + +Running supermount +------------------ + +To run supermount, compile and install a kernel with the supermount +patches and select "Y" to the question + + Supermount removable media support (CONFIG_SUPERMOUNT) [Y/n/?] + +when you run "make config". You mount a supermount filesystem with +the normal mount command, using the syntax + + mount -t supermount -o ,--, none + +or by adding correpsonding line to /etc/fstab + + none supermount ,--, 0 0 + +where + + are the options you want to pass to supermount + itself. These are described below. + + are the options you want supermount to pass to the + dismountable filesystem underneath. + + is the mount point where you want your removable media to be + mounted. + +WARNING: in the above description `none' is literal word. While device +is ignored by supermount itself, using real files in this place (real +device name or mount point directory name) is known to cause problems. +Some programs - fuser is one of them - will try to descend into filesystem +if dev can be statted, thus making supermount to attempt to access media. +This is annoying at best - in the worst case it can take very long time +during startup or shutdown. + +Notice that you do not directly specify the block device you are going +to mount on the mount command line. This is because the supermount +filesystem is NOT connected to a block device; rather, supermount is +responsible for connecting a separate filesystem to the block device. +You specify the sub-filesystem and block device name by providing the + field, where the following options are currently +recognised: + + +* fs= [default is "auto"] + + Specify the subfilesystem type. Not every filesystem type has +been tested. If you use `auto', it will try the following filesystems +in order: + "udf" + "iso9660" + "ext2" + "vfat" + "msdos" + +It is also possible to give list of types separated by `:', like + + fs=ext2:vfat + - or - + fs=udf:iso9660 + + +* dev= [no default, mandatory] + + Specify the block device on which the subfs is to be mounted. + + +* tray_lock={always,onwrite,never} [default is "onwrite"] + + Specify when supermount is to prevent media removal. `always' means +on every access (it was default in earlier versions), `onwrite' means only +for write access and `never' means never :) `onwrite' and `never' are the +same for ro media like CD-ROM. It is not clear when `never' is actually useful, +it is presented for completeness only. + +* debug[=] [default is no debug] + + Enable debugging code in the supermount filesystem, if +the debug option was enabled at compile time. By default, debugging +code is compiled into the kernel but is disabled until a debug mount +option is seen. is the combination of debug flags, following +flags are possible: + + 0x001 - "generic" debug (used by supermount_debug) - default + 0x002 - trace dentry.c + 0x004 - trace file.c + 0x008 - trace filemap.c + 0x010 - trace mediactl.c + 0x020 - trace namei.c + 0x040 - trace subfs.c + 0x080 - trace super.c + +Trace flags turn on tracing of functions in correpsonding files. +"Generic" debug flag is tested in supermount_debug; for compatibility, +if no flags are specified, this flag is set. + + +* '--' + + All options after the option string '--' will be passed +directly to the subfilesystem when it gets mounted. + +Errors +------ + +In addition to "normal" errors during file operations supermount may +return following error codes: + +* No medium found + + You attempt to access supermounted filesystem when there is no +medium inserted + +* Wrong medium type + + (Not really generated by supermount) You attempt to mount CD +without data tracks + +* Stale NFS file handle + + You attempt to use file handle after medium has been changed. + +* No such device or address + + (Not really generated by supermount) device specified in +dev= option does not exist. Also some drivers return this +error instead of "No medium found", one example being floppy driver. + +* Device or resource busy + + (Not really generated by supermount) device is already mounted. +Supermount prevents double mount even if kernel otherwise would make it +possible. + +* No such file or directory + + (Not really generated by supermount) file name specified by +dev= option does not exist + +* Operation not permitted + + You attempt to access subfs that is currently disabled + +/proc support +------------- + +If kernel has been compiled with procfs support, supermount will provide +/proc interface to read subfs status and to control some aspects of subfs. +The following files are created under /proc/fs/supermount: + +* version (ro) + Shows supermount version. + +* subfs (rw) + + Reading this file returns list of all subfs status. One line for +every subfs is returned; the format is + + disabled + - or - + unmounted + - or - + mounted readcount writecount + +where is the string passed in `dev=' parameter during mount. +`readcount' is number of current subfs "users" needing ro access; `writecount' +is the number of "users" needing rw mode. It is mostly the number of open +files, but inode operations also add to these counts. Those operations +are normally short-lived to be seen. + + Writing this file changes subfs status; the following commands are +suported: + + [disable|enable] [release [force]] + +`disable' will disable subfs (i.e. any futher attempt to mount is rejected). +Subfs must be unmounted; use `disable release' or `disable release force' to +unmount and disable at the same time. + +`enable' will enable disabled subfs, it has no effect if subfs is already +enabled. + +`release' will unmount subfs unless it is busy (opencount > 0). To unmount +busy subfs add `force'; the effect is very much as if media change has been +detected (with the difference that subfs will be cleanly unmounted). + +Some basic sanity checks are performed, i.e. it is impossible to specify +both `enable' and `disable' or `force' without `release'. + +Internals/Locking +----------------- + +THIS SECTION IS PROBABLY INCOMPLETE. CORRECTIONS ARE WELCOME. + +Supermount itself is using two locks and relies on two more locking rules +as implemented by kernel. + +* supermount_proc_sem + + Global mutex used to protect list of sbi during access to +/proc/fs/supermount/subfs + +* sbi->sem + + Per-filesystem mutex that protects subfs state (mounted/unmounted). +Any changes to subfs state (mounting, unmouting, adding or removing file, +dentry or inode) happen under this mutex. + +* inode->i_sem (see Documentation/filesystem/Locking) + + Per-inode mutex used by VFS to serialize inode unlink operation. +Supermount relies on the fact that link/unlink for an inode are mutually +locked and thus inode->i_nlink is atomic inside of fs method. + +* BKL + + Used to protect device usage count. It is changed in open/release +and referenced in ioctl all of which run under BKL. Supermount adds mediactl +and internally wraps it in BKL as well. + +Caveats/BUGS +------------ + +Inode times are believed to be correctly updated; still it is possible that +I missed some point. + +If subfs is not yet mounted, find /mnt/cdrom fails with "find: /mnt/cdrom +changed during execution of find". It is unlikely it can be fixed in +supermount; much more simple is to provide wrapper that will check if subfs +is mounted and do simple "touch /mnt/cdrom" if not to make sure it is. + +Supermount attempts to check for changed media at every operation and +invalidate and umount old subfs to avoid new media corruption in rw case. +Still it is possible that kernel will write out stale information and +it is outside of supermount control. + +By default cdrom driver does not check media type i.e. supermount will +try all configured fs types in turn that may be quite time consuming. +Use sysctl -w dev.cdrom.check_media=1 to enable it. Comments in cdrom.c +indicate that some (non conforming) software may have problems with this +settings. YMMV + +atime management does not work for special files. There is no hooks from +VFS into fs to indicate when it gets updated + +$Id: 4715_supermount-2.0.4-2.6.7.patch,v 1.1 2004/06/22 22:15:04 gregkh Exp $ diff -Nur linux-2.6.8.1_supermount/drivers/cdrom/cdrom.c linux-2.6.8.1/drivers/cdrom/cdrom.c --- linux-2.6.8.1_supermount/drivers/cdrom/cdrom.c 2004-08-14 12:54:50.000000000 +0200 +++ linux-2.6.8.1/drivers/cdrom/cdrom.c 2004-08-17 03:42:58.000000000 +0200 @@ -275,6 +275,7 @@ #include #include #include +#include #include @@ -333,7 +334,7 @@ #define CHECKAUDIO if ((ret=check_for_audio_disc(cdi, cdo))) return ret /* Not-exported routines. */ -static int open_for_data(struct cdrom_device_info * cdi); +static int open_for_data(struct cdrom_device_info * cdi, struct block_device *bdev); static int check_for_audio_disc(struct cdrom_device_info * cdi, struct cdrom_device_ops * cdo); static void sanitize_format(union cdrom_addr *addr, @@ -885,7 +886,7 @@ */ int cdrom_open(struct cdrom_device_info *cdi, struct inode *ip, struct file *fp) { - int ret; + int ret = 0; cdinfo(CD_OPEN, "entering cdrom_open\n"); @@ -895,7 +896,7 @@ if ((fp->f_flags & O_NONBLOCK) && (cdi->options & CDO_USE_FFLAGS)) { ret = cdi->ops->open(cdi, 1); } else { - ret = open_for_data(cdi); + open_for_data(cdi, ip->i_bdev); if (ret) goto err; if (fp->f_mode & FMODE_WRITE) { @@ -923,7 +924,7 @@ } static -int open_for_data(struct cdrom_device_info * cdi) +int open_for_data(struct cdrom_device_info * cdi, struct block_device *bdev) { int ret; struct cdrom_device_ops *cdo = cdi->ops; @@ -1006,7 +1007,8 @@ cdinfo(CD_OPEN, "open device failed.\n"); goto clean_up_and_return; } - if (CDROM_CAN(CDC_LOCK) && (cdi->options & CDO_LOCK)) { + if (CDROM_CAN(CDC_LOCK) && (cdi->options & CDO_LOCK) && + supermount_usage_count(bdev, cdi->use_count) > 0) { cdo->lock_door(cdi, 1); cdinfo(CD_OPEN, "door locked.\n"); } @@ -1019,7 +1021,7 @@ This ensures that the drive gets unlocked after a mount fails. This is a goto to avoid bloating the driver with redundant code. */ clean_up_and_return: - cdinfo(CD_WARNING, "open failed.\n"); + cdinfo(CD_OPEN, "open failed.\n"); if (CDROM_CAN(CDC_LOCK) && cdi->options & CDO_LOCK) { cdo->lock_door(cdi, 0); cdinfo(CD_OPEN, "door unlocked.\n"); @@ -1096,7 +1098,7 @@ cdi->use_count--; if (cdi->use_count == 0) cdinfo(CD_CLOSE, "Use count for \"/dev/%s\" now zero\n", cdi->name); - if (cdi->use_count == 0 && + if (!supermount_usage_count(fp ? fp->f_dentry->d_inode->i_bdev : 0, cdi->use_count) && (cdo->capability & CDC_LOCK) && !keeplocked) { cdinfo(CD_CLOSE, "Unlocking door!\n"); cdo->lock_door(cdi, 0); @@ -1368,6 +1370,38 @@ tracks->cdi, tracks->xa); } +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) +/* + * MEDIA_LOCK, MEDIA_UNLOCK + * optarg == 0 - do not adjust usage count (compatibility) + * optarg == 1 - adjust usage count + */ +int cdrom_mediactl(struct cdrom_device_info *cdi, struct block_device *bdev, int op, int optarg) +{ + struct cdrom_device_ops *cdo = cdi->ops; + + switch (op) { + case MEDIA_LOCK: + case MEDIA_UNLOCK: + if (op == MEDIA_UNLOCK && optarg) { + cdi->use_count--; + if (cdi->use_count < 0) + cdi->use_count = 0; + } + if (cdo->capability & ~cdi->mask & CDC_LOCK && + cdi->options & CDO_LOCK && + supermount_usage_count(bdev, cdi->use_count) == 0) + cdo->lock_door(cdi, (op == MEDIA_LOCK)); + if (op == MEDIA_LOCK && optarg) + cdi->use_count++; + break; + default: + return -EINVAL; + } + return 0; +} +#endif + /* Requests to the low-level drivers will /always/ be done in the following format convention: @@ -2114,8 +2148,9 @@ cdinfo(CD_DO_IOCTL, "entering CDROMEJECT\n"); if (!CDROM_CAN(CDC_OPEN_TRAY)) return -ENOSYS; - if (cdi->use_count != 1 || keeplocked) - return -EBUSY; + if (keeplocked || + (supermount_usage_count(ip->i_bdev,cdi->use_count) != 1)) + return -EBUSY; if (CDROM_CAN(CDC_LOCK)) if ((ret=cdo->lock_door(cdi, 0))) return ret; @@ -2914,6 +2949,9 @@ EXPORT_SYMBOL(cdrom_release); EXPORT_SYMBOL(cdrom_ioctl); EXPORT_SYMBOL(cdrom_media_changed); +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) +EXPORT_SYMBOL(cdrom_mediactl); +#endif EXPORT_SYMBOL(cdrom_number_of_slots); EXPORT_SYMBOL(cdrom_mode_select); EXPORT_SYMBOL(cdrom_mode_sense); diff -Nur linux-2.6.8.1_supermount/drivers/cdrom/cdu31a.c linux-2.6.8.1/drivers/cdrom/cdu31a.c --- linux-2.6.8.1_supermount/drivers/cdrom/cdu31a.c 2004-08-14 12:55:47.000000000 +0200 +++ linux-2.6.8.1/drivers/cdrom/cdu31a.c 2004-08-17 03:16:00.000000000 +0200 @@ -3173,6 +3173,25 @@ static int scd_block_release(struct inode *inode, struct file *file) { +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + /* + * This is the same sort of clockload as use in blkdev_get. + * We need information whether device is supermounted inside + * of cdrom_release to decide if tray must be unlocked. + * This information so far is available only by looking + * up superblock but it needs struct *bdev and it is not + * available in cdrom_release anymore + */ + struct dentry t_dentry; + struct file t_file; + + if (!file) { + t_file.f_dentry = &t_dentry; + t_dentry.d_inode = inode; + file = &t_file; + } +#endif + return cdrom_release(&scd_info, file); } @@ -3187,6 +3206,13 @@ return cdrom_media_changed(&scd_info); } +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) +static int scd_block_mediactl(struct block_device *bdev, int op, int arg) +{ + return cdrom_mediactl(&scd_info, bdev, op, arg); +} +#endif + struct block_device_operations scd_bdops = { .owner = THIS_MODULE, @@ -3194,6 +3220,9 @@ .release = scd_block_release, .ioctl = scd_block_ioctl, .media_changed = scd_block_media_changed, +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + .mediactl = scd_block_mediactl, +#endif }; static struct gendisk *scd_gendisk; diff -Nur linux-2.6.8.1_supermount/drivers/cdrom/cm206.c linux-2.6.8.1/drivers/cdrom/cm206.c --- linux-2.6.8.1_supermount/drivers/cdrom/cm206.c 2004-08-14 12:56:15.000000000 +0200 +++ linux-2.6.8.1/drivers/cdrom/cm206.c 2004-08-17 03:16:00.000000000 +0200 @@ -1357,6 +1357,25 @@ static int cm206_block_release(struct inode *inode, struct file *file) { +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + /* + * This is the same sort of clockload as use in blkdev_get. + * We need information whether device is supermounted inside + * of cdrom_release to decide if tray must be unlocked. + * This information so far is available only by looking + * up superblock but it needs struct *bdev and it is not + * available in cdrom_release anymore + */ + struct dentry t_dentry; + struct file t_file; + + if (!file) { + t_file.f_dentry = &t_dentry; + t_dentry.d_inode = inode; + file = &t_file; + } +#endif + return cdrom_release(&cm206_info, file); } @@ -1371,6 +1390,13 @@ return cdrom_media_changed(&cm206_info); } +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) +static int cm206_block_mediactl(struct block_device *bdev, int op, int arg) +{ + return cdrom_mediactl(&cm206_info, bdev, op, arg); +} +#endif + static struct block_device_operations cm206_bdops = { .owner = THIS_MODULE, @@ -1378,6 +1404,9 @@ .release = cm206_block_release, .ioctl = cm206_block_ioctl, .media_changed = cm206_block_media_changed, +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + .mediactl = cm206_block_mediactl, +#endif }; static struct gendisk *cm206_gendisk; diff -Nur linux-2.6.8.1_supermount/drivers/cdrom/mcd.c linux-2.6.8.1/drivers/cdrom/mcd.c --- linux-2.6.8.1_supermount/drivers/cdrom/mcd.c 2004-08-14 12:55:34.000000000 +0200 +++ linux-2.6.8.1/drivers/cdrom/mcd.c 2004-08-17 03:16:00.000000000 +0200 @@ -221,6 +221,25 @@ static int mcd_block_release(struct inode *inode, struct file *file) { +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + /* + * This is the same sort of clockload as use in blkdev_get. + * We need information whether device is supermounted inside + * of cdrom_release to decide if tray must be unlocked. + * This information so far is available only by looking + * up superblock but it needs struct *bdev and it is not + * available in cdrom_release anymore + */ + struct dentry t_dentry; + struct file t_file; + + if (!file) { + t_file.f_dentry = &t_dentry; + t_dentry.d_inode = inode; + file = &t_file; + } +#endif + return cdrom_release(&mcd_info, file); } @@ -235,6 +254,13 @@ return cdrom_media_changed(&mcd_info); } +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) +static int mcd_block_mediactl(struct block_device *bdev, int op, int arg) +{ + return cdrom_mediactl(&mcd_info, bdev, op, arg); +} +#endif + static struct block_device_operations mcd_bdops = { .owner = THIS_MODULE, @@ -242,6 +268,9 @@ .release = mcd_block_release, .ioctl = mcd_block_ioctl, .media_changed = mcd_block_media_changed, +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + .mediactl = mcd_block_mediactl, +#endif }; static struct gendisk *mcd_gendisk; diff -Nur linux-2.6.8.1_supermount/drivers/cdrom/mcdx.c linux-2.6.8.1/drivers/cdrom/mcdx.c --- linux-2.6.8.1_supermount/drivers/cdrom/mcdx.c 2004-08-14 12:55:32.000000000 +0200 +++ linux-2.6.8.1/drivers/cdrom/mcdx.c 2004-08-17 03:16:00.000000000 +0200 @@ -226,6 +226,25 @@ static int mcdx_block_release(struct inode *inode, struct file *file) { struct s_drive_stuff *p = inode->i_bdev->bd_disk->private_data; +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + /* + * This is the same sort of clockload as use in blkdev_get. + * We need information whether device is supermounted inside + * of cdrom_release to decide if tray must be unlocked. + * This information so far is available only by looking + * up superblock but it needs struct *bdev and it is not + * available in cdrom_release anymore + */ + struct dentry t_dentry; + struct file t_file; + + if (!file) { + t_file.f_dentry = &t_dentry; + t_dentry.d_inode = inode; + file = &t_file; + } +#endif + return cdrom_release(&p->info, file); } @@ -242,6 +261,14 @@ return cdrom_media_changed(&p->info); } +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) +static int mcdx_block_mediactl(struct block_device *bdev, int op, int arg) +{ + struct s_drive_stuff *p = bdev->bd_disk->private_data; + return cdrom_mediactl(&p->info, bdev, op, arg); +} +#endif + static struct block_device_operations mcdx_bdops = { .owner = THIS_MODULE, @@ -249,6 +276,9 @@ .release = mcdx_block_release, .ioctl = mcdx_block_ioctl, .media_changed = mcdx_block_media_changed, +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + .mediactl = mcdx_block_mediactl, +#endif }; diff -Nur linux-2.6.8.1_supermount/drivers/cdrom/sbpcd.c linux-2.6.8.1/drivers/cdrom/sbpcd.c --- linux-2.6.8.1_supermount/drivers/cdrom/sbpcd.c 2004-08-14 12:54:50.000000000 +0200 +++ linux-2.6.8.1/drivers/cdrom/sbpcd.c 2004-08-17 03:16:00.000000000 +0200 @@ -5365,6 +5365,25 @@ static int sbpcd_block_release(struct inode *inode, struct file *file) { struct sbpcd_drive *p = inode->i_bdev->bd_disk->private_data; +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + /* + * This is the same sort of clockload as use in blkdev_get. + * We need information whether device is supermounted inside + * of cdrom_release to decide if tray must be unlocked. + * This information so far is available only by looking + * up superblock but it needs struct *bdev and it is not + * available in cdrom_release anymore + */ + struct dentry t_dentry; + struct file t_file; + + if (!file) { + t_file.f_dentry = &t_dentry; + t_dentry.d_inode = inode; + file = &t_file; + } +#endif + return cdrom_release(p->sbpcd_infop, file); } @@ -5381,6 +5400,14 @@ return cdrom_media_changed(p->sbpcd_infop); } +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) +static int sbpcd_block_mediactl(struct block_device *bdev, int op, int arg) +{ + struct sbpcd_drive *p = bdev->bd_disk->private_data; + return cdrom_mediactl(p->sbpcd_infop, bdev, op, arg); +} +#endif + static struct block_device_operations sbpcd_bdops = { .owner = THIS_MODULE, @@ -5388,6 +5415,9 @@ .release = sbpcd_block_release, .ioctl = sbpcd_block_ioctl, .media_changed = sbpcd_block_media_changed, +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + .mediactl = sbpcd_block_mediactl, +#endif }; /*==========================================================================*/ /* diff -Nur linux-2.6.8.1_supermount/drivers/ide/ide-cd.c linux-2.6.8.1/drivers/ide/ide-cd.c --- linux-2.6.8.1_supermount/drivers/ide/ide-cd.c 2004-08-14 12:55:22.000000000 +0200 +++ linux-2.6.8.1/drivers/ide/ide-cd.c 2004-08-17 03:16:00.000000000 +0200 @@ -3384,6 +3384,24 @@ { ide_drive_t *drive = inode->i_bdev->bd_disk->private_data; struct cdrom_info *info = drive->driver_data; +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + /* + * This is the same sort of clockload as use in blkdev_get. + * We need information whether device is supermounted inside + * of cdrom_release to decide if tray must be unlocked. + * This information so far is available only by looking + * up superblock but it needs struct *bdev and it is not + * available in cdrom_release anymore + */ + struct dentry t_dentry; + struct file t_file; + + if (!file) { + t_file.f_dentry = &t_dentry; + t_dentry.d_inode = inode; + file = &t_file; + } +#endif cdrom_release (&info->devinfo, file); drive->usage--; @@ -3418,13 +3436,26 @@ return 0; } +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) +static int idecd_mediactl(struct block_device *bdev, int op, int arg) +{ + struct gendisk *disk = bdev->bd_disk; + ide_drive_t *drive = disk->private_data; + struct cdrom_info *info = drive->driver_data; + return cdrom_mediactl(&info->devinfo, bdev, op, arg); +} +#endif + static struct block_device_operations idecd_ops = { .owner = THIS_MODULE, .open = idecd_open, .release = idecd_release, .ioctl = idecd_ioctl, .media_changed = idecd_media_changed, - .revalidate_disk= idecd_revalidate_disk + .revalidate_disk= idecd_revalidate_disk, +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + .mediactl = idecd_mediactl, +#endif }; /* options */ diff -Nur linux-2.6.8.1_supermount/drivers/ide/ide-floppy.c linux-2.6.8.1/drivers/ide/ide-floppy.c --- linux-2.6.8.1_supermount/drivers/ide/ide-floppy.c 2004-08-14 12:55:32.000000000 +0200 +++ linux-2.6.8.1/drivers/ide/ide-floppy.c 2004-08-17 03:16:00.000000000 +0200 @@ -98,6 +98,8 @@ #include #include +#include + #include #include #include @@ -1877,7 +1879,7 @@ debug_log(KERN_INFO "Reached idefloppy_open\n"); - if (drive->usage == 1) { + if (supermount_usage_count(inode->i_bdev, drive->usage) == 1) { clear_bit(IDEFLOPPY_FORMAT_IN_PROGRESS, &floppy->flags); /* Just in case */ @@ -1924,7 +1926,7 @@ debug_log(KERN_INFO "Reached idefloppy_release\n"); - if (drive->usage == 1) { + if (supermount_usage_count(inode->i_bdev, drive->usage) == 1) { idefloppy_floppy_t *floppy = drive->driver_data; /* IOMEGA Clik! drives do not support lock/unlock commands */ @@ -1957,7 +1959,7 @@ prevent = 0; /* fall through */ case CDROM_LOCKDOOR: - if (drive->usage > 1) + if (supermount_usage_count(bdev, drive->usage) > 1) return -EBUSY; /* The IOMEGA Clik! Drive doesn't support this command - no room for an eject mechanism */ @@ -2025,13 +2027,47 @@ return 0; } +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) +static int idefloppy_mediactl (struct block_device *bdev, int op, int optarg) +{ + ide_drive_t *drive = bdev->bd_disk->private_data; + idefloppy_floppy_t *floppy = drive->driver_data; + idefloppy_pc_t pc; + + switch (op) { + case MEDIA_LOCK: + case MEDIA_UNLOCK: + if (op == MEDIA_UNLOCK && optarg) { + drive->usage--; + if (drive->usage < 0) + drive->usage = 0; + } + /* IOMEGA Clik! drives do not support lock/unlock commands */ + if (!test_bit(IDEFLOPPY_CLIK_DRIVE, &floppy->flags) + && supermount_usage_count(bdev, drive->usage) == 0) { + idefloppy_create_prevent_cmd(&pc, (op == MEDIA_LOCK)); + (void) idefloppy_queue_pc_tail(drive, &pc); + } + if (op == MEDIA_LOCK && optarg) + drive->usage++; + break; + default: + return -EINVAL; + } + return 0; +} +#endif + static struct block_device_operations idefloppy_ops = { .owner = THIS_MODULE, .open = idefloppy_open, .release = idefloppy_release, .ioctl = idefloppy_ioctl, .media_changed = idefloppy_media_changed, - .revalidate_disk= idefloppy_revalidate_disk + .revalidate_disk= idefloppy_revalidate_disk, +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + .mediactl = idefloppy_mediactl, +#endif }; static int idefloppy_attach (ide_drive_t *drive) diff -Nur linux-2.6.8.1_supermount/drivers/scsi/sd.c linux-2.6.8.1/drivers/scsi/sd.c --- linux-2.6.8.1_supermount/drivers/scsi/sd.c 2004-08-14 12:55:48.000000000 +0200 +++ linux-2.6.8.1/drivers/scsi/sd.c 2004-08-17 03:17:14.000000000 +0200 @@ -49,6 +49,8 @@ #include #include +#include + #include #include #include @@ -469,7 +471,7 @@ if (!scsi_device_online(sdev)) goto error_out; - if (!sdkp->openers++ && sdev->removable) { + if (supermount_usage_count(inode->i_bdev, sdkp->openers++) == 0 && sdev->removable) { if (scsi_block_when_processing_errors(sdev)) scsi_set_medium_removal(sdev, SCSI_REMOVAL_PREVENT); } @@ -500,7 +502,7 @@ SCSI_LOG_HLQUEUE(3, printk("sd_release: disk=%s\n", disk->disk_name)); - if (!--sdkp->openers && sdev->removable) { + if (supermount_usage_count(inode->i_bdev, --sdkp->openers) == 0 && sdev->removable) { if (scsi_block_when_processing_errors(sdev)) scsi_set_medium_removal(sdev, SCSI_REMOVAL_ALLOW); } @@ -660,6 +662,16 @@ goto not_present; /* + * FIXME HACK + * busy device that is unplugged is SDEV_DEL but online and ioctl + * does not return any error. Oh well, it is likely layering + * violation but for now it enables media checks for supermount + */ + + if (sdp->sdev_state == SDEV_DEL) + goto not_present; + + /* * For removable scsi disk we have to recognise the presence * of a disk in the drive. This is kept in the struct scsi_disk * struct and tested at open ! Daniel Roche (dan@lectra.fr) @@ -682,6 +694,51 @@ sd_revalidate_disk(sdkp->disk); } +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) +/* + * This function performs media control operations. Currently the + * only functions used are MEDIA_LOCK and MEDIA_UNLOCK, to lock and + * unlock the drive door. + */ + +static int sd_mediactl(struct block_device *bdev, int op, int optarg) +{ + struct scsi_disk *sdkp = scsi_disk(bdev->bd_disk); + struct scsi_device *sdp = sdkp->device; + int rc = 0; + + SCSI_LOG_HLQUEUE(3, printk("sd_mediactl: disk=%s\n", + bdev->bd_disk->disk_name)); + + if (!sdp->removable) + return 0; + + if (!scsi_block_when_processing_errors(sdp)) + return -ENODEV; + + switch (op) { + case MEDIA_LOCK: + if (supermount_usage_count(bdev, sdkp->openers) == 0) + rc = scsi_set_medium_removal(sdp, SCSI_REMOVAL_PREVENT); + /* FIXME is it the right way? */ + if (optarg) + sdkp->openers++; + break; + case MEDIA_UNLOCK: + if (optarg && sdkp->openers > 0) + sdkp->openers--; + if (supermount_usage_count(bdev, sdkp->openers) == 0) + rc = scsi_set_medium_removal(sdp, SCSI_REMOVAL_ALLOW); + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} +#endif + static struct block_device_operations sd_fops = { .owner = THIS_MODULE, .open = sd_open, @@ -689,6 +746,9 @@ .ioctl = sd_ioctl, .media_changed = sd_media_changed, .revalidate_disk = sd_revalidate_disk, +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + .mediactl = sd_mediactl, +#endif }; /** diff -Nur linux-2.6.8.1_supermount/drivers/scsi/sr.c linux-2.6.8.1/drivers/scsi/sr.c --- linux-2.6.8.1_supermount/drivers/scsi/sr.c 2004-08-14 12:56:25.000000000 +0200 +++ linux-2.6.8.1/drivers/scsi/sr.c 2004-08-17 03:16:00.000000000 +0200 @@ -480,6 +480,26 @@ { int ret; struct scsi_cd *cd = scsi_cd(inode->i_bdev->bd_disk); + +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + /* + * This is the same sort of clockload as use in blkdev_get. + * We need information whether device is supermounted inside + * of cdrom_release to decide if tray must be unlocked. + * This information so far is available only by looking + * up superblock but it needs struct *bdev and it is not + * available in cdrom_release anymore + */ + struct dentry t_dentry; + struct file t_file; + + if (!file) { + t_file.f_dentry = &t_dentry; + t_dentry.d_inode = inode; + file = &t_file; + } +#endif + ret = cdrom_release(&cd->cdi, file); if(ret) return ret; @@ -513,6 +533,15 @@ return cdrom_media_changed(&cd->cdi); } +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) +static int sr_block_mediactl(struct block_device *bdev, int op, int arg) +{ + struct gendisk *disk = bdev->bd_disk; + struct scsi_cd *cd = scsi_cd(disk); + return cdrom_mediactl(&cd->cdi, bdev, op, arg); +} +#endif + struct block_device_operations sr_bdops = { .owner = THIS_MODULE, @@ -520,6 +549,9 @@ .release = sr_block_release, .ioctl = sr_block_ioctl, .media_changed = sr_block_media_changed, +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + .mediactl = sr_block_mediactl, +#endif }; static int sr_open(struct cdrom_device_info *cdi, int purpose) diff -Nur linux-2.6.8.1_supermount/fs/Kconfig linux-2.6.8.1/fs/Kconfig --- linux-2.6.8.1_supermount/fs/Kconfig 2004-08-14 12:55:33.000000000 +0200 +++ linux-2.6.8.1/fs/Kconfig 2004-08-17 03:16:00.000000000 +0200 @@ -937,6 +937,8 @@ To compile this as a module, choose M here: the module will be called ramfs. +source "fs/supermount/Kconfig" + endmenu menu "Miscellaneous filesystems" diff -Nur linux-2.6.8.1_supermount/fs/Makefile linux-2.6.8.1/fs/Makefile --- linux-2.6.8.1_supermount/fs/Makefile 2004-08-14 12:55:33.000000000 +0200 +++ linux-2.6.8.1/fs/Makefile 2004-08-17 03:16:00.000000000 +0200 @@ -91,3 +91,4 @@ obj-$(CONFIG_XFS_FS) += xfs/ obj-$(CONFIG_AFS_FS) += afs/ obj-$(CONFIG_BEFS_FS) += befs/ +obj-$(CONFIG_SUPERMOUNT) += supermount/ diff -Nur linux-2.6.8.1_supermount/fs/block_dev.c linux-2.6.8.1/fs/block_dev.c --- linux-2.6.8.1_supermount/fs/block_dev.c 2004-08-14 12:56:24.000000000 +0200 +++ linux-2.6.8.1/fs/block_dev.c 2004-08-17 03:16:00.000000000 +0200 @@ -511,12 +511,45 @@ { struct gendisk *disk = bdev->bd_disk; struct block_device_operations * bdops = disk->fops; +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + struct super_block *sb = NULL; + int supermounted = 0; +#endif if (!bdops->media_changed) return 0; if (!bdops->media_changed(bdev->bd_disk)) return 0; +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + sb = get_super(bdev); + if (sb) { + atomic_set(&sb->s_media_changed, 1); + supermounted = sb->s_flags & MS_SUPERMOUNTED; + drop_super(sb); + } + + /* + * Supermount used to did invalidate_device followed by + * destroy_buffers. invalidate_device hardly does anything + * useful here as it won't really invalidate "busy" inodes + * and every inode in subfs is busy (at least one reference + * from superfs exists). So now it just flushes + * buffers so they do not accidentally overwrite newly + * inserted media + * + * FIXME unfortunately during umount VFS may write on its + * own, like write_super. Those writes will go to a wrong + * media thus corrupting it :( + * + * There is no error print because supermount warns user + * itself. + */ + + if (supermounted) { + invalidate_bdev(bdev, 1); + } else +#endif if (__invalidate_device(bdev, 0)) printk("VFS: busy inodes on changed media.\n"); diff -Nur linux-2.6.8.1_supermount/fs/ext2/super.c linux-2.6.8.1/fs/ext2/super.c --- linux-2.6.8.1_supermount/fs/ext2/super.c 2004-08-14 12:55:35.000000000 +0200 +++ linux-2.6.8.1/fs/ext2/super.c 2004-08-17 03:16:00.000000000 +0200 @@ -303,6 +303,12 @@ {Opt_ignore, "noquota"}, {Opt_ignore, "quota"}, {Opt_ignore, "usrquota"}, +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + /* Silently ignore NLS options */ + {Opt_ignore, "iocharset"}, + {Opt_ignore, "codepage"}, + {Opt_ignore, "umask"}, +#endif {Opt_err, NULL} }; diff -Nur linux-2.6.8.1_supermount/fs/isofs/inode.c linux-2.6.8.1/fs/isofs/inode.c --- linux-2.6.8.1_supermount/fs/isofs/inode.c 2004-08-14 12:56:23.000000000 +0200 +++ linux-2.6.8.1/fs/isofs/inode.c 2004-08-17 03:16:00.000000000 +0200 @@ -364,6 +364,10 @@ {Opt_ignore, "conv=auto"}, {Opt_ignore, "conv=a"}, {Opt_nocompress, "nocompress"}, +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + {Opt_ignore, "codepage=%s"}, + {Opt_ignore, "umask=%s"}, +#endif {Opt_err, NULL} }; diff -Nur linux-2.6.8.1_supermount/fs/namespace.c linux-2.6.8.1/fs/namespace.c --- linux-2.6.8.1_supermount/fs/namespace.c 2004-08-14 12:55:35.000000000 +0200 +++ linux-2.6.8.1/fs/namespace.c 2004-08-17 03:16:00.000000000 +0200 @@ -1016,6 +1016,15 @@ if (retval) goto dput_out; +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + if (!(flags & (MS_REMOUNT | MS_MOVE)) && + (nd.mnt->mnt_sb->s_type->fs_flags & FS_NO_SUBMNT)) { + retval = -EPERM; + path_release(&nd); + return retval; + } +#endif + if (flags & MS_REMOUNT) retval = do_remount(&nd, flags & ~MS_REMOUNT, mnt_flags, data_page); diff -Nur linux-2.6.8.1_supermount/fs/super.c linux-2.6.8.1/fs/super.c --- linux-2.6.8.1_supermount/fs/super.c 2004-08-14 12:55:22.000000000 +0200 +++ linux-2.6.8.1/fs/super.c 2004-08-17 03:30:49.000000000 +0200 @@ -82,6 +82,9 @@ s->dq_op = sb_dquot_ops; s->s_qcop = sb_quotactl_ops; s->s_op = &default_op; +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + atomic_set(&s->s_media_changed, 0); +#endif } out: return s; @@ -527,6 +530,10 @@ return 0; } +#ifdef CONFIG_SUPERMOUNT_MODULE +EXPORT_SYMBOL(do_remount_sb); +#endif + static void do_emergency_remount(unsigned long foo) { struct super_block *sb; @@ -657,11 +664,18 @@ goto out; if (s->s_root) { - if ((flags ^ s->s_flags) & MS_RDONLY) { + if (((flags ^ s->s_flags) & MS_RDONLY) +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + /* disallow double mounting for supermounted device */ + || ((flags | s->s_flags) & MS_SUPERMOUNTED) +#endif + ) + { up_write(&s->s_umount); deactivate_super(s); s = ERR_PTR(-EBUSY); } + goto out; } else { char b[BDEVNAME_SIZE]; @@ -765,6 +779,17 @@ if (!type) return ERR_PTR(-ENODEV); +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + /* sanity checks; supermount relies on these assumptions */ + if (flags & MS_SUPERMOUNTED) { + sb = ERR_PTR(-EINVAL); + if (type->fs_flags & FS_ODD_RENAME) + goto out; + if (!(type->fs_flags & FS_REQUIRES_DEV)) + goto out; + sb = ERR_PTR(-ENOMEM); + } +#endif mnt = alloc_vfsmnt(name); if (!mnt) goto out; @@ -810,7 +835,9 @@ return (struct vfsmount *)sb; } -EXPORT_SYMBOL_GPL(do_kern_mount); +#ifdef CONFIG_SUPERMOUNT_MODULE +EXPORT_SYMBOL(do_kern_mount); +#endif struct vfsmount *kern_mount(struct file_system_type *type) { diff -Nur linux-2.6.8.1_supermount/fs/supermount/Kconfig linux-2.6.8.1/fs/supermount/Kconfig --- linux-2.6.8.1_supermount/fs/supermount/Kconfig 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.8.1/fs/supermount/Kconfig 2004-08-17 03:16:00.000000000 +0200 @@ -0,0 +1,26 @@ +config SUPERMOUNT + tristate "Supermount removable media support" + help + Supermount gives you the ability to access CD-ROMs and Floppies + without mounting/unmounting them every time you want to access + a different disk/floppy. Just eject the media, insert a new one + and you are able to access it. + + Read Documentation/filesystems/supermount.txt for more information. + + If you want to compile the Supermount support as a module ( = code + which can be inserted in and removed from the running kernel whenever + you want), say M here and read Documentation/modules.txt. The module + will be called supermount.o. + + If unsure, say N. + +config SUPERMOUNT_DEBUG + bool "Enable supermount debug code" + depends on SUPERMOUNT + help + If you set this to Y, additional debug code will be compiled in. + Debug output is controlled with debug=N mount option. Possible + values are listed in Documentation/filesystems/supermount.txt. + + If unsure, say Y. diff -Nur linux-2.6.8.1_supermount/fs/supermount/Makefile linux-2.6.8.1/fs/supermount/Makefile --- linux-2.6.8.1_supermount/fs/supermount/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.8.1/fs/supermount/Makefile 2004-08-17 03:16:00.000000000 +0200 @@ -0,0 +1,15 @@ +# +# Makefile for the linux supermounting routines. +# + +supermount-objs := dentry.o \ + file.o \ + filemap.o \ + init.o \ + mediactl.o \ + namei.o \ + proc.o \ + subfs.o \ + super.o + +obj-$(CONFIG_SUPERMOUNT) += supermount.o diff -Nur linux-2.6.8.1_supermount/fs/supermount/changelog linux-2.6.8.1/fs/supermount/changelog --- linux-2.6.8.1_supermount/fs/supermount/changelog 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.8.1/fs/supermount/changelog 2004-08-17 03:16:00.000000000 +0200 @@ -0,0 +1,462 @@ +* ? ? ? Andrey Borzenkov ?.?.? + +* Sun 18 Jan 2004 Andrey Borzenkov 2.0.4 + + - yet another attempt to fix detection of USB drive removale + (explicitly check for SDVE_DEL state in sd.c:sd_media_changed) + + - fixed i18n patch for ext2 and udf (ignored options) + + - ->nopage prototype changed; it makes it incompatible with + kernel < 2.6.1 (I wonder how it compiled - or did I miss + warning). Add comments about ->populate and why it won't be + implemented + + - fix "auto" option. Simplification of option processing turned + out to be a mess :( Reported by Bart + +* Mon 12 Jan 2004 Andrey Borzenkov 2.0.3b + + - Fix compilation problem in subfs.c with older GCC - declaration + after statement in clear_inodes(bug 875009). Pointed by Adrian Punga. + +* Wed 07 Jan 2004 Andrey Borzenkov 2.0.3a + + - fix Oops during options parsing if no subfs option is + given after "--" (bug 869863) + +* Mon 29 Dec 2003 Andrey Borzenkov 2.0.3 + + - move test for device offline in sd.c to the top in attempt + to fix USB removale problem + + - simplify options procesing + + - remove cdrom "open failed" message (it is now under + CDROM_OPEN flag like all others in cdrom_open). + + - rewrote option parsing to use new lib/parse library + +* Sun 26 Oct 2003 Andrey Borzenkov 2.0.2a + + - remove some obsolete files that are not used anymore from CVS + (i.e. from patch) + + - isofs needs extra ignored options now. It rurns out it silently + ignored any unknown option before new and shiny option parsing + code + + - consistently use "kernel 2.6" in version print + + - updates for -test8. Change ext2/udf patches to match new options + parsing code. + +* Wed 10 Sep 2003 Andrey Borzenkov 2.0.2 + + - NODEV is gone, use MKDEV(UNNAMED_MAJOR, 0) instead, it is just + as good as anything else + +* Sat 23 Aug 2003 Andrey Borzenkov 2.0.1d + + - rediff against 2.6.0-test4 + +* Mon 18 Aug 2003 Andrey Borzenkov 2.0.1c + + - fix compilation without CONFIG_SUPERMOUNT. Reported by + Igor Strygin + +* Sun 10 Aug 2003 Andrey Borzenkov 2.0.1b + + - /proc/fs/supermount/version is read-only, do not set permissions to + rw + + - remove generic_unplug_queue from ide_do_drive_cmd, apparently + it is redundant now (fixes bug 785691) + + - rediff for 2.6.0-test3 + +* Sun 27 Jul 2003 Andrey Borzenkov 2.0.1a + + - rediff for 2.6.0-test2 + +* Sun 13 Jul 2003 Andrey Borzenkov 2.0.1 + + - hush most unused variables warnings with CONFIG_DEBUG (idea + suggested by Jeff Garzik) + + - replace ugly SUPERMOUNT_TARCE_{ENTER,LEAVE}[_ARGS] with ENTER/LEAVE + Use %p throughout for pointer output. + + - send DN_CREATE for root on mount + + - allow open and readdir on mountpoint even if subfs is unmounted. + This provides for active monitoring by FAM/dnotify and is overall + less surprising - ls /mnt/cdrom just returns empty directory now + instead of error. For technical reasons ->flush and ->permissions + need be aware about it too. + + Files opened while subfs is unmounted are NOT usable for anything + except readdir even AFTER subfs gets mounted. So application + has to reopen directory to rescan it. AFAIK all of them do it :) + +* Sat 12 Jul 2003 Andrey Borzenkov 2.0.0a + + - add struct nameidata * goes into d_revalidate as well + + - add struct nameidata * to some inode methods (introduced in + 2.5.75). Currently it is just passed along - it is not clear if + local subfs-specific nameidata has to be constructed. Apparently + it existed in follow_link all the time without any evil done. + + - fix small races in supermount_getattr - do not attempt to + get subdent if prevent_umount failed + +* Mon 23 Jun 2003 Andrey Borzenkov 2.0.0 + + - added ->getattr; it never fails on both unmounted mountpoint + and stale open files - to ensure fuser -m /mnt/cdrom still finds them + + - merge 2.5.73; update sd.c to new refcount interface in sd.c + + - fix SUPERMOUNT_BUG_ON_LOCKED without SUPERMOUNT_DEBUG + + - add media management to ide-floppy and those CD-ROM drivers that + are using standard cdrom_* calls (cdu31a, cm206, mcd, mcdx, sbpcd). + Testdrive revealed that none of them builds for other reasons :) + + - add (un)mark_media_supermounted; mark after mount, unmark + before umount. These functions just manipulate bd_disk->scount + + - add scount to struct gendisk to serve as count of total + supermounted partitions; use everywhere to check if device + is supermounted + +* Tue 17 Jun 2003 Andrey Borzenkov 2.0.0test2 + + - update kernel patch for 2.5.72 (there was a reject in + drivers/ide/ide-io.c) + + - move init_inode_info into alloc_inode; use iget_locked + instead of iget to skip ->read_inode with intent to move + inode init under if (inode->i_state & I_NEW) + + - polish dentry.c a bit while I am on it + + - ->d_compare is not run under dcache_lock anymore. Pointed by + Maneesh Soni. As we can't sleep in d_compare, use rwlock to + protect against races with supermount_clean_dentries until + I find better solution. + + - remove new_subfs_dentry - it is not used anymore and comments + there were wrong anyway + +* Sun 01 Jun 2003 Andrey Borzenkov 2.0.0test1 + + - add sr and sd support + + - update sb s_blocksize and s_blocksize_bits on subfs mount + + - use BKL around chek_disk_change; it is not garanteed by VFS now + + - llseek no more updates file readahead pointer + + - used bdev not gendisk in mediactl - it needs it for + supermount_usage_count. It probably needs redesign. Also some minor + changes on the way. + + - 2.5 changes. supermount_get_sb, inode alloc/dealloc, use bdev instead + of dev, Makefile change, add CONFIG_SUPERMOUNT_DEBUG, do_remount_sb + prototype change, mknod prototype change, UPDATE_ATIME dead, + dnotify_parent is now true function + +* Tue 27 May 2003 Andrey Borzenkov 1.2.7 + + - (re-)set root inode flags after unmounting subfs + + - fix mounting of ro media on rw filesystem. It was broken by fix + in 1.2.4. Remove saved s_mflags and just use real sb->s_flags. + Reported by Con Kolivas + + - do not use MS_MGC_VAL in subfs_real_mount - it is not used anywhere + in kernel and all lower bits are already taken in 2.5 + + - configure help from Marc-Christian Petersen + + - use subsb->s_bdev instead of looking up using s_dev. This also + fixes problem with O(1) patch (bug 737783) + +* Wed 14 May 2003 Andrey Borzenkov 1.2.6 + + - check for correct inode in attach_subfs_inode - if super inode + has subinode attached it must be the same as is currently + resuested. Else it is a bug. + + - use lookup_one_len to lookup subfs dentry in cache; check if + subfs did not change name and lookup "real" dentry in parent + cache in this case. This finally puts an end to the attempt to + control subfs dcache. The only thing we rely upon now is that + nothing can change directory contents while in lookup method. + + - implement d_hash and d_compare to properly support case- + insensitive filesystems (vfat & Co). + + - reset super inode i_mapping so it does not point to freed + subinode mapping when unmounting subfs + + - send dnotify_parent on dentry cleanup, not on inode + + - use write_inode_now in ->write_inode. Reading intricated flags + manipulations in fs/inode.c was just too intimidating. It is not as + much overhead as it looks like - it is called from sync_one only + so all dirty pages are scheduled to be flushed anyway. + +* Sun 04 May 2003 Andrey Borzenkov 1.2.5 + + - enable dentries caching; properly use d_add when dentry is first + looked up and d_instantiate when inode is finally available. + + - first stab at sending notifications on media umount/mount + + - more caveats :( + +* Sat 26 Apr 2003 Andrey Borzenkov 1.2.4 + + - fix write_inode/prepare_inode deadlock. It looked like + set I_LOCK + down(&sbi->sem); + iget4 -> wait_on_inode + down(&sbi->sem) + + - MS_ACTIVE was reset on subfs remount + + - print process PID in trace + + - adjust directory structure to make patch generation easier + +* Sun 20 Apr 2003 Andrey Borzenkov 1.2.3 + + - dummy version due to script problems + +* Sun 20 Apr 2003 Andrey Borzenkov 1.2.2 + + - remove #ifdef CONFIG_PROC_FS from init.c, it already exists in + supermount.h + + - no_tray_lock -> TRAY_LOCK_NEVER. Just to make sure it remains + compatible (even if it is very unlikely anybody is using it). + + - backout kernel stat patch; remove getattr alltogether. It fixed + fuser -m for stale files. getattr remains uniplemented because + it does not seem to be used anywhere in kernel at all so I am not + actually sure how to implement it + + It also backouts 10_readlik and 30_lseek as they are not used + anymore for a long time. + + - remove bogus check for sub- and superinodes i_count. i_count is + outside of supermount control; the only thing that we must be + sure in - inode is free on umount. + + - reset root inode attributes and operations on umount. This fixes + the problem of attempt to mount media on ls -l /mnt after subfs + has been mounted once. + + - remove ->stale. For all practical purposes an entity is stale iff + it does not (yet) have associated subfs entity. Creating yet another + field that just mirrors this condition does not add a single bit + of useful information. + + - subfs_is_busy iff subfs mnt_count > 1 + +* Sat 05 Apr 2003 Andrey Borzenkov 1.2.1 + + - fixed stupid thinko in mediactl methods (the effect was it was + impossible to manually eject ro media). It was exposed by recent + changes. No changes in supermount itself only in driver code + + - replace no_tray_lock with tray_lock={never,onwrite,always} with + "onwrite" being default. I stil do not quite like resulting code + but it will do for now. + + - simplified subfs_(get|put)_(write|read) interface; merge both + read/write in one function; remove subfs_get_atime. To my surprise + it resulted on more clean interface in the rest of supermount even + if subfs_(get|put)_access does look a bit weird. + +* Sun 30 Mar 2003 Andrey Borzenkov 1.2.0 + + - changed proc command format to + [enable|disable] [release [force]] + + - add /proc/fs/supermount/version; printk version on starup only + if procfs is not configured. + + - unify media lock rules for thosed drivers using implementing + mediactl (cdrom, sd, ide-floppy as of this writing). Lock + media for both manual and software eject; do not unlock media + that is in used by something else. + + - added show_options method to super.c + + - implemented /proc/fs/supermount/subfs read as seq_file + +* Sat 29 Mar 2003 Andrey Borzenkov 1.1.4 + + - supermount_rename leftover - it does not need new_subfs_dentry. + It still worked because new dentry was destroyed immediately, but + let's do it the right way. Also mark target rw if exists + + - propagate S_IMMUTABLE, S_NOATIME and S_APPEND flags from subfs + to super inode. Filesystems usually rely on VFS to check these + flags which means it was possible to overwrite immutable file. Also + needed for atime management (to be added). + + - added atime management. It is based on grep UPDATE_ATIME so it may + be incomplete; still it gives right results in most obvious cases :) + It respects no(dir)atime mount flag. + + - Make sure super inode times and sizes are updated to reflect subfs + inode. Done in preparation to remove stat kernel patch. Only those + fields needed by stat are updated. + + - remove assertion subi->i_count == 1 from clear_inode. Subfs icache + is managed independently and there is no lock (as opposed to dentry + case where we never do cached lookup for subfs). + + - use inode cache for supermount inodes to avoid linear search. This + moves almost the whole inode creation task into read_inode2 - the + only place where inode needs to be created is root inode. + + We use inode numbers from subfs; root inode is never cached until + subfs has been mounted. Stale inodes are removed from cache in + clean_inodes (just like dentries are in clean_dentries) so aliasing + problem is solved. + + - add "disabled" state - prevent any attempt to mount media + + - add procfs support. Reading /proc/fs/supermount/subfs will return + list of subfs and their status. Writing into it allows you to + control status of subfs (mounted/unmounted, enabled/dusabled) + + - fix race in clear_inode. It was possible that subfs was umounted + after inode has been removed from list but before its access + counters were cleared. The code that checkes for that exists only + for debugging still I'd like it to be useful. + +* Sun 16 Mar 2003 Andrey Borzenkov 1.1.3 + + - add CVS verion Id's to files + + - finally get rid of ugly generation number. It is enough to just + mark entity obsolete; we do not care actually when it happened. + Now every entity starts out stale; stale flag is reset when subfs + object is attached; stale flag is set again for the rest of life + in subfs_umount + + - as a side effect read/write count is associated with inodes + exclusively + + - clear_inode and d_iput need prevent/allow umount to avoid "stealing" + subfs while holding active entities. File release is safe as VFS does + nou put mnt until _release is finished. + + - subfs_prevent_umount does not need validator. The worst thing that + can happen is that newly mounted subfs is accessed. That does not + matter, access to subfs object is checked for validity separately in + every case. + + - finally clarify dcache management. As we cannot garantee coherency + between sub- and super- fs caches we ignore subfs cache entirely. + Super dentry is instantiated in supermount_lookup and passed around + with subfs dentry attached. It does not matter if it is positive or + negative. Currently dentries are forced to be deleted in + supermount_d_delete but I believe it is not really needed. + + - print version number on registering filesystem to facilitate debugging + + - umount subfs as soon as possible in check_disk_change. This eliminates + zombie state (mounted but inactive due to media change detected). + + - kill subfs_is_active, it is the same as subfs_is_mounted now. + + - kill translations.c it does not do anything special now; move functions + in file/dentry/namei + + - fix stupid bug in get_supermount_inode again - super inode must + be inited and attached just once not every time. + Praise assertions again :) + +* 10 Mar 2003 Andrey Borzenkov 1.1.2 + + - allow fs=type1:type2:... + + - remove supermount_drop_dentries (no more used) + + - add some more assertions to subfs_umount + +* 10 Mar 2003 Andrey Borzenkov 1.1.1 + + - fix stupid bug in subfs inode refcounting. It was visible with + hardlinks only so CD-ROM users would not ever see it. + + - fix even more stupid bug in unlink - subi was used instead of superi + +* 8 Mar 2003 Andrey Borzenkov 1.1.0 + + - files cannot use inode obsolete flag - it does not work for + root inodes + + - make file system structure exact mirror of subfs - now there + is 1-to-1 correspondence between super- and subfs files, dentries + and inodes. This is needed to properly manage write access without + too much locking + + - finally realized that subfs cannot be obsolete; use either file + or inode pointer in all calls into subfs.c to check for freshness + + - debug is now per-mount; added trace of all functions + + - move setting of SUPERMOUNT_DEBUG to Makefile instead of supermount_i.h + +* 1 Mar 2003 Andrey Borzenkov 1.0.0 + + - make sure it is always possible umount subfs. It does it by + keeping list of opened files (in addition to inodes) and closing + them if media change is detected. + + - make sure subfs can't go away while it is being in use. For + files it get_file for subfile to ensure subfs mnt is not freed. For + inode operation it increments usage counter of submnt for duration + of operation. + + - make sure supermounted device can't be mounted twice (that is + allowed by kernel) because it relies on being the only "owner" of + subfs. It does it by checking for MS_SUPERMOUNTED flag on mount. + + - make sure you can't mount over supermounted directory. Allowing + it is a nightmare and I cannot see any reason for it (if you can + provide valid one - I may reconsider this part). It does so by + adding fstype flag and checking it on mounting. + + - reject mount -o remount for the time being (until we can deal + with it properly) + + - completely change implementation of individual inode methods. + Instead of calling VFS recursively it calls subfs methods directly. + It is to ensure we are in full control of subfs (assuming it behaves + properly :) + + - properly implement VM methods. Currently changing media would + leave you with vm_area pointing to no more existing inode ... + + - remove many obsolete pieces like flags that just duplicate + existing information. + + - ensure coherency between super- and subfs dentries. Either we + have both active in dcache or do not have them at all. + + - fixe the problem with media ejection after direct access to CD-ROM + + - add "just disc change" to all operations to detect media change + as soon as possible. It is possible to add option to do it just + for writable media or to completely turn it off. diff -Nur linux-2.6.8.1_supermount/fs/supermount/dentry.c linux-2.6.8.1/fs/supermount/dentry.c --- linux-2.6.8.1_supermount/fs/supermount/dentry.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.8.1/fs/supermount/dentry.c 2004-08-17 03:16:00.000000000 +0200 @@ -0,0 +1,282 @@ +/* + * linux/fs/supermount/dentry.c + * + * Original version: + * Copyright (C) 1995 + * Stephen Tweedie (sct@dcs.ed.ac.uk) + * + * Rewriten for kernel 2.2 & 2.4. (C) 1999, 2000 Alexis Mikhailov + * (alexis@abc.cap.ru) + * Rewritten for kernel 2.4.21 (C) 2003 Andrey Borzenkov + * (arvidjaar@mail.ru) + * Rewritten for kernel 2.5.70 (C) 2003 Andrey Borzenkov + * (arvidjaar@mail.ru) + * + * $Id: 4715_supermount-2.0.4-2.6.7.patch,v 1.1 2004/06/22 22:15:04 gregkh Exp $ + */ + +#define S_DBG_TRACE_CURRENT S_DBG_TRACE_DENTRY +#include "supermount.h" + +int +init_dentry_info(struct dentry *dentry) +{ + struct super_block *sb = dentry->d_sb; + struct supermount_dentry_info *sdi; + int rc; + + ENTER(sb, "dentry=%s", dentry->d_name.name); + + rc = 1; + sdi = kmalloc(sizeof(*sdi), GFP_KERNEL); + if (!sdi) + goto out; + + memset(sdi, 0, sizeof(*sdi)); + + INIT_LIST_HEAD(&sdi->list); + sdi->dentry = 0; + sdi->host = dentry; + + dentry->d_fsdata = sdi; + dentry->d_op = &supermount_dops; + + rc = 0; +out: + LEAVE(sb, "dentry=%s rc=%d", dentry->d_name.name, rc); + + return rc; + +} + +void +attach_subfs_dentry(struct dentry *dentry, struct dentry *subdent) +{ + struct super_block *sb = dentry->d_sb; + struct supermount_sb_info *sbi = supermount_sbi(sb); + struct supermount_dentry_info *sdi; + + ENTER(sb, "dentry=%s subd=%p", dentry->d_name.name, subdent); + + sdi = supermount_d(dentry); + SUPERMOUNT_BUG_LOCKED_ON(sb, sdi->dentry); + sdi->dentry = dget(subdent); + list_add(&sdi->list, &sbi->s_dentries); + + LEAVE(sb, "dentry=%s", dentry->d_name.name); +} + + +struct dentry * +get_subfs_dentry(struct dentry *dentry) +{ + struct super_block *sb = dentry->d_sb; + struct dentry *err; + struct supermount_dentry_info *sdi = supermount_d(dentry); + + ENTER(sb, "dentry=%s", dentry->d_name.name); + + subfs_lock(sb); + + err = ERR_PTR(-ENOMEDIUM); + if (!subfs_is_mounted(sb)) + goto out; + + err = ERR_PTR(-ESTALE); + if (is_dentry_obsolete(dentry)) + goto out; + + err = dget(sdi->dentry); + SUPERMOUNT_BUG_LOCKED_ON(sb, !err); + SUPERMOUNT_BUG_LOCKED_ON(sb, (dentry->d_inode == 0) ^ (err->d_inode == 0)); + +out: + subfs_unlock(sb); + + LEAVE(sb, "dentry=%s subd=%p", dentry->d_name.name, err); + + return err; +} + +static int +supermount_d_revalidate(struct dentry *dentry, struct nameidata *nd) +{ + struct super_block *sb = dentry->d_sb; + struct dentry *subd; + int rc; + struct vfsmount *mnt; + + ENTER(sb, "dentry=%s", dentry->d_name.name); + + rc = 0; + mnt = subfs_prevent_umount(sb); + if (!mnt) + goto out; + + subd = get_subfs_dentry(dentry); + + if (IS_ERR(subd)) + goto allow_umount; + + rc = 1; + /* FIXME do we need to build proper subfs nd? */ + if (subd->d_op && subd->d_op->d_revalidate) + rc = subd->d_op->d_revalidate(subd, nd); + + dput(subd); +allow_umount: + subfs_allow_umount(sb, mnt); +out: + + LEAVE(sb, "dentry=%s rc=%d", dentry->d_name.name, rc); + + return rc; +} + +static int +supermount_d_hash(struct dentry *dentry, struct qstr *name) +{ + struct super_block *sb = dentry->d_sb; + struct dentry *subd; + int rc; + struct vfsmount *mnt; + + ENTER(sb, "dentry=%s name=%s", dentry->d_name.name, name->name); + + rc = -ENOMEDIUM; + mnt = subfs_prevent_umount(sb); + if (!mnt) + goto out; + + subd = get_subfs_dentry(dentry); + rc = PTR_ERR(subd); + if (IS_ERR(subd)) + goto allow_umount; + + rc = 0; + if (subd->d_op && subd->d_op && subd->d_op->d_hash) + rc = subd->d_op->d_hash(subd, name); + + dput(subd); +allow_umount: + subfs_allow_umount(sb, mnt); +out: + LEAVE(sb, "dentry=%s rc=%d", dentry->d_name.name, rc); + + return rc; +} + + +rwlock_t d_compare_lock = RW_LOCK_UNLOCKED; + +static int +supermount_d_compare(struct dentry *dentry, struct qstr *name1, struct qstr *name2) +{ + struct super_block *sb = dentry->d_sb; + struct supermount_dentry_info *sdi; + struct dentry *subd; + int rc; + + ENTER(sb, "dentry=%s name1=%s name2=%s", dentry->d_name.name, name1->name, name2->name); + + rc = 1; /* fail by default */ + sdi = dentry->d_fsdata; + SUPERMOUNT_BUG_ON(!sdi); + + /* + * HACK - FIXME + * this protects against races with supermount_clean_dentries + * I cannot use blocking calls (i.e. sbi->sem) here and it is not + * protected by dcache_lock anymore + */ + read_lock(&d_compare_lock); + subd = sdi->dentry; + if (!subd) { + read_unlock(&d_compare_lock); + goto out; + } + + if (subd->d_op && subd->d_op->d_compare) + rc = subd->d_op->d_compare(subd, name1, name2); + else { /* based on fs/dcache.c:d_lookup */ + if (name1->len == name2->len) + rc = memcmp(name1->name, name2->name, name1->len); + } + read_unlock(&d_compare_lock); + +out: + LEAVE(sb, "dentry=%s rc=%d", dentry->d_name.name, rc); + return rc; +} + +static inline void +handle_subdent(struct dentry *dentry) +{ + struct super_block *sb = dentry->d_sb; + struct vfsmount *mnt; + struct supermount_dentry_info *sdi = supermount_d(dentry); + struct dentry *subd = 0; + + mnt = subfs_prevent_umount(sb); + if (!mnt) + goto out; + + subfs_lock(sb); + if (sdi) { + subd = sdi->dentry; + list_del_init(&sdi->list); + sdi->dentry = 0; + } + subfs_unlock(sb); + + if (subd) + dput(subd); + + subfs_allow_umount(sb, mnt); +out: + return; +} + +/* + * in case of active dentry we must be sure subfs dentry is released + * before subfs inode to correctly maintain write state + */ +static void +supermount_d_iput(struct dentry *dentry, struct inode *inode) +{ + struct super_block *sb = dentry->d_sb; + + ENTER(sb, "dentry=%s inode=%p", dentry->d_name.name, inode); + + handle_subdent(dentry); + iput(inode); + + LEAVE(sb, "dentry=%s", dentry->d_name.name); +} + + +/* + * this duplicated code is due to the lack of common "destroy" method + * for both negative and positive dentries + */ +static void +supermount_d_release(struct dentry *dentry) +{ + struct super_block *sb = dentry->d_sb; + struct supermount_dentry_info *sdi = supermount_d(dentry); + + ENTER(sb, "dentry=%s", dentry->d_name.name); + + handle_subdent(dentry); + kfree(sdi); + + LEAVE(sb, "dentry=%s", dentry->d_name.name); +} + +struct dentry_operations supermount_dops = { + .d_revalidate = supermount_d_revalidate, + .d_hash = supermount_d_hash, + .d_compare = supermount_d_compare, + .d_iput = supermount_d_iput, + .d_release = supermount_d_release, +}; diff -Nur linux-2.6.8.1_supermount/fs/supermount/file.c linux-2.6.8.1/fs/supermount/file.c --- linux-2.6.8.1_supermount/fs/supermount/file.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.8.1/fs/supermount/file.c 2004-08-17 03:16:00.000000000 +0200 @@ -0,0 +1,689 @@ +/* + * linux/fs/supermount/file.c + * + * Original version: + * Copyright (C) 1995, 1997 + * Stephen Tweedie (sct@dcs.ed.ac.uk) + * + * from + * + * linux/fs/minix/dir.c + * Copyright (C) 1991, 1992 Linus Torvalds + * + * and + * + * linux/fs/ext2/dir.c + * Copyright (C) 1992, 1993, 1994, 1995 Remy Card + * + * Rewriten for kernel 2.2 & 2.4. (C) 1999, 2000 Alexis Mikhailov + * (alexis@abc.cap.ru) + * Rewriten for kernel 2.4. (C) 2001 MandrakeSoft Inc. + * Juan Quintela (quintela@mandrakesoft.com) + * Rewritten for kernel 2.4.21 (C) 2003 Andrey Borzenkov + * (arvidjaar@mail.ru) + * Rewritten for kernel 2.5.70 (C) 2003 Andrey Borzenkov + * (arvidjaar@mail.ru) + * + * $Id: 4715_supermount-2.0.4-2.6.7.patch,v 1.1 2004/06/22 22:15:04 gregkh Exp $ + */ + +#define S_DBG_TRACE_CURRENT S_DBG_TRACE_FILE +#include "supermount.h" + +#define DEFAULT_POLLMASK (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM) + +static inline int +init_file_info(struct file *file, unsigned int fake) +{ + struct super_block *sb = file->f_dentry->d_sb; + struct supermount_file_info *sfi; + int rc; + + ENTER(sb, "file=%p fake=%d", file, fake); + + rc = 1; + sfi = kmalloc(sizeof(*sfi), GFP_KERNEL); + if (!sfi) + goto out; + + memset(sfi, 0, sizeof(*sfi)); + + INIT_LIST_HEAD(&sfi->list); + sfi->host = file; + sfi->owner = current->pid; + sfi->vm_ops = 0; + sfi->file = 0; + sfi->fake = fake; + + file->f_supermount = sfi; + + rc = 0; +out: + LEAVE(sb, "file=%p fake=%d rc=%d", file, fake, rc); + + return rc; +} + +static inline void +attach_subfs_file(struct file *file, struct file *subfile) +{ + struct super_block *sb = file->f_dentry->d_sb; + struct supermount_sb_info *sbi = supermount_sbi(sb); + struct supermount_file_info *sfi; + + ENTER(sb, "file=%p subfile=%p", file, subfile); + + sfi = supermount_f(file); + sfi->file = subfile; + list_add(&sfi->list, &sbi->s_files); + + LEAVE(sb, "file=%p subfile=%p", file, subfile); +} + +static inline int +prepare_file(struct file *file, struct file *subfile) +{ + struct super_block *sb = file->f_dentry->d_sb; + int rc; + + ENTER(sb, "file=%p subfile=%p", file, subfile); + + subfs_lock(sb); + + rc = -ENOMEDIUM; + if (!subfs_is_mounted(sb)) + goto out; + + rc = -ESTALE; + if (is_dentry_obsolete(file->f_dentry)) + goto out; + + rc = -ENOMEM; + if (init_file_info(file, 0)) + goto out; + + attach_subfs_file(file, subfile); + rc = 0; + +out: + subfs_unlock(sb); + + LEAVE(sb, "file=%p subfile=%p", file, subfile); + + return rc; +} + +struct file * +get_subfs_file(struct file *file) +{ + struct super_block *sb = file->f_dentry->d_sb; + struct supermount_file_info *sfi = supermount_f(file); + struct file *err; + + ENTER(sb, "file=%p", file); + + subfs_lock(sb); + + err = ERR_PTR(-ENOMEDIUM); + if (!subfs_is_mounted(sb)) + goto out; + + err = ERR_PTR(-ESTALE); + if (is_file_fake(file) || is_file_obsolete(file)) + goto out; + + err = sfi->file; + SUPERMOUNT_BUG_LOCKED_ON(sb, !err->f_dentry); + SUPERMOUNT_BUG_LOCKED_ON(sb, !err->f_dentry->d_inode); + get_file(err); + +out: + subfs_unlock(sb); + + LEAVE(sb, "file=%p subfile=%p", file, err); + + return err; +} + +static loff_t +supermount_llseek(struct file *file, loff_t offset, int origin) +{ + struct super_block *sb = file->f_dentry->d_sb; + struct file *subfile; + loff_t rc; + + ENTER(sb, "file=%p offset=%lld origin=%d", file, offset, origin); + + rc = -ESTALE; + if (subfs_check_disk_change(sb)) + goto out; + + subfile = get_subfs_file(file); + rc = PTR_ERR(subfile); + if (IS_ERR(subfile)) + goto out; + + if (subfile->f_op && subfile->f_op->llseek) + rc = subfile->f_op->llseek(subfile, offset, origin); + else + rc = default_llseek(subfile, offset, origin); + file->f_pos = subfile->f_pos; + file->f_version = subfile->f_version; + + fput(subfile); +out: + LEAVE(sb, "file=%p rc=%lld", file, rc); + + return rc; +} + +static ssize_t +supermount_read(struct file *file, char *buf, size_t count, loff_t * ppos) +{ + struct super_block *sb = file->f_dentry->d_sb; + struct inode *inode = file->f_dentry->d_inode; + struct file *subfile; + int write_on = NEED_WRITE_ATIME(inode); + int rc; + + ENTER(sb, "file=%p", file); + + rc = -ESTALE; + if (subfs_check_disk_change(sb)) + goto out; + + subfile = get_subfs_file(file); + rc = PTR_ERR(subfile); + if (IS_ERR(subfile)) + goto out; + + rc = -EINVAL; + if (!subfile->f_op || !subfile->f_op->read) + goto put_subfile; + + rc = subfs_get_access(inode, write_on); + if (rc) + goto put_subfile; + + rc = subfile->f_op->read(subfile, buf, count, ppos); + subfs_put_access(inode, write_on); + if (rc < 0) + goto put_subfile; + + inode->i_atime = subfile->f_dentry->d_inode->i_atime; + + if (rc > 0) + file->f_pos = subfile->f_pos = *ppos; + +put_subfile: + fput(subfile); +out: + LEAVE(sb, "file=%p rc=%d", file, rc); + + return rc; +} + +static ssize_t +supermount_write(struct file *file, const char *buf, + size_t count, loff_t * ppos) +{ + struct super_block *sb = file->f_dentry->d_sb; + struct file *subfile; + int rc; + + ENTER(sb, "file=%p", file); + + rc = -ESTALE; + if (subfs_check_disk_change(sb)) + goto out; + + subfile = get_subfs_file(file); + rc = PTR_ERR(subfile); + if (IS_ERR(subfile)) + goto out; + + rc = 0; + if (subfile->f_op && subfile->f_op->write) + rc = subfile->f_op->write(subfile, buf, count, ppos); + if (rc > 0) { + struct inode *subinode = subfile->f_dentry->d_inode; + + file->f_pos = subfile->f_pos = *ppos; + file->f_mode = subfile->f_mode; + file->f_dentry->d_inode->i_size = subinode->i_size; + file->f_dentry->d_inode->i_blocks = subinode->i_blocks; + file->f_dentry->d_inode->i_mode = subinode->i_mode; + file->f_dentry->d_inode->i_ctime = subinode->i_ctime; + file->f_dentry->d_inode->i_mtime = subinode->i_mtime; + } + + fput(subfile); +out: + LEAVE(sb, "file=%p rc=%d", file, rc); + + return rc; +} + +int +supermount_readdir(struct file *file, void *buf, filldir_t fill_fn) +{ + struct super_block *sb = file->f_dentry->d_sb; + struct inode *inode = file->f_dentry->d_inode; + struct file *subfile; + int write_on = NEED_WRITE_ATIME(inode); + int fake_readdir = 1; + int rc; + + ENTER(sb, "file=%p", file); + + rc = -ESTALE; + if (subfs_check_disk_change(sb)) + goto out; + + subfile = get_subfs_file(file); + rc = PTR_ERR(subfile); + if (IS_ERR(subfile)) + goto out; + + rc = -ENOTDIR; + if (!subfile->f_op || !subfile->f_op->readdir) + goto put_subfile; + + rc = subfs_get_access(inode, write_on); + if (rc) + goto put_subfile; + + /* FIXME should it go before get_access? */ + fake_readdir = 0; + rc = subfile->f_op->readdir(subfile, buf, fill_fn); + subfs_put_access(inode, write_on); + if (rc) + goto put_subfile; + + inode->i_atime = subfile->f_dentry->d_inode->i_atime; + file->f_pos = subfile->f_pos; + +put_subfile: + fput(subfile); +out: + if (fake_readdir && is_file_fake(file)) { + /* cf. supermount_open */ + rc = 0; + } + LEAVE(sb, "file=%p rc=%d fpos=%lld", file, rc, file->f_pos); + + return rc; +} + +static unsigned int +supermount_poll(struct file *file, struct poll_table_struct *table) +{ + struct super_block *sb = file->f_dentry->d_sb; + struct file *subfile; + int rc; + + ENTER(sb, "file=%p", file); + + rc = -ESTALE; + if (subfs_check_disk_change(sb)) + goto out; + + subfile = get_subfs_file(file); + rc = PTR_ERR(subfile); + if (IS_ERR(subfile)) + goto out; + + rc = DEFAULT_POLLMASK; + if (subfile->f_op && subfile->f_op->poll) + rc = subfile->f_op->poll(subfile, table); + + fput(subfile); +out: + LEAVE(sb, "file=%p rc=%d", file, rc); + + return rc; +} + +static int +supermount_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct super_block *sb = file->f_dentry->d_sb; + struct file *subfile; + struct inode *subinode; + int rc; + + ENTER(sb, "file=%p cmd=%u arg=%lu", file, cmd, arg); + + rc = -ESTALE; + if (subfs_check_disk_change(sb)) + goto out; + + subfile = get_subfs_file(file); + rc = PTR_ERR(subfile); + if (IS_ERR(subfile)) + goto out; + + rc = -ENOTTY; + subinode = subfile->f_dentry->d_inode; + if (subfile->f_op && subfile->f_op->ioctl) + rc = subfile->f_op->ioctl(subinode, subfile, cmd, arg); + + /* flags may have been changed by ioctl */ + if (!rc) + set_inode_flags(file->f_dentry->d_inode, subinode); + + fput(subfile); +out: + LEAVE(sb, "file=%p rc=%d", file, rc); + + return rc; +} + +int +supermount_open(struct inode *inode, struct file *file) +{ + struct super_block *sb = inode->i_sb; + struct dentry *subdent; + struct file *subfile = 0; + struct vfsmount *submnt; + int write_on = file->f_mode & FMODE_WRITE; + int fake_open = 1; + int rc; + + ENTER(sb, "inode=%p file=%p", inode, file); + + rc = -ESTALE; + if (subfs_check_disk_change(sb)) + goto out; + + submnt = subfs_get_mnt(sb); + if (!submnt) + goto out; + + subdent = get_subfs_dentry(file->f_dentry); + rc = PTR_ERR(subdent); + if (IS_ERR(subdent)) + goto put_submnt; + + rc = subfs_get_access(inode, write_on); + if (rc) + goto put_subdent; + + /* + * the following is used to simplify error processing. dentry_open + * automatically does mntput and dput in error case, this may result + * in subfs being destroyed + * We just make sure we need to do mntput exactly once here; + * additionally it guards against accidental remounting of subfs + * until we has cleaned up + */ + submnt = mntget(submnt); + subdent = dget(subdent); + + subfile = dentry_open(subdent, submnt, file->f_flags); + rc = PTR_ERR(subfile); + if (IS_ERR(subfile)) + goto put_access; + /* + * no need to do extra mntput and dput, it is done automatically in + * dentry_open on error + */ + + rc = prepare_file(file, subfile); + if (rc) + goto put_subfile; + + subfile->f_mode = file->f_mode; + /* + * this is needed for mmap to work. In current model vm_area + * is associated with superfile; we never explicitly call + * any vm method with subfile as pointer. But many drivers + * attach private structures to this field and mmap of special + * files on supermount fs won't work without it + */ + file->private_data = subfile->private_data; + /* + * we have real subfile now, do not fake anything + */ + fake_open = 0; + + /* + * Now get rid of extra mntget and dget + */ + goto put_subdent; + + /* + * error cleanup + */ + +put_subfile: + fput(subfile); + subfile = 0; +put_access: + subfs_put_access(inode, write_on); +put_subdent: + dput(subdent); +put_submnt: + mntput(submnt); +out: + if (fake_open && inode == sb->s_root->d_inode) { + /* + * always appear to succeed for root open. It allows active + * monitoring of mountpoint using FAM/dnotify and also is less + * surprising for other programs + */ + rc = init_file_info(file, 1); + if (rc) + rc = -ENOMEM; + } + LEAVE(sb, "inode=%p file=%p rc=%d subfile=0x%p", inode, file, rc, subfile); + + return rc; +} + +static int +supermount_flush(struct file *file) +{ + struct super_block *sb = file->f_dentry->d_sb; + struct file *subfile; + int fake_flush = 1; + int rc; + + ENTER(sb, "file=%p", file); + + rc = -ESTALE; + if (subfs_check_disk_change(sb)) + goto out; + + subfile = get_subfs_file(file); + rc = PTR_ERR(subfile); + if (IS_ERR(subfile)) + goto out; + + rc = 0; + fake_flush = 0; + if (subfile->f_op && subfile->f_op->flush) + rc = subfile->f_op->flush(subfile); + + fput(subfile); +out: + if (fake_flush && is_file_fake(file)) { + /* cf. supermount_open */ + rc = 0; + } + LEAVE(sb, "file=%p rc=%d fake=%d", file, rc, fake_flush); + + return rc; +} + +/* + * if subfile is NULL it has already been released in supermount_clean_files + * together with adjusting open/write counters. Else we do it here. + * + * The reason is, it may be called long after media has been changed + * and we definitely do not want this function to mess up the + * new subfs state. + */ +static int +supermount_release(struct inode *inode, struct file *file) +{ + struct file *subfile = 0; + struct super_block *sb = inode->i_sb; + struct supermount_file_info *sfi = file->f_supermount; + + ENTER(sb, "inode=%p file=%p", inode, file); + + subfs_lock(sb); + /* + * FIXME + * this sucks. But there does not seem to be any way + * to distinguish between ENOMEM on _open (legitimate + * case) and anything else (plain bug) + */ + if (sfi) { + list_del(&sfi->list); + subfile = sfi->file; + sfi->file = 0; + } else + supermount_warning(sb, "no supermount file info attached"); + subfs_unlock(sb); + + if (subfile) { + int bug = atomic_read(&subfile->f_count) != 1; + fput(subfile); + subfs_put_access(inode, file->f_mode & FMODE_WRITE); + SUPERMOUNT_BUG_ON(bug); + } + + if (sfi) + kfree(sfi); + + LEAVE(sb, "inode=%p file=%p", inode, file); + + return 0; + +} + +static int +supermount_fsync(struct file *file, struct dentry *dentry, int datasync) +{ + struct super_block *sb = file->f_dentry->d_sb; + struct file *subfile; + int rc; + + ENTER(sb, "file=%p dentry=%s sync=%d", file, dentry->d_name.name, datasync); + + rc = -ESTALE; + if (subfs_check_disk_change(sb)) + goto out; + + subfile = get_subfs_file(file); + rc = PTR_ERR(subfile); + if (IS_ERR(subfile)) + goto out; + + rc = -EINVAL; + if (subfile->f_op && subfile->f_op->fsync) + rc = subfile->f_op->fsync(subfile, subfile->f_dentry, datasync); + + fput(subfile); +out: + ENTER(sb, "file=%p dentry=%s rc=%d", file, dentry->d_name.name, rc); + + return rc; +} + +static int +supermount_fasync(int fd, struct file *file, int on) +{ + struct super_block *sb = file->f_dentry->d_sb; + struct file *subfile; + int rc; + + ENTER(sb, "fd=%d file=%p on=%d", fd, file, on); + + rc = -ESTALE; + if (subfs_check_disk_change(sb)) + goto out; + + subfile = get_subfs_file(file); + rc = PTR_ERR(subfile); + if (IS_ERR(subfile)) + goto out; + + rc = -EINVAL; + if (subfile->f_op && subfile->f_op->fasync) + rc = subfile->f_op->fasync(fd, subfile, on); + + fput(subfile); +out: + LEAVE(sb, "fd=%d file=%p rc=%d", fd, file, rc); + + return rc; +} + +static int +supermount_lock(struct file *file, int cmd, struct file_lock *fl) +{ + struct super_block *sb = file->f_dentry->d_sb; + struct file *subfile; + int rc; + + ENTER(sb, "file=%p cmd=%d", file, cmd); + + rc = -ESTALE; + if (subfs_check_disk_change(sb)) + goto out; + + subfile = get_subfs_file(file); + rc = PTR_ERR(subfile); + if (IS_ERR(subfile)) + goto out; + + rc = 0; + if (subfile->f_op && subfile->f_op->lock) + rc = subfile->f_op->lock(subfile, cmd, fl); + else if (cmd == F_GETLK) + posix_test_lock(file, fl); + + fput(subfile); +out: + LEAVE(sb, "file=%p rc=%d", file, rc); + + return rc; +} + +/* Fixme: + * readv: easy, export churnk from vfs + * writev: easy, export churnk from vfs + * sendpage: only used for networking, not needed + * get_unmmapped_area: only used for devices, not needed + */ + +struct file_operations supermount_dir_operations = { + .llseek = supermount_llseek, + .read = supermount_read, + .readdir = supermount_readdir, + .ioctl = supermount_ioctl, + .open = supermount_open, + .flush = supermount_flush, + .release = supermount_release, + .fsync = supermount_fsync, + .fasync = supermount_fasync, +}; + +struct file_operations supermount_file_operations = { + .llseek = supermount_llseek, + .read = supermount_read, + .write = supermount_write, + .poll = supermount_poll, + .ioctl = supermount_ioctl, + .mmap = supermount_file_mmap, /* from filemap.c */ + .open = supermount_open, + .flush = supermount_flush, + .release = supermount_release, + .fsync = supermount_fsync, + .fasync = supermount_fasync, + .lock = supermount_lock, +}; diff -Nur linux-2.6.8.1_supermount/fs/supermount/filemap.c linux-2.6.8.1/fs/supermount/filemap.c --- linux-2.6.8.1_supermount/fs/supermount/filemap.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.8.1/fs/supermount/filemap.c 2004-08-17 03:16:00.000000000 +0200 @@ -0,0 +1,182 @@ +/* + * linux/fs/supermount/filemap.c + * + * Initial version for kernel 2.4.21 (C) 2003 Andrey Borzenkov + * (arvidjaar@mail.ru) + * + * $Id: 4715_supermount-2.0.4-2.6.7.patch,v 1.1 2004/06/22 22:15:04 gregkh Exp $ + */ + +#define S_DBG_TRACE_CURRENT S_DBG_TRACE_FILEMAP +#include "supermount.h" + +/* + * Some rationale and justification for this file + * + * We play dirty tricks with mm management for mmaped files on supermount. + * Address space points to subinode but vm area is associated with superfile. + * When media change is detected, subinode together with all associated + * pages goes away as well (at least, I hope so ...) Now we must prevent + * any attempt to access no more existing address space. To do so we save + * original vm_ops in private field and replace them with vm_ops in this file. + * They check if file is stale, if yes, they try to return sensible error. + * There is some doubt about possibility to block here ... OTOH any write + * lock on subfs is hold in context where no mm lock is expected. + */ +static void +supermount_vm_open(struct vm_area_struct *area) +{ + struct file *file = area->vm_file, *subfile; + struct super_block *sb = file->f_dentry->d_sb; + struct supermount_file_info *sfi; + + ENTER(sb, "vm=%p", area); + + if (subfs_check_disk_change(sb)) + goto out; + + subfile = get_subfs_file(file); + if (IS_ERR(subfile)) + goto out; + + sfi = supermount_f(file); + if (sfi->vm_ops && sfi->vm_ops->open) + sfi->vm_ops->open(area); + + fput(subfile); +out: + LEAVE(sb, "vm=%p", area); + + return; +} + +static void +supermount_vm_close(struct vm_area_struct *area) +{ + struct file *file = area->vm_file, *subfile; + struct super_block *sb = file->f_dentry->d_sb; + struct supermount_file_info *sfi; + + ENTER(sb, "vm=%p", area); + + if (subfs_check_disk_change(sb)) + goto out; + + subfile = get_subfs_file(file); + if (IS_ERR(subfile)) + goto out; + + sfi = supermount_f(file); + if (sfi->vm_ops && sfi->vm_ops->close) + sfi->vm_ops->close(area); + + fput(subfile); +out: + LEAVE(sb, "vm=%p", area); + + return; +} + +static struct page * +supermount_vm_nopage(struct vm_area_struct *area, unsigned long address, int *type) +{ + struct file *file = area->vm_file, *subfile; + struct super_block *sb = file->f_dentry->d_sb; + struct supermount_file_info *sfi; + struct page *page = 0; + + ENTER(sb, "vm=%p addr=%lx", area, address); + + /* + * this is called with mm semaphore down read and pagetable + * spinlock released. So it _appears_ safe to sleep ... + */ + if (subfs_check_disk_change(sb)) + goto out; + + subfile = get_subfs_file(file); + if (IS_ERR(subfile)) + goto out; + + sfi = supermount_f(file); + page = sfi->vm_ops->nopage(area, address, type); + + fput(subfile); +out: + LEAVE(sb, "vm=%p page=%p", area, page); + + return page; +} + +int +supermount_file_mmap(struct file *file, struct vm_area_struct *vma) +{ + + struct super_block *sb = file->f_dentry->d_sb; + struct inode *inode = file->f_dentry->d_inode; + struct supermount_file_info *sfi; + struct file *subfile; + int write_on = NEED_WRITE_ATIME(inode); + int rc; + + ENTER(sb, "file=%p vm=%p", file, vma); + + rc = -ESTALE; + if (subfs_check_disk_change(sb)) + goto out; + + subfile = get_subfs_file(file); + rc = PTR_ERR(subfile); + if (IS_ERR(subfile)) + goto out; + + sfi = supermount_f(file); + + rc = -ENODEV; + if (!subfile->f_op || !subfile->f_op->mmap) + goto put_subfile; + + rc = subfs_get_access(inode, write_on); + if (rc) + goto put_subfile; + + rc = subfile->f_op->mmap(subfile, vma); + subfs_put_access(inode, write_on); + if (rc) + goto put_subfile; + /* + * we cannot deal with anonymous mapping + */ + if (!vma->vm_ops || !vma->vm_ops->nopage) { + rc = -ENOSYS; + goto put_subfile; + } + + /* + * now do the nasty trick + */ + sfi->vm_ops = vma->vm_ops; + vma->vm_ops = &supermount_vm_ops; + +put_subfile: + fput(subfile); +out: + LEAVE(sb, "file=%p vm=%p rc=%d", file, vma, rc); + + return rc; +} + +/* + * ->populate: to properly implement it it should call do_no_page if + * subfs does not provide its own ->populate. Unfortunately + * populate gets different parameters. Translating them into + * what ->nopage expects just does not worth it - currently + * supermount is slow enough to bother about this imaginary + * speed up even if it was possible (which probably is not + * due to how locking is done; see mm/memory.c). + */ +struct vm_operations_struct supermount_vm_ops = { + .open = supermount_vm_open, + .close = supermount_vm_close, + .nopage = supermount_vm_nopage, +}; diff -Nur linux-2.6.8.1_supermount/fs/supermount/init.c linux-2.6.8.1/fs/supermount/init.c --- linux-2.6.8.1_supermount/fs/supermount/init.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.8.1/fs/supermount/init.c 2004-08-17 03:16:00.000000000 +0200 @@ -0,0 +1,48 @@ +/* + * linux/fs/supermount/init.c + * + * (C) Copyright 2001-2002 Juan Quintela + * Released unde GPL v2. + * Rewritten for kernel 2.4.21 (C) 2003 Andrey Borzenkov + * (arvidjaar@mail.ru) + * Rewritten for kernel 2.5.70 (C) 2003 Andrey Borzenkov + * (arvidjaar@mail.ru) + * + * $Id: 4715_supermount-2.0.4-2.6.7.patch,v 1.1 2004/06/22 22:15:04 gregkh Exp $ + */ + +#include "supermount.h" + +struct file_system_type supermount_fs_type = { + .owner = THIS_MODULE, + .name = "supermount", + .get_sb = supermount_get_sb, + .kill_sb = kill_anon_super, + .fs_flags = FS_NO_SUBMNT, +}; + +static int __init +init_supermount_fs(void) +{ + int rc = register_filesystem(&supermount_fs_type); + + if (!rc) { + printk(KERN_INFO "Supermount version %s for kernel 2.6\n", SUPERMOUNT_VERSION); + supermount_proc_register(); + } + + return rc; +} + +static void __exit +exit_supermount_fs(void) +{ + supermount_proc_unregister(); + unregister_filesystem(&supermount_fs_type); +} + +MODULE_AUTHOR("Stephen Tweedie, Alexis Mikhailov, Juan Quintela, Andrey Borzenkov and others"); +MODULE_DESCRIPTION("Transparent removable media support"); +MODULE_LICENSE("GPL"); +module_init(init_supermount_fs); +module_exit(exit_supermount_fs); diff -Nur linux-2.6.8.1_supermount/fs/supermount/mediactl.c linux-2.6.8.1/fs/supermount/mediactl.c --- linux-2.6.8.1_supermount/fs/supermount/mediactl.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.8.1/fs/supermount/mediactl.c 2004-08-17 03:16:00.000000000 +0200 @@ -0,0 +1,69 @@ +/* + * linux/fs/supermount/mediactl.c + * + * Original version: + * Copyright (C) 1995, 1997 + * Stephen Tweedie (sct@dcs.ed.ac.uk) + * + * Rewriten for kernel 2.2, 2.4. (C) 1999, 2000 Alexis Mikhailov + * (alexis@abc.cap.ru) + * Rewritten for kernel 2.4.21 (C) 2003 Andrey Borzenkov + * (arvidjaar@mail.ru) + * Rewritten for kernel 2.5.70 (C) 2003 Andrey Borzenkov + * (arvidjaar@mail.ru) + * + * $Id: 4715_supermount-2.0.4-2.6.7.patch,v 1.1 2004/06/22 22:15:04 gregkh Exp $ + */ + +#define S_DBG_TRACE_CURRENT S_DBG_TRACE_MEDIACTL +#include "supermount.h" + +/* + * Try to lock the drive door. This is not guaranteed to work on all + * hardware, but we try any tricks we know of anyway. + */ +void +supermount_mediactl(struct super_block *sb, int operation, int opt) +{ + struct block_device *bdev; + struct super_block *subsb; + struct block_device_operations *fops = 0; + + if (!subfs_is_mounted(sb)) + return; + + subsb = subfs_sb(sb); + bdev = subsb->s_bdev; + if (!bdev->bd_disk) + return; + + /* FIXME is it enough to use bd_sem here? */ + switch (operation) { + case SUPERMOUNT_INC_COUNT: + lock_kernel(); + SUPERMOUNT_BUG_ON(bdev->bd_disk->scount < 0); + bdev->bd_disk->scount++; + unlock_kernel(); + return; + case SUPERMOUNT_DEC_COUNT: + lock_kernel(); + SUPERMOUNT_BUG_ON(bdev->bd_disk->scount <= 0); + bdev->bd_disk->scount--; + unlock_kernel(); + return; + } + + fops = bdev->bd_disk->fops; + if (!fops || !fops->mediactl) + return; + /* + * tray is (un-)locked in open (BKL for bdev), release (BKL for bdev) + * and ioctl (BKL). We are in good company. + * This must be changed if block devices ever stop using BKL + * for open/release. Unfortunately, using just bdev->bd_sem is not + * enough due to ioctl. + */ + lock_kernel(); + fops->mediactl(bdev, operation, opt); + unlock_kernel(); +} diff -Nur linux-2.6.8.1_supermount/fs/supermount/namei.c linux-2.6.8.1/fs/supermount/namei.c --- linux-2.6.8.1_supermount/fs/supermount/namei.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.8.1/fs/supermount/namei.c 2004-08-17 03:16:00.000000000 +0200 @@ -0,0 +1,1143 @@ +/* + * linux/fs/supermount/namei.c + * + * Original version: + * Copyright (C) 1995 + * Stephen Tweedie (sct@dcs.ed.ac.uk) + * + * from + * + * linux/fs/minix/namei.c + * Copyright (C) 1991, 1992 Linus Torvalds + * + * and + * + * linux/fs/ext2/namei.c + * Copyright (C) 1992, 1993, 1994, 1995 Remy Card + * + * Rewriten for kernel 2.2 & 2.4. (C) 1999, 2000 Alexis Mikhailov + * (alexis@abc.cap.ru) + * Rewritten for kernel 2.4.21 (C) 2003 Andrey Borzenkov + * (arvidjaar@mail.ru) + * Rewritten for kernel 2.5.70 (C) 2003 Andrey Borzenkov + * (arvidjaar@mail.ru) + * + * $Id: 4715_supermount-2.0.4-2.6.7.patch,v 1.1 2004/06/22 22:15:04 gregkh Exp $ + */ + +#define S_DBG_TRACE_CURRENT S_DBG_TRACE_NAMEI +#include "supermount.h" + +/** + * Attach super dentry to sub dentry + * + * @dentry: super dentry that is being created + * @subd: subfs dentry that just has been found + * + * It checks whether subfs is still valid using @dentry->d_parent; + * new supermount_dentry_info is created and set to point to @subd. + * + * It is possible that @subd actually has different name (ntfs, vfat or + * in general any case-insensitive filesystems). Search @dentry->d_parent + * for a child matching == @subd->d_name. If found, discard @dentry and child + * (after some validity checks) is returned. Check that child actually points + * to @subd + * + * d_lookup relies on the fact that hash is properly initialized in + * @subd->d_name and that superfs is using the same compare method as subfs. + * + * About ref counting. @subd is dput in supermount_lookup. I.e. in case of + * error or if we find out it is already connected to superfs the excessive + * counter gets decremented. @dentry is finally dput in caller of ->lookup + * if ->lookup returns something != 0. + */ + +static struct dentry * +prepare_dentry(struct dentry *dentry, struct dentry *subd) +{ + struct super_block *sb = dentry->d_sb; + struct dentry *rc; + + ENTER(sb, "dentry=%s subd=%s", dentry->d_name.name, subd->d_name.name); + + subfs_lock(sb); + + SUPERMOUNT_BUG_LOCKED_ON(sb, !subd); + SUPERMOUNT_BUG_LOCKED_ON(sb, dentry->d_fsdata); + + rc = ERR_PTR(-ENOMEDIUM); + if (!subfs_is_mounted(sb)) + goto out; + + rc = ERR_PTR(-ESTALE); + if (is_dentry_obsolete(dentry->d_parent)) + goto out; + + rc = d_lookup(dentry->d_parent, &subd->d_name); + if (IS_ERR(rc)) + goto out; + + if (rc) { + SUPERMOUNT_BUG_LOCKED_ON(sb, !rc->d_fsdata); + SUPERMOUNT_BUG_LOCKED_ON(sb, + ((struct supermount_dentry_info *)rc->d_fsdata)->dentry != subd); + } else { + /* + * this is theoretically possible. We cannot garantee full + * coherency between subfs and superfs cache; i.e. entry + * may have been left in one cache but removed from another + */ + rc = ERR_PTR(-ENOMEM); + if (init_dentry_info(dentry)) + goto out; + attach_subfs_dentry(dentry, subd); + d_add(dentry, 0); + rc = 0; + } + +out: + subfs_unlock(sb); + + LEAVE(sb, "dentry=%p subd=%p rc=%p", dentry, subd, rc); + + return rc; + /* + * subdent is implicitly freed on return if we skip dget here + */ +} + + +/** + * Attach superfs inode to subfs inode + * + * @dentry: superfs dentry + * @subd: subfs dentry + * + * This is expected to be called only in cointext that requires + * negative dentry. + * + * FIXME + * Holding sbi->sem during iget4 creates deadlock with write_inode - + * write_inode sets I_LOCK abd calls supermount_write_inode at the + * same moment as iget4 sleeps waiting for I_LOCK to be cleared. So + * it first acquires inode and then checks if subfs is still valid. + */ + +static int +prepare_inode(struct dentry *dentry, struct dentry *subd) +{ + struct super_block *sb = dentry->d_sb; + struct inode *inode = dentry->d_inode; + struct inode *subi = subd->d_inode; + int rc; + + ENTER(sb, "dentry=%s", dentry->d_name.name); + + SUPERMOUNT_BUG_ON(inode); + SUPERMOUNT_BUG_ON(!subi); + + rc = -ENOMEM; + inode = iget_locked(sb, subi->i_ino); + if (!inode) + goto out; + else if (inode->i_state & I_NEW) + unlock_new_inode(inode); + + rc = 0; + + subfs_lock(sb); + if (!subfs_is_mounted(sb)) + rc = -ENOMEDIUM; + else if (is_dentry_obsolete(dentry)) + rc = -ESTALE; + else { + attach_subfs_inode(inode, subi); + d_instantiate(dentry, inode); + } + subfs_unlock(sb); + + if (rc) + iput(inode); + +out: + LEAVE(sb, "dentry=%s inode=%p rc=%d", dentry->d_name.name, inode, rc); + + return rc; +} + +struct inode * +get_subfs_inode(struct inode *inode) +{ + struct super_block *sb = inode->i_sb; + struct inode *err; + struct supermount_inode_info *sii = supermount_i(inode); + + ENTER(sb, "inode=%p", inode); + + subfs_lock(sb); + + err = ERR_PTR(-ENOMEDIUM); + if (!subfs_is_mounted(sb)) + goto out; + + err = ERR_PTR(-ESTALE); + if (is_inode_obsolete(inode)) + goto out; + + err = igrab(sii->inode); + SUPERMOUNT_BUG_LOCKED_ON(sb, !err); + +out: + subfs_unlock(sb); + + LEAVE(sb, "inode=%p subi=%p", inode, err); + + return err; +} + +/* inode methods */ + +static int +supermount_create(struct inode *dir, struct dentry *dentry, int mode, struct nameidata *nd) +{ + struct super_block *sb = dir->i_sb; + struct dentry *subdent; + struct inode *subdir; + int rc; + struct vfsmount *mnt; + + ENTER(sb, "dir=%p dentry=%s", dir, dentry->d_name.name); + + mnt = subfs_go_online(sb); + rc = PTR_ERR(mnt); + if (IS_ERR(mnt)) + goto out; + + subdir = get_subfs_inode(dir); + rc = PTR_ERR(subdir); + if (IS_ERR(subdir)) + goto go_offline; + + rc = -EACCES; + if (!subdir->i_op || !subdir->i_op->create) + goto put_subdir; + + subdent = get_subfs_dentry(dentry); + rc = PTR_ERR(subdent); + if (IS_ERR(subdent)) + goto put_subdir; + + rc = subfs_get_access(dir, 1); + if (rc) + goto put_subdent; + + /* FIXME build proper nd struct */ + rc = subdir->i_op->create(subdir, subdent, mode, nd); + subfs_put_access(dir, 1); + if (rc) + goto put_subdent; + + rc = prepare_inode(dentry, subdent); + if (rc) + goto put_subdent; + + dir->i_mtime = subdir->i_mtime; + dir->i_ctime = subdir->i_ctime; + dir->i_nlink = subdir->i_nlink; + dir->i_size = subdir->i_size; + dir->i_blocks = subdir->i_blocks; + +put_subdent: + dput(subdent); +put_subdir: + iput(subdir); +go_offline: + subfs_go_offline(sb, mnt); +out: + LEAVE(sb, "dir=%p dentry=%s rc=%d", dir, dentry->d_name.name, rc); + + return rc; +} + +/** + * Search directory for a matching name + * + * @dir: directory that is being searched + * @dentry: name to search for (in dentry->d_name) + * + * This is (currently :) the only method where we do not call subfs + * directly. The reason is coherency between subfs and superfs dentry + * caches. It is impossible to ensure it without modifying the very + * guts of fs/dcache.c; so we check cache before doing actual lookup. + * lookup_one_len just avoids duplicating of code. + * + * Supermount is in exclusive control of subfs so it is garanteed that + * dentry cannot magically appear in the middle of lookup. + * + * There are filesystems that support multiple forms of file name, like + * case-insensitive or short-long names on NTFS. In this case cache lookup + * fails but filesystem may return dentry for different name. In this case + * we check if dentry with matching name exists in parent and reuse it. + */ +static struct dentry * +supermount_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) +{ + struct super_block *sb = dir->i_sb; + struct dentry *rc, *subdent, *subparent; + struct inode *subdir; + struct vfsmount *mnt; + int ret; + + ENTER(sb, "dir=%p dentry=%s", dir, dentry->d_name.name); + + mnt = subfs_go_online(sb); + rc = (struct dentry *)mnt; + if (IS_ERR(mnt)) + goto out; + + subdir = get_subfs_inode(dir); + rc = (struct dentry *)subdir; + if (IS_ERR(subdir)) + goto go_offline; + + subparent = get_subfs_dentry(dentry->d_parent); + rc = subparent; + if (IS_ERR(subparent)) + goto put_subdir; + + ret = subfs_get_access(dir, 0); + rc = ERR_PTR(ret); + if (ret) + goto put_subparent; + + SUPERMOUNT_BUG_ON(subparent->d_inode != subdir); + + /* FIXME usually lookup_one_len is called under i_sem */ + /* FIXME what to do with nd? */ + subdent = lookup_one_len(dentry->d_name.name, subparent, + dentry->d_name.len); + + subfs_put_access(dir, 0); + rc = subdent; + if (IS_ERR(rc)) + goto put_subparent; + + rc = prepare_dentry(dentry, subdent); + if (IS_ERR(rc)) + goto put_subdent; + + if (!rc && subdent->d_inode) { + ret = prepare_inode(dentry, subdent); + if (ret < 0) + rc = ERR_PTR(ret); + } + +put_subdent: + dput(subdent); +put_subparent: + dput(subparent); +put_subdir: + iput(subdir); +go_offline: + subfs_go_offline(sb, mnt); +out: + LEAVE(sb, "dir=%p dentry=%s rc=%p", dir, dentry->d_name.name, rc); + + return rc; +} + +static int +supermount_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *new_dentry) +{ + struct super_block *sb = dir->i_sb; + struct dentry *old_subdent, *new_subdent; + struct inode *subdir; + int rc; + struct vfsmount *mnt; + + ENTER(sb, "from=%s dir=%p to=%s", old_dentry->d_name.name, dir, new_dentry->d_name.name); + + SUPERMOUNT_BUG_ON(new_dentry->d_inode); + + mnt = subfs_go_online(sb); + rc = PTR_ERR(mnt); + if (IS_ERR(mnt)) + goto out; + + subdir = get_subfs_inode(dir); + rc = PTR_ERR(subdir); + if (IS_ERR(subdir)) + goto go_offline; + + rc = -EPERM; + if (!subdir->i_op || !subdir->i_op->link) + goto put_subdir; + + old_subdent = get_subfs_dentry(old_dentry); + rc = PTR_ERR(old_subdent); + if (IS_ERR(old_subdent)) + goto put_subdir; + + new_subdent = get_subfs_dentry(new_dentry); + rc = PTR_ERR(new_subdent); + if (IS_ERR(new_subdent)) + goto put_old_subdent; + + rc = subfs_get_access(dir, 1); + if (rc) + goto put_new_subdent; + + rc = subdir->i_op->link(old_subdent, subdir, new_subdent); + subfs_put_access(dir, 1); + if (rc) + goto put_new_subdent; + + rc = prepare_inode(new_dentry, new_subdent); + if (rc) + goto put_new_subdent; + + dir->i_mtime = subdir->i_mtime; + dir->i_ctime = subdir->i_ctime; + dir->i_nlink = subdir->i_nlink; + dir->i_size = subdir->i_size; + dir->i_blocks = subdir->i_blocks; + +put_new_subdent: + dput(new_subdent); +put_old_subdent: + dput(old_subdent); +put_subdir: + iput(subdir); +go_offline: + subfs_go_offline(sb, mnt); +out: + LEAVE(sb, "from=%s dir=%p to=%s rc=%d", old_dentry->d_name.name, dir, new_dentry->d_name.name, rc); + + return rc; +} + +static int +supermount_unlink(struct inode *dir, struct dentry *dentry) +{ + struct super_block *sb = dir->i_sb; + struct dentry *subdent; + struct inode *subdir; + int rc; + struct vfsmount *mnt; + + ENTER(sb, "dir=%p dentry=%s", dir, dentry->d_name.name); + + mnt = subfs_go_online(sb); + rc = PTR_ERR(mnt); + if (IS_ERR(mnt)) + goto out; + + subdir = get_subfs_inode(dir); + rc = PTR_ERR(subdir); + if (IS_ERR(subdir)) + goto go_offline; + + rc = -EPERM; + if (!subdir->i_op || !subdir->i_op->unlink) + goto put_subdir; + + subdent = get_subfs_dentry(dentry); + rc = PTR_ERR(subdent); + if (IS_ERR(subdent)) + goto put_subdir; + + /* + * below is not a typo. We have to mark _deleted_ inode + * for possible later delete in clear_inode + */ + rc = subfs_get_access(dentry->d_inode, 1); + if (rc) + goto put_subdent; + + rc = subdir->i_op->unlink(subdir, subdent); + if (rc) + goto put_write_access; + + dir->i_mtime = subdir->i_mtime; + dir->i_ctime = subdir->i_ctime; + dir->i_nlink = subdir->i_nlink; + dir->i_size = subdir->i_size; + dir->i_blocks = subdir->i_blocks; + + dentry->d_inode->i_nlink = subdent->d_inode->i_nlink; + dentry->d_inode->i_ctime = subdent->d_inode->i_ctime; + +put_write_access: + /* + * we can't put write access if there are pending deletes + * so we leave it on and let it be put in clear_inode for + * i_nlink == 0 + * + * While i_nlink is believed to be atomic here i_count not, + * so we cannot check && i_count == 0. It is expected that + * deleted files are kept open only rarely. + */ + if (dentry->d_inode->i_nlink) + subfs_put_access(dentry->d_inode, 1); +put_subdent: + dput(subdent); +put_subdir: + iput(subdir); +go_offline: + subfs_go_offline(sb, mnt); +out: + LEAVE(sb, "dir=%p dentry=%s rc=%d", dir, dentry->d_name.name, rc); + + return rc; +} + +static int +supermount_symlink(struct inode *dir, struct dentry *dentry, + const char *symname) +{ + struct super_block *sb = dir->i_sb; + struct dentry *subdent; + struct inode *subdir; + int rc; + struct vfsmount *mnt; + + ENTER(sb, "dir=%p dentry=%s", dir, dentry->d_name.name); + + mnt = subfs_go_online(sb); + rc = PTR_ERR(mnt); + if (IS_ERR(mnt)) + goto out; + + subdir = get_subfs_inode(dir); + rc = PTR_ERR(subdir); + if (IS_ERR(subdir)) + goto go_offline; + + rc = -EPERM; + if (!subdir->i_op || !subdir->i_op->symlink) + goto put_subdir; + + subdent = get_subfs_dentry(dentry); + rc = PTR_ERR(subdent); + if (IS_ERR(subdent)) + goto put_subdir; + + rc = subfs_get_access(dir, 1); + if (rc) + goto put_subdent; + + rc = subdir->i_op->symlink(subdir, subdent, symname); + subfs_put_access(dir, 1); + if (rc) + goto put_subdent; + + rc = prepare_inode(dentry, subdent); + if (rc) + goto put_subdent; + + dir->i_mtime = subdir->i_mtime; + dir->i_ctime = subdir->i_ctime; + dir->i_nlink = subdir->i_nlink; + dir->i_size = subdir->i_size; + dir->i_blocks = subdir->i_blocks; + +put_subdent: + dput(subdent); +put_subdir: + iput(subdir); +go_offline: + subfs_go_offline(sb, mnt); +out: + LEAVE(sb, "dir=%p dentry=%s rc=%d", dir, dentry->d_name.name, rc); + + return rc; +} + +static int +supermount_mkdir(struct inode *dir, struct dentry *dentry, int mode) +{ + struct super_block *sb = dir->i_sb; + struct dentry *subdent; + struct inode *subdir; + int rc; + struct vfsmount *mnt; + + ENTER(sb, "dir=%p dentry=%s", dir, dentry->d_name.name); + + mnt = subfs_go_online(sb); + rc = PTR_ERR(mnt); + if (IS_ERR(mnt)) + goto out; + + subdir = get_subfs_inode(dir); + rc = PTR_ERR(subdir); + if (IS_ERR(subdir)) + goto go_offline; + + rc = -EPERM; + if (!subdir->i_op || !subdir->i_op->mkdir) + goto put_subdir; + + subdent = get_subfs_dentry(dentry); + rc = PTR_ERR(subdent); + if (IS_ERR(subdent)) + goto put_subdir; + + rc = subfs_get_access(dir, 1); + if (rc) + goto put_subdent; + + rc = subdir->i_op->mkdir(subdir, subdent, mode); + subfs_put_access(dir, 1); + if (rc) + goto put_subdent; + + rc = prepare_inode(dentry, subdent); + if (rc) + goto put_subdent; + + dir->i_mtime = subdir->i_mtime; + dir->i_ctime = subdir->i_ctime; + dir->i_nlink = subdir->i_nlink; + dir->i_size = subdir->i_size; + dir->i_blocks = subdir->i_blocks; + +put_subdent: + dput(subdent); +put_subdir: + iput(subdir); +go_offline: + subfs_go_offline(sb, mnt); +out: + LEAVE(sb, "dir=%p dentry=%s rc=%d", dir, dentry->d_name.name, rc); + + return rc; +} + +static int +supermount_rmdir(struct inode *dir, struct dentry *dentry) +{ + struct super_block *sb = dir->i_sb; + struct dentry *subdent; + struct inode *subdir; + int rc; + struct vfsmount *mnt; + + ENTER(sb, "dir=%p dentry=%s", dir, dentry->d_name.name); + + mnt = subfs_go_online(sb); + rc = PTR_ERR(mnt); + if (IS_ERR(mnt)) + goto out; + + subdir = get_subfs_inode(dir); + rc = PTR_ERR(subdir); + if (IS_ERR(subdir)) + goto go_offline; + + rc = -EPERM; + if (!subdir->i_op || !subdir->i_op->rmdir) + goto put_subdir; + + subdent = get_subfs_dentry(dentry); + rc = PTR_ERR(subdent); + if (IS_ERR(subdent)) + goto put_subdir; + + /* cf. supermount_unlink */ + rc = subfs_get_access(dentry->d_inode, 1); + if (rc) + goto put_subdent; + + rc = subdir->i_op->rmdir(subdir, subdent); + if (rc) + goto put_write_access; + + dir->i_mtime = subdir->i_mtime; + dir->i_ctime = subdir->i_ctime; + dir->i_nlink = subdir->i_nlink; + dir->i_size = subdir->i_size; + dir->i_blocks = subdir->i_blocks; + + /* hmm ... hard links to directories are not allowed, are they? */ + dentry->d_inode->i_nlink = subdent->d_inode->i_nlink; + dentry->d_inode->i_ctime = subdent->d_inode->i_ctime; + +put_write_access: + /* cf. supermount_unlink */ + if (dentry->d_inode->i_nlink) + subfs_put_access(dentry->d_inode, 1); +put_subdent: + dput(subdent); +put_subdir: + iput(subdir); +go_offline: + subfs_go_offline(sb, mnt); +out: + LEAVE(sb, "dir=%p dentry=%s rc=%d", dir, dentry->d_name.name, rc); + + return rc; +} + +static int +supermount_mknod(struct inode *dir, struct dentry *dentry, int + mode, dev_t dev) +{ + struct super_block *sb = dir->i_sb; + struct dentry *subdent; + struct inode *subdir; + int rc; + struct vfsmount *mnt; + + ENTER(sb, "dir=%p dentry=%s", dir, dentry->d_name.name); + + mnt = subfs_go_online(sb); + rc = PTR_ERR(mnt); + if (IS_ERR(mnt)) + goto out; + + subdir = get_subfs_inode(dir); + rc = PTR_ERR(subdir); + if (IS_ERR(subdir)) + goto go_offline; + + rc = -EPERM; + if (!subdir->i_op || !subdir->i_op->mknod) + goto put_subdir; + + subdent = get_subfs_dentry(dentry); + rc = PTR_ERR(subdent); + if (IS_ERR(subdent)) + goto put_subdir; + + rc = subfs_get_access(dir, 1); + if (rc) + goto put_subdent; + + rc = subdir->i_op->mknod(subdir, subdent, mode, dev); + subfs_put_access(dir, 1); + if (rc) + goto put_subdent; + + rc = prepare_inode(dentry, subdent); + if (rc) + goto put_subdent; + + dir->i_mtime = subdir->i_mtime; + dir->i_ctime = subdir->i_ctime; + dir->i_nlink = subdir->i_nlink; + dir->i_size = subdir->i_size; + dir->i_blocks = subdir->i_blocks; + +put_subdent: + dput(subdent); +put_subdir: + iput(subdir); +go_offline: + subfs_go_offline(sb, mnt); +out: + LEAVE(sb, "dir=%p dentry=%s rc=%d", dir, dentry->d_name.name, rc); + + return rc; +} + +static int +supermount_rename(struct inode *olddir, struct dentry *olddentry, + struct inode *newdir, struct dentry *newdentry) +{ + struct super_block *sb = olddir->i_sb; + struct dentry *oldsubdent, *newsubdent; + struct inode *oldsubdir, *newsubdir; + int rc; + struct vfsmount *mnt; + + ENTER(sb, "olddir=%p olddentry=%s newdir=%p newdentry=%s", olddir, olddentry->d_name.name, newdir, newdentry->d_name.name); + + mnt = subfs_go_online(sb); + rc = PTR_ERR(mnt); + if (IS_ERR(mnt)) + goto out; + + oldsubdir = get_subfs_inode(olddir); + rc = PTR_ERR(oldsubdir); + if (IS_ERR(oldsubdir)) + goto go_offline; + + rc = -EPERM; + if (!oldsubdir->i_op || !oldsubdir->i_op->rename) + goto put_old_subdir; + + oldsubdent = get_subfs_dentry(olddentry); + rc = PTR_ERR(oldsubdent); + if (IS_ERR(oldsubdent)) + goto put_old_subdir; + + newsubdir = get_subfs_inode(newdir); + rc = PTR_ERR(newsubdir); + if (IS_ERR(newsubdir)) + goto put_old_subdent; + + newsubdent = get_subfs_dentry(newdentry); + rc = PTR_ERR(newsubdent); + if (IS_ERR(newsubdent)) + goto put_new_subdir; + + /* + * If new file exists it will be implcitly unlinked so + * behave like in unlink case. + * If it does not exist we have two write accesses - for + * both old and new directory, I guess it does not matter + * which one is used in this case + */ + if (newdentry->d_inode) + rc = subfs_get_access(newdentry->d_inode, 1); + else + rc = subfs_get_access(olddir, 1); + if (rc) + goto put_new_subdent; + + rc = oldsubdir->i_op->rename(oldsubdir, oldsubdent, + newsubdir, newsubdent); + if (rc) + goto put_write_access; + + /* + * this is strictly speaking conditional on FS_ODD_RENAME + * flag, but as of this writing this flag is set only + * for NFS or intermezzo and it hopefully goes away sometimes ... + * + * Supermount patch adds code to do_kern_mount that checks + * for this flag and refuses mounting + */ + d_move(oldsubdent, newsubdent); + + olddir->i_mtime = oldsubdir->i_mtime; + olddir->i_ctime = oldsubdir->i_ctime; + olddir->i_nlink = oldsubdir->i_nlink; + olddir->i_size = oldsubdir->i_size; + olddir->i_blocks = oldsubdir->i_blocks; + + newdir->i_mtime = newsubdir->i_mtime; + newdir->i_ctime = newsubdir->i_ctime; + newdir->i_nlink = newsubdir->i_nlink; + newdir->i_size = newsubdir->i_size; + newdir->i_blocks = newsubdir->i_blocks; + + olddentry->d_inode->i_nlink = oldsubdent->d_inode->i_nlink; + olddentry->d_inode->i_ctime = oldsubdent->d_inode->i_ctime; + + if (newdentry->d_inode) { + newdentry->d_inode->i_nlink = newsubdent->d_inode->i_nlink; + newdentry->d_inode->i_ctime = newsubdent->d_inode->i_ctime; + } + +put_write_access: + if (newdentry->d_inode) { + /* cf. supermount_unlink */ + if (newdentry->d_inode->i_nlink) + subfs_put_access(newdentry->d_inode, 1); + } else + subfs_put_access(olddir, 1); +put_new_subdent: + dput(newsubdent); +put_new_subdir: + iput(newsubdir); +put_old_subdent: + dput(oldsubdent); +put_old_subdir: + iput(oldsubdir); +go_offline: + subfs_go_offline(sb, mnt); +out: + LEAVE(sb, "olddentry=%s newdentry=%s rc=%d", olddentry->d_name.name, newdentry->d_name.name, rc); + + return rc; +} + +static int +supermount_readlink(struct dentry *dentry, char *buffer , int buflen) +{ + struct super_block *sb = dentry->d_sb; + struct inode *inode = dentry->d_inode; + struct dentry *subdent; + int write_on = NEED_WRITE_ATIME(inode); + int rc; + struct vfsmount *mnt; + + ENTER(sb, "dentry=%s", dentry->d_name.name); + + mnt = subfs_go_online(sb); + rc = PTR_ERR(mnt); + if (IS_ERR(mnt)) + goto out; + + subdent = get_subfs_dentry(dentry); + rc = PTR_ERR(subdent); + if (IS_ERR(subdent)) + goto go_offline; + + rc = -EINVAL; + if (!subdent->d_inode->i_op || !subdent->d_inode->i_op->readlink) + goto put_subdent; + + rc = subfs_get_access(inode, write_on); + if (rc) + goto put_subdent; + + update_atime(subdent->d_inode); + rc = subdent->d_inode->i_op->readlink(subdent, buffer, buflen); + subfs_put_access(inode, write_on); + inode->i_atime = subdent->d_inode->i_atime; + +put_subdent: + dput(subdent); +go_offline: + subfs_go_offline(sb, mnt); +out: + LEAVE(sb, "dentry=%s rc=%d", dentry->d_name.name, rc); + + return rc; +} + +static int +supermount_follow_link(struct dentry *dentry, struct nameidata *nd) +{ + struct super_block *sb = dentry->d_sb; + struct inode *inode = dentry->d_inode; + struct dentry *subdent; + int write_on = NEED_WRITE_ATIME(inode); + int rc; + struct vfsmount *mnt; + + ENTER(sb, "dentry=%s", dentry->d_name.name); + + mnt = subfs_go_online(sb); + rc = PTR_ERR(mnt); + if (IS_ERR(mnt)) + goto out; + + subdent = get_subfs_dentry(dentry); + rc = PTR_ERR(subdent); + if (IS_ERR(subdent)) + goto go_offline; + + rc = subfs_get_access(inode, write_on); + if (rc) + goto put_subdent; + + update_atime(subdent->d_inode); + /* FIXME do we need proper subfs nd here? */ + rc = subdent->d_inode->i_op->follow_link(subdent, nd); + subfs_put_access(inode, write_on); + inode->i_atime = subdent->d_inode->i_atime; + +put_subdent: + dput(subdent); +go_offline: + subfs_go_offline(sb, mnt); +out: + LEAVE(sb, "dentry=%s rc=%d", dentry->d_name.name, rc); + + return rc; +} + +static int +supermount_permission(struct inode *inode, int mask, struct nameidata *nd) +{ + struct super_block *sb = inode->i_sb; + struct inode *subi; + int rc; + int write_on = !IS_RDONLY(inode) && (mask & MAY_WRITE); + struct vfsmount *mnt; + int fake_permissions = 1; + + ENTER(sb, "inode=%p", inode); + + mnt = subfs_go_online(sb); + rc = PTR_ERR(mnt); + if (IS_ERR(mnt)) + goto out; + + subi = get_subfs_inode(inode); + rc = PTR_ERR(subi); + if (IS_ERR(subi)) + goto go_offline; + + rc = subfs_get_access(inode, write_on); + if (rc) + goto put_subi; + + fake_permissions = 0; + /* FIXME do we need proper subfs nd here */ + if (subi->i_op && subi->i_op->permission) + rc = subi->i_op->permission(subi, mask, nd); + else + rc = vfs_permission(subi, mask); + + subfs_put_access(inode, write_on); + +put_subi: + iput(subi); +go_offline: + subfs_go_offline(sb, mnt); +out: + if (fake_permissions && inode == sb->s_root->d_inode) { + /* cf. file.c:supermount_open() */ + rc = vfs_permission(inode, mask); + } + + LEAVE(sb, "inode=%p rc=%d fake=%d", inode, rc, fake_permissions); + + return rc; +} + +static int +supermount_setattr(struct dentry *dentry, struct iattr *attr) +{ + struct super_block *sb = dentry->d_sb; + struct inode *subi; + struct dentry *subdent; + int rc; + struct vfsmount *mnt; + + ENTER(sb, "dentry=%s", dentry->d_name.name); + + mnt = subfs_go_online(sb); + rc = PTR_ERR(mnt); + if (IS_ERR(mnt)) + goto out; + + subdent = get_subfs_dentry(dentry); + rc = PTR_ERR(subdent); + if (IS_ERR(subdent)) + goto go_offline; + + rc = subfs_get_access(dentry->d_inode, 1); + if (rc) + goto put_subdent; + + subi = subdent->d_inode; + if (subi->i_op && subi->i_op->setattr) + rc = subi->i_op->setattr(subdent, attr); + else { + rc = inode_change_ok(subi, attr); + /* + * FIXME + * What to do with quota? + */ + if (!rc) + rc = inode_setattr(subi, attr); + } + subfs_put_access(dentry->d_inode, 1); + if (rc) + goto put_subdent; + + /* + * If it worked, then we need to mark the modification + * to the subfs, and we also need to propogate the + * change up to the shadowing inode. + */ + attr->ia_mode = subi->i_mode; + attr->ia_uid = subi->i_uid; + attr->ia_gid = subi->i_gid; + attr->ia_size = subi->i_size; + attr->ia_atime = subi->i_atime; + attr->ia_mtime = subi->i_mtime; + attr->ia_ctime = subi->i_ctime; + attr->ia_valid = + ATTR_UID | ATTR_GID | ATTR_MODE | ATTR_SIZE | + ATTR_ATIME | ATTR_MTIME | ATTR_CTIME; + inode_setattr(dentry->d_inode, attr); + +put_subdent: + dput(subdent); +go_offline: + subfs_go_offline(sb, mnt); +out: + LEAVE(sb, "dentry=%s rc=%d", dentry->d_name.name, rc); + + return rc; +} + +static int +supermount_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat) +{ + struct super_block *sb = dentry->d_sb; + struct vfsmount *submnt; + struct dentry *subdent = 0; + int rc; + + ENTER(sb, "dentry=%s", dentry->d_name.name); + + /* + * do not use subfs_go_online - it will result in ls /mnt + * attempting to mount all supermounted directories + */ + submnt = subfs_prevent_umount(sb); + if (submnt) + subdent = get_subfs_dentry(dentry); + + /* + * do not fail stat for stale files - it is needed to + * make sure fuser -m /mnt/cdrom lists all processes still + * having any (obsolete) file open + */ + if (submnt && subdent && !IS_ERR(subdent)) { + rc = vfs_getattr(submnt, subdent, stat); + stat->dev = sb->s_dev; + } else { + subfs_lock(sb); + generic_fillattr(dentry->d_inode, stat); + subfs_unlock(sb); + rc = 0; + } + + if (subdent && !IS_ERR(subdent)) + dput(subdent); + if (submnt) + subfs_allow_umount(sb, submnt); + + LEAVE(sb, "dentry=%s rc=%d", dentry->d_name.name, rc); + + return rc; +} + +/* + * directories can handle most operations... supermount/namei.c just + * passes them through to the underlying subfs, except for lookup(). + */ + +/* truncate: is not necesary, handled with setattr + * revalidate: only needed by nfs + * ->getattr: FIXME is not appeared to be used anywhere in kernel; so I am + * not sure how to implement it or what to return + * FIXME: implement accl functions + */ + +struct inode_operations supermount_dir_iops = { + .create = supermount_create, + .lookup = supermount_lookup, + .link = supermount_link, + .unlink = supermount_unlink, + .symlink = supermount_symlink, + .mkdir = supermount_mkdir, + .rmdir = supermount_rmdir, + .mknod = supermount_mknod, + .rename = supermount_rename, + .permission = supermount_permission, + .setattr = supermount_setattr, + .getattr = supermount_getattr, +}; + +struct inode_operations supermount_symlink_iops = { + .readlink = supermount_readlink, + .follow_link = supermount_follow_link, + .setattr = supermount_setattr, + .getattr = supermount_getattr, +}; + +struct inode_operations supermount_file_iops = { + .setattr = supermount_setattr, + .getattr = supermount_getattr, +}; diff -Nur linux-2.6.8.1_supermount/fs/supermount/proc.c linux-2.6.8.1/fs/supermount/proc.c --- linux-2.6.8.1_supermount/fs/supermount/proc.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.8.1/fs/supermount/proc.c 2004-08-17 03:16:00.000000000 +0200 @@ -0,0 +1,289 @@ +/* + * linux/fs/supermount/proc.c + * + * Initial version for kernel 2.4.21 (C) 2003 Andrey Borzenkov + * (arvidjaar@mail.ru) + * Rewritten for kernel 2.5.70 (C) 2003 Andrey Borzenkov + * (arvidjaar@mail.ru) + * + * $Id: 4715_supermount-2.0.4-2.6.7.patch,v 1.1 2004/06/22 22:15:04 gregkh Exp $ + */ + +#include "supermount.h" + +#ifdef CONFIG_PROC_FS + +/* + * we use semaphore because we have to lock subfs inside and it can + * sleep. Unlocking procfs list would mean adding generic usage count + * management to sbi and this is far too fetching + */ + +static DECLARE_MUTEX(supermount_proc_sem); +static struct proc_dir_entry *supermount_proc_root; +static struct proc_dir_entry *supermount_proc_subfs; +static struct proc_dir_entry *supermount_proc_version; +static struct supermount_sb_info *supermount_list_head; + +#define SKIP_BLANKS(s) while(*s == ' ' || *s == '\t' || *s == '\n') s++ +#define CHECK_COUNT do { \ + size += n; \ + count -= n; \ + if (count <= 0) { \ + subfs_unlock(sbi->host); \ + break; \ + } \ +} while (0) + +/* iterator; shamelessly copied over from pci.c */ +static void *supermount_seq_start(struct seq_file *sf, loff_t *pos) +{ + struct supermount_sb_info *sbi; + loff_t n = *pos; + + down(&supermount_proc_sem); + + sbi = supermount_list_head; + while (n-- && sbi) + sbi = sbi->next; + + return sbi; +} + +static void *supermount_seq_next(struct seq_file *sf, void *v, loff_t *pos) +{ + struct supermount_sb_info *sbi = v; + (*pos)++; + return sbi->next; +} + +static void supermount_seq_stop(struct seq_file *sf, void *v) +{ + up(&supermount_proc_sem); +} + + +static int +supermount_show_sbi(struct seq_file *sf, void *v) +{ + struct supermount_sb_info *sbi = v; + + + subfs_lock(sbi->host); + seq_puts(sf, sbi->devname); + if (sbi->disabled) + seq_puts(sf, " disabled\n"); + else if (subfs_is_mounted(sbi->host)) + seq_printf(sf, " mounted %d %d\n", + sbi->readcount, sbi->writecount); + else + seq_puts(sf, " unmounted\n"); + subfs_unlock(sbi->host); + + return 0; +} + +static struct seq_operations supermount_proc_subfs_op = { + start: supermount_seq_start, + next: supermount_seq_next, + stop: supermount_seq_stop, + show: supermount_show_sbi +}; + +static int supermount_proc_subfs_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &supermount_proc_subfs_op); +} + +/* + * mostly copied over from drivers/scsi/scsi.c:proc_scsi_gen_write() + */ +static int +supermount_proc_subfs_write(struct file *file, const char *buf, size_t length, loff_t *offset) +{ + char *buffer, *s, *dev = 0; + int disable = 0, enable = 0, release = 0, force = 0; + struct supermount_sb_info *sbi; + size_t rc; + + rc = -EINVAL; + if (!buf || length > PAGE_SIZE) + goto out; + + rc = -ENOMEM; + if (!(s = buffer = (char *)__get_free_page(GFP_KERNEL))) + goto out; + + rc =-EFAULT; + if(copy_from_user(buffer, buf, length)) + goto free_buffer; + + rc = -EINVAL; + if (length < PAGE_SIZE) + buffer[length] = '\0'; + else if (buffer[PAGE_SIZE-1]) + goto free_buffer; + + /* + * echo "/dev/cdrom [enable|disable] [release [force]]" > \ + * /proc/fs/supermount/subfs + */ + + do { + char *p; + + SKIP_BLANKS(s); + p = strpbrk(s, " \t\n"); + if (p) + *p++ = '\0'; + if (!dev) + dev = s; + else if (!strcmp(s, "disable")) + disable = 1; + else if (!strcmp(s, "enable")) + enable = 1; + else if (!strcmp(s, "release")) + release = 1; + else if (!strcmp(s, "force")) + force = 1; + else + goto free_buffer; + + s = p; + } while (s && *s); + + if ((enable && disable) || (force && !release)) + goto free_buffer; + + down(&supermount_proc_sem); + for(sbi = supermount_list_head; sbi; sbi = sbi->next) { + if (strcmp(sbi->devname, dev)) + continue; + + subfs_lock(sbi->host); + + rc = length; + if (release && subfs_is_mounted(sbi->host)) { + if (!subfs_is_busy(sbi->host) || force) + subfs_umount(sbi->host, SUBFS_UMNT_USER); + else + rc = -EBUSY; + } + + if (disable && subfs_is_mounted(sbi->host)) + rc = -EBUSY; + + if (rc >= 0) { + if (disable) + sbi->disabled = 1; + else if (enable) + sbi->disabled = 0; + } + + subfs_unlock(sbi->host); + break; + } + up(&supermount_proc_sem); + +free_buffer: + free_page((unsigned long)buffer); +out: + return rc; + +} + +static struct file_operations supermount_proc_subfs_operations = { + open: supermount_proc_subfs_open, + read: seq_read, + llseek: seq_lseek, + release: seq_release, + write: supermount_proc_subfs_write, +}; + +static int +supermount_proc_version_read(char *page, char **start, off_t pos, int count, int *eof, void *data) +{ + int rc; + + rc = snprintf(page, count, "Supermount version %s for kernel 2.6\n", + SUPERMOUNT_VERSION); + *eof = 1; + return rc; +} + +void +supermount_proc_register(void) +{ + supermount_proc_root = proc_mkdir("fs/supermount", 0); + if (!supermount_proc_root) { + printk(KERN_ERR "SUPERMOUNT failed to create /proc/fs/supermount"); + return; + } + supermount_proc_root->owner = THIS_MODULE; + + supermount_proc_version = create_proc_read_entry("version", + S_IFREG | S_IRUGO, + supermount_proc_root, + supermount_proc_version_read, + 0); + + if (supermount_proc_version) + supermount_proc_version->owner = THIS_MODULE; + else + printk(KERN_ERR + "SUPERMOUNT failed to create /proc/fs/supermount/version"); + + supermount_proc_subfs = create_proc_entry("subfs", + S_IFREG | S_IRUGO | S_IWUSR, + supermount_proc_root); + + if (supermount_proc_subfs) { + supermount_proc_subfs->proc_fops = + &supermount_proc_subfs_operations; + supermount_proc_subfs->owner = THIS_MODULE; + } else + printk(KERN_ERR + "SUPERMOUNT failed to create /proc/fs/supermount/subfs"); + +} + +void +supermount_proc_unregister(void) +{ + remove_proc_entry("fs/supermount/subfs", 0); + remove_proc_entry("fs/supermount/version", 0); + remove_proc_entry("fs/supermount", 0); +} + +void +supermount_proc_insert(struct supermount_sb_info *sbi) +{ + + down(&supermount_proc_sem); + + sbi->next = supermount_list_head; + supermount_list_head = sbi; + + up(&supermount_proc_sem); +} + +void +supermount_proc_remove(struct supermount_sb_info *sbi) +{ + struct supermount_sb_info **p, *q; + + down(&supermount_proc_sem); + + for(p = &supermount_list_head, q = supermount_list_head; + q; p = &q->next, q = q->next) + if (q == sbi) + break; + + if (q) + *p = q->next; + + up(&supermount_proc_sem); + + SUPERMOUNT_BUG_ON(!q); +} +#endif /* CONFIG_PROC_FS */ diff -Nur linux-2.6.8.1_supermount/fs/supermount/subfs.c linux-2.6.8.1/fs/supermount/subfs.c --- linux-2.6.8.1_supermount/fs/supermount/subfs.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.8.1/fs/supermount/subfs.c 2004-08-17 03:16:00.000000000 +0200 @@ -0,0 +1,742 @@ +/* + * linux/fs/supermount/subfs.c + * + * Original version: + * Copyright (C) 1995, 1997 + * Stephen Tweedie (sct@dcs.ed.ac.uk) + * + * from + * + * linux/fs/minix/inode.c + * Copyright (C) 1991, 1992 Linus Torvalds + * + * and + * + * linux/fs/ext2/super.c + * Copyright (C) 1992, 1993, 1994, 1995 Remy Card + * + * Rewriten for kernel 2.2, 2.4. (C) 1999, 2000 Alexis Mikhailov + * (alexis@abc.cap.ru) + * Rewritten for kernel 2.4.21 (C) 2003 Andrey Borzenkov + * (arvidjaar@mail.ru) + * Rewritten for kernel 2.5.70 (C) 2003 Andrey Borzenkov + * (arvidjaar@mail.ru) + * + * $Id: 4715_supermount-2.0.4-2.6.7.patch,v 1.1 2004/06/22 22:15:04 gregkh Exp $ + */ + +#define S_DBG_TRACE_CURRENT S_DBG_TRACE_SUBFS +#include "supermount.h" + +/* + * close all open files on subfs + */ +static void +supermount_clean_files(struct super_block *sb) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + struct list_head *ptr, *n; + + ENTER(sb); + + list_for_each_safe(ptr, n, &sbi->s_files) { + struct supermount_file_info *sfi; + struct file *subfile; + + sfi = list_entry(ptr, struct supermount_file_info, list); + + subfile = sfi->file; + sfi->file = 0; + list_del_init(&sfi->list); + + SUPERMOUNT_BUG_LOCKED_ON(sb, !subfile); + + fput(subfile); + } + + LEAVE(sb); + +} + +/* + * close all open dentries on subfs + */ +static void +supermount_clean_dentries(struct super_block *sb) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + struct list_head *ptr, *n; + + ENTER(sb); + + list_for_each_safe(ptr, n, &sbi->s_dentries) { + struct supermount_dentry_info *sdi; + struct dentry *subdent; + + sdi = list_entry(ptr, struct supermount_dentry_info, list); + /* + * HACK - FIXME + * see dentry.c:supermount_d_compare + */ + write_lock(&d_compare_lock); + subdent = sdi->dentry; + sdi->dentry = 0; + write_unlock(&d_compare_lock); + + list_del_init(&sdi->list); + d_drop(sdi->host); + dnotify_parent(sdi->host, DN_DELETE); + + SUPERMOUNT_BUG_LOCKED_ON(sb, !subdent); + + dput(subdent); + } + + LEAVE(sb); + +} + +/* + * close all open inodes on subfs + */ +static void +supermount_clean_inodes(struct super_block *sb) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + struct list_head *ptr, *n; + + ENTER(sb); + + list_for_each_safe(ptr, n, &sbi->s_inodes) { + struct supermount_inode_info *sii; + struct inode *host; + struct inode *subi; + + sii = list_entry(ptr, struct supermount_inode_info, list); + host = &sii->vfs_inode; + + subi = sii->inode; + sii->inode = NULL; + list_del_init(&sii->list); + remove_inode_hash(host); + host->i_mapping = &host->i_data; + + /* + * it is possible to have no subi here. clear_inode does + * release lock after removing subi but before unlinking + * it + */ + if (subi) + iput(subi); + + SUPERMOUNT_BUG_LOCKED_ON(sb, sii->writecount < 0); + SUPERMOUNT_BUG_LOCKED_ON(sb, sii->readcount < 0); + while (sii->writecount) { + sii->writecount--; + sbi->writecount--; + } + while (sii->readcount) { + sii->readcount--; + sbi->readcount--; + } + } + + LEAVE(sb); +} + +/* + * reason can be + * SUBFS_UMNT_NORMAL - normal umount, do not try to release subfs + * SUBFS_UMNT_MEDIA - media change detected, release subfs, + * do not remount ro (as media is already gone) + * SUBFS_UMNT_USER - user request, release subfs, remount ro before + * releasing tray lock + * + * unlock_door is always needed to keep device usage count correct + */ +static inline int subfs_remount_ro(struct super_block *sb); +void +subfs_umount(struct super_block *sb, int reason) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + int writecount = sbi->writecount; + + ENTER(sb); + + if (reason != SUBFS_UMNT_NORMAL) { + /* + * we used to did shrink_dcache here. It compicates locking + * (clear_inode is called under sbi->sem thus requiring either + * recursive lock or separate lock just for inode list). + * This is not needed any more to ensure subfs can be umounted + * so we let dentries die and rely on dentry_revalidate to + * reject stale dentries + */ + + if (subfs_is_busy(sb)) + supermount_warning(sb, "opened files during media change"); + + supermount_clean_files(sb); + supermount_clean_dentries(sb); + supermount_clean_inodes(sb); + + if (reason == SUBFS_UMNT_USER && writecount > 0) + subfs_remount_ro(sb); + if (sbi->lockcount > 0) + supermount_unlock_door(sb); + /* + * this is quite ugly but so far I have no idea how to + * do it cleanly + */ + sbi->lockcount = 0; + } + + /* + * all files are expected to be closed + */ + SUPERMOUNT_BUG_LOCKED_ON(sb, sbi->writecount); + SUPERMOUNT_BUG_LOCKED_ON(sb, sbi->readcount); + SUPERMOUNT_BUG_LOCKED_ON(sb, sbi->lockcount); + SUPERMOUNT_BUG_LOCKED_ON(sb, !list_empty(&sbi->s_files)); + SUPERMOUNT_BUG_LOCKED_ON(sb, !list_empty(&sbi->s_dentries)); + SUPERMOUNT_BUG_LOCKED_ON(sb, !list_empty(&sbi->s_inodes)); + + unmark_media_supermounted(sb); + mntput(sbi->s_undermount); + sbi->s_undermount = NULL; + if (sbi->rw) + sb->s_flags &= ~MS_RDONLY; + sb->s_blocksize = 1024; + sb->s_blocksize_bits = 10; + + /* + * FIXME + * again the problem of unmounting subfs from inside of put_super + */ + if (sb->s_root) + supermount_init_root_inode(sb->s_root->d_inode); + + LEAVE(sb); +} + +static inline int +subfs_remount_ro(struct super_block *sb) +{ + struct super_block *subsb = subfs_sb(sb); + int rc; + + ENTER(sb); + + rc = do_remount_sb(subsb, subsb->s_flags | MS_RDONLY, NULL, 0); + + LEAVE(sb); + + return rc; +} + +static inline int +subfs_remount_rw(struct super_block *sb) +{ + struct super_block *subsb = subfs_sb(sb); + int rc; + + ENTER(sb); + + rc = do_remount_sb(subsb, subsb->s_flags & ~MS_RDONLY, NULL, 0); + + LEAVE(sb); + + return rc; +} + +static struct vfsmount * +subfs_real_mount(struct super_block *sb, char *type) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + struct vfsmount *mnt; + char *opts = NULL; + + ENTER(sb, "type=%s", type); + + if (sbi->s_data) { + opts = strdup(sbi->s_data); + if (!opts) { + mnt = ERR_PTR(-ENOMEM); + goto fail; + } + } + + mnt = do_kern_mount(type, + (sb->s_flags & MS_RMT_MASK) | MS_SUPERMOUNTED, + sbi->devname, opts); + if (opts) + kfree(opts); + +fail: + LEAVE(sb, "submnt=%p", mnt); + + return mnt; +} + +static int +subfs_real_mount2(struct super_block *sb, char *type) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + struct vfsmount *mnt; + struct super_block *subsb; + int rc = 0; + + ENTER(sb, "type=%s", type); + + mnt = subfs_real_mount(sb, type); + + if (IS_ERR(mnt) && PTR_ERR(mnt) == -EROFS) { + sb->s_flags |= MS_RDONLY; + mnt = subfs_real_mount(sb, type); + } + rc = PTR_ERR(mnt); + if (IS_ERR(mnt)) + goto out; + + /* paranoid check for double-mounting */ + SUPERMOUNT_BUG_LOCKED_ON(sb, atomic_read(&mnt->mnt_sb->s_active) != 1); + + sbi->s_undermount = mnt; + + if (!(sb->s_flags & MS_RDONLY)) { + rc = subfs_remount_ro(sb); + if (rc) + goto mntput; + } + + subsb = mnt->mnt_sb; + sb->s_blocksize = subsb->s_blocksize; + sb->s_blocksize_bits = subsb->s_blocksize_bits; + attach_subfs_dentry(sb->s_root, mnt->mnt_root); + attach_subfs_inode(sb->s_root->d_inode, mnt->mnt_root->d_inode); + insert_inode_hash(sb->s_root->d_inode); + dnotify_parent(sb->s_root, DN_CREATE); + rc = 0; + goto out; + + /* + * error clean up + */ +mntput: + sbi->s_undermount = 0; + mntput(mnt); +out: + LEAVE(sb, "type=%s rc=%d", type, rc); + + return rc; +} + +/* + * Error values from mount + * ENOENT - no device file (quite possible with devfs) + * ENXIO - device does not exist + * ENOMEDIUM - no medium inserted (surprise, surprise :) + * EBUSY - attempt to mount on already mounted device + * we specifically disallow it even when both + * file system and mode (ro/rw) are the same + */ +static int +subfs_mount(struct super_block *sb) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + int retval = -ENODEV; + char *types = strdup(sbi->s_type); + + ENTER(sb); + + if (sbi->disabled) + retval = -EPERM; + else if (!types) + retval = -ENOMEM; + else { + char *p = types, *fs; + + while ((fs = strsep(&p, ":")) != NULL && retval + && retval != -ENXIO + && retval != -ENOMEDIUM + && retval != -ENOENT + && retval != -EBUSY) + retval = subfs_real_mount2(sb, fs); + } + + if (types) + kfree(types); + + if (!retval) + mark_media_supermounted(sb); + + LEAVE(sb, "rc=%d", retval); + + return retval; +} + +static int +__subfs_check_disk_change(struct super_block *sb) +{ + struct super_block *subsb = subfs_sb(sb); + int rc; + struct block_device *dev = subsb->s_bdev; + + ENTER(sb); + + rc = atomic_read(&subsb->s_media_changed); + if (!rc) { + lock_kernel(); + rc = check_disk_change(dev); + unlock_kernel(); + } + + if (rc) + subfs_umount(sb, SUBFS_UMNT_MEDIA); + + LEAVE(sb, "rc=%d", rc); + + return rc; +} + +/* + * this must really be called subfs_active ... + */ +int +subfs_check_disk_change(struct super_block *sb) +{ + int rc; + + ENTER(sb); + + subfs_lock(sb); + if (subfs_is_mounted(sb)) + rc = __subfs_check_disk_change(sb); + else + rc = 1; + subfs_unlock(sb); + + + LEAVE(sb, "rc=%d", rc); + + return rc; +} + +static int +check_and_remount_subfs(struct super_block *sb) +{ + int rc; + + ENTER(sb); + + if (subfs_is_mounted(sb) && !__subfs_check_disk_change(sb)) { + rc = 0; + goto out; + } + + rc = subfs_mount(sb); + +out: + LEAVE(sb, "rc=%d", rc); + + return rc; +} + +static inline void +subfs_tray_lock(struct super_block *sb) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + + ENTER(sb); + + SUPERMOUNT_BUG_LOCKED_ON(sb, sbi->lockcount < 0); + + if (!sbi->lockcount++) + supermount_lock_door(sb); + + LEAVE(sb); +} + +static inline void +subfs_tray_unlock(struct super_block *sb) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + + ENTER(sb); + + SUPERMOUNT_BUG_LOCKED_ON(sb, sbi->lockcount <= 0); + + if (!--sbi->lockcount) + supermount_unlock_door(sb); + + LEAVE(sb); +} + +static void +subfs_get_read_access(struct super_block *sb) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + + ENTER(sb); + + SUPERMOUNT_BUG_LOCKED_ON(sb, sbi->readcount < 0); + + if (!sbi->readcount++ && sbi->tray_lock == TRAY_LOCK_ALWAYS) + subfs_tray_lock(sb); + + LEAVE(sb); +} + +static int +subfs_get_write_access(struct super_block *sb) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + int rc; + + ENTER(sb); + + SUPERMOUNT_BUG_LOCKED_ON(sb, sbi->writecount < 0); + + rc = 0; + if (!sbi->writecount) { + if (sb->s_flags & MS_RDONLY) + rc = -EROFS; + else + rc = subfs_remount_rw(sb); + } + + if (!rc && !sbi->writecount++ && sbi->tray_lock != TRAY_LOCK_NEVER) + subfs_tray_lock(sb); + + LEAVE(sb, "rc=%d", rc); + + return rc; +} + +static void +subfs_put_read_access(struct super_block *sb) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + + ENTER(sb); + + SUPERMOUNT_BUG_LOCKED_ON(sb, sbi->readcount <= 0); + + if (!--sbi->readcount && sbi->tray_lock == TRAY_LOCK_ALWAYS) + subfs_tray_unlock(sb); + + LEAVE(sb); +} + +static void +subfs_put_write_access(struct super_block *sb) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + + ENTER(sb); + + SUPERMOUNT_BUG_LOCKED_ON(sb, sbi->writecount <= 0); + + if (!--sbi->writecount) { + /* + * no need to fsync, it is done automatically on remount + */ + int rc = subfs_remount_ro(sb); + if (rc) + supermount_error(sb, "failed to remount ro, error = %d", rc); + if (sbi->tray_lock != TRAY_LOCK_NEVER) + subfs_tray_unlock(sb); + } + + LEAVE(sb); +} + +int +subfs_get_access(struct inode *inode, int rw) +{ + struct super_block *sb = inode->i_sb; + int rc = 0; + + ENTER(sb); + + subfs_lock(sb); + if (is_inode_obsolete(inode)) + rc = -ESTALE; + else { + struct supermount_inode_info *sii = supermount_i(inode); + + if (rw) { + SUPERMOUNT_BUG_LOCKED_ON(sb, sii->writecount < 0); + rc = subfs_get_write_access(sb); + if (!rc) + sii->writecount++; + } else { + SUPERMOUNT_BUG_LOCKED_ON(sb, sii->readcount < 0); + subfs_get_read_access(sb); + sii->readcount++; + } + } + subfs_unlock(sb); + + LEAVE(sb, "rc=%d", rc); + + return rc; +} + +void +subfs_put_access(struct inode *inode, int rw) +{ + struct super_block *sb = inode->i_sb; + + ENTER(sb); + + subfs_lock(sb); + if (!is_inode_obsolete(inode)) { + struct supermount_inode_info *sii = supermount_i(inode); + + if (rw) { + SUPERMOUNT_BUG_LOCKED_ON(sb, sii->writecount <= 0); + sii->writecount--; + subfs_put_write_access(sb); + } else { + SUPERMOUNT_BUG_LOCKED_ON(sb, sii->readcount <= 0); + sii->readcount--; + subfs_put_read_access(sb); + } + + } + subfs_unlock(sb); + + LEAVE(sb); +} + +struct vfsmount * +subfs_go_online(struct super_block *sb) +{ + int rc; + struct vfsmount *mnt; + + ENTER(sb); + + subfs_lock(sb); + + rc = check_and_remount_subfs(sb); + mnt = ERR_PTR(rc); + if (!rc) + mnt = mntget(subfs_mnt(sb)); + subfs_unlock(sb); + + LEAVE(sb, "mnt=%p", mnt); + + return mnt; +} + +void +subfs_go_offline(struct super_block *sb, struct vfsmount *mnt) +{ + ENTER(sb); + + SUPERMOUNT_BUG_ON(!mnt); + mntput(mnt); + + LEAVE(sb); + +} + +struct vfsmount * +subfs_prevent_umount(struct super_block *sb) +{ + struct vfsmount *mnt; + + ENTER(sb); + + subfs_lock(sb); + mnt = subfs_mnt(sb); + if (mnt) + mnt = mntget(mnt); + subfs_unlock(sb); + + LEAVE(sb, "mnt=%p", mnt); + + return mnt; +} + +void +subfs_allow_umount(struct super_block *sb, struct vfsmount *mnt) +{ + ENTER(sb); + + SUPERMOUNT_BUG_ON(!mnt); + mntput(mnt); + + LEAVE(sb); + +} + +struct vfsmount * +subfs_get_mnt(struct super_block *sb) +{ + struct vfsmount *mnt = NULL; + + ENTER(sb); + + subfs_lock(sb); + if (subfs_is_mounted(sb)) + mnt = mntget(subfs_mnt(sb)); + subfs_unlock(sb); + + LEAVE(sb, "mnt=%p", mnt); + + return mnt; +} + +struct super_block * +subfs_get_sb(struct super_block *sb) +{ + struct super_block *subsb = NULL; + + ENTER(sb); + + subfs_lock(sb); + if (subfs_is_mounted(sb)) + subsb = subfs_sb(sb); + subfs_unlock(sb); + + LEAVE(sb, "subsb=%p", subsb); + + return subsb; +} + +/* + * contrary to its name this function deals with _supermount_ inode + */ +void +subfs_clear_inode(struct inode *inode) +{ + struct super_block *sb = inode->i_sb; + struct supermount_inode_info *sii = supermount_i(inode); + + ENTER(sb, "inode=%p", inode); + + subfs_lock(sb); + + /* + * this is safe. If subfs has been unmounted, counters has been + * set to 0 already + */ + while(sii->writecount > 0) { + sii->writecount--; + subfs_put_write_access(sb); + } + + while(sii->readcount > 0) { + sii->readcount--; + subfs_put_read_access(sb); + } + + list_del(&sii->list); + + subfs_unlock(sb); + + LEAVE(sb, "inode=%p", inode); +} diff -Nur linux-2.6.8.1_supermount/fs/supermount/super.c linux-2.6.8.1/fs/supermount/super.c --- linux-2.6.8.1_supermount/fs/supermount/super.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.8.1/fs/supermount/super.c 2004-08-17 03:16:00.000000000 +0200 @@ -0,0 +1,646 @@ +/* + * linux/fs/supermount/super.c + * + * Original version: + * Copyright (C) 1995, 1997 + * Stephen Tweedie (sct@@dcs.ed.ac.uk) + * + * from + * + * linux/fs/minix/inode.c + * Copyright (C) 1991, 1992 Linus Torvalds + * + * and + * + * linux/fs/ext2/super.c + * Copyright (C) 1992, 1993, 1994, 1995 Remy Card + * + * Rewriten for kernel 2.2, 2.4. (C) 1999, 2000 Alexis Mikhailov + * (alexis@@abc.cap.ru) + * Rewritten for kernel 2.4.21 (C) 2003 Andrey Borzenkov + * (arvidjaar@@mail.ru) + * Rewritten for kernel 2.5.70 (C) 2003 Andrey Borzenkov + * (arvidjaar@@mail.ru) + * + * $Id: 4715_supermount-2.0.4-2.6.7.patch,v 1.1 2004/06/22 22:15:04 gregkh Exp $ + */ + +#define S_DBG_TRACE_CURRENT S_DBG_TRACE_SUPER + +#include +#include +#include "supermount.h" + +/* + * vfat and msdos do not have any valididty checks for superblock and must + * be the last entries! + */ + +static char *default_fs_types = "udf:iso9660:ext2:vfat:msdos"; + +static struct super_operations supermount_sops; + +/* ========================= helpers ================================= */ + +static inline void +supermount_update_inode(struct inode *superi, struct inode *subi) +{ + struct super_block *sb = superi->i_sb; + + ENTER(sb, "superi=%p subi=%p", superi, subi); + + superi->i_ino = subi->i_ino; + superi->i_mode = subi->i_mode; + superi->i_uid = subi->i_uid; + superi->i_gid = subi->i_gid; + superi->i_nlink = subi->i_nlink; + superi->i_size = subi->i_size; + superi->i_atime = subi->i_atime; + superi->i_ctime = subi->i_ctime; + superi->i_mtime = subi->i_mtime; + superi->i_blksize = subi->i_blksize; + superi->i_blocks = subi->i_blocks; + superi->i_rdev = subi->i_rdev; + superi->i_version++; + set_inode_flags(superi, subi); + + if (S_ISDIR(superi->i_mode)) { + superi->i_op = &supermount_dir_iops; + superi->i_fop = &supermount_dir_operations; + } else if (S_ISLNK(superi->i_mode)) { + superi->i_op = &supermount_symlink_iops; + superi->i_mapping = subi->i_mapping; + } else { + superi->i_op = &supermount_file_iops; + superi->i_fop = &supermount_file_operations; + superi->i_mapping = subi->i_mapping; + } + + LEAVE(sb, "superi=%p subi=%p", superi, subi); +} + +/* this is also called from subfs_mount to reinstantiate root inode */ +void +attach_subfs_inode(struct inode *inode, struct inode *subi) +{ + struct super_block *sb = inode->i_sb; + struct supermount_sb_info *sbi = supermount_sbi(sb); + struct supermount_inode_info *sii; + + ENTER(sb, "inode=%p subi=%p", inode, subi); + + sii = supermount_i(inode); + if (!sii->inode) { + /* + * this can be run concurrently. It is executed under + * sbi->sem so only one task would actually instantate + * inode. See namei.c:prepare_inode + * Another user is subfs.c:subfs_real_mount2 + */ + sii->inode = igrab(subi); + supermount_update_inode(inode, subi); + list_add(&sii->list, &sbi->s_inodes); + } else + SUPERMOUNT_BUG_LOCKED_ON(sb, sii->inode != subi); + + LEAVE(sb, "inode=%p subi=%p", inode, subi); +} + +char * +strdup(const char *val) +{ + char *tmp; + tmp = kmalloc(1 + strlen(val), GFP_KERNEL); + if (tmp) + strcpy(tmp, val); + return tmp; +} + +static struct supermount_sb_info * +create_sbi(struct super_block *sb) +{ + struct supermount_sb_info *sbi = kmalloc(sizeof (*sbi), GFP_KERNEL); + + if (!sbi) + return NULL; + + memset(sbi, 0, sizeof (*sbi)); + + sbi->s_undermount = NULL; + sbi->s_type = sbi->devname = sbi->s_data = NULL; + INIT_LIST_HEAD(&sbi->s_inodes); + INIT_LIST_HEAD(&sbi->s_files); + INIT_LIST_HEAD(&sbi->s_dentries); + init_MUTEX(&sbi->sem); + sbi->host = sb; + sbi->tray_lock = TRAY_LOCK_ONWRITE; + sbi->rw = !(sb->s_flags & MS_RDONLY); + + return sbi; +} + +static void +free_sbi(struct supermount_sb_info *sbi) +{ + if (sbi->s_type && sbi->s_type != default_fs_types) + kfree(sbi->s_type); + if (sbi->devname) + kfree(sbi->devname); + if (sbi->s_data) + kfree(sbi->s_data); + kfree(sbi); +} + +enum { + Opt_fs, Opt_fs_auto, Opt_dev, Opt_debug, Opt_debug_old, Opt_subfsopts, + Opt_tray_lock_always, Opt_tray_lock_never, Opt_tray_lock_onwrite, + Opt_ignore, Opt_err +}; + +static match_table_t tokens = { + {Opt_fs_auto, "fs=auto"}, + {Opt_fs, "fs=%s"}, + {Opt_dev, "dev=%s"}, + {Opt_debug, "debug=%u"}, + {Opt_debug_old, "debug"}, + {Opt_subfsopts, "--"}, + {Opt_tray_lock_always, "tray_lock=always"}, + {Opt_tray_lock_never, "tray_lock=never"}, + {Opt_tray_lock_onwrite, "tray_lock=onwrite"}, + {Opt_ignore, "no_tray_lock"}, + {Opt_ignore, "fail_statfs_until_mount"}, + {Opt_err, NULL} +}; + +static int +parse_options(char *options, struct super_block *sb) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + int rc; + char *p; + + if (!options) + return 0; + + while ((p = strsep(&options, ",")) != NULL) { + int token; + substring_t args[MAX_OPT_ARGS]; + int n; + + /* empty option means start of subfs options */ + if (!*p) + goto copy_subfs_options; + + token = match_token(p, tokens, args); + switch(token) { + case Opt_fs: + sbi->s_type = match_strdup(&args[0]); + if (!sbi->s_type) + return -ENOMEM; + break; + case Opt_fs_auto: + sbi->s_type = default_fs_types; + break; + case Opt_dev: + sbi->devname = match_strdup(&args[0]); + if (!sbi->devname) + return -ENOMEM; + break; + case Opt_debug: + if ((rc = match_int(&args[0], &n))) + return rc; + sbi->s_debug = n; + break; + case Opt_debug_old: + sbi->s_debug = S_DBG_DEBUG; + break; + case Opt_tray_lock_always: + sbi->tray_lock = TRAY_LOCK_ALWAYS; + break; + case Opt_tray_lock_onwrite: + sbi->tray_lock = TRAY_LOCK_ONWRITE; + break; + case Opt_tray_lock_never: + sbi->tray_lock = TRAY_LOCK_NEVER; + break; + case Opt_subfsopts: +copy_subfs_options: + /* mount may have removed options after -- */ + if (options && *options) { + sbi->s_data = strdup(options); + if (!sbi->s_data) + return -ENOMEM; + } + return 0; + break; + case Opt_ignore: + break; + default: + supermount_error(sb, "Unrecognized mount option \"%s\"", + p); + return -EINVAL; + break; + } + } + + return 0; +} + +void +supermount_init_root_inode(struct inode *inode) +{ + inode->i_mode = 0777 | S_IFDIR; + inode->i_uid = current->fsuid; + inode->i_gid = current->fsgid; + inode->i_blksize = inode->i_sb->s_blocksize; + inode->i_rdev = MKDEV(UNNAMED_MAJOR, 0); + inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; + inode->i_size = 0; + inode->i_blocks = 0; + inode->i_ino = 0; + inode->i_nlink = 0; + + set_inode_flags(inode, 0); + + inode->i_op = &supermount_dir_iops; + inode->i_fop = &supermount_dir_operations; +} + +static struct inode * +supermount_root_inode(struct super_block *sb) +{ + struct inode *inode = new_inode(sb); + + ENTER(sb); + + if (inode) { + inode->i_version = 0; + supermount_init_root_inode(inode); + } + + LEAVE(sb, "inode=%p", inode); + + return inode; +} + +/* ========================== used in fstype declaration ================*/ + +/* read_super: the main mount() entry point into the VFS layer. */ +static int +supermount_read_super(struct super_block *sb, void *data, int silent) +{ + struct inode *root_inode; + struct dentry *root; + struct supermount_sb_info *sbi = create_sbi(sb); + int rc = -ENOMEM; + + if (!sbi) + goto fail_no_memory; + + sb->s_fs_info = sbi; + + rc = parse_options((char *) data, sb); + if (rc) + goto fail_parsing; + + rc = -EINVAL; + if (!sbi->devname) { + supermount_error(sb, "no dev= option"); + goto fail_parsing; + } + + if (!sbi->s_type) { + sbi->s_type = default_fs_types; + supermount_warning(sb, "no fs= option, assuming fs=auto"); + } + + rc = -ENOMEM; + + sb->s_blocksize = 1024; + sb->s_blocksize_bits = 10; + sb->s_magic = SUPERMOUNT_SUPER_MAGIC; + sb->s_op = &supermount_sops; + + root_inode = supermount_root_inode(sb); + if (!root_inode) + goto fail_allocating_root_inode; + + root = d_alloc_root(root_inode); + if (!root) + goto fail_allocating_root_dentry; + if (init_dentry_info(root)) + goto fail_init_root_info; + + sb->s_root = root; + + supermount_proc_insert(sbi); + + return 0; + +fail_init_root_info: + dput(root); +fail_allocating_root_dentry: + iput(root_inode); +fail_parsing: +fail_allocating_root_inode: + free_sbi(sbi); + +fail_no_memory: + return rc; +} + +struct super_block *supermount_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) +{ + return get_sb_nodev(fs_type, flags, data, supermount_read_super); +} + +/* ======================= super_operations methods ==================== */ + +static struct inode * +supermount_alloc_inode(struct super_block *sb) +{ + struct supermount_inode_info *sii = kmalloc(sizeof(*sii), GFP_KERNEL); + struct inode *inode = 0; + + ENTER(sb); + + if (sii) { + INIT_LIST_HEAD(&sii->list); + sii->inode = 0; + sii->readcount = 0; + sii->writecount = 0; + inode = &sii->vfs_inode; + inode_init_once(inode); + } + + LEAVE(sb, "inode=%p", inode); + + return inode; +} + +static void +supermount_destroy_inode(struct inode *inode) +{ + struct super_block *sb = inode->i_sb; + struct supermount_inode_info *sii = supermount_i(inode); + + ENTER(sb, "inode=%p", inode); + + kfree(sii); + + ENTER(sb, "inode=%p", inode); +} + +/* + * FIXME + * I am still unsure if (or why) this functions is needed; it is likely + * to go away. So far the only _real_ user seems to be knfsd. + */ +static void +supermount_write_inode(struct inode *inode, int sync) +{ + struct super_block *sb = inode->i_sb; + struct super_block *subsb; + struct inode *subi; + struct vfsmount *submnt; + + ENTER(sb); + + if (subfs_check_disk_change(sb)) + goto out; + + submnt = subfs_prevent_umount(sb); + if (!submnt) + goto out; + + subi = get_subfs_inode(inode); + if (IS_ERR(subi)) + goto allow_umount; + + subsb = subfs_get_sb(sb); + if (!sb) + goto put_subi; + + if (subsb->s_op && subsb->s_op->write_inode) { + if (subfs_get_access(inode, 1)) + goto put_subi; + + write_inode_now(subi, sync); + + subfs_put_access(inode, 1); + } +put_subi: + iput(subi); +allow_umount: + subfs_allow_umount(sb, submnt); +out: + LEAVE(sb); + + return; +} + +/* + * FIXME + * subfs_umount has no business here + * it should be moved into umount_begin; in this case force umount will + * free subfs (and give false No medium as error ... oh, well) + */ +static void +supermount_put_super(struct super_block *sb) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + + ENTER(sb); + + subfs_lock(sb); + if (subfs_is_mounted(sb)) + subfs_umount(sb, SUBFS_UMNT_NORMAL); + subfs_unlock(sb); + + supermount_proc_remove(sbi); + + LEAVE(sb); + + sb->s_fs_info = 0; + free_sbi(sbi); +} + +/* + * The following has to be done in two steps (unfortunately) + * First make sure iput(subi) is run just once - and then put + * read/write access and finally remove inode from list + * + * We can't do it in one shot because removing inode from list + * will remove also possibility to mark it stale. It has been + * introduced by me :( removing generation numbers. OTOH so far + * it is the only place that needs extra treatment because of it. + */ +static void +supermount_clear_inode(struct inode *inode) +{ + struct supermount_inode_info *sii = supermount_i(inode); + struct super_block *sb = inode->i_sb; + struct inode *subi; + struct vfsmount *mnt; + + ENTER(sb, "inode=%p", inode); + + mnt = subfs_prevent_umount(sb); + if (!mnt) + goto out; + + subfs_lock(sb); + subi = sii->inode; + sii->inode = 0; + subfs_unlock(sb); + + + if (subi) { + /* + * we used to check for subi->i_count != 1 here. + * This does not actually work - it is quite possible + * that inode has been looked up while we were in this + * function. So just ignore it (the worst thing that + * may happen is that subfs cannot be remounted) + */ + + iput(subi); + + } + subfs_allow_umount(sb, mnt); + + /* + * This does not has much to do with subfs but I did not want + * to export __ functions + */ + subfs_clear_inode(inode); +out: + LEAVE(sb, "inode=%p", inode); +} +/* + * FIXME why it is needed? + */ + +static void +supermount_write_super(struct super_block *sb) +{ + struct vfsmount *mnt; + + ENTER(sb); + + if (subfs_check_disk_change(sb)) + goto out; + + if (subfs_is_rw(sb)) { + struct super_block *subsb; + + mnt = subfs_prevent_umount(sb); + if (!mnt) + goto out; + + subsb = subfs_get_sb(sb); + if (subsb && subsb->s_op && subsb->s_op->write_super) + subsb->s_op->write_super(subsb); + + subfs_allow_umount(sb, mnt); + } + +out: + LEAVE(sb); + + return; +} + +static int +supermount_statfs(struct super_block *sb, struct kstatfs *buf) +{ + int rc = 0; + struct super_block *subsb; + struct vfsmount *mnt; + + ENTER(sb); + + (void)subfs_check_disk_change(sb); + + mnt = subfs_prevent_umount(sb); + if (!mnt) + goto out; + + subsb = subfs_get_sb(sb); + if (subsb && subsb->s_op && subsb->s_op->statfs) + rc = subsb->s_op->statfs(subsb, buf); + if (!rc) + buf->f_type = SUPERMOUNT_SUPER_MAGIC; + + subfs_allow_umount(sb, mnt); +out: + LEAVE(sb, "rc=%d", rc); + + return rc; +} + +static int +supermount_remount_fs(struct super_block *sb, int *flags, char *data) +{ + return -ENOSYS; +} + +/* + * based on fs/ntfs/inode.c:ntfs_show_options + */ +static int +supermount_show_options(struct seq_file *sf, struct vfsmount *mnt) +{ + struct supermount_sb_info *sbi = supermount_sbi(mnt->mnt_sb); + + seq_printf(sf, ",dev=%s", sbi->devname); + seq_puts(sf, ",fs="); + if (sbi->s_type == default_fs_types) + seq_puts(sf, "auto"); + else + seq_puts(sf, sbi->s_type); + seq_puts(sf, ",tray_lock="); + if (sbi->tray_lock == TRAY_LOCK_ALWAYS) + seq_puts(sf, "always"); + else if (sbi->tray_lock == TRAY_LOCK_NEVER) + seq_puts(sf, "never"); + else if (sbi->tray_lock == TRAY_LOCK_ONWRITE) + seq_puts(sf, "onwrite"); + else + SUPERMOUNT_BUG_ON(1); + + if (sbi->s_debug) + seq_printf(sf, ",debug=0x%lx", sbi->s_debug); + if (sbi->s_data) + seq_printf(sf, ",--,%s", sbi->s_data); + return 0; +} + +/* + * ->read_inode: not needed + * ->dirty_inode: probably not needed. It appears VFS layer never + * calls mark_inode_dirty itself; exception is + * UPDATE_ATIME and it is already handled specially + * FIXME + * It may be needed for knfsd and similar + * ->put_inode: not needed + * ->delete_inode: not needed + * ->write_super: FIXME not needed, we do not have any backing store + * ->sync_fs: not needed + * ->write_super_lockfs:not needed + * ->unlockfs: not needed + * ->umount_begin: TODO maybe to move subfs_umount aways from put_super + * ->fh_to_dentry: TODO may be + * ->dentry_to_fh: TODO may be + */ +static struct super_operations supermount_sops = { + .alloc_inode = supermount_alloc_inode, + .destroy_inode = supermount_destroy_inode, + .write_inode = supermount_write_inode, + .clear_inode = supermount_clear_inode, + .put_super = supermount_put_super, + .write_super = supermount_write_super, + .statfs = supermount_statfs, + .remount_fs = supermount_remount_fs, + .show_options = supermount_show_options, +}; diff -Nur linux-2.6.8.1_supermount/fs/supermount/supermount.h linux-2.6.8.1/fs/supermount/supermount.h --- linux-2.6.8.1_supermount/fs/supermount/supermount.h 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.8.1/fs/supermount/supermount.h 2004-08-17 03:16:01.000000000 +0200 @@ -0,0 +1,497 @@ +#ifndef _SUPERMOUNT_I_H +#define _SUPERMOUNT_I_H + +/* + * $Id: 4715_supermount-2.0.4-2.6.7.patch,v 1.1 2004/06/22 22:15:04 gregkh Exp $ + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SUPERMOUNT_DEBUG +#define SUPERMOUNT_DEBUG +#endif +/* + * The supermount superblock magic number + */ + +#define SUPERMOUNT_SUPER_MAGIC 0x9fa1 +#define SUPERMOUNT_VERSION "2.0.4" + +#define S_DBG_DEBUG 0x001 +#define S_DBG_TRACE_DENTRY 0x002 +#define S_DBG_TRACE_FILE 0x004 +#define S_DBG_TRACE_FILEMAP 0x008 +#define S_DBG_TRACE_MEDIACTL 0x010 +#define S_DBG_TRACE_NAMEI 0x020 +#define S_DBG_TRACE_SUBFS 0x040 +#define S_DBG_TRACE_SUPER 0x080 + +/* + * The subfs umount reason + */ +#define SUBFS_UMNT_NORMAL 0 /* normal umount */ +#define SUBFS_UMNT_MEDIA 1 /* media change detected */ +#define SUBFS_UMNT_USER 2 /* user request */ + +/* + * When to lock media + */ +#define TRAY_LOCK_NEVER 0 +#define TRAY_LOCK_ONWRITE 1 +#define TRAY_LOCK_ALWAYS 2 + +extern struct file_system_type supermount_fs_type; + +#ifdef SUPERMOUNT_DEBUG + +#define SUPERMOUNT_BUG_ON(x) BUG_ON(x) +#define SUPERMOUNT_BUG_LOCKED_ON(sb, x) \ +do { \ + if (x) { \ + subfs_unlock(sb); \ + BUG(); \ + } \ +} while (0) + +#define supermount_debug(sb, args...) \ +do { \ + struct supermount_sb_info *sbi = supermount_sbi(sb); \ + char *dev = sbi->devname ? sbi->devname : "unknown"; \ +\ + if (supermount_dbg(sb, S_DBG_DEBUG)) { \ + printk("%sSUPERMOUNT DEBUG [dev=%s] <%s:%d> ", \ + KERN_DEBUG, dev, __FUNCTION__, __LINE__); \ + printk(args); \ + printk("\n"); \ + } \ +} while(0) + +#define ENTER(sb, args...) \ +do { \ + struct supermount_sb_info *sbi = supermount_sbi(sb); \ + char *dev = sbi->devname ? sbi->devname : "unknown"; \ +\ + if (supermount_dbg(sb, S_DBG_TRACE_CURRENT)) { \ + printk("%sSUPERMOUNT TRACE [dev=%s] PID=%lu ENTER %s", \ + KERN_DEBUG, dev, (unsigned long)current->pid, \ + __FUNCTION__); \ + printk(" " args); \ + printk("\n"); \ + } \ +} while (0) + +#define LEAVE(sb, args...) \ +do { \ + struct supermount_sb_info *sbi = supermount_sbi(sb); \ + char *dev = sbi->devname ? sbi->devname : "unknown"; \ +\ + if (supermount_dbg(sb, S_DBG_TRACE_CURRENT)) { \ + printk("%sSUPERMOUNT TRACE [dev=%s] PID=%lu LEAVE %s", \ + KERN_DEBUG, dev, (unsigned long)current->pid, \ + __FUNCTION__); \ + printk(" " args); \ + printk("\n"); \ + } \ +} while (0) + +#else /* SUPERMOUNT_DEBUG */ +#define supermount_debug(f, a...) /**/ +#define SUPERMOUNT_BUG_ON(x) do { } while (0) +#define SUPERMOUNT_BUG_LOCKED_ON(sb, x) do { } while (0) + +#define ENTER(sb, args...) do { (void)sb; } while (0) +#define LEAVE(sb, args...) do { (void)sb; } while (0) +#endif /* SUPERMOUNT_DEBUG */ + +#define supermount_warning(sb, ...) \ +do { \ + struct supermount_sb_info *sbi = supermount_sbi(sb); \ + char *dev = sbi->devname ? sbi->devname : "unknown"; \ +\ + printk("%sSUPERMOUNT WARNING [dev=%s] ", KERN_WARNING, dev); \ + printk(__VA_ARGS__); \ + printk("\n"); \ +} while(0) + +#define supermount_error(sb, ...) \ +do { \ + struct supermount_sb_info *sbi = supermount_sbi(sb); \ + char *dev = sbi->devname ? sbi->devname : "unknown"; \ +\ + printk("%sSUPERMOUNT ERROR [dev=%s] ", KERN_ERR, dev); \ + printk(__VA_ARGS__); \ + printk("\n"); \ +} while(0) + +/* + * The following is drived from fs/inode.c:update_atime() + */ +#define NEED_WRITE_ATIME(inode) \ + (!(IS_NOATIME(inode) || \ + (IS_NODIRATIME(inode) && S_ISDIR(inode->i_mode)) || \ + IS_RDONLY(inode))) + + +/* + * supermount super-block data in memory + */ +struct supermount_sb_info; +struct supermount_sb_info { + /* == options == */ + unsigned long s_debug; /* debug flags S_DBG_* */ + char *s_type; /* Type of fs to be sub-mounted */ + char *devname; /* Where to mount the subfs from */ + char *s_data; /* Data to pass when mounting subfs */ + /* == end of options == */ + struct vfsmount *s_undermount; + /* Mount point for subfs */ + int readcount; /* Refcount of read access + on the filesystem */ + int writecount; /* Refcount of write access + on the filesystem */ + int lockcount; /* Refcount of requests to lock tray */ + struct list_head s_inodes; + /* list of active inodes */ + struct list_head s_files; + /* list of active files */ + struct list_head s_dentries; + /* list of active dentries */ + struct semaphore sem; + struct super_block *host; + /* needed for procfs support */ + struct supermount_sb_info *next; + /* list of all supermount fs */ + unsigned int tray_lock:2; + unsigned int disabled:1; + unsigned int rw:1; +}; + +static inline struct supermount_sb_info * +supermount_sbi(struct super_block *sb) +{ + struct supermount_sb_info *sbi; + BUG_ON(!sb); + BUG_ON(sb->s_type != &supermount_fs_type); + + sbi = (struct supermount_sb_info *) (sb->s_fs_info); + BUG_ON(!sbi); + + return sbi; +} + +static inline struct vfsmount * +subfs_mnt(struct super_block *sb) +{ + return supermount_sbi(sb)->s_undermount; +} + +static inline struct super_block * +subfs_sb(struct super_block *sb) +{ + struct vfsmount *mnt = subfs_mnt(sb); + + if (mnt) return mnt->mnt_sb; + + return 0; +} + +/* this is expected to run under sb->sem */ +static inline int +subfs_is_mounted(struct super_block *sb) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + + return sbi->s_undermount != 0; +} + +/* this is expected to run under sb->sem */ +static inline int +subfs_is_busy(struct super_block *sb) +{ + struct vfsmount *mnt = subfs_mnt(sb); + + /* + * In "normal" case mnt_count is 2. But we currently do + * not insert subfs into task namespace so count is 1 + * FIXME + * This also means we do not do_umount i.e. do not run + * either umount_begin or DQUOT_OFF for subfs. + */ + if (mnt) return atomic_read(&mnt->mnt_count) > 1; + + return 0; +} + +/* this is expected to run under sb->sem */ +static inline int +subfs_is_rw(struct super_block *sb) +{ + struct super_block *subsb; + + if (!subfs_is_mounted(sb)) + return 0; + + subsb = subfs_sb(sb); + return !(subsb->s_flags & MS_RDONLY); +} + +static inline void +subfs_lock(struct super_block *sb) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + + down(&sbi->sem); +} + +static inline void +subfs_unlock(struct super_block *sb) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + + up(&sbi->sem); +} + +/* + * query debug flags set + */ +static inline int supermount_dbg(struct super_block *sb, unsigned long flags) +{ + struct supermount_sb_info *sbi = supermount_sbi(sb); + + return sbi->s_debug & flags; +} + +/* + * supermount inode info + */ + +struct supermount_inode_info { + struct list_head list; + struct inode *inode; /* subfs inode */ + int readcount; + int writecount; + struct inode vfs_inode; /* superfs inode */ +}; + +static inline int +is_inode_supermounted(struct inode *inode) +{ + return inode && inode->i_sb && inode->i_sb->s_type == &supermount_fs_type; +} + +static inline struct supermount_inode_info * +supermount_i(struct inode *inode) +{ + SUPERMOUNT_BUG_ON(!is_inode_supermounted(inode)); + + return list_entry(inode, struct supermount_inode_info, vfs_inode); +} + +static inline int +is_inode_obsolete(struct inode *inode) +{ + struct supermount_inode_info *sii = supermount_i(inode); + + return sii->inode == 0; +} + +static inline void +supermount_list_add_inode(struct inode *inode) +{ + struct supermount_inode_info *sii = supermount_i(inode); + + list_add(&(sii->list), &(supermount_sbi(inode->i_sb)->s_inodes)); +} + +/* + * FIXME + * Should we propagate all flags? *_QUOTA looks very possible candidate + * We can't just assign them because other flags may be set by VFS + */ +#define SMNT_INODE_FLAGS (S_IMMUTABLE|S_NOATIME|S_APPEND|S_SYNC) +static inline void +set_inode_flags(struct inode *inode, struct inode *subi) +{ + inode->i_flags &= ~SMNT_INODE_FLAGS; + if (subi) + inode->i_flags |= (subi->i_flags & SMNT_INODE_FLAGS); +} + +/* + * supermount dentry info + */ + +struct supermount_dentry_info { + struct list_head list; + struct dentry *dentry; + struct dentry *host; +}; + +static inline int +is_dentry_supermounted(struct dentry *dentry) +{ + return (dentry && dentry->d_sb && dentry->d_sb->s_type == &supermount_fs_type); +} + +static inline struct supermount_dentry_info * +supermount_d(struct dentry *dentry) +{ + SUPERMOUNT_BUG_ON(!is_dentry_supermounted(dentry)); + SUPERMOUNT_BUG_ON(!dentry->d_fsdata); + + return (struct supermount_dentry_info *)dentry->d_fsdata; +} + +static inline int +is_dentry_obsolete(struct dentry *dentry) +{ + struct supermount_dentry_info *sdi = supermount_d(dentry); + + return sdi->dentry == 0; +} + +/* + * Supermount file info + */ + +struct supermount_file_info { + struct list_head list; + struct file * host; + struct file *file; + pid_t owner; + struct vm_operations_struct *vm_ops; + unsigned int fake:1; +}; + +static inline int +is_file_supermounted(struct file *file) +{ + return file && file->f_dentry && is_dentry_supermounted(file->f_dentry); +} + +static inline struct supermount_file_info * +supermount_f(struct file *file) +{ + SUPERMOUNT_BUG_ON(!is_file_supermounted(file)); + SUPERMOUNT_BUG_ON(!file->f_supermount); + + return file->f_supermount; +} + +static inline int +is_file_obsolete(struct file *file) +{ + struct supermount_file_info *sfi = supermount_f(file); + + return sfi->file == NULL; +} + +static inline int +is_file_fake(struct file *file) +{ + struct supermount_file_info *sfi = supermount_f(file); + + return sfi->fake; +} + +/* dentry.c */ +extern rwlock_t d_compare_lock; +extern struct dentry_operations supermount_dops; +extern int init_dentry_info(struct dentry *); +extern void attach_subfs_dentry(struct dentry *, struct dentry *); +extern struct dentry *get_subfs_dentry(struct dentry *dentry); + +/* file.c */ +extern struct file_operations supermount_dir_operations; +extern struct file_operations supermount_file_operations; + +/* filemap.c */ +extern struct vm_operations_struct supermount_vm_ops; +extern int supermount_file_mmap(struct file *, struct vm_area_struct *); +extern struct file *get_subfs_file(struct file*); + +/* mediactl.c */ +extern void supermount_mediactl(struct super_block *, int, int); +static inline void +supermount_lock_door(struct super_block *sb) +{ + supermount_mediactl(sb, MEDIA_LOCK, 1); +} +static inline void +supermount_unlock_door(struct super_block *sb) +{ + supermount_mediactl(sb, MEDIA_UNLOCK, 1); +} + +static inline void +mark_media_supermounted(struct super_block *sb) +{ + supermount_mediactl(sb, SUPERMOUNT_INC_COUNT, 0); + supermount_mediactl(sb, MEDIA_UNLOCK, 0); +} + +static inline void +unmark_media_supermounted(struct super_block *sb) +{ + supermount_mediactl(sb, SUPERMOUNT_DEC_COUNT, 0); +} + +/* namei.c */ +extern struct inode_operations supermount_dir_iops; +extern struct inode_operations supermount_file_iops; +extern struct inode_operations supermount_symlink_iops; +extern struct inode *create_supermount_inode(struct super_block *); +extern struct inode *get_subfs_inode(struct inode *inode); + +/* proc.c */ +#ifdef CONFIG_PROC_FS +extern void supermount_proc_register(void); +extern void supermount_proc_unregister(void); +extern void supermount_proc_insert(struct supermount_sb_info *); +extern void supermount_proc_remove(struct supermount_sb_info *); +#else +#define supermount_proc_register() do { } while(0) +#define supermount_proc_unregister() do { } while(0) +#define supermount_proc_insert(sbi) do { } while(0) +#define supermount_proc_remove(sbi) do { } while(0) +#endif + +/* subfs.c */ +extern void subfs_umount(struct super_block *sb, int); +extern struct vfsmount *subfs_go_online(struct super_block *); +extern void subfs_go_offline(struct super_block *, struct vfsmount *); +extern int subfs_get_access(struct inode *, int); +extern void subfs_put_access(struct inode *, int); +extern int subfs_check_disk_change(struct super_block *); +extern struct vfsmount *subfs_prevent_umount(struct super_block *); +extern void subfs_allow_umount(struct super_block *, struct vfsmount *); +extern struct vfsmount *subfs_get_mnt(struct super_block *sb); +extern struct super_block *subfs_get_sb(struct super_block *sb); +extern void subfs_clear_inode(struct inode *); + +/* super.c */ +extern struct super_block *supermount_get_sb(struct file_system_type *, int, const char *, void *); +extern void attach_subfs_inode(struct inode *, struct inode *); +extern void supermount_init_root_inode(struct inode *); +extern char *strdup(const char *); + +#endif /* _SUPERMOUNT_I_H */ diff -Nur linux-2.6.8.1_supermount/fs/udf/super.c linux-2.6.8.1/fs/udf/super.c --- linux-2.6.8.1_supermount/fs/udf/super.c 2004-08-14 12:55:35.000000000 +0200 +++ linux-2.6.8.1/fs/udf/super.c 2004-08-17 03:16:01.000000000 +0200 @@ -278,7 +278,7 @@ Opt_gid, Opt_uid, Opt_umask, Opt_session, Opt_lastblock, Opt_anchor, Opt_volume, Opt_partition, Opt_fileset, Opt_rootdir, Opt_utf8, Opt_iocharset, - Opt_err + Opt_ignore, Opt_err }; static match_table_t tokens = { @@ -303,6 +303,10 @@ {Opt_rootdir, "rootdir=%u"}, {Opt_utf8, "utf8"}, {Opt_iocharset, "iocharset=%s"}, +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + /* Silently ignore NLS option */ + {Opt_ignore, "codepage"}, +#endif {Opt_err, NULL} }; @@ -423,6 +427,8 @@ uopt->flags |= (1 << UDF_FLAG_NLS_MAP); break; #endif + case Opt_ignore: + break; default: printk(KERN_ERR "udf: bad mount option \"%s\" " "or missing value\n", p); diff -Nur linux-2.6.8.1_supermount/include/linux/cdrom.h linux-2.6.8.1/include/linux/cdrom.h --- linux-2.6.8.1_supermount/include/linux/cdrom.h 2004-08-14 12:54:47.000000000 +0200 +++ linux-2.6.8.1/include/linux/cdrom.h 2004-08-17 03:16:01.000000000 +0200 @@ -987,6 +987,9 @@ extern int cdrom_ioctl(struct file *file, struct cdrom_device_info *cdi, struct inode *ip, unsigned int cmd, unsigned long arg); extern int cdrom_media_changed(struct cdrom_device_info *); +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) +extern int cdrom_mediactl(struct cdrom_device_info *, struct block_device *, int, int); +#endif extern int register_cdrom(struct cdrom_device_info *cdi); extern int unregister_cdrom(struct cdrom_device_info *cdi); diff -Nur linux-2.6.8.1_supermount/include/linux/fs.h linux-2.6.8.1/include/linux/fs.h --- linux-2.6.8.1_supermount/include/linux/fs.h 2004-08-14 12:55:09.000000000 +0200 +++ linux-2.6.8.1/include/linux/fs.h 2004-08-17 03:16:01.000000000 +0200 @@ -96,6 +96,7 @@ /* public flags for file_system_type */ #define FS_REQUIRES_DEV 1 #define FS_BINARY_MOUNTDATA 2 +#define FS_NO_SUBMNT 64 /* Prevent mounting over this filesystem */ #define FS_REVAL_DOT 16384 /* Check the paths ".", ".." for staleness */ #define FS_ODD_RENAME 32768 /* Temporary stuff; will go away as soon * as nfs_rename() will be cleaned up @@ -119,6 +120,7 @@ #define MS_VERBOSE 32768 #define MS_POSIXACL (1<<16) /* VFS does not apply the umask */ #define MS_ONE_SECOND (1<<17) /* fs has 1 sec a/m/ctime resolution */ +#define MS_SUPERMOUNTED (1<<18) #define MS_ACTIVE (1<<30) #define MS_NOUSER (1<<31) @@ -587,6 +589,11 @@ spinlock_t f_ep_lock; #endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; + +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + /* Used by supermount. Use(fullness) unconfirmed */ + void *f_supermount; +#endif }; extern spinlock_t files_lock; #define file_list_lock() spin_lock(&files_lock); @@ -771,6 +778,10 @@ * even looking at it. You had been warned. */ struct semaphore s_vfs_rename_sem; /* Kludge */ +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + atomic_t s_media_changed; +#endif + }; /* @@ -840,6 +851,9 @@ int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long); int (*media_changed) (struct gendisk *); int (*revalidate_disk) (struct gendisk *); +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + int (*mediactl) (struct block_device *, int, int); +#endif struct module *owner; }; diff -Nur linux-2.6.8.1_supermount/include/linux/genhd.h linux-2.6.8.1/include/linux/genhd.h --- linux-2.6.8.1_supermount/include/linux/genhd.h 2004-08-14 12:54:50.000000000 +0200 +++ linux-2.6.8.1/include/linux/genhd.h 2004-08-17 03:16:01.000000000 +0200 @@ -108,6 +108,9 @@ #else struct disk_stats dkstats; #endif +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + int scount; /* number of supermounted partitions */ +#endif }; /* diff -Nur linux-2.6.8.1_supermount/include/linux/supermount_media.h linux-2.6.8.1/include/linux/supermount_media.h --- linux-2.6.8.1_supermount/include/linux/supermount_media.h 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.8.1/include/linux/supermount_media.h 2004-08-17 03:16:01.000000000 +0200 @@ -0,0 +1,47 @@ +#if defined(CONFIG_SUPERMOUNT) || defined(CONFIG_SUPERMOUNT_MODULE) + +/* + * These are the "op" operation codes for mediactl() media control + * calls for device special files + */ +enum { + MEDIA_LOCK = 1234, /* Lock the drive door */ + MEDIA_UNLOCK, /* Unlock the drive door */ + SUPERMOUNT_INC_COUNT, + SUPERMOUNT_DEC_COUNT, +}; + +static inline int +supermount_usage_count(struct block_device *bdev, int count) +{ + if (bdev) { + count -= bdev->bd_disk->scount; + if (count < 0) + count = 0; + } + return count; +} + +static inline int +dev_is_supermounted(struct block_device *bdev) +{ + return bdev ? bdev->bd_disk->scount > 0 : 0; +} + +#else /* CONFIG_SUPERMOUNT */ + +static inline int +supermount_usage_count(struct block_device *bdev, int count) +{ + (void)bdev; // make compiler happy + return count; +} + +static inline int +dev_is_supermounted(struct block_device *bdev) +{ + (void)bdev; // make compiler happy + return 0; +} + +#endif /* CONFIG_SUPERMOUNT */