lkml.org 
[lkml]   [2000]   [Jan]   [16]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
Date
From
SubjectRFC & PATCH: initrd's change_root exorcised
While thinking about ways to generalize change_root, it finally occurred to
me that all of the "magic" is already in chroot(). The only difficulty then
is to get rid of the old, unused FS.

I've attached a patch (for 2.3.38) that does this, plus a description to
show how it's used. The main limitation vs. the currrent approach is that
you can't just leave processes running somewhere in the old root and still
peek at their files. Not sure if anybody actually used that property.
(To let them run until they complete, and then unmount the old FS is okay,
though.) Added bonus: you can change root as many times as you wish.

There are still some issues with /proc/mounts, because hopping from one
root to the next obviously wrecks its semantics. I'm discussing this in
the description.

Comments welcome.

- Werner

--
_________________________________________________________________________
/ Werner Almesberger, ICA, EPFL, CH werner.almesberger@ica.epfl.ch /
/_IN_N_032__Tel_+41_21_693_6621__Fax_+41_21_693_6610_____________________/
What does it change ?
---------------------

- /proc/mounts shows mount points relative to the root of the current process
- umount <block_device> unmounts the block device if it is "busy" but all
references are mount points (in particular, root and pwd of all processes
are on other file systems)


Example
-------

My / is on /dev/hda1. /var is on a separate partition /dev/hda5.

1) apply the root-umount patch and build the kernel
2) make sure there's a chroot in /sbin:
[ -x /sbin/chroot ] || cp /usr/sbin/chroot /sbin
3) create a minimum system environment on a non-root partition, e.g.
mkdir /var/test
(cd / && tar cfl - bin lib sbin etc dev) | (cd /var/test && tar xf -)
4) reboot with init=/bin/sh
5) make the test environment your new root (assume /var has its own partition):
mount /var
exec /sbin/chroot /var/test /bin/sh \
</var/test/dev/console >/var/test/dev/console 2>&1
6) get rid of the old root (assume it's on /dev/hda1):
umount /dev/hda1
7) for kicks, return to the old root FS (assume /var is on /dev/hda5):
mkdir /mnt
mount /dev/hda1 /mnt
exec /sbin/chroot /mnt /bin/sh </mnt/dev/console >/mnt/dev/console 2>&1
umount /dev/hda5
8) bring up the system:
mount -o remount,ro /
exec /sbin/init


Known problems and anomalies
----------------------------

- /proc/mounts is inconsistent for mount points which are not underneath the
root of the current process (NB: such mount points should typically be
transient in regular operation, so this problem will mainly confuse people
building boot environments). Observe how /proc/mounts is changed by each
chroot and umount in the example above.
- if new root is not top-level of file system, /proc/mounts will first show the
old path, then, after unmounting the old root, / as mount point of this file
system (this is a common case of the problem above)
- if multiple file systems were mounted on old root, each of them becomes a
potential root. This can be considered to be a feature ;-)
- if anonymous block devices are detached this way, it can be difficult to
unmount them (work-around: unmout everything that's not needed while the old
root is still accessible)
- umount <old_root_dev> is overloaded. Therefore, if the umount fails for some
reason, it will still appear to succeed, but actually just set the old root
read-only (do we need a new mount option ?)
- the content of ROOT_DEV is meaningless after a root directory change
- disk quota support also uses mnt_dirname and still prints the original path
names (see below)
- changing the root confuses the RH 6.1 halt script, causing it to hang. Need
to examine this in detail.


Discussion of problems
----------------------

The main problem is certainly tmp_dirname, which shows up in /proc/mounts and
in disk quota messages. The semantics of a mount point's path name are
inherently ambiguous from the perspective of a process that uses a different
root than the one under which the mount point was made. This ambiguity is
aggravated if the mount point is not below the process' root. Things get even
worse if already the normal system environment is chrooted (which this
extension encourages to do).

One problem of using d_path to construct path names with respect to the
current root is that mount points outside the current root yield paths that
are not only meaningless, but that may also change as a side-effect of mount
operations.

An entirely correct solution would be to include the specification of the
current root at mount time in the mount information. This is of course
overkill, and not really useful anyway. Another possibility would be to
indicate that the path is not valid for the current process, e.g. by
putting the name in parentheses or by prefixing it with an asterisk, a
question mark, or an additional slash. This needs input from people with
programs using /proc/mounts. Also, d_path would have to be extended to
provide the necessary information.

The second major problem are "lost" unnamed block devices. In principle,
all such problems are caused by user errors. Considering that interactive
use of this facility will be rare, we may just decide to live with this
problem. Furthermore, recovery is possible by trying to manually construct
the right device numbers. A clean solution would involve exposing the
minor numbers assigned by the kernel. E.g. /proc/mounts could use
kdevname instead of mnt_devname in such cases. Again, the feasibility of
this depends on what users of /proc/mounts expect.

