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