/* $NetBSD: t_fmemopen.c,v 1.7 2021/09/11 18:18:28 rillig Exp $ */

/*-
 * Copyright (c)2010 Takehiko NOZAKI,
 * All rights reserved.
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
 *
 */

#if defined(__NetBSD__)
#include <atf-c.h>
#else
#define _POSIX_C_SOURCE 200809L /* for strnlen */
#include <assert.h>
#define ATF_TC(arg0)		static void arg0##_head(void)
#define ATF_TC_HEAD(arg0, arg1)	static void arg0##_head(void)
#define atf_tc_set_md_var(arg0, arg1, ...) do {	\
	printf(__VA_ARGS__);			\
	puts("");				\
} while (/*CONSTCOND*/0)
#define ATF_TC_BODY(arg0, arg1)	static void arg0##_body(void)
#define ATF_CHECK(arg0)		assert(arg0)
#define ATF_CHECK_MSG(arg0, fmt, ...) do { \
	if (!(arg0)) { \
		fprintf(stderr, fmt, __VA_ARGS__); \
		exit(1); \
	} \
} while (/*CONSTCOND*/0)
#define ATF_TP_ADD_TCS(arg0)	int main(void)
#define ATF_TP_ADD_TC(arg0, arg1) arg1##_head(); arg1##_body()
#define atf_no_error()		0
#endif

#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>

const char *mode_rwa[] = {
    "r", "rb", "r+", "rb+", "r+b",
    "w", "wb", "w+", "wb+", "w+b",
    "a", "ab", "a+", "ab+", "a+b",
    NULL
};

const char *mode_r[] = { "r", "rb", "r+", "rb+", "r+b", NULL };
const char *mode_w[] = { "w", "wb", "w+", "wb+", "w+b", NULL };
const char *mode_a[] = { "a", "ab", "a+", "ab+", "a+b", NULL };

struct testcase {
	const char *s;
	off_t n;
} testcases[] = {
#define TESTSTR(s)	{ s, sizeof(s)-1 }
	TESTSTR("\0he quick brown fox jumps over the lazy dog"),
	TESTSTR("T\0e quick brown fox jumps over the lazy dog"),
	TESTSTR("Th\0 quick brown fox jumps over the lazy dog"),
	TESTSTR("The\0quick brown fox jumps over the lazy dog"),
	TESTSTR("The \0uick brown fox jumps over the lazy dog"),
	TESTSTR("The q\0ick brown fox jumps over the lazy dog"),
	TESTSTR("The qu\0ck brown fox jumps over the lazy dog"),
	TESTSTR("The qui\0k brown fox jumps over the lazy dog"),
	TESTSTR("The quic\0 brown fox jumps over the lazy dog"),
	TESTSTR("The quick\0brown fox jumps over the lazy dog"),
	TESTSTR("The quick \0rown fox jumps over the lazy dog"),
	TESTSTR("The quick b\0own fox jumps over the lazy dog"),
	TESTSTR("The quick br\0wn fox jumps over the lazy dog"),
	TESTSTR("The quick bro\0n fox jumps over the lazy dog"),
	TESTSTR("The quick brow\0 fox jumps over the lazy dog"),
	TESTSTR("The quick brown\0fox jumps over the lazy dog"),
	TESTSTR("The quick brown \0ox jumps over the lazy dog"),
	TESTSTR("The quick brown f\0x jumps over the lazy dog"),
	TESTSTR("The quick brown fo\0 jumps over the lazy dog"),
	TESTSTR("The quick brown fox\0jumps over the lazy dog"),
	TESTSTR("The quick brown fox \0umps over the lazy dog"),
	TESTSTR("The quick brown fox j\0mps over the lazy dog"),
	TESTSTR("The quick brown fox ju\0ps over the lazy dog"),
	TESTSTR("The quick brown fox jum\0s over the lazy dog"),
	TESTSTR("The quick brown fox jump\0 over the lazy dog"),
	TESTSTR("The quick brown fox jumps\0over the lazy dog"),
	TESTSTR("The quick brown fox jumps \0ver the lazy dog"),
	TESTSTR("The quick brown fox jumps o\0er the lazy dog"),
	TESTSTR("The quick brown fox jumps ov\0r the lazy dog"),
	TESTSTR("The quick brown fox jumps ove\0 the lazy dog"),
	TESTSTR("The quick brown fox jumps over\0the lazy dog"),
	TESTSTR("The quick brown fox jumps over \0he lazy dog"),
	TESTSTR("The quick brown fox jumps over t\0e lazy dog"),
	TESTSTR("The quick brown fox jumps over th\0 lazy dog"),
	TESTSTR("The quick brown fox jumps over the\0lazy dog"),
	TESTSTR("The quick brown fox jumps over the \0azy dog"),
	TESTSTR("The quick brown fox jumps over the l\0zy dog"),
	TESTSTR("The quick brown fox jumps over the la\0y dog"),
	TESTSTR("The quick brown fox jumps over the laz\0 dog"),
	TESTSTR("The quick brown fox jumps over the lazy\0dog"),
	TESTSTR("The quick brown fox jumps over the lazy \0og"),
	TESTSTR("The quick brown fox jumps over the lazy d\0g"),
	TESTSTR("The quick brown fox jumps over the lazy do\0"),
	TESTSTR("The quick brown fox jumps over the lazy dog"),
	{ NULL, 0 },
};

