1 /* Tests for lchmod and fchmodat with AT_SYMLINK_NOFOLLOW.
2 Copyright (C) 2020-2021 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <https://www.gnu.org/licenses/>. */
19 #include <array_length.h>
26 #include <support/check.h>
27 #include <support/descriptors.h>
28 #include <support/namespace.h>
29 #include <support/support.h>
30 #include <support/temp_file.h>
31 #include <support/xunistd.h>
34 #if __has_include (<sys/mount.h>)
35 # include <sys/mount.h>
38 /* Array of file descriptors. */
39 #define DYNARRAY_STRUCT fd_list
40 #define DYNARRAY_ELEMENT int
41 #define DYNARRAY_INITIAL_SIZE 0
42 #define DYNARRAY_PREFIX fd_list_
43 #include <malloc/dynarray-skeleton.c>
46 fchmodat_with_lchmod (int fd
, const char *path
, mode_t mode
, int flags
)
48 TEST_COMPARE (fd
, AT_FDCWD
);
50 return chmod (path
, mode
);
53 TEST_COMPARE (flags
, AT_SYMLINK_NOFOLLOW
);
54 return lchmod (path
, mode
);
58 /* Chose the appropriate path to pass as the path argument to the *at
61 select_path (bool do_relative_path
, const char *full_path
, const char *relative_path
)
70 test_1 (bool do_relative_path
, int (*chmod_func
) (int fd
, const char *, mode_t
, int))
72 char *tempdir
= support_create_temp_directory ("tst-lchmod-");
74 char *path_dangling
= xasprintf ("%s/dangling", tempdir
);
75 char *path_file
= xasprintf ("%s/file", tempdir
);
76 char *path_loop
= xasprintf ("%s/loop", tempdir
);
77 char *path_missing
= xasprintf ("%s/missing", tempdir
);
78 char *path_to_file
= xasprintf ("%s/to-file", tempdir
);
82 fd
= xopen (tempdir
, O_DIRECTORY
| O_RDONLY
, 0);
86 add_temp_file (path_dangling
);
87 add_temp_file (path_loop
);
88 add_temp_file (path_file
);
89 add_temp_file (path_to_file
);
91 support_write_file_string (path_file
, "");
92 xsymlink ("file", path_to_file
);
93 xsymlink ("loop", path_loop
);
94 xsymlink ("target-does-not-exist", path_dangling
);
96 /* Check that the modes do not collide with what we will use in the
99 xstat (path_file
, &st
);
100 TEST_VERIFY ((st
.st_mode
& 0777) != 1);
101 xlstat (path_to_file
, &st
);
102 TEST_VERIFY ((st
.st_mode
& 0777) != 2);
103 mode_t original_symlink_mode
= st
.st_mode
;
105 /* We should be able to change the mode of a file, including through
106 the symbolic link to-file. */
107 const char *arg
= select_path (do_relative_path
, path_file
, "file");
108 TEST_COMPARE (chmod_func (fd
, arg
, 1, 0), 0);
109 xstat (path_file
, &st
);
110 TEST_COMPARE (st
.st_mode
& 0777, 1);
111 arg
= select_path (do_relative_path
, path_to_file
, "to-file");
112 TEST_COMPARE (chmod_func (fd
, arg
, 2, 0), 0);
113 xstat (path_file
, &st
);
114 TEST_COMPARE (st
.st_mode
& 0777, 2);
115 xlstat (path_to_file
, &st
);
116 TEST_COMPARE (original_symlink_mode
, st
.st_mode
);
117 arg
= select_path (do_relative_path
, path_file
, "file");
118 TEST_COMPARE (chmod_func (fd
, arg
, 1, 0), 0);
119 xstat (path_file
, &st
);
120 TEST_COMPARE (st
.st_mode
& 0777, 1);
121 xlstat (path_to_file
, &st
);
122 TEST_COMPARE (original_symlink_mode
, st
.st_mode
);
124 /* Changing the mode of a symbolic link should fail. */
125 arg
= select_path (do_relative_path
, path_to_file
, "to-file");
126 int ret
= chmod_func (fd
, arg
, 2, AT_SYMLINK_NOFOLLOW
);
127 TEST_COMPARE (ret
, -1);
128 TEST_COMPARE (errno
, EOPNOTSUPP
);
130 /* The modes should remain unchanged. */
131 xstat (path_file
, &st
);
132 TEST_COMPARE (st
.st_mode
& 0777, 1);
133 xlstat (path_to_file
, &st
);
134 TEST_COMPARE (original_symlink_mode
, st
.st_mode
);
136 /* Likewise, changing dangling and looping symbolic links must
138 const char *paths
[] = { path_dangling
, path_loop
};
139 for (size_t i
= 0; i
< array_length (paths
); ++i
)
141 const char *path
= paths
[i
];
142 const char *filename
= strrchr (path
, '/');
143 TEST_VERIFY_EXIT (filename
!= NULL
);
145 mode_t new_mode
= 010 + i
;
148 TEST_VERIFY ((st
.st_mode
& 0777) != new_mode
);
149 original_symlink_mode
= st
.st_mode
;
150 arg
= select_path (do_relative_path
, path
, filename
);
151 ret
= chmod_func (fd
, arg
, new_mode
, AT_SYMLINK_NOFOLLOW
);
152 TEST_COMPARE (ret
, -1);
153 TEST_COMPARE (errno
, EOPNOTSUPP
);
155 TEST_COMPARE (st
.st_mode
, original_symlink_mode
);
158 /* A missing file should always result in ENOENT. The presence of
159 /proc does not matter. */
160 arg
= select_path (do_relative_path
, path_missing
, "missing");
161 TEST_COMPARE (chmod_func (fd
, arg
, 020, 0), -1);
162 TEST_COMPARE (errno
, ENOENT
);
163 TEST_COMPARE (chmod_func (fd
, arg
, 020, AT_SYMLINK_NOFOLLOW
), -1);
164 TEST_COMPARE (errno
, ENOENT
);
166 /* Test without available file descriptors. */
168 struct fd_list fd_list
;
169 fd_list_init (&fd_list
);
172 int ret
= dup (STDOUT_FILENO
);
175 if (errno
== ENFILE
|| errno
== EMFILE
)
177 FAIL_EXIT1 ("dup: %m");
179 fd_list_add (&fd_list
, ret
);
180 TEST_VERIFY_EXIT (!fd_list_has_failed (&fd_list
));
182 /* Without AT_SYMLINK_NOFOLLOW, changing the permissions should
184 arg
= select_path (do_relative_path
, path_file
, "file");
185 TEST_COMPARE (chmod_func (fd
, arg
, 3, 0), 0);
186 xstat (path_file
, &st
);
187 TEST_COMPARE (st
.st_mode
& 0777, 3);
188 /* But with AT_SYMLINK_NOFOLLOW, even if we originally had
189 support, we may have lost it. */
190 ret
= chmod_func (fd
, arg
, 2, AT_SYMLINK_NOFOLLOW
);
193 xstat (path_file
, &st
);
194 TEST_COMPARE (st
.st_mode
& 0777, 2);
198 TEST_COMPARE (ret
, -1);
199 /* The error code from the openat fallback leaks out. */
200 if (errno
!= ENFILE
&& errno
!= EMFILE
)
201 TEST_COMPARE (errno
, EOPNOTSUPP
);
203 xstat (path_file
, &st
);
204 TEST_COMPARE (st
.st_mode
& 0777, 3);
206 /* Close the descriptors. */
207 for (int *pfd
= fd_list_begin (&fd_list
); pfd
< fd_list_end (&fd_list
);
210 fd_list_free (&fd_list
);
213 if (do_relative_path
)
216 free (path_dangling
);
228 puts ("info: testing lchmod");
229 test_1 (false, fchmodat_with_lchmod
);
230 puts ("info: testing fchmodat with AT_FDCWD");
231 test_1 (false, fchmodat
);
232 puts ("info: testing fchmodat with relative path");
233 test_1 (true, fchmodat
);
239 struct support_descriptors
*descriptors
= support_descriptors_list ();
241 /* Run the three tests in the default environment. */
244 /* Try to set up a /proc-less environment and re-test. */
245 #if __has_include (<sys/mount.h>)
246 if (!support_become_root ())
247 puts ("warning: could not obtain root-like privileges");
248 if (!support_enter_mount_namespace ())
249 puts ("warning: could enter a mount namespace");
252 /* Attempt to mount an empty directory over /proc. */
253 char *tempdir
= support_create_temp_directory ("tst-lchmod-");
255 = mount (tempdir
, "/proc", "none", MS_BIND
, NULL
) == 0;
257 printf ("warning: bind-mounting /proc failed: %m");
260 puts ("info: re-running tests (after trying to empty /proc)");
264 /* Reveal the original /proc, which is needed by the
265 descriptors check below. */
266 TEST_COMPARE (umount ("/proc"), 0);
268 #endif /* <sys/mount.h>. */
270 support_descriptors_check (descriptors
);
271 support_descriptors_free (descriptors
);
276 #include <support/test-driver.c>