xref: /linux/tools/testing/selftests/fchmodat2/fchmodat2_test.c (revision 9e229025e2474115c151f08bdbdd3d8d5f159af3)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 
3 #define _GNU_SOURCE
4 #include <fcntl.h>
5 #include <sys/stat.h>
6 #include <sys/types.h>
7 #include <syscall.h>
8 #include <unistd.h>
9 
10 #include "kselftest.h"
11 
12 struct testdir {
13 	char *dirname;
14 	int dfd;
15 };
16 
17 int sys_fchmodat2(int dfd, const char *filename, mode_t mode, int flags)
18 {
19 	int ret = syscall(__NR_fchmodat2, dfd, filename, mode, flags);
20 
21 	return ret >= 0 ? ret : -errno;
22 }
23 
24 static void setup_testdir(struct testdir *testdir)
25 {
26 	int ret, dfd;
27 	char dirname[] = "/tmp/ksft-fchmodat2.XXXXXX";
28 
29 	/* Make the top-level directory. */
30 	if (!mkdtemp(dirname))
31 		ksft_exit_fail_msg("%s: failed to create tmpdir\n", __func__);
32 
33 	dfd = open(dirname, O_PATH | O_DIRECTORY);
34 	if (dfd < 0) {
35 		ksft_perror("failed to open tmpdir");
36 		goto err;
37 	}
38 
39 	ret = openat(dfd, "regfile", O_CREAT | O_WRONLY | O_TRUNC, 0644);
40 	if (ret < 0) {
41 		ksft_perror("failed to create file in tmpdir");
42 		goto err;
43 	}
44 	close(ret);
45 
46 	ret = symlinkat("regfile", dfd, "symlink");
47 	if (ret < 0) {
48 		ksft_perror("symlinkat() failed");
49 		goto err_regfile;
50 	}
51 
52 	testdir->dirname = strdup(dirname);
53 	if (!testdir->dirname) {
54 		ksft_perror("Out of memory");
55 		goto err_symlink;
56 	}
57 	testdir->dfd = dfd;
58 
59 	return;
60 
61 err_symlink:
62 	unlinkat(testdir->dfd, "symlink", 0);
63 err_regfile:
64 	unlinkat(testdir->dfd, "regfile", 0);
65 err:
66 	unlink(dirname);
67 	ksft_exit_fail();
68 }
69 
70 static void cleanup_testdir(struct testdir *testdir)
71 {
72 	unlinkat(testdir->dfd, "regfile", 0);
73 	unlinkat(testdir->dfd, "symlink", 0);
74 	rmdir(testdir->dirname);
75 	free(testdir->dirname);
76 }
77 
78 int expect_mode(int dfd, const char *filename, mode_t expect_mode)
79 {
80 	struct stat st;
81 	int ret = fstatat(dfd, filename, &st, AT_SYMLINK_NOFOLLOW);
82 
83 	if (ret) {
84 		ksft_perror("fstatat() failed\n");
85 		return 0;
86 	}
87 
88 	return (st.st_mode == expect_mode);
89 }
90 
91 void test_regfile(void)
92 {
93 	struct testdir testdir;
94 	int ret;
95 
96 	setup_testdir(&testdir);
97 
98 	ret = sys_fchmodat2(testdir.dfd, "regfile", 0640, 0);
99 
100 	if (ret < 0) {
101 		ksft_perror("fchmodat2(noflag) failed");
102 		goto out;
103 	}
104 
105 	if (!expect_mode(testdir.dfd, "regfile", 0100640)) {
106 		ksft_print_msg("%s: wrong file mode bits after fchmodat2\n",
107 				__func__);
108 		ret = 1;
109 		goto out;
110 	}
111 
112 	ret = sys_fchmodat2(testdir.dfd, "regfile", 0600, AT_SYMLINK_NOFOLLOW);
113 
114 	if (ret < 0) {
115 		ksft_perror("fchmodat2(AT_SYMLINK_NOFOLLOW) failed");
116 		goto out;
117 	}
118 
119 	if (!expect_mode(testdir.dfd, "regfile", 0100600)) {
120 		ksft_print_msg("%s: wrong file mode bits after fchmodat2 with nofollow\n",
121 			       __func__);
122 		ret = 1;
123 	}
124 
125 out:
126 	ksft_test_result(ret == 0, "fchmodat2(regfile)\n");
127 	cleanup_testdir(&testdir);
128 }
129 
130 void test_symlink(void)
131 {
132 	struct testdir testdir;
133 	int ret;
134 
135 	setup_testdir(&testdir);
136 
137 	ret = sys_fchmodat2(testdir.dfd, "symlink", 0640, 0);
138 
139 	if (ret < 0) {
140 		ksft_perror("fchmodat2(noflag) failed");
141 		goto err;
142 	}
143 
144 	if (!expect_mode(testdir.dfd, "regfile", 0100640)) {
145 		ksft_print_msg("%s: wrong file mode bits after fchmodat2\n",
146 			       __func__);
147 		goto err;
148 	}
149 
150 	if (!expect_mode(testdir.dfd, "symlink", 0120777)) {
151 		ksft_print_msg("%s: wrong symlink mode bits after fchmodat2\n",
152 			       __func__);
153 		goto err;
154 	}
155 
156 	ret = sys_fchmodat2(testdir.dfd, "symlink", 0600, AT_SYMLINK_NOFOLLOW);
157 
158 	/*
159 	 * On certain filesystems (xfs or btrfs), chmod operation fails. So we
160 	 * first check the symlink target but if the operation fails we mark the
161 	 * test as skipped.
162 	 *
163 	 * https://sourceware.org/legacy-ml/libc-alpha/2020-02/msg00467.html
164 	 */
165 	if (ret == 0 && !expect_mode(testdir.dfd, "symlink", 0120600)) {
166 		ksft_print_msg("%s: wrong symlink mode bits after fchmodat2 with nofollow\n",
167 				__func__);
168 		ret = 1;
169 		goto err;
170 	}
171 
172 	if (!expect_mode(testdir.dfd, "regfile", 0100640)) {
173 		ksft_print_msg("%s: wrong file mode bits after fchmodat2 with nofollow\n",
174 			       __func__);
175 	}
176 
177 	if (ret != 0)
178 		ksft_test_result_skip("fchmodat2(symlink)\n");
179 	else
180 		ksft_test_result_pass("fchmodat2(symlink)\n");
181 	cleanup_testdir(&testdir);
182 	return;
183 
184 err:
185 	ksft_test_result_fail("fchmodat2(symlink)\n");
186 	cleanup_testdir(&testdir);
187 }
188 
189 #define NUM_TESTS 2
190 
191 int main(int argc, char **argv)
192 {
193 	ksft_print_header();
194 	ksft_set_plan(NUM_TESTS);
195 
196 	test_regfile();
197 	test_symlink();
198 
199 	ksft_finished();
200 }
201