/*
 * This file is part of Jehanne.
 *
 * Copyright (C) 2017 Giacomo Tesio <giacomo@tesio.it>
 *
 * This is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, version 3 of the License.
 *
 * Jehanne 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 Affero General Public License
 * along with Jehanne.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <u.h>
#include <lib9.h>
#include <9P2000.h>
#include <posix.h>
#include "internal.h"

static PosixStatReader __libposix_stat_reader;
static PosixOpenTranslator __libposix_open_translation;
static int __libposix_AT_FDCWD;
static int __libposix_F_OK;
static int __libposix_R_OK;
static int __libposix_W_OK;
static int __libposix_X_OK;

static int *__libposix_coe_fds;
static int __libposix_coe_fds_size;
static int __libposix_coe_fds_used;

typedef enum SeekTypes
{
	SeekSet		= 0,
	SeekCurrent	= 1,
	SeekEnd		= 2
} SeekTypes;
static int *__libposix_seek_types;

void
__libposix_files_check_conf(void)
{
	if(__libposix_stat_reader == nil)
		sysfatal("libposix: no stat reader");
	if(__libposix_open_translation == nil)
		sysfatal("libposix: no open translator");
	if(__libposix_seek_types == nil)
		sysfatal("libposix: no seek translations");
}

void
__libposix_set_close_on_exec(int fd, int close)
{
	int i;
	if(__libposix_coe_fds_size == __libposix_coe_fds_used){
		__libposix_coe_fds_size += 8;
		__libposix_coe_fds = realloc(__libposix_coe_fds, __libposix_coe_fds_size * sizeof(int));
		i = __libposix_coe_fds_size;
		while(i > __libposix_coe_fds_used)
			__libposix_coe_fds[--i] = -1;
	}
	/* remove fd if already present */
	i = 0;
	while(i < __libposix_coe_fds_size){
		if(__libposix_coe_fds[i] == fd){
			__libposix_coe_fds[i] = -1;
			--__libposix_coe_fds_used;
			break;
		}
		++i;
	}
	if(close){
		/* add fd to close on exec */
		i = 0;
		while(i < __libposix_coe_fds_size){
			if(__libposix_coe_fds[i] == -1){
				__libposix_coe_fds[i] = fd;
				break;
			}
			++i;
		}
		++__libposix_coe_fds_used;
	}
}

void
__libposix_close_on_exec(void)
{
	int i = 0;
	while(i < __libposix_coe_fds_size){
		if(__libposix_coe_fds[i] != -1){
			close(__libposix_coe_fds[i]);
			__libposix_coe_fds[i] = -1;
			--__libposix_coe_fds_used;
		}
		++i;
	}
	assert(__libposix_coe_fds_used == 0);
	free(__libposix_coe_fds);
	__libposix_coe_fds = nil;
	__libposix_coe_fds_size = 0;
}

int
__libposix_should_close_on_exec(int fd)
{
	int i = 0;
	while(i < __libposix_coe_fds_size){
		if(__libposix_coe_fds[i] == fd)
			return 1;
		++i;
	}
	return 0;
}

int
libposix_translate_open(PosixOpenTranslator translation)
{
	if(__libposix_initialized())
		return 0;
	if(translation == nil)
		return 0;
	__libposix_open_translation = translation;
	return 1;
}

int
libposix_set_stat_reader(PosixStatReader reader)
{
	if(__libposix_initialized())
		return 0;
	if(reader == nil)
		return 0;
	__libposix_stat_reader = reader;
	return 1;
}

int
libposix_translate_seek_whence(int seek_set, int seek_cur, int seek_end)
{
	if(__libposix_initialized())
		return 0;
	if(seek_set == seek_cur)
		return 0;
	if(seek_set == seek_end)
		return 0;
	if(seek_cur == seek_end)
		return 0;
	__libposix_seek_types = malloc(sizeof(int)*3);
	if(__libposix_seek_types == nil)
		return 0;

	__libposix_seek_types[SeekSet] = seek_set;
	__libposix_seek_types[SeekCurrent] = seek_cur;
	__libposix_seek_types[SeekEnd] = seek_end;
	return 1;
}

int
libposix_translate_access_mode(int f_ok, int r_ok, int w_ok, int x_ok)
{
	if(__libposix_initialized())
		return 0;

	__libposix_F_OK = f_ok;
	__libposix_R_OK = r_ok;
	__libposix_W_OK = w_ok;
	__libposix_X_OK = x_ok;
	return 1;
}

