223 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			223 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			C
		
	
	
	
| // SPDX-License-Identifier: GPL-2.0-or-later
 | |
| /*
 | |
|  * Copyright (C) 2017 Red Hat, Inc.  All rights reserved.
 | |
|  * Author: Zorro Lang <zlang@redhat.com>
 | |
|  *
 | |
|  *  Test functional SEEK_HOLE and SEEK_DATA of lseek(2).
 | |
|  *
 | |
|  *  Since version 3.1, Linux supports the following additional values for
 | |
|  *  whence:
 | |
|  *
 | |
|  *  SEEK_DATA
 | |
|  *       Adjust the file offset to the next location in the file greater than
 | |
|  *       or  equal  to  offset  containing data.  If offset points to data,
 | |
|  *       then the file offset is set to offset.
 | |
|  *
 | |
|  *  SEEK_HOLE
 | |
|  *       Adjust the file offset to the next hole in the file greater than or
 | |
|  *       equal to offset.  If offset points into the middle of a hole, then
 | |
|  *       the file offset is set to offset. If there is no hole past offset,
 | |
|  *       then the file offset is adjusted to the end of the file (i.e., there
 | |
|  *       is an implicit hole at the end of any file).
 | |
|  */
 | |
| 
 | |
| #define _GNU_SOURCE
 | |
| #include <sys/types.h>
 | |
| #include <unistd.h>
 | |
| #include <fcntl.h>
 | |
| #include <stdio.h>
 | |
| #include <string.h>
 | |
| #include <errno.h>
 | |
| 
 | |
| #include "tst_test.h"
 | |
| #include "tst_safe_prw.h"
 | |
| #include "lapi/seek.h"
 | |
| 
 | |
| /*
 | |
|  * This case create 3 holes and 4 data fields, every (data) is 12 bytes,
 | |
|  * every UNIT has UNIT_BLOCKS * block_size bytes. The structure as below:
 | |
|  *
 | |
|  * ----------------------------------------------------------------------------------------------
 | |
|  * data01suffix      (hole)      data02suffix      (hole)       data03suffix  (hole)  data04sufix
 | |
|  * ----------------------------------------------------------------------------------------------
 | |
|  * |<--- UNIT_BLOCKS blocks --->||<--- UNIT_BLOCKS blocks  --->||<---  UNIT_BLOCKS blocks   --->|
 | |
|  *
 | |
|  */
 | |
| #define UNIT_COUNT   3
 | |
| #define UNIT_BLOCKS  10
 | |
| #define FILE_BLOCKS  (UNIT_BLOCKS * UNIT_COUNT)
 | |
| 
 | |
| static int fd;
 | |
| static blksize_t block_size;
 | |
| 
 | |
| /*
 | |
|  * SEEK from "startblock * block_size - offset", "whence" as the directive
 | |
|  * whence.
 | |
|  * startblock * block_size - offset: as offset of lseek()
 | |
|  * whence: as whence of lseek()
 | |
|  * data: as the expected result read from file offset. NULL means expect
 | |
|  *       the end of file.
 | |
|  * count: as the count read from file
 | |
|  */
 | |
