/*
 * Copyright (c) 2004, 2005 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_IDSTR(id, "@(#)$Id: t-readwrite.c,v 1.6 2006/07/16 02:07:39 ca Exp $")

#include "sm/assert.h"
#include "sm/fcntl.h"
#include "sm/io.h"
#include "sm/string.h"
#include "sm/wait.h"
#include "sm/resource.h"
#include "sm/test.h"

/*
**  Write and read a file.
**  This is just a very simple test to see whether an OS caches the file
**  data after it has been written.
**  This can be used for two test cases:
**  write a file and read it back in the same process: this shouldn't cause
**  any disk reads;
**  write a file and read it back in a different process: this shouldn't cause
**  any disk reads either.
**  See also rdwr.sh
**
**  Optional this program can write several files from several (sub)processes.
**  This is useful to figure out whether it is more efficient to write files
**  from just a single process or from multiple processes. In the latter case
**  the OS (FS) might be able to "group" operations such that they are more
**  efficient when performing disk I/O.
*/

#define MAXFN	128
#define MAX_BUFSZ	(8 * 1024)
uchar buf[MAX_BUFSZ];

/*
**  READFILE -- read data from file 
**
**	Parameters:
**		fn -- file name context
**
**	Returns:
**		usual sm_error code
*/

static int
readfile(char *fn)
{
	sm_ret_T res;
	sm_file_T *fp;
	size_t s;
	ssize_t rd;

	SM_REQUIRE(fn != NULL);
	res = sm_io_open(SmStStdio, fn, SM_IO_RDONLY, &fp, SM_IO_WHAT_END);
	SM_TEST(res == SM_SUCCESS);
	SM_TEST(fp != NULL);
	if (fp != NULL)
	{
		s = sizeof(buf);
		do
		{
			res = sm_io_read(fp, buf, s, &rd);
			if (res == SM_SUCCESS)
			{
				SM_TEST(res == SM_SUCCESS);
				SM_TEST(rd >= 0);
			}
			else
			{
				SM_TEST(res == SM_IO_EOF);
				SM_TEST(rd == 0);
			}
		} while (rd > 0);
		res = sm_io_close(fp, SM_IO_CF_NONE);
		SM_TEST(res == SM_SUCCESS);
	}
	return res;
}

/*
**  WRITEFILE -- write data to file 
**
**	Parameters:
**		fn -- file name context
**		sync -- call fsync()?
**
**	Returns:
**		usual sm_error code
*/

static int
writefile(char *fn, bool sync)
{
	sm_ret_T res;
	ssize_t n;
	size_t s, i;
	int r;
	sm_file_T *fp;

	SM_REQUIRE(fn != NULL);
	res = sm_io_open(SmStStdio, fn, SM_IO_WRONLY, &fp, SM_IO_WHAT_END);
	SM_TEST(res == SM_SUCCESS);
	SM_TEST(fp != NULL);
	if (fp != NULL)
	{
		s = sizeof(buf);
		for (i = 0; i < s; i++)
			buf[i] = 'a' + (i & 0x1f);
		res = sm_io_write(fp, buf, s, &n);
		SM_TEST(res == SM_SUCCESS);
		SM_TEST(n == (ssize_t) s);
		if (sync)
		{
			r = fsync(f_fd(*fp));
			SM_TEST(r == 0);
		}
		res = sm_io_close(fp, SM_IO_CF_NONE);
		SM_TEST(res == SM_SUCCESS);
	}
	return res;
}

/*
**  WRITEMANY -- write many files
**
**	Parameters:
**		base -- start of filename
**		n -- number of files to write
**		sync -- call fsync()?
**
**	Returns:
**		usual sm_error code
*/

static int
writemany(char *base, int n, int p, bool sync)
{
	int i, ret;
	char fn[MAXFN];

	ret = SM_SUCCESS;
	for (i = 0; i < n; i++)
	{
		sm_snprintf(fn, sizeof(fn), "%s-%04d-%04d", base, i, p);
		ret = writefile(fn, sync);
		if (ret != SM_SUCCESS)
			break;
	}
	return ret;
}

