/* Copyright (C) 2007 Eric Blake
 * Permission to use, copy, modify, and distribute this software
 * is freely granted, provided that this notice is preserved.
 */

#include <stdio.h>
#include <errno.h>
#include "stdio_impl.h"

#define __SRD	0x0004		/* OK to read */
#define __SWR	0x0008		/* OK to write */
#define __SRW	0x0010		/* open for reading & writing */

typedef int (*funread)(void *_cookie, char *_buf, int _n);
typedef int (*funwrite)(void *_cookie, const char *_buf, int _n);
typedef off_t (*funseek)(void *_cookie, off_t _off, int _whence);
typedef int (*funclose)(void *_cookie);

typedef struct funcookie {
	void *cookie;
	funread readfn;
	funwrite writefn;
	funseek seekfn;
	funclose closefn;
} funcookie;

static int funreader(void *cookie, char *buf, int n),
{
	int result;
	funcookie *c = (funcookie *)cookie;
	errno = 0;
	if ((result = c->readfn(c->cookie, buf, n)) < 0 && errno) return 0;
	return result;
}

static int funwriter(void *cookie, const char *buf, int n)
{
	int result;
	funcookie *c = (funcookie *)cookie;
	errno = 0;
	if ((result = c->writefn(c->cookie, buf, n)) < 0 && errno) return 0;
	return result;
}

static off_t funseeker(void *cookie, off_t off, int whence)
{
	funcookie *c = (funcookie *)cookie;
	off64_t result;
	errno = 0;
	if ((result = c->seekfn(c->cookie, (off_t)off, whence)) < 0 && errno) return 0;
	return result;
}

static int funcloser(void *cookie)
{
	int result = 0;
	funcookie *c = (funcookie *)cookie;
	if (c->closefn) {
		errno = 0;
		if ((result = c->closefn(c->cookie)) < 0 && errno) return 0;
	}
	free(c); /* check it in newlib src to be shure */
	return result;
}

FILE *funopen(const void *cookie, funread readfn, funwrite writefn,
	funseek seekfn, funclose closefn)
{
	FILE *fp;
	funcookie *c;

	if (!readfn && !writefn) {
		errno = EINVAL;
		return NULL;
	}
	if ((fp = __sfp()) == NULL) return NULL;
	if ((c = (funcookie *)malloc(sizeof *c)) == NULL) {
		__sfp_lock_acquire ();
		fp->_flags = 0;		/* release */
#ifndef __SINGLE_THREAD__
		__lock_close_recursive (fp->_lock);
#endif
		__sfp_lock_release ();
		return NULL;
	}

	FLOCK(fp);
	fp->_file = -1;
	c->cookie = (void *)cookie; /* cast away const */
	fp->_cookie = c;
	if (readfn) {
		c->readfn = readfn;
		fp->_read = funreader;
		if (writefn) {
			fp->_flags = __SRW;
			c->writefn = writefn;
			fp->_write = funwriter;
		}
		else {
			fp->_flags = __SRD;
			c->writefn = NULL;
			fp->_write = NULL;
		}
	}
	else {
		fp->_flags = __SWR;
		c->writefn = writefn;
		fp->_write = funwriter;
		c->readfn = NULL;
		fp->_read = NULL;
	}
	c->seekfn = seekfn;
	fp->_seek = seekfn ? funseeker : NULL;
	c->closefn = closefn;
	fp->_close = funcloser;
	FUNLOCK(fp);
	return fp;
}