ATF_TC(test00);
ATF_TC_HEAD(test00, tc)
{
	atf_tc_set_md_var(tc, "descr", "test00");
}
ATF_TC_BODY(test00, tc)
{
	const char **p;
	char buf[BUFSIZ];
	FILE *fp;

	for (p = &mode_rwa[0]; *p != NULL; ++p) {
		fp = fmemopen(&buf[0], sizeof(buf), *p);
/*
 * Upon successful completion, fmemopen() shall return a pointer to the
 * object controlling the stream.
 */
		ATF_CHECK(fp != NULL);

		ATF_CHECK(fclose(fp) == 0);
	}
}

ATF_TC(test01);
ATF_TC_HEAD(test01, tc)
{
	atf_tc_set_md_var(tc, "descr", "test01");
}
ATF_TC_BODY(test01, tc)
{
	const char **p;
	const char *mode[] = {
	    "r+", "rb+", "r+b",
	    "w+", "wb+", "w+b",
	    "a+", "ab+", "a+b",
	    NULL
	};
	FILE *fp;

	for (p = &mode[0]; *p != NULL; ++p) {
/*
 * If a null pointer is specified as the buf argument, fmemopen() shall
 * allocate size bytes of memory as if by a call to malloc().
 */
		fp = fmemopen(NULL, BUFSIZ, *p);
		ATF_CHECK(fp != NULL);

/*
 * If buf is a null pointer, the initial position shall always be set
 * to the beginning of the buffer.
 */
		ATF_CHECK(ftello(fp) == (off_t)0);

		ATF_CHECK(fclose(fp) == 0);
	}
}

ATF_TC(test02);
ATF_TC_HEAD(test02, tc)
{
	atf_tc_set_md_var(tc, "descr", "test02");
}
ATF_TC_BODY(test02, tc)
{
	const char **p;
	char buf[BUFSIZ];
	FILE *fp;

	for (p = &mode_r[0]; *p != NULL; ++p) {

		memset(&buf[0], 0x1, sizeof(buf));
		fp = fmemopen(&buf[0], sizeof(buf), *p);
		ATF_CHECK(fp != NULL);

/*
 * This position is initially set to either the beginning of the buffer
 * (for r and w modes)
 */
		ATF_CHECK((unsigned char)buf[0] == 0x1);
		ATF_CHECK(ftello(fp) == (off_t)0);

/*
 * The stream also maintains the size of the current buffer contents.
 * For modes r and r+ the size is set to the value given by the size argument.
 */
#if !defined(__GLIBC__)
		ATF_CHECK(fseeko(fp, (off_t)0, SEEK_END) == 0);
		ATF_CHECK(ftello(fp) == (off_t)sizeof(buf));
#endif
		ATF_CHECK(fclose(fp) == 0);
	}
}

static bool
fmemopen_truncates_buffer(const char *mode)
{
    if (mode[0] != 'w')
		return false;
#if defined(__NetBSD__)
	/* The NetBSD implementation truncates for all 'w' open flags */
	return true;
#elif defined(__GLIBC__)
	/* glibc only truncates the file if "+" is the second char. */
	return (mode[1] == '+');
#else
	/* POSIX mandates truncation for w+ and wb+ */
	return strchr(mode, '+') != 0;
#endif
}

ATF_TC(test03);
ATF_TC_HEAD(test03, tc)
{
	atf_tc_set_md_var(tc, "descr", "test03");
}
ATF_TC_BODY(test03, tc)
{
	const char **p;
	char buf[BUFSIZ];
	FILE *fp;

	for (p = &mode_w[0]; *p != NULL; ++p) {

		memset(&buf[0], 0x1, sizeof(buf));
		fp = fmemopen(&buf[0], sizeof(buf), *p);
		ATF_CHECK(fp != NULL);

/*
 * This position is initially set to either the beginning of the buffer
 * (for r and w modes)
 */
		if (fmemopen_truncates_buffer(*p)) {
			ATF_CHECK(buf[0] == '\0');
		} else {
			ATF_CHECK(buf[0] == 1);
		}
		ATF_CHECK(ftello(fp) == (off_t)0);

/*
 * For modes w and w+ the initial size is zero
 */
		ATF_CHECK(fseeko(fp, (off_t)0, SEEK_END) == 0);
		ATF_CHECK(ftello(fp) == (off_t)0);

		ATF_CHECK(fclose(fp) == 0);
	}
}

ATF_TC(test04);
ATF_TC_HEAD(test04, tc)
{
	atf_tc_set_md_var(tc, "descr", "test04");
}
ATF_TC_BODY(test04, tc)
{
	const char **p;
	char buf[BUFSIZ];
	FILE *fp;

/*
 * or to the first null byte in the buffer (for a modes)
 */
	for (p = &mode_a[0]; *p != NULL; ++p) {

		memset(&buf[0], 0x1, sizeof(buf));
		fp = fmemopen(&buf[0], sizeof(buf), *p);
		ATF_CHECK(fp != NULL);

		ATF_CHECK((unsigned char)buf[0] == 0x1);

/* If no null byte is found in append mode,
 * the initial position is set to one byte after the end of the buffer.
 */
#if !defined(__GLIBC__)
		ATF_CHECK(ftello(fp) == (off_t)sizeof(buf));
#endif

/*
 * and for modes a and a+ the initial size is either the position of the
 * first null byte in the buffer or the value of the size argument
 * if no null byte is found.
 */
#if !defined(__GLIBC__)
		ATF_CHECK(fseeko(fp, (off_t)0, SEEK_END) == 0);
		ATF_CHECK(ftello(fp) == (off_t)sizeof(buf));
#endif

		ATF_CHECK(fclose(fp) == 0);
	}
}

