/* ----------------------------------------------------------------
 *   FILE
 *      fe-pqufs.c
 *
 *   DESCRIPTION
 *      Unix filesystem abstractions for large objects
 *
 *   SUPPORT ROUTINES
 *
 *   INTERFACE ROUTINES
 *      p_open,p_read,p_write,p_close
 *      p_creat, p_lseek, p_tell
 *      p_stat
 *      p_rename, p_mkdir, p_rmdir, p_unlink
 *      p_opendir, p_readdir, p_rewinddir, p_closedir
 *      p_chdir, p_getwd
 *
 *      For description, see below and unix man(2) pages.
 *
 *   NOTES
 *      These routines are NOT compiled into the postgres backend,
 *      rather they end up in libpq.a.
 *
 *   IDENTIFICATION
 *      $Header: /private/postgres/src/lib/libpq/RCS/fe-pqufs.c,v 1.11 1992/06/15 08:24:49 dpassage Exp $
 * ----------------------------------------------------------------
 */

/*
 * Unix abstractions for large objects.
 *
 * p_open, p_read, p_write, p_close
 *
 * p_creat, p_lseek, p_tell
 * p_ftruncate *
 *
 * p_stat (certain fields in the stat structure, e.g device, are
 *         meaningless.  Other fields, such as the times, are not valid yet.)
 *
 * p_rename, p_mkdir, p_rmdir, p_unlink
 *
 * p_opendir, p_readdir, p_rewinddir, p_closedir
 *
 *
 * These p_* calls are translated to fastpath function calls in backend's 
 * address space.  The large object file handles reside in, and the 
 * operations occur in backend's address space.
 *
 * These two calls are local functions that do not call the fastpath.  Each 
 * pathname in the p_* calls are translated to an absolute pathname using the
 * current working directory, initially "/".
 * p_chdir *
 * p_getwd *
 *
 * The semantics of the p_* calls are, for the most part, the same as the 
 * unix calls.
 *
 * Extent of the similarity with the Unix filesystem
 * -------------------------------------------------
 * There is no idea or support of a '.' or '..' file.  The functions opendir, 
 * readdir, telldir, seekdir, rewinddir and closedir are instead implemented 
 * as queries.
 */

#include "tmp/simplelists.h"
#include "tmp/libpq.h"
#include "tmp/libpq-fe.h"
#include "tmp/libpq-fs.h"
#include "utils/fmgr.h"
#include "tmp/postgres.h"
#include "tmp/fastpath.h"

#ifndef MAXPATHLEN
#define MAXPATHLEN 1024
#endif

/* cwdir set from PFCWD environment variable, if available, else default */
char cwdir[MAXPATHLEN] = "/";			/* current working directory */

/*
 * Forward declarations for functions used later in this module
 */
static PDIR *new_PDIR ARGS((void ));
static Direntry *new_Direntry ARGS((char *name , oid OIDf ));
static char *resolve_path ARGS((char *path ));
int p_errno = 0;			/* error code */

int p_open(fname,mode)
     char *fname;
     int mode;
{
    int fd;
    PQArgBlock argv[2];
    char *ret;

    argv[0].isint = 0;
    argv[0].len = VAR_LENGTH_ARG;
    argv[0].u.ptr = (int *)resolve_path(fname);

    argv[1].isint = 1;
    argv[1].len = 4;
    argv[1].u.integer = mode;
    
    ret = PQfn(F_LOOPEN,&fd,sizeof(int32),NULL,1,argv,2); 
    if (ret == NULL)
      return -1;
    else if (*ret == 'V')
      return -1;
    else {
	p_errno = fd>=0 ? 0 : -fd;

	/* have to do this to reset offset in shared fd cache */
	/* but only if fd is valid */
	if (fd >= 0 && p_lseek(fd, 0L, L_SET) < 0)
		return -1;

	return fd;
    }
}

