lkml.org 
[lkml]   [2003]   [Aug]   [4]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
/
SubjectRe: RO --bind mount implementation ...
From
Date
Herbert Pötzl <herbert@13thfloor.at> writes:

> A few days ago, I asked on lkml if mount options like RO would be
> useful for --bind mounts, and Christoph was so nice to refer me to a
> 'n step' plan posted by Al 'a while ago', which I could not find 8-)
> ...

-------------------- Start of forwarded message --------------------
Date: Thu, 29 May 2003 09:21:27 +0100
From: viro@parcelfarce.linux.theplanet.co.uk
To: Shaya Potter <spotter@cs.columbia.edu>
Cc: linux-fsdevel@vger.kernel.org
Subject: [long] per-mountpoint readonly
Message-ID: <20030529082127.GL14138@parcelfarce.linux.theplanet.co.uk>

On Thu, May 29, 2003 at 12:20:23AM -0400, Shaya Potter wrote:
> On Wed, 2003-05-28 at 20:55, viro@parcelfarce.linux.theplanet.co.uk
> wrote:
> > So give it a _REAL_ namespace. And bind /dev/null over /bin/sh in it.
> > And revoke the capability to call mount(2) and umount(2). End of story.
>
> The issue w/ the fs namespaces the kernel supports (at least AFAI
> understand them) is that you can't change the underlying permission
> model.
>
> so for example, an "apache namespace" you might want it to only have
> read only access to everything besides a few files in /var/log. Can one
> do that w/ namespaces?


Now, _that_ would be a good thing to implement. To do it the right way
we need to make readonly status per-mountpoint. Note that we already have
that for e.g. nosuid and friends. If we get that done for readonly
you can
* in your namespace remount everything readonly
* mount --recbind /var/log /var/log
mount -o remount,rw /var/log
and enjoy.

The first question is obviously "could it be done at all?"

Situation with IS_RDONLY() is interesting. First of all, there are
uses of that beast in ->permission() instances. We definitely can
(and should) take them upstream. No matter how hard somebody might
wish to allow write access to e.g. regular file, if fs is readonly,
it's readonly. Period. No ACLs can override it.

IOW, that check (IS_RDONLY and (regular|directory|symlink) ==> EROFS)
should be moved into fs/namei.c::permission(). Ditto for checks in
xattr methods - they also go upstream.

ioctls (ext2, ext3, reiserfs) have struct file * and thus are not
a problem.

fs/open.c callers also have struct file * or struct nameidata. Not a
problem.

fat_truncate() - actually, both checks there are bogus; callers do
check for that stuff. Should be removed.

ncpfs uses should be replaced with update_atime() (and that will be
a huge can of worms). We have struct file * in them, anyway.

ntfs uses. These are actually about the state of filesystem itself -
they attempt to fill the missing permission bits.

arch/sparc64/solaris/fs.c: there we have vfsmount.

fs/namei.c::may_open(). After we make sure that permission() _always_
honors IS_RDONLY() (i.e. move the checks into beginning of the thing),
that particular check is not needed anymore.

vfs_permission() - check moves to permission(). NB: we need to verify
that it's only called by permission() and instances of ->permission().

nfsd_permission() - there we have the export, ergo the vfsmount.

presto_settime() - the check is bogus, we only call it from ->create(),
->unlink(), etc. and the upper layer logics takes care of these checks.

presto_do_setattr() - fsck knows, ask Peter. I suspect that callers
should do the check, if they are not doing it already.

ext3_change_inode_journal_flag() - called only from ioctl.


And that leaves us with two big ones. Namely, permission() and update_atime().
The above will take quite a few accurate steps, but it's doable and would
make sense regardless of the following. And yes, there's a lot of work on
accurate verification of correctness that I'd skipped - it has to be done
and there might be nasty surprises. However, assuming that it's done,
we are at the interesting point.

The thing is, we have no hope in hell to propagate vfsmount into all calls
of permission(). _HOWEVER_, we might not need to do that. Note that part
potentially interested in vfsmount is very small and specific. Again,
we are talking about
want write and is readonly and (file or directory or symlink).
Right now it needs only inode, but we want to make "is readonly" a function
of vfsmount. Let's start with taking that check out of permission() and
into its callers.

