/*
 * gawkmisc.vms --- miscellanious gawk routines that are OS specific.
 */

/* 
 * Copyright (C) 1986, 1988, 1989, 1991-1996, 2003, 2011, 2014
 * the Free Software Foundation, Inc.
 * 
 * This file is part of GAWK, the GNU implementation of the
 * AWK Progamming Language.
 * 
 * GAWK is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GAWK 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 General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>

#include <descrip.h>
#include <dvidef.h>
#include <efndef.h>
#include <fscndef.h>
#include <stsdef.h>
#include <time.h>
#include <lnmdef.h>


#pragma member_alignment save
#pragma nomember_alignment longword
struct item_list_3 {
	unsigned short len;
	unsigned short code;
	void * bufadr;
	unsigned short * retlen;
};

struct filescan_itmlst_2 {
    unsigned short length;
    unsigned short itmcode;
    char * component;
};

#pragma member_alignment

int SYS$GETDVIW(
        unsigned long efn,
        unsigned short chan,
        const struct dsc$descriptor_s * devnam,
        const struct item_list_3 * itmlst,
        void * iosb,
        void (* astadr)(unsigned long),
        unsigned long astprm,
        void * nullarg);

int SYS$FILESCAN(
        const struct dsc$descriptor_s * srcstr,
        struct filescan_itmlst_2 * valuelist,
        unsigned long * fldflags,
        struct dsc$descriptor_s *auxout,
        unsigned short * retlen);

int   SYS$TRNLNM(
        const unsigned long * attr,
        const struct dsc$descriptor_s * table_dsc,
        struct dsc$descriptor_s * name_dsc,
        const unsigned char * acmode,
        const struct item_list_3 * item_list);

char quote = '\'';
char *defpath = DEFPATH;
char *deflibpath = DEFLIBPATH;
char envsep  = ',';
#define VMS_NAME_LEN 255
static char vms_name[VMS_NAME_LEN+1];

/* Take all the fun out of simply looking up a logical name */
static int sys_trnlnm
   (const char * logname,
    char * value,
    int value_len)
{
	const $DESCRIPTOR(table_dsc, "LNM$FILE_DEV");
	const unsigned long attr = LNM$M_CASE_BLIND;
	struct dsc$descriptor_s name_dsc;
	int status;
	unsigned short result;
	struct item_list_3 itlst[2];

	itlst[0].len = value_len;
	itlst[0].code = LNM$_STRING;
	itlst[0].bufadr = value;
	itlst[0].retlen = &result;

	itlst[1].len = 0;
	itlst[1].code = 0;

	name_dsc.dsc$w_length = strlen(logname);
	name_dsc.dsc$a_pointer = (char *)logname;
	name_dsc.dsc$b_dtype = DSC$K_DTYPE_T;
	name_dsc.dsc$b_class = DSC$K_CLASS_S;

	status = SYS$TRNLNM(&attr, &table_dsc, &name_dsc, 0, itlst);

	if ($VMS_STATUS_SUCCESS(status)) {

		/* Null terminate and return the string */
		value[result] = '\0';
	}

	return status;
}


/* gawk_name --- pull out the "gawk" part from how the OS called us */

/* You would not think that this should be a such a problem, but
 * VMS extended file specifications are tricky to parse, and we have
 * to tell the difference between a CRTL generated argv[0] and a
 * passed exec() argv[0] and handle both cases.
 */

