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