1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Copyright (C) 2017 Red Hat, Inc. All rights reserved. 4 * Author: Zorro Lang <zlang@redhat.com> 5 * 6 * Test functional SEEK_HOLE and SEEK_DATA of lseek(2). 7 * 8 * Since version 3.1, Linux supports the following additional values for 9 * whence: 10 * 11 * SEEK_DATA 12 * Adjust the file offset to the next location in the file greater than 13 * or equal to offset containing data. If offset points to data, 14 * then the file offset is set to offset. 15 * 16 * SEEK_HOLE 17 * Adjust the file offset to the next hole in the file greater than or 18 * equal to offset. If offset points into the middle of a hole, then 19 * the file offset is set to offset. If there is no hole past offset, 20 * then the file offset is adjusted to the end of the file (i.e., there 21 * is an implicit hole at the end of any file). 22 */ 23 24#define _GNU_SOURCE 25#include <sys/types.h> 26#include <unistd.h> 27#include <fcntl.h> 28#include <stdio.h> 29#include <string.h> 30#include <errno.h> 31 32#include "tst_test.h" 33#include "tst_safe_prw.h" 34#include "lapi/seek.h" 35 36/* 37 * This case create 3 holes and 4 data fields, every (data) is 12 bytes, 38 * every UNIT has UNIT_BLOCKS * block_size bytes. The structure as below: 39 * 40 * ---------------------------------------------------------------------------------------------- 41 * data01suffix (hole) data02suffix (hole) data03suffix (hole) data04sufix 42 * ---------------------------------------------------------------------------------------------- 43 * |<--- UNIT_BLOCKS blocks --->||<--- UNIT_BLOCKS blocks --->||<--- UNIT_BLOCKS blocks --->| 44 * 45 */ 46#define UNIT_COUNT 3 47#define UNIT_BLOCKS 10 48#define FILE_BLOCKS (UNIT_BLOCKS * UNIT_COUNT) 49 50static int fd; 51static blksize_t block_size; 52 53/* 54 * SEEK from "startblock * block_size - offset", "whence" as the directive 55 * whence. 56 * startblock * block_size - offset: as offset of lseek() 57 * whence: as whence of lseek() 58 * data: as the expected result read from file offset. NULL means expect 59 * the end of file. 60 * count: as the count read from file 61 */ 62static struct tparam { 63 off_t startblock; 64 off_t offset; 65 int whence; 66 char *data; 67 size_t count; 68} tparams[] = { 69 {0, 0, SEEK_DATA, "data01", 6}, /* SEEK_DATA from starting of file*/ 70 {0, 4, SEEK_DATA, "01suffix", 8}, /* SEEK_DATA from maddle of the first data */ 71 {0, 0, SEEK_HOLE, "", 1023}, /* SEEK_HOLE from starting of file */ 72 {0, 4, SEEK_HOLE, "", 1023}, /* SEEK_HOLE from maddle of the first data */ 73 {1, 0, SEEK_HOLE, "", 1023}, /* SEEK_HOLE from the starting of the first hole */ 74 {1, 128, SEEK_HOLE, "", 1023}, /* SEEK_HOLE from maddle of the first hole */ 75 {1, 0, SEEK_DATA, "data02", 6}, /* SEEK_DATA from the starting of the first hole */ 76 {UNIT_BLOCKS, -1, SEEK_DATA, "data02", 6}, /* SEEK_DATA from the tail of the first hole */ 77 {UNIT_BLOCKS, 0, SEEK_DATA, "data02", 6}, /* SEEK_DATA from the starting of the second data */ 78 {UNIT_BLOCKS, 4, SEEK_DATA, "02suffix", 8}, /* SEEK_DATA from middle of the second data */ 79 {UNIT_BLOCKS, 0, SEEK_HOLE, "", 1023}, /* SEEK_HOLE from the starting of the second data */ 80 {UNIT_BLOCKS, 4, SEEK_HOLE, "", 1023}, /* SEEK_HOLE from middle of the second data */ 81 {UNIT_BLOCKS + 1, 128, SEEK_HOLE, "", 1023}, /* SEEK_HOLE from middle of the second hole */ 82 {UNIT_BLOCKS + 1, 128, SEEK_DATA, "data03", 6}, /* SEEK_DATA from middle of the second hole */ 83 {FILE_BLOCKS, -128, SEEK_HOLE, NULL, 0}, /* SEEK_HOLE from no hole pass offset*/ 84}; 85 86static void cleanup(void) 87{ 88 SAFE_CLOSE(fd); 89} 90 91static void get_blocksize(void) 92{ 93 off_t pos = 0, offset = 128; 94 int shift; 95 struct stat st; 96 97 SAFE_FSTAT(fd, &st); 98 99 /* try to discover the actual alloc size */ 100 while (pos == 0 && offset < (st.st_blksize * 2)) { 101 offset <<= 1; 102 SAFE_FTRUNCATE(fd, 0); 103 SAFE_PWRITE(1, fd, "a", 1, offset); 104 SAFE_FSYNC(fd); 105 pos = lseek(fd, 0, SEEK_DATA); 106 if (pos == -1) { 107 if (errno == EINVAL || errno == EOPNOTSUPP) { 108 tst_brk(TCONF | TERRNO, "SEEK_DATA " 109 "and SEEK_HOLE not implemented"); 110 } 111 tst_brk(TBROK | TERRNO, "SEEK_DATA failed"); 112 } 113 } 114 115 /* bisect for double check */ 116 shift = offset >> 2; 117 while (shift && offset < (st.st_blksize * 2)) { 118 SAFE_FTRUNCATE(fd, 0); 119 SAFE_PWRITE(1, fd, "a", 1, offset); 120 SAFE_FSYNC(fd); 121 pos = SAFE_LSEEK(fd, 0, SEEK_DATA); 122 offset += pos ? -shift : shift; 123 shift >>= 1; 124 } 125 126 if (!shift) 127 offset += pos ? 0 : 1; 128 block_size = offset; 129 130 /* 131 * Due to some filesystems use generic_file_llseek(), e.g: CIFS, 132 * it thinks the entire file is data, only a virtual hole at the end 133 * of the file. This case can't test this situation, so if the minimum 134 * alloc size we got bigger then st.st_blksize, we think it's not 135 * a valid value. 136 */ 137 if (block_size > st.st_blksize) { 138 tst_brk(TCONF, 139 "filesystem maybe use generic_file_llseek(), not support real SEEK_DATA/SEEK_HOLE"); 140 } 141} 142 143static void write_data(int fd, int num) 144{ 145 char buf[64]; 146 147 sprintf(buf, "data%02dsuffix", num); 148 SAFE_WRITE(SAFE_WRITE_ALL, fd, buf, strlen(buf)); 149} 150 151static void setup(void) 152{ 153 int i; 154 off_t offset = 0; 155 char fname[255]; 156 157 sprintf(fname, "tfile_lseek_%d", getpid()); 158 159 fd = SAFE_OPEN(fname, O_RDWR | O_CREAT, 0666); 160 161 get_blocksize(); 162 tst_res(TINFO, "The block size is %lld", (long long int)block_size); 163 164 /* 165 * truncate to the expected file size directly, to keep away the effect 166 * of speculative preallocation of some filesystems (e.g. XFS) 167 */ 168 SAFE_FTRUNCATE(fd, FILE_BLOCKS * block_size); 169 170 SAFE_LSEEK(fd, 0, SEEK_HOLE); 171 172 for (i = 0; i < UNIT_COUNT; i++) { 173 offset = UNIT_BLOCKS * block_size * i; 174 SAFE_LSEEK(fd, offset, SEEK_SET); 175 write_data(fd, i + 1); 176 } 177 178 SAFE_LSEEK(fd, -128, SEEK_END); 179 write_data(fd, i + 1); 180 181 SAFE_FSYNC(fd); 182 SAFE_LSEEK(fd, 0, SEEK_SET); 183} 184 185static void test_lseek(unsigned int n) 186{ 187 struct tparam *tp = &tparams[n]; 188 off_t offset; 189 char buf[1024]; 190 int rc = 0; 191 192 memset(buf, 0, sizeof(buf)); 193 offset = (tp->startblock * block_size) + tp->offset; 194 offset = SAFE_LSEEK(fd, offset, tp->whence); 195 if (tp->data) { 196 SAFE_READ(1, fd, buf, tp->count); 197 rc = strcmp(buf, tp->data); 198 } else { 199 if (offset != SAFE_LSEEK(fd, 0, SEEK_END)) 200 rc = 1; 201 } 202 203 if (rc != 0) { 204 tst_res(TFAIL, 205 "The %uth test failed: %s from startblock %lld offset %lld, expect \'%s\' return \'%s\'", 206 n, (tp->whence == SEEK_DATA) ? "SEEK_DATA" : "SEEK_HOLE", 207 (long long int)tp->startblock, (long long int)tp->offset, 208 tp->data ? tp->data : "", buf); 209 } else { 210 tst_res(TPASS, 211 "The %uth test passed: %s from startblock %lld offset %lld", 212 n, (tp->whence == SEEK_DATA) ? "SEEK_DATA" : "SEEK_HOLE", 213 (long long int)tp->startblock, (long long int)tp->offset); 214 } 215} 216 217static struct tst_test test = { 218 .tcnt = ARRAY_SIZE(tparams), 219 .test = test_lseek, 220 .setup = setup, 221 .cleanup = cleanup, 222 .needs_tmpdir = 1, 223}; 224