char *
gawk_name(filespec)
const char *filespec;
{
	int status;
	int result;
	char * shell;
	int lcname = 0;

	/* If the path name starts with a /, then it is an absolute path
	 * that may have been generated by the CRTL instead of the command
	 * name.  If it is the device name between the slashes, then this
	 * was likely from the run command and needs to be fixed up.
	 * If the DECC$POSIX_COMPLIANT_PATHNAMES is set to 2, then it is
	 * the DISK$VOLUME that will be present, and it will still need to
	 * be fixed.
	 */

	result = 0;
	if (filespec[0] == '/') {
		char * nextslash;
		int length;
		struct item_list_3 itemlist[3];
		unsigned short dvi_iosb[4];
		char alldevnam[64];
		unsigned short alldevnam_len;
		struct dsc$descriptor_s devname_dsc;
		char diskvolnam[256];
		unsigned short diskvolnam_len;

		 /* Get some information about the disk */
		/*--------------------------------------*/
		itemlist[0].len = (sizeof alldevnam) - 1;
		itemlist[0].code = DVI$_ALLDEVNAM;
		itemlist[0].bufadr = alldevnam;
		itemlist[0].retlen = &alldevnam_len;
		itemlist[1].len = (sizeof diskvolnam) - 1 - 5;
		itemlist[1].code = DVI$_VOLNAM;
		itemlist[1].bufadr = &diskvolnam[5];
		itemlist[1].retlen = &diskvolnam_len;
		itemlist[2].len = 0;
		itemlist[2].code = 0;

		/* Add the prefix for the volume name. */
		/* SYS$GETDVI will append the volume name to this */
		strcpy(diskvolnam,"DISK$");

		nextslash = strchr(&filespec[1], '/');
		if (nextslash != NULL) {
			length = nextslash - filespec - 1;

			/* DECC requires a cast here */
			devname_dsc.dsc$a_pointer = (char *)&filespec[1];
			devname_dsc.dsc$w_length = length;
			devname_dsc.dsc$b_dtype = DSC$K_DTYPE_T;
			devname_dsc.dsc$b_class = DSC$K_CLASS_S;

			status = SYS$GETDVIW(
				EFN$C_ENF,
				0,
				&devname_dsc,
				itemlist,
				dvi_iosb,
				NULL, 0, 0);
			if (!$VMS_STATUS_SUCCESS(status)) {
				/* If the sys$getdviw fails, then this path
				 * was passed by an exec() program and not
				 * from DCL, so do nothing.
				 * An example is "/tmp/program" where tmp:
				 * does not exist
				 */
				result = 0;
			} else if (!$VMS_STATUS_SUCCESS(dvi_iosb[0])) {
				result = 0;
			} else {
				char * devnam;
				int devnam_len;
				char argv_dev[64];

				/* Null terminate the returned alldevnam */
				alldevnam[alldevnam_len] = 0;
				devnam = alldevnam;
				devnam_len = alldevnam_len;

				/* Need to skip past any leading underscore */
				if (devnam[0] == '_') {
					devnam++;
					devnam_len--;
				}

				/* And remove the trailing colon */
				if (devnam[devnam_len - 1] == ':') {
					devnam_len--;
					devnam[devnam_len] = 0;
				}

				/* Null terminate the returned volnam */
				diskvolnam_len += 5;
				diskvolnam[diskvolnam_len] = 0;

				/* Check first for normal CRTL behavior */
				if (devnam_len == length) {
					strncpy(vms_name, &filespec[1], length);
				vms_name[length] = 0;
				result = (strcasecmp(devnam, vms_name) == 0);
				}

				/* If we have not got a match check for
				 * POSIX Compliant behavior.  To be more
				 * accurate, we could also check to see
				 * if that feature is active.
				 */
				if ((result == 0) &&
				    (diskvolnam_len == length)) {
					int cmp;
					strncpy(vms_name, &filespec[1], length);
					vms_name[length] = 0;
					cmp = strcasecmp(diskvolnam, vms_name);
					result = (cmp == 0);
				}
			}
		}
	} else {
		/* The path did not start with a slash, so it could be VMS
		 * format.  If it is vms format, it has a volume/device in
		 * it as it must be an absolute path
		 */
		struct dsc$descriptor_s path_desc;
		int status;
		unsigned long field_flags;
		struct filescan_itmlst_2 item_list[5];
		char * volume;
		char * name;
		int name_len;
		char * ext;

		/* DECC requires a cast here */
		path_desc.dsc$a_pointer = (char *)filespec;
		path_desc.dsc$w_length = strlen(filespec);
		path_desc.dsc$b_dtype = DSC$K_DTYPE_T;
		path_desc.dsc$b_class = DSC$K_CLASS_S;

		/* Don't actually need to initialize anything buf itmcode */
		/* I just do not like uninitialized input values */

		/* Sanity check, this must be the same length as input */
		item_list[0].itmcode = FSCN$_FILESPEC;
		item_list[0].length = 0;
		item_list[0].component = NULL;

		/* If the device is present, then it if a VMS spec */
		item_list[1].itmcode = FSCN$_DEVICE;
		item_list[1].length = 0;
		item_list[1].component = NULL;

		/* we need the program name and type */
		item_list[2].itmcode = FSCN$_NAME;
		item_list[2].length = 0;
		item_list[2].component = NULL;

		item_list[3].itmcode = FSCN$_TYPE;
		item_list[3].length = 0;
		item_list[3].component = NULL;

		/* End the list */
		item_list[4].itmcode = 0;
		item_list[4].length = 0;
		item_list[4].component = NULL;

		status = SYS$FILESCAN(
		    (const struct dsc$descriptor_s *)&path_desc,
		     item_list, &field_flags, NULL, NULL);

		if ($VMS_STATUS_SUCCESS(status) &&
		   (item_list[0].length == path_desc.dsc$w_length) &&
		   (item_list[1].length != 0)) {

			char * dollar;
			int keep_ext;
			int i;

			/* We need the filescan to be successful,
			 * same length as input, and a volume to be present.
			 *
			 * We will assume that we only get to this path on
			 * a version of VMS that does not support the EFS
			 * character set.
			 *
			 * There may be a xxx$ prefix on the image name.
			 * Linux  programs do not handle that well, so
			 * strip the prefix.
			 */
			name = item_list[2].component;
			name_len = item_list[2].length;
			dollar = strrchr(name, '$');
			if (dollar != NULL) {
				dollar++;
				name_len = name_len - (dollar - name);
				name = dollar;
			}

			strncpy(vms_name, name, name_len);
			vms_name[name_len] = 0;
			result = 1;

			/* We only keep the extension if it is not ".exe" */
			keep_ext = 0;
			ext = item_list[3].component;

			if (item_list[3].length != 1) {
				if (item_list[3].length != 4) {
					keep_ext = 1;
				} else {
					int x;
					x = strncmp(ext, ".exe", 4);
					if (x != 0) {
						keep_ext = 1;
					}
				}
			}

			if (keep_ext == 1) {
				strncpy(&vms_name[name_len],
					ext, item_list[3].length);
			}
		}
	}

	if (result) {
		char * lastslash;
		char * dollar;
		char * dotexe;
		char * lastdot;
		char * extension;

		/* This means it is probably the name from a DCL command
		 * Find the last slash which separates the file from the
		 * path.
		 */
		lastslash = strrchr(filespec, '/');

		if (lastslash != NULL) {
			int i;

			lastslash++;

			/* There may be a xxx$ prefix on the image name. */
			/* Linux programs do not handle that well, so    */
			/* strip the prefix */
			dollar = strrchr(lastslash, '$');

			if (dollar != NULL) {
				dollar++;
				lastslash = dollar;
			}

			strcpy(vms_name, lastslash);

			/* In UNIX mode + EFS character set, there should
			 * not be a version present, as it is not possible
			 * when parsing to tell if it is a version or part
			 * of the UNIX filename as UNIX programs use numeric
			 * extensions for many reasons.
			 */

			lastdot = strrchr(vms_name, '.');
			if (lastdot != NULL) {
				int i;

				i = 1;
				while (isdigit(lastdot[i])) {
					i++;
				}
				if (lastdot[i] == 0) {
					*lastdot = 0;
				}
			}

			/* Find the .exe on the name (case insenstive)
			 * and toss it
			 */
			dotexe = strrchr(vms_name, '.');
			if (dotexe != NULL) {
				if ((dotexe[1] == 'e' || dotexe[1] == 'E') &&
				    (dotexe[2] == 'x' || dotexe[2] == 'X') &&
				    (dotexe[3] == 'e' || dotexe[3] == 'E') &&
				    (dotexe[4] == 0)) {

					*dotexe = 0;
				} else {
					/* Also need to handle a null
					 * extension because of a CRTL bug.
					 */
					if (dotexe[1] == 0) {
						*dotexe = 0;
					}
				}
			}
		}
	} else {
		/* No changes needed */
		strncpy(vms_name, filespec, VMS_NAME_LEN);
		vms_name[VMS_NAME_LEN] = 0;
	}


	/*
	 * The above fixes up the name, but for the DCL shell
	 * may leave it in upper case, which messes up the self tests.
	 * force it to lower case here.
	 */
	shell = getenv("SHELL");
	if (shell != NULL) {
		if (strcmp(shell, "DCL") == 0) {
			lcname = 1;
		}
	} else {
		lcname = 1;
	}
	if (lcname == 1) {
		int i = 0;
		while (vms_name[i] != 0) {
			vms_name[i] = tolower(vms_name[i]);
			i++;
		}
	}
	return vms_name;
}