Note that callers in floppy_open() (the worst from the "where do
we get vfsmount/dentry" POV) do not give a damn - there the check is
always false, since we are talking about block device. So they are
not a problem.

Places where we got the inode from struct file * or struct nameidata
are also not a problem - there we have everything we might possibly ask for.
That kills a lot of permission() instances.

We also do not care for cases when we don't ask for write access
(obviously - there the check can be dropped).

Call in nfsd_permission() is not a problem, for the same reasons
as with explicit IS_RDONLY() there. We have export, ergo we have a vfsmount.

Call in presto_permission() is an utter bullshit - it's a hack of
rather bad taste and should have been replaced with call of vfs_permission()
(I mean, just look at it - Peter, please, LART whoever had done that).

Checks that come out of calls in ext2 and ext3 xattr stuff can be
dropped - after the changes above we would have them already done in callers.

Now we are down to 8 functions
may_create(), may_delete(), vfs_rename_dir()
their intermezzo copies
hpfs_unlink() and update_atime().

Let's take a look at update_atime(). We want to propagate struct vfsmount
and struct dentry into that guy. It's worth the effort for a lot of reasons,
not the least of them being that we will get per-mountpoint noatime and
nodiratime out that - immediately.

All callers that have inode obtained by struct file * are trivial. We have
what we need there. Now, a large group actually comes from the same kind
of place - it's ->readdir() in the majority of filesystems. We might be
better off just taking that to caller of ->readdir() - provided that NFS
folks are OK with that (nfs does _not_ update atime on client after
readdir()). In any case, we have struct file * there anyway.

nfsd_readlink() doesn't have struct file *, but it has export. Same as above.

sys_readlink() has nameidata.

open_namei() and do_follow_link() have pointers to relevant vfsmount (should
be done accurately - it's easy to get confused there).

autofs4 use - AFAICS there we want atime updated unconditionally, so calling
update_atime() (update atime after checking noatime/nodiratime/readonly flags)
is wrong.

That's it with update_atime() and by that time we get visible result - ability
to turn noatime and nodiratime on and off on a subtree basis.

Now, what's left is hpfs_unlink(), may_create()/may_delete()/vfs_rename_dir()
and intermezzo analogs of these 3. Let's see.

Check in hpfs_unlink() can be dropped. It should call permission(), all
right, but check for filesystem being writable for us is already done, so
we can skip it.

Situation with may_create()/may_delete() is more interesting. They are
called from vfs_{create,link,unlink,....} and there we do not have a
vfsmount. Let's start with taking the check ("fs is writable for us")
from may_...() into these functions. They are _always_ done on a single
fs, so even if we get several checks, they'll combine nicely. Moreover,
in all cases except vfs_rename() that check can be done as the very first
operation - if it fails we shoudl return -EROFS and do nothing.

vfs_rename() is almost OK - there we have a POSIX-mandated idiocy:
if (old_dentry->d_inode == new_dentry->d_inode)
return 0;
in the very beginning. The check can go immediately after that, but
we can't move it in the very beginning. IOW, there is a case when
rename() on a read-only filesystem is required to succeed - when
source and target are links to the same inode. In that case POSIX
requires to return 0 and do nothing.

Check that had migrated into vfs_rename_dir() can be dropped - we
do the same check in its caller earlier (it's called from vfs_rename()).
BTW, why the fsck vfs_rename_dir() is not static? We don't use it
anywhere else in the tree and it's not exported, so...

Now, we are left with that merry bunch - vfs_<operation>. Let's take
the POSIX idiocy to the callers of vfs_rename() and let's move the
check for fs being writable to us into callers of all these guys.

_NOW_ we are done. Indeed, there are two groups of callers (syscalls
and nfsd) and both know what we are dealing with. The former have
vfsmount, the latter have export.

Note that passing vfsmount into vfs_...() would be a Wrong Thing(tm) -
they are deliberately fs-local. I.e. they don't care where (and if)
fs is mounted - they operate below the mount tree level.

Intermezzo analogs are analogous ;-) We can deal with them in the
same manner.

OK, so far we have shown that per-mountpoint ro/noatime/nodiratime are
doable. However, the solution as described above is *NOT* going to
be accepted - it sprinkles a lot of crap all over the tree for no
visible reason. Let's take a look at the resulting picture and see
how it can be cleaned up.

What had actually changed? We used to have checks for "fs is writable"
on a fairly low level. The end result has their analogs on earlier
stages - at the point where we have decided that operation will happen
on *this* part of mount tree.

That has an obvious functionality problem - what happens if we decide that
fs is writable and remount it read-only between the check and actual work?
*However*, this is not a new issue. Current code has exact same problem.
We need to deal with it anyway.

Basically, what's wrong with both current and modified trees is that
we ask "can I write to that sucker now?" and consider the positive
answer as go-ahead.

Theoretically, it could even work - if remount would block until the
transient operation is done or say just say "busy" in all cases when
something is going on. We even do some checks of that sort. However,
they deal only with long-term stuff - files being opened for write or
unlinked files kept alive by being open. Transient stuff is ignored,
so we can very well get fs remounted r/o right after e.g. mkdir()
does all checks and decides to go ahead.

How to fix that? Well, the obvious way would be to bracket transient
stuff with WILL WRITE/WONT WRITE. With filesystem being able to say
DO WRITE/DONT WRITE, obviously ;-)

But look, we have already done half of that - we have the opening
brackets. And that's exactly what our shifted checks are - we are
asking for write access to filesystem, we call a function that assumes
such access and once it returns we are done with the thing. IOW,
adding the closing brackets is easy now.

And that's what we were missing in the above. Now we can get sane
semantics for all that stuff. Why would filesystem be read-only
or read-write? Well,
(1) it can be read-only because it refuses to be read-write, period.
E.g. no matter what you do, filesystem on a CD will *not* be writable. Ever.
Ditto for remote filesystem that is not exported read-write. Ditto for a
filesystem that doesn't know how to be writable.
(2) it can be read-only because nobody asked it to be read-write.
(3) it can be read-only because sysadmin *told* it to stop being
writable.

What happens to these cases if we want per-mountpoint writability?
(1) doesn't change at all
(2) becomes "none of the instances are asked to be writable"
(3) becomes independent from (2)
IOW, we need to distinguish between "make that instance read-only, stop
allowing writes to come via it" and "make fs readonly if it's not busy
writing stuff; do _not_ consider lusers wanting it to be writable an
obstacle, they are welcome to go forth and procreate".

And with that distinction we get a very nice semantics, indeed. Namely,
* vfsmounts have a "Don't write through me" flag.
* superblock has a counter for transient write accesses (->s_writes).
* superblock has a "Sod off, I'm readonly" flag (->s_baldrick).
Requesting a write access checks both flags and bumps ->s_writes.
Saying that write is over decrements ->s_writes.
Requesting a global readonly remount does current checks (for non-transient
writes), checks ->s_writes and if it's 0 - sets ->s_baldrick and initiates
syncing.
Asking to remount read-write asks filesystem if it would agree to reset
aforementioned flag and resets the vfsmount flag if fs does agree.

That's it. Now, we might actually go further and try to eliminate the
current mess in check for non-transient write access. It's not even
that hard - there are only two kinds of non-transients.
1) file can be opened for write. We have asked for write access
in may_open(); so let's not relinquish it until the final fput().
2) file can be unlinked but kept open. Let the filesystem bump
the ->s_writes when the ->i_nlink reaches 0 and have the ->delete_inode()
decrement it when it's done.
That will make the current mess unnecessary - we can just check ->s_writes
and it will tell us if the thing is busy.

