/*
 *	vms2unix.c
 *
 *	Miscellaneous routines for UNIX/VMS compatibility.
 *
 * $Header: /usr/build/vile/vile/RCS/vms2unix.c,v 1.34 1999/11/24 19:50:39 cmorgan Exp $
 *
 */
#include	"estruct.h"
#include	"edef.h"
#include	"dirstuff.h"

#define PERIOD    '.'
#define COLON     ':'
#define SEMICOLON ';'

static const char RootDir[] = "000000";
static const char DirType[] = ".DIR";

#if SYS_VMS
#include	<starlet.h>
#include	<unixio.h>
#include	<ssdef.h>
#include	<fab.h>

#define	zfab	dirp->dd_fab
#define	znam	dirp->dd_nam
#define	zrsa	dirp->dd_ret.d_name
#define	zrsl	dirp->dd_ret.d_namlen
#define	zesa	dirp->dd_esa

DIR *
opendir(char *filename)
{
	DIR	*dirp = typecalloc(DIR);
	long	status;

	if (dirp == 0)
		return (0);

	zfab = cc$rms_fab;
	zfab.fab$l_fop = FAB$M_NAM;
	zfab.fab$l_nam = &znam;		/* FAB => NAM block	*/
	zfab.fab$l_dna = global_g_val(GMDALL_VERSIONS)
			? "*.*;*"
			: "*.*;";
	zfab.fab$b_dns = strlen(zfab.fab$l_dna);

	zfab.fab$l_fna = filename;
	zfab.fab$b_fns = strlen(filename);

	znam = cc$rms_nam;
	znam.nam$b_ess = NAM$C_MAXRSS;
	znam.nam$l_esa = zesa;
	znam.nam$b_rss = NAM$C_MAXRSS;
	znam.nam$l_rsa = zrsa;

	if (sys$parse(&zfab) != RMS$_NORMAL) {
		(void)closedir(dirp);
		dirp = 0;
	}
	return (dirp);
}

DIRENT *
readdir(DIR *dirp)
{
	if (sys$search(&zfab) == RMS$_NORMAL) {
		zrsl = znam.nam$b_rsl;
		return (&(dirp->dd_ret));
	}
	return (0);
}

int
closedir(DIR *dirp)
{
	cfree(dirp);
	return 0;
}

char *
tempnam(const char *head, const char *tail)
{
	char	temp[NFILEN];
	char	leaf[NFILEN];
	return mktemp(
		strmalloc(
			pathcat(temp,
				head,
				strcat(strcpy(leaf, tail), "XXXXXX"))));
}

/*
 * Check if the given file exists.  If so, try to reuse its record-format when
 * creating a new version.
 *
 * Note:  using a '0' protection on VMS C 'open()' tells it to use an existing
 * file's protection, or (if the file doesn't exist) the user's default
 * protection.
 */
int
vms_creat(char *filename)
{
	char rfm_option[80], len_option[80];
	struct stat sb;
	int my_rfm = b_val(curbp, VAL_RECORD_FORMAT);
	int my_len = b_val(curbp, VAL_RECORD_LENGTH);
	int fd;

	/*
	 * Only check for the previous file's record-format if the mode has not
	 * been specified.
	 */
	if (my_rfm == FAB$C_UDF) {
		if (stat(filename, &sb) == 0) {
			my_rfm = sb.st_fab_rfm;
			my_len = sb.st_fab_mrs;
			/* Ignore the old record attributes, since 'cr' is the
			 * only one we know anything about.
			 */
		}
		if (my_rfm == FAB$C_UDF)
			my_rfm = FAB$C_STMLF;
	}

	lsprintf(rfm_option, "rfm=%s", vms_record_format(my_rfm));

	switch (my_rfm) {
	case FAB$C_FIX:
		lsprintf(len_option, "mrs=%d", my_len);
		fd = creat(filename, 0, rfm_option, len_option, "rat=cr");
		break;
	case FAB$C_VFC:
		/*
		 * FIXME:  Must specify the fixed-length control field size
		 * (there's no interface for that at the moment, so I
		 * hardwired it at two bytes.  Tom, I'm punting this one
		 * back to you.
		 *
		 * C.  Morgan 11/29/1998.
		 */

		lsprintf(len_option, "mrs=%d", my_len);
		fd = creat(filename,
		           0,
			   rfm_option,
			   len_option,
			   "fsz=2",    /* Fixed-length control field size */
			   "rat=cr");
		break;
	default:
		/*
		 * The stmlf and var record-formats are really all that is
		 * required.  Other combinations are experimental.
		 */
		fd = creat(filename, 0, rfm_option, "rat=cr");
		break;
	}

	return fd;
}


