/* UNFSD - copyright Mark A Shand, May 1988.
 * This software maybe be used for any purpose provided
 * the above copyright notice is retained.  It is supplied
 * as is, with no warranty expressed or implied.
 */
#include <errno.h>
#include "unfsd.h"
#include "tmp/libpq-fs.h"
#include "catalog/pg_lobj.h"

static struct timeval TIMEOUT = { 25, 0 };
extern int p_errno;
extern void ibegin();
extern void icommit();

int smgr;			/* storage manager # for inversion */

/* ====================================================================== */
#ifdef DEBUG
FILE *debuglog = NULL;
static char *pname = "unfsd";
static char argbuf[1024];

logcall(name, arg, rqstp)
char	*name;
char	*arg;
struct svc_req	*rqstp;
{
	int	i;

	if (debuglog == NULL)
	{
		unsigned long tloc;
		debuglog = stderr;
#if 0
		if ((debuglog = fopen("/tmp/unfsd.log", "w")) == NULL)
			return;
#endif
		setlinebuf(debuglog);
		time(&tloc);
		fprintf(debuglog, "\n\nstarting %s at %s\n", pname, ctime(&tloc));
	}
	fprintf(debuglog, "%s [%d ", name, rqstp->rq_cred.oa_flavor);
	if (rqstp->rq_cred.oa_flavor == AUTH_UNIX)
	{
		struct authunix_parms *unix_cred;
		struct tm *tm;
		unix_cred = (struct authunix_parms *) rqstp->rq_clntcred;
		tm = localtime(&unix_cred->aup_time);
		fprintf(debuglog, "%d/%d/%d %02d:%02d:%02d %s %d.%d",
			tm->tm_year, tm->tm_mon+1, tm->tm_mday,
			tm->tm_hour, tm->tm_min, tm->tm_sec,
			unix_cred->aup_machname,
			unix_cred->aup_uid,
			unix_cred->aup_gid);
		if (unix_cred->aup_len > 0)
		{
			fprintf(debuglog, "+%d", unix_cred->aup_gids[0]);
			for (i = 1; i < unix_cred->aup_len; i++)
				fprintf(debuglog, ",%d",unix_cred->aup_gids[i]);
		}
	}
	fprintf(debuglog, "]\n\t%s\n", arg);
	fflush(debuglog);
}

#else
#define logcall(name, arg, client)
#define fh_pr(x)	""
#endif DEBUG
/* ====================================================================== */

extern int errno;
#ifdef DEBUG
extern char *sys_errlist[];
#endif DEBUG

/* ====================================================================== */

void *
nfsproc_null_2(argp, rqstp)
	void *argp;
	struct svc_req *rqstp;
{
	static char res;

	bzero(&res, sizeof(res));
	logcall("nfsproc_null", "", rqstp);
	return ((void *)&res);
}


static void
inner_getattr(fh, status, attr, stat_optimize, cp, rqstp)
	nfs_fh		*fh;
	nfsstat		*status;
	fattr		*attr;
	struct pgstat	*stat_optimize;
	clnt_param	*cp;
	struct svc_req	*rqstp;
{
	char	*path;
	struct pgstat *s;
	struct pgstat sbuf;

#ifdef DEBUG
	if (debuglog)
		fprintf(debuglog, " inner_getattr");
#endif
	if ((path = fh_path(fh, status)) != 0)
	{
		if (stat_optimize != NULL)
			s = stat_optimize;
		else
		{
			s = &sbuf;
			if (p_stat(path, s) < 0)
				*status = (nfsstat) errno;
		}
			
		attr->type = ft_map[ft_extr(s->st_mode)];
		attr->mode = s->st_mode;
		attr->nlink = 1 /*s->st_nlink*/;
		attr->uid = ruid(s->st_uid, cp, rqstp->rq_xprt);
		attr->gid = rgid(6 /*s->st_gid*/, cp, rqstp->rq_xprt);
		attr->size = s->st_size;
		attr->blocksize = /*s->st_blksize*/ 1024;
		attr->rdev = 0 /*s->st_rdev*/;
		attr->blocks = s->st_size/1024 /*s->st_blocks*/;
		attr->fsid = 1;
		attr->fileid = fh_psi(fh);
		attr->atime.seconds = s->st_atime;
		attr->mtime.seconds = s->st_mtime;
		attr->ctime.seconds = s->st_ctime;
#ifdef DEBUG
		if (debuglog)
		{
			if (*status == NFS_OK)
			{
				fprintf(debuglog, " path=%s, t=%d, m=%o, lk=%d, u/g=%d/%d, sz=%d, bsz=%d",
					path,
					attr->type, attr->mode, attr->nlink,
					attr->uid, attr->gid, attr->size,
					attr->blocksize);
				if (attr->type == NFCHR || attr->type == NFBLK)
					fprintf(debuglog, " rdev=%d/%d",
						(attr->rdev>>8)&0xff, attr->rdev&0xff);
				fprintf(debuglog, "\n  blks=%d, fsid=%d, psi=%d, at=%d, mt=%d, ct=%d\n",
					attr->blocks, attr->fsid, attr->fileid,
					attr->atime.seconds,
					attr->mtime.seconds,
					attr->ctime.seconds);
			}
			else
				fprintf(debuglog, " >>> %s\n", sys_errlist[(int) *status]);
		}
#endif
	}
#ifdef DEBUG
	else if (debuglog)
		fprintf(debuglog, " failed!\n");
#endif
}