| static struct tparam {
 | |
| 	off_t  startblock;
 | |
| 	off_t  offset;
 | |
| 	int    whence;
 | |
| 	char   *data;
 | |
| 	size_t count;
 | |
| } tparams[] = {
 | |
| 	{0,               0,    SEEK_DATA, "data01",   6},    /* SEEK_DATA from starting of file*/
 | |
| 	{0,               4,    SEEK_DATA, "01suffix", 8},    /* SEEK_DATA from maddle of the first data */
 | |
| 	{0,               0,    SEEK_HOLE, "",         1023}, /* SEEK_HOLE from starting of file */
 | |
| 	{0,               4,    SEEK_HOLE, "",         1023}, /* SEEK_HOLE from maddle of the first data */
 | |
| 	{1,               0,    SEEK_HOLE, "",         1023}, /* SEEK_HOLE from the starting of the first hole */
 | |
| 	{1,               128,  SEEK_HOLE, "",         1023}, /* SEEK_HOLE from maddle of the first hole */
 | |
| 	{1,               0,    SEEK_DATA, "data02",   6},    /* SEEK_DATA from the starting of the first hole */
 | |
| 	{UNIT_BLOCKS,     -1,   SEEK_DATA, "data02",   6},    /* SEEK_DATA from the tail of the first hole */
 | |
| 	{UNIT_BLOCKS,     0,    SEEK_DATA, "data02",   6},    /* SEEK_DATA from the starting of the second data */
 | |
| 	{UNIT_BLOCKS,     4,    SEEK_DATA, "02suffix", 8},    /* SEEK_DATA from middle of the second data */
 | |
| 	{UNIT_BLOCKS,     0,    SEEK_HOLE, "",         1023}, /* SEEK_HOLE from the starting of the second data */
 | |
| 	{UNIT_BLOCKS,     4,    SEEK_HOLE, "",         1023}, /* SEEK_HOLE from middle of the second data */
 | |
| 	{UNIT_BLOCKS + 1, 128,  SEEK_HOLE, "",         1023}, /* SEEK_HOLE from middle of the second hole */
 | |
| 	{UNIT_BLOCKS + 1, 128,  SEEK_DATA, "data03",   6},    /* SEEK_DATA from middle of the second hole */
 | |
| 	{FILE_BLOCKS,    -128,  SEEK_HOLE, NULL,       0},    /* SEEK_HOLE from no hole pass offset*/
 | |
| };
 | |
| 
 | |
| static void cleanup(void)
 | |
| {
 | |
| 	SAFE_CLOSE(fd);
 | |
| }
 | |
| 
 | |
| static void get_blocksize(void)
 | |