/*
 * Returns TRUE if fseek() may be used to position a file at a byte offset.
 * Fseek acceptable if the file uses stream access or fixed-length records
 * with no carriage control.
 */
int
vms_fseek_ok(char *file)
{
#define CARRIAGE_CNTL (FAB$M_FTN | FAB$M_CR)

    struct FAB fab;
    int        status, retval;

    fab            = cc$rms_fab;
    fab.fab$b_fac  = FAB$M_GET;
    fab.fab$b_shr  = FAB$M_SHRGET|FAB$M_SHRUPD;
    fab.fab$l_fna  = file;
    fab.fab$b_fns  = strlen(file);
    status         = sys$open(&fab);
    if (status != RMS$_NORMAL)
	retval = FALSE;	       /* What? */
    else
    {
        int rfm = fab.fab$b_rfm;   /* rfm == record format
				    * rat == record attributes
				    */

	retval = (rfm == FAB$C_STMLF ||
		  rfm == FAB$C_STMCR ||
		  rfm == FAB$C_STM   ||
		 (rfm == FAB$C_FIX  && ((fab.fab$b_rat & CARRIAGE_CNTL) == 0)));
    }
    (void) sys$close(&fab);
    return (retval);
#undef CARRIAGE_CNTL
}



/*
 * Does "filename" exist?  Can't use stat(), fstat(), or access() because
 * they return incorrect results for DECNET-based files.
 *
 * Note 1:  This routine will handle wildcarded filenames, but that's not it's
 *          purpose.  Think of this routine as a replacement for 
 *          "access(file, F_OK)".
 *
 * Note 2:  This routine returns TRUE for "some.dir" (assuming it exists),
 *          but not "[.some]" .
 */
int
vms_ffexists(char *filename)
{
    struct FAB fab;
    struct NAM nam;
    char       expnd_file_buffer[NAM$C_MAXRSS], rslt_file_buffer[NAM$C_MAXRSS];

    fab           = cc$rms_fab;
    nam           = cc$rms_nam;
    fab.fab$l_fna = filename;
    fab.fab$b_fns = strlen(filename);
    fab.fab$l_nam = &nam;
    nam.nam$l_esa = expnd_file_buffer;
    nam.nam$b_ess = sizeof(expnd_file_buffer);
    nam.nam$l_rsa = rslt_file_buffer;
    nam.nam$b_rss = sizeof(rslt_file_buffer);

    return (sys$parse(&fab) == RMS$_NORMAL && sys$search(&fab) == RMS$_NORMAL);
}

#endif

#if OPT_VMS_PATH
/*
 * These functions are adapted from my port2vms library -- T.Dickey
 */

/******************************************************************************
 * Translate a UNIX-style name into a VMS-style name.                         *
 ******************************************************************************/

static	int	DotPrefix (char *s);
static	char	CharToVms (int c);
static	int	leading_uc (char *dst, char *src);
static	int	is_version (char *s);

static	int	leaf_dot;   /* counts dots found in a particular leaf */
static	int	leaf_ver;   /* set if we found a DECshell version */

/*
 * If we have a dot in the second character position, force that to a dollar
 * sign.  Otherwise, keep the first dot in each unix leaf as a dot in the
 * resulting vms name.
 */
static int
DotPrefix(char * s)
{
	if (s[0] != EOS
	 && s[1] == PERIOD
	 && s[2] != EOS
	 && strchr("sp", s[0]))	/* hack for SCCS */
		return (-1);
	return (0);
}

static char
CharToVms(int c)
{
	if (c == PERIOD) {
		if (leaf_dot++)
			c = '$';
	} else if (!isAlnum(c) && !strchr("_-", c)) {
		c = '$';
	}
	return (c);
}

static int
leading_uc(char * dst, char * src)
{
	char	*base = dst;
	register int	c;

	while ((c = *src) != EOS && c != SLASHC) {
		if (isAlpha(c)) {
			if (isLower(c))
				return (0);
		} else if (!strchr("0123456789$_", c))
			return (0);
		*dst++ = c;
		*dst   = EOS;
		src++;
	}
	*dst = EOS;
	if ((*base) && (dst = getenv(base)) != 0) {
		c = strlen(base);
		dst = skip_blanks(dst);
		(void)strcpy(base, dst);
		return (c);
	}
	return (0);
}

