I am trying to understand why this design decision was made with the rename() syscall in 4.2BSD. There’s nothing I’m trying to solve here, just understand the rationale for the behavior itself.
4.2BSD saw the introduction of the rename() syscall for the purpose of allowing atomic renames/moves of files. From 4.3BSD-Reno/src/sys/ufs/ufs_vnops.c:
/*
* If ".." must be changed (ie the directory gets a new
* parent) then the source directory must not be in the
* directory heirarchy above the target, as this would
* orphan everything below the source directory. Also
* the user must have write permission in the source so
* as to be able to change "..". We must repeat the call
* to namei, as the parent directory is unlocked by the
* call to checkpath().
*/
if (oldparent != dp->i_number)
newparent = dp->i_number;
if (doingdirectory && newparent) {
VOP_LOCK(fndp->ni_vp);
error = ufs_access(fndp->ni_vp, VWRITE, tndp->ni_cred);
VOP_UNLOCK(fndp->ni_vp);
So clearly this check was added intentionally. My question is – why? Is this behavior supposed to be intuitive?
The effect of this is that one cannot move a directory (located in a directory that one can write) that one cannot write to another directory that one can write to atomically. You can, however, create a new directory, move the links over (assuming one has read access to the directory), and then remove one’s write bit on the directory. You just can’t do so atomically.
% cd /tmp
% mkdir stackoverflow-question
% cd stackoverflow-question
% mkdir directory-1
% mkdir directory-2
% mkdir directory-1/directory-i-cant-write
% echo "foo" > directory-1/directory-i-cant-write/contents
% chmod 000 directory-1/directory-i-cant-write/contents
% chmod 000 directory-1/directory-i-cant-write
% mv directory-1/directory-i-cant-write directory-2
mv: rename directory-1/directory-i-cant-write to directory-2/directory-i-cant-write: Permission denied
We now have a directory I can’t write with contents I can’t read that I can’t move atomically. I can, however, achieve the same effect non-atomically by changing permissions, making the new directory, using ln to create the new links, and changing permissions. (Left as an exercise to the reader)
. and .. are special cased already, so I don’t particularly buy that it is intuitive that if I can’t write a directory I can’t “change ..” which is what the source suggests. Is there any reason for this besides it being the perceived correct behavior by the author of the code? Is there anything bad that can happen if we let people atomically move directories (that they can’t write) between directories that they can write?
I think it’s quite likely that Andrew McGregor is right. Not necessarily that UFS has to work this way, but that the implementor (Kirk McKusick) simply extended the logic of file system permissions to cover this case. Thus, if you don’t have write permissions on the target directory, you shouldn’t be able to change its “..” entry.
But in looking at your example, another possibility came to mind. It might be that the concern isn’t a case, like you show, where a single user owns all of the directories in question, but rather a case where the directories are owned by different users. In other words, this check prevents me from moving a directory you own between parent directories that I have write permission on. Assuming, of course, that you haven’t given me write permissions in your directory.
Admittedly, the scenarios where this could come up are few and far between in normal usage. But the kernel has to worry about all of the oddball use cases as well as the common ones.
An obvious counter argument is that if we wanted to worry about this scenario, then we would also want to stop people from moving files they don’t own between directories they have write permissions on…