os161 / userland / testbin / sbrktest / sbrktest.c
sbrktest.c
Raw
/*
 * Copyright (c) 2014
 *	The President and Fellows of Harvard College.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <errno.h>

#define _PATH_RANDOM   "random:"

/*
 * Caution: OS/161 doesn't provide any way to get this properly from
 * the kernel. The page size is 4K on almost all hardware... but not
 * all. If porting to certain weird machines this will need attention.
 */
#define PAGE_SIZE 4096

////////////////////////////////////////////////////////////
// support code

static
int
geti(void)
{
	int val=0;
	int ch, digits=0;

	while (1) {
		ch = getchar();
		if (ch=='\n' || ch=='\r') {
			putchar('\n');
			break;
		}
		else if ((ch=='\b' || ch==127) && digits>0) {
			printf("\b \b");
			val = val/10;
			digits--;
		}
		else if (ch>='0' && ch<='9') {
			putchar(ch);
			val = val*10 + (ch-'0');
			digits++;
		}
		else {
			putchar('\a');
		}
	}

	if (digits==0) {
		return -1;
	}
	return val;
}

static
unsigned long
getseed(void)
{
	int fd, len;
	unsigned long seed;

	fd = open(_PATH_RANDOM, O_RDONLY);
	if (fd < 0) {
		err(1, "%s", _PATH_RANDOM);
	}
	len = read(fd, &seed, sizeof(seed));
	if (len < 0) {
		err(1, "%s", _PATH_RANDOM);
	}
	else if (len < (int)sizeof(seed)) {
		errx(1, "%s: Short read", _PATH_RANDOM);
	}
	close(fd);

	return seed;
}

static
pid_t
dofork(void)
{
	pid_t pid;

	pid = fork();
	if (pid < 0) {
		err(1, "fork");
	}
	return pid;
}

static
void
dowait(pid_t pid)
{
	int status;
	int result;

	result = waitpid(pid, &status, 0);
	if (result == -1) {
		err(1, "waitpid");
	}
	if (WIFSIGNALED(status)) {
		errx(1, "child: Signal %d", WTERMSIG(status));
	}
	if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
		errx(1, "child: Exit %d", WEXITSTATUS(status));
	}
}

static
void
say(const char *msg)
{
	/* Use one write so it's atomic (printf usually won't be) */
	write(STDOUT_FILENO, msg, strlen(msg));
}

////////////////////////////////////////////////////////////
// memory checking

/*
 * Fill a page of memory with a test pattern.
 */
static
void
markpage(volatile void *baseptr, unsigned pageoffset)
{
	volatile char *pageptr;
	size_t n, i;
	volatile unsigned long *pl;
	unsigned long val;

	pageptr = baseptr;
	pageptr += (size_t)PAGE_SIZE * pageoffset;

	pl = (volatile unsigned long *)pageptr;
	n = PAGE_SIZE / sizeof(unsigned long);

	for (i=0; i<n; i++) {
		val = ((unsigned long)i ^ (unsigned long)pageoffset);
		pl[i] = val;
	}
}

/*
 * Check a page marked with markblock()
 */
static
int
checkpage(volatile void *baseptr, unsigned pageoffset, bool neednl)
{
	volatile char *pageptr;
	size_t n, i;
	volatile unsigned long *pl;
	unsigned long val;

	pageptr = baseptr;
	pageptr += (size_t)PAGE_SIZE * pageoffset;

	pl = (volatile unsigned long *)pageptr;
	n = PAGE_SIZE / sizeof(unsigned long);

	for (i=0; i<n; i++) {
		val = ((unsigned long)i ^ (unsigned long)pageoffset);
		if (pl[i] != val) {
			if (neednl) {
				printf("\n");
			}
			printf("FAILED: data mismatch at offset %lu of page "
			       "at 0x%lx: %lu vs. %lu\n",
			       (unsigned long) (i*sizeof(unsigned long)),
			       (unsigned long)(uintptr_t)pl,
			       pl[i], val);
			return -1;
		}
	}

	return 0;
}

