Home | History | Annotate | Download | only in lseek
      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 (at) 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 
    101 static void cleanup(void)
    102 {
    103 	SAFE_CLOSE(fd);
    104 }
    105 
    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 || errno == EOPNOTSUPP) {
    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 
    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 
    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 
    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 	.tcnt         = ARRAY_SIZE(tparams),
    233 	.test         = test_lseek,
    234 	.setup        = setup,
    235 	.cleanup      = cleanup,
    236 	.needs_tmpdir = 1,
    237 };
    238