// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2017 Red Hat, Inc. All rights reserved. * Author: Zorro Lang * * Test functional SEEK_HOLE and SEEK_DATA of lseek(2). * * Since version 3.1, Linux supports the following additional values for * whence: * * SEEK_DATA * Adjust the file offset to the next location in the file greater than * or equal to offset containing data. If offset points to data, * then the file offset is set to offset. * * SEEK_HOLE * Adjust the file offset to the next hole in the file greater than or * equal to offset. If offset points into the middle of a hole, then * the file offset is set to offset. If there is no hole past offset, * then the file offset is adjusted to the end of the file (i.e., there * is an implicit hole at the end of any file). */ #define _GNU_SOURCE #include #include #include #include #include #include #include "tst_test.h" #include "tst_safe_prw.h" #include "lapi/seek.h" /* * This case create 3 holes and 4 data fields, every (data) is 12 bytes, * every UNIT has UNIT_BLOCKS * block_size bytes. The structure as below: * * ---------------------------------------------------------------------------------------------- * data01suffix (hole) data02suffix (hole) data03suffix (hole) data04sufix * ---------------------------------------------------------------------------------------------- * |<--- UNIT_BLOCKS blocks --->||<--- UNIT_BLOCKS blocks --->||<--- UNIT_BLOCKS blocks --->| * */ #define UNIT_COUNT 3 #define UNIT_BLOCKS 10 #define FILE_BLOCKS (UNIT_BLOCKS * UNIT_COUNT) static int fd; static blksize_t block_size; /* * SEEK from "startblock * block_size - offset", "whence" as the directive * whence. * startblock * block_size - offset: as offset of lseek() * whence: as whence of lseek() * data: as the expected result read from file offset. NULL means expect * the end of file. * count: as the count read from file */ static struct tparam { off_t startblock; off_t offset; int whence; char *data; size_t count; } tparams[] = { {0, 0, SEEK_DATA, "data01", 6}, /* SEEK_DATA from starting of file*/ {0, 4, SEEK_DATA, "01suffix", 8}, /* SEEK_DATA from maddle of the first data */ {0, 0, SEEK_HOLE, "", 1023}, /* SEEK_HOLE from starting of file */ {0, 4, SEEK_HOLE, "", 1023}, /* SEEK_HOLE from maddle of the first data */ {1, 0, SEEK_HOLE, "", 1023}, /* SEEK_HOLE from the starting of the first hole */ {1, 128, SEEK_HOLE, "", 1023}, /* SEEK_HOLE from maddle of the first hole */ {1, 0, SEEK_DATA, "data02", 6}, /* SEEK_DATA from the starting of the first hole */ {UNIT_BLOCKS, -1, SEEK_DATA, "data02", 6}, /* SEEK_DATA from the tail of the first hole */ {UNIT_BLOCKS, 0, SEEK_DATA, "data02", 6}, /* SEEK_DATA from the starting of the second data */ {UNIT_BLOCKS, 4, SEEK_DATA, "02suffix", 8}, /* SEEK_DATA from middle of the second data */ {UNIT_BLOCKS, 0, SEEK_HOLE, "", 1023}, /* SEEK_HOLE from the starting of the second data */ {UNIT_BLOCKS, 4, SEEK_HOLE, "", 1023}, /* SEEK_HOLE from middle of the second data */ {UNIT_BLOCKS + 1, 128, SEEK_HOLE, "", 1023}, /* SEEK_HOLE from middle of the second hole */ {UNIT_BLOCKS + 1, 128, SEEK_DATA, "data03", 6}, /* SEEK_DATA from middle of the second hole */ {FILE_BLOCKS, -128, SEEK_HOLE, NULL, 0}, /* SEEK_HOLE from no hole pass offset*/ }; static void cleanup(void) { SAFE_CLOSE(fd); } static void get_blocksize(void) { off_t pos = 0, offset = 128; int shift; struct stat st; SAFE_FSTAT(fd, &st); /* try to discover the actual alloc size */ while (pos == 0 && offset < (st.st_blksize * 2)) { offset <<= 1; SAFE_FTRUNCATE(fd, 0); SAFE_PWRITE(1, fd, "a", 1, offset); SAFE_FSYNC(fd); pos = lseek(fd, 0, SEEK_DATA); if (pos == -1) { if (errno == EINVAL || errno == EOPNOTSUPP) { tst_brk(TCONF | TERRNO, "SEEK_DATA " "and SEEK_HOLE not implemented"); } tst_brk(TBROK | TERRNO, "SEEK_DATA failed"); } } /* bisect for double check */ shift = offset >> 2; while (shift && offset < (st.st_blksize * 2)) { SAFE_FTRUNCATE(fd, 0); SAFE_PWRITE(1, fd, "a", 1, offset); SAFE_FSYNC(fd); pos = SAFE_LSEEK(fd, 0, SEEK_DATA); offset += pos ? -shift : shift; shift >>= 1; } if (!shift) offset += pos ? 0 : 1; block_size = offset; /* * Due to some filesystems use generic_file_llseek(), e.g: CIFS, * it thinks the entire file is data, only a virtual hole at the end * of the file. This case can't test this situation, so if the minimum * alloc size we got bigger then st.st_blksize, we think it's not * a valid value. */ if (block_size > st.st_blksize) { tst_brk(TCONF, "filesystem maybe use generic_file_llseek(), not support real SEEK_DATA/SEEK_HOLE"); } } static void write_data(int fd, int num) { char buf[64]; sprintf(buf, "data%02dsuffix", num); SAFE_WRITE(1, fd, buf, strlen(buf)); } static void setup(void) { int i; off_t offset = 0; char fname[255]; sprintf(fname, "tfile_lseek_%d", getpid()); fd = SAFE_OPEN(fname, O_RDWR | O_CREAT, 0666); get_blocksize(); tst_res(TINFO, "The block size is %lu", block_size); /* * truncate to the expected file size directly, to keep away the effect * of speculative preallocation of some filesystems (e.g. XFS) */ SAFE_FTRUNCATE(fd, FILE_BLOCKS * block_size); SAFE_LSEEK(fd, 0, SEEK_HOLE); for (i = 0; i < UNIT_COUNT; i++) { offset = UNIT_BLOCKS * block_size * i; SAFE_LSEEK(fd, offset, SEEK_SET); write_data(fd, i + 1); } SAFE_LSEEK(fd, -128, SEEK_END); write_data(fd, i + 1); SAFE_FSYNC(fd); SAFE_LSEEK(fd, 0, SEEK_SET); } static void test_lseek(unsigned int n) { struct tparam *tp = &tparams[n]; off_t offset; char buf[1024]; int rc = 0; memset(buf, 0, sizeof(buf)); offset = (tp->startblock * block_size) + tp->offset; offset = SAFE_LSEEK(fd, offset, tp->whence); if (tp->data) { SAFE_READ(1, fd, buf, tp->count); rc = strcmp(buf, tp->data); } else { if (offset != SAFE_LSEEK(fd, 0, SEEK_END)) rc = 1; } if (rc != 0) { tst_res(TFAIL, "The %uth test failed: %s from startblock %ld offset %ld, expect \'%s\' return \'%s\'", n, (tp->whence == SEEK_DATA) ? "SEEK_DATA" : "SEEK_HOLE", tp->startblock, tp->offset, tp->data ? tp->data : "", buf); } else { tst_res(TPASS, "The %uth test passed: %s from startblock %ld offset %ld", n, (tp->whence == SEEK_DATA) ? "SEEK_DATA" : "SEEK_HOLE", tp->startblock, tp->offset); } } static struct tst_test test = { .tcnt = ARRAY_SIZE(tparams), .test = test_lseek, .setup = setup, .cleanup = cleanup, .needs_tmpdir = 1, };