/* 
   Unix SMB/Netbios implementation.
   Version 1.9.
   Copyright (C) Andrew Tridgell 1994-1997

   This file is part of the port to OpenVMS
   General VMS support routines.
   Copyright (C) Eckart Meyer 1996-1997
   
   This program 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 2 of the License, or
   (at your option) any later version.
   
   This program 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*	vms_sup.c
 *	V1.3			2-Mar-1997	IfN/Mey
 *
 *	Copyright E. Meyer <meyer@ifn.ing.tu-bs.de>
 *+
 * Samba VMS support.
 * Misc. routines not present on VMS.
 *
 * NOTE: includes.h *IS* included here.
 *-
 */

/* #define __FCNTL_LOADED 1 */
#include "includes.h"

#include <starlet.h>
#include <lib$routines.h>
#include <descrip.h>
#include <dvidef.h>
#include <quidef.h>
#include <jpidef.h>

char *VMSMakeFilename(char * __vms_buf, char * __file_spec);

/* SAMBA debugging */
extern int DEBUGLEVEL;

struct itm_list_2 {
	unsigned short len;
	unsigned short code;
	void *bufadr;
};

struct itm_list_3 {
	unsigned short len;
	unsigned short code;
	void *bufadr;
	short int *retadr;
};

struct vms_timbuf {
	short int year;		/* year since 0 */
	short int mon;		/* month of year */
	short int mday;		/* day of month */
	short int hour;
	short int min;
	short int sec;
	short int hundred;
};

BOOL vms_lp_share_modes(i)	/* HACK to switch off share modes */
{
	return(FALSE);
}

/*
 * vms_set_process_name
 */

void vms_set_process_name(char *name)
{
	int lgth;
	char buf[16];	/* max 15 characters */
	struct dsc$descriptor prcnam;

	lgth = strlen(name);
	if (lgth > 15) lgth = 15;
	strncpy(buf,name,lgth);
	prcnam.dsc$a_pointer = buf;
	prcnam.dsc$w_length = lgth;
	sys$setprn(&prcnam);
}


/*
 * vms_process_exists
 */
int vms_process_exists(int pid)
{
	unsigned long result;
	unsigned long sts;
	long item;

	item = JPI$_PID;
	sts = lib$getjpi(&item,&pid,0,&result,0,0);
	return(sts&1);
}


/*
 * Make a suitable VMS file name from a DOS long filename (in UNIX syntax).
 * Characters not possible for a VMS filename are replaced by '__NN' where
 * 'NN' is the two-digit hex ASCII code.
 *
 * The resultant string is returned in a static buffer.
 */
static char buffer[256];
char *vms_make_name(char *fname)
{
	char escbuf[5];
	char *p,*q;
	int dotcnt = 0;
	int ulcnt = 0;
	int i;

	for (p=fname, q=buffer; *p != '\0'; p++) {
/*
 * If we find a colon in the name, we assume the filename is already in
 * VMS format. Copy original string over and return.
 */
		if (*p == ':') {
			strcpy(buffer,fname);
			return(buffer);
		}
		if ((*p < 'a' || *p > 'z')
		&& (*p < 'A' || *p > 'Z')
		&& (*p < '0' || *p > '9')
		&& *p != '$' && *p != '-' && *p != '/'
		&& *p != '*' && *p != '?' && *p != '%'
		&& (*p != '.' || dotcnt >= 1)
		&& (*p != '_' || ulcnt >= 1)) {
			if (*p == '_') {
				sprintf(escbuf,"_%02X_",*p & 0xFF);
				strcpy(q, escbuf);
			}
			else {
				ulcnt = 0;
				sprintf(escbuf,"__%02X",*p & 0xFF);
				strcpy(q, escbuf);
			}
			q += 4;
		}
		else {
			*q++ = *p;
			if (*p == '.') dotcnt++;
			if (*p == '/') dotcnt = 0;
			if (*p == '_') {
				ulcnt++;
			}
			else {
				ulcnt = 0;
			}
		}
	}
	*q = '\0';
	return(buffer);
}