ATF_TC(test05);
ATF_TC_HEAD(test05, tc)
{
	atf_tc_set_md_var(tc, "descr", "test05");
}
ATF_TC_BODY(test05, tc)
{
#if !defined(__GLIBC__)
	const char **p;
	FILE *fp;
	char buf[BUFSIZ];

	for (p = &mode_rwa[0]; *p != NULL; ++p) {
/*
 * Otherwise, a null pointer shall be returned, and errno shall be set
 * to indicate the error.
 */
		errno = 0;
		fp = fmemopen(NULL, (size_t)0, *p);
		ATF_CHECK(fp == NULL);
		ATF_CHECK(errno == EINVAL);

		errno = 0;
		fp = fmemopen((void *)&buf[0], 0, *p);
		ATF_CHECK(fp == NULL);
		ATF_CHECK(errno == EINVAL);
	}
#endif
}

ATF_TC(test06);
ATF_TC_HEAD(test06, tc)
{
	atf_tc_set_md_var(tc, "descr", "test06");
}
ATF_TC_BODY(test06, tc)
{
	const char **p;
	const char *mode[] = { "", " ", "???", NULL };
	FILE *fp;

	for (p = &mode[0]; *p != NULL; ++p) {
/*
 * The value of the mode argument is not valid.
 */
		fp = fmemopen(NULL, 1, *p);
		ATF_CHECK(fp == NULL);
		ATF_CHECK(errno == EINVAL);
	}
}

ATF_TC(test07);
ATF_TC_HEAD(test07, tc)
{
	atf_tc_set_md_var(tc, "descr", "test07");
}
ATF_TC_BODY(test07, tc)
{
#if !defined(__GLIBC__)
	const char **p;
	const char *mode[] = {
	    "r", "rb",
	    "w", "wb",
	    "a", "ab",
	    NULL
	};
	FILE *fp;

	for (p = &mode[0]; *p != NULL; ++p) {
/*
 * Because this feature is only useful when the stream is opened for updating
 * (because there is no way to get a pointer to the buffer) the fmemopen()
 * call may fail if the mode argument does not include a '+'.
 */
		errno = 0;
		fp = fmemopen(NULL, 1, *p);
		ATF_CHECK(fp == NULL);
		ATF_CHECK(errno == EINVAL);
	}
#endif
}

ATF_TC(test08);
ATF_TC_HEAD(test08, tc)
{
	atf_tc_set_md_var(tc, "descr", "test08");
}
ATF_TC_BODY(test08, tc)
{
#if !defined(__GLIBC__)
	const char **p;
	const char *mode[] = {
	    "r+", "rb+", "r+b",
	    "w+", "wb+", "w+b",
	    "a+", "ab+", "a+b",
	    NULL
	};
	FILE *fp;

	for (p = &mode[0]; *p != NULL; ++p) {
/*
 * The buf argument is a null pointer and the allocation of a buffer of
 * length size has failed.
 */
		fp = fmemopen(NULL, SIZE_MAX, *p);
		ATF_CHECK(fp == NULL);
		ATF_CHECK(errno == ENOMEM);
	}
#endif
}

/*
 * test09 - test14:
 * An attempt to seek a memory buffer stream to a negative position or to a
 * position larger than the buffer size given in the size argument shall fail.
 */

ATF_TC(test09);
ATF_TC_HEAD(test09, tc)
{
	atf_tc_set_md_var(tc, "descr", "test09");
}
ATF_TC_BODY(test09, tc)
{
	struct testcase *t;
	const char **p;
	char buf[BUFSIZ];
	FILE *fp;
	off_t i;

	for (t = &testcases[0]; t->s != NULL; ++t) {
		for (p = &mode_rwa[0]; *p != NULL; ++p) {

			memcpy(&buf[0], t->s, t->n);
			fp = fmemopen(&buf[0], t->n, *p);
			ATF_CHECK(fp != NULL);

/*
 * test fmemopen_seek(SEEK_SET)
 */
			/* zero */
			ATF_CHECK(fseeko(fp, (off_t)0, SEEK_SET) == 0);
			ATF_CHECK(ftello(fp) == (off_t)0);

			/* positive */
			for (i = (off_t)1; i <= (off_t)t->n; ++i) {
				ATF_CHECK(fseeko(fp, i, SEEK_SET) == 0);
				ATF_CHECK(ftello(fp) == i);
			}
			/* positive + OOB */
			ATF_CHECK(ftello(fp) == t->n);
#if defined(__GLIBC__)
			/* seeking beyond the end of a buffered w/a file on
			 * glibc appears to rewind the buffer. */
			if (**p != 'r')
				setbuf(fp, NULL);
#endif
			ATF_CHECK(fseeko(fp, t->n + 1, SEEK_SET) == -1);
			ATF_CHECK(ftello(fp) == t->n);

			/* negative + OOB */
			ATF_CHECK(fseeko(fp, (off_t)-1, SEEK_SET) == -1);
			ATF_CHECK(ftello(fp) == t->n);

			ATF_CHECK(fclose(fp) == 0);
		}
	}
}