/*
 * Light version; touches just the first word of a page.
 */
static
void
markpagelight(volatile void *baseptr, unsigned pageoffset)
{
	volatile char *pageptr;
	volatile unsigned long *pl;

	pageptr = baseptr;
	pageptr += (size_t)PAGE_SIZE * pageoffset;

	pl = (volatile unsigned long *)pageptr;
	pl[0] = pageoffset;
}

/*
 * Light version; checks just the first word of a page.
 */
static
int
checkpagelight(volatile void *baseptr, unsigned pageoffset, bool neednl)
{
	volatile char *pageptr;
	volatile unsigned long *pl;

	pageptr = baseptr;
	pageptr += (size_t)PAGE_SIZE * pageoffset;

	pl = (volatile unsigned long *)pageptr;
	if (pl[0] != pageoffset) {
		if (neednl) {
			printf("\n");
		}
		printf("FAILED: data mismatch at offset 0 of page "
		       "at 0x%lx: %lu vs. %u\n",
		       (unsigned long)(uintptr_t)pl,
		       pl[0], pageoffset);
		return -1;
	}
	return 0;
}

////////////////////////////////////////////////////////////
// error wrapper

static
void *
dosbrk(ssize_t size)
{
	void *p;

	p = sbrk(size);
	if (p == (void *)-1) {
		err(1, "FAILED: sbrk");
	}
	if (p == NULL) {
		errx(1, "FAILED: sbrk returned NULL, which is illegal");
	}
	return p;
}

////////////////////////////////////////////////////////////
// align the heap

static
void
setup(void)
{
	void *op;
	uintptr_t opx;
	size_t amount;
	int error;

	op = dosbrk(0);
	opx = (uintptr_t)op;

	if (opx % PAGE_SIZE) {
		amount = PAGE_SIZE - (opx % PAGE_SIZE);
		if (sbrk(amount) == (void *)-1) {
			error = errno;
			warnx("Initial heap was not page aligned");
			warnx("...and trying to align it gave: %s",
			      strerror(error));
		}
	}

	op = dosbrk(0);
	opx = (uintptr_t)op;

	if (opx % PAGE_SIZE) {
		warnx("Initial heap was not page aligned");
		errx(1, "...and trying to align it didn't take.");
	}
}

////////////////////////////////////////////////////////////
// simple allocation

/*
 * Allocate one page, check that it holds data, and leak it.
 */
static
void
test1(void)
{
	void *p;

	printf("Allocating a page...\n");
	p = dosbrk(PAGE_SIZE);
	markpage(p, 0);
	if (checkpage(p, 0, false)) {
		errx(1, "FAILED: data corrupt");
	}

	printf("Passed sbrk test 1.\n");
}

/*
 * Allocate one page, check that it holds data, and free it.
 */
static
void
test2(void)
{
	void *op, *p, *q;

	op = dosbrk(0);

	printf("Allocating a page...\n");
	p = dosbrk(PAGE_SIZE);
	if (p != op) {
		errx(1, "FAILED: sbrk grow didn't return the old break "
		    "(got %p, expected %p", p, op);
	}
	markpage(p, 0);
	if (checkpage(p, 0, false)) {
		errx(1, "FAILED: data corrupt");
	}

	p = dosbrk(0);

	printf("Freeing the page...\n");
	q = dosbrk(-PAGE_SIZE);
	if (q != p) {
		errx(1, "FAILED: sbrk shrink didn't return the old break "
		     "(got %p, expected %p", q, p);
	}
	q = dosbrk(0);
	if (q != op) {
		errx(1, "FAILED: sbrk shrink didn't restore the heap "
		     "(got %p, expected %p", q, op);
	}

	printf("Passed sbrk test 2.\n");
}

/*
 * Allocate six pages, check that they hold data and that the
 * pages don't get mixed up, and free them.
 */