/*
 * Make DOS filename from a VMS name.
 */
char *vms_get_name(char *fname)
{
	char escbuf[5];
	char *p,*q;
	int dotcnt = 0;
	int ulcnt = 0;
	int i;

	for (p=fname, q=buffer; *p != '\0'; p++) {
		if (*p == '_') {
			if (ulcnt == 0) {	/* if first underline */
				ulcnt++;
			}
			else {
				p++;
				if (*p == '\0') break;
				sscanf(p,"%02X",q++);
				p++;
				ulcnt = 0;
				if (*p == '\0') break;
			}
		}
		else {
			if (ulcnt > 0) {	/* if single underline */
				*q++ = '_';
				ulcnt = 0;
			}
			*q++ = *p;
		}
	}
	if (ulcnt > 0) *q++ = '_';
	*q = '\0';
	return(buffer);
}


#define QUI_OK	(QUI$M_QUEUE_AVAILABLE | QUI$M_QUEUE_BUSY | QUI$M_QUEUE_IDLE)
#define QUI_ERROR  (QUI$M_QUEUE_STALLED | QUI$M_QUEUE_UNAVAILABLE)

/*
 * vms_getqui_init
 *
 * Establish a $GETQUI context for a specified queue.
 */
BOOL vms_getqui_init(char *queue_name, print_status_struct *status)
{
	unsigned long sts;
	unsigned long search_flags;
	unsigned long queue_status;
	struct itm_list_3 queue_list[4];
	unsigned long iosb[2];
/*
 * Cancel any previous context.
 */
	sts = sys$getquiw(0,QUI$_CANCEL_OPERATION,0,0,0,0,0);
	if (!(sts&1)) return(False);
/*
 * Establish queue context.
 */
	queue_list[0].code = QUI$_QUEUE_STATUS;
	queue_list[0].bufadr = &queue_status;
	queue_list[0].len = sizeof(queue_status);
	queue_list[0].retadr = NULL;

	queue_list[1].code = QUI$_SEARCH_NAME;
	queue_list[1].bufadr = queue_name;
	queue_list[1].len = strlen(queue_name);
	queue_list[1].retadr = NULL;

	queue_list[2].code = QUI$_SEARCH_FLAGS;
	queue_list[2].bufadr = &search_flags;
	queue_list[2].len = sizeof(search_flags);
	queue_list[2].retadr = NULL;

	queue_list[3].code = 0;
	queue_list[3].len = 0;

	search_flags = QUI$M_SEARCH_WILDCARD;

	sts = sys$getquiw(0,QUI$_DISPLAY_QUEUE,0,queue_list,iosb,0,0);
	DEBUG(5,("getqui_init:  sts = %08X, iosb = %08X\n",sts,iosb[0]));
	if ( (!(sts&1)) ||  (!(iosb[0]&1)) ) return(False);

	DEBUG(5,("              queue_status = %08X\n",queue_status));

	if (status) {
		if (queue_status & QUI_OK) {
			strcpy(status->message,"Queue status is OK");
			status->status=LPSTAT_OK;
		}
		else if (queue_status & QUI_ERROR) {
			strcpy(status->message,"Queue status is ERROR");
			status->status=LPSTAT_ERROR;
		}
		else {
			strcpy(status->message,"Queue status is STOPPED");
			status->status = LPSTAT_STOPPED;
		}
	}

	return(True);
}

/*
 * vms_getqui_entry
 *
 * Get job information for next job.
 */
