#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/vfs.h>
#include <sys/resource.h>

#define ARCH64 ((sizeof(long)) > 4)
#define ARCH32 ((sizeof(long)) == 4)

char *testfile = "testlen.dat";
int testfd;
volatile int sigxfsz;

void try(const char *what, int err)
{
	if (!err)
		return;
	fprintf (stderr, "Unexpected result %d.  %s: %s\n",
		 err, what, strerror(errno));
	exit(1);
}

void compare(const char *what, int error, int result)
{
	if (!error && !result)
		return;
	if (error && result && error == errno)
		return;
	
	if (result && !error)
		fprintf (stderr, "%s: unexpected failure, %s\n", what,
			 strerror(errno));
	else if (result)
		fprintf (stderr, "%s: wrong failure, %s (expected %s)\n", what,
			 strerror(errno), strerror(error));
	else
		fprintf (stderr, "%s: unexpected success\n", what);
	exit(1);
}

void check_signal(const char *what, int expected)
{
	if (expected == sigxfsz)
		return;
	if (expected)
		fprintf (stderr, "%s: expected signal missing,\n", what);
	else
		fprintf (stderr, "%s: unexpected signal\n", what);
	exit(1);
}

void handle_sigxfsz(int n)
{
	sigxfsz = 1;
	signal (SIGXFSZ, handle_sigxfsz);
}

void create_file(void)
{
	testfd = open(testfile, O_CREAT|O_TRUNC|O_RDWR, 0666);
	try ("open(O_CREAT)", testfd < 0);
}

void set_length(long length, int error, int expect_signal)
{
	sigxfsz = 0;
	compare ("ftruncate", error, ftruncate(testfd, length) != 0);
	check_signal ("ftruncate", expect_signal);
}

void test_seek(long where, long expected, int error)
{
	errno = 0;
	try ("lseek", expected != lseek(testfd, where, SEEK_SET));
	compare ("lseek", errno, error);
}

void write_file(long where, long how_much, 
		long expected, int error, int expect_signal)
{
	char *buffer = alloca(how_much);
	
	errno = 0;
	sigxfsz = 0;
	try ("lseek", where != lseek(testfd, where, SEEK_SET));
	try ("write", expected != write(testfd, buffer, how_much));
	compare ("write", errno, error);
	assert (expect_signal == sigxfsz);
}


/*
 * Set the max-file-length resource limit.
 */

void set_maxfile (int max)
{
	struct rlimit rlimit;
	
	if (max)
		rlimit.rlim_cur = max;
	else
		rlimit.rlim_cur = RLIM_INFINITY;
	rlimit.rlim_max = RLIM_INFINITY;
	try ("setrlimit", setrlimit(RLIMIT_FSIZE, &rlimit));
}