int p_close(fd)
     int fd;
{
    PQArgBlock argv[1];
    char *pqret;
    int retval;

    argv[0].isint = 1;
    argv[0].len = 4;
    argv[0].u.integer = fd;
    pqret = PQfn(F_LOCLOSE,&retval,sizeof(int32),NULL,1,argv,1);
    if (pqret == NULL)
      return -1;
    else if (*pqret == 'V')
      return -1;
    else {
	p_errno = retval>=0 ? 0 : -retval;
	return retval;
    }
}

int p_read(fd, buf, len)
     int fd;
     char *buf;
     int len;
{
    return (PQfsread(fd, buf, len));
}

int p_write(fd, buf, len)
     int fd;
     char *buf;
     int len;
{
    return (PQfswrite(fd, buf, len));
}

int p_lseek(fd,offset,whence)
     int fd, offset, whence;
{
    PQArgBlock argv[3];
    char *pqret;
    int retval;
    
    argv[0].isint = 1;
    argv[0].len = 4;
    argv[0].u.integer = fd;
    
    argv[1].isint = 1;
    argv[1].len = 4;
    argv[1].u.integer = offset;

    argv[2].isint = 1;
    argv[2].len = 4;
    argv[2].u.integer = whence;

    pqret = PQfn(F_LOLSEEK,&retval,sizeof(int32),NULL,1,argv,3);

    if (pqret == NULL)
      return -1;
    else if (*pqret == 'V')
      return -1;
    else {
	p_errno = retval>=0 ? 0 : -retval;
	return retval;
    }
}

int p_creat(path,mode,objtype)
     char *path;
     int mode;
     int objtype;
{
    PQArgBlock argv[3];
    char *pqret;
    int retval;

    argv[0].isint = 0;
    argv[0].len = VAR_LENGTH_ARG;
    argv[0].u.ptr = (int *)resolve_path(path);

    argv[1].isint = 1;
    argv[1].len = 4;
    argv[1].u.integer = mode;

    argv[2].isint = 1;
    argv[2].len = 4;
    argv[2].u.integer = objtype;

    pqret = PQfn(F_LOCREAT,&retval,sizeof(int32),NULL,1,argv,3);

    if (pqret == NULL)
      return -1;
    else if (*pqret == 'V')
      return -1;
    else {
	p_errno = retval>=0 ? 0 : -retval;
	return retval;
    }
}

int p_tell(fd)
     int fd;
{
    int retval;
    PQArgBlock argv[1];
    char *pqret;

    argv[0].isint = 1;
    argv[0].len = 4;
    argv[0].u.integer = fd;

    pqret = PQfn(F_LOTELL,&retval,sizeof(int32),NULL,1,argv,1);

    if (pqret == NULL)
      return -1;
    else if (*pqret == 'V')
      return -1;
    else {
	p_errno = retval>=0 ? 0 : -retval;
	return retval;
    }
}

int p_mkdir(path,mode)
     char *path;
     int mode;
{
    PQArgBlock argv[2];
    char *pqret;
    int retval;

    argv[0].isint = 0;
    argv[0].len = VAR_LENGTH_ARG;
    argv[0].u.ptr = (int *)resolve_path(path);

    argv[1].isint = 1;
    argv[1].len = 4;
    argv[1].u.integer = mode;

    pqret = PQfn(F_LOMKDIR,&retval,sizeof(int32),NULL,1,argv,2);

    if (pqret == NULL)
      return -1;
    else if (*pqret == 'V')
      return -1;
    else {
	p_errno = retval>=0 ? 0 : -retval;
	return retval;
    }
}

int p_unlink(path)
     char *path;
{
    PQArgBlock argv[1];
    char *pqret;
    int retval;

    argv[0].isint = 0;
    argv[0].len = VAR_LENGTH_ARG;
    argv[0].u.ptr = (int *)resolve_path(path);

    pqret = PQfn(F_LOUNLINK,&retval,sizeof(int32),NULL,1,argv,1);

    if (pqret == NULL)
      return -1;
    else if (*pqret == 'V')
      return -1;
    else {
	p_errno = retval>=0 ? 0 : -retval;
	return retval;
    }
}