attrstat *
nfsproc_getattr_2(argp, rqstp)
	nfs_fh *argp;
	struct svc_req *rqstp;
{
	static attrstat res;
	nfsstat st;
	clnt_param	*cp;
	int fd;

	bzero(&res, sizeof(res));
	logcall("nfsproc_getattr", fh_pr(argp), rqstp);
	if ((cp = knownclient(rqstp)) == NULL)
	{
		res.status = NFSERR_ACCES;
		return (&res);
	}
	fd = fh_fd(NULL);
	if (fd < 0) ibegin();
	inner_getattr(argp, &(res.status),
		      &(res.attrstat_u.attributes), NULL, cp, rqstp);
	if (fd < 0) icommit();
	return (&res);
}


attrstat *
nfsproc_setattr_2(argp, rqstp)
	sattrargs *argp;
	struct svc_req *rqstp;
{
	static attrstat res;

	bzero(&res, sizeof(res));
	logcall("nfsproc_setattr", "", rqstp);
	if (knownclient(rqstp) == NULL)
	{
		res.status = NFSERR_ACCES;
		return (&res);
	}
#ifdef READ_ONLY
	res.status = NFSERR_ROFS;
#else
	not implemented
#endif
	return (&res);
}


void *
nfsproc_root_2(argp, rqstp)
	void *argp;
	struct svc_req *rqstp;
{
	static char res;

	bzero(&res, sizeof(res));
	logcall("nfsproc_root", "", rqstp);
	return ((void *)&res);
}


diropres *
nfsproc_lookup_2(argp, rqstp)
	diropargs *argp;
	struct svc_req *rqstp;
{
	static diropres res;
	clnt_param	*cp;
	struct pgstat	sbuf;
	struct pgstat	*sbp = &sbuf;
	int fd;
	
	bzero(&res, sizeof(res));
	logcall("nfsproc_lookup", sprintf(argbuf, "fh=%s n=%s", fh_pr(&(argp->dir)), argp->name), rqstp);
	if ((cp = knownclient(rqstp)) == NULL)
	{
		res.status = NFSERR_ACCES;
		return (&res);
	}
	fd = fh_fd(NULL);		/* close any open files */
	if (fd < 0) ibegin();
	res.status = fh_compose(argp, &(res.diropres_u.diropres.file), &sbp);
	if (res.status == NFS_OK)
	{
		inner_getattr(&(res.diropres_u.diropres.file), &(res.status),
			&(res.diropres_u.diropres.attributes), sbp, cp,rqstp);
#ifdef DEBUG
		if (debuglog && res.status == NFS_OK)
			fprintf(debuglog, "\tnew_fh = %s\n", fh_pr(&(res.diropres_u.diropres.file)));
#endif /* DEBUG */
	}
	if (fd < 0) icommit();
	return (&res);
}


