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 | int sys_fchmodat2(int dfd, const char *filename, mode_t mode, int flags) |
13 | { |
14 | int ret = syscall(__NR_fchmodat2, dfd, filename, mode, flags); |
15 | |
16 | return ret >= 0 ? ret : -errno; |
17 | } |
18 | |
19 | int setup_testdir(void) |
20 | { |
21 | int dfd, ret; |
22 | char dirname[] = "/tmp/ksft-fchmodat2.XXXXXX" ; |
23 | |
24 | /* Make the top-level directory. */ |
25 | if (!mkdtemp(dirname)) |
26 | ksft_exit_fail_msg(msg: "%s: failed to create tmpdir\n" , __func__); |
27 | |
28 | dfd = open(dirname, O_PATH | O_DIRECTORY); |
29 | if (dfd < 0) |
30 | ksft_exit_fail_msg(msg: "%s: failed to open tmpdir\n" , __func__); |
31 | |
32 | ret = openat(dfd, "regfile" , O_CREAT | O_WRONLY | O_TRUNC, 0644); |
33 | if (ret < 0) |
34 | ksft_exit_fail_msg(msg: "%s: failed to create file in tmpdir\n" , |
35 | __func__); |
36 | close(ret); |
37 | |
38 | ret = symlinkat("regfile" , dfd, "symlink" ); |
39 | if (ret < 0) |
40 | ksft_exit_fail_msg(msg: "%s: failed to create symlink in tmpdir\n" , |
41 | __func__); |
42 | |
43 | return dfd; |
44 | } |
45 | |
46 | int expect_mode(int dfd, const char *filename, mode_t expect_mode) |
47 | { |
48 | struct stat st; |
49 | int ret = fstatat(dfd, filename, &st, AT_SYMLINK_NOFOLLOW); |
50 | |
51 | if (ret) |
52 | ksft_exit_fail_msg(msg: "%s: %s: fstatat failed\n" , |
53 | __func__, filename); |
54 | |
55 | return (st.st_mode == expect_mode); |
56 | } |
57 | |
58 | void test_regfile(void) |
59 | { |
60 | int dfd, ret; |
61 | |
62 | dfd = setup_testdir(); |
63 | |
64 | ret = sys_fchmodat2(dfd, "regfile" , 0640, 0); |
65 | |
66 | if (ret < 0) |
67 | ksft_exit_fail_msg(msg: "%s: fchmodat2(noflag) failed\n" , __func__); |
68 | |
69 | if (!expect_mode(dfd, "regfile" , 0100640)) |
70 | ksft_exit_fail_msg(msg: "%s: wrong file mode bits after fchmodat2\n" , |
71 | __func__); |
72 | |
73 | ret = sys_fchmodat2(dfd, "regfile" , 0600, AT_SYMLINK_NOFOLLOW); |
74 | |
75 | if (ret < 0) |
76 | ksft_exit_fail_msg(msg: "%s: fchmodat2(AT_SYMLINK_NOFOLLOW) failed\n" , |
77 | __func__); |
78 | |
79 | if (!expect_mode(dfd, "regfile" , 0100600)) |
80 | ksft_exit_fail_msg(msg: "%s: wrong file mode bits after fchmodat2 with nofollow\n" , |
81 | __func__); |
82 | |
83 | ksft_test_result_pass(msg: "fchmodat2(regfile)\n" ); |
84 | } |
85 | |
86 | void test_symlink(void) |
87 | { |
88 | int dfd, ret; |
89 | |
90 | dfd = setup_testdir(); |
91 | |
92 | ret = sys_fchmodat2(dfd, "symlink" , 0640, 0); |
93 | |
94 | if (ret < 0) |
95 | ksft_exit_fail_msg(msg: "%s: fchmodat2(noflag) failed\n" , __func__); |
96 | |
97 | if (!expect_mode(dfd, "regfile" , 0100640)) |
98 | ksft_exit_fail_msg(msg: "%s: wrong file mode bits after fchmodat2\n" , |
99 | __func__); |
100 | |
101 | if (!expect_mode(dfd, "symlink" , 0120777)) |
102 | ksft_exit_fail_msg(msg: "%s: wrong symlink mode bits after fchmodat2\n" , |
103 | __func__); |
104 | |
105 | ret = sys_fchmodat2(dfd, "symlink" , 0600, AT_SYMLINK_NOFOLLOW); |
106 | |
107 | /* |
108 | * On certain filesystems (xfs or btrfs), chmod operation fails. So we |
109 | * first check the symlink target but if the operation fails we mark the |
110 | * test as skipped. |
111 | * |
112 | * https://sourceware.org/legacy-ml/libc-alpha/2020-02/msg00467.html |
113 | */ |
114 | if (ret == 0 && !expect_mode(dfd, "symlink" , 0120600)) |
115 | ksft_exit_fail_msg(msg: "%s: wrong symlink mode bits after fchmodat2 with nofollow\n" , |
116 | __func__); |
117 | |
118 | if (!expect_mode(dfd, "regfile" , 0100640)) |
119 | ksft_exit_fail_msg(msg: "%s: wrong file mode bits after fchmodat2 with nofollow\n" , |
120 | __func__); |
121 | |
122 | if (ret != 0) |
123 | ksft_test_result_skip(msg: "fchmodat2(symlink)\n" ); |
124 | else |
125 | ksft_test_result_pass(msg: "fchmodat2(symlink)\n" ); |
126 | } |
127 | |
128 | #define NUM_TESTS 2 |
129 | |
130 | int main(int argc, char **argv) |
131 | { |
132 | ksft_print_header(); |
133 | ksft_set_plan(NUM_TESTS); |
134 | |
135 | test_regfile(); |
136 | test_symlink(); |
137 | |
138 | if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0) |
139 | ksft_exit_fail(); |
140 | else |
141 | ksft_exit_pass(); |
142 | } |
143 | |