const char *mode_rw[] = {
    "r", "rb", "r+", "rb+", "r+b",
    "w", "wb", "w+", "wb+", "w+b",
    NULL
};

ATF_TC(test10);
ATF_TC_HEAD(test10, tc)
{
	atf_tc_set_md_var(tc, "descr", "test10");
}
ATF_TC_BODY(test10, tc)
{
	struct testcase *t;
	off_t i;
	const char **p;
	char buf[BUFSIZ];
	FILE *fp;

	for (t = &testcases[0]; t->s != NULL; ++t) {
		for (p = &mode_rw[0]; *p != NULL; ++p) {

			memcpy(&buf[0], t->s, t->n);
			fp = fmemopen(&buf[0], t->n, *p);
			ATF_CHECK(fp != NULL);

/*
 * test fmemopen_seek(SEEK_CUR)
 */
			ATF_CHECK(ftello(fp) == (off_t)0);

			/* zero */
			ATF_CHECK(fseeko(fp, (off_t)0, SEEK_CUR) == 0);
			ATF_CHECK(ftello(fp) == (off_t)0);

			/* negative & OOB */
			ATF_CHECK(fseeko(fp, (off_t)-1, SEEK_CUR) == -1);
			ATF_CHECK(ftello(fp) == (off_t)0);

			/* positive */
			for (i = 0; i < (off_t)t->n; ++i) {
				ATF_CHECK(fseeko(fp, (off_t)1, SEEK_CUR) == 0);
				ATF_CHECK(ftello(fp) == i + 1);
			}

			/* positive & OOB */
			ATF_CHECK(fseeko(fp, (off_t)1, SEEK_CUR) == -1);
			ATF_CHECK(ftello(fp) == (off_t)t->n);

			ATF_CHECK(fclose(fp) == 0);
		}
	}
}

ATF_TC(test11);
ATF_TC_HEAD(test11, tc)
{
	atf_tc_set_md_var(tc, "descr", "test11");
}
ATF_TC_BODY(test11, tc)
{
	struct testcase *t;
	off_t len, rest, i;
	const char **p;
	char buf[BUFSIZ];
	FILE *fp;

	/* test fmemopen_seek(SEEK_CUR) */
	for (t = &testcases[0]; t->s != NULL; ++t) {
		len = (off_t)strnlen(t->s, t->n);
		rest = (off_t)t->n - len;
		for (p = &mode_a[0]; *p != NULL; ++p) {

			memcpy(&buf[0], t->s, t->n);
			fp = fmemopen(&buf[0], t->n, *p);
			ATF_CHECK(fp != NULL);
/*
 * test fmemopen_seek(SEEK_CUR)
 */
			/* zero */
			ATF_CHECK(fseeko(fp, (off_t)0, SEEK_CUR) == 0);
			ATF_CHECK(ftello(fp) == len);

			/* posive */
			for (i = (off_t)1; i <= rest; ++i) {
				ATF_CHECK(fseeko(fp, (off_t)1, SEEK_CUR) == 0);
				ATF_CHECK(ftello(fp) == len + i);
			}

			/* positive + OOB */
			ATF_CHECK(fseeko(fp, (off_t)1, SEEK_CUR) == -1);
			ATF_CHECK(ftello(fp) == (off_t)t->n);

			/* negative */
			for (i = (off_t)1; i <= (off_t)t->n; ++i) {
				ATF_CHECK(fseeko(fp, (off_t)-1, SEEK_CUR) == 0);
				ATF_CHECK(ftello(fp) == (off_t)t->n - i);
			}

			/* negative + OOB */
			ATF_CHECK(fseeko(fp, (off_t)-1, SEEK_CUR) == -1);
			ATF_CHECK(ftello(fp) == (off_t)0);

			ATF_CHECK(fclose(fp) == 0);
		}
	}
}

ATF_TC(test12);
ATF_TC_HEAD(test12, tc)
{
	atf_tc_set_md_var(tc, "descr", "test12");
}
ATF_TC_BODY(test12, tc)
{
	struct testcase *t;
	off_t len, rest, i;
	const char **p;
	char buf[BUFSIZ];
	FILE *fp;

	/* test fmemopen_seek(SEEK_END) */
	for (t = &testcases[0]; t->s != NULL; ++t) {
#if !defined(__NetBSD__)
		/* Modes starting with 'r' treat the buffer size as length not the
		 * first NUL byte offset. */
		len = t->n;
#else
		len = (off_t)strnlen(t->s, t->n);
#endif
		rest = t->n - len;
		for (p = &mode_r[0]; *p != NULL; ++p) {

			memcpy(buf, t->s, t->n);
			fp = fmemopen(&buf[0], t->n, *p);
			ATF_CHECK(fp != NULL);

/*
 * test fmemopen_seek(SEEK_END)
 */
			ATF_CHECK(ftello(fp) == (off_t)0);

			/* zero */
			ATF_CHECK(fseeko(fp, (off_t)0, SEEK_END) == 0);
			ATF_CHECK(ftello(fp) == len);

			/* positive + OOB */
			ATF_CHECK(fseeko(fp, rest + 1, SEEK_END) == -1);
			ATF_CHECK(ftello(fp) == len);

			/* negative + OOB */
			ATF_CHECK(fseeko(fp, -(len + 1), SEEK_END) == -1);
			ATF_CHECK(ftello(fp) == len);

			/* positive */
			for (i = 1; i <= rest; ++i) {
				ATF_CHECK(fseeko(fp, i, SEEK_END) == 0);
				ATF_CHECK(ftello(fp) == len + i);
			}

			/* negative */
			for (i = 1; i < len; ++i) {
				ATF_CHECK(fseeko(fp, -i, SEEK_END) == 0);
				ATF_CHECK(ftello(fp) == len - i);
			}
			ATF_CHECK(fclose(fp) == 0);
		}
	}
}