int p_rmdir(path)
     char *path;
{
    PQArgBlock argv[1];
    char *pqret;
    int retval;

    argv[0].isint = 0;
    argv[0].len = VAR_LENGTH_ARG;
    argv[0].u.ptr = (int *)resolve_path(path);

    pqret = PQfn(F_LORMDIR,&retval,sizeof(int32),NULL,1,argv,1);

    if (pqret == NULL)
      return -1;
    else if (*pqret == 'V')
      return -1;
    else {
	p_errno = retval>=0 ? 0 : -retval;
	return retval;
    }
}

int p_ferror(fd)
     int fd;
{
    return 0; /* no errors reported yet... */
}

int p_rename(path,pathnew)
     char *path, *pathnew;
{
    PQArgBlock argv[2];
    char *pqret;
    int retval;
    static char tmpdir[MAXPATHLEN];
    strcpy(tmpdir,resolve_path(path));
    argv[0].isint = 0;
    argv[0].len = VAR_LENGTH_ARG;
    argv[0].u.ptr = (int *)tmpdir;

    argv[1].isint = 0;
    argv[1].len = VAR_LENGTH_ARG;
    argv[1].u.ptr = (int *)resolve_path(pathnew);

    pqret = PQfn(F_LORENAME,&retval,sizeof(int32),NULL,1,argv,2);

    if (pqret == NULL)
      return -1;
    else if (*pqret == 'V')
      return -1;
    else {
	p_errno = retval>=0 ? 0 : -retval;
	return retval;
    }
}

int p_stat(path,statbuf)
     char *path;
     struct pgstat *statbuf;
{
    int stlen;
    PQArgBlock argv[2];
    struct varlena *statres;
    int vstatlen;
    char *pqret;

    argv[0].isint = 0;
    argv[0].len = VAR_LENGTH_ARG;
    argv[0].u.ptr = (int *)resolve_path(path);

/* PQfn deals with >4byte return values fine. */
/*    vstatlen = sizeof(struct pgstat) + sizeof(int);
    statres = (struct varlena *) palloc(vstatlen); */
    pqret = PQfn(F_LOSTAT,(int *)statbuf,sizeof(struct pgstat),&stlen,2,argv,1);
/*    bcopy((char *) VARDATA(statres), (char *) statbuf, sizeof(struct pgstat));
    pfree(statres); */

    if (stlen == 5) {
	p_errno = PENOENT;
        return -1;
    }

    if (pqret == NULL)
      return -1;
    else if (*pqret == 'V')
      return -1;
    else {
	return 0;
    }
}


/*
 * open a portal to the query 
 * 'retrieve portal dir (pg_naming.filename) where pg_naming.parentid =
 * id-of-directory'
 */
PDIR *p_opendir(path)
     char *path;
{
    /* Get OID of directory (path) */
    int stlen;
    PQArgBlock argv[2];
    char *pqret;
    oid pathOID;

    argv[0].isint = 0;
    argv[0].len = VAR_LENGTH_ARG;
    argv[0].u.ptr = (int *)resolve_path(path);

    pqret = PQfn(F_FILETOOID,(int *)&pathOID,sizeof(int32),NULL,1,argv,1);
    if (pqret == NULL)
      return NULL;
    else if (*pqret == 'V')
      return NULL;
    else {
	PDIR *pdir = new_PDIR();
	char query[512];
	char *res;

#if 0
	if ((res == NULL) || (*res != 'C')) { /* CBEGIN */
	    return NULL;
	}
#endif
	sprintf(query,"retrieve (pg_naming.filename,pg_naming.ourid) where pg_naming.parentid = \"%d\"::oid",pathOID);
	res = PQexec(query);
	if ((res != NULL) && (*res == 'P')) /* CRETRIEVE */
	{
	    PortalBuffer *p;
	    int k,g,t;
	    p = PQparray(++res);
	    g = PQngroups(p);
	    t = 0;
	    for (k=0;k<g;k++) {
		int m,n,i;
		n = PQntuplesGroup(p,k);
		m = PQnfieldsGroup(p,k);
		for (i =0;i<n;i++) {
		    char *name = PQgetvalue(p,t+i,0);
		    oid OIDf = atoi(PQgetvalue(p,t+i,1));
		    Direntry *d = new_Direntry(name,OIDf);
		    SLAddTail(&pdir->dirlist,&d->Node);
		}
		t += n;
	    }
	}
	pdir->current_entry = (Direntry *)SLGetHead(&pdir->dirlist);
	return pdir;
    }
}