/*
 * Returns true if the string points to a valid VMS version indicator, which
 * may begin with a '.' or ';', e.g.,
 *	;1
 *	;*
 *	;-1
 */
static int
is_version(char *s)
{
	char 	*t;

	if (*s == PERIOD
	 || *s == SEMICOLON) {
		if (*++s == EOS
		 || !strcmp(s, "*")
		 || !strcmp(s, "0")
		 || (strtol(s, &t, 10) && *t == EOS))
		 	return TRUE;
	}
	return FALSE;
}

static char *
path_suffix(char *path)
{
	char *leaf = pathleaf(path);
	char *type = strchr(leaf, '.');
	if (type == 0)
		type = skip_string(leaf);
	return type;
}

static char *
path_version(char *path)
{
	char *vers = strchr(path, SEMICOLON);
	if (vers == 0)
		vers = skip_string(path);
	return vers;
}

char *
unix2vms_path(char *dst, const char *src)
{
#if !SYS_VMS
	char	tmp2[NFILEN];
#endif
	char	tmp[NFILEN],
		leading[NFILEN],
		*t,
		*s = strcpy(tmp, src),	/* ... to permit src == dst */
		*d = dst,
		c  = '?';
	int	bracket	= FALSE,	/* true when "[" passed. */
		on_top	= FALSE,	/* true when no "[." lead */
		node	= FALSE,	/* true when node found */
		device	= FALSE,	/* true when device found */
		len;

	/*
	 * If VMS 'getenv()' is given an upper-case name, it assumes that it
	 * corresponds to a logical device assignment.  As a special case, if
	 * we have a leading token of this form, translate it.
	 */
	if ((len = leading_uc(leading,s)) != 0) {
		s  += len;
		len = strlen(strcpy(d, leading));
		while (len > 1 && d[len-1] == ' ')
			len--;
		if (*s) {		/* text follows leading token */
			s++;		/* skip (assumed) SLASHC */
			if ((len > 1)
			&&  (d[len-1] == COLON)) {
				on_top = TRUE;
			} else if (strchr(s, SLASHC)) {	/* must do a splice */
				if ((len > 2)
				&&  (d[len-1] == RBRACK)) {
					bracket++;
					if (d[len-2] == PERIOD)
						/* rooted-device ? */
						len -= 2;
					else
						len--;
				}
			}
		}
		d[len] = EOS;
		if ((t = strchr(d, COLON)) != NULL) {
			if (t[1] == COLON) {
				node = TRUE;
				if ((t = strchr(t+2, COLON)) != NULL)
					device = TRUE;
			} else
				device = TRUE;
		}
		d  += len;
	} else if (*s == '~') {		/* process home-directory reference */
		char *home = getenv("SYS$LOGIN");
#if !SYS_VMS
		if (home == 0)
			home = unix2vms_path(tmp2, getenv("HOME"));
#endif
		node =
		device = TRUE;
		s++;

		len = strlen(strcpy(d, home));

		if (d[len-1] == RBRACK) {
			bracket++;
			if (strcmp(s, "/")) { /* strip right-bracket to allow new levels */
				if (d[len-2] == PERIOD)
					len--;
				d[len-1] = PERIOD;
			} else {
				s++;
				len--;
			}
		}
		d += len;
	}

	/* look for node-name in VMS-format */
	if (!node
	&&  (t = strchr(s, '!')) != 0
	&&  (t[1] == SLASHC || t[1] == EOS)) {
		leaf_dot = DotPrefix(s);
		while (s < t)
			*d++ = CharToVms(*s++);
		*d++ = COLON;
		*d++ = COLON;
		s++;		/* skip over '!' */
	}

	/* look for device-name, indicated by a leading SLASHC */
	if (!device
	&&  (*s == SLASHC)) {
		leaf_dot = DotPrefix(++s);
		if ((t = strchr(s, SLASHC)) == 0)
			t = skip_string(s);
		else if (t[1] == EOS)
			on_top = TRUE;
		while (s < t)
			*d++ = CharToVms(*s++);
		if (d != dst)
			*d++ = COLON;
	}

	/* permit leading "./" to simplify cases in which we concatenate */
	if (!strncmp(s, "./", 2))
		s += 2;

	/* translate repeated leading "../" */
	while (!strncmp(s, "../", 3)) {
		s += 3;
		if (!bracket++)
			*d++ = LBRACK;
		*d++ = '-';
	}
	if (!strcmp(s, "..")) {
		s += 2;
		if (!bracket++)
			*d++ = LBRACK;
		*d++ = '-';
	}

	if (strchr(s, SLASHC)) {
		if (!bracket++)
			*d++ = LBRACK;
		if (*s == SLASHC) {
			s++;
		} else if (!on_top) {
			*d++ = PERIOD;
		}
		while ((c = *s++) != EOS) {
			if (c == PERIOD) {
				c = '$';
				if (*s == SLASHC) /* ignore "./" */
					continue;
			}
			if (c == SLASHC) {
		    		leaf_dot = DotPrefix(s);
				if (strchr(s, SLASHC))
					*d++ = PERIOD;
				else {
					break;
				}
			} else {
				*d++ = CharToVms(c);
			}
		}
	}
	if (bracket) {
		if (on_top && d[-1] == LBRACK) {
			(void)strcpy(d, RootDir);
			d += strlen(d);
		}
		*d++ = RBRACK;
	}
	if (c != EOS && *s) {
		leaf_dot = DotPrefix(s);
		while ((c = *s) != EOS) {
			if ((leaf_ver = is_version(s)) == TRUE) {
				leaf_dot = TRUE; /* no longer pertinent */
				(void)strcpy(d, s);
				*d = SEMICOLON;	/* make this unambiguous */
				d += strlen(d);
				break;
			} else {
				*d++ = CharToVms(c);
			}
			s++;
		}
		if (!leaf_dot)
			*d++ = PERIOD;
		if (!leaf_ver)
			*d++ = SEMICOLON;
	}
	*d = EOS;
	return mkupper(dst);
}