ATF_TC(test13);
ATF_TC_HEAD(test13, tc)
{
	atf_tc_set_md_var(tc, "descr", "test13");
}
ATF_TC_BODY(test13, tc)
{
	struct testcase *t;
	off_t i;
	const char **p;
	char buf[BUFSIZ];
	FILE *fp;

	/* test fmemopen_seek(SEEK_END) */
	for (t = &testcases[0]; t->s != NULL; ++t) {
		for (p = &mode_w[0]; *p != NULL; ++p) {

			memcpy(buf, t->s, t->n);
			fp = fmemopen(&buf[0], t->n, *p);
			ATF_CHECK(fp != NULL);
/*
 * test fmemopen_seek(SEEK_END)
 */
			ATF_CHECK(ftello(fp) == (off_t)0);
			if (fmemopen_truncates_buffer(*p))
				ATF_CHECK(buf[0] == '\0');

			/* zero */
			ATF_CHECK(fseeko(fp, (off_t)0, SEEK_END) == 0);
			ATF_CHECK(ftello(fp) == (off_t)0);

			/* positive + OOB */
			ATF_CHECK(fseeko(fp, (off_t)t->n + 1, SEEK_END) == -1);
			ATF_CHECK(ftello(fp) == (off_t)0);

			/* negative + OOB */
			ATF_CHECK(fseeko(fp, -1, SEEK_END) == -1);
			ATF_CHECK(ftello(fp) == (off_t)0);

			/* positive */
			for (i = 1; i <= t->n; ++i) {
				ATF_CHECK(fseeko(fp, i, SEEK_END) == 0);
				ATF_CHECK(ftello(fp) == i);
			}
			ATF_CHECK(fclose(fp) == 0);
		}
	}
}

ATF_TC(test14);
ATF_TC_HEAD(test14, tc)
{
	atf_tc_set_md_var(tc, "descr", "test14");
}
ATF_TC_BODY(test14, tc)
{
	struct testcase *t;
	off_t len, rest, i;
	const char **p;
	char buf[BUFSIZ];
	FILE *fp;

	/* test fmemopen_seek(SEEK_END) */
	for (t = &testcases[0]; t->s != NULL; ++t) {
		len = (off_t)strnlen(t->s, t->n);
		rest = (off_t)t->n - len;
		for (p = &mode_a[0]; *p != NULL; ++p) {

			memcpy(buf, t->s, t->n);
			fp = fmemopen(&buf[0], t->n, *p);
			ATF_CHECK(fp != NULL);
/*
 * test fmemopen_seek(SEEK_END)
 */
			ATF_CHECK(ftello(fp) == len);

			/* zero */
			ATF_CHECK(fseeko(fp, 0, SEEK_END) == 0);
			ATF_CHECK(ftello(fp) == len);

			/* positive + OOB */
			ATF_CHECK(fseeko(fp, rest + 1, SEEK_END) == -1);
			ATF_CHECK(ftello(fp) == len);

			/* negative + OOB */
			ATF_CHECK(fseeko(fp, -(len + 1), SEEK_END) == -1);
			ATF_CHECK(ftello(fp) == len);

			/* positive */
			for (i = 1; i <= rest; ++i) {
				ATF_CHECK(fseeko(fp, i, SEEK_END) == 0);
				ATF_CHECK(ftello(fp) == len + i);
			}

			/* negative */
			for (i = 1; i < len; ++i) {
				ATF_CHECK(fseeko(fp, -i, SEEK_END) == 0);
				ATF_CHECK(ftello(fp) == len - i);
			}
			ATF_CHECK(fclose(fp) == 0);
		}
	}
}

const char *mode_rw1[] = {
    "r", "rb", "r+", "rb+", "r+b",
    "w+", "wb+",
    NULL
};

/* test15 - 18:
 * When a stream open for writing is flushed or closed, a null byte is written
 * at the current position or at the end of the buffer, depending on the size
 * of the contents.
 */

ATF_TC(test15);
ATF_TC_HEAD(test15, tc)
{
	atf_tc_set_md_var(tc, "descr", "test15");
}
ATF_TC_BODY(test15, tc)
{
	struct testcase *t;
	const char **p;
	char buf0[BUFSIZ];
	FILE *fp;
	off_t i, read_end;

	for (t = &testcases[0]; t->s != NULL; ++t) {
		for (p = &mode_rw1[0]; *p != NULL; ++p) {
			read_end = (off_t)t->n;
#ifndef __NetBSD__
			/* NetBSD allows reading beyond EOF for 'w' mode. */
			if (**p == 'w') {
				read_end = 0;
			}
#endif

			memcpy(&buf0[0], t->s, t->n);
			fp = fmemopen(&buf0[0], t->n, *p);
			ATF_CHECK(fp != NULL);
/*
 * test fmemopen_read + fgetc(3)
 */
			for (i = 0; i < read_end; ++i) {
				ATF_CHECK(ftello(fp) == i);
				ATF_CHECK(fgetc(fp) == buf0[i]);
				ATF_CHECK(feof(fp) == 0);
				ATF_CHECK(ftello(fp) == i + 1);
			}
			ATF_CHECK(fgetc(fp) == EOF);
			ATF_CHECK(feof(fp) != 0);
			ATF_CHECK(ftello(fp) == read_end);
			ATF_CHECK(fclose(fp) == 0);
		}
	}
}