struct pgdirent *p_readdir(dirp)
     PDIR *dirp;
{
    struct pgdirent *d = NULL;
    if (dirp->current_entry != NULL) {
	d = &dirp->current_entry->d;
	dirp->current_entry = (Direntry *)SLGetSucc(&dirp->current_entry->Node);
    }
    return d;
}

void p_rewinddir(dirp)
     PDIR *dirp;
{
    dirp->current_entry = (Direntry *)SLGetHead(&dirp->dirlist);
}

int p_closedir(dirp)
     PDIR *dirp;
{
    /* well, how does one garbage collect these things? */
}

int p_chdir(path)
     char *path;
{
    struct pgstat st;

    if (p_stat(path,&st) < 0) {
	return -1;
    }
    if (!S_ISDIR(st.st_mode)) {
	p_errno = PENOTDIR;
	return -1;
    }
    
    if (*path == '/') {
	strcpy(cwdir,path);
    } else {
	strcpy(cwdir,resolve_path(path));
    }
    setenv("PFCWD",cwdir,1);
    return 0;
}

char *p_getwd(path)
     char *path;
{
    char *foo;
    foo = resolve_path("/"); 	/* initialize cwdir, if necessary */
    strcpy(path,cwdir);
    return path;
}

/*
 * ------------------------------------------------
 * Support routines for DIR operations
 * ------------------------------------------------
 */
static PDIR *new_PDIR()
{
    PDIR *p;
    p = (PDIR *)palloc(sizeof(PDIR));
    SLNewList(&p->dirlist,offsetof(Direntry,Node));
    p->current_entry = NULL;
    return p;
}

static Direntry *new_Direntry(name,OIDf)
     char *name;
     oid OIDf;
{
    Direntry *d;
    d = (Direntry *)palloc(sizeof(Direntry));
    SLNewNode(&d->Node);
    d->d.d_ino = OIDf;
    strcpy(d->d.d_name,name);
    return d;
}
/* use by all the p_* routines to map relative to absolute path */
#define EOP(x) ((x)=='/' ||(x) =='\0')
#define DOT(x) ((x) =='.')

static int cwdinit = 0;

/* Be careful, the result returned is statically allocated*/
static char *resolve_path(gpath)
     char *gpath;
{
    char *cp;
    static char thenewdir[MAXPATHLEN];
    char tpath[MAXPATHLEN];
    char *path = tpath;
    strcpy(path,gpath);

    if (!cwdinit) {
	if ((cp = (char *)getenv("PFCWD")) != NULL)
		strcpy(cwdir,cp);
	cwdinit = 1;
    }
    if (*path == '/') {
	strcpy(thenewdir,path);
	return thenewdir;
    }
    strcpy(thenewdir,cwdir);
    while (path && *path) {
	if (DOT(path[0]) && DOT(path[1]) && EOP(path[2])) {
	    char *sep = (char *)rindex(thenewdir,'/');
	    if (path[2] == '/')
	      path += 3;
	    else
	      path += 2;
	    if (sep != NULL && sep != thenewdir) {
		*sep = '\0';
	    } else {
		thenewdir[0] = '/'; thenewdir[1] = '\0';
	    }
	    continue;
	}
	if (DOT(path[0]) && EOP(path[1])) {
	    if (path[1] == '/')
	      path += 2;
	    else
	      path += 1;
	    continue;
	}
	if (path[0]) {
	    char *comp = (char *)index(path,'/');
	    if (comp != NULL) {
		*comp='\0';
		if (thenewdir[1]) /* not simply "/" */
		  strcat(thenewdir,"/");
		strcat(thenewdir,path);
		path = comp+1;
	    } else {
		if (thenewdir[1]) /* not simply "/" */
		  strcat(thenewdir,"/");
		strcat(thenewdir,path);
		path = NULL; /* end of all the components */
	    }
	    continue;
	}
    }
    return thenewdir;
}