static SeekTypes
find_seek_type(int whence)
{
	if(__libposix_seek_types[SeekSet] == whence)
		return SeekSet;
	if(__libposix_seek_types[SeekCurrent] == whence)
		return SeekCurrent;
	if(__libposix_seek_types[SeekEnd] == whence)
		return SeekEnd;
	return -1;
}

int
POSIX_access(int *errnop, const char *path, int amode)
{
	Dir *d;
	PosixError e = 0;
	
	d = dirstat(path);
	if(path == nil || path[0] == '\0'){
		e = PosixENOENT;
	} else if(d == nil){
		e = PosixENOENT;
	} else if(amode == __libposix_F_OK){
		goto AccessDone;
	} else if(amode & ~(__libposix_R_OK|__libposix_W_OK|__libposix_X_OK)){
		e = PosixEINVAL;
	} else if((amode & __libposix_R_OK) && access(path, AREAD) != 0){
		e = PosixEACCES;
	} else if((d->mode & DMDIR) != 0){
		/* we lie, but on Plan 9 access(AWRITE) and access(AEXEC)
		 * will always fail on directories.
		 */
		goto AccessDone;
	} else if((amode & __libposix_W_OK) && access(path, AWRITE) != 0){
		e = PosixEACCES;
	} else if((amode & __libposix_X_OK) && access(path, AEXEC) != 0){
		e = PosixEACCES;
	}else{
AccessDone:
		free(d);
		return 0;
	}

	free(d);
	*errnop = __libposix_get_errno(e);
	return -1;
}

int
POSIX_dup2(int *errnop, int fildes, int fildes2)
{
	int newfd;
	if(fildes < 0 || fildes2 < 0){
		*errnop = __libposix_get_errno(PosixEBADF);
		return -1;
	}
	newfd = dup(fildes, fildes2);
	if(newfd < 0){
		*errnop = __libposix_get_errno(PosixEINTR);
		return -1;
	}
	return newfd;
}

int
POSIX_dup(int *errnop, int fildes)
{
	int newfd;
	if(fildes < 0){
		*errnop = __libposix_get_errno(PosixEBADF);
		return -1;
	}
	newfd = dup(fildes, -1);
	if(newfd < 0){
		*errnop = __libposix_get_errno(PosixEBADF);
		return -1;
	}
	return newfd;
}

int
POSIX_pipe(int *errnop, int fildes[2])
{
	int res;
	res = jehanne_pipe(fildes);
	if(res == -1)
		*errnop = __libposix_get_errno(PosixEMFILE);
	return res;
}

int
POSIX_open(int *errnop, const char *name, int flags, int mode)
{
	long omode = 0, cperm = 0;
	PosixError e;
	Dir *d;
	int f;

	if(name == nil){
		*errnop = __libposix_get_errno(PosixENOENT);
		return -1;
	}
	e = __libposix_open_translation(flags, mode, &omode, &cperm);
	if(e != 0)
		goto FailWithError;
	if(omode & DMDIR){
		d = dirstat(name);
		if(d != nil){
			if((d->mode & DMDIR) == 0)
				e = PosixENOTDIR;
			free(d);
		}
		if(e != 0)
			goto FailWithError;
		omode &= ~DMDIR;
	}
	if(cperm == 0){
		f = sys_open(name, omode);
	} else {
		f = ocreate(name, (unsigned int)omode, (unsigned int)cperm);
	}
	if(f >= 0)
		return f;
	*errnop = __libposix_translate_errstr((uintptr_t)POSIX_open);
	return -1;

FailWithError:
	*errnop = __libposix_get_errno(e);
	return -1;
}

long
POSIX_read(int *errnop, int fd, char *buf, size_t len)
{
	long r;

	if(fd < 0){
		*errnop = __libposix_get_errno(PosixEBADF);
		return -1;
	}
OnIgnoredSignalInterrupt:
	r = sys_pread(fd, buf, len, -1);
	if(r < 0){
		if(__libposix_restart_syscall())
			goto OnIgnoredSignalInterrupt;
		*errnop = __libposix_translate_errstr((uintptr_t)POSIX_read);
		return -1;
	}
	return r;
}

long
POSIX_write(int *errnop, int fd, const void *buf, size_t len)
{
	long w;

	if(fd < 0){
		*errnop = __libposix_get_errno(PosixEBADF);
		return -1;
	}
OnIgnoredSignalInterrupt:
	w = sys_pwrite(fd, buf, len, -1);
	if(w < 0){
		if(__libposix_restart_syscall())
			goto OnIgnoredSignalInterrupt;
		*errnop = __libposix_translate_errstr((uintptr_t)POSIX_write);
		return -1;
	}
	return w;
}