/* os_arg_fixup --- fixup the command line */

void
os_arg_fixup(argcp, argvp)
int *argcp;
char ***argvp;
{
	char *tz_rule;
	int status;

	(void) vms_arg_fixup(argcp, argvp);

	/* Fix up the time zone */
	/* For some reason it gets trashed */
	tz_rule = malloc(1024);
	status = sys_trnlnm("TZ", tz_rule, 1024);
	if ($VMS_STATUS_SUCCESS(status)) {
		setenv("TZ", tz_rule, 1);
	} else {
		status = sys_trnlnm("SYS$TIMEZONE_RULE", tz_rule, 1024);
		if ($VMS_STATUS_SUCCESS(status)) {
			setenv("TZ", tz_rule, 1);
		}
	}
	free(tz_rule);
}

/* os_devopen --- open special per-OS devices */

int
os_devopen(name, flag)
const char *name;
int flag;
{
	return vms_devopen(name, flag);
}

/* optimal_bufsize --- determine optimal buffer size */

size_t
optimal_bufsize(fd, stb)
int fd;
struct stat *stb;
{

	/* force all members to zero in case OS doesn't use all of them. */
	memset(stb, '\0', sizeof(struct stat));

	/*
	 * These values correspond with the RMS multi-block count used by
	 * vms_open() in vms/vms_misc.c.
	 */
	if (isatty(fd) > 0)
		return BUFSIZ;
	else if (fstat(fd, stb) < 0)
		return 8*512;	/* conservative in case of DECnet access */
	else
		return 32*512;
}

