142 lines
3.7 KiB
C
142 lines
3.7 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
*
|
|
* Copyright (c) Linux Test Project, 2016
|
|
*/
|
|
|
|
/*
|
|
* Test Description:
|
|
* Verify writev() behaviour with partially valid iovec list.
|
|
* Kernel <4.8 used to shorten write up to first bad invalid
|
|
* iovec. Starting with 4.8, a writev with short data (under
|
|
* page size) is likely to get shorten to 0 bytes and return
|
|
* EFAULT.
|
|
*
|
|
* This test doesn't make assumptions how much will write get
|
|
* shortened. It only tests that file content/offset after
|
|
* syscall corresponds to return value of writev().
|
|
*
|
|
* See: [RFC] writev() semantics with invalid iovec in the middle
|
|
* https://marc.info/?l=linux-kernel&m=147388891614289&w=2
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/uio.h>
|
|
#include "tst_test.h"
|
|
|
|
#define TESTFILE "testfile"
|
|
#define CHUNK 64
|
|
#define BUFSIZE (CHUNK * 4)
|
|
|
|
static void *bad_addr;
|
|
|
|
static void test_partially_valid_iovec(int initial_file_offset)
|
|
{
|
|
int i, fd;
|
|
unsigned char buffer[BUFSIZE], fpattern[BUFSIZE], tmp[BUFSIZE];
|
|
long off_after;
|
|
struct iovec wr_iovec[] = {
|
|
{ buffer, CHUNK },
|
|
{ bad_addr, CHUNK },
|
|
{ buffer + CHUNK, CHUNK },
|
|
{ buffer + CHUNK * 2, CHUNK },
|
|
};
|
|
|
|
tst_res(TINFO, "starting test with initial file offset: %d ",
|
|
initial_file_offset);
|
|
|
|
for (i = 0; i < BUFSIZE; i++)
|
|
buffer[i] = i % (CHUNK - 1);
|
|
|
|
memset(fpattern, 0xff, BUFSIZE);
|
|
tst_fill_file(TESTFILE, 0xff, CHUNK, BUFSIZE / CHUNK);
|
|
|
|
fd = SAFE_OPEN(TESTFILE, O_RDWR, 0644);
|
|
SAFE_LSEEK(fd, initial_file_offset, SEEK_SET);
|
|
TEST(writev(fd, wr_iovec, ARRAY_SIZE(wr_iovec)));
|
|
off_after = (long) SAFE_LSEEK(fd, 0, SEEK_CUR);
|
|
|
|
/* bad errno */
|
|
if (TST_RET == -1 && TST_ERR != EFAULT) {
|
|
tst_res(TFAIL | TTERRNO, "unexpected errno");
|
|
SAFE_CLOSE(fd);
|
|
return;
|
|
}
|
|
|
|
/* nothing has been written */
|
|
if (TST_RET == -1 && TST_ERR == EFAULT) {
|
|
tst_res(TINFO, "got EFAULT");
|
|
/* initial file content remains untouched */
|
|
SAFE_LSEEK(fd, 0, SEEK_SET);
|
|
SAFE_READ(1, fd, tmp, BUFSIZE);
|
|
if (memcmp(tmp, fpattern, BUFSIZE))
|
|
tst_res(TFAIL, "file was written to");
|
|
else
|
|
tst_res(TPASS, "file stayed untouched");
|
|
|
|
/* offset hasn't changed */
|
|
if (off_after == initial_file_offset)
|
|
tst_res(TPASS, "offset stayed unchanged");
|
|
else
|
|
tst_res(TFAIL, "offset changed to %ld",
|
|
off_after);
|
|
|
|
SAFE_CLOSE(fd);
|
|
return;
|
|
}
|
|
|
|
/* writev() wrote more bytes than bytes preceding invalid iovec */
|
|
tst_res(TINFO, "writev() has written %ld bytes", TST_RET);
|
|
if (TST_RET > (long) wr_iovec[0].iov_len) {
|
|
tst_res(TFAIL, "writev wrote more than expected");
|
|
SAFE_CLOSE(fd);
|
|
return;
|
|
}
|
|
|
|
/* file content matches written bytes */
|
|
SAFE_LSEEK(fd, initial_file_offset, SEEK_SET);
|
|
SAFE_READ(1, fd, tmp, TST_RET);
|
|
if (memcmp(tmp, wr_iovec[0].iov_base, TST_RET) == 0) {
|
|
tst_res(TPASS, "file has expected content");
|
|
} else {
|
|
tst_res(TFAIL, "file has unexpected content");
|
|
tst_res_hexd(TFAIL, wr_iovec[0].iov_base, TST_RET,
|
|
"expected:");
|
|
tst_res_hexd(TFAIL, tmp, TST_RET,
|
|
"actual file content:");
|
|
}
|
|
|
|
/* file offset has been updated according to written bytes */
|
|
if (off_after == initial_file_offset + TST_RET)
|
|
tst_res(TPASS, "offset at %ld as expected", off_after);
|
|
else
|
|
tst_res(TFAIL, "offset unexpected %ld", off_after);
|
|
|
|
SAFE_CLOSE(fd);
|
|
}
|
|
|
|
static void test_writev(void)
|
|
{
|
|
test_partially_valid_iovec(0);
|
|
test_partially_valid_iovec(CHUNK + 1);
|
|
test_partially_valid_iovec(getpagesize());
|
|
test_partially_valid_iovec(getpagesize() + 1);
|
|
}
|
|
|
|
static void setup(void)
|
|
{
|
|
bad_addr = SAFE_MMAP(0, 1, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS,
|
|
0, 0);
|
|
}
|
|
|
|
static struct tst_test test = {
|
|
.needs_tmpdir = 1,
|
|
.setup = setup,
|
|
.test_all = test_writev,
|
|
};
|