199 lines
4.4 KiB
C
199 lines
4.4 KiB
C
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||
|
|
/*
|
||
|
|
* Copyright (c) 2015 Fujitsu Ltd. Xiaoguang Wang <wangxg.fnst@cn.fujitsu.com>
|
||
|
|
* Copyright (C) 2021 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
|
||
|
|
*/
|
||
|
|
|
||
|
|
/*\
|
||
|
|
* [Description]
|
||
|
|
*
|
||
|
|
* This is a regression test for a silent data corruption for a mmaped file
|
||
|
|
* when filesystem gets out of space.
|
||
|
|
*
|
||
|
|
* Fixed by commits:
|
||
|
|
*
|
||
|
|
* commit 0572639ff66dcffe62d37adfe4c4576f9fc398f4
|
||
|
|
* Author: Xiaoguang Wang <wangxg.fnst@cn.fujitsu.com>
|
||
|
|
* Date: Thu Feb 12 23:00:17 2015 -0500
|
||
|
|
*
|
||
|
|
* ext4: fix mmap data corruption in nodelalloc mode when blocksize < pagesize
|
||
|
|
*
|
||
|
|
* commit d6320cbfc92910a3e5f10c42d98c231c98db4f60
|
||
|
|
* Author: Jan Kara <jack@suse.cz>
|
||
|
|
* Date: Wed Oct 1 21:49:46 2014 -0400
|
||
|
|
*
|
||
|
|
* ext4: fix mmap data corruption when blocksize < pagesize
|
||
|
|
*/
|
||
|
|
|
||
|
|
#define _GNU_SOURCE
|
||
|
|
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <signal.h>
|
||
|
|
#include <time.h>
|
||
|
|
#include <sys/mman.h>
|
||
|
|
#include <sys/wait.h>
|
||
|
|
#include "tst_test.h"
|
||
|
|
|
||
|
|
#define MNTPOINT "mntpoint"
|
||
|
|
#define FILE_PARENT "mntpoint/testfilep"
|
||
|
|
#define FILE_CHILD "mntpoint/testfilec"
|
||
|
|
#define FS_BLOCKSIZE 1024
|
||
|
|
#define LOOPS 10
|
||
|
|
|
||
|
|
static int parentfd = -1;
|
||
|
|
static int childfd = -1;
|
||
|
|
|
||
|
|
static void do_child(void)
|
||
|
|
{
|
||
|
|
int offset;
|
||
|
|
int page_size;
|
||
|
|
char buf[FS_BLOCKSIZE];
|
||
|
|
char *addr = NULL;
|
||
|
|
|
||
|
|
page_size = getpagesize();
|
||
|
|
|
||
|
|
childfd = SAFE_OPEN(FILE_CHILD, O_RDWR | O_CREAT, 0666);
|
||
|
|
|
||
|
|
memset(buf, 'a', FS_BLOCKSIZE);
|
||
|
|
SAFE_WRITE(SAFE_WRITE_ALL, childfd, buf, FS_BLOCKSIZE);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* In case mremap() may fail because that memory area can not be
|
||
|
|
* expanded at current virtual address(MREMAP_MAYMOVE is not set),
|
||
|
|
* we first do a mmap(page_size * 2) operation to reserve some
|
||
|
|
* free address space.
|
||
|
|
*/
|
||
|
|
addr = SAFE_MMAP(NULL, page_size * 2, PROT_WRITE | PROT_READ,
|
||
|
|
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||
|
|
SAFE_MUNMAP(addr, page_size * 2);
|
||
|
|
|
||
|
|
addr = SAFE_MMAP(addr, FS_BLOCKSIZE, PROT_WRITE | PROT_READ, MAP_SHARED, childfd, 0);
|
||
|
|
|
||
|
|
addr[0] = 'a';
|
||
|
|
|
||
|
|
SAFE_FTRUNCATE(childfd, page_size * 2);
|
||
|
|
|
||
|
|
addr = mremap(addr, FS_BLOCKSIZE, 2 * page_size, 0);
|
||
|
|
if (addr == MAP_FAILED)
|
||
|
|
tst_brk(TBROK | TERRNO, "mremap failed unexpectedly");
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Let parent process consume FS free blocks as many as possible, then
|
||
|
|
* there'll be no free blocks allocated for this new file mmaping for
|
||
|
|
* offset starting at 1024, 2048, or 3072. If this above kernel bug
|
||
|
|
* has been fixed, usually child process will killed by SIGBUS signal,
|
||
|
|
* if not, the data 'A', 'B', 'C' will be silently discarded later when
|
||
|
|
* kernel writepage is called, that means data corruption.
|
||
|
|
*/
|
||
|
|
TST_CHECKPOINT_WAKE_AND_WAIT(0);
|
||
|
|
|
||
|
|
for (offset = FS_BLOCKSIZE; offset < page_size; offset += FS_BLOCKSIZE)
|
||
|
|
addr[offset] = 'a';
|
||
|
|
|
||
|
|
SAFE_MUNMAP(addr, 2 * page_size);
|
||
|
|
SAFE_CLOSE(childfd);
|
||
|
|
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void run_single(void)
|
||
|
|
{
|
||
|
|
int ret, status;
|
||
|
|
pid_t child;
|
||
|
|
char buf[FS_BLOCKSIZE];
|
||
|
|
int bug_reproduced = 0;
|
||
|
|
|
||
|
|
child = SAFE_FORK();
|
||
|
|
if (!child) {
|
||
|
|
do_child();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
parentfd = SAFE_OPEN(FILE_PARENT, O_RDWR | O_CREAT, 0666);
|
||
|
|
|
||
|
|
memset(buf, 'a', FS_BLOCKSIZE);
|
||
|
|
|
||
|
|
TST_CHECKPOINT_WAIT(0);
|
||
|
|
|
||
|
|
while (1) {
|
||
|
|
ret = write(parentfd, buf, FS_BLOCKSIZE);
|
||
|
|
if (ret < 0) {
|
||
|
|
if (errno == ENOSPC)
|
||
|
|
break;
|
||
|
|
|
||
|
|
tst_brk(TBROK | TERRNO, "write failed unexpectedly");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
SAFE_CLOSE(parentfd);
|
||
|
|
|
||
|
|
TST_CHECKPOINT_WAKE(0);
|
||
|
|
|
||
|
|
SAFE_WAITPID(child, &status, 0);
|
||
|
|
if (WIFEXITED(status) && WEXITSTATUS(status) == 1) {
|
||
|
|
bug_reproduced = 1;
|
||
|
|
} else {
|
||
|
|
/*
|
||
|
|
* If child process was killed by SIGBUS, bug is not reproduced.
|
||
|
|
*/
|
||
|
|
if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGBUS) {
|
||
|
|
tst_brk(TBROK | TERRNO, "child process terminate unexpectedly with status %s",
|
||
|
|
tst_strstatus(status));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
SAFE_UNLINK(FILE_PARENT);
|
||
|
|
SAFE_UNLINK(FILE_CHILD);
|
||
|
|
|
||
|
|
if (bug_reproduced)
|
||
|
|
tst_res(TFAIL, "bug is reproduced");
|
||
|
|
else
|
||
|
|
tst_res(TPASS, "bug is not reproduced");
|
||
|
|
}
|
||
|
|
|
||
|
|
static void run(void)
|
||
|
|
{
|
||
|
|
int i;
|
||
|
|
|
||
|
|
for (i = 0; i < LOOPS; i++)
|
||
|
|
run_single();
|
||
|
|
}
|
||
|
|
|
||
|
|
static void cleanup(void)
|
||
|
|
{
|
||
|
|
if (childfd >= 0)
|
||
|
|
SAFE_CLOSE(childfd);
|
||
|
|
|
||
|
|
if (parentfd >= 0)
|
||
|
|
SAFE_CLOSE(parentfd);
|
||
|
|
}
|
||
|
|
|
||
|
|
static struct tst_test test = {
|
||
|
|
.test_all = run,
|
||
|
|
.cleanup = cleanup,
|
||
|
|
.forks_child = 1,
|
||
|
|
.needs_root = 1,
|
||
|
|
.needs_checkpoints = 1,
|
||
|
|
.mount_device = 1,
|
||
|
|
.mntpoint = MNTPOINT,
|
||
|
|
.dev_fs_type = "ext4",
|
||
|
|
.dev_fs_opts = (const char *const[]){
|
||
|
|
"-b",
|
||
|
|
"1024",
|
||
|
|
NULL,
|
||
|
|
},
|
||
|
|
.dev_extra_opts = (const char *const[]){
|
||
|
|
"10240",
|
||
|
|
NULL,
|
||
|
|
},
|
||
|
|
.needs_cmds = (const char *const[]){
|
||
|
|
"mkfs.ext4",
|
||
|
|
NULL,
|
||
|
|
},
|
||
|
|
.tags = (const struct tst_tag[]){
|
||
|
|
{"linux-git", "d6320cbfc929"},
|
||
|
|
{"linux-git", "0572639ff66d"},
|
||
|
|
{},
|
||
|
|
},
|
||
|
|
};
|