1226 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1226 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2008, 2009, 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.
 | 
						|
 */
 | 
						|
 | 
						|
/*
 | 
						|
 * psort - parallel sort.
 | 
						|
 *
 | 
						|
 * This is loosely based on some real parallel sort benchmarks, but
 | 
						|
 * because of various limitations of OS/161 it is massively
 | 
						|
 * inefficient. But that's ok; the goal is to stress the VM and buffer
 | 
						|
 * cache.
 | 
						|
 */
 | 
						|
 | 
						|
#include <sys/types.h>
 | 
						|
#include <sys/stat.h>
 | 
						|
#include <sys/wait.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdarg.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <assert.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <fcntl.h>
 | 
						|
#include <errno.h>
 | 
						|
 | 
						|
#ifndef RANDOM_MAX
 | 
						|
/* Note: this is correct for OS/161 but not for some Unix C libraries */
 | 
						|
#define RANDOM_MAX RAND_MAX
 | 
						|
#endif
 | 
						|
 | 
						|
#define PATH_KEYS    "sortkeys"
 | 
						|
#define PATH_SORTED  "output"
 | 
						|
#define PATH_TESTDIR "psortdir"
 | 
						|
#define PATH_RANDOM  "rand:"
 | 
						|
 | 
						|
/*
 | 
						|
 * Workload sizing.
 | 
						|
 *
 | 
						|
 * We fork numprocs processes. Each one works on WORKNUM integers at a
 | 
						|
 * time, so the total VM load is WORKNUM * sizeof(int) * numprocs. For
 | 
						|
 * the best stress testing, this should be substantially larger than
 | 
						|
 * your available RAM.
 | 
						|
 *
 | 
						|
 * Meanwhile we generate and process numkeys integers, so the total
 | 
						|
 * filesystem load is numkeys * sizeof(int). For the best stress
 | 
						|
 * testing this should be substantially larger than your buffer cache.
 | 
						|
 *
 | 
						|
 * This test is supposed to adjust to arbitrary settings of WORKNUM,
 | 
						|
 * numprocs, and numkeys. For a small test try these settings:
 | 
						|
 *      WORKNUM         (16*1024)
 | 
						|
 *      numprocs        4, or 2
 | 
						|
 *      numkeys         10000
 | 
						|
 *
 | 
						|
 * This should fit in memory (memory footprint is 256K) and generate
 | 
						|
 * only small files (~40K) which will work on the pre-largefiles SFS.
 | 
						|
 *
 | 
						|
 * The default settings are:
 | 
						|
 *      WORKNUM         (96*1024)
 | 
						|
 *      numprocs        4
 | 
						|
 *      numkeys         128*1024
 | 
						|
 *
 | 
						|
 * so the memory footprint is 1.5M (comparable to the VM tests) and
 | 
						|
 * the base file size is 512K. Note that because there are a lot of
 | 
						|
 * temporary files, it pumps a good deal more than 512K of data. For
 | 
						|
 * the record, the solution set takes about 7.5 minutes of virtual
 | 
						|
 * time to run it, in either 1M or 2M of RAM and with 4 CPUs; this
 | 
						|
 * currently corresponds to about 13-14 minutes of real time.
 | 
						|
 *
 | 
						|
 * Note that the parent psort process serves as a director and doesn't
 | 
						|
 * itself compute; it has a workspace (because it's a static buffer)
 | 
						|
 * but doesn't use it. A VM system that doesn't do zerofill
 | 
						|
 * optimization will be a lot slower because it has to copy this space
 | 
						|
 * for every batch of forks.
 | 
						|
 *
 | 
						|
 * Also note that you can set numprocs and numkeys on the command
 | 
						|
 * line, but not WORKNUM.
 | 
						|
 *
 | 
						|
 * FUTURE: maybe make a build option to malloc the work space instead
 | 
						|
 * of using a static buffer, which would allow choosing WORKNUM on the
 | 
						|
 * command line too, at the cost of depending on malloc working.
 | 
						|
 */
 | 
						|
 | 
						|
/* Set the workload size. */
 | 
						|
#define WORKNUM      (96*1024)
 | 
						|
static int numprocs = 4;
 | 
						|
static int numkeys = 128*1024;
 | 
						|
 | 
						|
/* Per-process work buffer */
 | 
						|
static int workspace[WORKNUM];
 | 
						|
 | 
						|
/* Random seed for generating the data */
 | 
						|
static long randomseed = 15432753;
 | 
						|
 | 
						|
/* other state */
 | 
						|
static off_t correctsize;
 | 
						|
static unsigned long checksum;
 | 
						|
 | 
						|
#define NOBODY (-1)
 | 
						|
static int me = NOBODY;
 | 
						|
 | 
						|