BOOL vms_getqui_entry(print_queue_struct *buf, BOOL first)
{
	unsigned long sts;
	unsigned long search_flags;
	struct itm_list_3 job_list[8];
	unsigned long iosb[2];
	char job_name[40];
	char username[13];
	long job_size;
	long entry_number;
	unsigned long job_status;
	unsigned long submission_time[2];
	short int job_name_len;
	short int username_len;
	struct vms_timbuf timbuf;

	job_list[0].code = QUI$_JOB_NAME;
	job_list[0].bufadr = job_name;
	job_list[0].len = 39;
	job_list[0].retadr = &job_name_len;

	job_list[1].code = QUI$_USERNAME;
	job_list[1].bufadr = username;
	job_list[1].len = 12;
	job_list[1].retadr = &username_len;

	job_list[2].code = QUI$_JOB_SIZE;
	job_list[2].bufadr = &job_size;
	job_list[2].len = sizeof(job_size);
	job_list[2].retadr = NULL;

	job_list[3].code = QUI$_ENTRY_NUMBER;
	job_list[3].bufadr = &entry_number;
	job_list[3].len = sizeof(entry_number);
	job_list[3].retadr = NULL;

	job_list[4].code = QUI$_JOB_STATUS;
	job_list[4].bufadr = &job_status;
	job_list[4].len = sizeof(job_status);
	job_list[4].retadr = NULL;

	job_list[5].code = QUI$_SUBMISSION_TIME;
	job_list[5].bufadr = submission_time;
	job_list[5].len = sizeof(submission_time);
	job_list[5].retadr = NULL;

	job_list[6].code = QUI$_SEARCH_FLAGS;
	job_list[6].bufadr = &search_flags;
	job_list[6].len = sizeof(search_flags);
	job_list[6].retadr = NULL;

	job_list[7].code = 0;
	job_list[7].len = 0;

	search_flags = QUI$M_SEARCH_ALL_JOBS;

	sts = sys$getquiw(0,QUI$_DISPLAY_JOB,0,job_list,iosb,0,0);
	DEBUG(5,("getqui_entry: sts = %08X, iosb = %08X\n",sts,iosb[0]));
	if ( (!(sts&1)) ||  (!(iosb[0]&1)) ) return(False);

	job_name[job_name_len] = '\0';
	username[username_len] = '\0';

	DEBUG(5,("              entry_number = %d\n",entry_number));
	DEBUG(5,("              job_size     = %d\n",job_size));
	DEBUG(5,("              job_status   = %08X\n",job_status));
	DEBUG(5,("              job_name     = {%s}\n",job_name));
	DEBUG(5,("              username     = {%s}\n",username));

	buf->job = entry_number;
	buf->size = job_size * 512;
	buf->status = (job_status & QUI$M_JOB_EXECUTING)?LPQ_PRINTING:LPQ_QUEUED;
	buf->time = time(NULL);
	StrnCpy(buf->user,username,sizeof(buf->user)-1);
	StrnCpy(buf->file,job_name,sizeof(buf->file)-1);

	sts = sys$numtim(&timbuf,submission_time);
	if (sts&1) {
		struct tm tm;
		int i;
		
		i = timbuf.year / 100;
		i = timbuf.year - (i * 100);
		tm.tm_year = i;
		tm.tm_mon = timbuf.mon;
		tm.tm_mday = timbuf.mday;
		tm.tm_hour = timbuf.hour;
		tm.tm_min = timbuf.min;
		tm.tm_sec = timbuf.sec;
		buf->time = mktime(&tm);
/*		buf->time += GMT_TO_LOCAL*TimeDiff(buf->time); /* have to fix this */
	}

	return(True);
}

/*
 * statfs - return mounted file system info
 */

#include <sys/vfs.h>

