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("%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("%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("%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("%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("%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("%s: fchmodat2(noflag) failed\n", __func__); 68 69 if (!expect_mode(dfd, "regfile", 0100640)) 70 ksft_exit_fail_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("%s: fchmodat2(AT_SYMLINK_NOFOLLOW) failed\n", 77 __func__); 78 79 if (!expect_mode(dfd, "regfile", 0100600)) 80 ksft_exit_fail_msg("%s: wrong file mode bits after fchmodat2 with nofollow\n", 81 __func__); 82 83 ksft_test_result_pass("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("%s: fchmodat2(noflag) failed\n", __func__); 96 97 if (!expect_mode(dfd, "regfile", 0100640)) 98 ksft_exit_fail_msg("%s: wrong file mode bits after fchmodat2\n", 99 __func__); 100 101 if (!expect_mode(dfd, "symlink", 0120777)) 102 ksft_exit_fail_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("%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("%s: wrong file mode bits after fchmodat2 with nofollow\n", 120 __func__); 121 122 if (ret != 0) 123 ksft_test_result_skip("fchmodat2(symlink)\n"); 124 else 125 ksft_test_result_pass("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