/*	SCCS Id: @(#)vmsfiles.c	3.2	95/11/08	*/
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed.  See license for details. */

/*
 *  VMS-specific file manipulation routines to implement some missing
 *  routines or substitute for ones where we want behavior modification.
 */
#include "config.h"
#include <ctype.h>

/* lint supression due to lack of extern.h */
int FDECL(vms_link, (const char *,const char *));
int FDECL(vms_unlink, (const char *));
int FDECL(vms_creat, (const char *,unsigned int));
int FDECL(vms_open, (const char *,int,unsigned int));
boolean FDECL(same_dir, (const char *,const char *));
int FDECL(c__translate, (int));
char *FDECL(vms_basename, (const char *));

#include <rms.h>
#if 0
#include <psldef.h>
#else
#define PSL$C_EXEC 1	/* executive mode, for priv'd logical name handling */
#endif
#include <errno.h>
#ifndef C$$TRANSLATE	/* don't rely on VAXCRTL's internal routine */
#define C$$TRANSLATE(status) (errno = EVMSERR,  vaxc$errno = (status))
#endif
extern unsigned long sys$parse(), sys$search(), sys$enter(), sys$remove();
extern int VDECL(lib$match_cond, (int,int,...));

#define vms_success(sts) ((sts)&1)		/* odd, */
#define vms_failure(sts) (!vms_success(sts))	/* even */

/* vms_link() -- create an additional directory for an existing file */
int vms_link(file, new)
const char *file, *new;
{
    struct FAB fab;
    struct NAM nam;
    unsigned short fid[3];
    char esa[NAM$C_MAXRSS];

    fab = cc$rms_fab;	/* set block ID and length, zero the rest */
    fab.fab$l_fop = FAB$M_OFP;
    fab.fab$l_fna = (char *) file;
    fab.fab$b_fns = strlen(file);
    fab.fab$l_nam = &nam;
    nam = cc$rms_nam;
    nam.nam$l_esa = esa;
    nam.nam$b_ess = sizeof esa;

    if (vms_success(sys$parse(&fab)) && vms_success(sys$search(&fab))) {
	fid[0] = nam.nam$w_fid[0];
	fid[1] = nam.nam$w_fid[1];
	fid[2] = nam.nam$w_fid[2];
	fab.fab$l_fna = (char *) new;
	fab.fab$b_fns = strlen(new);

	if (vms_success(sys$parse(&fab))) {
	    nam.nam$w_fid[0] = fid[0];
	    nam.nam$w_fid[1] = fid[1];
	    nam.nam$w_fid[2] = fid[2];
	    nam.nam$l_esa = nam.nam$l_name;
	    nam.nam$b_esl = nam.nam$b_name + nam.nam$b_type + nam.nam$b_ver;

	    (void) sys$enter(&fab);
	}
    }

    if (vms_failure(fab.fab$l_sts)) {
	C$$TRANSLATE(fab.fab$l_sts);
	return -1;
    }
    return 0;	/* success */
}

/*
   vms_unlink() -- remove a directory entry for a file; should only be used
   for files which have had extra directory entries added, not for deletion
   (because the file won't be deleted, just made inaccessible!).
 */
int vms_unlink(file)
const char *file;
{
    struct FAB fab;
    struct NAM nam;
    char esa[NAM$C_MAXRSS];

    fab = cc$rms_fab;	/* set block ID and length, zero the rest */
    fab.fab$l_fop = FAB$M_DLT;
    fab.fab$l_fna = (char *) file;
    fab.fab$b_fns = strlen(file);
    fab.fab$l_nam = &nam;
    nam = cc$rms_nam;
    nam.nam$l_esa = esa;
    nam.nam$b_ess = sizeof esa;

    if (vms_failure(sys$parse(&fab)) || vms_failure(sys$remove(&fab))) {
	C$$TRANSLATE(fab.fab$l_sts);
	return -1;
    }
    return 0;
}

/*
   Substitute creat() routine -- if trying to create a specific version,
   explicitly remove an existing file of the same name.  Since it's only
   used when we expect exclusive access, add a couple RMS options for
   optimization.  (Don't allow sharing--eliminates coordination overhead,
   and use 32 block buffer for faster throughput; ~30% speedup measured.)
 */
#undef creat
int vms_creat(file, mode)
const char *file;
unsigned int mode;
{
    if (index(file, ';')) {
	/* assumes remove or delete, not vms_unlink */
	if (!unlink(file)) {
	    (void)sleep(1);
	    (void)unlink(file);
	}
    }
    return creat(file, mode, "shr=nil", "mbc=32", "mbf=2", "rop=wbh");
}

/*
   Similar substitute for open() -- if an open attempt fails due to being
   locked by another user, retry it once (work-around for a limitation of
   at least one NFS implementation).
 */
#undef open
int vms_open(file, flags, mode)
const char *file;
int flags;
unsigned int mode;
{
    int fd = open(file, flags, mode, "mbc=32", "mbf=2", "rop=rah");

    if (fd < 0 && errno == EVMSERR && lib$match_cond(vaxc$errno, RMS$_FLK)) {
	(void)sleep(1);
	fd = open(file, flags, mode, "mbc=32", "mbf=2", "rop=rah");
    }
    return fd;
}

/*
   Determine whether two strings contain the same directory name.
   Used for deciding whether installed privileges should be disabled
   when HACKDIR is defined in the environment (or specified via -d on
   the command line).  This version doesn't handle Unix-style file specs.
 */