readlinkres *
nfsproc_readlink_2(argp, rqstp)
	nfs_fh *argp;
	struct svc_req *rqstp;
{
	static readlinkres res;
	clnt_param	*cp;
	char	*path;

	bzero(&res, sizeof(res));
	logcall("nfsproc_readlink", fh_pr(argp), rqstp);
	if ((cp = knownclient(rqstp)) == NULL)
	{
		res.status = NFSERR_ACCES;
		return (&res);
	}
	res.status = (nfsstat) EINVAL; /* Postgres doesn't support symlinks.*/
#if 0
	if ((path = fh_path(argp, &(res.status))) != 0)
	{
		int	cc;
		static char	linkbuf[NFS_MAXPATHLEN];

		errno = 0;
		if ((cc = readlink(path, linkbuf, NFS_MAXPATHLEN)) < 0)
			res.status = (nfsstat) errno;
		else
		{
			res.status = NFS_OK;
			linkbuf[cc] = '\0';
			res.readlinkres_u.data = linkbuf;
			if (cp->o.link_relative && linkbuf[0] == '/')
			{
				/* prepend ../ sequence.  Note: relies that
				 * fh_path returns a path containing real
				 * directories.
				 */
				int	slash_cnt = 0;
				char	*p, *q;
				for (p = index(path+1, '/'); p != NULL; p = index(p+1, '/'))
					slash_cnt++;
				p = linkbuf + strlen(linkbuf);
				q = p + 3 * slash_cnt - 1;
				if (q >= linkbuf + NFS_MAXPATHLEN)
					res.status = NFSERR_NAMETOOLONG;
				else
				{
					while (p >= linkbuf)
						*q-- = *p--;
					p = linkbuf;
					while (slash_cnt-- > 0)
					{
						*p++ = '.';	
						*p++ = '.';	
						*p++ = '/';	
					}
				}
			}
		}
	}
#endif
#ifdef DEBUG
	if (debuglog)
	{
		if (res.status != NFS_OK)
			fprintf(debuglog, " >>> %s\n", sys_errlist[(int) res.status]);
		else
			fprintf(debuglog, " %s\n", res.readlinkres_u.data);
	}
#endif /* DEBUG */
	return (&res);
}


static char iobuf[NFS_MAXDATA];

readres *
nfsproc_read_2(argp, rqstp)
	readargs *argp;
	struct svc_req *rqstp;
{
	static readres res;
	clnt_param	*cp;
	int	fd;

	bzero(&res, sizeof(res));
	logcall("nfsproc_read", sprintf(argbuf, "%s @%d for %d", fh_pr(&(argp->file)), argp->offset, argp->count), rqstp);
	if ((cp = knownclient(rqstp)) == NULL)
	{
		res.status = NFSERR_ACCES;
		return (&res);
	}
	if ((fd = fh_fd(&(argp->file), &(res.status), READFLAGS)) >= 0)
	{
	    int off;
	    int len = 0, count = 0, nread = 0;

	    p_errno = 0;
	    off= p_lseek(fd, (long) argp->offset, L_SET);
#ifdef DEBUG
	    fprintf(debuglog,"at pos %d\n",off);
#endif
	    res.readres_u.reply.data.data_val = iobuf;
	    while (!p_errno && argp->count > 0 ) {
		count = MIN(8092,argp->count);
		len = p_read(fd, res.readres_u.reply.data.data_val + nread, count);
		if (len <= 0) {
		    break;
		}
		nread += len;
		argp->count -= len;
	    }
	    res.readres_u.reply.data.data_len = nread;
#ifdef DEBUG
	    fprintf(debuglog,"read %d\n",res.readres_u.reply.data.data_len);
#endif
	    fd_idle(fd);
	    res.status = (nfsstat) p_errno;
	    if (!p_errno)
	      inner_getattr(&(argp->file), &(res.status),
			    &(res.readres_u.reply.attributes), NULL, cp, rqstp);
	}
	return (&res);
}


void *
nfsproc_writecache_2(argp, rqstp)
	void *argp;
	struct svc_req *rqstp;
{
	static char res;

	bzero(&res, sizeof(res));
	logcall("nfsproc_writecache", "", rqstp);
	return ((void *)&res);
}