int vms_statfs(char *path, struct statfs *buf)
{
	unsigned long iosb[2];
	struct itm_list_3 dvi_itm[4];
	unsigned long sts;
	char *p;
	char name[256];
	struct dsc$descriptor dev;

	p = VMSMakeFilename(name,path);
DEBUG (3,("vms_statfs: path = \"%s\", vms_file = \"%s\"\n",path,name));
	if (p == NULL) {
		DEBUG (0,("vms_statfs: VMSMakeFilename ERROR\n"));
		return(-1);
	}

	dvi_itm[0].code = DVI$_MAXBLOCK;
	dvi_itm[0].len = 4;
	dvi_itm[0].bufadr = &buf->f_blocks;	/* total blocks */
	dvi_itm[0].retadr = NULL;

	dvi_itm[1].code = DVI$_FREEBLOCKS;
	dvi_itm[1].len = 4;
	dvi_itm[1].bufadr = &buf->f_bfree;	/* free blocks */
	dvi_itm[1].retadr = NULL;

	dvi_itm[2].code = DVI$_MAXFILES;
	dvi_itm[2].len = 4;
	dvi_itm[2].bufadr = &buf->f_files;	/* total files in file system */
	dvi_itm[2].retadr = NULL;

	dvi_itm[3].code = 0;
	dvi_itm[3].len = 0;

	dev.dsc$a_pointer = name;
	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;

	sts = sys$getdvi(0,0,&dev,&dvi_itm,iosb,0,0,0);
	if ( (!(sts&1)) || (!(iosb[0]&1)) ) {
		DEBUG (0,("vms_statfs: $GETDVI ERROR: sts = %08X, iosb = %08X\n",
			sts,iosb[0]));
		return(-1);
	}
	
	buf->f_type = 0;
	buf->f_bsize = 512;		/* unsupported */
	buf->f_bavail = buf->f_bfree;	/* avaiable blocks to non-super-user */
	buf->f_ffree = -1;		/* free files, unsupported */
DEBUG (4,("   f_blocks = %d\n",buf->f_blocks));
DEBUG (4,("   f_bfree = %d\n",buf->f_bfree));
DEBUG (4,("   f_files = %d\n",buf->f_files));
	return(0);
}

/*
 * setgroups - dummy
 */

int vms_setgroups(int ngroups, gid_t gidset[])
{
	return(0);
}


/*
 * waitpid (T.B.S.)
 */
pid_t vms_waitpid(pid_t pid, int *stat, int options)
{
	return(0);
}


/*
 * delete
 *
 * Get the VMS filespec and delete all versions of the file.
 */
int vms_delete (char *path)
{
	char buf[256];
	int sts;
        struct dsc$descriptor nam;
	char *p;

  p = VMSMakeFilename(buf, path);
  if(!p)
  {
	errno = ENOENT;
	return(-1);
  }
  strcat(buf,";*");
  nam.dsc$a_pointer = buf;
  nam.dsc$w_length = strlen(nam.dsc$a_pointer);
  nam.dsc$b_class = DSC$K_CLASS_S;
  nam.dsc$b_dtype = DSC$K_DTYPE_T;
  sts = lib$delete_file(&nam, 0, 0, 0, 0, 0, 0, 0, 0);
  if( 1 == sts )
	return(0);
  DEBUG(1,("VMS-error %d deleting %s\n", sts, buf));
  errno = EACCES;
  return -1;
}


#ifdef rename
#undef rename
int rename (const char *__old, const char *__new);
#endif
/*
 * rename
 *
 * Rename files. Take care of VMS directory files.
 */
int vms_rename(char *oldname, char *newname)
{
	char buf1[256];
	char buf2[256];
	char *p;

	DEBUG(3,("vms_rename: was: %s -> %s\n",oldname,newname));
	VMSMakeFilename(buf1, oldname);
	VMSMakeFilename(buf2, newname);

/* Do we need the following??? */
        p = strrchr(buf1, '.');
        if( p && !strcmp(p, ".DIR")) {
                strcat(buf2, ".DIR");
	}
/*
 * If the filename does not contain a dot, VMS automatically uses
 * the file extension from the input file.
 * Thus, if the client request a rename to an empty file extension, we
 * add a single dot to the end of the new filename which prevents VMS
 * from using the original one.
 */
	p = strrchr(buf2, ']');		/* isolate file name from path */
	if (!p) p = buf2;
	p = strchr(p, '.');		/* any dots in file name ? */
	if (!p) strcat(buf2, ".");	/* no, append one */ 

	DEBUG(3,("vms_rename: VMS: %s -> %s\n",buf1, buf2));
	return(rename(buf1, buf2));
}