/******************************************************************************
 * Returns a pointer to a pathname's suffix iff it is likely a directory's    *
 ******************************************************************************/
char *
is_vms_dirtype(char *path)
{
	register char *t = path_suffix(path);
	register char *v = path_version(t);
	size_t len = (v-t);

	if (len == sizeof(DirType)-1
	 && !strncmp(t, DirType, len)
	 && (!*v || !strcmp(v, ";1"))) {
		return t;
	}
	return 0;
}

/******************************************************************************
 * Returns a pointer to a pathname's leaf iff it is the root directory        *
 ******************************************************************************/
char *
is_vms_rootdir(char *path)
{
	char *type;
	if ((type = is_vms_dirtype(path)) != 0) {
		char *leaf = pathleaf(path);
		size_t len = (type - leaf);
		if (len == sizeof(RootDir)-1
		 && !strncmp(leaf, RootDir, len))
		 	return leaf;
	}
	return 0;
}

/******************************************************************************
 * Convert a VMS directory-filename into the corresponding pathname           *
 ******************************************************************************/
void
vms_dir2path(char *path)
{
	char *s;

	if ((s = is_vms_rootdir(path)) != 0) {
		*s = EOS;
	} else {
		if ((s = strrchr(path, RBRACK)) != 0
		 && (s[1] != EOS)) {
			char *t;
			if ((t = is_vms_dirtype(s)) != 0) {
				*s = '.';
				*t++ = RBRACK;
				*t = EOS;
			}
		}
	}
}

/******************************************************************************
 * Convert a VMS pathname into the name of the corresponding directory-file.  *
 *                                                                            *
 * Note that this returns a pointer to a static buffer which is overwritten   *
 * by each call.                                                              *
 ******************************************************************************/

char *
vms_path2dir(const char *src)
{
	static	char	buffer[NFILEN];
	register char	*s	= skip_string(strcpy(buffer, src));

	if (s != buffer && *(--s) == RBRACK) {
		(void)strcpy(s, DirType);
		while (--s >= buffer) {
			if (*s == PERIOD) {
				*s = RBRACK;
				if (s == buffer+1) {	/* absorb "]" */
					register char *t = s + 1;
					s = buffer;
					while ((*s++ = *t++) != EOS)
						/*EMPTY*/;
				}
				break;
			}
			if (*s == LBRACK) {		/* absorb "[" */
				register char *t = s + 1;
				if (is_vms_rootdir(t)
				 && (s == buffer || s[-1] == COLON)) {
					(void) lsprintf(t, "%s%c%s%s",
						RootDir,
						RBRACK,
						RootDir,
						DirType);
				} else {
					while ((*s++ = *t++) != EOS)
						/*EMPTY*/;
				}
				break;
			}
		}
	}
	return (buffer);
}