int main()
{
	long max_filesize;
	long soft_limit = 1000000;
	
	signal (SIGXFSZ, handle_sigxfsz);

	/* First set of tests.  Check out the handling of the filesystem
	 * offset maximum. */

	if (ARCH32) 
		max_filesize = 0x7FFFFFFFL;
	else {
		long ind_entries;
		struct statfs statbuf;
		try ("statfs", statfs (".", &statbuf));
		
		/* We know about ext2 internals here... */
		ind_entries = statbuf.f_bsize / 4;

		max_filesize = statbuf.f_bsize * 
			(12L +
			 ind_entries +
			 ind_entries * ind_entries +
			 ind_entries * ind_entries * ind_entries) - 1;
	}

	printf ("Using a maximum file size of %ld %ld\n", max_filesize);


	/* Creating a file and extending to 0 should be a noop. */

	create_file();
	set_length(0, 0, 0);
	printf ("truncate to 0: PASSED\n");
	

	/* Creating a file and extending to maxlen should be fine. */

	create_file();
	set_length(max_filesize, 0, 0);
	printf ("truncate to %ld: PASSED\n", max_filesize);

	
	/* Creating a file 1 beyond maxlen should fail with EINVAL (-ve)
	 * on ARCH32, and EFBIG (it's still +ve) on ARCH64. */

	create_file();
	if (ARCH32)
		set_length(max_filesize+1, EINVAL, 0);
	else
		set_length(max_filesize+1, EFBIG, 0);
	printf ("truncate to %ld: PASSED\n", max_filesize + 1);


	/* Creating a file with -ve size should fail with EINVAL. */

	create_file();
	set_length(-1, EINVAL, 0);
	printf ("truncate to -1: PASSED\n");


	/*
	 * Now test out writes near the limit boundaries. 
	 */


	/* Just test that simple writes work.  */

	create_file();
	set_length(1000, 0, 0);
	write_file(1000, 1000, 1000, 0, 0);
	printf ("simple write: PASSED\n");


	/* Test writes just below maxlen */

	create_file();
	set_length(max_filesize - 1000, 0, 0);
	write_file(max_filesize - 1000, 1000, 1000, 0, 0);
	printf ("write (max_filesize-1000, 1000): PASSED\n");


	/* Test overwrites just below maxlen */

	create_file();
	set_length(max_filesize, 0, 0);
	write_file(max_filesize - 1000, 1000, 1000, 0, 0);
	printf ("overwrite (max_filesize-1000, 1000): PASSED\n");


	/* Test writes passing maxlen */

	create_file();
	set_length(max_filesize - 1000, 0, 0);
	write_file(max_filesize - 1000, 1001, 1000, 0, 0);
	write_file(max_filesize, 1, -1, EFBIG, 0);
	printf ("write (max_filesize-1000, 1001): PASSED\n");


	/* Test writes at maxlen */

	create_file();
	write_file(max_filesize, 1000, -1, EFBIG, 0);
	printf ("write (max_filesize, 1000): PASSED\n");


	/* Test writes beyond maxlen */

	create_file();
	test_seek(max_filesize+1000, -1, EINVAL);
	printf ("seek (max_filesize+1000): PASSED\n");


	/*
	 * Set up a filesize resource limit and repeat the tests.
	 * Truncate first:
	 */

	set_maxfile(soft_limit);
	printf ("Established soft file size limit.\n");
	

	/* Creating a file and extending to 0 should be a noop. */

	create_file();
	set_length(0, 0, 0);
	printf ("truncate to 0: PASSED\n");
	

	/* Creating a file and extending to the soft limit should be
           fine. */

	create_file();
	set_length(soft_limit, 0, 0);
	printf ("truncate to soft_limit: PASSED\n");

	
	/* Creating a file 1 beyond the soft limit should fail with
	 * EFBIG and SIGXFSZ. */

	create_file();
	set_length(soft_limit+1, EFBIG, 1);
	printf ("truncate to soft_limit+1: PASSED\n");


	/* Creating a file with -ve size should fail with EINVAL. */

	create_file();
	set_length(-1, EINVAL, 0);
	printf ("truncate to -1: PASSED\n");


	/*
	 * Now the write tests again.
	 */


	/* Just test that simple writes work.  */

	create_file();
	set_length(1000, 0, 0);
	write_file(1000, 1000, 1000, 0, 0);
	printf ("simple write: PASSED\n");


	/* Test writes just below soft_limit */

	create_file();
	set_length(soft_limit - 1000, 0, 0);
	write_file(soft_limit - 1000, 1000, 1000, 0, 0);
	printf ("write (soft_limit-1000, 1000): PASSED\n");


	/* Test overwrites just below soft_limit */

	create_file();
	set_length(soft_limit, 0, 0);
	write_file(soft_limit - 1000, 1000, 1000, 0, 0);
	printf ("overwrite (soft_limit-1000, 1000): PASSED\n");


	/* Test writes passing soft_limit */

	create_file();
	set_length(soft_limit - 1000, 0, 0);
	write_file(soft_limit - 1000, 1001, 1000, 0, 0);
	write_file(soft_limit, 1, -1, EFBIG, 1);
	printf ("write (soft_limit-1000, 1001): PASSED\n");


	/* Test writes at soft_limit */

	create_file();
	write_file(soft_limit, 1000, -1, EFBIG, 1);
	printf ("write (soft_limit, 1000): PASSED\n");


	/* Test writes beyond soft_limit */

	create_file();
	test_seek(soft_limit+1000, soft_limit+1000, 0);
	write_file(soft_limit+1000, 1000, -1, EFBIG, 1);
	printf ("seek (soft_limit+1000): PASSED\n");


	return 0;
}
