| // SPDX-License-Identifier: GPL-2.0+ | 
 | /* | 
 |  * holetest -- test simultaneous page faults on hole-backed pages | 
 |  * Copyright (C) 2015  Hewlett Packard Enterprise Development LP | 
 |  */ | 
 |  | 
 |  | 
 | /* | 
 |  * holetest | 
 |  * | 
 |  * gcc -Wall -pthread -o holetest holetest.c | 
 |  * | 
 |  * This test tool exercises page faults on hole-y portions of an mmapped | 
 |  * file.  The file is created, sized using various methods, mmapped, and | 
 |  * then two threads race to write a marker to different offsets within | 
 |  * each mapped page.  Once the threads have finished marking each page, | 
 |  * the pages are checked for the presence of the markers. | 
 |  * | 
 |  * The file is sized four different ways: explicitly zero-filled by the | 
 |  * test, posix_fallocate(), fallocate(), and ftruncate().  The explicit | 
 |  * zero-fill does not really test simultaneous page faults on hole-backed | 
 |  * pages, but rather serves as control of sorts. | 
 |  * | 
 |  * Usage: | 
 |  * | 
 |  *   holetest [-f] FILENAME FILESIZEinMB | 
 |  * | 
 |  * Where: | 
 |  * | 
 |  *   FILENAME is the name of a non-existent test file to create | 
 |  * | 
 |  *   FILESIZEinMB is the desired size of the test file in MiB | 
 |  * | 
 |  * If the test is successful, FILENAME will be unlinked.  By default, | 
 |  * if the test detects an error in the page markers, then the test exits | 
 |  * immediately and FILENAME is left.  If -f is given, then the test | 
 |  * continues after a marker error and FILENAME is unlinked, but will | 
 |  * still exit with a non-0 status. | 
 |  */ | 
 |  | 
 | #include <stdlib.h> | 
 | #include <stdio.h> | 
 | #include <errno.h> | 
 | #include <inttypes.h> | 
 | #include <sys/mman.h> | 
 | #include <fcntl.h> | 
 | #include <unistd.h> | 
 | #include <pthread.h> | 
 | #include <string.h> | 
 | #include <getopt.h> | 
 | #include <sys/wait.h> | 
 |  | 
 | #define THREADS 2 | 
 |  | 
 | long page_size; | 
 | long page_offs[THREADS]; | 
 | int use_wr[THREADS]; | 
 | int prefault = 0; | 
 | int use_fork = 0; | 
 | int use_private = 0; | 
 |  | 
 | uint64_t get_id(void) | 
 | { | 
 | 	if (!use_fork) | 
 | 		return (uint64_t) pthread_self(); | 
 | 	return getpid(); | 
 | } | 
 |  | 
 | void prefault_mapping(char *addr, long npages) | 
 | { | 
 | 	long i; | 
 |  | 
 | 	for (i = 0; i < npages; i++) { | 
 | 		if (addr[i * page_size] != 0) { | 
 | 			fprintf(stderr, "Prefaulting found non-zero value in " | 
 | 				"page %ld: %d\n", i, (int)addr[i * page_size]); | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | int verify_mapping(char *vastart, long npages, uint64_t *expect) | 
 | { | 
 | 	int errcnt = 0; | 
 | 	int i; | 
 | 	char *va; | 
 |  | 
 | 	for (va = vastart; npages > 0; va += page_size, npages--) { | 
 | 		for (i = 0; i < THREADS; i++) { | 
 | 			if (*(uint64_t*)(va + page_offs[i]) != expect[i]) { | 
 | 				printf("ERROR: thread %d, " | 
 | 				       "offset %08llx, %08llx != %08llx\n", i, | 
 | 				       (unsigned long long) (va + page_offs[i] - vastart), | 
 | 				       (unsigned long long) *(uint64_t*)(va + page_offs[i]), | 
 | 				       (unsigned long long) expect[i]); | 
 | 				errcnt++; | 
 | 			} | 
 | 		} | 
 | 	} | 
 | 	return errcnt; | 
 | } | 
 |  | 
 | void *pt_page_marker(void *args) | 
 | { | 
 | 	void **a = args; | 
 | 	char *va = (char *)a[1]; | 
 | 	long npages = (long)a[2]; | 
 | 	long i; | 
 | 	long pgoff = page_offs[(long)a[3]]; | 
 | 	uint64_t tid = get_id(); | 
 | 	long errors = 0; | 
 |  | 
 | 	if (prefault && use_fork) | 
 | 		prefault_mapping(va, npages); | 
 |  | 
 | 	/* mark pages */ | 
 | 	for (i = 0; i < npages; i++) | 
 | 		*(uint64_t *)(va + pgoff + i * page_size) = tid; | 
 |  | 
 | 	if (use_private && use_fork) { | 
 | 		uint64_t expect[THREADS] = {}; | 
 |  | 
 | 		expect[(long)a[3]] = tid; | 
 | 		errors = verify_mapping(va, npages, expect); | 
 | 	} | 
 |  | 
 | 	return (void *)errors; | 
 | }  /* pt_page_marker() */ | 
 |  | 
 | void *pt_write_marker(void *args) | 
 | { | 
 | 	void **a = args; | 
 | 	int fd = (long)a[0]; | 
 | 	long npages = (long)a[2]; | 
 | 	long pgoff = page_offs[(long)a[3]]; | 
 | 	uint64_t tid = get_id(); | 
 | 	long i; | 
 |  | 
 | 	/* mark pages */ | 
 | 	for (i = 0; i < npages; i++) | 
 | 		pwrite(fd, &tid, sizeof(tid), i * page_size + pgoff); | 
 |  | 
 | 	return NULL; | 
 | } | 
 |  | 
 | int test_this(int fd, loff_t sz) | 
 | { | 
 | 	long npages; | 
 | 	char *vastart; | 
 | 	void *targs[THREADS][4]; | 
 | 	pthread_t t[THREADS]; | 
 | 	uint64_t tid[THREADS]; | 
 | 	int errcnt = 0; | 
 | 	int i; | 
 |  | 
 | 	npages = sz / page_size; | 
 | 	printf("INFO: sz = %llu\n", (unsigned long long)sz); | 
 |  | 
 | 	/* mmap it */ | 
 | 	vastart = mmap(NULL, sz, PROT_READ | PROT_WRITE, | 
 | 		       use_private ? MAP_PRIVATE : MAP_SHARED, fd, 0); | 
 | 	if (MAP_FAILED == vastart) { | 
 | 		perror("mmap()"); | 
 | 		exit(20); | 
 | 	} | 
 |  | 
 | 	if (prefault && !use_fork) | 
 | 		prefault_mapping(vastart, npages); | 
 |  | 
 | 	/* prepare the thread args */ | 
 | 	for (i = 0; i < THREADS; i++) { | 
 | 		targs[i][0] = (void *)(long)fd; | 
 | 		targs[i][1] = vastart; | 
 | 		targs[i][2] = (void *)npages; | 
 | 		targs[i][3] = (void *)(long)i; | 
 | 	} | 
 |  | 
 | 	for (i = 0; i < THREADS; i++) { | 
 | 		if (!use_fork) { | 
 | 			/* start two threads */ | 
 | 			if (pthread_create(&t[i], NULL, | 
 | 				   use_wr[i] ? pt_write_marker : pt_page_marker, | 
 | 				   &targs[i])) { | 
 | 				perror("pthread_create"); | 
 | 				exit(21); | 
 | 			} | 
 | 			tid[i] = (uint64_t)t[i]; | 
 | 			printf("INFO: thread %d created\n", i); | 
 | 		} else { | 
 | 			pid_t pid; | 
 | 			/* | 
 | 			 * Flush stdout before fork, otherwise some lines get | 
 | 			 * duplicated... ?!?!? | 
 | 			 */ | 
 | 			fflush(stdout); | 
 | 			pid = fork(); | 
 | 			if (pid < 0) { | 
 | 				int j; | 
 |  | 
 | 				perror("fork"); | 
 | 				for (j = 0; j < i; j++) | 
 | 					waitpid(tid[j], NULL, 0); | 
 | 				exit(21); | 
 | 			} else if (!pid) { | 
 | 				/* Child? */ | 
 | 				void *ret; | 
 |  | 
 | 				if (use_wr[i]) | 
 | 					ret = pt_write_marker(&targs[i]); | 
 | 				else | 
 | 					ret = pt_page_marker(&targs[i]); | 
 | 				exit(ret ? 1 : 0); | 
 | 			} | 
 | 			tid[i] = pid; | 
 | 			printf("INFO: process %d created\n", i); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* wait for them to finish */ | 
 | 	for (i = 0; i < THREADS; i++) { | 
 | 		if (!use_fork) { | 
 | 			void *status; | 
 |  | 
 | 			pthread_join(t[i], &status); | 
 | 			if (status) | 
 | 				errcnt++; | 
 | 		} else { | 
 | 			int status; | 
 |  | 
 | 			waitpid(tid[i], &status, 0); | 
 | 			if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) | 
 | 				errcnt++; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* check markers on each page */ | 
 | 	/* For private mappings & fork we should see no writes happen */ | 
 | 	if (use_private && use_fork) | 
 | 		for (i = 0; i < THREADS; i++) | 
 | 			tid[i] = 0; | 
 | 	errcnt = verify_mapping(vastart, npages, tid); | 
 | 	munmap(vastart, sz); | 
 |  | 
 | 	if (use_private) { | 
 | 		/* Check that no writes propagated into original file */ | 
 | 		for (i = 0; i < THREADS; i++) | 
 | 			tid[i] = 0; | 
 | 		vastart = mmap(NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0); | 
 | 		if (vastart == MAP_FAILED) { | 
 | 			perror("mmap()"); | 
 | 			exit(20); | 
 | 		} | 
 | 		errcnt += verify_mapping(vastart, npages, tid); | 
 | 		munmap(vastart, sz); | 
 | 	} | 
 |  | 
 | 	printf("INFO: %d error(s) detected\n", errcnt); | 
 |  | 
 |  | 
 | 	return errcnt; | 
 | } | 
 |  | 
 | int main(int argc, char **argv) | 
 | { | 
 | 	int stoponerror = 1; | 
 | 	char *path; | 
 | 	loff_t sz; | 
 | 	int fd; | 
 | 	int errcnt; | 
 | 	int toterr = 0; | 
 | 	int i, step; | 
 | 	char *endch; | 
 | 	int opt; | 
 |  | 
 | 	page_size = getpagesize(); | 
 | 	step = page_size / THREADS; | 
 | 	page_offs[0] = step / 2; | 
 | 	for (i = 1; i < THREADS; i++) | 
 | 		page_offs[i] = page_offs[i-1] + step; | 
 |  | 
 | 	while ((opt = getopt(argc, argv, "fwrFp")) > 0) { | 
 | 		switch (opt) { | 
 | 		case 'f': | 
 | 			/* ignore errors */ | 
 | 			stoponerror = 0; | 
 | 			break; | 
 | 		case 'w': | 
 | 			/* use writes instead of mmap for one thread */ | 
 | 			use_wr[0] = 1; | 
 | 			break; | 
 | 		case 'r': | 
 | 			/* prefault mmapped area by reading it */ | 
 | 			prefault = 1; | 
 | 			break; | 
 | 		case 'F': | 
 | 			/* create processes instead of threads */ | 
 | 			use_fork = 1; | 
 | 			break; | 
 | 		case 'p': | 
 | 			/* Use private mappings for testing */ | 
 | 			use_private = 1; | 
 | 			break; | 
 | 		default: | 
 | 			fprintf(stderr, "ERROR: Unknown option character.\n"); | 
 | 			exit(1); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (optind != argc - 2) { | 
 | 		fprintf(stderr, "ERROR: usage: holetest [-fwrFp] " | 
 | 			"FILENAME FILESIZEinMB\n"); | 
 | 		exit(1); | 
 | 	} | 
 | 	if (use_private && use_wr[0]) { | 
 | 		fprintf(stderr, "ERROR: Combinations of writes and private" | 
 | 			"mappings not supported.\n"); | 
 | 		exit(1); | 
 | 	} | 
 |  | 
 | 	path = argv[optind]; | 
 | 	sz = strtol(argv[optind + 1], &endch, 10); | 
 | 	if (*endch || sz < 1) { | 
 | 		fprintf(stderr, "ERROR: bad FILESIZEinMB\n"); | 
 | 		exit(1); | 
 | 	} | 
 | 	sz <<= 20; | 
 |  | 
 | 	/* | 
 | 	 * we're going to run our test in several different ways: | 
 | 	 * | 
 | 	 * 1. explictly zero-filled | 
 | 	 * 2. posix_fallocated | 
 | 	 * 3. ftruncated | 
 | 	 */ | 
 |  | 
 |  | 
 | 	/* | 
 | 	 * explicitly zero-filled | 
 | 	 */ | 
 | 	printf("\nINFO: zero-filled test...\n"); | 
 |  | 
 | 	/* create the file */ | 
 | 	fd = open(path, O_RDWR | O_EXCL | O_CREAT, 0644); | 
 | 	if (fd < 0) { | 
 | 		perror(path); | 
 | 		exit(2); | 
 | 	} | 
 |  | 
 | 	/* truncate it to size */ | 
 | 	if (ftruncate(fd, sz)) { | 
 | 		perror("ftruncate()"); | 
 | 		exit(3); | 
 | 	} | 
 |  | 
 | 	/* explicitly zero-fill */ | 
 | 	{ | 
 | 		char*   va = mmap(NULL, sz, PROT_READ | PROT_WRITE, | 
 | 				  MAP_SHARED, fd, 0); | 
 | 		if (MAP_FAILED == va) { | 
 | 			perror("mmap()"); | 
 | 			exit(4); | 
 | 		} | 
 | 		memset(va, 0, sz); | 
 | 		munmap(va, sz); | 
 | 	} | 
 |  | 
 | 	/* test it */ | 
 | 	errcnt = test_this(fd, sz); | 
 | 	toterr += errcnt; | 
 | 	close(fd); | 
 | 	if (stoponerror && errcnt > 0) | 
 | 		exit(5); | 
 |  | 
 | 	/* cleanup */ | 
 | 	if (unlink(path)) { | 
 | 		perror("unlink()"); | 
 | 		exit(6); | 
 | 	} | 
 |  | 
 |  | 
 | 	/* | 
 | 	 * posix_fallocated | 
 | 	 */ | 
 | 	printf("\nINFO: posix_fallocate test...\n"); | 
 |  | 
 | 	/* create the file */ | 
 | 	fd = open(path, O_RDWR | O_EXCL | O_CREAT, 0644); | 
 | 	if (fd < 0) { | 
 | 		perror(path); | 
 | 		exit(7); | 
 | 	} | 
 |  | 
 | 	/* fill it to size */ | 
 | 	if (posix_fallocate(fd, 0, sz)) { | 
 | 		perror("posix_fallocate()"); | 
 | 		exit(8); | 
 | 	} | 
 |  | 
 | 	/* test it */ | 
 | 	errcnt = test_this(fd, sz); | 
 | 	toterr += errcnt; | 
 | 	close(fd); | 
 | 	if (stoponerror && errcnt > 0) | 
 | 		exit(9); | 
 |  | 
 | 	/* cleanup */ | 
 | 	if (unlink(path)) { | 
 | 		perror("unlink()"); | 
 | 		exit(10); | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * ftruncated | 
 | 	 */ | 
 | 	printf("\nINFO: ftruncate test...\n"); | 
 |  | 
 | 	/* create the file */ | 
 | 	fd = open(path, O_RDWR | O_EXCL | O_CREAT, 0644); | 
 | 	if (fd < 0) { | 
 | 		perror(path); | 
 | 		exit(15); | 
 | 	} | 
 |  | 
 | 	/* truncate it to size */ | 
 | 	if (ftruncate(fd, sz)) { | 
 | 		perror("ftruncate()"); | 
 | 		exit(16); | 
 | 	} | 
 |  | 
 | 	/* test it */ | 
 | 	errcnt = test_this(fd, sz); | 
 | 	toterr += errcnt; | 
 | 	close(fd); | 
 | 	if (stoponerror && errcnt > 0) | 
 | 		exit(17); | 
 |  | 
 | 	/* cleanup */ | 
 | 	if (unlink(path)) { | 
 | 		perror("unlink()"); | 
 | 		exit(18); | 
 | 	} | 
 |  | 
 | 	/* done */ | 
 | 	if (toterr > 0) | 
 | 		exit(19); | 
 | 	return 0; | 
 | } |