/* stdio1.c (emx+gcc) */

#undef NDEBUG
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

static void usage (void)
{
  fputs ("Usage: stdio1 -t <output_file>\n", stderr);
  fputs ("       stdio1 -s <output_file>\n", stderr);
  fputs ("       stdio1 -s <output_file> <iterations>\n", stderr);
  fputs ("       stdio1 -se <output_file>\n", stderr);
  fputs ("       stdio1 -ud\n", stderr);
  fputs ("       stdio1 -uf\n", stderr);
  fputs ("       stdio1 -us\n", stderr);
  fputs ("       stdio1 -i\n", stderr);
  exit (1);
}


/* Test ftell(). */

static void test_t (const char *fname)
{
  FILE *f;
  int i, j;

  f = fopen (fname, "wb");
  if (f == NULL)
    {
      perror ("fopen");
      exit (1);
    }
  for (i = 0; i < 32768; ++i)
    {
      assert (fwrite (&i, sizeof (i), 1, f) == 1);
      if (i % 4111 == 0)
        assert (fflush (f) == 0);
      assert (ftell (f) == (i+1) * sizeof (i));
    }
  if (fclose (f) != 0)
    {
      perror ("fclose");
      exit (1);
    }
  f = fopen (fname, "rb");
  assert (f != NULL);
  for (i = 0; i < 32768; ++i)
    {
      assert (fread (&j, sizeof (j), 1, f) == 1);
      assert (i == j);
      if (i % 4111 == 0)
        assert (fflush (f) == 0);
      assert (ftell (f) == (i+1) * sizeof (i));
    }
  assert (fclose (f) == 0);
}


/* Test fseek() after reading beyond EOF. */

static void test_s1 (const char *fname)
{
  FILE *f;
  size_t i;
  static char pattern[10993];
  static char buf[8192];

  assert (BUFSIZ == 5120);
  f = fopen (fname, "wb");
  if (f == NULL)
    {
      perror ("fopen");
      exit (1);
    }
  for (i = 0; i < sizeof (pattern); ++i)
    pattern[i] = (char)rand ();
  assert (fwrite (pattern, 1, sizeof (pattern), f) == sizeof (pattern));
  if (fclose (f) != 0)
    {
      perror ("fclose");
      exit (1);
    }

  f = fopen (fname, "rb");
  assert (f != NULL);
  assert (fread (buf, 1, sizeof (buf), f) == sizeof (buf));
  assert (memcmp (buf, pattern, sizeof (buf)) == 0);
  assert (ftell (f) == sizeof (buf));
  assert (fread (buf, 1, sizeof (buf), f) == sizeof (pattern) - sizeof (buf));
  assert (memcmp (buf, pattern+sizeof (buf),
                  sizeof (pattern) - sizeof (buf)) == 0);
  assert (ftell (f) == sizeof (pattern));
  assert (fseek (f, 7461, SEEK_SET) == 0);
  assert (ftell (f) == 7461);
  assert (fread (buf, 1, 1024, f) == 1024);
  assert (memcmp (buf, pattern+7461, 1024) == 0);
  assert (ftell (f) == 7461 + 1024);
  assert (fclose (f) == 0);
}

static void test_s2 (const char *fname, long iterations)
{
  FILE *f;
  long n, pos, count, exp;
  size_t i;
  static char pattern[2*BUFSIZ+17];
  static char buf[sizeof (pattern)];

  assert (2*BUFSIZ+17 <= RAND_MAX);
  if (iterations < 1)
    usage ();
  f = fopen (fname, "wb");
  if (f == NULL)
    {
      perror ("fopen");
      exit (1);
    }
  for (i = 0; i < sizeof (pattern); ++i)
    pattern[i] = (char)rand ();
  assert (fwrite (pattern, 1, sizeof (pattern), f) == sizeof (pattern));
  if (fclose (f) != 0)
    {
      perror ("fclose");
      exit (1);
    }

  f = fopen (fname, "rb");
  assert (f != NULL);
  for (n = 0; n < iterations; ++n)
    {
      pos = rand () % sizeof (pattern);
      count = rand () % sizeof (pattern);
      assert (fseek (f, pos, SEEK_SET) == 0);
      exp = count;
      if (pos + exp > sizeof (pattern))
        exp = sizeof (pattern) - pos;
      assert (fread (buf, 1, count, f) == exp);
      assert (memcmp (buf, pattern + pos, (size_t)exp) == 0);
      assert (ftell (f) == pos + exp);
    }
  assert (fclose (f) == 0);
}