static
void
test3(void)
{
	const unsigned num = 6;

	void *op, *p, *q;
	unsigned i;
	bool bad;

	op = dosbrk(0);

	printf("Allocating %u pages...\n", num);
	p = dosbrk(PAGE_SIZE * num);
	if (p != op) {
		errx(1, "FAILED: sbrk grow didn't return the old break "
		     "(got %p, expected %p", p, op);
	}

	bad = false;
	for (i=0; i<num; i++) {
		markpage(p, i);
		if (checkpage(p, i, false)) {
			warnx("FAILED: data corrupt on page %u", i);
			bad = true;
		}
	}
	if (bad) {
		exit(1);
	}

	p = dosbrk(0);

	printf("Freeing the pages...\n");
	q = dosbrk(-PAGE_SIZE * num);
	if (q != p) {
		errx(1, "FAILED: sbrk shrink didn't return the old break "
		     "(got %p, expected %p", q, p);
	}
	q = dosbrk(0);
	if (q != op) {
		errx(1, "FAILED: sbrk shrink didn't restore the heap "
		     "(got %p, expected %p", q, op);
	}

	printf("Passed sbrk test 3.\n");
}

/*
 * Allocate six pages, check that they hold data and that the pages
 * don't get mixed up, and free them one at a time, repeating the
 * check after each free.
 */
static
void
test4(void)
{
	const unsigned num = 6;

	void *op, *p, *q;
	unsigned i, j;
	bool bad;

	op = dosbrk(0);

	printf("Allocating %u pages...\n", num);
	p = dosbrk(PAGE_SIZE * num);
	if (p != op) {
		errx(1, "FAILED: sbrk grow didn't return the old break "
		     "(got %p, expected %p", p, op);
	}

	bad = false;
	for (i=0; i<num; i++) {
		markpage(p, i);
		if (checkpage(p, i, false)) {
			warnx("FAILED: data corrupt on page %u", i);
			bad = true;
		}
	}
	if (bad) {
		exit(1);
	}

	printf("Freeing the pages one at a time...\n");
	for (i=num; i-- > 0; ) {
		(void)dosbrk(-PAGE_SIZE);
		for (j=0; j<i; j++) {
			if (checkpage(p, j, false)) {
				warnx("FAILED: data corrupt on page %u "
				      "after freeing %u pages", j, i);
				bad = true;
			}
		}
	}
	if (bad) {
		exit(1);
	}

	q = dosbrk(0);
	if (q != op) {
		errx(1, "FAILED: sbrk shrink didn't restore the heap "
		     "(got %p, expected %p", q, op);
	}

	printf("Passed sbrk test 4.\n");
}

////////////////////////////////////////////////////////////
// crashing off the end

/*
 * Checks that the page past end of the heap as we got it is not
 * valid. (Crashes when successful.)
 */
static
void
test5(void)
{
	void *p;

	p = dosbrk(0);
	printf("This should produce fatal signal 11 (SIGSEGV).\n");
	((long *)p)[10] = 0;
	errx(1, "FAILED: I didn't crash");
}

/*
 * Allocates a page and checks that the next page past it is not
 * valid. (Crashes when successful.)
 */
static
void
test6(void)
{
	void *p;

	(void)dosbrk(PAGE_SIZE);
	p = dosbrk(0);
	printf("This should produce fatal signal 11 (SIGSEGV).\n");
	((long *)p)[10] = 0;
	errx(1, "FAILED: I didn't crash");
}

/*
 * Allocates and frees a page and checks that the page freed is no
 * longer valid. (Crashes when successful.)
 */
static
void
test7(void)
{
	void *p;

	(void)dosbrk(PAGE_SIZE);
	(void)dosbrk(-PAGE_SIZE);
	p = dosbrk(0);
	printf("This should produce fatal signal 11 (SIGSEGV).\n");
	((long *)p)[10] = 0;
	errx(1, "FAILED: I didn't crash");
}