/*
 * mkdir
 *
 * Yes, even VAXC has mkdir. But we would like to really know,
 * what mkdir() really does...
 *
 * This is a special version for Samba: We ignore 'mode' and just
 * use the parent directory protection.
 */
int vms_mkdir( char *name, mode_t mode, ...)
{
	char buf[256], *cp, *subdir;
	int sts;
/*	unsigned short penable = 0xFFF0;  /* copy systemprotect from parent */
	unsigned short penable = 0;	  /* copy all from parent! */
	unsigned short protect = 0;
	struct dsc$descriptor nam;
	const char prot_unix2vms[8]={0,4,10,14,1,5,11,15};

	DEBUG(0,("vms_mkdir: %s, mode: %07o\n", name, mode));
	subdir=strrchr( name,'/');
	if (subdir) {
		subdir++;
	}
	else {
		subdir = name;
	}
	cp=strrchr( name, '.');
	errno = EACCES;
	if( cp  &&  cp != name) return( -1); /* '.' in directory specification 
                                    i.e.  K:\> mkdir  aaa.bb         */

	strcpy(buf,"[]");
	nam.dsc$a_pointer = VMSMakeFilename(buf+2, name);
	if(!nam.dsc$a_pointer) {
		return(-1);
	}
	cp=strrchr( nam.dsc$a_pointer, '.');
	if(cp && strcmp( subdir, cp+1) ) {
		errno = EEXIST;
		if( !strcmp( cp, ".dir") )return -1;
		if( !strcmp( cp, ".DIR") )return -1;
		errno = EACCES;
	}   
	cp=strchr( nam.dsc$a_pointer, ']');
	if (!cp) nam.dsc$a_pointer = buf;
	DEBUG(8,("  -> %s\n", nam.dsc$a_pointer));
	cp=strchr( nam.dsc$a_pointer, ']');
	if (!cp) return(-1);

	*cp = '.';
	strcat( cp, "]");
	nam.dsc$w_length = strlen(nam.dsc$a_pointer);
	nam.dsc$b_class = DSC$K_CLASS_S;
	nam.dsc$b_dtype = DSC$K_DTYPE_T;

#if 1	/* don't use currently */
	if (getenv("SAMBA_ALTERNATE_DIRECTORY_PROTECTION")) {
		protect = prot_unix2vms[mode&7];
		mode >>= 3;
		protect <<= 4; /*world*/
		protect |= prot_unix2vms[mode&7];
		mode >>= 3;
		protect <<= 4; /*group*/
		protect |= prot_unix2vms[mode&7];
		/*mode >>= 3;*/
		protect <<= 4; /*owner*/

		protect = ~protect;

		penable = 0xFFF0;  /* copy systemprotect from parent */
	}
#endif

	sts = lib$create_dir( &nam, 0, &penable, &protect, 0, 0);

	DEBUG(8,("mkdir: sts = %08X, name = %s, mask = %04X\n", sts, nam.dsc$a_pointer, protect));
	return( sts & 1) ? 0 : -1;
}


#ifdef delete
#undef delete
int delete(const char *__path);
#endif
/*
 * rmdir
 *
 * Get the VMS directory filespec and delete the <path>.DIR;1 file.
 */
int vms_rmdir (char *path)
{
	char buf[256];
	int sts;
	char *p;

	p = VMSMakeFilename(buf, path);
	if (p == NULL) {
		errno = ENOTDIR;
		return(-1);
	}
	strcat(buf,";1");
	DEBUG(3,("Deleting directory %s -> %s\n",path,buf));
	sts = delete(buf);
	if (sts < 0) {
		DEBUG(3,("rmdir: errno = %d, vaxc$errno = %08X\n",errno,vaxc$errno));		
		if (errno == EVMSERR) {
			if (vaxc$errno == 0x0001C032) errno = ENOTEMPTY;
		}
		return(-1);
	}
	return(0);
}