/* Test for a sign extension bug (actually in read(), not in stdio) */

static void test_se (const char *fname)
{
  FILE *f;
  size_t i;
  int c;
  static char pattern[] = "ab\r\377cd";

  f = fopen (fname, "w");
  if (f == NULL)
    {
      perror ("fopen");
      exit (1);
    }
  assert (fwrite (pattern, 1, sizeof (pattern), f) == sizeof (pattern));
  if (fclose (f) != 0)
    {
      perror ("fclose");
      exit (1);
    }

  f = fopen (fname, "r");       /* Text mode! */
  assert (f != NULL);
  assert (setvbuf (f, NULL, _IONBF, 0) == 0);
  for (i = 0; i < sizeof (pattern); ++i)
    {
      c = fgetc (f);
      assert (c == (unsigned char)pattern[i]);
    }
  assert (fclose (f) == 0);
}

/* Test switching between `read mode' and `write mode' of a stream
   open for update (seekable file). */

static void test_uf (void)
{
  FILE *f;
  char buf[10];

  f = tmpfile ();
  if (f == NULL)
    {
      perror ("tmpfile()");
      exit (2);
    }
  assert (fwrite ("abc", 1, 3, f) == 3);
  rewind (f);
  assert (fread (buf, 1, 10, f) == 3);
  assert (memcmp (buf, "abc", 3) == 0);
  assert (fseek (f, 0L, SEEK_SET) == 0);
  assert (fputc ('A', f) == 'A');
  assert (fseek (f, 0L, SEEK_SET) == 0);
  assert (fread (buf, 1, 2, f) == 2);
  assert (memcmp (buf, "Ab", 2) == 0);
  assert (fseek (f, 0L, SEEK_CUR) == 0);
  assert (fputc ('C', f) == 'C');
  assert (fseek (f, 0L, SEEK_SET) == 0);
  assert (fread (buf, 1, 10, f) == 3);
  assert (memcmp (buf, "AbC", 3) == 0);
  /* At EOF, we don't have to switch explicitely to write mode. */
  assert (fputc ('D', f) == 'D');
  assert (fseek (f, 0L, SEEK_SET) == 0);
  assert (fread (buf, 1, 10, f) == 4);
  assert (memcmp (buf, "AbCD", 4) == 0);

  assert (fseek (f, 1L, SEEK_SET) == 0);
  assert (fputc ('B', f) == 'B');
  assert (fflush (f) == 0);
  assert (fgetc (f) == 'C');
  assert (fseek (f, 0L, SEEK_SET) == 0);
  assert (fread (buf, 1, 10, f) == 4);
  assert (memcmp (buf, "ABCD", 4) == 0);

  assert (fseek (f, 3L, SEEK_SET) == 0);
  assert (fgetc (f) == 'D');
  assert (fgetc (f) == EOF);
  assert (fputc ('E', f) == 'E');
  assert (fseek (f, 0L, SEEK_SET) == 0);
  assert (fread (buf, 1, 10, f) == 5);
  assert (memcmp (buf, "ABCDE", 5) == 0);

  assert (fclose (f) == 0);
}


/* Test switching between `read mode' and `write mode' of a stream
   open for update (non-seekable device). */

static void test_ud (void)
{
  FILE *f;
  char buf[10];

  f = fopen ("con", "r+");
  if (f == NULL)
    {
      perror ("con");
      exit (2);
    }
  assert (setvbuf (f, NULL, _IOFBF, 512) == 0);
  assert (fputs ("Type \"abc\": ", f) != EOF);
  assert (fflush (f) == 0);     /* Buffer non-empty */
  assert (fflush (f) == 0);     /* Buffer empty */
  assert (fgets (buf, sizeof (buf), f) == buf);
  assert (strcmp (buf, "abc\n") == 0);
  assert (fseek (f, 0L, SEEK_CUR) == 0); /* Buffer non-empty */
  assert (fseek (f, 0L, SEEK_CUR) == 0); /* Buffer empty */
  assert (fputs ("OK!\n", f) != EOF);
  assert (fclose (f) == 0);
}


/* Test whether fscanf() pushes back or makes characters unread. */

