aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--io/tst-lchmod.c83
-rw-r--r--sysdeps/unix/sysv/linux/fchmodat.c28
2 files changed, 49 insertions, 62 deletions
diff --git a/io/tst-lchmod.c b/io/tst-lchmod.c
index 73e4554..59873f1 100644
--- a/io/tst-lchmod.c
+++ b/io/tst-lchmod.c
@@ -102,68 +102,44 @@ test_1 (bool do_relative_path, int (*chmod_func) (int fd, const char *, mode_t,
TEST_VERIFY ((st.st_mode & 0777) != 2);
mode_t original_symlink_mode = st.st_mode;
- /* Set to true if AT_SYMLINK_NOFOLLOW is supported. */
- bool nofollow;
-
/* We should be able to change the mode of a file, including through
the symbolic link to-file. */
const char *arg = select_path (do_relative_path, path_file, "file");
TEST_COMPARE (chmod_func (fd, arg, 1, 0), 0);
xstat (path_file, &st);
TEST_COMPARE (st.st_mode & 0777, 1);
- int ret = chmod_func (fd, path_file, 2, AT_SYMLINK_NOFOLLOW);
- if (ret == 0)
- {
- printf ("info: AT_SYMLINK_NOFOLLOW support in %s\n", tempdir);
- nofollow = true;
- }
- else
- {
- printf ("info: no AT_SYMLINK_NOFOLLOW support in %s\n", tempdir);
- nofollow = false;
-
- /* Set up things for the code below. */
- TEST_COMPARE (chmod_func (fd, path_file, 2, 0), 0);
- }
+ arg = select_path (do_relative_path, path_to_file, "to-file");
+ TEST_COMPARE (chmod_func (fd, path_to_file, 2, 0), 0);
xstat (path_file, &st);
TEST_COMPARE (st.st_mode & 0777, 2);
- arg = select_path (do_relative_path, path_to_file, "to-file");
- TEST_COMPARE (chmod_func (fd, path_to_file, 1, 0), 0);
+ xlstat (path_to_file, &st);
+ TEST_COMPARE (original_symlink_mode, st.st_mode);
+ arg = select_path (do_relative_path, path_file, "file");
+ TEST_COMPARE (chmod_func (fd, arg, 1, 0), 0);
xstat (path_file, &st);
TEST_COMPARE (st.st_mode & 0777, 1);
xlstat (path_to_file, &st);
TEST_COMPARE (original_symlink_mode, st.st_mode);
- /* Changing the mode of a symbolic link may fail. */
+ /* Changing the mode of a symbolic link should fail fail. */
arg = select_path (do_relative_path, path_to_file, "to-file");
- ret = chmod_func (fd, arg, 2, AT_SYMLINK_NOFOLLOW);
- if (nofollow)
- {
- TEST_COMPARE (ret, 0);
-
- /* The mode of the link changed. */
- xlstat (path_to_file, &st);
- TEST_COMPARE (st.st_mode & 0777, 2);
-
- /* But the mode of the file is unchanged. */
- xstat (path_file, &st);
- TEST_COMPARE (st.st_mode & 0777, 1);
+ int ret = chmod_func (fd, arg, 2, AT_SYMLINK_NOFOLLOW);
+ TEST_COMPARE (ret, -1);
+ TEST_COMPARE (errno, EOPNOTSUPP);
- }
- else
- {
- TEST_COMPARE (ret, -1);
- TEST_COMPARE (errno, EOPNOTSUPP);
+ /* The modes should remain unchanged. */
+ xstat (path_file, &st);
+ TEST_COMPARE (st.st_mode & 0777, 1);
+ xlstat (path_to_file, &st);
+ TEST_COMPARE (original_symlink_mode, st.st_mode);
- /* The modes should remain unchanged. */
- xstat (path_file, &st);
- TEST_COMPARE (st.st_mode & 0777, 1);
- xlstat (path_to_file, &st);
- TEST_COMPARE (original_symlink_mode, st.st_mode);
- }
+ arg = select_path (do_relative_path, path_to_file, "to-file");
+ ret = chmod_func (fd, arg, 2, AT_SYMLINK_NOFOLLOW);
+ TEST_COMPARE (ret, -1);
+ TEST_COMPARE (errno, EOPNOTSUPP);
- /* If we have NOFOLLOW support, we should be able to change the mode
- of a dangling symbolic link or a symbolic link loop. */
+ /* Likewise, changing dangling and looping symbolic links must
+ fail. */
const char *paths[] = { path_dangling, path_loop };
for (size_t i = 0; i < array_length (paths); ++i)
{
@@ -178,19 +154,10 @@ test_1 (bool do_relative_path, int (*chmod_func) (int fd, const char *, mode_t,
original_symlink_mode = st.st_mode;
arg = select_path (do_relative_path, path, filename);
ret = chmod_func (fd, arg, new_mode, AT_SYMLINK_NOFOLLOW);
- if (nofollow)
- {
- TEST_COMPARE (ret, 0);
- xlstat (path, &st);
- TEST_COMPARE (st.st_mode & 0777, new_mode);
- }
- else /* !nofollow. */
- {
- TEST_COMPARE (ret, -1);
- TEST_COMPARE (errno, EOPNOTSUPP);
- xlstat (path, &st);
- TEST_COMPARE (st.st_mode, original_symlink_mode);
- }
+ TEST_COMPARE (ret, -1);
+ TEST_COMPARE (errno, EOPNOTSUPP);
+ xlstat (path, &st);
+ TEST_COMPARE (st.st_mode, original_symlink_mode);
}
/* A missing file should always result in ENOENT. The presence of
diff --git a/sysdeps/unix/sysv/linux/fchmodat.c b/sysdeps/unix/sysv/linux/fchmodat.c
index 719053b..17eca54 100644
--- a/sysdeps/unix/sysv/linux/fchmodat.c
+++ b/sysdeps/unix/sysv/linux/fchmodat.c
@@ -45,6 +45,30 @@ fchmodat (int fd, const char *file, mode_t mode, int flag)
caller can treat them as temporary if necessary. */
return pathfd;
+ /* Use fstatat because fstat does not work on O_PATH descriptors
+ before Linux 3.6. */
+ struct stat64 st;
+ if (fstatat64 (pathfd, "", &st, AT_EMPTY_PATH) != 0)
+ {
+ __close_nocancel (pathfd);
+ return -1;
+ }
+
+ /* Some Linux versions with some file systems can actually
+ change symbolic link permissions via /proc, but this is not
+ intentional, and it gives inconsistent results (e.g., error
+ return despite mode change). The expected behavior is that
+ symbolic link modes cannot be changed at all, and this check
+ enforces that. */
+ if (S_ISLNK (st.st_mode))
+ {
+ __close_nocancel (pathfd);
+ __set_errno (EOPNOTSUPP);
+ return -1;
+ }
+
+ /* For most file systems, fchmod does not operate on O_PATH
+ descriptors, so go through /proc. */
char buf[32];
if (__snprintf (buf, sizeof (buf), "/proc/self/fd/%d", pathfd) < 0)
{
@@ -54,10 +78,6 @@ fchmodat (int fd, const char *file, mode_t mode, int flag)
return -1;
}
- /* This operates directly on the symbolic link if it is one.
- /proc/self/fd files look like symbolic links, but they are
- not. (fchmod and fchmodat do not work on O_PATH descriptors,
- similar to fstat before Linux 3.6.) */
int ret = __chmod (buf, mode);
if (ret != 0)
{