off_t
POSIX_lseek(int *errnop, int fd, off_t pos, int whence)
{
	SeekTypes stype;
	long r;

	stype = find_seek_type(whence);
	if(stype == -1){
		*errnop = __libposix_get_errno(PosixEINVAL);
		return -1;
	}
	if(fd < 0){
		*errnop = __libposix_get_errno(PosixEBADF);
		return -1;
	}
	r = sys_seek(fd, pos, stype);
	if(r >= 0)
		return r;
	*errnop = __libposix_translate_errstr((uintptr_t)POSIX_lseek);
	return -1;
}

long
POSIX_pread(int *errnop, int fd, char *buf, size_t len, long offset)
{
	long r;

	if(fd < 0){
		*errnop = __libposix_get_errno(PosixEBADF);
		return -1;
	}
OnIgnoredSignalInterrupt:
	r = sys_pread(fd, buf, len, offset);
	if(r < 0){
		if(__libposix_restart_syscall())
			goto OnIgnoredSignalInterrupt;
		*errnop = __libposix_translate_errstr((uintptr_t)POSIX_read);
		return -1;
	}
	return r;
}

long
POSIX_pwrite(int *errnop, int fd, const char *buf, size_t len, long offset)
{
	long w;

	if(fd < 0){
		*errnop = __libposix_get_errno(PosixEBADF);
		return -1;
	}
OnIgnoredSignalInterrupt:
	w = sys_pwrite(fd, buf, len, offset);
	if(w < 0){
		if(__libposix_restart_syscall())
			goto OnIgnoredSignalInterrupt;
		*errnop = __libposix_translate_errstr((uintptr_t)POSIX_write);
		return -1;
	}
	return w;
}

int
POSIX_close(int *errno, int file)
{
	long ret;

	ret = sys_close(file);
	switch(ret){
	case 0:
		return 0;
	case ~0:
		*errno = __libposix_translate_errstr((uintptr_t)POSIX_close);
		break;
	default:
		*errno = ret;
		break;
	}
	return -1;
}

int
POSIX_unlink(int *errnop, const char *name)
{
	long ret;
	if(name == nil || name[0] == 0){
		*errnop = __libposix_get_errno(PosixENOENT);
		return -1;
	}
	ret = sys_remove(name);
	switch(ret){
	case 0:
		return 0;
	case ~0:
		*errnop = __libposix_translate_errstr((uintptr_t)POSIX_unlink);
		break;
	default:
		*errnop = ret;
		break;
	}
	return -1;
}

int
POSIX_fstat(int *errnop, int file, void *pstat)
{
	Dir *d;
	PosixError e;
	if(pstat == nil){
		*errnop = __libposix_get_errno(PosixEOVERFLOW);
		return -1;
	}
	if(file < 0){
		*errnop = __libposix_get_errno(PosixEBADF);
		return -1;
	}
OnIgnoredSignalInterrupt:
	d = dirfstat(file);
	if(d == nil){
		if(__libposix_restart_syscall())
			goto OnIgnoredSignalInterrupt;
		*errnop = __libposix_translate_errstr((uintptr_t)POSIX_fstat);
		return -1;
	}
	e = __libposix_stat_reader(pstat, d);
	free(d);
	if(e == 0)
		return 0;
	*errnop = __libposix_get_errno(e);
	return -1;
}

int
POSIX_stat(int *errnop, const char *file, void *pstat)
{
	Dir *d;
	PosixError e;
	if(pstat == nil){
		*errnop = __libposix_get_errno(PosixEOVERFLOW);
		return -1;
	}
	if(file == nil){
		*errnop = __libposix_get_errno(PosixEBADF);
		return -1;
	}
	d = dirstat(file);
	if(d == nil){
		*errnop = __libposix_translate_errstr((uintptr_t)POSIX_stat);
		return -1;
	}
	e = __libposix_stat_reader(pstat, d);
	free(d);
	if(e == 0)
		return 0;
	*errnop = __libposix_get_errno(e);
	return -1;
}

int
POSIX_chmod(int *errnop, const char *path, int mode)
{
	Dir *dir, ndir;
	long cperm = 0;
	PosixError e;

	if(path == nil)
		e = PosixENOENT;
	else
		e = __libposix_open_translation(0, mode, nil, &cperm);
	if(e != 0)
		goto FailWithError;
	dir = dirstat(path);
	if(dir == nil){
		e = PosixENOENT;
		goto FailWithError;
	}
	nulldir(&ndir);
	ndir.mode = (dir->mode & ~(0777)) | (cperm & 0777);
	free(dir);

	if(dirwstat(path, &ndir) < 0){
		*errnop = __libposix_translate_errstr((uintptr_t)POSIX_chmod);
		return -1;
	}

	return 0;

FailWithError:
	*errnop = __libposix_get_errno(e);
	return -1;
}

