#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <assert.h>
#include <errno.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <unistd.h>

#if 0
#define DPRINTF(a)	printf a
#else
#define DPRINTF(a)
#endif

typedef int		l_int;
typedef int32_t		l_long;
typedef int64_t		l_longlong;
typedef unsigned int	l_uint;
typedef uint32_t	l_ulong;
typedef uint64_t	l_ulonglong;
typedef unsigned short	l_ushort;

struct l_rlimit {
	l_ulong rlim_cur;
	l_ulong rlim_max;
} __packed;

struct l_timespec {
	l_ulong		tv_sec;
	l_ulong		tv_nsec;
};

struct l_stat {
	l_uint	st_dev;
	l_uint		st_ino;
	l_uint		st_mode;
	l_uint		st_nlink;
	l_uint		st_uid;
	l_uint		st_gid;
	l_uint		st_rdev;
	l_long		st_size;
	struct l_timespec	st_atimespec;
	struct l_timespec	st_mtimespec;
	struct l_timespec	st_ctimespec;
	l_uint		st_blksize;
	l_int		st_blocks;
	l_uint		st_flags;
	l_uint		st_gen;
};

struct l_stat64 {
	l_ushort	st_dev;
	u_char		__pad0[10];
	l_ulong		__st_ino;
	l_uint		st_mode;
	l_uint		st_nlink;
	l_ulong		st_uid;
	l_ulong		st_gid;
	l_ushort	st_rdev;
	u_char		__pad3[10];
	l_longlong	st_size;
	l_ulong		st_blksize;
	l_ulong		st_blocks;
	l_ulong		__pad4;
	struct l_timespec	st_atimespec;
	struct l_timespec	st_mtimespec;
	struct l_timespec	st_ctimespec;
	l_ulonglong	st_ino;
};

char *__strtok_r(char *str, const char *sep, char **last);
void __bzero(void *s, size_t n);

int *__errno_location(void);
int *__h_errno_location(void);

int __sigsetjmp(sigjmp_buf env, int savesigs);

long __strtol_internal(
    const char *nptr, char **endptr, int base, int group __unused);
unsigned long
__strtoul_internal(
    const char *nptr, char **endptr, int base, int group __unused);