/******************************************************************************
 * Translate a VMS-style pathname to a UNIX-style pathname                    *
 ******************************************************************************/

char *
vms2unix_path(char *dst, const char *src)
{
	char	current[NFILEN];
	int	need_dev = FALSE,
		have_dev = FALSE;
	char	tmp[NFILEN],
		*output = dst,
		*base = tmp,
		*s = strcpy(tmp, src),	/* ... to permit src == dst */
		*d;

	strip_version(s);

	/* look for node specification */
	if ((s = strchr(base, COLON)) != 0
	&&  (s[1] == COLON)) {
		while (base < s) {
			*dst++ = *base++;
		}
		*dst++ = '!';
		base += 2;
		need_dev = TRUE;
	}

	/*
	 * Look for device specification.  If not found, see if the path must
	 * begin at the top of the device.  In this case, it would be ambiguous
	 * if no device is supplied.
	 */
	if ((s = strchr(base, COLON)) != NULL) {
		*dst++ = SLASHC;
		while (base < s) {
			*dst++ = *base++;
		}
		base++;			/* skip over ":" */
		have_dev = TRUE;
	} else if (need_dev
	||	  ((base[0] == LBRACK)
	&&	   (base[1] != '-')
	&&	   (base[1] != PERIOD)
	&&	   (base[1] != RBRACK))) {	/* must supply a device */
		register char	*a = getcwd(current, NFILEN),
				*b = strchr(a ? a : "?", COLON);
		if ((b != 0)
		&&  (b[1] == COLON)) {	/* skip over node specification */
			a = b + 2;
			b = strchr(a, COLON);
		}
		if (b != 0) {
			*dst++ = SLASHC;	/* begin the device */
			while (a < b) {
				*dst++ = *a++;
			}
			have_dev = TRUE;
		}			/* else, no device in getcwd! */
	}

	/* translate directory-syntax */
	if ((s = strchr(base, LBRACK)) != NULL) {
		int bracketed = TRUE;
		if (s[1] == RBRACK) {
			if (dst != output && *dst != SLASHC)
				*dst++ = SLASHC;
			*dst++ = PERIOD;
			if (s[2] != EOS)
				*dst++ = SLASHC;
			s += 2;
			d = s;
			bracketed = FALSE;
		} else if (s[1] == PERIOD) {
			if (have_dev && dst[-1] != SLASHC)
				*dst++ = SLASHC;
			s += 2;
			d = s;
		} else if (s[1] == '-' && strchr("-.]", s[2])) {
			s++;
			while (*s == '-') {
				s++;
				*dst++ = PERIOD;
				*dst++ = PERIOD;
				if (*s == PERIOD
				 && (s[1] == '-' || s[1] == RBRACK))
					/* allow "-.-" */
					s++;
				if (*s == '-')
					*dst++ = SLASHC;
			}
			d = s;
		} else if (!strncmp(s+1, RootDir, sizeof(RootDir)-1)
		        && strchr(".]", s[sizeof(RootDir)])) {
			s += sizeof(RootDir);
			d = s;
		} else {
			d = s;
			*s++ = SLASHC;
		}
		/* expect s points to the last token before right-bracket */
		if (bracketed) {
			while (*s && *s != RBRACK) {
				if (*s == PERIOD)
					*s = SLASHC;
				s++;
			}
			if (*s)
				*s = s[1] ? SLASHC : EOS;
		}
	} else {
		if (have_dev && dst[-1] != SLASHC)
			*dst++ = SLASHC;
		d = base;
	}

	/*
	 * Copy the remainder of the string, trimming trailing "."
	 */
	for (s = dst; *d; s++, d++) {
		*s = *d;
		if (*s == PERIOD && d[1] == EOS)
			*s = EOS;
	}
	*s = EOS;

	s = vms_pathleaf(dst);

	/* SCCS hack */
	if (*s == '$') {
		*s++ = PERIOD;
	} else if (s[0] == 's' && s[1] == '$') {
		s[1] = PERIOD;
		s += 2;
	}

	return mklower(output);
}
#endif	/* OPT_VMS_PATH */
