/*
* Copyright (C) 2017 Red Hat, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* AUTHOR: Zorro Lang <zlang@redhat.com>
*
* DESCRIPTION
* This case does functional SEEK_HOLE and SEEK_DATA of lseek(2) testing.
*
* 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,
};