| {
 | |
| 	off_t pos = 0, offset = 128;
 | |
| 	int shift;
 | |
| 	struct stat st;
 | |
| 
 | |
| 	SAFE_FSTAT(fd, &st);
 | |
| 
 | |
| 	/* try to discover the actual alloc size */
 | |
| 	while (pos == 0 && offset < (st.st_blksize * 2)) {
 | |
| 		offset <<= 1;
 | |
| 		SAFE_FTRUNCATE(fd, 0);
 | |
| 		SAFE_PWRITE(1, fd, "a", 1, offset);
 | |
| 		SAFE_FSYNC(fd);
 | |
| 		pos = lseek(fd, 0, SEEK_DATA);
 | |
| 		if (pos == -1) {
 | |
| 			if (errno == EINVAL || errno == EOPNOTSUPP) {
 | |
| 				tst_brk(TCONF | TERRNO, "SEEK_DATA "
 | |
| 					"and SEEK_HOLE not implemented");
 | |
| 			}
 | |
| 			tst_brk(TBROK | TERRNO, "SEEK_DATA failed");
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* bisect for double check */
 | |
| 	shift = offset >> 2;
 | |
| 	while (shift && offset < (st.st_blksize * 2)) {
 | |
| 		SAFE_FTRUNCATE(fd, 0);
 | |
| 		SAFE_PWRITE(1, fd, "a", 1, offset);
 | |
| 		SAFE_FSYNC(fd);
 | |
| 		pos = SAFE_LSEEK(fd, 0, SEEK_DATA);
 | |
| 		offset += pos ? -shift : shift;
 | |
| 		shift >>= 1;
 | |
| 	}
 | |
| 
 | |
| 	if (!shift)
 | |
| 		offset += pos ? 0 : 1;
 | |
| 	block_size = offset;
 | |
| 
 | |
| 	/*
 | |
| 	 * Due to some filesystems use generic_file_llseek(), e.g: CIFS,
 | |
| 	 * it thinks the entire file is data, only a virtual hole at the end
 | |
| 	 * of the file. This case can't test this situation, so if the minimum
 | |
| 	 * alloc size we got bigger then st.st_blksize, we think it's not
 | |
| 	 * a valid value.
 | |
| 	 */
 | |
| 	if (block_size > st.st_blksize) {
 | |
| 		tst_brk(TCONF,
 | |
| 		        "filesystem maybe use generic_file_llseek(), not support real SEEK_DATA/SEEK_HOLE");
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void write_data(int fd, int num)
 | |
| {
 | |
| 	char buf[64];
 | |
| 
 | |
| 	sprintf(buf, "data%02dsuffix", num);
 | |
| 	SAFE_WRITE(1, fd, buf, strlen(buf));
 | |
| }
 | |
| 
 | |
| static void setup(void)
 | |
| {
 | |
| 	int i;
 | |
| 	off_t offset = 0;
 | |
| 	char fname[255];
 | |
| 
 | |
| 	sprintf(fname, "tfile_lseek_%d", getpid());
 | |
| 
 | |
| 	fd = SAFE_OPEN(fname, O_RDWR | O_CREAT, 0666);
 | |
| 
 | |
| 	get_blocksize();
 | |
| 	tst_res(TINFO, "The block size is %lu", block_size);
 | |
| 
 | |
| 	/*
 | |
| 	 * truncate to the expected file size directly, to keep away the effect
 | |
| 	 * of speculative preallocation of some filesystems (e.g. XFS)
 | |
| 	 */
 | |
| 	SAFE_FTRUNCATE(fd, FILE_BLOCKS * block_size);
 | |
| 
 | |
| 	SAFE_LSEEK(fd, 0, SEEK_HOLE);
 | |
| 
 | |
| 	for (i = 0; i < UNIT_COUNT; i++) {
 | |
| 		offset = UNIT_BLOCKS * block_size * i;
 | |
| 		SAFE_LSEEK(fd, offset, SEEK_SET);
 | |
| 		write_data(fd, i + 1);
 | |
| 	}
 | |
| 
 | |
| 	SAFE_LSEEK(fd, -128, SEEK_END);
 | |
| 	write_data(fd, i + 1);
 | |
| 
 | |
| 	SAFE_FSYNC(fd);
 | |
| 	SAFE_LSEEK(fd, 0, SEEK_SET);
 | |
| }
 | |
| 
 | |
| static void test_lseek(unsigned int n)
 | |
| {
 | |
| 	struct tparam *tp = &tparams[n];
 | |
| 	off_t offset;
 | |
| 	char buf[1024];
 | |
| 	int rc = 0;
 | |
| 
 | |
| 	memset(buf, 0, sizeof(buf));
 | |
| 	offset = (tp->startblock * block_size) + tp->offset;
 | |
| 	offset = SAFE_LSEEK(fd, offset, tp->whence);
 | |
| 	if (tp->data) {
 | |
| 		SAFE_READ(1, fd, buf, tp->count);
 | |
| 		rc = strcmp(buf, tp->data);
 | |
| 	} else {
 | |
| 		if (offset != SAFE_LSEEK(fd, 0, SEEK_END))
 | |
| 			rc = 1;
 | |
| 	}
 | |
| 
 | |
| 	if (rc != 0) {
 | |
| 		tst_res(TFAIL,
 | |
| 		        "The %uth test failed: %s from startblock %ld offset %ld, expect \'%s\' return \'%s\'",
 | |
| 		        n, (tp->whence == SEEK_DATA) ? "SEEK_DATA" : "SEEK_HOLE",
 | |
| 		        tp->startblock, tp->offset, tp->data ? tp->data : "", buf);
 | |
| 	} else {
 | |
| 		tst_res(TPASS,
 | |
| 		        "The %uth test passed: %s from startblock %ld offset %ld",
 | |
| 		        n, (tp->whence == SEEK_DATA) ? "SEEK_DATA" : "SEEK_HOLE",
 | |
| 		        tp->startblock, tp->offset);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static struct tst_test test = {
 | |
| 	.tcnt         = ARRAY_SIZE(tparams),
 | |
| 	.test         = test_lseek,
 | |
| 	.setup        = setup,
 | |
| 	.cleanup      = cleanup,
 | |
| 	.needs_tmpdir = 1,
 | |
| };
 |