/*
 * ftruncate
 * (SAMBA only !? does it ever really shorten a file? - we only support
 * extension for now...)
 */

int vms_ftruncate(int f,off_t l)
{
/*
 * I believe that lseek does extension correctly...
 */
	if(lseek(f, l, SEEK_SET) != l)
		return(-1);
	return(0);
}

#ifndef __DECC
#define decc$to_vms shell$to_vms
#endif

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

static char vms_file[256];
static int vms_file_type;

static int vms_action_routine(char *s, int type)
{
	vms_file_type = type;
	strcpy(vms_file,s);
	return(1);
}

char *cvt_file_to_vms(char *unixfile)
{
	if (unixfile == NULL) return(NULL);
	strcpy(vms_file, unixfile);
	decc$to_vms(unixfile, vms_action_routine, 0, 1);
	return(vms_file);
}

/*
 * system
 *
 * This version of the system() call tries to detect an output redirection
 * in the UNIX compatible command and if found, spawns the subprocess
 * with SYS$OUTPUT assigned to the specified path.
 *
 * Input redirection is not supported - SYS$INPUT is set to NLA0:
 */
int vms_system(const char *cmdline)
{
	char buf[256];
	$DESCRIPTOR(in,"NLA0:");
	$DESCRIPTOR(out,"SYS$OUTPUT");
	$DESCRIPTOR(cmd,"");
	unsigned long sts;
	unsigned long compl;
	char *p, *q;

	strcpy(buf,cmdline);
	cmd.dsc$a_pointer = buf;
	if (buf[0] == '\"') cmd.dsc$a_pointer++;

	if(strlen(buf) > 0)
		if (buf[strlen(buf) - 1] == '\"') buf[strlen(buf) - 1] = '\0';
	p = strrchr(cmd.dsc$a_pointer,'>');
	if (p != NULL) {
		for (q = p + 1; *q == ' ' && *q != '\0'; q++);
		out.dsc$a_pointer = cvt_file_to_vms(q);
		if (strcmp(out.dsc$a_pointer,"/dev/null") == 0) {
			out.dsc$a_pointer = in.dsc$a_pointer;
		}
		if (p != cmd.dsc$a_pointer && *(p-1) == '>') p--;
		*p = '\0';
	}
	cmd.dsc$w_length = strlen(cmd.dsc$a_pointer);
	out.dsc$w_length = strlen(out.dsc$a_pointer);
	sts = lib$spawn(&cmd,&in,&out,0,0,0,&compl,0,0,0,0,0,0);
DEBUG(5,("system: {%s}, output: {%s}\n",cmd.dsc$a_pointer,out.dsc$a_pointer));
DEBUG(5,("        sts = %08X, compl = %08X\n",sts,compl));
	if (!(sts&1)) return(127);
	return((int)compl);
}


/* ======= keyboard ========================================== */

/*
 * vms_read_keyboard
 *
 * This routine has to read from keyboard and to check the socket for
 * incoming data (see wait_keyboard in client.c).
 *
 * As we can't use select() for the keyboard we call the keyboard read
 * routine synchroneously. Before, a cyclic timer is started which
 * checks the network. Note that the checking routine is called at
 * AST level. The only drawback is that there is a delay until a
 * network action is noticed, but this seems no problem here.
 *
 * To enable command line editing we use the SMG routines to read from
 * keyboard.
 *
 * I tried to strictly separate the VMS specific code from the base
 * samba code. So this routine is in a common VMS support module but has
 * a callback into the client.c. To avoid unresolved symbols with executables
 * other than smbclient, the callback address is included as a parameter
 * to vms_read_keyboard.
 */
