1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Copyright (c) 2019 SUSE LLC <mdoucha@suse.cz> 4 */ 5 6/*\ 7 * [Description] 8 * 9 * Tests misaligned fallocate() 10 * 11 * Test scenario: 12 * 13 * 1. write() several blocks worth of data 14 * 2. fallocate() some more space (not aligned to FS blocks) 15 * 3. try to write() into the allocated space 16 * 4. deallocate misaligned part of file range written in step 1 17 * 5. read() the deallocated range and check that it was zeroed 18 * 19 * Subtests: 20 * 21 * - fill filesystem between step 2 and 3 22 * - disable copy-on-write on test file 23 * - combinations of above subtests 24 */ 25 26/* 27 * This is also regression test for: 28 * e093c4be760e ("xfs: Fix tail rounding in xfs_alloc_file_space()") 29 * 6d4572a9d71d ("Allow btrfs_truncate_block() to fallback to nocow for data 30 * space reservation") 31 */ 32 33#define _GNU_SOURCE 34 35#include <stdio.h> 36#include <stdlib.h> 37#include <string.h> 38#include <fcntl.h> 39#include <sys/ioctl.h> 40#include <linux/fs.h> 41#include "tst_test.h" 42#include "lapi/fallocate.h" 43 44#define MNTPOINT "mntpoint" 45#define TEMPFILE MNTPOINT "/test_file" 46#define WRITE_BLOCKS 8 47#define FALLOCATE_BLOCKS 2 48#define DEALLOCATE_BLOCKS 3 49#define TESTED_FLAGS "fallocate(FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE)" 50 51const struct test_case { 52 int no_cow, fill_fs; 53} testcase_list[] = { 54 {1, 0}, 55 {1, 1}, 56 {0, 0}, 57 {0, 1} 58}; 59 60static int cow_support; 61static char *wbuf, *rbuf; 62static blksize_t blocksize; 63static long wbuf_size, rbuf_size, block_offset; 64 65static int toggle_cow(int fd, int enable) 66{ 67 int ret, attr; 68 69 ret = ioctl(fd, FS_IOC_GETFLAGS, &attr); 70 71 if (ret) 72 return ret; 73 74 if (enable) 75 attr &= ~FS_NOCOW_FL; 76 else 77 attr |= FS_NOCOW_FL; 78 79 return ioctl(fd, FS_IOC_SETFLAGS, &attr); 80} 81 82static void setup(void) 83{ 84 unsigned char ch; 85 long i; 86 int fd; 87 struct stat statbuf; 88 89 fd = SAFE_OPEN(TEMPFILE, O_WRONLY | O_CREAT | O_TRUNC, 0644); 90 91 /* 92 * Set FS_NOCOW_FL flag on the temp file. Non-CoW filesystems will 93 * return error. 94 */ 95 TEST(toggle_cow(fd, 0)); 96 SAFE_FSTAT(fd, &statbuf); 97 blocksize = statbuf.st_blksize; 98 block_offset = MIN(blocksize / 2, (blksize_t)512); 99 wbuf_size = MAX(WRITE_BLOCKS, FALLOCATE_BLOCKS) * blocksize; 100 rbuf_size = (DEALLOCATE_BLOCKS + 1) * blocksize; 101 SAFE_CLOSE(fd); 102 SAFE_UNLINK(TEMPFILE); 103 104 if (blocksize < 2) 105 tst_brk(TCONF, "Block size %ld too small for test", blocksize); 106 107 if (!TST_RET) { 108 cow_support = 1; 109 } else { 110 switch (TST_ERR) { 111 case ENOTSUP: 112 case ENOTTY: 113 case EINVAL: 114 case ENOSYS: 115 cow_support = 0; 116 break; 117 118 default: 119 tst_brk(TBROK|TTERRNO, 120 "Error checking copy-on-write support"); 121 break; 122 } 123 } 124 125 tst_res(TINFO, "Copy-on-write is%s supported", 126 cow_support ? "" : " not"); 127 wbuf = SAFE_MALLOC(wbuf_size); 128 rbuf = SAFE_MALLOC(rbuf_size); 129 130 /* Fill the buffer with known values */ 131 for (i = 0, ch = 1; i < wbuf_size; i++, ch++) 132 wbuf[i] = ch; 133} 134 135static int check_result(const struct test_case *tc, const char *func, long exp) 136{ 137 if (tc->fill_fs && !tc->no_cow && TST_RET < 0) { 138 if (TST_RET != -1) { 139 tst_res(TFAIL, "%s returned unexpected value %ld", 140 func, TST_RET); 141 return 0; 142 } 143 144 if (TST_ERR != ENOSPC) { 145 tst_res(TFAIL | TTERRNO, "%s should fail with ENOSPC", 146 func); 147 return 0; 148 } 149 150 tst_res(TPASS | TTERRNO, "%s on full FS with CoW", func); 151 return 1; 152 } 153 154 if (TST_RET < 0) { 155 tst_res(TFAIL | TTERRNO, "%s failed unexpectedly", func); 156 return 0; 157 } 158 159 if (TST_RET != exp) { 160 tst_res(TFAIL, 161 "Unexpected return value from %s: %ld (expected %ld)", 162 func, TST_RET, exp); 163 return 0; 164 } 165 166 tst_res(TPASS, "%s successful", func); 167 return 1; 168} 169 170static void run(unsigned int n) 171{ 172 int fd, i, err; 173 long offset, size; 174 const struct test_case *tc = testcase_list + n; 175 176 tst_res(TINFO, "Case %u. Fill FS: %s; Use copy on write: %s", n+1, 177 tc->fill_fs ? "yes" : "no", tc->no_cow ? "no" : "yes"); 178 fd = SAFE_OPEN(TEMPFILE, O_RDWR | O_CREAT | O_TRUNC, 0644); 179 180 if (cow_support) 181 toggle_cow(fd, !tc->no_cow); 182 else if (!tc->no_cow) 183 tst_brk(TCONF, "File system does not support copy-on-write"); 184 185 /* Prepare test data for deallocation test */ 186 size = WRITE_BLOCKS * blocksize; 187 SAFE_WRITE(SAFE_WRITE_ALL, fd, wbuf, size); 188 189 /* Allocation test */ 190 offset = size + block_offset; 191 size = FALLOCATE_BLOCKS * blocksize; 192 TEST(fallocate(fd, 0, offset, size)); 193 194 if (TST_RET) { 195 SAFE_CLOSE(fd); 196 197 if (TST_ERR == ENOTSUP) 198 tst_brk(TCONF | TTERRNO, "fallocate() not supported"); 199 200 tst_brk(TBROK | TTERRNO, "fallocate(fd, 0, %ld, %ld)", offset, 201 size); 202 } 203 204 if (tc->fill_fs) 205 tst_fill_fs(MNTPOINT, 1, TST_FILL_RANDOM); 206 207 SAFE_LSEEK(fd, offset, SEEK_SET); 208 TEST(write(fd, wbuf, size)); 209 if (check_result(tc, "write()", size)) 210 tst_res(TPASS, "Misaligned allocation works as expected"); 211 212 /* Deallocation test */ 213 size = DEALLOCATE_BLOCKS * blocksize; 214 offset = block_offset; 215 TEST(fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, offset, 216 size)); 217 218 if (TST_RET == -1 && TST_ERR == ENOTSUP) { 219 tst_res(TCONF | TTERRNO, TESTED_FLAGS); 220 goto end; 221 } 222 223 if (!check_result(tc, TESTED_FLAGS, 0) || TST_RET) 224 goto end; 225 226 /* Validate that fallocate() cleared the correct file range */ 227 SAFE_LSEEK(fd, 0, SEEK_SET); 228 SAFE_READ(1, fd, rbuf, rbuf_size); 229 230 for (err = 0, i = offset; i < offset + size; i++) { 231 if (rbuf[i]) { 232 err = 1; 233 break; 234 } 235 } 236 237 err = err || memcmp(rbuf, wbuf, offset); 238 offset += size; 239 size = rbuf_size - offset; 240 err = err || memcmp(rbuf + offset, wbuf + offset, size); 241 242 if (err) 243 tst_res(TFAIL, TESTED_FLAGS 244 " did not clear the correct file range."); 245 else 246 tst_res(TPASS, TESTED_FLAGS " cleared the correct file range"); 247 248end: 249 SAFE_CLOSE(fd); 250 tst_purge_dir(MNTPOINT); 251} 252 253static void cleanup(void) 254{ 255 free(wbuf); 256 free(rbuf); 257} 258 259static struct tst_test test = { 260 .test = run, 261 .tcnt = ARRAY_SIZE(testcase_list), 262 .needs_root = 1, 263 .mount_device = 1, 264 .mntpoint = MNTPOINT, 265 .all_filesystems = 1, 266 .setup = setup, 267 .cleanup = cleanup, 268 .tags = (const struct tst_tag[]) { 269 {"linux-git", "e093c4be760e"}, 270 {"linux-git", "6d4572a9d71d"}, 271 {} 272 } 273}; 274