attrstat *
nfsproc_write_2(argp, rqstp)
	writeargs *argp;
	struct svc_req *rqstp;
{
	static attrstat res;
	int fd;
	clnt_param	*cp;

	bzero(&res, sizeof(res));
	logcall("nfsproc_write", sprintf(argbuf, "%s @%d for %d", fh_pr(&(argp->file)), argp->offset, argp->data.data_len), rqstp);
	if ((cp  = knownclient(rqstp)) == NULL)
	{
		res.status = NFSERR_ACCES;
		return (&res);
	}
	if ((fd = fh_fd(&(argp->file), &(res.status), WRITEFLAGS)) >= 0) {
	    p_lseek(fd,(long) argp->offset,L_SET);
	    p_write(fd,argp->data.data_val,argp->data.data_len);
	    fd_idle(fd);
	    res.status = p_errno;
	    if (!p_errno)
	      inner_getattr(&(argp->file), &(res.status),
			    &(res.attrstat_u.attributes), NULL, cp, rqstp);
	}
	return (&res);
}


diropres *
nfsproc_create_2(argp, rqstp)
	createargs *argp;
	struct svc_req *rqstp;
{
	static diropres res;
	char *parent;
	char pathname[MAXPATHLEN+1];
	struct pgstat	sbuf;
	struct pgstat	*sbp = &sbuf;
	clnt_param	*cp;

	bzero(&res, sizeof(res));
	logcall("nfsproc_create", sprintf(argbuf, "fh=%s n=%s m=%0o u/g=%d/%d sz=%d", fh_pr(&(argp->where.dir)), argp->where.name, argp->attributes.mode, argp->attributes.uid, argp->attributes.gid, argp->attributes.size), rqstp);
	if ((cp = knownclient(rqstp)) == NULL)
	{
		res.status = NFSERR_ACCES;
		return (&res);
	}
	parent = fh_path(&argp->where.dir,&res.status);
	if (res.status == NFS_OK) {
	    int smgrno = 0;	/* magnetic disk hardwired XXX */
	    int fd;
	    int fhfd;
	    
	    strcpy(pathname,parent);
	    strcat(pathname,"/");
	    strcat(pathname,argp->where.name);
	    fhfd = fh_fd(NULL);	/* close open files */
	    if (fhfd < 0) ibegin();
	    fd = p_creat(pathname,CREATFLAGS,CREATTYPE);
	    if (fd >= 0) {
		res.status = fh_compose(&argp->where, &(res.diropres_u.diropres.file), &sbp);
		if (res.status == NFS_OK) {
		    p_dirflush(parent);
		    fh_setfd(&(res.diropres_u.diropres.file),fd,WRITEFLAGS);
		    inner_getattr(&(res.diropres_u.diropres.file), &(res.status),
				  &(res.diropres_u.diropres.attributes), sbp, cp,rqstp);
#ifdef DEBUG
		    if (debuglog && res.status == NFS_OK)
		      fprintf(debuglog, "\tnew_fh = %s\n", fh_pr(&(res.diropres_u.diropres.file)));
#endif /* DEBUG */
		} else {
		    if (fhfd < 0) icommit();
		}
	    } else {
		if (fhfd < 0) icommit();
	    }
	}
	return (&res);
}


nfsstat *
nfsproc_remove_2(argp, rqstp)
	diropargs *argp;
	struct svc_req *rqstp;
{
	static nfsstat res;
	char pathname[MAXPATHLEN+1];
	char *path;
	int fd;

	bzero(&res, sizeof(res));
	logcall("nfsproc_remove", "", rqstp);
	if (!knownclient(rqstp))
	{
		res = NFSERR_ACCES;
		return (&res);
	}
	fd = fh_fd(NULL);		/* close any open files */
	if (fd < 0) ibegin();
	path = fh_path(&argp->dir,&res);
	if (res == NFS_OK) {
	    strcpy(pathname,path);
	    strcat(pathname,"/");
	    strcat(pathname,argp->name);
	    if (p_unlink(pathname)) {
		res = NFSERR_ACCES;
		if (fd < 0) icommit();
		return &res;
	    }
	}
	p_dirflush(path);
	if (fd < 0) icommit();
#ifdef DEBUG
	if (debuglog && res == NFS_OK)
	  fprintf(debuglog, "\tunlinked %s\n",pathname);
#endif				/* DEBUG */
	return (&res);
}