static void test_us_1 (FILE *f)
{
  int c, i;

  /* This works even if fscanf() uses ungetc(). */

  assert (fscanf (f, "%d", &i) == 1);
  assert (i == 1);
  c = fgetc (f);
  assert (c == 'x');

  /* This fails if fscanf() uses ungetc(). */

  assert (fscanf (f, "%d", &i) == 1);
  assert (i == 2);
  assert (fseek (f, 0L, SEEK_CUR) == 0);
  c = fgetc (f);
  assert (c == 'x');

  /* Check if the normal case works. */

  assert (ungetc ('y', f) == 'y');
  assert (fscanf (f, "%d", &i) == 0);
  c = fgetc (f);
  assert (c == 'y');

  /* Check if the hard case works. */

  assert (ungetc ('z', f) == 'z');
  assert (fscanf (f, "%d", &i) == 0);
  assert (fseek (f, 0L, SEEK_CUR) == 0);
  /* Note: ISO 9899 does not unambiguously specify whether we'll get
     'x' or '3' next -- fseek() after ungetc(). */
  c = fgetc (f);
  assert (c == 'x');
  c = fgetc (f);
  assert (c == '3');
}


static void test_us (void)
{
  char *fname = "stdio1.tst";
  FILE *f;

  f = fopen (fname, "wb");
  if (f == NULL)
    {
      perror (fname);
      exit (2);
    }
  assert (fputs ("1x2x3", f) != EOF);
  assert (fclose (f) == 0);

  f = fopen (fname, "rb");
  if (f == NULL)
    {
      perror (fname);
      exit (2);
    }
  test_us_1 (f);
  assert (fclose (f) == 0);

  f = fopen (fname, "rb");
  if (f == NULL)
    {
      perror (fname);
      exit (2);
    }
  assert (setvbuf (f, NULL, _IONBF, 0) == 0);
  test_us_1 (f);
  assert (fclose (f) == 0);

  f = fopen (fname, "rb");
  if (f == NULL)
    {
      perror (fname);
      exit (2);
    }
  assert (setvbuf (f, NULL, _IOFBF, 1) == 0);
  test_us_1 (f);
  assert (fclose (f) == 0);
  assert (remove (fname) == 0);

#if 0
  assert (setvbuf (stdin, NULL, _IONBF, 0) == 0);
  assert (_fsetmode (stdin, "b") == 0);
  fputs ("Please type \"1x2x3\": ", stdout);
  fflush (stdout);
  test_us_1 (stdin);
#endif
}


static void handler (int signo)
{
  static char msg[] = "[Interrupted]\n";

  /* Note: do not use stdio -- we must not call stdio functions on
     stdout when interrupting code which calls stdio functions on
     stdout, see putchar() and fputs() below! */

  write (1, msg, sizeof (msg) - 1);
}


/* EINTR should be ignored. */

static void test_i (void)
{
  char buf[100], *p;

  signal (SIGINT, handler);
  puts ("Type text intermixed with Ctrl-C to check if EINTR is ignored.\n"
        "Type a line containing only Ctrl-Z to exit.");
  for (;;)
    {
      fputs ("Your input: ", stdout); fflush (stdout);
      if (fgets (buf, sizeof (buf), stdin) == NULL)
        break;
      fputs ("Read \"", stdout);
      for (p = buf; *p != 0; ++p)
        if (*p == '\n')
          fputs ("\\n", stdout);
        else
          putchar (*p);
      fputs ("\"\n", stdout);
    }
}


int main (int argc, char *argv[])
{

  if (argc == 3 && strcmp (argv[1], "-t") == 0)
    test_t (argv[2]);
  else if (argc == 3 && strcmp (argv[1], "-s") == 0)
    test_s1 (argv[2]);
  else if (argc == 4 && strcmp (argv[1], "-s") == 0)
    test_s2 (argv[2], atol (argv[3]));
  else if (argc == 3 && strcmp (argv[1], "-se") == 0)
    test_se (argv[2]);
  else if (argc == 2 && strcmp (argv[1], "-ud") == 0)
    test_ud ();
  else if (argc == 2 && strcmp (argv[1], "-uf") == 0)
    test_uf ();
  else if (argc == 2 && strcmp (argv[1], "-us") == 0)
    test_us ();
  else if (argc == 2 && strcmp (argv[1], "-i") == 0)
    test_i ();
  else
    usage ();
  return 0;
}
