/*	
 * Special vms version for stat().
 * Also included lstat() (mapped to stat()) and fstat().
 *
 * NOTE: SLIGHTLY MODIFIED FOR USE WITH SAMBA!
 *
 * Since we are using UNIX filespecs, stat() of a directory will not work.
 * We try to find out if we have to stat() a directory and then convert it
 * to a .DIR file.
 *
 * use DECC$TO_VMS (SHELL$TO_VMS) here:
 *
 *	decc$to_vms(char *unix_file_spec, int *action_routine,
 *		int wild_flag, int no_directory)
 *
 *	unix_file_spec		- a UNIX style file specification
 *	action routine		- action_routine
 *	wild_flag		- if 1, pass each file to action_routine.
 *				   only existing files are passed.
 *	no_directory		- 0: directory is allowed
 *				- 1: prevent expansion as directory string
 *				- 2: force into dir spec (??)
 *
 *	action_routine(char *vms_file_spec, int type)
 *
 *	vms_file_spec		- one (expanded, if wild_flag is set) vms
 *				  filespec.
 *	type			- 0: some strange "foreign" file...
 *				- 1: normal file
 *				- 2: directory file
 *
 * These Routines have been tested on Alpha with DEC C and on VAX
 * with VAXC. I don't have DEC C for VAX yet.
 */

#define const

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include "incl.h"
#include <starlet.h>
#include <lib$routines.h>
#include <dvidef.h>
#include <lnmdef.h>
#include <descrip.h>
#ifndef __DECC
#define decc$to_vms shell$to_vms
#define decc$translate_vms shell$translate_vms
#endif

#if 1	/* for use with samba's debugging */	/* SAMBA */
extern int DEBUGLEVEL;
#define DEBUG(level,body) ((DEBUGLEVEL>=(level))?(Debug1 body):0)
int Debug1();
#else
int DEBUGLEVEL = 8;
#define DEBUG(level,body) ((DEBUGLEVEL>=(level))?(printf body):0)
#endif

char *vms_make_name(char * __name);	/* SAMBA */

char *decc$translate_vms(char * __vmsfile);
int decc$to_vms(char * __unixfile,
		int (* __action_routine)(char * __vmsfile, int __type),
		int __wild_flag,int __no_dir_flag);

struct item_list {
	short len;
	short code;
	void *bufadr;
	int *retlen;
};

struct vms_ino {
	short fid1;
	short fid2;
	short fid3;
};

#define MAXFILLEN 256

static char *vms_stat_file = NULL;
static int vms_stat_file_type;

static int vms_stat_action_routine(char *s, int type)
{
	vms_stat_file_type = type;
	if (vms_stat_file != NULL) strcpy(vms_stat_file,s);
	return(1);
}

static char *trnlnm(char *lnm)
{
	$DESCRIPTOR(dsc,"");
	$DESCRIPTOR(lnmtab,"LNM$FILE_DEV");
	int n = 0;
	static char buf[256];
	struct item_list lnm_items[] = {
		{255, LNM$_STRING, NULL, NULL},
		{0,0,NULL,NULL}
		};
	int attr = LNM$M_CASE_BLIND;
	long sts;

	lnm_items[0].bufadr = buf;
	lnm_items[0].retlen = &n;
	dsc.dsc$a_pointer = lnm;
	dsc.dsc$w_length = strlen(lnm);
	sts = sys$trnlnm(&attr, &lnmtab, &dsc, 0, lnm_items);
	if (sts&1) {
		buf[n] = '\0';
		return(buf);
	}
	else {
		return(NULL);
	}
}

static char buf0[MAXFILLEN];

