summaryrefslogtreecommitdiff
path: root/lib/lchmod.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/lchmod.c')
-rw-r--r--lib/lchmod.c84
1 files changed, 34 insertions, 50 deletions
diff --git a/lib/lchmod.c b/lib/lchmod.c
index 706dddff7bb..8410a2d835f 100644
--- a/lib/lchmod.c
+++ b/lib/lchmod.c
@@ -25,17 +25,9 @@
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
+#include <string.h>
#include <unistd.h>
-#ifdef __osf__
-/* Write "sys/stat.h" here, not <sys/stat.h>, otherwise OSF/1 5.1 DTK cc
- eliminates this include because of the preliminary #include <sys/stat.h>
- above. */
-# include "sys/stat.h"
-#else
-# include <sys/stat.h>
-#endif
-
#include <intprops.h>
/* Work like chmod, except when FILE is a symbolic link.
@@ -45,7 +37,9 @@
int
lchmod (char const *file, mode_t mode)
{
-#if defined O_PATH && defined AT_EMPTY_PATH
+ char readlink_buf[1];
+
+#ifdef O_PATH
/* Open a file descriptor with O_NOFOLLOW, to make sure we don't
follow symbolic links, if /proc is mounted. O_PATH is used to
avoid a failure if the file is not readable.
@@ -54,56 +48,46 @@ lchmod (char const *file, mode_t mode)
if (fd < 0)
return fd;
- /* Up to Linux 5.3 at least, when FILE refers to a symbolic link, the
- chmod call below will change the permissions of the symbolic link
- - which is undesired - and on many file systems (ext4, btrfs, jfs,
- xfs, ..., but not reiserfs) fail with error EOPNOTSUPP - which is
- misleading. Therefore test for a symbolic link explicitly.
- Use fstatat because fstat does not work on O_PATH descriptors
- before Linux 3.6. */
- struct stat st;
- if (fstatat (fd, "", &st, AT_EMPTY_PATH) != 0)
+ int err;
+ if (0 <= readlinkat (fd, "", readlink_buf, sizeof readlink_buf))
+ err = EOPNOTSUPP;
+ else if (errno == EINVAL)
{
- int stat_errno = errno;
- close (fd);
- errno = stat_errno;
- return -1;
- }
- if (S_ISLNK (st.st_mode))
- {
- close (fd);
- errno = EOPNOTSUPP;
- return -1;
+ static char const fmt[] = "/proc/self/fd/%d";
+ char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)];
+ sprintf (buf, fmt, fd);
+ err = chmod (buf, mode) == 0 ? 0 : errno == ENOENT ? -1 : errno;
}
+ else
+ err = errno == ENOENT ? -1 : errno;
-# if defined __linux__ || defined __ANDROID__ || defined __CYGWIN__
- static char const fmt[] = "/proc/self/fd/%d";
- char buf[sizeof fmt - sizeof "%d" + INT_BUFSIZE_BOUND (int)];
- sprintf (buf, fmt, fd);
- int chmod_result = chmod (buf, mode);
- int chmod_errno = errno;
close (fd);
- if (chmod_result == 0)
- return chmod_result;
- if (chmod_errno != ENOENT)
+
+ errno = err;
+ if (0 <= err)
+ return err == 0 ? 0 : -1;
+#endif
+
+ size_t len = strlen (file);
+ if (len && file[len - 1] == '/')
{
- errno = chmod_errno;
- return chmod_result;
+ struct stat st;
+ if (lstat (file, &st) < 0)
+ return -1;
+ if (!S_ISDIR (st.st_mode))
+ {
+ errno = ENOTDIR;
+ return -1;
+ }
}
-# endif
- /* /proc is not mounted or would not work as in GNU/Linux. */
-
-#elif HAVE_LSTAT
- struct stat st;
- int lstat_result = lstat (file, &st);
- if (lstat_result != 0)
- return lstat_result;
- if (S_ISLNK (st.st_mode))
+
+ /* O_PATH + /proc is not supported. */
+
+ if (0 <= readlink (file, readlink_buf, sizeof readlink_buf))
{
errno = EOPNOTSUPP;
return -1;
}
-#endif
/* Fall back on chmod, despite a possible race. */
return chmod (file, mode);