Concerning the overloading of umount <root_dev>: since mount will try to
translate the device name to the mount point, the new behaviour is only
selected if the root FS is already inaccessible. Therefore, the only
ambiguity occurs when the new behaviour is desired, but due to some
mistake the umount fails and (if the file system happens to be the old
root) it silently turned into a remount,ro. A mount option to specificly
indicate that we intend to unmount an inaccessible file system may be
cleaner, but I'm not sure if it's worth the effort.
--- linux/fs/dcache.c.orig Sun Jan 16 00:41:21 2000
+++ linux/fs/dcache.c Sun Jan 16 01:17:39 2000
@@ -302,7 +302,7 @@
* child dentries were freed. This allows a non-destructive
* test for unmounting a device.
*/
-int is_root_busy(struct dentry *root)
+int __is_root_busy(struct dentry *root,int ignore_mount_points)
{
struct dentry *this_parent = root;
struct list_head *next;
@@ -314,15 +314,20 @@
while (next != &this_parent->d_subdirs) {
struct list_head *tmp = next;
struct dentry *dentry = list_entry(tmp, struct dentry, d_child);
+ int effective_count;
next = tmp->next;
+ /* Mount points may not count */
+ effective_count = dentry->d_count;
+ if (ignore_mount_points && dentry->d_mounts != dentry)
+ effective_count--;
/* Decrement count for unused children */
- count += (dentry->d_count - 1);
+ count += (effective_count - 1);
if (!list_empty(&dentry->d_subdirs)) {
this_parent = dentry;
goto repeat;
}
/* root is busy if any leaf is busy */
- if (dentry->d_count)
+ if (effective_count)
return 1;
}
/*
@@ -334,6 +339,11 @@
goto resume;
}
return (count > 1); /* remaining users? */
+}
+
+int is_root_busy(struct dentry *root)
+{
+ return __is_root_busy(root,0);
}

/*
--- linux/fs/super.c.orig Sat Jan 15 12:53:27 2000
+++ linux/fs/super.c Sun Jan 16 01:37:10 2000
@@ -307,11 +307,15 @@
struct proc_nfs_info *nfs_infop;
struct nfs_server *nfss;
int len = 0;
+ char *path,*buffer = (char *) __get_free_page(GFP_KERNEL);

+ if (!buffer) return 0;
while ( tmp && len < PAGE_SIZE - 160)
{
+ path = d_path(tmp->mnt_sb->s_root,buffer,PAGE_SIZE);
len += sprintf( buf + len, "%s %s %s %s",
- tmp->mnt_devname, tmp->mnt_dirname, tmp->mnt_sb->s_type->name,
+ tmp->mnt_devname, path,
+ tmp->mnt_sb->s_type->name,
tmp->mnt_flags & MS_RDONLY ? "ro" : "rw" );
for (fs_infop = fs_info; fs_infop->flag; fs_infop++) {
if (tmp->mnt_flags & fs_infop->flag) {
@@ -368,6 +372,7 @@
tmp = tmp->mnt_next;
}

+ free_page((unsigned long) buffer);
return len;
}

@@ -467,6 +472,34 @@
return NULL;
}

+static void detach_mount_points(struct super_block *sb)
+{
+ struct super_block *s;
+
+ for (s = sb_entry(super_blocks.next); s != sb_entry(&super_blocks);
+ s = sb_entry(s->s_list.next)) {
+ struct dentry *root,*covered;
+
+ if (!s->s_dev || s == sb) continue;
+ root = s->s_root;
+ covered = root->d_covers;
+ if (covered->d_sb != sb) continue;
+ if (covered->d_inode->i_count != 1) {
+ printk(KERN_CRIT "detach_mount_points: inode %ld on %s "
+ "count %d (must be 1)\n",covered->d_inode->i_ino,
+ kdevname(sb->s_dev),covered->d_inode->i_count);
+ continue;
+ }
+ covered->d_mounts = covered; /* l'art pour l'art ... */
+ dput(covered);
+ root->d_covers = root;
+ printk(KERN_DEBUG "detached %s (count %d)",kdevname(s->s_dev),
+ root->d_count);
+ printk(" from %s (count %d)\n",kdevname(sb->s_dev),
+ sb->s_root->d_count);
+ }
+}
+
asmlinkage long sys_ustat(dev_t dev, struct ustat * ubuf)
{
struct super_block *s;
@@ -612,8 +645,15 @@
struct dentry * root = sb->s_root;
struct dentry * covered = root->d_covers;

- if (root->d_count != 1)
- return -EBUSY;
+ printk(KERN_DEBUG "d_umount: ref %d\n",root->d_count);
+ if (root->d_count != 1) {
+ if (__is_root_busy(sb->s_root,1)) return -EBUSY;
+ else {
+ detach_mount_points(sb);
+ shrink_dcache_sb(sb);
+ fsync_dev(sb->s_dev);
+ }
+ }

if (root->d_inode->i_state)
return -EBUSY;
@@ -681,7 +721,8 @@
shrink_dcache_sb(sb);
fsync_dev(dev);

- if (dev==ROOT_DEV && !unmount_root) {
+ printk(KERN_DEBUG "do_umount: ref %d\n",sb->s_root->d_count);
+ if (dev==ROOT_DEV && !unmount_root && __is_root_busy(sb->s_root,1)) {
/*
* Special case for "unmounting" root ...
* we just try to remount it readonly.
@@ -1273,7 +1314,7 @@
}
return 0;
}
- printk(KERN_ERR "error %d\n",PTR_ERR(bdev));
+ printk(KERN_ERR "error %ld\n",PTR_ERR(bdev));
return error;
}
remove_vfsmnt(old_root_dev);
--- linux/include/linux/dcache.h.orig Fri Jan 7 01:21:23 2000
+++ linux/include/linux/dcache.h Sun Jan 16 01:17:16 2000
@@ -150,6 +150,7 @@

/* test whether root is busy without destroying dcache */
extern int is_root_busy(struct dentry *);
+extern int __is_root_busy(struct dentry *,int ignore_mount_points);

/*
* This adds the entry to the hash queues.
\
 
 \ /
  Last update: 2005-03-22 13:55    [W:0.075 / U:0.224 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site