boolean
same_dir(d1, d2)
const char *d1, *d2;
{
    if (!d1 || !*d1 || !d2 || !*d2)
	return FALSE;
    else if (!strcmp(d1, d2))	/* strcmpi() would be better, but that leads */
	return TRUE;		/* to linking problems for the utilities */
    else {
	struct FAB f1, f2;
	struct NAM n1, n2;

	f1 = f2 = cc$rms_fab;	/* initialize file access block */
	n1 = n2 = cc$rms_nam;	/* initialize name block */
	f1.fab$b_acmodes = PSL$C_EXEC << FAB$V_LNM_MODE;
	f1.fab$b_fns = strlen( f1.fab$l_fna = (char *)d1 );
	f2.fab$b_fns = strlen( f2.fab$l_fna = (char *)d2 );
	f1.fab$l_nam = (genericptr_t)&n1;	/* link nam to fab */
	f2.fab$l_nam = (genericptr_t)&n2;
	n1.nam$b_nop = n2.nam$b_nop = NAM$M_NOCONCEAL; /* want true device name */

	return (vms_success(sys$parse(&f1)) && vms_success(sys$parse(&f2))
	     && n1.nam$t_dvi[0] == n2.nam$t_dvi[0]
	     && !strncmp(&n1.nam$t_dvi[1], &n2.nam$t_dvi[1], n1.nam$t_dvi[0])
	     && !memcmp((genericptr_t)n1.nam$w_did,
			(genericptr_t)n2.nam$w_did,
			sizeof n1.nam$w_did));	/*{ short nam$w_did[3]; }*/
    }
}


/*
 * c__translate -- substitute for VAXCRTL routine C$$TRANSLATE.
 *
 *	Try to convert a VMS status code into its Unix equivalent,
 *	then set `errno' to that value; use EVMSERR if there's no
 *	appropriate translation; set `vaxc$errno' to the original
 *	status code regardless.
 *
 *	These translations match only a subset of VAXCRTL's lookup
 *	table, but work even if the severity has been adjusted or
 *	the inhibit-message bit has been set.
 */
#include <errno.h>
#include <ssdef.h>
#include <rmsdef.h>
/* #include <libdef.h> */
/* #include <mthdef.h> */

#define VALUE(U)	trans = U; break
#define CASE1(V)	case (V >> 3)
#define CASE2(V,W)	CASE1(V): CASE1(W)

int c__translate(code)
    int code;
{
    register int trans;

    switch ((code & 0x0FFFFFF8) >> 3) {	/* strip upper 4 and bottom 3 bits */
	CASE2(RMS$_PRV,SS$_NOPRIV):
				VALUE(EPERM);	/* not owner */
	CASE2(RMS$_DNF,RMS$_DIR):
	CASE2(RMS$_FNF,RMS$_FND):
	CASE1(SS$_NOSUCHFILE):
				VALUE(ENOENT);	/* no such file or directory */
	CASE2(RMS$_IFI,RMS$_ISI):
				VALUE(EIO);	/* i/o error */
	CASE1(RMS$_DEV):
	CASE2(SS$_NOSUCHDEV,SS$_DEVNOTMOUNT):
				VALUE(ENXIO);	/* no such device or address codes */
	CASE1(RMS$_DME):
     /* CASE1(LIB$INSVIRMEM): */
	CASE2(SS$_VASFULL,SS$_INSFWSL):
				VALUE(ENOMEM);	/* not enough core */
	CASE1(SS$_ACCVIO):
				VALUE(EFAULT);	/* bad address */
	CASE2(RMS$_DNR,SS$_DEVASSIGN):
	CASE2(SS$_DEVALLOC,SS$_DEVALRALLOC):
	CASE2(SS$_DEVMOUNT,SS$_DEVACTIVE):
				VALUE(EBUSY);	/* mount device busy codes to name a few */
	CASE2(RMS$_FEX,SS$_FILALRACC):
				VALUE(EEXIST);	/* file exists */
	CASE2(RMS$_IDR,SS$_BADIRECTORY):
				VALUE(ENOTDIR);	/* not a directory */
	CASE1(SS$_NOIOCHAN):
				VALUE(EMFILE);	/* too many open files */
	CASE1(RMS$_FUL):
	CASE2(SS$_DEVICEFULL,SS$_EXDISKQUOTA):
				VALUE(ENOSPC);	/* no space left on disk codes */
	CASE2(RMS$_WLK,SS$_WRITLCK):
				VALUE(EROFS);	/* read-only file system */
	default:
				VALUE(EVMSERR);
    };

    errno = trans;
    vaxc$errno = code;
    return code;	/* (not very useful) */
}

#undef VALUE
#undef CASE1
#undef CASE2

static char basename[NAM$C_MAXRSS+1];

/* return a copy of the 'base' portion of a filename */
char *
vms_basename(name)
const char *name;
{
    unsigned len;
    char *base, *base_p;
    register const char *name_p;

    /* skip directory/path */
    if ((name_p = strrchr(name, ']')) != 0) name = name_p + 1;
    if ((name_p = strrchr(name, '>')) != 0) name = name_p + 1;
    if ((name_p = strrchr(name, ':')) != 0) name = name_p + 1;
    if ((name_p = strrchr(name, '/')) != 0) name = name_p + 1;
    if (!*name) name = ".";		/* this should never happen */

    /* find extension/version and derive length of basename */
    if ((name_p = strchr(name, '.')) == 0 || name_p == name)
	name_p = strchr(name, ';');
    len = (name_p && name_p > name) ? name_p - name : strlen(name);

    /* return a lowercase copy of the name in a private static buffer */
    base = strncpy(basename, name, len);
    base[len] = '\0';
    /* we don't use lcase() so that utilities won't need hacklib.c */
    for (base_p = base; base_p < &base[len]; base_p++)
	if (isupper(*base_p)) *base_p = tolower(*base_p);

    return base;
}

/*vmsfiles.c*/
