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