/* 
   Unix SMB/Netbios implementation.
   Version 2.0
   SMB wrapper functions for calls that syscall() can't do
   Copyright (C) Andrew Tridgell 1998
   
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "includes.h"
#include "realcalls.h"

#ifdef REPLACE_DIRENT
#ifndef DIRBLKSIZ
#define DIRBLKSIZ	DEV_BSIZE
#endif
#ifdef HAVE_GLIBC
struct __dirstream {
	int dd_fd;
	char *dd_buf;
	size_t dd_len;
	size_t dd_size;
	size_t dd_loc;
};
#define HAVE_DIR_DD_LEN 1
typedef struct {
	long d_ino;
#ifdef HAVE_DIRENT_D_OFF
	off_t d_off;
#endif
	unsigned short d_reclen;
	char d_name[1];
} kernel_dirent;
#else
typedef struct dirent kernel_dirent;
#endif
#endif

#if HAVE___XSTAT
#ifndef _STAT_VER
#define _STAT_VER	0
#endif
#endif

#if defined(REPLACE_DIRENT) || defined(REPLACE_GETCWD)
#if HAVE___XSTAT
static int real_xstat(const char *name, struct stat *st)
{
	double xx[32];
	int ret;

	ret = real_stat(name, xx);
	xstat_convert(_STAT_VER, xx, st);
	return ret;
}
typedef struct stat struct_stat;
#elif HAVE_STAT64
#define real_xstat real_stat64
typedef struct stat64 struct_stat;
#else
#define real_xstat real_stat
typedef struct stat struct_stat;
#endif
#endif

#ifdef REPLACE_GETCWD
#if HAVE___LXSTAT
static int real_lxstat(const char *name, struct stat *st)
{
	double xx[32];
	int ret;

	ret = real_lstat(name, xx);
	xstat_convert(_STAT_VER, xx, st);
	return ret;
}
typedef struct stat struct_lstat;
#elif HAVE_LSTAT64
#define real_lxstat real_lstat64
typedef struct stat64 struct_lstat;
#else
#define real_lxstat real_lstat
typedef struct stat struct_lstat;
#endif
#ifdef NO_OPENDIR_WRAPPER
#define real_opendir	opendir
#endif
#ifdef NO_READDIR_WRAPPER
#define real_readdir	readdir
#endif
#ifdef NO_CLOSEDIR_WRAPPER
#define real_closedir	closedir
#endif
#define PARENTDIR	".."
#define isdotdir(s)	((s)[0] == '.' \
			&& (!(s)[1] || ((s)[1] == '.' && !(s)[2])))
#endif

int real_fcntl(int fd, int cmd, void *arg)
{
#ifdef HAVE_STRUCT_FLOCK64
	struct flock64 *lock64;
	struct flock lock;
	int ret;

#if defined(LINUX) && defined(SYS_fcntl64)
	ret = syscall(SYS_fcntl64, fd, cmd, arg);
	if (ret >= 0 || errno != ENOSYS) {
		return ret;
	}
#endif

	switch (cmd) {
#if defined(F_GETLK) && defined(F_GETLK64)
	case F_GETLK64:
		lock64 = (struct flock64 *)arg;
		cmd = F_GETLK;
		break;
#endif
#if defined(F_SETLK) && defined(F_SETLK64)
	case F_SETLK64:
		lock64 = (struct flock64 *)arg;
		cmd = F_SETLK;
		break;
#endif
#if defined(F_SETLKW) && defined(F_SETLKW64)
	case F_SETLKW64:
		lock64 = (struct flock64 *)arg;
		cmd = F_SETLKW;
		break;
#endif
	default:
		lock64 = NULL;
		break;
	}

	if (lock64) {
		lock.l_type = lock64->l_type;
		lock.l_whence = lock64->l_whence;
		lock.l_start = lock64->l_start;
		lock.l_len = lock64->l_len;
		lock.l_pid = lock64->l_pid;
		ret = syscall(SYS_fcntl, fd, cmd, &lock);
		if (ret >= 0) {
			lock64->l_type = lock.l_type;
			lock64->l_whence = lock.l_whence;
			lock64->l_start = lock.l_start;
			lock64->l_len = lock.l_len;
			lock64->l_pid = lock.l_pid;
		}
		return ret;
	}
#endif
	return syscall(SYS_fcntl, fd, cmd, arg);
}

#ifdef REPLACE_UTIME
int real_utime(const char *name, struct utimbuf *buf)
{
	struct timeval tv[2];
	
	tv[0].tv_sec = buf->actime;
	tv[0].tv_usec = 0;
	tv[1].tv_sec = buf->modtime;
	tv[1].tv_usec = 0;
	
	return real_utimes(name, &tv[0]);
}
#endif

#ifdef REPLACE_UTIMES
int real_utimes(const char *name, struct timeval tv[2])
{
	struct utimbuf buf;

	buf.actime = tv[0].tv_sec;
	buf.modtime = tv[1].tv_sec;
	
	return real_utime(name, &buf);
}
#endif

#ifdef HAVE_STAT64
#ifdef HAVE_STRUCT_STAT64
#ifdef REPLACE_STAT64
 int real_stat64(const char *name, struct stat64 *st64)
{
	double xx[32];
	int ret;

	ret = real_stat(name, xx);
#if HAVE___XSTAT
	xstat64_convert(_STAT_VER, xx, st64);
#else
	stat64_convert(xx, st64);
#endif
	return ret;
}
#endif

#ifdef REPLACE_FSTAT64
 int real_fstat64(int fd, struct stat64 *st64)
{
	double xx[32];
	int ret;

	ret = real_fstat(fd, xx);
#if HAVE___XSTAT
	xstat64_convert(_STAT_VER, xx, st64);
#else
	stat64_convert(xx, st64);
#endif
	return ret;
}
#endif

#ifdef REPLACE_LSTAT64
 int real_lstat64(const char *name, struct stat64 *st64)
{
	double xx[32];
	int ret;

	ret = real_lstat(name, xx);
#if HAVE___XSTAT
	xstat64_convert(_STAT_VER, xx, st64);
#else
	stat64_convert(xx, st64);
#endif
	return ret;
}
#endif
#endif
#endif

#ifdef REPLACE_DIRENT
#if defined(HAVE_GLIBC) || defined(HAVE_POSIX_READDIR_R) || defined(HAVE_OLD_READDIR_R)
static void kernel_dirent_convert(kernel_dirent *kd, struct dirent *d)
{
	d->d_ino = kd->d_ino;
#ifdef HAVE_DIRENT_D_OFF
	d->d_off = kd->d_off;
#endif
#ifdef HAVE_DIRENT_D_RECLEN
	d->d_reclen = kd->d_reclen;
#endif
#ifdef HAVE_DIRENT_D_NAMLEN
	d->d_namlen = strlen(kd->d_name);
#endif
#ifdef HAVE_DIRENT_D_TYPE
	d->d_type = DT_UNKNOWN;
#endif
	pstrcpy(d->d_name, kd->d_name);
}
#endif

#if defined(HAVE_READDIR64) && defined(HAVE_STRUCT_DIRENT64)
static void kernel_dirent64_convert(kernel_dirent *kd, struct dirent64 *d64)
{
	d64->d_ino = kd->d_ino;
#ifdef HAVE_DIRENT_D_OFF
	d64->d_off = kd->d_off;
#endif
#ifdef HAVE_DIRENT_D_RECLEN
	d64->d_reclen = kd->d_reclen;
#endif
#ifdef HAVE_DIRENT_D_NAMLEN
	d64->d_namlen = strlen(kd->d_name);
#endif
#ifdef HAVE_DIRENT_D_TYPE
	d64->d_type = DT_UNKNOWN;
#endif
	pstrcpy(d64->d_name, kd->d_name);
}
#endif

static kernel_dirent *kernel_readdir(DIR *dirp)
{
	kernel_dirent *dp;
	int len;

	for (;;) {
		if (dirp->dd_loc >= dirp->dd_size) return NULL;
		dp = (kernel_dirent *)(dirp->dd_buf + dirp->dd_loc);
#ifdef HAVE_DIRENT_D_RECLEN
		len = dp->d_reclen;
		if (len <= 0) return NULL;
#else
		len = sizeof(kernel_dirent);
#endif
#ifdef HAVE_DIR_DD_LEN
		if (dirp->dd_loc + len > dirp->dd_len) return NULL;
#endif
		dirp->dd_loc += len;
		if (dp->d_ino > 0) break;
	}

	return dp;
}

DIR *real_opendir(const char *fname)
{
	DIR *dirp;
#ifdef real_getdirentries
	long base;
#endif
	struct_stat st;
	char *buf, *new;
	int n, fd, size, total;

	if (real_xstat(fname, &st) < 0) {
		return NULL;
	}
	if (!S_ISDIR(st.st_mode)) {
		errno = ENOTDIR;
		return NULL;
	}

	if ((fd = real_open(fname, O_NONBLOCK | O_RDONLY, 0644)) < 0) {
		return NULL;
	}
	if (!(dirp = (DIR *)malloc(sizeof(DIR)))) {
		real_close(fd);
		return NULL;
	}

	size = total = 0;
	buf = NULL;
	do {
		if (size - total < DIRBLKSIZ) {
			size += DIRBLKSIZ;
			new = (char *)Realloc(buf, size);
			if (!new) {
				SAFE_FREE(buf);
				SAFE_FREE(dirp);
				real_close(fd);
				return NULL;
			}
			buf = new;
		}

#ifdef real_getdirentries
		n = real_getdirentries(fd, buf + total, size - total, &base);
#elif defined(real_getdents)
		n = real_getdents(fd, buf + total, size - total);
#else
		n = real_read(fd, buf + total, size - total);
#endif

		if (n < 0) {
			SAFE_FREE(buf);
			SAFE_FREE(dirp);
			real_close(fd);
			return NULL;
		}
		total += n;
	} while (n > 0);

	dirp->dd_buf = buf;
#ifdef HAVE_DIR_DD_LEN
	dirp->dd_len = size;
#endif
	dirp->dd_size = total;
	dirp->dd_loc = 0;
	dirp->dd_fd = fd;

	return dirp;
}

struct dirent *real_readdir(DIR *dirp)
{
#ifdef HAVE_GLIBC
	static struct dirent dent;
#endif
	kernel_dirent *dp;

	dp = kernel_readdir(dirp);
	if (!dp) return NULL;
#ifdef HAVE_GLIBC
	kernel_dirent_convert(dp, &dent);
	return &dent;
#else
	return dp;
#endif
}

#if defined(HAVE_POSIX_READDIR_R)
int real_posix_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)
{
	kernel_dirent *dp;

	dp = kernel_readdir(dirp);
	if (!dp) {
		*result = NULL;
		return 0;
	}
	kernel_dirent_convert(dp, entry);

	*result = entry;
	return 0;
}
#elif defined(HAVE_OLD_READDIR_R)
struct dirent real_old_readdir_r(DIR *dirp, struct dirent *entry)
{
	kernel_dirent *dp;

	dp = kernel_readdir(dirp);
	if (!dp) return NULL;
	kernel_dirent_convert(dp, entry);

	return entry;
}
#endif

#if defined(HAVE_READDIR64) && defined(HAVE_STRUCT_DIRENT64)
 struct dirent64 *real_readdir64(DIR *dirp)
{
	static struct dirent64 dent;
	kernel_dirent *dp;

	dp = kernel_readdir(dirp);
	if (!dp) return NULL;
	kernel_dirent64_convert(dp, &dent);

	return &dent;
}

#if defined(HAVE_POSIX_READDIR_R)
 int real_posix_readdir64_r(DIR *dirp, struct dirent64 *entry, struct dirent64 **result)
{
	kernel_dirent *dp;

	dp = kernel_readdir(dirp);
	if (!dp) {
		*result = NULL;
		return 0;
	}
	kernel_dirent64_convert(dp, entry);

	*result = entry;
	return 0;
}
#elif defined(HAVE_OLD_READDIR_R)
 struct dirent64 real_old_readdir64_r(DIR *dirp, struct dirent64 *entry)
{
	kernel_dirent *dp;

	dp = kernel_readdir(dirp);
	if (!dp) return NULL;
	kernel_dirent64_convert(dp, entry);

	return entry;
}
#endif
#endif

int real_closedir(DIR *dirp)
{
	int ret;

	ret = real_close(dirp->dd_fd);
	SAFE_FREE(dirp->dd_buf);
	SAFE_FREE(dirp);

	return ret;
}

void real_seekdir(DIR *dirp, off_t offset)
{
	if (offset >= 0 && offset < dirp->dd_size) dirp->dd_loc = offset;
}

off_t real_telldir(DIR *dirp)
{
	return dirp->dd_loc;
}
#endif

#ifdef REPLACE_GETCWD
char *real_getcwd(char *buf, size_t size)
{
	DIR *dirp;
	struct dirent *dp;
	struct_stat root;
	struct_lstat st, tmp, prev;
	char *cp, *cp2, path[MAXPATHLEN], cwd[MAXPATHLEN];
	int len;

	if (size <= 0) {
		errno = EINVAL;
		return NULL;
	}
	if (real_xstat("/", &root) < 0) {
		return NULL;
	}

	cp = path;
	cp2 = cwd;
	*cp2++ = '/';
	*cp2 = '\0';
	if (real_lxstat(".", &st) < 0) {
		return NULL;
	}

	for (;;) {
		if (st.st_dev == root.st_dev && st.st_ino == root.st_ino) {
			break;
		}
		if (cp + sizeof(PARENTDIR) >= path + MAXPATHLEN) {
			errno = ERANGE;
			return NULL;
		}
		memcpy(cp, PARENTDIR, sizeof(PARENTDIR));
		cp += sizeof(PARENTDIR) - 1;

		memcpy(&prev, &st, sizeof(prev));
		if (!(dirp = real_opendir(path))
		    || real_lxstat(path, &st) < 0) {
			return NULL;
		}

		*cp++ = '/';
		*cp = '\0';
		while ((dp = real_readdir(dirp))) {
			if (isdotdir(dp->d_name)) {
				continue;
			}
			len = strlen(dp->d_name);
			if (st.st_dev == prev.st_dev) {
				if (dp->d_ino == prev.st_ino) {
					break;
				}
			} else {
				if (cp + len >= path + MAXPATHLEN) {
					continue;
				}
				memcpy(cp, dp->d_name, len + 1);
				if (real_lxstat(path, &tmp) < 0) {
					continue;
				}
				if (tmp.st_dev == prev.st_dev &&
				    tmp.st_ino == prev.st_ino) {
					break;
				}
			}
		}
		real_closedir(dirp);
		if (!dp) {
			errno = ENOENT;
			return NULL;
		}

		if (cp2 + len + 1 >= cwd + MAXPATHLEN) {
			errno = ERANGE;
			return NULL;
		}
		if (cp2 <= cwd + 1) {
			cwd[len + 1] = '\0';
		} else {
			memmove(cwd + len + 1, cwd, ++cp2 - cwd);
		}
		memcpy(cwd + 1, dp->d_name, len);
		cp2 += len;
	}

	len = strlen(cwd) + 1;
	if (!buf) {
		buf = (char *)malloc(len);
		if (!buf) {
			return NULL;
		}
	} else if (len > size) {
		errno = ERANGE;
		return NULL;
	}
	memcpy(buf, cwd, len);

	return buf;
}
#endif