char *VMSMakeFilename(char *vms_buf, char *file_spec)
{
	char *preturn = NULL;
	char buf1[MAXFILLEN];
	char buf2[MAXFILLEN];
	char *p;
	char *q;
	char *r;
	int i;
	int n;
	struct dsc$descriptor_s dev;

DEBUG (3,("== vms_file: file_spec: \"%s\"\n",file_spec)); /**/
	vms_stat_file = buf0;
#if 1						/* SAMBA */
	file_spec = vms_make_name(file_spec);
DEBUG (3,("                     -> \"%s\"\n",file_spec)); /**/
#endif
	strcpy(buf1,file_spec);
	p = buf1 + strlen(buf1) - 1;
	while (*p == '/'  &&  p != buf1) *p-- = '\0';
	if ( strcmp(buf1,".") == 0) {
		strcpy(buf1,"sys$disk:[]");
	}
	if ( strcmp(buf1,"..") == 0) {
		strcpy(buf1,"sys$disk:[.-]");
	}
DEBUG (8,("== vms_file: file_spec: \"%s\"\n",buf1)); /**/
	p = decc$translate_vms(buf1);
	if (p == NULL) p = file_spec;
DEBUG (8,(".. vms_file: file_spec: \"%s\"\n",p)); /**/
	strcpy(buf1,p);
	buf2[0] = '\0';
	p = buf1 + strlen(buf1) - 1;
	if (*p == '~'  &&  p != buf1) *p = '$';
	while (*p == '/'  &&  p != buf1) *p-- = '\0';
restart:
	strcpy(buf0, buf1);
DEBUG (8,("** vms_file: file_spec: \"%s\"\n",buf0)); /**/
	vms_stat_file_type = -1;
	i = decc$to_vms(buf1, vms_stat_action_routine, 0, 0);
DEBUG (8,("**** vms_file: i=%d, \"%s\"\n",i,buf0)); /**/
	p = buf1;
/*	if (*p == '/'  &&  strchr(p+1,'/') == NULL) { /**/
	if (*p == '/') { /**/
/*
 * Input is "/name" which must a device name, or it is "/".
 * Check for "/" which translates to "SYS$SYSDEVICE".
 */
		if (strlen(p) == 1) {
			strcat(p,"SYS$SYSDEVICE");
		}
/*
 * A real device ends up in ":".
 * NOTE: There is a bug in vaxc$to_vms in that it sometimes returns
 * the device name "dev:" and sometimes not. If it returns it, make the
 * translation invalid which forces the getdvi code below. Yes, that's a
 * hack. decc$to_vms should always return the device if it exists.
 */
		q = buf0;
		if (q[strlen(q)-1] == ':') i = 0;
/*
 * Check for a concealed device.
 */
		strcpy(buf2,p);
		p = buf2;
		p[strlen(p)+1] = '\0';		/* second 0 at the end */
		r = strchr(p+1,'/');
		if (r == NULL) r = p + strlen(p);
		*r++ = '\0';	/* extract first (or only) part */
DEBUG (8,("p: \"%s\"   r: \"%s\"\n",p,r)); /**/
		q = trnlnm(p+1); /**/
		if (q != NULL) {
DEBUG (8,("q: \"%s\"\n",q)); /**/
			p = buf0;
			strcpy(p,q);
			decc$to_vms(q, vms_stat_action_routine, 0, 0);
			q = p + strlen(p) - 2;
			if (q[0] == '.' && q[1] == ']') {
				strcpy(q,"]");
			}
/*			q = p + strlen(p) - 1; */
			if (strchr(p,']') == NULL && strchr(p,':') == NULL) {
				strcat(p,":");
			}
DEBUG (8,("p: \"%s\"\n",buf0)); /**/
			q = decc$translate_vms(p);
			strcpy(buf1,q);
			if (*r != '\0') {
				strcat(buf1,"/");
				strcat(buf1,r);
			}
DEBUG (8,("buf1: \"%s\"\n",buf1)); /**/
			goto restart;	/* sorry... */
		}
		if (*r != '\0') *--r = '/';
/*
 * No concealed device.
 * Check for a physical device.
 */
		if (i == 0) {
			vms_stat_file_type = -1;
			dev.dsc$a_pointer = p + 1;
			dev.dsc$w_length = strlen(dev.dsc$a_pointer);
			dev.dsc$b_class = DSC$K_CLASS_S;
			dev.dsc$b_dtype = DSC$K_DTYPE_T;
			i = DVI$_MAXBLOCK;
			i = lib$getdvi(&i,0,&dev,&n,0,0);
DEBUG (8,("//// vms_file: i=%08X, maxblock=%d\n",i,n)); /**/
			if (i&1) {
				i = 1;
				strcpy(buf0,p+1);
				strcat(buf0,":[000000]000000.DIR");
			}
			else {
				i = 0;
			}
		}
	}
	if (vms_stat_file_type == 2) {
		strcpy(buf2, buf0);
#ifdef asdasd
		strcpy(buf0, buf1);
		i = decc$to_vms(buf1, vms_stat_action_routine, 0, 1);
#endif
		p = buf2;
DEBUG (8,("++++ vms_file: i=%d, \"%s\"\n",i,buf2)); /**/
		if (p[strlen(p)-1] == ']') {
			p = strrchr(buf2,'.');
			if (p == NULL) {
				p = strrchr(buf2,'[');
				strcpy(buf0,"[000000]");
			}
			else
			{
				strcpy(buf0,"]");
			}
			strcat(buf0,p+1);
			buf0[strlen(buf0)-1] = '\0';
			strcat(buf0,".DIR");
			*p = '\0';
			strcat(buf2, buf0);
			strcpy(buf0, buf2);
			i = 1;
DEBUG (8,("**** vms_file: \"%s\"\n",buf0)); /**/
		}
		else {
			strcat(buf0, ".dir");
DEBUG (8,("**** vms_file: i=%d, \"%s\"\n",i,buf0)); /**/
		}
	}
	if (i > 0) {
DEBUG (3,("** vms_file: \"%s\"\n",buf0)); /**/
		if (vms_buf) strcpy(vms_buf, buf0);
		preturn = buf0;
		i = 1;
	}
	else {
DEBUG (3,("** vms_file: no filespec available\n")); /**/
		i = -1;
	}
	return(preturn);
}