ATF_TC(test16);
ATF_TC_HEAD(test16, tc)
{
	atf_tc_set_md_var(tc, "descr", "test16");
}
ATF_TC_BODY(test16, tc)
{
	struct testcase *t;
	const char **p;
	char buf0[BUFSIZ], buf1[BUFSIZ];
	FILE *fp;
	size_t read_end;

	for (t = &testcases[0]; t->s != NULL; ++t) {
		for (p = &mode_rw1[0]; *p != NULL; ++p) {
			read_end = t->n;
#ifndef __NetBSD__
			/* NetBSD allows reading beyond EOF for 'w' mode. */
			if (**p == 'w') {
				read_end = 0;
			}
#endif

			memcpy(&buf0[0], t->s, t->n);
			buf1[t->n] = 0x1;
			fp = fmemopen(&buf0[0], t->n, *p);
			ATF_CHECK(fp != NULL);
/*
 * test fmemopen_read + fread(4)
 */
			ATF_CHECK(ftello(fp) == (off_t)0);
			ATF_CHECK(fread(&buf1[0], 1, sizeof(buf1), fp) == read_end);
			ATF_CHECK(feof(fp) != 0);
			ATF_CHECK(memcmp(&buf0[0], &buf1[0], read_end) == 0);
			ATF_CHECK((unsigned char)buf1[t->n] == 0x1);

			ATF_CHECK(fclose(fp) == 0);
		}
	}
}

const char *mode_a1[] = { "a+", "ab+", NULL };

ATF_TC(test17);
ATF_TC_HEAD(test17, tc)
{
	atf_tc_set_md_var(tc, "descr", "test17");
}
ATF_TC_BODY(test17, tc)
{
	struct testcase *t;
	size_t len;
	size_t i;
	const char **p;
	char buf[BUFSIZ];
	FILE *fp;
	size_t read_end;

	for (t = &testcases[0]; t->s != NULL; ++t) {
		len = strnlen(t->s, t->n);
		for (p = &mode_a1[0]; *p != NULL; ++p) {

			memcpy(&buf[0], t->s, t->n);
			fp = fmemopen(&buf[0], t->n, *p);
			ATF_CHECK(fp != NULL);
/*
 * test fmemopen_read + fgetc(3)
 */
#ifdef __NetBSD__
			/* NetBSD allows reading beyond EOF for 'a' mode. */
			read_end = t->n;
#else
			read_end = len;
#endif
			for (i = len; i < read_end; ++i) {
				ATF_CHECK(ftello(fp) == (off_t)i);
				ATF_CHECK(fgetc(fp) == buf[i]);
				ATF_CHECK(feof(fp) == 0);
				ATF_CHECK(ftello(fp) == (off_t)i + 1);
			}
			ATF_CHECK(fgetc(fp) == EOF);
			ATF_CHECK(feof(fp) != 0);
#ifdef __NetBSD__
			ATF_CHECK(ftello(fp) == (off_t)t->n);
#else
			ATF_CHECK(ftello(fp) == (off_t)len);
#endif
			rewind(fp);
			for (i = 0; i < read_end; ++i) {
				ATF_CHECK(ftello(fp) == (off_t)i);
				ATF_CHECK(fgetc(fp) == buf[i]);
				ATF_CHECK(feof(fp) == 0);
				ATF_CHECK(ftello(fp) == (off_t)i + 1);
			}
			ATF_CHECK(fgetc(fp) == EOF);
			ATF_CHECK(feof(fp) != 0);
			ATF_CHECK(ftello(fp) == (off_t)read_end);
			ATF_CHECK(fclose(fp) == 0);
		}
	}
}

ATF_TC(test18);
ATF_TC_HEAD(test18, tc)
{
	atf_tc_set_md_var(tc, "descr", "test18");
}
ATF_TC_BODY(test18, tc)
{
	struct testcase *t;
	size_t len, readlen1, readlen2;
	const char **p;
	char buf0[BUFSIZ], buf1[BUFSIZ];
	FILE *fp;

	for (t = &testcases[0]; t->s != NULL; ++t) {
		len = strnlen(t->s, t->n);
		for (p = &mode_a1[0]; *p != NULL; ++p) {
			/* POSIX says reads end at current buffer length. */
#if defined(__NetBSD__)
			readlen1 = t->n - len;
			readlen2 = (size_t)t->n;
#else
			readlen1 = 0;
			readlen2 = len;
#endif
			memcpy(&buf0[0], t->s, t->n);
			buf1[t->n - len] = 0x1;
			fp = fmemopen(&buf0[0], t->n, *p);
			ATF_CHECK(fp != NULL);
/*
 * test fmemopen_read + fread(3)
 */
			ATF_CHECK(ftello(fp) == (off_t)len);
			ATF_CHECK(fread(&buf1[0], 1, sizeof(buf1), fp)
			    == readlen1);
			ATF_CHECK(feof(fp) != 0);
			ATF_CHECK(!memcmp(&buf0[len], &buf1[0], readlen1));
			ATF_CHECK((unsigned char)buf1[t->n - len] == 0x1);
			rewind(fp);
			buf1[t->n] = 0x1;
			ATF_CHECK(ftello(fp) == (off_t)0);
			ATF_CHECK(fread(&buf1[0], 1, sizeof(buf1), fp)
			    == readlen2);
			ATF_CHECK(feof(fp) != 0);
			ATF_CHECK(!memcmp(&buf0[0], &buf1[0], readlen2));
			ATF_CHECK((unsigned char)buf1[t->n] == 0x1);
			ATF_CHECK(fclose(fp) == 0);
		}
	}
}

