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,
|
|
};
|