int vms_stat(char *file_spec, struct stat *st_buf)
{
	char buf[MAXFILLEN];
	int status;
	char *p;
	time_t t;
	short ino2;
	struct vms_ino *inofid;

	p = VMSMakeFilename(buf, file_spec);
	if (p) {
		status = stat(buf, st_buf);
/*
 * To be compatible to UNIX, st_ino must be 1 32-bit words. VMS uses the
 * 3 16-bit words from the FID. As the goal is to uniquely identify the file by
 * st_ino, we believe that using the 1. and 3. word of the FID is sufficient,
 * since the 2. word is a sequence number only.
 */
		inofid = (struct vms_ino *)(&st_buf->st_ino);
		ino2 = inofid->fid3;
		inofid->fid3 = inofid->fid2;
		inofid->fid2 = ino2;
/*
 * SAMBA
 *
 * A quick and (very) dirty hack to overcome the lack of real
 * access checking (who want's to program it?). We just tell
 * the client the file may be writable, so the client will at least
 * *try* to do it's work. Then the underlying VMS security will of course reject
 * the request if ACL's won't allow. The client will issue an error
 * message, but without it will not even try if standard access says
 * NO but ACL's say YES.
 */
                st_buf->st_mode |= S_IWOTH;    /* probably lie! */
/*
 * SAMBA
 * The meaning of creation time and modification time seems to be
 * reversed in LANmanager. Thus, samba changes the two times.
 */
		if (getenv("SAMBA_SWAP_FILE_TIMES")) {
			t = st_buf->st_mtime;
			st_buf->st_mtime = st_buf->st_ctime;
			st_buf->st_ctime = t;
		}

		if (status == -2) {
/*
 * SAMBA
 * samba requires that stat() returns 0 in order to display the file.
 * since we want to see them at least with size 0 we fake a stat buffer.
 */
			char *p;
			memset(st_buf,0,sizeof(struct stat));
			p = strrchr(buf,'.');
			if (p != NULL  &&  strcmp(p,".DIR") == 0) {
				st_buf->st_mode |= S_IFDIR;
			}
			else {
				st_buf->st_mode |= S_IFREG;
			}
			status = 0;
		}
	}
	else {
		status = -1;
		errno = ENOENT;
	}
DEBUG (3,("** vms_stat: st = %d, mode = %06o\n",status,st_buf->st_mode));
DEBUG (3,("             size = %d\n",st_buf->st_size));
DEBUG (8,("             ino = %08X, %04X\n",st_buf->st_ino,st_buf->_st_ino3));
DEBUG (8,("             fab_rfm = %d\n",st_buf->st_fab_rfm));
DEBUG (8,("             fab_rat = %d\n",st_buf->st_fab_rat));
DEBUG (8,("             fab_mrs = %d\n",st_buf->st_fab_mrs));
	return(status);
}


int vms_lstat(char *file_spec, struct stat *st_buf)
{
	return(vms_stat(file_spec,st_buf));
}


int vms_fstat(int fd, struct stat *st_buf)
{
	int status;
	time_t t;
	short ino2;
	struct vms_ino *inofid;

	status = fstat(fd, st_buf);

	inofid = (struct vms_ino *)(&st_buf->st_ino);
	ino2 = inofid->fid3;
	inofid->fid3 = inofid->fid2;
	inofid->fid2 = ino2;

	st_buf->st_mode |= S_IWOTH;    /* probably lie! see stat() */
/*
 * SAMBA
 * The meaning of creation time and modification time seems to be
 * reversed in LANmanager. Thus, samba changes the two times.
 */
	if (getenv("SAMBA_SWAP_FILE_TIMES")) {
		t = st_buf->st_mtime;
		st_buf->st_mtime = st_buf->st_ctime;
		st_buf->st_ctime = t;
	}

	if (status == -2) {
/*
 * samba requires that stat() returns 0 in order to display the file.
 * since we want to see them at least with size 0 we fake a stat buffer.
 */
		char *p;
		memset(st_buf,0,sizeof(struct stat));
		st_buf->st_mode |= S_IFREG;
		status = 0;
	}
	return(status);
}

/*
 * vms_get_attr
 *
 * This routine is added to make the extra elements in the DECC
 * stat structure available.
 */

#include <fab.h>
struct vms_attr {
	unsigned int	fab_rfm;
	unsigned int	fab_rat;
	unsigned int	fab_fsz;
	unsigned int	fab_mrs;
};

int vms_get_attr(char *fname, struct vms_attr *attr)
{
	int status;
	struct stat st_crtl;

	if (attr == NULL || fname == NULL) return(-1);
	status = stat(fname,&st_crtl);
	if (status == 0) {
		attr->fab_rfm = st_crtl.st_fab_rfm;
		attr->fab_rat = st_crtl.st_fab_rat;
		attr->fab_fsz = st_crtl.st_fab_fsz;
		attr->fab_mrs = st_crtl.st_fab_mrs;
	}
}

int vms_is_textfile(char *fname)
{
	struct vms_attr st_attr;

	if (fname == NULL) return(FALSE);
	if (vms_get_attr(fname, &st_attr) < 0) return(FALSE);
	if (st_attr.fab_rfm == FAB$C_VAR  &&  (st_attr.fab_rat & FAB$M_CR))
		return(TRUE);
	return(FALSE);
}