nfsstat *
nfsproc_rename_2(argp, rqstp)
	renameargs *argp;
	struct svc_req *rqstp;
{
	static nfsstat res;
	char frompath[MAXPATHLEN+1];
	char topath[MAXPATHLEN+1];
	char *fromdir, *todir;

	bzero(&res, sizeof(res));
	logcall("nfsproc_rename", sprintf(argbuf, "from=%s/%s to=%s/%s", fh_pr(&(argp->from.dir)), argp->from.name,fh_pr(&(argp->to.dir)),argp->to.name), rqstp);
	if (!knownclient(rqstp))
	{
		res = NFSERR_ACCES;
		return (&res);
	}
	fromdir = fh_path(&argp->from.dir,&res);
	if (res == NFS_OK) {
	    strcpy(frompath,fromdir);
	    strcat(frompath,"/");
	    strcat(frompath,argp->from.name);
	}
	todir = fh_path(&argp->to.dir,&res);
	if (res == NFS_OK) {
	    strcpy(topath,todir);
	    strcat(topath,"/");
	    strcat(topath,argp->to.name);
	}
	res = p_rename(frompath,topath);
	p_dirflush(fromdir);
	p_dirflush(todir);
	return (&res);
}


nfsstat *
nfsproc_link_2(argp, rqstp)
	linkargs *argp;
	struct svc_req *rqstp;
{
	static nfsstat res;

	bzero(&res, sizeof(res));
	logcall("nfsproc_link", "", rqstp);
	if (!knownclient(rqstp))
	{
		res = NFSERR_ACCES;
		return (&res);
	}
	res = NFSERR_ROFS;
	return (&res);
}


nfsstat *
nfsproc_symlink_2(argp, rqstp)
	symlinkargs *argp;
	struct svc_req *rqstp;
{
	static nfsstat res;

	bzero(&res, sizeof(res));
	logcall("nfsproc_symlink", "", rqstp);
	if (!knownclient(rqstp))
	{
		res = NFSERR_ACCES;
		return (&res);
	}
	res = NFSERR_ROFS;
	return (&res);
}


diropres *
nfsproc_mkdir_2(argp, rqstp)
	createargs *argp;
	struct svc_req *rqstp;
{
	static diropres res;
	static nfsstat status;
	struct pgstat	sbuf;
	struct pgstat	*sbp = &sbuf;
	clnt_param   *cp;
	char *parent;
	char pathname[MAXPATHLEN+1];
	int fd;
	
	bzero(&res, sizeof(res));
	logcall("nfsproc_mkdir", "", rqstp);
	if (!(cp = knownclient(rqstp)))
	{
		res.status = NFSERR_ACCES;
		return (&res);
	}
	fd = fh_fd(NULL);		/* close any open files */
	if (fd < 0) ibegin();
	
	parent = fh_path(&argp->where.dir,&status);
	if (status != NFS_OK) {
	    res.status = status;
	    if (fd < 0) icommit();
	    return &res;
	}
	strcpy(pathname,parent);
	strcat(pathname,"/");
	strcat(pathname,argp->where.name);
	if (p_mkdir(pathname, MKDIRFLAGS)) {
	    res.status = NFSERR_ACCES;
	    if (fd < 0) icommit();
	    return &res;
	}
	p_dirflushparent(parent); /* invalidate directory */
	res.status = fh_compose(&argp->where,&res.diropres_u.diropres.file,&sbp);
	if (res.status == NFS_OK) {
	    inner_getattr(&res.diropres_u.diropres.file,&res.status,
			  &res.diropres_u.diropres.attributes,sbp,cp,rqstp);
#ifdef DEBUG
	    if (debuglog && res.status == NFS_OK)
	      fprintf(debuglog, "\tnew_fh = %s\n", fh_pr(&(res.diropres_u.diropres.file)));
#endif				/* DEBUG */
	}
	if (fd < 0) icommit();
	return (&res);
}