/*
 * Allocates some pages, frees half of them, and checks that the page
 * past the new end of the heap is no longer valid. (Crashes when
 * successful.)
 */
static
void
test8(void)
{
	void *p;

	(void)dosbrk(PAGE_SIZE * 12);
	(void)dosbrk(-PAGE_SIZE * 6);
	p = dosbrk(0);
	printf("This should produce fatal signal 11 (SIGSEGV).\n");
	((long *)p)[10] = 0;
	errx(1, "FAILED: I didn't crash");
}

////////////////////////////////////////////////////////////
// heap size

/*
 * Allocate all memory at once.
 *
 * This works by trying successively smaller sizes until one succeeds.
 * Note that if you allow arbitrary swap overcommit this test will run
 * the system out of swap. If this kills the system that's probably ok
 * (check with your course staff, but handing OOM is difficult and
 * probably not worth the time it would take you)... but ideally no
 * one process is allowed to get big enough to do this.
 *
 * The recommended behavior is to set the process's maximum heap size
 * to the amount of available RAM + swap that can be used at once.
 * Because the test uses powers of two, this doesn't have to be very
 * precise (e.g. counting the size of the non-heap parts of the
 * process probably doesn't matter unless you're very unlucky) and
 * isn't very difficult.
 *
 * Another option is to disallow swap overcommit, in which case you
 * have sbrk try to reserve swap pages for each allocation, which will
 * fail for huge allocations. The problem with this in practice is
 * that you end up needing a huge swap disk even to run relatively
 * small workloads, and then this test takes forever to run.
 */
static
void
test9(void)
{
	size_t size;
	unsigned i, pages, dot;
	void *p;
	bool bad;

#define HUGESIZE (1024 * 1024 * 1024)	/* 1G */

	printf("Checking how much memory we can allocate:\n");
	for (size = HUGESIZE; (p = sbrk(size)) == (void *)-1; size = size/2) {
		printf("  %9lu bytes: failed\n", (unsigned long) size);
	}
	printf("  %9lu bytes: succeeded\n", (unsigned long) size);
	printf("Passed sbrk test 9 (part 1/5)\n");

	printf("Touching each page.\n");
	pages = size / PAGE_SIZE;
	dot = pages / 64;
	for (i=0; i<pages; i++) {
		markpagelight(p, i);
		if (dot > 0 && i % dot == 0) {
			printf(".");
		}
	}
	if (dot > 0) {
		printf("\n");
	}

	printf("Testing each page.\n");
	bad = false;
	for (i=0; i<pages; i++) {
		if (checkpagelight(p, i, dot > 0)) {
			if (dot > 0) {
				printf("\n");
			}
			warnx("FAILED: data corrupt");
			bad = true;
		}
		if (dot > 0 && i % dot == 0) {
			printf(".");
		}
	}
	if (dot > 0) {
		printf("\n");
	}
	if (bad) {
		exit(1);
	}
	printf("Passed sbrk test 9 (part 2/5)\n");

	printf("Freeing the memory.\n");
	(void)dosbrk(-size);
	printf("Passed sbrk test 9 (part 3/5)\n");

	printf("Allocating the memory again.\n");
	(void)dosbrk(size);
	printf("Passed sbrk test 9 (part 4/5)\n");

	printf("And really freeing it.\n");
	(void)dosbrk(-size);
	printf("Passed sbrk test 9 (all)\n");
}

/*
 * Allocate all of memory one page at a time. The same restrictions
 * and considerations apply as above.
 */