/*
 * test19 - test22:
 * If a stream open for update is flushed or closed and the last write has
 * advanced the current buffer size, a null byte is written at the end of the
 * buffer if it fits.
 */

const char *mode_rw2[] = {
    "r+", "rb+", "r+b",
    "w", "wb", "w+", "wb+", "w+b",
    NULL
};

ATF_TC(test19);
ATF_TC_HEAD(test19, tc)
{
	atf_tc_set_md_var(tc, "descr", "test19");
}
ATF_TC_BODY(test19, tc)
{
	struct testcase *t;
	int i;
	const char **p;
	char buf[BUFSIZ];
	FILE *fp;

	for (t = &testcases[0]; t->s != NULL; ++t) {
		for (p = &mode_rw2[0]; *p != NULL; ++p) {

			memcpy(&buf[0], t->s, t->n);
			buf[t->n] = 0x1;
			fp = fmemopen(&buf[0], t->n + 1, *p);
			ATF_CHECK(fp != NULL);
			setbuf(fp, NULL);
/*
 * test fmemopen_write + fputc(3)
 */
			for (i = 0; i < t->n; ++i) {
				ATF_CHECK(ftello(fp) == (off_t)i);
				ATF_CHECK(fputc(t->s[i], fp) == t->s[i]);
				ATF_CHECK(buf[i] == t->s[i]);
				ATF_CHECK(ftello(fp) == (off_t)i + 1);
				ATF_CHECK(buf[i] == t->s[i]);
#if !defined(__NetBSD__)
				if (**p == 'w' && t->s[i] != '\0')
#endif
				ATF_CHECK(buf[i + 1] == '\0');
			}

/* don't accept non nul character at end of buffer (not mandated by POSIX, so
 * fails with glibc, etc.) */
#if defined(__NetBSD__)
			ATF_CHECK(fputc(0x1, fp) == EOF);
			ATF_CHECK_MSG(ftello(fp) == (off_t)t->n,
				"%jd != %jd", (intmax_t)ftello(fp),
				(intmax_t)t->n);
			ATF_CHECK(feof(fp) == 0);
#endif

/* accept nul character at end of buffer */
			ATF_CHECK(fputc('\0', fp) == '\0');
			ATF_CHECK(ftello(fp) == (off_t)t->n + 1);
			ATF_CHECK(feof(fp) == 0);

/* reach EOF */
			ATF_CHECK(fputc('\0', fp) == EOF);
			ATF_CHECK(ftello(fp) == (off_t)t->n + 1);

			/* compare */
			ATF_CHECK(memcmp(&buf[0], t->s, t->n) == 0);
			ATF_CHECK(buf[t->n] == '\0');

			ATF_CHECK(fclose(fp) == 0);
		}
	}
}

ATF_TC(test20);
ATF_TC_HEAD(test20, tc)
{
	atf_tc_set_md_var(tc, "descr", "test20");
}
ATF_TC_BODY(test20, tc)
{
	struct testcase *t;
	const char **p;
	char buf[BUFSIZ];
	FILE *fp;

	for (t = &testcases[0]; t->s != NULL; ++t) {
		for (p = &mode_rw2[0]; *p != NULL; ++p) {

			memcpy(&buf[0], t->s, t->n);
			buf[t->n] = 0x1;
			fp = fmemopen(&buf[0], t->n + 1, *p);
			ATF_CHECK(fp != NULL);
			setbuf(fp, NULL);
			ATF_CHECK(fwrite(t->s, 1, t->n, fp) == (size_t)t->n);
/*
 * test fmemopen_write + fwrite(3)
 */
#if defined(__NetBSD__)
/* NUL termination is not mandated by POSIX, so fails with glibc, etc. */
			ATF_CHECK(buf[t->n] == '\0');
/* don't accept non nul character at end of buffer (not mandated by POSIX, so
 * fails with glibc, etc.) */
			ATF_CHECK(fwrite("\x1", 1, 1, fp) == 0);
			ATF_CHECK(ftello(fp) == (off_t)t->n);
			ATF_CHECK(feof(fp) == 0);
#endif

/* accept nul character at end of buffer */
			ATF_CHECK(fwrite("\x0", 1, 1, fp) == 1);
			ATF_CHECK(ftello(fp) == (off_t)t->n + 1);
			ATF_CHECK(feof(fp) == 0);

/* reach EOF */
			ATF_CHECK(fputc('\0', fp) == EOF);
			ATF_CHECK(ftello(fp) == (off_t)t->n + 1);

/* compare */
			ATF_CHECK(memcmp(&buf[0], t->s, t->n) == 0);
			ATF_CHECK(buf[t->n] == '\0');

			ATF_CHECK(fclose(fp) == 0);
		}
	}
}