static const char *progname;
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
sortints(int *v, int num)
 | 
						|
{
 | 
						|
	int pivotval, pivotpoint, pivotcount;
 | 
						|
	int frontpos, readpos, endpos, i, j;
 | 
						|
	int tmp;
 | 
						|
 | 
						|
	if (num < 2) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	pivotpoint = num/2;
 | 
						|
	pivotval = v[pivotpoint];
 | 
						|
	pivotcount = 0;
 | 
						|
 | 
						|
	frontpos = 0;
 | 
						|
	readpos = 0;
 | 
						|
	endpos = num;
 | 
						|
	while (readpos < endpos) {
 | 
						|
		if (v[readpos] < pivotval) {
 | 
						|
			v[frontpos++] = v[readpos++];
 | 
						|
		}
 | 
						|
		else if (v[readpos] == pivotval) {
 | 
						|
			readpos++;
 | 
						|
			pivotcount++;
 | 
						|
		}
 | 
						|
		else {
 | 
						|
			tmp = v[--endpos];
 | 
						|
			v[endpos] = v[readpos];
 | 
						|
			v[readpos] = tmp;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	assert(readpos == endpos);
 | 
						|
	assert(frontpos + pivotcount == readpos);
 | 
						|
 | 
						|
	for (i=frontpos; i<endpos; i++) {
 | 
						|
		v[i] = pivotval;
 | 
						|
	}
 | 
						|
 | 
						|
	for (i=endpos, j=num-1; i<j; i++,j--) {
 | 
						|
		tmp = v[i];
 | 
						|
		v[i] = v[j];
 | 
						|
		v[j] = tmp;
 | 
						|
	}
 | 
						|
 | 
						|
	sortints(v, frontpos);
 | 
						|
	sortints(&v[endpos], num-endpos);
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
initprogname(const char *av0)
 | 
						|
{
 | 
						|
	if (av0) {
 | 
						|
		progname = strrchr(av0, '/');
 | 
						|
		if (progname) {
 | 
						|
			progname++;
 | 
						|
		}
 | 
						|
		else {
 | 
						|
			progname = av0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		progname = "psort";
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
vscomplain(char *buf, size_t len, const char *fmt, va_list ap, int err)
 | 
						|
{
 | 
						|
	size_t pos;
 | 
						|
 | 
						|
	if (me >= 0) {
 | 
						|
		snprintf(buf, len, "%s: proc %d: ", progname, me);
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		snprintf(buf, len, "%s: ", progname);
 | 
						|
	}
 | 
						|
	pos = strlen(buf);
 | 
						|
 | 
						|
	vsnprintf(buf+pos, len-pos, fmt, ap);
 | 
						|
	pos = strlen(buf);
 | 
						|
 | 
						|
	if (err >= 0) {
 | 
						|
		snprintf(buf+pos, len-pos, ": %s\n", strerror(err));
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		snprintf(buf+pos, len-pos, "\n");
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
complainx(const char *fmt, ...)
 | 
						|
{
 | 
						|
	char buf[256];
 | 
						|
	va_list ap;
 | 
						|
	ssize_t junk;
 | 
						|
 | 
						|
	va_start(ap, fmt);
 | 
						|
	vscomplain(buf, sizeof(buf), fmt, ap, -1);
 | 
						|
	va_end(ap);
 | 
						|
 | 
						|
	/* Write the message in one go so it's atomic */
 | 
						|
	junk = write(STDERR_FILENO, buf, strlen(buf));
 | 
						|
 | 
						|
	/*
 | 
						|
	 * This variable must be assigned and then ignored with some
 | 
						|
	 * (broken) Linux C libraries. Ah, Linux...
 | 
						|
	 */
 | 
						|
	(void)junk;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
complain(const char *fmt, ...)
 | 
						|
{
 | 
						|
	char buf[256];
 | 
						|
	va_list ap;
 | 
						|
	ssize_t junk;
 | 
						|
	int err = errno;
 | 
						|
 | 
						|
	va_start(ap, fmt);
 | 
						|
	vscomplain(buf, sizeof(buf), fmt, ap, err);
 | 
						|
	va_end(ap);
 | 
						|
 | 
						|
	/* Write the message in one go so it's atomic */
 | 
						|
	junk = write(STDERR_FILENO, buf, strlen(buf));
 | 
						|
 | 
						|
	/*
 | 
						|
	 * This variable must be assigned and then ignored with some
 | 
						|
	 * (broken) Linux C libraries. Ah, Linux...
 | 
						|
	 */
 | 
						|
	(void)junk;
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
static
 | 
						|
int
 | 
						|
doopen(const char *path, int flags, int mode)
 | 
						|
{
 | 
						|
	int fd;
 | 
						|
 | 
						|
	fd = open(path, flags, mode);
 | 
						|
	if (fd<0) {
 | 
						|
		complain("%s", path);
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
	return fd;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
doclose(const char *path, int fd)
 | 
						|
{
 | 
						|
	if (close(fd)) {
 | 
						|
		complain("%s: close", path);
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
docreate(const char *path)
 | 
						|
{
 | 
						|
	int fd;
 | 
						|
 | 
						|
	fd = doopen(path, O_WRONLY|O_CREAT|O_TRUNC, 0664);
 | 
						|
	doclose(path, fd);
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
doremove(const char *path)
 | 
						|
{
 | 
						|
	static int noremove;
 | 
						|
 | 
						|
	if (noremove) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (remove(path) < 0) {
 | 
						|
		if (errno == ENOSYS) {
 | 
						|
			/* Complain (and try) only once. */
 | 
						|
			noremove = 1;
 | 
						|
		}
 | 
						|
		complain("%s: remove", path);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
off_t
 | 
						|
getsize(const char *path)
 | 
						|
{
 | 
						|
	struct stat buf;
 | 
						|
	int fd;
 | 
						|
	static int no_stat, no_fstat;
 | 
						|
 | 
						|
	if (!no_stat) {
 | 
						|
		if (stat(path, &buf) == 0) {
 | 
						|
			return buf.st_size;
 | 
						|
		}
 | 
						|
		if (errno != ENOSYS) {
 | 
						|
			complain("%s: stat", path);
 | 
						|
			exit(1);
 | 
						|
		}
 | 
						|
		/* Avoid further "Unknown syscall 81" messages */
 | 
						|
		no_stat = 1;
 | 
						|
	}
 | 
						|
 | 
						|
	fd = doopen(path, O_RDONLY, 0);
 | 
						|
	if (!no_fstat) {
 | 
						|
		if (fstat(fd, &buf) == 0) {
 | 
						|
			close(fd);
 | 
						|
			return buf.st_size;
 | 
						|
		}
 | 
						|
		if (errno != ENOSYS) {
 | 
						|
			complain("%s: stat", path);
 | 
						|
			exit(1);
 | 
						|
		}
 | 
						|
		/* Avoid further "Unknown syscall 82" messages */
 | 
						|
		no_fstat = 1;
 | 
						|
	}
 | 
						|
 | 
						|
	/* otherwise, lseek */
 | 
						|
	if (lseek(fd, 0, SEEK_END) >= 0) {
 | 
						|
		buf.st_size = lseek(fd, 0, SEEK_CUR);
 | 
						|
		if (buf.st_size >= 0) {
 | 
						|
			return buf.st_size;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	complain("%s: getting file size with lseek", path);
 | 
						|
	close(fd);
 | 
						|
	exit(1);
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
size_t
 | 
						|
doread(const char *path, int fd, void *buf, size_t len)
 | 
						|
{
 | 
						|
	int result;
 | 
						|
 | 
						|
	result = read(fd, buf, len);
 | 
						|
	if (result < 0) {
 | 
						|
		complain("%s: read", path);
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
	return (size_t) result;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
doexactread(const char *path, int fd, void *buf, size_t len)
 | 
						|
{
 | 
						|
	size_t result;
 | 
						|
 | 
						|
	result = doread(path, fd, buf, len);
 | 
						|
	if (result != len) {
 | 
						|
		complainx("%s: read: short count", path);
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
dowrite(const char *path, int fd, const void *buf, size_t len)
 | 
						|
{
 | 
						|
	int result;
 | 
						|
 | 
						|
	result = write(fd, buf, len);
 | 
						|
	if (result < 0) {
 | 
						|
		complain("%s: write", path);
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
	if ((size_t) result != len) {
 | 
						|
		complainx("%s: write: short count", path);
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
dolseek(const char *name, int fd, off_t offset, int whence)
 | 
						|
{
 | 
						|
	if (lseek(fd, offset, whence) < 0) {
 | 
						|
		complain("%s: lseek", name);
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
#if 0 /* let's not require subdirs */
 | 
						|
static
 | 
						|
void
 | 
						|
dochdir(const char *path)
 | 
						|
{
 | 
						|
	if (chdir(path) < 0) {
 | 
						|
		complain("%s: chdir", path);
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
domkdir(const char *path, int mode)
 | 
						|
{
 | 
						|
	if (mkdir(path, mode) < 0) {
 | 
						|
		complain("%s: mkdir", path);
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
}
 | 
						|
#endif /* 0 */
 | 
						|
 | 
						|
static
 | 
						|
pid_t
 | 
						|
dofork(void)
 | 
						|
{
 | 
						|
	pid_t pid;
 | 
						|
 | 
						|
	pid = fork();
 | 
						|
	if (pid < 0) {
 | 
						|
		complain("fork");
 | 
						|
		/* but don't exit */
 | 
						|
	}
 | 
						|
 | 
						|
	return pid;
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
static
 | 
						|
int
 | 
						|
dowait(int guy, pid_t pid)
 | 
						|
{
 | 
						|
	int status, result;
 | 
						|
 | 
						|
	result = waitpid(pid, &status, 0);
 | 
						|
	if (result < 0) {
 | 
						|
		complain("waitpid");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
	if (WIFSIGNALED(status)) {
 | 
						|
		complainx("proc %d: signal %d", guy, WTERMSIG(status));
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
	assert(WIFEXITED(status));
 | 
						|
	status = WEXITSTATUS(status);
 | 
						|
	if (status) {
 | 
						|
		complainx("proc %d: exit %d", guy, status);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
doforkall(const char *phasename, void (*func)(void))
 | 
						|
{
 | 
						|
	int i, bad = 0;
 | 
						|
	pid_t pids[numprocs];
 | 
						|
 | 
						|
	for (i=0; i<numprocs; i++) {
 | 
						|
		pids[i] = dofork();
 | 
						|
		if (pids[i] < 0) {
 | 
						|
			bad = 1;
 | 
						|
		}
 | 
						|
		else if (pids[i] == 0) {
 | 
						|
			/* child */
 | 
						|
			me = i;
 | 
						|
			func();
 | 
						|
			exit(0);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for (i=0; i<numprocs; i++) {
 | 
						|
		if (pids[i] > 0 && dowait(i, pids[i])) {
 | 
						|
			bad = 1;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (bad) {
 | 
						|
		complainx("%s failed.", phasename);
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
seekmyplace(const char *name, int fd)
 | 
						|
{
 | 
						|
	int keys_per, myfirst;
 | 
						|
	off_t offset;
 | 
						|
 | 
						|
	keys_per = numkeys / numprocs;
 | 
						|
	myfirst = me*keys_per;
 | 
						|
	offset = myfirst * sizeof(int);
 | 
						|
 | 
						|
	dolseek(name, fd, offset, SEEK_SET);
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
int
 | 
						|
getmykeys(void)
 | 
						|
{
 | 
						|
	int keys_per, myfirst, mykeys;
 | 
						|
 | 
						|
	keys_per = numkeys / numprocs;
 | 
						|
	myfirst = me*keys_per;
 | 
						|
	mykeys = (me < numprocs-1) ? keys_per : numkeys - myfirst;
 | 
						|
 | 
						|
	return mykeys;
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
static
 | 
						|
unsigned long
 | 
						|
checksum_file(const char *path)
 | 
						|
{
 | 
						|
	int fd;
 | 
						|
	char buf[512];
 | 
						|
	size_t count, i;
 | 
						|
	unsigned long sum = 0;
 | 
						|
 | 
						|
	fd = doopen(path, O_RDONLY, 0);
 | 
						|
 | 
						|
	while ((count = doread(path, fd, buf, sizeof(buf))) > 0) {
 | 
						|
		for (i=0; i<count; i++) {
 | 
						|
			sum += (unsigned char) buf[i];
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	doclose(path, fd);
 | 
						|
 | 
						|
	return sum;
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
static long *seeds;
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
genkeys_sub(void)
 | 
						|
{
 | 
						|
	int fd, i, mykeys, keys_done, keys_to_do, value;
 | 
						|
 | 
						|
	fd = doopen(PATH_KEYS, O_WRONLY, 0);
 | 
						|
 | 
						|
	mykeys = getmykeys();
 | 
						|
	seekmyplace(PATH_KEYS, fd);
 | 
						|
 | 
						|
	srandom(seeds[me]);
 | 
						|
	keys_done = 0;
 | 
						|
	while (keys_done < mykeys) {
 | 
						|
		keys_to_do = mykeys - keys_done;
 | 
						|
		if (keys_to_do > WORKNUM) {
 | 
						|
			keys_to_do = WORKNUM;
 | 
						|
		}
 | 
						|
 | 
						|
		for (i=0; i<keys_to_do; i++) {
 | 
						|
			value = random();
 | 
						|
 | 
						|
			// check bounds of value
 | 
						|
			assert(value >= 0);
 | 
						|
			assert(value <= RANDOM_MAX);
 | 
						|
 | 
						|
			// do not allow the value to be zero or RANDOM_MAX
 | 
						|
			while (value == 0 || value == RANDOM_MAX) {
 | 
						|
				value = random();
 | 
						|
			}
 | 
						|
 | 
						|
			workspace[i] = value;
 | 
						|
		}
 | 
						|
 | 
						|
		dowrite(PATH_KEYS, fd, workspace, keys_to_do*sizeof(int));
 | 
						|
		keys_done += keys_to_do;
 | 
						|
	}
 | 
						|
 | 
						|
	doclose(PATH_KEYS, fd);
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
genkeys(void)
 | 
						|
{
 | 
						|
	long seedspace[numprocs];
 | 
						|
	int i;
 | 
						|
 | 
						|
	/* Create the file. */
 | 
						|
	docreate(PATH_KEYS);
 | 
						|
 | 
						|
	/* Generate random seeds for each subprocess. */
 | 
						|
	srandom(randomseed);
 | 
						|
	for (i=0; i<numprocs; i++) {
 | 
						|
		seedspace[i] = random();
 | 
						|
	}
 | 
						|
 | 
						|
	/* Do it. */
 | 
						|
	complainx("Generating %d integers using %d procs", numkeys, numprocs);
 | 
						|
	seeds = seedspace;
 | 
						|
	doforkall("Initialization", genkeys_sub);
 | 
						|
	seeds = NULL;
 | 
						|
 | 
						|
	/* Cross-check the size of the output. */
 | 
						|
	if (getsize(PATH_KEYS) != correctsize) {
 | 
						|
		complainx("%s: file is wrong size", PATH_KEYS);
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
 | 
						|
	/* Checksum the output. */
 | 
						|
	complainx("Checksumming the data (using one proc)");
 | 
						|
	checksum = checksum_file(PATH_KEYS);
 | 
						|
	complainx("Checksum of unsorted keys: %ld", checksum);
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
static
 | 
						|
const char *
 | 
						|
binname(int a, int b)
 | 
						|
{
 | 
						|
	static char rv[32];
 | 
						|
	snprintf(rv, sizeof(rv), "bin-%d-%d", a, b);
 | 
						|
	return rv;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
const char *
 | 
						|
mergedname(int a)
 | 
						|
{
 | 
						|
	static char rv[32];
 | 
						|
	snprintf(rv, sizeof(rv), "merged-%d", a);
 | 
						|
	return rv;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
bin(void)
 | 
						|
{
 | 
						|
	int infd, outfds[numprocs];
 | 
						|
	const char *name;
 | 
						|
	int i, mykeys, keys_done, keys_to_do;
 | 
						|
	int key, pivot, binnum;
 | 
						|
 | 
						|
	infd = doopen(PATH_KEYS, O_RDONLY, 0);
 | 
						|
 | 
						|
	mykeys = getmykeys();
 | 
						|
	seekmyplace(PATH_KEYS, infd);
 | 
						|
 | 
						|
	for (i=0; i<numprocs; i++) {
 | 
						|
		name = binname(me, i);
 | 
						|
		outfds[i] = doopen(name, O_WRONLY|O_CREAT|O_TRUNC, 0664);
 | 
						|
	}
 | 
						|
 | 
						|
	pivot = (RANDOM_MAX / numprocs);
 | 
						|
 | 
						|
	keys_done = 0;
 | 
						|
	while (keys_done < mykeys) {
 | 
						|
		keys_to_do = mykeys - keys_done;
 | 
						|
		if (keys_to_do > WORKNUM) {
 | 
						|
			keys_to_do = WORKNUM;
 | 
						|
		}
 | 
						|
 | 
						|
		doexactread(PATH_KEYS, infd, workspace,
 | 
						|
			    keys_to_do * sizeof(int));
 | 
						|
 | 
						|
		for (i=0; i<keys_to_do; i++) {
 | 
						|
			key = workspace[i];
 | 
						|
 | 
						|
			binnum = key / pivot;
 | 
						|
			if (key <= 0) {
 | 
						|
				complainx("proc %d: garbage key %d", me, key);
 | 
						|
				key = 0;
 | 
						|
			}
 | 
						|
			assert(binnum >= 0);
 | 
						|
			assert(binnum < numprocs);
 | 
						|
			dowrite("bin", outfds[binnum], &key, sizeof(key));
 | 
						|
		}
 | 
						|
 | 
						|
		keys_done += keys_to_do;
 | 
						|
	}
 | 
						|
	doclose(PATH_KEYS, infd);
 | 
						|
 | 
						|
	for (i=0; i<numprocs; i++) {
 | 
						|
		doclose(binname(me, i), outfds[i]);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
sortbins(void)
 | 
						|
{
 | 
						|
	const char *name;
 | 
						|
	int i, fd;
 | 
						|
	off_t binsize;
 | 
						|
 | 
						|
	for (i=0; i<numprocs; i++) {
 | 
						|
		name = binname(me, i);
 | 
						|
		binsize = getsize(name);
 | 
						|
		if (binsize % sizeof(int) != 0) {
 | 
						|
			complainx("%s: bin size %ld no good", name,
 | 
						|
				  (long) binsize);
 | 
						|
			exit(1);
 | 
						|
		}
 | 
						|
		if (binsize > (off_t) sizeof(workspace)) {
 | 
						|
			complainx("proc %d: %s: bin too large", me, name);
 | 
						|
			exit(1);
 | 
						|
		}
 | 
						|
 | 
						|
		fd = doopen(name, O_RDWR, 0);
 | 
						|
		doexactread(name, fd, workspace, binsize);
 | 
						|
 | 
						|
		sortints(workspace, binsize/sizeof(int));
 | 
						|
 | 
						|
		dolseek(name, fd, 0, SEEK_SET);
 | 
						|
		dowrite(name, fd, workspace, binsize);
 | 
						|
		doclose(name, fd);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
mergebins(void)
 | 
						|
{
 | 
						|
	int infds[numprocs], outfd;
 | 
						|
	int values[numprocs], ready[numprocs];
 | 
						|
	const char *name, *outname;
 | 
						|
	int i, result;
 | 
						|
	int numready, place, val, worknum;
 | 
						|
 | 
						|
	outname = mergedname(me);
 | 
						|
	outfd = doopen(outname, O_WRONLY|O_CREAT|O_TRUNC, 0664);
 | 
						|
 | 
						|
	for (i=0; i<numprocs; i++) {
 | 
						|
		name = binname(i, me);
 | 
						|
		infds[i] = doopen(name, O_RDONLY, 0);
 | 
						|
		values[i] = 0;
 | 
						|
		ready[i] = 0;
 | 
						|
	}
 | 
						|
 | 
						|
	worknum = 0;
 | 
						|
 | 
						|
	while (1) {
 | 
						|
		numready = 0;
 | 
						|
		for (i=0; i<numprocs; i++) {
 | 
						|
			if (infds[i] < 0) {
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			if (!ready[i]) {
 | 
						|
				result = doread("bin", infds[i],
 | 
						|
						&val, sizeof(int));
 | 
						|
				if (result == 0) {
 | 
						|
					doclose("bin", infds[i]);
 | 
						|
					infds[i] = -1;
 | 
						|
					continue;
 | 
						|
				}
 | 
						|
				if ((size_t) result != sizeof(int)) {
 | 
						|
					complainx("%s: read: short count",
 | 
						|
						  binname(i, me));
 | 
						|
					exit(1);
 | 
						|
				}
 | 
						|
				values[i] = val;
 | 
						|
				ready[i] = 1;
 | 
						|
			}
 | 
						|
			numready++;
 | 
						|
		}
 | 
						|
		if (numready == 0) {
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		/* find the smallest */
 | 
						|
		place = -1;
 | 
						|
		for (i=0; i<numprocs; i++) {
 | 
						|
			if (!ready[i]) {
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
			if (place < 0 || values[i] < val) {
 | 
						|
				val = values[i];
 | 
						|
				place = i;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		assert(place >= 0);
 | 
						|
 | 
						|
		workspace[worknum++] = val;
 | 
						|
		if (worknum >= WORKNUM) {
 | 
						|
			assert(worknum == WORKNUM);
 | 
						|
			dowrite(outname, outfd, workspace,
 | 
						|
				worknum * sizeof(int));
 | 
						|
			worknum = 0;
 | 
						|
		}
 | 
						|
		ready[place] = 0;
 | 
						|
	}
 | 
						|
 | 
						|
	dowrite(outname, outfd, workspace, worknum * sizeof(int));
 | 
						|
	doclose(outname, outfd);
 | 
						|
 | 
						|
	for (i=0; i<numprocs; i++) {
 | 
						|
		assert(infds[i] < 0);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
assemble(void)
 | 
						|
{
 | 
						|
	off_t mypos;
 | 
						|
	int i, fd;
 | 
						|
	const char *args[3];
 | 
						|
 | 
						|
	mypos = 0;
 | 
						|
	for (i=0; i<me; i++) {
 | 
						|
		mypos += getsize(mergedname(i));
 | 
						|
	}
 | 
						|
 | 
						|
	fd = doopen(PATH_SORTED, O_WRONLY, 0);
 | 
						|
	dolseek(PATH_SORTED, fd, mypos, SEEK_SET);
 | 
						|
 | 
						|
	if (dup2(fd, STDOUT_FILENO) < 0) {
 | 
						|
		complain("dup2");
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
 | 
						|
	doclose(PATH_SORTED, fd);
 | 
						|
 | 
						|
	args[0] = "cat";
 | 
						|
	args[1] = mergedname(me);
 | 
						|
	args[2] = NULL;
 | 
						|
	execv("/bin/cat", (char **) args);
 | 
						|
	complain("/bin/cat: exec");
 | 
						|
	exit(1);
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
checksize_bins(void)
 | 
						|
{
 | 
						|
	off_t totsize;
 | 
						|
	int i, j;
 | 
						|
 | 
						|
	totsize = 0;
 | 
						|
	for (i=0; i<numprocs; i++) {
 | 
						|
		for (j=0; j<numprocs; j++) {
 | 
						|
			totsize += getsize(binname(i, j));
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (totsize != correctsize) {
 | 
						|
		complain("Sum of bin sizes is wrong (%ld, should be %ld)",
 | 
						|
			 (long) totsize, (long) correctsize);
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
checksize_merge(void)
 | 
						|
{
 | 
						|
	off_t totsize;
 | 
						|
	int i;
 | 
						|
 | 
						|
	totsize = 0;
 | 
						|
	for (i=0; i<numprocs; i++) {
 | 
						|
		totsize += getsize(mergedname(i));
 | 
						|
	}
 | 
						|
	if (totsize != correctsize) {
 | 
						|
		complain("Sum of merged sizes is wrong (%ld, should be %ld)",
 | 
						|
			 (long) totsize, (long) correctsize);
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
sort(void)
 | 
						|
{
 | 
						|
	unsigned long sortedsum;
 | 
						|
	int i, j;
 | 
						|
 | 
						|
	/* Step 1. Toss into bins. */
 | 
						|
	complainx("Tossing into %d bins using %d procs",
 | 
						|
		  numprocs*numprocs, numprocs);
 | 
						|
	doforkall("Tossing", bin);
 | 
						|
	checksize_bins();
 | 
						|
	complainx("Done tossing into bins.");
 | 
						|
 | 
						|
	/* Step 2: Sort the bins. */
 | 
						|
	complainx("Sorting %d bins using %d procs",
 | 
						|
		  numprocs*numprocs, numprocs);
 | 
						|
	doforkall("Sorting", sortbins);
 | 
						|
	checksize_bins();
 | 
						|
	complainx("Done sorting the bins.");
 | 
						|
 | 
						|
	/* Step 3: Merge corresponding bins. */
 | 
						|
	complainx("Merging %d bins using %d procs",
 | 
						|
		  numprocs*numprocs, numprocs);
 | 
						|
	doforkall("Merging", mergebins);
 | 
						|
	checksize_merge();
 | 
						|
	complainx("Done merging the bins.");
 | 
						|
 | 
						|
	/* Step 3a: delete the bins */
 | 
						|
	for (i=0; i<numprocs; i++) {
 | 
						|
		for (j=0; j<numprocs; j++) {
 | 
						|
			doremove(binname(i, j));
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/* Step 4: assemble output file */
 | 
						|
	complainx("Assembling output file using %d procs", numprocs);
 | 
						|
	docreate(PATH_SORTED);
 | 
						|
	doforkall("Final assembly", assemble);
 | 
						|
	if (getsize(PATH_SORTED) != correctsize) {
 | 
						|
		complainx("%s: file is wrong size", PATH_SORTED);
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
 | 
						|
	/* Step 4a: delete the merged bins */
 | 
						|
	for (i=0; i<numprocs; i++) {
 | 
						|
		doremove(mergedname(i));
 | 
						|
	}
 | 
						|
 | 
						|
	/* Step 5: Checksum the result. */
 | 
						|
	complainx("Checksumming the output (using one proc)");
 | 
						|
	sortedsum = checksum_file(PATH_SORTED);
 | 
						|
	complainx("Checksum of sorted keys: %ld", sortedsum);
 | 
						|
 | 
						|
	if (sortedsum != checksum) {
 | 
						|
		complainx("Sums do not match");
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
static
 | 
						|
const char *
 | 
						|
validname(int a)
 | 
						|
{
 | 
						|
	static char rv[32];
 | 
						|
	snprintf(rv, sizeof(rv), "valid-%d", a);
 | 
						|
	return rv;
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
checksize_valid(void)
 | 
						|
{
 | 
						|
	off_t totvsize, correctvsize;
 | 
						|
	int i;
 | 
						|
 | 
						|
	correctvsize = (off_t) numprocs*2*sizeof(int);
 | 
						|
 | 
						|
	totvsize = 0;
 | 
						|
	for (i=0; i<numprocs; i++) {
 | 
						|
		totvsize += getsize(validname(i));
 | 
						|
	}
 | 
						|
	if (totvsize != correctvsize) {
 | 
						|
		complainx("Sum of validation sizes is wrong "
 | 
						|
			  "(%ld, should be %ld)",
 | 
						|
			  (long) totvsize, (long) correctvsize);
 | 
						|
		exit(1);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
dovalidate(void)
 | 
						|
{
 | 
						|
	const char *name;
 | 
						|
	int fd, i, mykeys, keys_done, keys_to_do;
 | 
						|
	int key, smallest, largest;
 | 
						|
 | 
						|
	name = PATH_SORTED;
 | 
						|
	fd = doopen(name, O_RDONLY, 0);
 | 
						|
 | 
						|
	mykeys = getmykeys();
 | 
						|
	seekmyplace(name, fd);
 | 
						|
 | 
						|
	smallest = RANDOM_MAX;
 | 
						|
	largest = 0;
 | 
						|
 | 
						|
	keys_done = 0;
 | 
						|
	while (keys_done < mykeys) {
 | 
						|
		keys_to_do = mykeys - keys_done;
 | 
						|
		if (keys_to_do > WORKNUM) {
 | 
						|
			keys_to_do = WORKNUM;
 | 
						|
		}
 | 
						|
 | 
						|
		doexactread(name, fd, workspace, keys_to_do * sizeof(int));
 | 
						|
 | 
						|
		for (i=0; i<keys_to_do; i++) {
 | 
						|
			key = workspace[i];
 | 
						|
 | 
						|
			if (key < 0) {
 | 
						|
				complain("%s: found negative key", name);
 | 
						|
				exit(1);
 | 
						|
			}
 | 
						|
			if (key == 0) {
 | 
						|
				complain("%s: found zero key", name);
 | 
						|
				exit(1);
 | 
						|
			}
 | 
						|
			if (key >= RANDOM_MAX) {
 | 
						|
				complain("%s: found too-large key", name);
 | 
						|
				exit(1);
 | 
						|
			}
 | 
						|
 | 
						|
			if (key < smallest) {
 | 
						|
				smallest = key;
 | 
						|
			}
 | 
						|
			if (key > largest) {
 | 
						|
				largest = key;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		keys_done += keys_to_do;
 | 
						|
	}
 | 
						|
	doclose(name, fd);
 | 
						|
 | 
						|
	name = validname(me);
 | 
						|
	fd = doopen(name, O_WRONLY|O_CREAT|O_TRUNC, 0664);
 | 
						|
	dowrite(name, fd, &smallest, sizeof(smallest));
 | 
						|
	dowrite(name, fd, &largest, sizeof(largest));
 | 
						|
	doclose(name, fd);
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
validate(void)
 | 
						|
{
 | 
						|
	int smallest, largest, prev_largest;
 | 
						|
	int i, fd;
 | 
						|
	const char *name;
 | 
						|
 | 
						|
	complainx("Validating the sorted data using %d procs", numprocs);
 | 
						|
	doforkall("Validation", dovalidate);
 | 
						|
	checksize_valid();
 | 
						|
 | 
						|
	prev_largest = 1;
 | 
						|
 | 
						|
	for (i=0; i<numprocs; i++) {
 | 
						|
		name = validname(i);
 | 
						|
		fd = doopen(name, O_RDONLY, 0);
 | 
						|
 | 
						|
		doexactread(name, fd, &smallest, sizeof(int));
 | 
						|
		doexactread(name, fd, &largest, sizeof(int));
 | 
						|
 | 
						|
		if (smallest < 1) {
 | 
						|
			complainx("Validation: block %d: bad SMALLEST", i);
 | 
						|
			exit(1);
 | 
						|
		}
 | 
						|
		if (largest >= RANDOM_MAX) {
 | 
						|
			complainx("Validation: block %d: bad LARGEST", i);
 | 
						|
			exit(1);
 | 
						|
		}
 | 
						|
		if (smallest > largest) {
 | 
						|
			complainx("Validation: block %d: SMALLEST > LARGEST",
 | 
						|
				  i);
 | 
						|
			exit(1);
 | 
						|
		}
 | 
						|
 | 
						|
		if (smallest < prev_largest) {
 | 
						|
			complain("Validation: block %d smallest key %d",
 | 
						|
				 i, smallest);
 | 
						|
			complain("Validation: previous block largest key %d",
 | 
						|
				 prev_largest);
 | 
						|
			complain("Validation failed");
 | 
						|
			exit(1);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	for (i=0; i<numprocs; i++) {
 | 
						|
		doremove(validname(i));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
setdir(void)
 | 
						|
{
 | 
						|
#if 0 /* let's not require subdirs */
 | 
						|
	domkdir(PATH_TESTDIR, 0775);
 | 
						|
	dochdir(PATH_TESTDIR);
 | 
						|
#endif /* 0 */
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
unsetdir(void)
 | 
						|
{
 | 
						|
	doremove(PATH_KEYS);
 | 
						|
	doremove(PATH_SORTED);
 | 
						|
#if 0 /* let's not require subdirs */
 | 
						|
	dochdir("..");
 | 
						|
 | 
						|
	if (rmdir(PATH_TESTDIR) < 0) {
 | 
						|
		complain("%s: rmdir", PATH_TESTDIR);
 | 
						|
		/* but don't exit */
 | 
						|
	}
 | 
						|
#endif /* 0 */
 | 
						|
}
 | 
						|
 | 
						|
////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
randomize(void)
 | 
						|
{
 | 
						|
	int fd;
 | 
						|
 | 
						|
	fd = doopen(PATH_RANDOM, O_RDONLY, 0);
 | 
						|
	doexactread(PATH_RANDOM, fd, &randomseed, sizeof(randomseed));
 | 
						|
	doclose(PATH_RANDOM, fd);
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
usage(void)
 | 
						|
{
 | 
						|
	complain("Usage: %s [-p procs] [-k keys] [-s seed] [-r]", progname);
 | 
						|
	exit(1);
 | 
						|
}
 | 
						|
 | 
						|
static
 | 
						|
void
 | 
						|
doargs(int argc, char *argv[])
 | 
						|
{
 | 
						|
	int i, ch, val, arg;
 | 
						|
 | 
						|
	for (i=1; i<argc; i++) {
 | 
						|
		if (argv[i][0] != '-') {
 | 
						|
			usage();
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		ch = argv[i][1];
 | 
						|
		switch (ch) {
 | 
						|
		    case 'p': arg = 1; break;
 | 
						|
		    case 'k': arg = 1; break;
 | 
						|
		    case 's': arg = 1; break;
 | 
						|
		    case 'r': arg = 0; break;
 | 
						|
		    default: usage(); return;
 | 
						|
		}
 | 
						|
		if (arg) {
 | 
						|
			if (argv[i][2]) {
 | 
						|
				val = atoi(argv[i]+2);
 | 
						|
			}
 | 
						|
			else {
 | 
						|
				i++;
 | 
						|
				if (!argv[i]) {
 | 
						|
					complain("Option -%c requires an "
 | 
						|
						 "argument", ch);
 | 
						|
					exit(1);
 | 
						|
				}
 | 
						|
				val = atoi(argv[i]);
 | 
						|
			}
 | 
						|
			switch (ch) {
 | 
						|
			    case 'p': numprocs = val; break;
 | 
						|
			    case 'k': numkeys = val; break;
 | 
						|
			    case 's': randomseed = val; break;
 | 
						|
			    default: assert(0); break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		else {
 | 
						|
			switch (ch) {
 | 
						|
			    case 'r': randomize(); break;
 | 
						|
			    default: assert(0); break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
main(int argc, char *argv[])
 | 
						|
{
 | 
						|
	initprogname(argc > 0 ? argv[0] : NULL);
 | 
						|
 | 
						|
	doargs(argc, argv);
 | 
						|
	correctsize = (off_t) (numkeys*sizeof(int));
 | 
						|
 | 
						|
	setdir();
 | 
						|
 | 
						|
	genkeys();
 | 
						|
	sort();
 | 
						|
	validate();
 | 
						|
	complainx("Succeeded.");
 | 
						|
 | 
						|
	unsetdir();
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 |