#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;
}