/* ispath --- return true if path has directory components */

int
ispath(file)
const char *file;
{
	for (; *file; file++) {
		switch (*file) {
		case ':':
		case ']':
		case '>':
		case '/':
			return 1;
		}
	}
	return 0;
}

/* isdirpunct --- return true if char is a directory separator */

int
isdirpunct(c)
int c;
{
	return (strchr(":]>/", c) != NULL);
}

/* os_close_on_exec --- set close on exec flag, print warning if fails */

void
os_close_on_exec(fd, name, what, dir)
int fd;
const char *name, *what, *dir;
{
	/* no-op */
}

/* os_isdir --- is this an fd on a directory? */

#if ! defined(S_ISDIR) && defined(S_IFDIR)
#define	S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif

int
os_isdir(fd)
int fd;
{
	struct stat sbuf;

	return (fstat(fd, &sbuf) == 0 && S_ISDIR(sbuf.st_mode));
}

/* os_isreadable --- fd can be read from */

int
os_isreadable(const awk_input_buf_t *iobuf, bool *isdir)
{
	*isdir = false;

	switch (iobuf->sbuf.st_mode & S_IFMT) {
	case S_IFREG:
	case S_IFCHR:	/* ttys, /dev/null, .. */
#ifdef S_IFSOCK
	case S_IFSOCK:
#endif
#ifdef S_IFIFO
	case S_IFIFO:
#endif
		return true;
	case S_IFDIR:
		*isdir = true;
		/* fall through */
	default:
		return false;
	}
}

/* os_is_setuid --- true if running setuid root */

int
os_is_setuid()
{
	return 0;
}

/* os_setbinmode --- set binary mode on file */

int
os_setbinmode (fd, mode)
int fd, mode;
{
	return 0;
}

/* os_restore_mode --- restore the original mode of the console device */

void
os_restore_mode (fd)
int fd;
{
	/* no-op */
	return;
}

/* files_are_same --- deal with VMS struct stat */

int
files_are_same(char *newfile, SRCFILE *oldfile)
{
	struct stat st, *f1, *f2;

	f1 = &st;
	if (stat(newfile, f1) != 0) return 0;

	f2 = &oldfile->sbuf;
		/* compare device string */
#ifdef _USE_STD_STAT
	return (f1->st_dev == f2->st_dev
		/* and 48-bit file id cookie */
		&& f1->st_ino == f2->st_ino);
 #else
	return (strcmp(f1->st_dev, f2->st_dev) == 0
		/* and 48-bit file id cookie stored in 3 short ints */
		&& f1->st_ino[0] == f2->st_ino[0]
		&& f1->st_ino[1] == f2->st_ino[1]
		&& f1->st_ino[2] == f2->st_ino[2]);
#endif
}

int
os_isatty(int fd)
{
	return (isatty(fd) > 0);
}

void
init_sockets(void)
{
}