ATF_TC(test21);
ATF_TC_HEAD(test21, tc)
{
	atf_tc_set_md_var(tc, "descr", "test21");
}
ATF_TC_BODY(test21, tc)
{
	struct testcase *t;
	int len, i;
	const char **p;
	char buf[BUFSIZ];
	FILE *fp;

	for (t = &testcases[0]; t->s != NULL; ++t) {
		len = strnlen(t->s, t->n);
		for (p = &mode_a[0]; *p != NULL; ++p) {
			memcpy(&buf[0], t->s, t->n);
			fp = fmemopen(&buf[0], t->n, *p);
			ATF_CHECK(fp != NULL);
			setbuf(fp, NULL);
/*
 * test fmemopen_write + fputc(3)
 */
			if (len < t->n) {
				for (i = len; i < t->n - 1; ++i) {
					ATF_CHECK(ftello(fp) == (off_t)i);
					ATF_CHECK(fputc(t->s[i - len], fp)
					    == t->s[i - len]);
					ATF_CHECK(buf[i] == t->s[i - len]);
					ATF_CHECK(ftello(fp) == (off_t)i + 1);
#if defined(__NetBSD__)
					ATF_CHECK(buf[i + 1] == '\0');
#endif
				}

/* don't accept non nul character at end of buffer (not mandated by POSIX, so
 * fails with glibc, etc.) */
#if defined(__NetBSD__)
				ATF_CHECK(ftello(fp) == (off_t)t->n - 1);
				ATF_CHECK(fputc(0x1, fp) == EOF);
				ATF_CHECK(ftello(fp) == (off_t)t->n - 1);
#endif

/* accept nul character at end of buffer */
				ATF_CHECK(ftello(fp) == (off_t)t->n - 1);
				ATF_CHECK(fputc('\0', fp) == '\0');
				ATF_CHECK(ftello(fp) == (off_t)t->n);
			}

/* reach EOF */
			ATF_CHECK(ftello(fp) == (off_t)t->n);
			ATF_CHECK(fputc('\0', fp) == EOF);
			ATF_CHECK(ftello(fp) == (off_t)t->n);

			ATF_CHECK(fclose(fp) == 0);
		}
	}
}

ATF_TC(test22);
ATF_TC_HEAD(test22, tc)
{
	atf_tc_set_md_var(tc, "descr", "test22");
}
ATF_TC_BODY(test22, tc)
{
	struct testcase *t0, *t1;
	size_t len0, len1, nleft, written, writelen;
	const char **p;
	char buf[BUFSIZ];
	FILE *fp;

	for (t0 = &testcases[0]; t0->s != NULL; ++t0) {
		len0 = strnlen(t0->s, t0->n);
		for (t1 = &testcases[0]; t1->s != NULL; ++t1) {
			for (p = &mode_a[0]; *p != NULL; ++p) {
				len1 = strnlen(t1->s, t1->n);
				memcpy(&buf[0], t0->s, t0->n);
				fp = fmemopen(&buf[0], t0->n, *p);
				ATF_CHECK(fp != NULL);
				setbuf(fp, NULL);
				ATF_CHECK(ftello(fp) == (off_t)len0);
/*
 * test fmemopen_write + fwrite(3)
 */
				nleft = t0->n - len0;
				written = fwrite(t1->s, 1, t1->n, fp);
				if (nleft == 0 || len1 == nleft - 1) {
					writelen = nleft;
				} else {
#if defined(__NetBSD__)
					/* NetBSD ensures NUL-termination */
					writelen = nleft - 1;
#else
					/* POSIX says it's not required. */
					writelen = nleft;
#endif
				}
#if defined(__GLIBC__)
				/* glibc incorrectly checks buf[writelen-1] for
				 * NUL termination instead of clamped index. */
				if (t1->s[t1->n - 1] != '\0' && nleft <= 1)
					writelen = 0;
#endif
				ATF_CHECK(written == writelen);
				ATF_CHECK(ftello(fp) == (off_t)(len0 + writelen));
				ATF_CHECK(fclose(fp) == 0);
			}
		}
	}
}

ATF_TP_ADD_TCS(tp)
{
	ATF_TP_ADD_TC(tp, test00);
	ATF_TP_ADD_TC(tp, test01);
	ATF_TP_ADD_TC(tp, test02);
	ATF_TP_ADD_TC(tp, test03);
	ATF_TP_ADD_TC(tp, test04);
	ATF_TP_ADD_TC(tp, test05);
	ATF_TP_ADD_TC(tp, test06);
	ATF_TP_ADD_TC(tp, test07);
	ATF_TP_ADD_TC(tp, test08);
	ATF_TP_ADD_TC(tp, test09);
	ATF_TP_ADD_TC(tp, test10);
	ATF_TP_ADD_TC(tp, test11);
	ATF_TP_ADD_TC(tp, test12);
	ATF_TP_ADD_TC(tp, test13);
	ATF_TP_ADD_TC(tp, test14);
	ATF_TP_ADD_TC(tp, test15);
	ATF_TP_ADD_TC(tp, test16);
	ATF_TP_ADD_TC(tp, test17);
	ATF_TP_ADD_TC(tp, test18);
	ATF_TP_ADD_TC(tp, test19);
	ATF_TP_ADD_TC(tp, test20);
	ATF_TP_ADD_TC(tp, test21);
	ATF_TP_ADD_TC(tp, test22);

	return atf_no_error();
}