nfsstat *
nfsproc_rmdir_2(argp, rqstp)
	diropargs *argp;
	struct svc_req *rqstp;
{
	static nfsstat res;
	char *path;
	int fd;
	
	bzero(&res, sizeof(res));
	logcall("nfsproc_rmdir", "", rqstp);
	if (!knownclient(rqstp))
	{
		res = NFSERR_ACCES;
		return (&res);
	}
	fd  = fh_fd(NULL);		/* close any open files */
	if (fd < 0) ibegin();
	path = fh_path(&argp->dir,&res);
	if (res == NFS_OK) {
	    if (p_rmdir(path)) {
		res = NFSERR_ACCES;
		if (fd < 0) icommit();
		return &res;
	    }
	    p_dirflush(path); /* invalidate directory */
	    p_dirflushparent(path); /* invalidate directorys parent */
	}
	if (fd < 0) icommit();
	return (&res);
}

static int
dpsize(dp)
struct pgdirent *dp;
{
#define DP_SLOP	16
#define MAX_E_SIZE sizeof(entry) + MAXNAMLEN + DP_SLOP
	return sizeof(entry) + strlen(dp->d_name) + DP_SLOP;
}

readdirres *
nfsproc_readdir_2(argp, rqstp)
	readdirargs *argp;
	struct svc_req *rqstp;
{
	static readdirres res;
	entry **e;
	char	*path;
	int fd;
	
	/*
	 * Free previous result
	 */
	xdr_free(xdr_readdirres, &res);

	bzero(&res, sizeof(res));
	logcall("nfsproc_readdir", fh_pr(&(argp->dir)), rqstp);
	if (!knownclient(rqstp))
	{
		res.status = NFSERR_ACCES;
		return (&res);
	}
	fd = fh_fd(NULL);		/* close any open files */
	if (fd < 0) ibegin();
	if ((path = fh_path(argp, &(res.status))) != 0)
	{
		long	dloc;
		PDIR	*dirp;
		struct pgdirent *dp;
		struct	pgstat	sbuf;

		errno = 0;
		p_stat(path, &sbuf);
		if ((dirp = p_opendir(path)) == NULL)
		{
			if (errno != 0)
				res.status = (nfsstat) errno;
			else
				res.status = NFSERR_NAMETOOLONG;
		}
		else
		{
			int	res_size = 0;

			res.status = NFS_OK;
			bcopy(argp->cookie, &dloc, sizeof(dloc));
			if (dloc != 0)
				p_seekdir(dirp, dloc);
			e = &(res.readdirres_u.reply.entries);
			while (((res_size + MAX_E_SIZE) < argp->count
					 || e == &(res.readdirres_u.reply.entries))
				 && (dp = p_readdir(dirp)) != NULL)
			{
				if ((*e = (entry *) malloc(sizeof(entry))) == NULL)
					mallocfailed();
				(*e)->fileid = pseudo_inode(dp->d_ino, 0 /*sbuf.st_dev*/);
				if (((*e)->name = malloc(strlen(dp->d_name)+1)) == NULL)
					mallocfailed();
				strcpy((*e)->name, dp->d_name);
				dloc = p_telldir(dirp);
				bcopy(&dloc, ((*e)->cookie), sizeof(nfscookie));
				e = &((*e)->nextentry);
				res_size += dpsize(dp);
			}
			*e = NULL;
			res.readdirres_u.reply.eof = (dp == NULL);
			p_closedir(dirp);
		}
	}
	if (fd < 0) icommit();
	return (&res);
}


statfsres *
nfsproc_statfs_2(argp, rqstp)
	nfs_fh *argp;
	struct svc_req *rqstp;
{
	static statfsres res;

	bzero(&res, sizeof(res));
	logcall("nfsproc_statfs", fh_pr(argp), rqstp);
	if (!knownclient(rqstp))
	{
		res.status = NFSERR_ACCES;
		return (&res);
	}
	/* no easy way to do this */
	res.status = NFS_OK;
	res.statfsres_u.reply.tsize = 4096;
	res.statfsres_u.reply.bsize = 4096;
	res.statfsres_u.reply.blocks = 100000;
	res.statfsres_u.reply.bfree = 80000;
	res.statfsres_u.reply.bavail = 71000;
	return (&res);
}