int
POSIX_fchmodat(int *errnop, int fd, const char *path, long mode, int flag)
{
	Dir *dir, ndir;
	long cperm = 0;
	char fullpath[4096], *p;
	int l;
	PosixError e;

	if(fd == __libposix_AT_FDCWD){
		return POSIX_chmod(errnop, path, mode);
	} else if(path == nil){
		e = PosixENOENT;
	} else if(strlen(path) > sizeof(fullpath)-100){
		e = PosixENAMETOOLONG;
	} else if(path[0] == '/'){
		if(fd < 0)
			e = PosixEBADF;
		else {
			dir = dirfstat(fd);
			if(dir == nil){
				e = PosixEBADF;
			} else {
				if((dir->mode & DMDIR) == 0)
					e = PosixENOTDIR;
				free(dir);
			}
		}
	} else {
		e = __libposix_open_translation(0, mode, nil, &cperm);
	}
	if(e == 0 && fd2path(fd, fullpath, sizeof(fullpath)-2) != 0)
		e = PosixEBADF;
	if(e != 0)
		goto FailWithError;

	l = strlen(fullpath);
	fullpath[l++] = '/';
	p = fullpath + l;
	l = strlen(path);
	if(l > sizeof(fullpath) - (p - fullpath) - 1){
		e = PosixENAMETOOLONG;
		goto FailWithError;
	}
	strncpy(p, path, strlen(path)+1);
	cleanname(fullpath);

	dir = dirstat(path);
	if(dir == nil){
		e = PosixENOENT;
		goto FailWithError;
	}
	nulldir(&ndir);
	ndir.mode = (dir->mode & ~(0777)) | (cperm & 0777);
	free(dir);

	if(dirwstat(path, &ndir) < 0){
		*errnop = __libposix_translate_errstr((uintptr_t)POSIX_chmod);
		return -1;
	}

	return 0;

FailWithError:
	*errnop = __libposix_get_errno(e);
	return -1;
}

int
POSIX_chown(int *errnop, const char *pathname, int owner, int group)
{
	/* TODO: implement when actually needed */
	return 0;
}

int
POSIX_fchownat(int *errnop, int fd, const char *path, int owner, int group, int flag)
{
	/* TODO: implement when actually needed */
	return 0;
}

int
POSIX_chdir(int *errnop, const char *path)
{
	Dir *d = nil;
	PosixError e = 0;

	if(path == nil){
		e = PosixEFAULT;
	} else {
		d = dirstat(path);
		if(d == nil){
			e = PosixENOENT;
		} else {
			if((d->mode & DMDIR) == 0)
				e = PosixENOTDIR;
			free(d);
		}
	}
	if(e != 0)
		goto FailWithError;

	if(chdir(path) == 0)
		return 0;

	*errnop = __libposix_translate_errstr((uintptr_t)POSIX_chdir);
	return -1;

FailWithError:
	*errnop = __libposix_get_errno(e);
	return -1;
}

int
POSIX_fchdir(int *errnop, int fd)
{
	Dir *d;
	PosixError e = 0;
	char buf[4096];

	if(fd < 0){
		e = PosixEBADF;
	} else {
		d = dirfstat(fd);
		if(d == nil){
			e = PosixENOENT;
		} else {
			if((d->mode & DMDIR) == 0)
				e = PosixENOTDIR;
			free(d);
			if(fd2path(fd, buf, sizeof(buf)) != 0)
				e = PosixENOENT;	/* just removed? */
		}
	}
	if(e != 0)
		goto FailWithError;

	if(chdir(buf) == 0)
		return 0;

	*errnop = __libposix_translate_errstr((uintptr_t)POSIX_fchdir);
	return -1;

FailWithError:
	*errnop = __libposix_get_errno(e);
	return -1;
}

