POSIX does not require lchmod, and Linux does not supply it.  On platforms that 
lack lchmod, symlinks always have the same default permissions, so the 
inability to change permissions is not a problem because the source that you 
would be copying has the same permissions (0777) as the newly created symlink 
destination.

On BSD, where lchmod is supported, only the 0555 bits matter (readlink() fails 
if the current user can't read the symlink, and all other syscalls fail with 
ENOENT for failure to follow the symlink if the current user can't 
search/execute the symlink during file name resolution), and even then, only if 
on a file system mounted with the symperm option.

Right now, gnulib has an lchmod replacement which always calls chmod; this will 
do the wrong thing if it is ever called on a symlink, but is right for 
everything else.  Meanwhile, the fchmodat replacment (and hence the lchmodat 
convenience wrapper) blindly fails with ENOSYS, even for non-symlinks; this is 
different than native fchmodat on Linux.

POSIX requires that fchmodat(,O_SYMLINK_NOFOLLOW) fail with EOPNOTSUPP (which 
is often equal to ENOTSUP, including on Linux), not ENOSYS, but only if the 
argument is a symlink and symlink modes can't be changed, and that otherwise it 
behaves like chmod.

In the current glibc.git, fchmodat blindly returns ENOTSUP for all file types, 
rather than behaving like chmod.


I'm thinking we should change things to be a bit more consistent, as part of 
preparing copy.c to be rewritten to use fts, but I'm not sure which way to go.  
Does anyone have preference?


If we go with POSIX, then:

The replacement lchmod should use fchmodat() when it is natively available and 
behaves like POSIX requires (getting ENOTSUP for free on symlinks, and no 
overhead fallback to chmod otherwise).  On Linux, it should lstat() the 
argument, and fail with ENOTSUP only if attempted on a symlink, otherwise call 
chmod (yes, this is a slight race, but so are most of our other emulations).

On Linux we wrap native fchmodat() to call fstatat(), so that we only fail on 
true symlinks, but again with a slight race.

In coreutils, we can blindly call lchmod without regards to file type, and rely 
on ENOTSUP occurring only on a symlink, at which point we safely ignore the 
failure; no extra call to chmod is needed.  But every call to lchmod adds an 
extra lstat() on Linux, until some future day when the kernel and glibc give 
atomic fchmodat support that obeys POSIX.


If we go with glibc behavior, then:

The replacement lchmod should be changed to blindly fail with ENOTSUP, rather 
than the current behavior of calling chmod.  No standards compliance issue, 
since lchmod is not standardized.

We do not need to replace native Linux fchmodat(), and we only have to tweak 
our existing replacement fchmodat() to blindly fail with ENOTSUP instead of 
ENOSYS on other platforms.  This is not strictly POSIX, but if used correctly, 
is more efficient on Linux.

In coreutils, we have to be careful to call chmod rather than lchmod on non-
symlinks, saving lchmod for when we know that the target should be a symlink.  
On ENOTSUP failure, we have to fall back to chmod for non-symlinks.


Either way:

cp.c has two uses of lchmod, to implement 'cp --parents --preserve a/b c/d' 
with correct permissions on c/d/a according to the original a.  One use is 
always on a directory (making it temporarily writable); the other is in 
re_protect to restore permissions, although I didn't spend enough time seeing 
whether the restore path could ever happen on a symlink to a directory 
installed in place of the original directory.  So it seems like cp.c should be 
using chmod, not lchmod.

copy.c has several uses of lchmod (including via the wrapper fchmod_or_lchmod); 
but again, all of them look like they are used for temporarily adding 
permissions to read-only destinations, or for granting additional permissions 
that were intentionally omitted at the beginning.  In particular, for the case 
of fchmod_or_lchmod, chmod should always be sufficient, as you cannot have an 
open fd on a symlink, but the function is designed for use on an open fd.  
Meanwhile, it looks like on a BSD system, 'cp --preserve' is missing a use of 
lchmod to preserve permissions on a copied symlink; here, we can safely ignore 
ENOTSUP failure of lchmod on Linux.

Finally, we should consider adding 'chmod -h' to mirror 'chown -h', where it 
would actually work on BSD, and would just print an error message on Linux when 
attempted on symlinks but behave like plain 'chmod' on regular files.  This 
would always require an lstat() or reliable d_type (either in the gnulib 
wrapper if we choose to make fchmodat match POSIX, or in chmod.c if we make 
fchmodat match glibc).

Right now, I'm leaning towards matching glibc behavior, and making the burden 
of the lstat() fall on the caller; particularly since the caller can use d_type 
information instead of lstat() in some cases.  Does all of this sound 
reasonable?

-- 
Eric Blake




Reply via email to