int open64(const char *path, int l_flags, ...);
off_t lseek64(int fd, off_t offset, int whence);
ssize_t pread64(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite64(int fd, void *buf, size_t count, off_t offset);
int stat64(const char *path, struct l_stat64 *l_sb64);
int __l_lseek(int fd, long offset, int whence);
int __xstat(int ver, const char *path, struct l_stat *l_sb);
int __fxstat(int ver, int fd, struct l_stat *l_sb);

FILE *fopen64(const char *path, const char *mode);
int fseeko64(FILE *fp, off_t offset, int whence);
off_t ftello64(FILE *fp);
int _IO_getc(FILE *fp);

int getrlimit64(int l_resource, struct l_rlimit *l_rlim);
int get_nprocs(void);

#if __FreeBSD_version < 601103
int gethostbyname_r(
    const char *name, struct hostent *ret, char *buf, size_t buflen,
    struct hostent **result, int *h_errnop);
#endif

static void bsd_stat_to_linux(struct stat *sb, struct l_stat *l_sb);
static void bsd_stat_to_linux64(struct stat *sb, struct l_stat64 *l_sb64);

int __l_sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

struct l_sockaddr {
	u_int16_t sa_family;
	char sa_data[14];
};

int __l_connect(int s, const struct l_sockaddr *l_sa, socklen_t addrlen);

#ifdef stdin
#undef stdin
#undef stdout
#undef stderr

FILE *stdin;
FILE *stdout;
FILE *stderr;
#endif

static void __attribute__((constructor))
oci8stub_init(void)
{
	DPRINTF(("%s\n", __FUNCTION__));

#ifdef stdin
	stdin = __stdinp;
	stdout = __stdoutp;
	stderr = __stderrp;
#endif
}

char *
__strtok_r(char *str, const char *sep, char **last)
{
	return strtok_r(str, sep, last);
}
	
void
__bzero(void *s, size_t n)
{
	bzero(s, n);
}

int *
__errno_location(void)
{
	return &errno;
}

int *
__h_errno_location(void)
{
	return &h_errno;
}

int
__sigsetjmp(sigjmp_buf env, int savesigs)
{
	return sigsetjmp(env, savesigs);
}

long
__strtol_internal(
    const char *nptr, char **endptr, int base, int group __unused)
{
	return strtol(nptr, endptr, base);
}

unsigned long
__strtoul_internal(
    const char *nptr, char **endptr, int base, int group __unused)
{
	return strtoul(nptr, endptr, base);
}

/*
 * open/fcntl flags
 */
#define LINUX_O_RDONLY          00
#define LINUX_O_WRONLY          01
#define LINUX_O_RDWR            02
#define LINUX_O_CREAT           0100
#define LINUX_O_EXCL            0200
#define LINUX_O_NOCTTY          0400
#define LINUX_O_TRUNC           01000
#define LINUX_O_APPEND          02000
#define LINUX_O_NONBLOCK        04000
#define LINUX_O_NDELAY          LINUX_O_NONBLOCK
#define LINUX_O_SYNC            010000
#define LINUX_FASYNC            020000

int
open64(const char *path, int l_flags, ...)
{
	int fd;
	int bsd_flags;
	mode_t mode = 0;	// whatever
	va_list ap;

	bsd_flags = 0;
	if (l_flags & LINUX_O_RDONLY)
		bsd_flags |= O_RDONLY;
	if (l_flags & LINUX_O_WRONLY)
		bsd_flags |= O_WRONLY;
	if (l_flags & LINUX_O_RDWR)
		bsd_flags |= O_RDWR;
	if (l_flags & LINUX_O_NDELAY)
		bsd_flags |= O_NONBLOCK;
	if (l_flags & LINUX_O_APPEND)
		bsd_flags |= O_APPEND;
	if (l_flags & LINUX_O_SYNC)
		bsd_flags |= O_FSYNC;
	if (l_flags & LINUX_O_NONBLOCK)
		bsd_flags |= O_NONBLOCK;
	if (l_flags & LINUX_FASYNC)
		bsd_flags |= O_ASYNC;
	if (l_flags & LINUX_O_CREAT)
		bsd_flags |= O_CREAT;
	if (l_flags & LINUX_O_TRUNC)
		bsd_flags |= O_TRUNC;
	if (l_flags & LINUX_O_EXCL)
		bsd_flags |= O_EXCL;
	if (l_flags & LINUX_O_NOCTTY)
		bsd_flags |= O_NOCTTY;

	va_start(ap, l_flags);
	if (bsd_flags & O_CREAT)
		mode = va_arg(ap, int);
	fd = open(path, bsd_flags, mode);
	va_end(ap);

	return fd;
}

off_t
lseek64(int fd, off_t offset, int whence)
{
	return lseek(fd, offset, whence);
}

ssize_t
pread64(int fd, void *buf, size_t count, off_t offset)
{
	return pread(fd, buf, count, offset);
}

ssize_t
pwrite64(int fd, void *buf, size_t count, off_t offset)
{
	return pwrite(fd, buf, count, offset);
}

int
__l_lseek(int fd, long offset, int whence)
{
	return lseek(fd, offset, whence);
}

int
stat64(const char *path, struct l_stat64 *l_sb64)
{
	int rc;
	struct stat bsd_sb;

	rc = stat(path, &bsd_sb);
	if (rc < 0)
		return -1;

	bsd_stat_to_linux64(&bsd_sb, l_sb64);
	return rc;
}

int
__xstat(int ver __unused, const char *path, struct l_stat *l_sb)
{
	int rc;
	struct stat bsd_sb;

	rc = stat(path, &bsd_sb);
	if (rc < 0)
		return -1;

	bsd_stat_to_linux(&bsd_sb, l_sb);
	return rc;
}

int
__fxstat(int ver __unused, int fd, struct l_stat *l_sb)
{
	int rc;
	struct stat bsd_sb;

	rc = fstat(fd, &bsd_sb);
	if (rc < 0)
		return -1;

	bsd_stat_to_linux(&bsd_sb, l_sb);
	return rc;
}

FILE *
fopen64(const char *path, const char *mode)
{
	return fopen(path, mode);
}

int
fseeko64(FILE *fp, off_t offset, int whence)
{
	return fseeko(fp, offset, whence);
}

off_t
ftello64(FILE *fp)
{
	return ftello(fp);
}

int
_IO_getc(FILE *fp)
{
	return getc(fp);
}

#define LINUX_RLIM_NLIMITS	10

static unsigned int linux_to_bsd_resource[LINUX_RLIM_NLIMITS] = {
	RLIMIT_CPU, RLIMIT_FSIZE, RLIMIT_DATA, RLIMIT_STACK,
	RLIMIT_CORE, RLIMIT_RSS, RLIMIT_NPROC, RLIMIT_NOFILE,
	RLIMIT_MEMLOCK, -1
};

int
getrlimit64(int l_resource, struct l_rlimit *l_rlim)
{
	int rc;
	struct rlimit bsd_rlim;
	int bsd_resource;

	if (l_resource < 0 || l_resource >= LINUX_RLIM_NLIMITS)
		return (EINVAL);

	bsd_resource = linux_to_bsd_resource[l_resource];
	if (bsd_resource == -1)
		return (EINVAL);

	rc = getrlimit(bsd_resource, &bsd_rlim);
	if (rc < 0)
		return rc;

	l_rlim->rlim_cur = (l_ulong)bsd_rlim.rlim_cur;
	l_rlim->rlim_max = (l_ulong)bsd_rlim.rlim_max;
	return rc;
}

int get_nprocs(void)
{
	return 1;	/* XXX */
}

#if __FreeBSD_version < 601103
int gethostbyname_r(
    const char *name, struct hostent *ret,
    char *buf __unused, size_t buflen __unused,
    struct hostent **result, int *h_errnop)
{
	/*
	 * XXX modifies thread-local h_errno and is not thread-safe
	 * on FreeBSD < 5.5
	 */
	struct hostent *h = gethostbyname(name);
	if (h != NULL) {
		memcpy(ret, h, sizeof(*ret));
		*result = ret;
	} else {
		*result = NULL;
	}
	*h_errnop = h_errno;
	return h != NULL ? 0 : -1;
}
#endif

/*--------------------------------------------------------------------
 * static functions
 */

static int
uminor(dev_t dev)
{
	        return (dev & 0xffff00ff);
}

static int
umajor(dev_t dev)
{
	        return ((dev & 0xff00) >> 8);
}

static void
bsd_stat_to_linux(struct stat *sb, struct l_stat *l_sb)
{
#if 0
	struct cdevsw *cdevsw;
	struct cdev *dev;
#endif

	bzero(l_sb, sizeof(*l_sb));
	l_sb->st_dev = uminor(sb->st_dev) | (umajor(sb->st_dev) << 8);
	l_sb->st_ino = sb->st_ino;
	l_sb->st_mode = sb->st_mode;
	l_sb->st_nlink = sb->st_nlink;
	l_sb->st_uid = sb->st_uid;
	l_sb->st_gid = sb->st_gid;
	l_sb->st_rdev = sb->st_rdev;
	l_sb->st_size = sb->st_size;
	l_sb->st_atime = sb->st_atime;
	l_sb->st_mtime = sb->st_mtime;
	l_sb->st_ctime = sb->st_ctime;
	l_sb->st_blksize = sb->st_blksize;
	l_sb->st_blocks = sb->st_blocks;

#if 0
	/* Lie about disk drives which are character devices
	 * in FreeBSD but block devices under Linux.
	 */
	if (S_ISCHR(l_sb->st_mode) &&
	    (dev = findcdev(sb->st_rdev)) != NULL) {
		cdevsw = devsw(dev);
		if (cdevsw != NULL && (cdevsw->d_flags & D_DISK)) {
			l_sb->st_mode &= ~S_IFMT;
			l_sb->st_mode |= S_IFBLK;

			/* XXX this may not be quite right */
			/* Map major number to 0 */
			l_sb->st_dev = uminor(sb->st_dev) & 0xf;
			l_sb->st_rdev = sb->st_rdev & 0xff;
		}
	}
#endif
}

static void
bsd_stat_to_linux64(struct stat *sb, struct l_stat64 *l_sb64)
{
#if 0
	struct cdevsw *cdevsw;
	struct cdev *dev;
#endif

	bzero(l_sb64, sizeof(*l_sb64));
	l_sb64->st_dev = uminor(sb->st_dev) | (umajor(sb->st_dev) << 8);
	l_sb64->st_ino = sb->st_ino;
	l_sb64->st_mode = sb->st_mode;
	l_sb64->st_nlink = sb->st_nlink;
	l_sb64->st_uid = sb->st_uid;
	l_sb64->st_gid = sb->st_gid;
	l_sb64->st_rdev = sb->st_rdev;
	l_sb64->st_size = sb->st_size;
	l_sb64->st_atime = sb->st_atime;
	l_sb64->st_mtime = sb->st_mtime;
	l_sb64->st_ctime = sb->st_ctime;
	l_sb64->st_blksize = sb->st_blksize;
	l_sb64->st_blocks = sb->st_blocks;

#if 0
	/* Lie about disk drives which are character devices
	 * in FreeBSD but block devices under Linux.
	 */
	if (S_ISCHR(l_sb64->st_mode) &&
	    (dev = findcdev(sb->st_rdev)) != NULL) {
		cdevsw = devsw(dev);
		if (cdevsw != NULL && (cdevsw->d_flags & D_DISK)) {
			l_sb64->st_mode &= ~S_IFMT;
			l_sb64->st_mode |= S_IFBLK;

			/* XXX this may not be quite right */
			/* Map major number to 0 */
			l_sb64->st_dev = uminor(sb->st_dev) & 0xf;
			l_sb64->st_rdev = sb->st_rdev & 0xff;
		}
	}
#endif

	/*
	 * The __st_ino field makes all the difference. In the Linux kernel
	 * it is conditionally compiled based on STAT64_HAS_BROKEN_ST_INO,
	 * but without the assignment to __st_ino the runtime linker refuses
	 * to mmap(2) any shared libraries. I guess it's broken alright :-)
	 */
	l_sb64->__st_ino = sb->st_ino;
}

/* linux sigprocmask actions */
#define LINUX_SIG_BLOCK		0
#define LINUX_SIG_UNBLOCK	1
#define LINUX_SIG_SETMASK	2

int
__l_sigprocmask(int l_how, const sigset_t *set, sigset_t *oldset)
{
	int how;

	switch (l_how) {
	case LINUX_SIG_BLOCK:
		how = SIG_BLOCK;
		break;
	case LINUX_SIG_UNBLOCK:
		how = SIG_UNBLOCK;
		break;
	case LINUX_SIG_SETMASK:
		how = SIG_SETMASK;
		break;
	default:
		return EINVAL;
	}

	return sigprocmask(how, set, oldset);
}

int
__l_connect(int s, const struct l_sockaddr *l_sa, socklen_t addrlen)
{
	struct sockaddr sa;

	memcpy(sa.sa_data, l_sa->sa_data, sizeof(sa.sa_data));
	sa.sa_len = addrlen;
	sa.sa_family = l_sa->sa_family;
	return connect(s, &sa, addrlen);
}
