Home | History | Annotate | Download | only in writev
      1 /*
      2  *
      3  *   Copyright (c) Linux Test Project, 2016
      4  *
      5  *   This program is free software;  you can redistribute it and/or modify
      6  *   it under the terms of the GNU General Public License as published by
      7  *   the Free Software Foundation; either version 2 of the License, or
      8  *   (at your option) any later version.
      9  *
     10  *   This program is distributed in the hope that it will be useful,
     11  *   but WITHOUT ANY WARRANTY;  without even the implied warranty of
     12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
     13  *   the GNU General Public License for more details.
     14  */
     15 
     16 /*
     17  * Test Description:
     18  *   Verify writev() behaviour with partially valid iovec list.
     19  *   Kernel <4.8 used to shorten write up to first bad invalid
     20  *   iovec. Starting with 4.8, a writev with short data (under
     21  *   page size) is likely to get shorten to 0 bytes and return
     22  *   EFAULT.
     23  *
     24  *   This test doesn't make assumptions how much will write get
     25  *   shortened. It only tests that file content/offset after
     26  *   syscall corresponds to return value of writev().
     27  *
     28  *   See: [RFC] writev() semantics with invalid iovec in the middle
     29  *        https://marc.info/?l=linux-kernel&m=147388891614289&w=2
     30  */
     31 
     32 #include <errno.h>
     33 #include <fcntl.h>
     34 #include <stdio.h>
     35 #include <sys/mman.h>
     36 #include <sys/stat.h>
     37 #include <sys/types.h>
     38 #include <sys/uio.h>
     39 #include "tst_test.h"
     40 
     41 #define TESTFILE "testfile"
     42 #define CHUNK 64
     43 #define BUFSIZE (CHUNK * 4)
     44 
     45 static void *bad_addr;
     46 
     47 static void test_partially_valid_iovec(int initial_file_offset)
     48 {
     49 	int i, fd;
     50 	unsigned char buffer[BUFSIZE], fpattern[BUFSIZE], tmp[BUFSIZE];
     51 	long off_after;
     52 	struct iovec wr_iovec[] = {
     53 		{ buffer, CHUNK },
     54 		{ bad_addr, CHUNK },
     55 		{ buffer + CHUNK, CHUNK },
     56 		{ buffer + CHUNK * 2, CHUNK },
     57 	};
     58 
     59 	tst_res(TINFO, "starting test with initial file offset: %d ",
     60 		initial_file_offset);
     61 
     62 	for (i = 0; i < BUFSIZE; i++)
     63 		buffer[i] = i % (CHUNK - 1);
     64 
     65 	memset(fpattern, 0xff, BUFSIZE);
     66 	tst_fill_file(TESTFILE, 0xff, CHUNK, BUFSIZE / CHUNK);
     67 
     68 	fd = SAFE_OPEN(TESTFILE, O_RDWR, 0644);
     69 	SAFE_LSEEK(fd, initial_file_offset, SEEK_SET);
     70 	TEST(writev(fd, wr_iovec, ARRAY_SIZE(wr_iovec)));
     71 	off_after = (long) SAFE_LSEEK(fd, 0, SEEK_CUR);
     72 
     73 	/* bad errno */
     74 	if (TEST_RETURN == -1 && TEST_ERRNO != EFAULT) {
     75 		tst_res(TFAIL | TTERRNO, "unexpected errno");
     76 		SAFE_CLOSE(fd);
     77 		return;
     78 	}
     79 
     80 	/* nothing has been written */
     81 	if (TEST_RETURN == -1 && TEST_ERRNO == EFAULT) {
     82 		tst_res(TINFO, "got EFAULT");
     83 		/* initial file content remains untouched */
     84 		SAFE_LSEEK(fd, 0, SEEK_SET);
     85 		SAFE_READ(1, fd, tmp, BUFSIZE);
     86 		if (memcmp(tmp, fpattern, BUFSIZE))
     87 			tst_res(TFAIL, "file was written to");
     88 		else
     89 			tst_res(TPASS, "file stayed untouched");
     90 
     91 		/* offset hasn't changed */
     92 		if (off_after == initial_file_offset)
     93 			tst_res(TPASS, "offset stayed unchanged");
     94 		else
     95 			tst_res(TFAIL, "offset changed to %ld",
     96 				off_after);
     97 
     98 		SAFE_CLOSE(fd);
     99 		return;
    100 	}
    101 
    102 	/* writev() wrote more bytes than bytes preceding invalid iovec */
    103 	tst_res(TINFO, "writev() has written %ld bytes", TEST_RETURN);
    104 	if (TEST_RETURN > (long) wr_iovec[0].iov_len) {
    105 		tst_res(TFAIL, "writev wrote more than expected");
    106 		SAFE_CLOSE(fd);
    107 		return;
    108 	}
    109 
    110 	/* file content matches written bytes */
    111 	SAFE_LSEEK(fd, initial_file_offset, SEEK_SET);
    112 	SAFE_READ(1, fd, tmp, TEST_RETURN);
    113 	if (memcmp(tmp, wr_iovec[0].iov_base, TEST_RETURN) == 0) {
    114 		tst_res(TPASS, "file has expected content");
    115 	} else {
    116 		tst_res(TFAIL, "file has unexpected content");
    117 		tst_res_hexd(TFAIL, wr_iovec[0].iov_base, TEST_RETURN,
    118 				"expected:");
    119 		tst_res_hexd(TFAIL, tmp, TEST_RETURN,
    120 				"actual file content:");
    121 	}
    122 
    123 	/* file offset has been updated according to written bytes */
    124 	if (off_after == initial_file_offset + TEST_RETURN)
    125 		tst_res(TPASS, "offset at %ld as expected", off_after);
    126 	else
    127 		tst_res(TFAIL, "offset unexpected %ld", off_after);
    128 
    129 	SAFE_CLOSE(fd);
    130 }
    131 
    132 static void test_writev(void)
    133 {
    134 	test_partially_valid_iovec(0);
    135 	test_partially_valid_iovec(CHUNK + 1);
    136 	test_partially_valid_iovec(getpagesize());
    137 	test_partially_valid_iovec(getpagesize() + 1);
    138 }
    139 
    140 static void setup(void)
    141 {
    142 	bad_addr = SAFE_MMAP(0, 1, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS,
    143 			0, 0);
    144 }
    145 
    146 static struct tst_test test = {
    147 	.needs_tmpdir = 1,
    148 	.setup = setup,
    149 	.test_all = test_writev,
    150 };
    151