
/*
 * Custom-made wrappers for some special POSIX functions.
 */

#include "codegen.h"
#include "hash.h"

#include <inttypes.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <pthread.h>


/* Wrapper for open(), we can't generate it because it has a variable number
 * of arguments */
mkwrap_init(int, open, (const char *pathname, int flags, ...),
		(const char *, int, ...))

int open(const char *pathname, int flags, ...)
{
	int r;
	int fstatus;

	/* Differences from the generated code begin here */

	mode_t mode;
	va_list l;

	if (flags & O_CREAT) {
		va_start(l, flags);

		/* va_arg() can only take fully promoted types, and mode_t
		 * sometimes is smaller than an int, so we should always pass
		 * int to it, and not mode_t. Not doing so would may result in
		 * a compile-time warning and run-time error. We asume that it
		 * is never bigger than an int, which holds in practice. */
		mode = va_arg(l, int);

		va_end(l);
	} else {
		/* set it to 0, it's ignored anyway */
		mode = 0;
	}

	/* Differences from the generated code end here */

/* Use the normal macros to complete the function, now that we have
 * set mode to something */
mkwrap_body_called(open, (pathname, flags, mode), -1)

	static const int valid_errnos[] = {
	  #ifdef EACCESS
		EACCES,
	  #endif
	  #ifdef EFAULT
		EFAULT,
	  #endif
	  #ifdef EFBIG
		EFBIG,
	  #endif
	  #ifdef EOVERFLOW
		EOVERFLOW,
	  #endif
	  #ifdef ELOOP
		ELOOP,
	  #endif
	  #ifdef EMFILE
		EMFILE,
	  #endif
	  #ifdef ENAMETOOLONG
		ENAMETOOLONG,
	  #endif
	  #ifdef ENFILE
		ENFILE,
	  #endif
	  #ifdef ENOENT
		ENOENT,
	  #endif
	  #ifdef ENOMEM
		ENOMEM,
	  #endif
	  #ifdef ENOSPC
		ENOSPC,
	  #endif
	  #ifdef ENOTDIR
		ENOTDIR,
	  #endif
	  #ifdef EROFS
		EROFS
	  #endif
	};
mkwrap_body_errno("posix/io/oc/open", -1)
mkwrap_bottom(open, (pathname, flags, mode))


/* The 64-bit variant for glibc.
 * The code is identical to open(), just using open64() where appropriate. */
#ifdef __GLIBC__
#if LIBFIU_CAN_DEFINE_64BIT_FUNCTIONS

mkwrap_init(int, open64, (const char *pathname, int flags, ...),
		(const char *, int, ...))

int open64(const char *pathname, int flags, ...)
{
	int r;
	int fstatus;

	/* Differences from the generated code begin here */

	mode_t mode;
	va_list l;

	if (flags & O_CREAT) {
		va_start(l, flags);

		/* va_arg() can only take fully promoted types, and mode_t
		 * sometimes is smaller than an int, so we should always pass
		 * int to it, and not mode_t. Not doing so would may result in
		 * a compile-time warning and run-time error. We asume that it
		 * is never bigger than an int, which holds in practise. */
		mode = va_arg(l, int);

		va_end(l);
	} else {
		/* set it to 0, it's ignored anyway */
		mode = 0;
	}

	/* Differences from the generated code end here */

/* Use the normal macros to complete the function, now that we have
 * set mode to something */
mkwrap_body_called(open, (pathname, flags, mode), -1)

	static const int valid_errnos[] = {
	  #ifdef EACCESS
		EACCES,
	  #endif
	  #ifdef EFAULT
		EFAULT,
	  #endif
	  #ifdef EFBIG
		EFBIG,
	  #endif
	  #ifdef EOVERFLOW
		EOVERFLOW,
	  #endif
	  #ifdef ELOOP
		ELOOP,
	  #endif
	  #ifdef EMFILE
		EMFILE,
	  #endif
	  #ifdef ENAMETOOLONG
		ENAMETOOLONG,
	  #endif
	  #ifdef ENFILE
		ENFILE,
	  #endif
	  #ifdef ENOENT
		ENOENT,
	  #endif
	  #ifdef ENOMEM
		ENOMEM,
	  #endif
	  #ifdef ENOSPC
		ENOSPC,
	  #endif
	  #ifdef ENOTDIR
		ENOTDIR,
	  #endif
	  #ifdef EROFS
		EROFS
	  #endif
	};
mkwrap_body_errno("posix/io/oc/open", -1)
mkwrap_bottom(open64, (pathname, flags, mode))

#endif  // LIBFIU_CAN_DEFINE_64BIT_FUNCTIONS
#endif  // __GLIBC__

/*
 * To simulate ferror() properly, we need to keep track of which FILE * have
 * we returned a failure for.
 *
 * To do that, we keep a set of FILE *. This set is implemented using a hash
 * table, for convenience.
 *
 * An alternative would be to have a static array, but then lookups would
 * scale linearly with the number of files we're expected to fail, and (most
 * importantly) require a new data type just for this.
 */
static hash_t *ferror_hash_table;
static pthread_mutex_t ferror_hash_table_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_once_t ferror_hash_table_is_initialized = PTHREAD_ONCE_INIT;

static void _fiu_init_ferror_hash_table(void)
{
	rec_inc();
	pthread_mutex_lock(&ferror_hash_table_mutex);

	// Create the hash table. No need for a value destructor, as our values
	// are not pointers but static values.
	ferror_hash_table = hash_create(NULL);

	pthread_mutex_unlock(&ferror_hash_table_mutex);
	rec_dec();
}

/* Our hash table uses character keys, to convert stream to keys we just get
 * the hexadecimal representation via PRIxPTR (%p is implementation
 * dependent). */
#define STREAM_KEY_SIZE (sizeof(uintptr_t) * 2 + 1)

static void stream_to_key(void *stream, char key[STREAM_KEY_SIZE])
{
	snprintf(key, STREAM_KEY_SIZE, "%" PRIxPTR, (uintptr_t) stream);
}

void set_ferror(void * stream)
{
	char key[STREAM_KEY_SIZE];
	stream_to_key(stream, key);

	pthread_once(&ferror_hash_table_is_initialized,
			_fiu_init_ferror_hash_table);

	pthread_mutex_lock(&ferror_hash_table_mutex);

	// Use a dummy the value; we don't care about it, we only need to be able to
	// distinguish it from NULL.
	hash_set(ferror_hash_table, key, (void *) 0xDEAD);

	pthread_mutex_unlock(&ferror_hash_table_mutex);
}

static int get_ferror(void * stream)
{
	char key[STREAM_KEY_SIZE];
	stream_to_key(stream, key);

	pthread_once(&ferror_hash_table_is_initialized,
			_fiu_init_ferror_hash_table);

	pthread_mutex_lock(&ferror_hash_table_mutex);

	void *value = hash_get(ferror_hash_table, key);

	pthread_mutex_unlock(&ferror_hash_table_mutex);

	return value != NULL;
}

static void clear_ferror(void * stream)
{
	char key[STREAM_KEY_SIZE];
	stream_to_key(stream, key);

	pthread_once(&ferror_hash_table_is_initialized,
			_fiu_init_ferror_hash_table);

	pthread_mutex_lock(&ferror_hash_table_mutex);

	hash_del(ferror_hash_table, key);

	pthread_mutex_unlock(&ferror_hash_table_mutex);
}


/* Wrapper for ferror() */
mkwrap_top(int, ferror, (FILE *stream), (stream), (FILE *), 1)
mkwrap_body_hardcoded("posix/stdio/error/ferror", 1)

	/* The following is like mkwrap_bottom(), but has an additional check to
	 * return 1 if we previously returned an error for this file, so the
	 * semantics are consistent. */
	if (_fiu_orig_ferror == NULL)
		_fiu_init_ferror();

	printd("calling orig\n");
	r = (*_fiu_orig_ferror) (stream);

	if (r == 0 && get_ferror(stream)) {
		printd("ferror fixed\n");
		r = 1;
	}

exit:
	rec_dec();
	return r;
}


/* Custom wrapper for clearerr().
 * This function does not fail as such, but we override it so we can provide a
 * consistent view. */
mkwrap_init(void, clearerr, (FILE *stream), (FILE *))

void clearerr (FILE *stream)
{
	rec_inc();

	if (_fiu_orig_clearerr == NULL)
			_fiu_init_clearerr();

	printd("calling orig\n");
	(*_fiu_orig_clearerr) (stream);

	printd("fixing internal state\n");
	clear_ferror(stream);

	rec_dec();
}


/* Custom wrapper for fclose() that clears ferror_hash_table. */
mkwrap_top(int , fclose, (FILE *stream), (stream), (FILE *), (EOF))
	static const int valid_errnos[] = {
	  #ifdef EAGAIN
		EAGAIN,
	  #endif
	  #ifdef EBADF
		EBADF,
	  #endif
	  #ifdef EFBIG
		EFBIG,
	  #endif
	  #ifdef EFBIG
		EFBIG,
	  #endif
	  #ifdef EINTR
		EINTR,
	  #endif
	  #ifdef EIO
		EIO,
	  #endif
	  #ifdef ENOMEM
		ENOMEM,
	  #endif
	  #ifdef ENOSPC
		ENOSPC,
	  #endif
	  #ifdef EPIPE
		EPIPE,
	  #endif
	  #ifdef ENXIO
		ENXIO,
	  #endif
	};
mkwrap_body_errno("posix/stdio/oc/fclose", EOF)

/* We need a custom injection of clear_ferror here to prevent stale entries in
 * ferror_hash_table. */
	clear_ferror(stream);

mkwrap_bottom(fclose, (stream))


/*
 * Variadic functions
 *
 * For some variadic functions, like printf(), we use a non-variadic
 * counterpart (vprintf()) to implement the wrapping.
 *
 */

/* Generate the body for a variadic function, which relies on a non-variadic
 * function for expansion.
 *
 * We expect mkwrap_init to be called on the non-variadic one, see the calls
 * below for examples.
 */
#define mkwrap_variadic_def_and_body_called( \
		RTYPE, /* return type */ \
		NAME, /* variadic name */ \
		NVNAME, /* non-variadic name */ \
		PARAMS, /* parameter definition, with types */ \
		PARAMSN, /* parameter names only, with "arguments" for va part */ \
		LASTPN, /* last parameter name */ \
		ON_ERR /* what to return on error */ \
		) \
								\
	RTYPE NAME PARAMS					\
	{ 							\
		RTYPE r;					\
		int fstatus;					\
		va_list arguments;				\
								\
		if (_fiu_called) {				\
			if (_fiu_orig_##NVNAME == NULL) {	\
				if (_fiu_in_init_##NVNAME) {	\
					printd("fail on init\n"); \
					return ON_ERR;		\
				} else {			\
					printd("get orig\n");	\
					_fiu_init_##NVNAME();	\
				}				\
			}					\
			printd("orig\n");			\
			va_start(arguments, LASTPN);		\
			r = (*_fiu_orig_##NVNAME) PARAMSN;	\
			va_end(arguments);			\
								\
			return r;				\
		}						\
								\
		printd("fiu\n");				\
								\
		/* fiu_fail() may call anything */		\
		rec_inc();


#define mkwrap_variadic_bottom( \
		NAME /* variadic name */, \
		NVNAME /* non-variadic name */, \
		PARAMSN /* parameter names only, with "arguments" for va part */, \
		LASTPN /* last parameter name */ \
		) \
								\
		if (_fiu_orig_##NVNAME == NULL)			\
			_fiu_init_##NVNAME();			\
								\
		printd("calling orig\n");			\
		va_start(arguments, LASTPN);			\
		r = (*_fiu_orig_##NVNAME) PARAMSN;		\
		va_end(arguments);				\
								\
	exit:							\
		rec_dec();					\
		return r;					\
	}


/* fprintf() -> vfprintf() */
mkwrap_init(int, vfprintf,
	(FILE *restrict stream, const char *restrict format, va_list ap),
	(FILE *restrict, const char *restrict, va_list))

mkwrap_variadic_def_and_body_called(int, fprintf, vfprintf,
	(FILE *restrict stream, const char *restrict format, ...),
	(stream, format, arguments),
	format, -1)

	static const int valid_errnos[] = {
	  #ifdef EAGAIN
		EAGAIN,
	  #endif
	  #ifdef EBADF
		EBADF,
	  #endif
	  #ifdef EFBIG
		EFBIG,
	  #endif
	  #ifdef EINTR
		EINTR,
	  #endif
	  #ifdef EIO
		EIO,
	  #endif
	  #ifdef ENOMEM
		ENOMEM,
	  #endif
	  #ifdef ENOSPC
		ENOSPC,
	  #endif
	  #ifdef ENXIO
		ENXIO,
	  #endif
	  #ifdef EPIPE
		EPIPE,
	  #endif
	  #ifdef EILSEQ
		EILSEQ,
	  #endif
	  #ifdef EOVERFLOW
		EOVERFLOW,
	  #endif
	};

mkwrap_body_errno_ferror("posix/stdio/sp/fprintf", -1, stream)
mkwrap_variadic_bottom(fprintf, vfprintf, (stream, format, arguments), format)


/* printf() -> vprintf() */
mkwrap_init(int, vprintf,
	(const char *restrict format, va_list ap),
	(const char *restrict, va_list))

mkwrap_variadic_def_and_body_called(int, printf, vprintf,
	(const char *restrict format, ...),
	(format, arguments),
	format, -1)

	static const int valid_errnos[] = {
	  #ifdef EAGAIN
		EAGAIN,
	  #endif
	  #ifdef EBADF
		EBADF,
	  #endif
	  #ifdef EFBIG
		EFBIG,
	  #endif
	  #ifdef EINTR
		EINTR,
	  #endif
	  #ifdef EIO
		EIO,
	  #endif
	  #ifdef ENOMEM
		ENOMEM,
	  #endif
	  #ifdef ENOSPC
		ENOSPC,
	  #endif
	  #ifdef ENXIO
		ENXIO,
	  #endif
	  #ifdef EPIPE
		EPIPE,
	  #endif
	  #ifdef EILSEQ
		EILSEQ,
	  #endif
	  #ifdef EOVERFLOW
		EOVERFLOW,
	  #endif
	};

mkwrap_body_errno_ferror("posix/stdio/sp/printf", -1, stdout)
mkwrap_variadic_bottom(printf, vprintf, (format, arguments), format)


/* dprintf() -> vdprintf() */
mkwrap_init(int, vdprintf,
	(int fildes, const char *restrict format, va_list ap),
	(int, const char *restrict, va_list))

mkwrap_variadic_def_and_body_called(int, dprintf, vdprintf,
	(int fildes, const char *restrict format, ...),
	(fildes, format, arguments),
	format, -1)

	static const int valid_errnos[] = {
	  #ifdef EAGAIN
		EAGAIN,
	  #endif
	  #ifdef EBADF
		EBADF,
	  #endif
	  #ifdef EFBIG
		EFBIG,
	  #endif
	  #ifdef EINTR
		EINTR,
	  #endif
	  #ifdef EIO
		EIO,
	  #endif
	  #ifdef ENOMEM
		ENOMEM,
	  #endif
	  #ifdef ENOSPC
		ENOSPC,
	  #endif
	  #ifdef ENXIO
		ENXIO,
	  #endif
	  #ifdef EPIPE
		EPIPE,
	  #endif
	  #ifdef EILSEQ
		EILSEQ,
	  #endif
	  #ifdef EOVERFLOW
		EOVERFLOW,
	  #endif
	};

mkwrap_body_errno("posix/stdio/sp/dprintf", -1)
mkwrap_variadic_bottom(dprintf, vdprintf, (fildes, format, arguments), format)


/* fscanf() -> vfscanf() */
mkwrap_init(int, vfscanf,
	(FILE *restrict stream, const char *restrict format, va_list ap),
	(FILE *restrict, const char *restrict, va_list))

mkwrap_variadic_def_and_body_called(int, fscanf, vfscanf,
	(FILE *restrict stream, const char *restrict format, ...),
	(stream, format, arguments),
	format, EOF)

	static const int valid_errnos[] = {
	  #ifdef EAGAIN
		EAGAIN,
	  #endif
	  #ifdef EBADF
		EBADF,
	  #endif
	  #ifdef EINTR
		EINTR,
	  #endif
	  #ifdef EIO
		EIO,
	  #endif
	  #ifdef ENOMEM
		ENOMEM,
	  #endif
	  #ifdef ENXIO
		ENXIO,
	  #endif
	  #ifdef EOVERFLOW
		EOVERFLOW,
	  #endif
	  #ifdef EILSEQ
		EILSEQ,
	  #endif
	  #ifdef EINVAL
		EINVAL,
	  #endif
	};

mkwrap_body_errno_ferror("posix/stdio/sp/fscanf", EOF, stream)
mkwrap_variadic_bottom(fscanf, vfscanf, (stream, format, arguments), format)


/* scanf() -> vscanf() */
mkwrap_init(int, vscanf,
	(const char *restrict format, va_list ap),
	(const char *restrict, va_list))

mkwrap_variadic_def_and_body_called(int, scanf, vscanf,
	(const char *restrict format, ...),
	(format, arguments),
	format, EOF)

	static const int valid_errnos[] = {
	  #ifdef EAGAIN
		EAGAIN,
	  #endif
	  #ifdef EBADF
		EBADF,
	  #endif
	  #ifdef EINTR
		EINTR,
	  #endif
	  #ifdef EIO
		EIO,
	  #endif
	  #ifdef ENOMEM
		ENOMEM,
	  #endif
	  #ifdef ENXIO
		ENXIO,
	  #endif
	  #ifdef EOVERFLOW
		EOVERFLOW,
	  #endif
	  #ifdef EILSEQ
		EILSEQ,
	  #endif
	};

mkwrap_body_errno_ferror("posix/stdio/sp/scanf", EOF, stdin)
mkwrap_variadic_bottom(scanf, vscanf, (format, arguments), format)