static
void
test10(void)
{
	void *p, *op;
	unsigned i, n;
	bool bad;

	printf("Allocating all of memory one page at a time:\n");
	op = dosbrk(0);
	n = 0;
	while ((p = sbrk(PAGE_SIZE)) != (void *)-1) {
		markpagelight(op, n);
		n++;
	}
	printf("Got %u pages (%zu bytes).\n", n, (size_t)PAGE_SIZE * n);

	printf("Now freeing them.\n");
	bad = false;
	for (i=0; i<n; i++) {
		if (checkpagelight(op, n - i - 1, false)) {
			warnx("FAILED: data corrupt on page %u", i);
			bad = true;
		}
		(void)dosbrk(-PAGE_SIZE);
	}
	if (bad) {
		exit(1);
	}
	printf("Freed %u pages.\n", n);

	p = dosbrk(0);
	if (p != op) {
		errx(1, "FAILURE: break did not return to original value");
	}

	printf("Now let's see if I can allocate another page.\n");
	p = dosbrk(PAGE_SIZE);
	markpage(p, 0);
	if (checkpage(p, 0, false)) {
		errx(1, "FAILED: data corrupt");
	}
	(void)dosbrk(-PAGE_SIZE);

	printf("Passed sbrk test 10.\n");
}

////////////////////////////////////////////////////////////
// leaking and cleanup on exit

static
void
test11(void)
{
	const unsigned num = 256;

	void *p;
	unsigned i;
	bool bad;

	printf("Allocating %u pages (%zu bytes).\n", num,
	       (size_t)PAGE_SIZE * num);
	p = dosbrk(num * PAGE_SIZE);

	printf("Touching the pages.\n");
	for (i=0; i<num; i++) {
		markpagelight(p, i);
		if (i % 4 == 0) {
			printf(".");
		}
	}
	printf("\n");

	printf("Checking the pages.\n");
	bad = false;
	for (i=0; i<num; i++) {
		if (checkpagelight(p, i, true)) {
			warnx("FAILED: data corrupt");
			bad = true;
		}
		if (i % 4 == 0) {
			printf(".");
		}
	}
	printf("\n");
	if (bad) {
		exit(1);
	}

	printf("Now NOT freeing the pages. They should get freed on exit.\n");
	printf("If not, you'll notice pretty quickly.\n");
}

////////////////////////////////////////////////////////////
// forking

/*
 * Fork and then allocate in both the parent and the child, in case
 * fork messes up the heap. Note: this is not intended to test the
 * parent and child running concurrently -- that should probably be
 * its own test program.
 */
static
void
test12(void)
{
	pid_t pid;
	void *p;

	printf("Forking...\n");
	pid = dofork();
	if (pid == 0) {
		/* child */
		say("Child allocating a page...\n");
		p = dosbrk(PAGE_SIZE);
		markpage(p, 0);
		if (checkpage(p, 0, false)) {
			errx(1, "FAILED: data corrupt in child");
		}
		say("Child done.\n");
		exit(0);
	}
	/* parent */
	say("Parent allocating a page...\n");
	p = dosbrk(PAGE_SIZE);
	markpage(p, 0);
	if (checkpage(p, 0, false)) {
		errx(1, "FAILED: data corrupt in parent");
	}
	say("Parent done.\n");
	dowait(pid);
	printf("Passed sbrk test 12.\n");
}

/*
 * Allocate and then fork, in case fork doesn't preserve the heap.
 */
static
void
test13(void)
{
	pid_t pid;
	void *p;

	printf("Allocating a page...\n");
	p = dosbrk(PAGE_SIZE);
	markpage(p, 0);
	if (checkpage(p, 0, false)) {
		errx(1, "FAILED: data corrupt before forking");
	}

	printf("Forking...\n");
	pid = dofork();
	if (pid == 0) {
		/* child */
		if (checkpage(p, 0, false)) {
			errx(1, "FAILED: data corrupt in child");
		}
		exit(0);
	}
	if (checkpage(p, 0, false)) {
		errx(1, "FAILED: data corrupt in parent");
	}
	dowait(pid);
	printf("Passed sbrk test 13.\n");
}

/*
 * Allocate, then fork, then free the allocated page in the child.
 */
