text: Her-)> ...-------------------- Start of forwarded message --------------------Date: Thu, 29 May 2003 09:21:27 +0100From: viro@parcelfarce.linux.theplanet.co.ukTo: Shaya Potter <spotter@cs.columbia.edu>Cc: linux-fsdevel@vger.kernel.orgSubject: [long] per-mountpoint readonlyMessage-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 waywe need to make readonly status per-mountpoint. Note that we already havethat for e.g. nosuid and friends. If we get that done for readonlyyou can * in your namespace remount everything readonly * mount --recbind /var/log /var/log mount -o remount,rw /var/logand enjoy.The first question is obviously "could it be done at all?"Situation with IS_RDONLY() is interesting. First of all, there areuses of that beast in ->permission() instances. We definitely can(and should) take them upstream. No matter how hard somebody mightwish 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 inxattr methods - they also go upstream.ioctls (ext2, ext3, reiserfs) have struct file * and thus are nota problem.fs/open.c callers also have struct file * or struct nameidata. Not aproblem.fat_truncate() - actually, both checks there are bogus; callers docheck for that stuff. Should be removed.ncpfs uses should be replaced with update_atime() (and that will bea 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 verifythat 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 callersshould 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 wouldmake sense regardless of the following. And yes, there's a lot of work onaccurate verification of correctness that I'd skipped - it has to be doneand 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 callsof permission(). _HOWEVER_, we might not need to do that. Note that partpotentially 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 functionof vfsmount. Let's start with taking that check out of permission() andinto its callers. Note that callers in floppy_open() (the worst from the "where dowe get vfsmount/dentry" POV) do not give a damn - there the check isalways false, since we are talking about block device. So they arenot a problem. Places where we got the inode from struct file * or struct nameidataare 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 reasonsas 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 ofrather 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 bedropped - after the changes above we would have them already done in callers. Now we are down to 8 functionsmay_create(), may_delete(), vfs_rename_dir()their intermezzo copieshpfs_unlink() and update_atime().Let's take a look at update_atime(). We want to propagate struct vfsmountand 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 andnodiratime out that - immediately.All callers that have inode obtained by struct file * are trivial. We havewhat we need there. Now, a large group actually comes from the same kindof place - it's ->readdir() in the majority of filesystems. We might bebetter off just taking that to caller of ->readdir() - provided that NFSfolks are OK with that (nfs does _not_ update atime on client afterreaddir()). 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 (shouldbe done accurately - it's easy to get confused there).autofs4 use - AFAICS there we want atime updated unconditionally, so callingupdate_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 - abilityto 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(), allright, but check for filesystem being writable for us is already done, sowe can skip it.Situation with may_create()/may_delete() is more interesting. They arecalled from vfs_{create,link,unlink,....} and there we do not have avfsmount. Let's start with taking the check ("fs is writable for us")from may_...() into these functions. They are _always_ done on a singlefs, 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 firstoperation - 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, butwe can't move it in the very beginning. IOW, there is a case whenrename() on a read-only filesystem is required to succeed - whensource and target are links to the same inode. In that case POSIXrequires to return 0 and do nothing.Check that had migrated into vfs_rename_dir() can be dropped - wedo 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 itanywhere else in the tree and it's not exported, so...Now, we are left with that merry bunch - vfs_<operation>. Let's takethe POSIX idiocy to the callers of vfs_rename() and let's move thecheck for fs being writable to us into callers of all these guys._NOW_ we are done. Indeed, there are two groups of callers (syscallsand nfsd) and both know what we are dealing with. The former havevfsmount, 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 thesame manner.OK, so far we have shown that per-mountpoint ro/noatime/nodiratime aredoable. However, the solution as described above is *NOT* going tobe accepted - it sprinkles a lot of crap all over the tree for novisible reason. Let's take a look at the resulting picture and seehow 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 earlierstages - at the point where we have decided that operation will happenon *this* part of mount tree.That has an obvious functionality problem - what happens if we decide thatfs 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 thatwe ask "can I write to that sucker now?" and consider the positiveanswer as go-ahead.Theoretically, it could even work - if remount would block until thetransient operation is done or say just say "busy" in all cases whensomething is going on. We even do some checks of that sort. However,they deal only with long-term stuff - files being opened for write orunlinked 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 transientstuff with WILL WRITE/WONT WRITE. With filesystem being able to sayDO WRITE/DONT WRITE, obviously ;-)But look, we have already done half of that - we have the openingbrackets. And that's exactly what our shifted checks are - we areasking for write access to filesystem, we call a function that assumessuch 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 sanesemantics for all that stuff. Why would filesystem be read-onlyor 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 afilesystem 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 beingwritable.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, stopallowing writes to come via it" and "make fs readonly if it's not busywriting stuff; do _not_ consider lusers wanting it to be writable anobstacle, 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-transientwrites), checks ->s_writes and if it's 0 - sets ->s_baldrick and initiatessyncing.Asking to remount read-write asks filesystem if it would agree to resetaforementioned flag and resets the vfsmount flag if fs does agree.That's it. Now, we might actually go further and try to eliminate thecurrent mess in check for non-transient write access. It's not eventhat hard - there are only two kinds of non-transients. 1) file can be opened for write. We have asked for write accessin may_open(); so let's not relinquish it until the final fput(). 2) file can be unlinked but kept open. Let the filesystem bumpthe ->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_writesand it will tell us if the thing is busy.Moreover, with that scheme we could even play with "if hadn't beenasked for write access in a while, tell it to go readonly but to beready to become read-write as soon as the next request for write accesswill be issued" - the policy would be in userland and kernel-side partwouldn't be hard. One such scheme would keep a flag that could be resetby userland if ->s_writes was currently 0 and set whenever we grantwrite 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/oremount, 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 donewe get remount logics into sane shape and that allows a lot of interestingthings. *Besides* the immediate results (per-subtree read-only).So there... It certainly appears to be doable, it doesn't require toonasty surgery of in-kernel APIs and it's splittable into reasonably smallsteps that make sense on their own. If somebody would start doing thatthey'd have to do (accurately) verification of correctness on each step -thing that is missing in the above and can require dealing with problemsI'd overlooked. FWIW, my gut feeling from looking at that stuff is thatthere won't be anything too nasty, but that needs to be checked ifsomebody will start that work - the stuff above is a quick and dirtyattempt 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 resultsafter the semantics of remount will be sanitized. YMMV.-To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" inthe body of a message to majordomo@vger.kernel.orgMore majordomo info at-------------------- End of forwarded message ---------------------To unsubscribe from this list: send the line "unsubscribe linux-kernel" inthe body of a message to majordomo@vger.kernel.orgMore majordomo info at read the FAQ at