#include <smgdef.h>
#include <smgmsg.h>
#include <ssdef.h>
#include <smg$routines.h>

static unsigned long smg_kb_id = -1;
static long time_bin[2];
char time_string[] = "0 00:00:01.01";
$DESCRIPTOR(time_dsc,time_string);
static char *smbbuffer;
static void (* client_chk_routine)();

void timer_ast(int reqid)
{
	unsigned long sts;
	void vms_check_net(char * __buffer, int __flag);

/*	DEBUG (3,("== timer AST ==\n")); /**/
	(* client_chk_routine)(smbbuffer);	/* Callback to client.c */
	sts = sys$setimr(0,time_bin,timer_ast,reqid,0);	/* restart */
}

int vms_read_keyboard(char *prompt, char *line, int size, char *buffer,
		void (* chk_routine)() )
{
	unsigned long sts;
	int retlen;
	struct dsc$descriptor_s dsc_line, dsc_prompt;
	struct dsc$descriptor_s *prm;
	void vms_exit_handler();

	smbbuffer = buffer;
	client_chk_routine = chk_routine;
	dsc_line.dsc$a_pointer = line;
	dsc_line.dsc$w_length = size - 1;
	dsc_line.dsc$b_class = DSC$K_CLASS_S;
	dsc_line.dsc$b_dtype = DSC$K_DTYPE_T;
	if (prompt == NULL) {
		prm = NULL;
	}
	else {
		dsc_prompt.dsc$a_pointer = prompt;
		dsc_prompt.dsc$w_length = strlen(prompt);
		dsc_prompt.dsc$b_class = DSC$K_CLASS_S;
		dsc_prompt.dsc$b_dtype = DSC$K_DTYPE_T;
		prm = &dsc_prompt;
	}

	if (smg_kb_id == -1) {
		sts = smg$create_virtual_keyboard(&smg_kb_id,0,0,0,0);
		if (!(sts&1)) {
			DEBUG (0,("can't create SMG keyboard - sts = %08X\n",sts));
			return(-1);
		}
		sts = sys$bintim(&time_dsc,time_bin);
		if (!(sts&1)) {
			DEBUG (0,("$BINTIM error - sts = %08X\n",sts));
			return(-1);
		}
	}
/*
 * Start cyclic timer.
 */
	sts = sys$setimr(0,time_bin,timer_ast,1,0);
	if (!(sts&1)) {
		DEBUG (0,("$SETIMR error - sts = %08X\n",sts));
		return(-1);
	}
/*
 * Read from keyboard.
 */
	retlen = 0;
	sts = smg$read_string(&smg_kb_id, &dsc_line, prm,
				0, 0, 0, 0, &retlen, 0,0,0,0,0,0);
	*(dsc_line.dsc$a_pointer + retlen) = '\0';
/*
 * We got a command line. Cancel timer and return.
 */
	sys$cantim(1,0);
	if (sts == SS$_NORMAL) return(0);
	if (sts == SMG$_EOF) return(-1);
	DEBUG (0,("keyboard read error: sts = %08X\n",sts));
	return(-1);
}

#if 1	/* for debug only */
/************************************************************/
/* dmphx - dump a buffer to the debug log in hex and ascii  */
/************************************************************/
void _dmphx(char *msg,int len)
{
  int i, j;
  unsigned char *v, *w;

  v = w = (unsigned char *)msg;
  j = len;
  while (j>0) {
     if (j>16) i = 16; else i=j;
     for(;i>0;i--,v++) Debug1(" %02x",*v);
     if (j<16) for(i=16-j;i>0;i--) Debug1("   ");
     Debug1("        ");
     if (j>16) i = 16; else i=j;
     for(;i>0;i--,w++) Debug1("%c",(*w>0x1f && *w<0x80)? *w : '.');
     Debug1("\n");
     j -= 16;
  }
}
#endif
