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