-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
trunctests: Add truncation-related tests
Add simple, easy to compreehend truncate tests. Signed-off-by: Pedro Falcato <[email protected]>
- Loading branch information
Showing
2 changed files
with
216 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import("//build/app.gni") | ||
|
||
app_executable("trunctests") { | ||
package_name = "trunctests" | ||
output_name = "$package_name" | ||
|
||
sources = [ "main.c" ] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
/* SPDX-License-Identifier: GPL-2.0-only */ | ||
/* Copyright (c) 2024 Pedro Falcato */ | ||
#include <err.h> | ||
#include <fcntl.h> | ||
#include <setjmp.h> | ||
#include <signal.h> | ||
#include <stdint.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <sys/ioctl.h> | ||
#include <sys/mman.h> | ||
#include <sys/statfs.h> | ||
#include <unistd.h> | ||
|
||
#ifdef __linux__ | ||
#include <linux/fs.h> | ||
#endif | ||
|
||
static void usage(void) | ||
{ | ||
printf("Usage: trunctests PATH_TO_FILE\n" | ||
" trunctests does truncation-related testing on a file\n"); | ||
} | ||
|
||
static void do_trunc_tests(const char *filename); | ||
|
||
int main(int argc, char **argv) | ||
{ | ||
if (argc < 2) | ||
{ | ||
usage(); | ||
return 1; | ||
} | ||
|
||
do_trunc_tests(argv[1]); | ||
} | ||
|
||
static void fill_trunc_params(int fd, unsigned long *blksize, unsigned long *pagesize) | ||
{ | ||
/* Fetch the block size for the filesystem, and the page size while we're at it */ | ||
struct statfs stafs; | ||
if (fstatfs(fd, &stafs) < 0) | ||
err(1, "fstatfs"); | ||
*blksize = stafs.f_bsize; | ||
*pagesize = sysconf(_SC_PAGE_SIZE); | ||
} | ||
|
||
static void fill_file(int fd, unsigned int size) | ||
{ | ||
ssize_t res; | ||
char *buffer = malloc(size); | ||
if (!buffer) | ||
err(1, "malloc"); | ||
/* Fill the file with a recognizable, relatively unique pattern */ | ||
memset(buffer, 0xad, size); | ||
|
||
res = write(fd, buffer, size); | ||
if (res < 0) | ||
err(1, "write"); | ||
if (res != size) | ||
errx(1, "write: short write (%zu bytes out of %zu)", (size_t) res, (size_t) size); | ||
free(buffer); | ||
} | ||
|
||
static sigjmp_buf jbuf; | ||
|
||
static void sigbus_handler(int sig) | ||
{ | ||
siglongjmp(jbuf, 1); | ||
} | ||
|
||
static void test_sigbus_works(void *ptr, unsigned int filesize) | ||
{ | ||
if (sigsetjmp(jbuf, 1) == 1) | ||
{ | ||
/* Success! */ | ||
signal(SIGBUS, SIG_DFL); | ||
printf("Test SIGBUS works OK\n"); | ||
return; | ||
} | ||
|
||
signal(SIGBUS, sigbus_handler); | ||
|
||
volatile int *p = (volatile int *) (ptr + filesize); | ||
*p; | ||
errx(1, "SIGBUS does not work on faults after EOF"); | ||
} | ||
|
||
static void file_check_unmapped(int fd, unsigned int off, unsigned int bsize) | ||
{ | ||
uint64_t blk = off / bsize; | ||
|
||
#ifdef FIBMAP | ||
unsigned int fibmap_blk = blk; | ||
if (ioctl(fd, FIBMAP, &fibmap_blk) == 0) | ||
{ | ||
blk = fibmap_blk; | ||
goto check; | ||
} | ||
#endif | ||
static int say_once = 0; | ||
if (say_once++ == 0) | ||
fprintf(stderr, "We failed to map the block, skipping...\n"); | ||
return; | ||
check: | ||
if (blk != 0) | ||
err(1, "Error: offset %u is still mapped to a real block (%llu), instead of a file hole\n", | ||
blk); | ||
} | ||
|
||
static void truncation_test(int fd, void *ptr, unsigned int filesize, unsigned int pagesize, | ||
unsigned int to_trunc, unsigned int bsize) | ||
{ | ||
ssize_t st; | ||
char buffer[to_trunc]; | ||
unsigned int newsize = filesize - to_trunc; | ||
if (ftruncate(fd, newsize) < 0) | ||
err(1, "ftruncate"); | ||
|
||
if (to_trunc >= pagesize) | ||
{ | ||
/* Check for a proper SIGBUS */ | ||
if (sigsetjmp(jbuf, 1) == 0) | ||
{ | ||
signal(SIGBUS, sigbus_handler); | ||
volatile int *p = (volatile int *) (ptr + newsize); | ||
*p; | ||
errx(1, "Access to bad page did not raise SIGBUS"); | ||
} | ||
|
||
/* Success! */ | ||
signal(SIGBUS, SIG_DFL); | ||
} | ||
|
||
/* Truncate it back to filesize */ | ||
if (ftruncate(fd, filesize) < 0) | ||
err(1, "ftruncate"); | ||
|
||
/* Check that the block is not mapped, if to_trunc is bsize aligned */ | ||
if ((to_trunc % bsize) == 0) | ||
file_check_unmapped(fd, filesize - to_trunc, bsize); | ||
|
||
/* Make sure that 1) both read() and mmap agree on the contents and 2) the contents are | ||
* completely zeroed. | ||
*/ | ||
st = pread(fd, buffer, to_trunc, newsize); | ||
if (st < 0) | ||
err(1, "pread"); | ||
if (st != to_trunc) | ||
errx(1, "pread partial read (%u out of %u bytes)", (unsigned int) st, to_trunc); | ||
if (memcmp(buffer, ptr + newsize, to_trunc)) | ||
errx(1, "read() and mmap's contents don't match"); | ||
for (unsigned int i = 0; i < to_trunc; i++) | ||
{ | ||
if (buffer[i] != 0) | ||
errx(1, "truncate did not free pages/blocks correctly"); | ||
} | ||
|
||
/* Restore the pattern */ | ||
memset(ptr + newsize, 0xad, to_trunc); | ||
} | ||
|
||
static void test_whole_page(int fd, void *ptr, unsigned int filesize, unsigned int pagesize, | ||
unsigned int bsize) | ||
{ | ||
truncation_test(fd, ptr, filesize, pagesize, pagesize, bsize); | ||
printf("Whole page truncation OK\n"); | ||
} | ||
|
||
static void test_partial_page(int fd, void *ptr, unsigned int filesize, unsigned int pagesize, | ||
unsigned int bsize) | ||
{ | ||
truncation_test(fd, ptr, filesize, pagesize, pagesize / 2, bsize); | ||
printf("Partial page truncation OK\n"); | ||
truncation_test(fd, ptr, filesize, pagesize, bsize / 2, bsize); | ||
printf("Partial block truncation (block size %u) OK\n", bsize); | ||
} | ||
|
||
static void do_trunc_tests(const char *filename) | ||
{ | ||
unsigned long blksize, pagesize; | ||
|
||
int fd = open(filename, O_RDWR | O_TRUNC | O_CREAT, 0644); | ||
if (fd < 0) | ||
err(1, "open"); | ||
|
||
fill_trunc_params(fd, &blksize, &pagesize); | ||
|
||
/* Fill the file with 16 pages */ | ||
unsigned int filesize = pagesize * 16; | ||
if (ftruncate(fd, filesize) < 0) | ||
err(1, "ftruncate"); | ||
fill_file(fd, filesize); | ||
|
||
/* Map a little out of bounds, for SIGBUS testing */ | ||
void *ptr = mmap(NULL, filesize + pagesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); | ||
if (ptr == MAP_FAILED) | ||
err(1, "mmap"); | ||
/* Test 1: SIGBUS works before truncation */ | ||
test_sigbus_works(ptr, filesize); | ||
|
||
/* Test 2: Whole page/block truncation */ | ||
test_whole_page(fd, ptr, filesize, pagesize, blksize); | ||
|
||
/* Test 3: Partial page/block truncation */ | ||
test_partial_page(fd, ptr, filesize, pagesize, blksize); | ||
} |