/* * Copyright (c) 2015 Oracle and/or its affiliates. All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it would be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Description: * Test allocates a file with specified size then tests the following modes: * FALLOC_FL_PUNCH_HOLE, FALLOC_FL_ZERO_RANGE and FALLOC_FL_COLLAPSE_RANGE. * * Author: Alexey Kodanev */ #define _GNU_SOURCE #include #include #include #include #include #include #include "tst_test.h" #include "lapi/fallocate.h" static int fd; static size_t block_size; static size_t buf_size; #define MNTPOINT "fallocate" #define FNAME MNTPOINT "/fallocate.txt" #define NUM_OF_BLOCKS 3 static char *verbose; static void get_blocksize(void) { struct stat file_stat; SAFE_FSTAT(fd, &file_stat); block_size = file_stat.st_blksize; buf_size = NUM_OF_BLOCKS * block_size; } static size_t get_allocsize(void) { struct stat file_stat; fsync(fd); SAFE_FSTAT(fd, &file_stat); return file_stat.st_blocks * 512; } static void fill_tst_buf(char buf[]) { /* fill the buffer with a, b, c, ... letters on each block */ int i; for (i = 0; i < NUM_OF_BLOCKS; ++i) memset(buf + i * block_size, 'a' + i, block_size); } static void check_file_data(const char exp_buf[], size_t size) { char rbuf[size]; tst_res(TINFO, "reading the file, compare with expected buffer"); SAFE_LSEEK(fd, 0, SEEK_SET); SAFE_READ(1, fd, rbuf, size); if (memcmp(exp_buf, rbuf, size)) { if (verbose) { tst_res_hexd(TINFO, exp_buf, size, "expected:"); tst_res_hexd(TINFO, rbuf, size, "but read:"); } tst_brk(TFAIL, "not expected file data"); } } static void test01(void) { tst_res(TINFO, "allocate '%zu' bytes", buf_size); if (fallocate(fd, 0, 0, buf_size) == -1) { if (errno == ENOSYS || errno == EOPNOTSUPP) tst_brk(TCONF, "fallocate() not supported"); tst_brk(TFAIL | TERRNO, "fallocate() failed"); } char buf[buf_size]; fill_tst_buf(buf); SAFE_WRITE(1, fd, buf, buf_size); tst_res(TPASS, "test-case succeeded"); } static void test02(void) { size_t alloc_size0 = get_allocsize(); tst_res(TINFO, "read allocated file size '%zu'", alloc_size0); tst_res(TINFO, "make a hole with FALLOC_FL_PUNCH_HOLE"); if (tst_kvercmp(2, 6, 38) < 0) { tst_brk(TCONF, "FALLOC_FL_PUNCH_HOLE needs Linux 2.6.38 or newer"); } if (fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, block_size, block_size) == -1) { if (errno == EOPNOTSUPP) { tst_brk(TCONF, "FALLOC_FL_PUNCH_HOLE not supported"); } tst_brk(TFAIL | TERRNO, "fallocate() failed"); } tst_res(TINFO, "check that file has a hole with lseek(,,SEEK_HOLE)"); off_t ret = lseek(fd, 0, SEEK_HOLE); if (ret != (ssize_t)block_size) { /* exclude error when kernel doesn't have SEEK_HOLE support */ if (errno != EINVAL) { tst_brk(TFAIL | TERRNO, "fallocate() or lseek() failed"); } if (tst_kvercmp(3, 1, 0) < 0) { tst_res(TINFO, "lseek() doesn't support SEEK_HOLE, " "this is expected for < 3.1 kernels"); } else { tst_brk(TBROK | TERRNO, "lseek() doesn't support SEEK_HOLE"); } } else { tst_res(TINFO, "found a hole at '%ld' offset", ret); } size_t alloc_size1 = get_allocsize(); tst_res(TINFO, "allocated file size before '%zu' and after '%zu'", alloc_size0, alloc_size1); if ((alloc_size0 - block_size) != alloc_size1) tst_brk(TFAIL, "not expected allocated size"); char exp_buf[buf_size]; fill_tst_buf(exp_buf); memset(exp_buf + block_size, 0, block_size); check_file_data(exp_buf, buf_size); tst_res(TPASS, "test-case succeeded"); } static void test03(void) { tst_res(TINFO, "zeroing file space with FALLOC_FL_ZERO_RANGE"); if (tst_kvercmp(3, 15, 0) < 0) { tst_brk(TCONF, "FALLOC_FL_ZERO_RANGE needs Linux 3.15 or newer"); } size_t alloc_size0 = get_allocsize(); tst_res(TINFO, "read current allocated file size '%zu'", alloc_size0); if (fallocate(fd, FALLOC_FL_ZERO_RANGE, block_size - 1, block_size + 2) == -1) { if (errno == EOPNOTSUPP) { tst_brk(TCONF, "FALLOC_FL_ZERO_RANGE not supported"); } tst_brk(TFAIL | TERRNO, "fallocate failed"); } /* The file hole in the specified range must be allocated and * filled with zeros. Check it. */ size_t alloc_size1 = get_allocsize(); tst_res(TINFO, "allocated file size before '%zu' and after '%zu'", alloc_size0, alloc_size1); if ((alloc_size0 + block_size) != alloc_size1) tst_brk(TFAIL, "not expected allocated size"); char exp_buf[buf_size]; fill_tst_buf(exp_buf); memset(exp_buf + block_size - 1, 0, block_size + 2); check_file_data(exp_buf, buf_size); tst_res(TPASS, "test-case succeeded"); } static void test04(void) { tst_res(TINFO, "collapsing file space with FALLOC_FL_COLLAPSE_RANGE"); size_t alloc_size0 = get_allocsize(); tst_res(TINFO, "read current allocated file size '%zu'", alloc_size0); if (fallocate(fd, FALLOC_FL_COLLAPSE_RANGE, block_size, block_size) == -1) { if (errno == EOPNOTSUPP) { tst_brk(TCONF, "FALLOC_FL_COLLAPSE_RANGE not supported"); } tst_brk(TFAIL | TERRNO, "fallocate failed"); } size_t alloc_size1 = get_allocsize(); tst_res(TINFO, "allocated file size before '%zu' and after '%zu'", alloc_size0, alloc_size1); if ((alloc_size0 - block_size) != alloc_size1) tst_brk(TFAIL, "not expected allocated size"); size_t size = buf_size - block_size; char tmp_buf[buf_size]; char exp_buf[size]; fill_tst_buf(tmp_buf); memcpy(exp_buf, tmp_buf, block_size); memcpy(exp_buf + block_size, tmp_buf + 2 * block_size, buf_size - block_size * 2); exp_buf[block_size - 1] = exp_buf[block_size] = '\0'; check_file_data(exp_buf, size); tst_res(TPASS, "test-case succeeded"); } static void test05(void) { tst_res(TINFO, "inserting space with FALLOC_FL_INSERT_RANGE"); size_t alloc_size0 = get_allocsize(); tst_res(TINFO, "read current allocated file size '%zu'", alloc_size0); if (fallocate(fd, FALLOC_FL_INSERT_RANGE, block_size, block_size) == -1) { if (errno == EOPNOTSUPP) { tst_brk(TCONF, "FALLOC_FL_INSERT_RANGE not supported"); } tst_brk(TFAIL | TERRNO, "fallocate failed"); } /* allocate space and ensure that it filled with zeroes */ if (fallocate(fd, FALLOC_FL_ZERO_RANGE, block_size, block_size) == -1) tst_brk(TFAIL | TERRNO, "fallocate failed"); size_t alloc_size1 = get_allocsize(); tst_res(TINFO, "allocated file size before '%zu' and after '%zu'", alloc_size0, alloc_size1); if ((alloc_size0 + block_size) != alloc_size1) tst_brk(TFAIL, "not expected allocated size"); char exp_buf[buf_size]; fill_tst_buf(exp_buf); memset(exp_buf + block_size - 1, 0, block_size + 2); check_file_data(exp_buf, buf_size); tst_res(TPASS, "test-case succeeded"); } static void (*tcases[])(void) = { test01, test02, test03, test04, test05 }; static void run(unsigned int i) { tcases[i](); } static void setup(void) { fd = SAFE_OPEN(FNAME, O_RDWR | O_CREAT, 0700); get_blocksize(); } static void cleanup(void) { if (fd > 0) SAFE_CLOSE(fd); } static struct tst_option opts[] = { {"v", &verbose, "-v Turns on verbose mode"}, {NULL, NULL, NULL} }; static struct tst_test test = { .options = opts, .cleanup = cleanup, .setup = setup, .test = run, .tcnt = ARRAY_SIZE(tcases), .mount_device = 1, .mntpoint = MNTPOINT, .all_filesystems = 1, .needs_tmpdir = 1, .needs_root = 1, };