/*
**  STARTPROC -- start several processes
**
**	Parameters:
**		base -- start of filename
**		nproc -- number of processes to write
**		nfiles -- number of files to write
**		sync -- call fsync()?
**
**	Returns:
**		usual sm_error code
*/

static int
startproc(char *base, int nproc, int nfiles, bool sync)
{
	int i, ret;
	pid_t pid, *pids;
	struct rusage rusage;

	ret = SM_SUCCESS;
	pids = malloc(sizeof(int) * nproc);
	SM_TEST(pids != NULL);
	if (pids == NULL)
		return ENOMEM;
	for (i = 0; i < nproc; i++)
	{
		pid = fork();
		if (pid < 0)
		{
			ret = errno;
			break;
		}
		else if (pid == 0)
		{
			return writemany(base, nfiles, i, sync);
		}
		else if (pid > 0)
		{
			pids[i] = pid;
		}
	}
	for (i = 0; i < nproc; i++)
	{
		pid = wait(&ret);
		if (pid < 0)
		{
			ret = errno;
			break;
		}
	}
	if (getrusage(RUSAGE_CHILDREN, &rusage) == 0)
	{
		sm_io_fprintf(smioerr,
			"ru_utime=   %7ld.%07ld\n"
			"ru_stime=   %7ld.%07ld\n"
			"ru_maxrss=  %7ld\n"
			"ru_ixrss=   %7ld\n"
			"ru_idrss=   %7ld\n"
			"ru_isrss=   %7ld\n"
			"ru_minflt=  %7ld\n"
			"ru_majflt=  %7ld\n"
			"ru_nswap=   %7ld\n"
			"ru_inblock= %7ld\n"
			"ru_oublock= %7ld\n"
			"ru_msgsnd=  %7ld\n"
			"ru_msgrcv=  %7ld\n"
			"ru_nsignals=%7ld\n"
			"ru_nvcsw=   %7ld\n"
			"ru_nivcsw=  %7ld\n"
			, rusage.ru_utime.tv_sec
			, rusage.ru_utime.tv_usec
			, rusage.ru_stime.tv_sec
			, rusage.ru_stime.tv_usec
			, rusage.ru_maxrss
			, rusage.ru_ixrss
			, rusage.ru_idrss
			, rusage.ru_isrss
			, rusage.ru_minflt
			, rusage.ru_majflt
			, rusage.ru_nswap
			, rusage.ru_inblock
			, rusage.ru_oublock
			, rusage.ru_msgsnd
			, rusage.ru_msgrcv
			, rusage.ru_nsignals
			, rusage.ru_nvcsw
			, rusage.ru_nivcsw
			);
	}

	return ret;
}

static void
usage(void)
{
	sm_io_fprintf(smioerr,
		"usage: t-readwrite [options] [filename]\n"
		"-b	write and read file\n"
		"-f n	number of files to write\n"
		"-p n	number of processes to use\n"
		"-s	call fsync(2) after write\n"
		"-w	write only\n"
		);
}

int
main(int argc, char *argv[])
{
	int c;
	char *fn;
	bool wr, rd, sync;
	int nproc, nfiles;

	wr = false;
	rd = true;
	sync = false;
	fn = "file";
	nfiles = nproc = 0;
	while ((c = getopt(argc, argv, "bf:p:sw")) != -1)
	{
		switch (c)
		{
		  case 'b':
			wr = true;
			break;
		  case 'f':
			nfiles = atoi(optarg);
			break;
		  case 'p':
			nproc = atoi(optarg);
			break;
		  case 's':
			sync = true;
			break;
		  case 'w':
			wr = true;
			rd = false;
			break;
		  default:
			usage();
			exit(1);
		}
	}
	sm_test_begin(argc, argv, "test readwrite");
	argc -= optind;
	argv += optind;
	if (argc > 0)
		fn = argv[0];
	if (nfiles > 0 && nproc > 0)
	{
		startproc(fn, nproc, nfiles, sync);
	}
	else
	{
		if (wr)
			writefile(fn, sync);
		if (rd)
			readfile(fn);
	}
	return sm_test_end();
}


syntax highlighted by Code2HTML, v. 0.9.1