static
void
test14(void)
{
	pid_t pid;
	void *p;

	printf("Allocating a page...\n");
	p = dosbrk(PAGE_SIZE);
	markpage(p, 0);
	if (checkpage(p, 0, false)) {
		errx(1, "FAILED: data corrupt before forking");
	}

	printf("Forking...\n");
	pid = dofork();
	if (pid == 0) {
		/* child */
		if (checkpage(p, 0, false)) {
			errx(1, "FAILED: data corrupt in child");
		}
		printf("Child freeing a page...\n");
		dosbrk(-PAGE_SIZE);
		exit(0);
	}
	dowait(pid);
	if (checkpage(p, 0, false)) {
		errx(1, "FAILED: data corrupt in parent after child ran");
	}
	printf("Passed sbrk test 14.\n");
}

/*
 * Allocate and free in both the parent and the child, and do more
 * than one page.
 */
static
void
test15(void)
{
	unsigned num = 12;

	pid_t pid;
	unsigned i;
	void *p;

	printf("Allocating %u pages...\n", num);
	p = dosbrk(PAGE_SIZE * num);
	for (i=0; i<num; i++) {
		markpage(p, i);
	}
	for (i=0; i<num; i++) {
		if (checkpage(p, i, false)) {
			errx(1, "FAILED: data corrupt before forking");
		}
	}

	printf("Freeing one page...\n");
	(void)dosbrk(-PAGE_SIZE);
	num--;
	for (i=0; i<num; i++) {
		if (checkpage(p, i, false)) {
			errx(1, "FAILED: data corrupt before forking (2)");
		}
	}

	printf("Allocating two pages...\n");
	(void)dosbrk(PAGE_SIZE * 2);
	markpage(p, num++);
	markpage(p, num++);
	for (i=0; i<num; i++) {
		if (checkpage(p, i, false)) {
			errx(1, "FAILED: data corrupt before forking (3)");
		}
	}

	printf("Forking...\n");
	pid = dofork();
	if (pid == 0) {
		/* child */
		for (i=0; i<num; i++) {
			if (checkpage(p, i, false)) {
				errx(1, "FAILED: data corrupt in child");
			}
		}

		say("Child: freeing three pages\n");
		dosbrk(-PAGE_SIZE * 3);
		num -= 3;
		for (i=0; i<num; i++) {
			if (checkpage(p, i, false)) {
				errx(1, "FAILED: data corrupt in child (2)");
			}
		}

		say("Child: allocating two pages\n");
		dosbrk(PAGE_SIZE * 2);
		markpage(p, num++);
		markpage(p, num++);
		for (i=0; i<num; i++) {
			if (checkpage(p, i, false)) {
				errx(1, "FAILED: data corrupt in child (3)");
			}
		}

		say("Child: freeing all\n");
		dosbrk(-PAGE_SIZE * num);
		exit(0);
	}
	say("Parent: allocating four pages\n");
	dosbrk(PAGE_SIZE * 4);
	for (i=0; i<4; i++) {
		markpage(p, num++);
	}
	for (i=0; i<num; i++) {
		if (checkpage(p, i, false)) {
			errx(1, "FAILED: data corrupt in parent");
		}
	}

	say("Parent: waiting\n");
	dowait(pid);

	for (i=0; i<num; i++) {
		if (checkpage(p, i, false)) {
			errx(1, "FAILED: data corrupt after waiting");
		}
	}

	(void)dosbrk(-PAGE_SIZE * num);
	printf("Passed sbrk test 15.\n");
}

////////////////////////////////////////////////////////////
// stress testing

