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