Moreover, with that scheme we could even play with "if hadn't been
asked for write access in a while, tell it to go readonly but to be
ready to become read-write as soon as the next request for write access
will be issued" - the policy would be in userland and kernel-side part
wouldn't be hard. One such scheme would keep a flag that could be reset
by userland if ->s_writes was currently 0 and set whenever we grant
write access; attempt to reset it when it's already reset would do a
"soft" remount - it would tell the fs driver to do usual steps of r/o
remount, but switch back to r/w when write access is requested again.
Userland process would simply try to reset the flag periodically.

There's a lot of possible variations - basically, with that stuff done
we get remount logics into sane shape and that allows a lot of interesting
things. *Besides* the immediate results (per-subtree read-only).


So there... It certainly appears to be doable, it doesn't require too
nasty surgery of in-kernel APIs and it's splittable into reasonably small
steps that make sense on their own. If somebody would start doing that
they'd have to do (accurately) verification of correctness on each step -
thing that is missing in the above and can require dealing with problems
I'd overlooked. FWIW, my gut feeling from looking at that stuff is that
there won't be anything too nasty, but that needs to be checked if
somebody will start that work - the stuff above is a quick and dirty
attempt of a roadmap, but there might be pits with something unpleasant.

I would estimate that as 3-4 months' work / 4-6 months on gradual merge
(started in parallel) / several months of playing with the results
after the semantics of remount will be sanitized. YMMV.
-
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html


-------------------- End of forwarded message --------------------
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/

\
 
 \ /
  Last update: 2005-03-22 13:47    [W:0.231 / U:0.028 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site