static
void
stresstest(unsigned long seed, bool large)
{
	const unsigned loops = 10000;
	const unsigned dot = 200;

	void *op;
	unsigned num;
	unsigned i, j, pages;
	unsigned long r;
	bool bad, neg;

	srandom(seed);
	printf("Seeded random number generator with %lu.\n", seed);

	op = dosbrk(0);

	bad = false;
	num = 0;
	for (i=0; i<loops; i++) {
		/*
		 * The goal of this test is not to stress the VM system
		 * by thrashing swap (other tests do that) but by running
		 * the sbrk code a lot. So, clamp the total size at either
		 * 128K or 512K, which is 32 or 128 pages respectively.
		 */
		r = random();
		pages = r % (large ? 32 : 8);
		neg = pages <= num && ((r & 128) == 0);
		if (!neg && num + pages > (large ? 128 : 32)) {
			neg = 1;
		}
		if (neg) {
			dosbrk(-(pages * PAGE_SIZE));
			num -= pages;
		}
		else {
			dosbrk(pages * PAGE_SIZE);
			for (j=0; j<pages; j++) {
				markpagelight(op, num + j);
			}
			num += pages;
		}
		for (j=0; j<num; j++) {
			if (checkpagelight(op, j, true)) {
				printf("\n");
				warnx("FAILED: data corrupt on page %u", j);
				bad = true;
			}
		}
		if (i % dot == 0) {
			printf(".");
		}
	}
	printf("\n");
	if (bad) {
		warnx("FAILED");
		exit(1);
	}

	dosbrk(-(num * PAGE_SIZE));
	printf("Passed sbrk %s stress test.\n", large ? "large" : "small");
}

static
void
test16(void)
{
	stresstest(0, false);
}

static
void
test17(void)
{
	stresstest(getseed(), false);
}

static
void
test18(void)
{
	printf("Enter random seed: ");
	stresstest(geti(), false);
}

static
void
test19(void)
{
	stresstest(0, true);
}

static
void
test20(void)
{
	stresstest(getseed(), true);
}

static
void
test21(void)
{
	printf("Enter random seed: ");
	stresstest(geti(), true);
}

////////////////////////////////////////////////////////////
// main

static const struct {
	int num;
	const char *desc;
	void (*func)(void);
} tests[] = {
	{ 1, "Allocate one page", test1 },
	{ 2, "Allocate and free one page", test2 },
	{ 3, "Allocate and free several pages", test3 },
	{ 4, "Allocate several pages and free them one at a time", test4 },
	{ 5, "Check the heap end (crashes)", test5 },
	{ 6, "Allocate and check the heap end (crashes)", test6 },
	{ 7, "Allocate and free and check the heap end (crashes)", test7 },
	{ 8, "Allocate several, free some, check heap end (crashes)", test8 },
	{ 9, "Allocate all memory in a big chunk", test9 },
	{ 10, "Allocate all memory a page at a time", test10 },
	{ 11, "Allocate a lot and intentionally leak it", test11 },
	{ 12, "Fork and then allocate", test12 },
	{ 13, "Allocate and then fork", test13 },
	{ 14, "Allocate and then fork and free", test14 },
	{ 15, "Allocate, fork, allocate more, and free (and spam)", test15 },
	{ 16, "Small stress test", test16 },
	{ 17, "Randomized small stress test", test17 },
	{ 18, "Small stress test with particular seed", test18 },
	{ 19, "Large stress test", test19 },
	{ 20, "Randomized large stress test", test20 },
	{ 21, "Large stress test with particular seed", test21 },
};
static const unsigned numtests = sizeof(tests) / sizeof(tests[0]);

static
int
dotest(int tn)
{
	unsigned i;

	for (i=0; i<numtests; i++) {
		if (tests[i].num == tn) {
			tests[i].func();
			return 0;
		}
	}
	return -1;
}

int
main(int argc, char *argv[])
{
	int i, tn;
	unsigned j;
	bool menu = true;

	setup();

	if (argc > 1) {
		for (i=1; i<argc; i++) {
			dotest(atoi(argv[i]));
		}
		return 0;
	}

	while (1) {
		if (menu) {
			for (j=0; j<numtests; j++) {
				printf("  %2d  %s\n", tests[j].num,
				       tests[j].desc);
			}
			menu = false;
		}
		printf("sbrktest: ");
		tn = geti();
		if (tn < 0) {
			break;
		}

		if (dotest(tn)) {
			menu = true;
		}
	}

	return 0;
}