int
POSIX_rename(int *errnop, const char *from, const char *to)
{
	int n, ffd = -1, tfd = -1;
	const char *f, *t;
	char buf[8192];
	PosixError e = 0;
	Dir *d, nd;

	if(from == nil || to == nil){
		e = PosixENOENT;
		goto FailWithError;
	}

	f = strrchr(from, '/');
	t = strrchr(to, '/');
	f = f ? f+1 : from;
	t = t ? t+1 : to;

	if(strcmp(".", f) == 0 || strcmp("..", f) == 0
	|| strcmp(".", t) == 0 || strcmp("..", t) == 0){
		e = PosixEINVAL;
		goto FailWithError;
	}
	if(access(to, AEXIST) == 0){
		if(remove(to) < 0){
			e = PosixEACCES;
			goto FailWithError;
		}
	}
	if((d = dirstat(from)) == nil){
		e = PosixENOENT;
	} else if(f-from == t-to && strncmp(from, to, f-from) == 0){
		/* from and to are in same directory (we miss some cases) */
		nulldir(&nd);
		nd.name = (char*)t;
		if(dirwstat(from, &nd) < 0)
			e = PosixEPERM;
	} else {
		/* different directories: with 9P2000 we have to copy */
		if((ffd = open(from, OREAD)) < 0
		|| (tfd = create(to, OWRITE, d->mode)) < 0){
			e = PosixEPERM;
		}
		while(e == 0 && (n = read(ffd, buf, sizeof(buf))) > 0)
			if(write(tfd, buf, n) != n)
				e = PosixEIO;
		if(ffd >= 0)
			close(ffd);
		if(tfd >= 0)
			close(tfd);
		if(e == 0 && remove(from) < 0)
			e = PosixEIO;
		if(e == PosixEIO && tfd >= 0)
			remove(to);
	}
	if(e != 0)
		goto FailWithError;
	free(d);
	return 0;

FailWithError:
	if(d != nil)
		free(d);
	*errnop = __libposix_get_errno(e);
	return -1;
}

int
POSIX_mkdir(int *errnop, const char *path, int mode)
{
	long cperm = 0;
	PosixError e = 0;
	int fd;

	if(path == nil)
		e = PosixEFAULT;
	else if(access(path, AEXIST) == 0)
		e = PosixEEXIST;
	else
		e = __libposix_open_translation(0, mode, nil, &cperm);
	if(e != 0)
		goto FailWithError;

	fd = create(path, OREAD, DMDIR | cperm);
	if(fd >= 0){
		close(fd);
		return 0;
	}

	*errnop = __libposix_translate_errstr((uintptr_t)POSIX_mkdir);
	return -1;

FailWithError:
	*errnop = __libposix_get_errno(e);
	return -1;
}


/* getdents is not POSIX, but is used in POSIX C library to implement
 * readdir.
 */
int
libposix_getdents(int *errnop, int fd, char *buf, int buf_bytes)
{
#define MINSTATLEN	(STATFIXLEN + 4) /* 4 = min (1 char) name, uid, gid, muid */

	int r;
	Dir *d;
	struct dirent *dp;
	char *p;

	if(fd < 0)
		goto FailWithEBADF;
	if(buf == nil)
		goto FailWithEFAULT;
	if(buf_bytes < MINSTATLEN)
		goto FailWithEINVAL;

	d = dirfstat(fd);
	if(d == nil)
		goto FailWithENOENT;	/* removed? */
	r = d->mode & DMDIR;
	free(d);

	if(r == 0)
		goto FailWithENOTDIR;	/* not a directory */

	r = read(fd, buf, buf_bytes);
	if(r == 0)
		return 0;
	if(r < 0)
		goto FailWithENOENT;	/* removed? */
	if(r < MINSTATLEN)
		goto FailWithEIO;

	p = buf;
	while(p - buf < r){
		/* here we adjust 9P2000 stat info to match
		 * dirent specification
		 */
		dp = (struct dirent *)p;

		/* the count field in stat structure exclude itself */
		dp->d_reclen += BIT16SZ;

		/* 9P2000 strings are not NUL-terminated */
		dp->d_name[dp->d_namlen] = '\0';

		/* we have to map types to UNIX */
		if(dp->d_type & (DMDIR >> 24))
			dp->d_type = DT_DIR;
		else if(dp->__pad1__ == '|')	/* kernel device == devpipe */
			dp->d_type = DT_FIFO;
		else
			dp->d_type = DT_REG;	/* UNIX lack fantasy :-) */

		p += dp->d_reclen;
	}

	return r;

FailWithEBADF:
	*errnop = __libposix_get_errno(PosixEBADF);
	return -1;

FailWithEFAULT:
	*errnop = __libposix_get_errno(PosixEFAULT);
	return -1;

FailWithEINVAL:
	*errnop = __libposix_get_errno(PosixEINVAL);
	return -1;

FailWithENOENT:
	*errnop = __libposix_get_errno(PosixENOENT);
	return -1;

FailWithENOTDIR:
	*errnop = __libposix_get_errno(PosixENOTDIR);
	return -1;

FailWithEIO:
	*errnop = __libposix_get_errno(PosixEIO);
	return -1;
}

int
libposix_define_at_fdcwd(int AT_FDCWD)
{
	if(__libposix_AT_FDCWD != 0)
		return -1;
	__libposix_AT_FDCWD = AT_FDCWD;
	return 0;
}
