#pragma module	TFTP	"TFTP-1-A"
#define	__MODULE__	"TFTP"

/*
**++
**  FACILITY:  TFTP Client - Command Line Utility
**
**  MODULE DESCRIPTION:
**
**      This standalone program implements a TFTP Client functionality.
**
**  AUTHORS:	Ruslan R. Laishev
**		
**
**  CREATION DATE:  14-MAY-2004
**
**
**  MODIFICATION HISTORY:
**
**  2004-09-08 SMS.  Fixed the "va_end()" argument in "tftp__log()",
**                   eliminating the "%CC-I-LVALUECAST" compiler
**                   diagnostic.
**                   Changed to use the "fromfile" name as a default
**                   "tofile" name, making the "tofile" argument
**                   optional.
**                   Changed to use the "tofile" name (not the
**                   "fromfile" name) as the remote file name for a PUT
**                   operation.
**                   Changed the GETSTART, GETSTOP, PUTSTART, and
**                   PUTSTOP messages.
**                   Changed "/LOG" to "/VERBOSE".
**                   Included "/DEBUG" in the usage instructions.
**                   Removed TAB characters from the usage instructions.
**                   Reduced source width to 80 columns.
**
**--
*/


/*
**
**  INCLUDE FILES
**
*/

#include	<starlet.h>
#include	<stdlib.h>
#include	<lib$routines.h>
#include	<descrip.h>
#include	<climsgdef.h>
#include	<cli$routines.h>
#include	<ssdef.h>
#include	<lbr$routines.h>
#include	<hlpdef.h>
#include	<inet.h>
#include	<iodef.h>
#include        <in.h>
#include        <netdb.h>
#include        <ucx$inetdef.h>
#include        <time.h>
#include        <iosbdef.h>
#include	<iledef.h>
#include	<string.h>
#include	<rms.h>
#include	<stdarg.h>

#include	"tftpdef.h"
#include	"tftp_msg.h"

/*
**
**  MACRO DEFINITIONS
**
*/
#define INIT_DDESC(dsc)                 {(dsc).dsc$b_dtype = DSC$K_DTYPE_T;\
                        (dsc).dsc$b_class = DSC$K_CLASS_D;\
                        (dsc).dsc$w_length = 0;\
                        (dsc).dsc$a_pointer = 0;}

#define INIT_SDESC(dsc, len, ptr)       {(dsc).dsc$b_dtype = DSC$K_DTYPE_T;\
                        (dsc).dsc$b_class = DSC$K_CLASS_S;\
                        (dsc).dsc$w_length = (short) (len);\
                        (dsc).dsc$a_pointer = (char*)(ptr);}

#define DEBUG   if(debug_flag) \
 printf("%-24.24s:%-08.0u",__MODULE__,__LINE__);\
 if(debug_flag)printf

#define _PUT_MSG_(s)    {int msgvec[] = {1,s};sys$putmsg(msgvec);}

#ifdef	__VAX
	#define	LONGWORD_SZ	4
#else
	#define	LONGWORD_SZ	8
#endif


/*
**
**	CLI$ Declaration stuff
**
*/

extern	void	*TFTP_CLD;

$DESCRIPTOR(usage, "TFTP 2.1 Client - Command Line Utility\n\r \
 Usage:\n\r \
        $ TFTP :==$<dev>:<dir>TFTP.EXE\n\r \
\n\r \
        $ TFTP PUT host local_file [ remote_file ]\n\r \
        $ TFTP GET host remote_file [ local_file ]\n\r \
                /MODE = [ ASCII | BINARY | NETASCII | OCTET ]\n\r \
                        (ASCII = NETASCII, BINARY = OCTET.)\n\r \
                /VERBOSE        Enables progress messages.\n\r \
                /DEBUG          Enables debug messages.\n\r \
 Example:\n\r \
        $ TFTP PUT TFTPd.ZZ.NET zz.txt \"/zz/zz.txt\"\n\r \
        $ TFTP GET TFTPd.ZZ.NET zz.txt zz.lis /VERBOSE /MODE = NETASCII\n\r \
 Note:\n\r \
        Supported file record formats:\n\r \
                Stream_LF, Stream_CR, Stream, Fixed Length");

$DESCRIPTOR(p_host,	"HOST");
$DESCRIPTOR(p_cmd,	"CMD");
$DESCRIPTOR(p_fromfile,	"FROMFILE");
$DESCRIPTOR(p_tofile,	"TOFILE");

$DESCRIPTOR(q_mode,	"MODE");
$DESCRIPTOR(amode_dsc,	"ASCII/NETASCII");
$DESCRIPTOR(bmode_dsc,	"OCTET/BINARY");

$DESCRIPTOR(q_fdl,	"FDL");
$DESCRIPTOR(q_log,	"VERBOSE");
$DESCRIPTOR(q_debug,	"DEBUG");


$DESCRIPTOR(dsc_ucxdev,	"UCX$DEVICE");
$DESCRIPTOR(dsc_tcpipdev,"TCPIP$DEVICE");

int	gl_status	= SS$_NORMAL;
int	log_flag = 0,debug_flag	= 0,iotmo[2];
int	iotmo [2] = { -100000000, -1 };      /* 10 seconds  */

/*
**++
**  FUNCTIONAL DESCRIPTION:
**
**      Put message to standard output device (SYS$OUTPUT).
**
**  FORMAL PARAMETERS:
**
**      msgid:
**              VMS condition code
**              variable agriments list
**
**  RETURN VALUE:
**
**      VMS condition code
**
**
**--
*/
int	tftp__log	(
			int	msgid,
			...
			)
{
long	status,retvalue = msgid;
va_list	args;
char	buf[1024] = {"!%D "},msg_buf[1024],outadr[4];
struct	dsc$descriptor	opr_dsc,buf_dsc,fao_dsc;
int	argc,argl[32],idx,flag=15,lvl;

	/*
	** Get a message text with given msgid
	*/
	INIT_SDESC(fao_dsc,sizeof(buf)-4,&buf[4]);
	if ( !(1 & (status = sys$getmsg( msgid, &fao_dsc.dsc$w_length,
	 &fao_dsc, flag, &outadr))) )
		lib$signal(status);
	
	/*
	** Reorganize parameters list for $FAOL
	*/
	va_start(args,msgid);
	argl[0] = 0;
	for (idx = 1,va_count(argc);idx < argc;idx++,args += LONGWORD_SZ)
		argl[idx] = *((long *)args);
        va_end( args);

	/*
	** Format a message, put it to SYS$OUTPUT
	*/
	fao_dsc.dsc$a_pointer -=4;
	fao_dsc.dsc$w_length  +=4;
	INIT_SDESC(buf_dsc, sizeof(msg_buf),msg_buf);
	if ( !(1 & (status = sys$faol( &fao_dsc, &buf_dsc.dsc$w_length,
	 &buf_dsc, argl))) )
		lib$signal(status);

	lib$put_output(&buf_dsc);

	return	retvalue;
}

/*
**++
**  FUNCTIONAL DESCRIPTION:
**
**      Canceling all queued and processed request due of expiration timer.
**
**  FORMAL PARAMETERS:
**
**      reqidt: pointer to a network channel
**
**  RETURN VALUE:
**
**      None
**
**  SIDE EFFECTS:
**
**      Cancel _all_ request for the given I/O channel.
**--
*/
void    timer_ast       (
                        int     *reqidt
                        )
{
        sys$cantim(reqidt,0);
        sys$cancel(*reqidt);
}

int	net_write	(
		unsigned short	 chan,
			void	*buf,
		unsigned short	 buflen,
	struct sockaddr_in	*remhost
			)
{
int     status,remhostlen;
iosb    netiosb;
ile3	rsck_adrs = {sizeof(struct sockaddr_in), 0, remhost,
	 (unsigned short*) &remhostlen};

        /*
        **
        */
        status = sys$qiow (0,chan,IO$_WRITEVBLK,&netiosb,0,0,
                                        buf,buflen,&rsck_adrs,0,0,0);
        return  (1 & status)?netiosb.iosb$w_status:status;
}

int	net_read	(
		unsigned short	 chan,
			void	*buf,
		unsigned short	 bufsz,
		unsigned short	*retlen,
	struct sockaddr_in	*remhost
			)
{
int     status,remhostlen,reqidt = 0;
iosb    netiosb;
ile3	rsck_adrs = {sizeof(struct sockaddr_in), 0, remhost,
	 (unsigned short*) &remhostlen};

	*retlen	= 0;

	/*
	** Setup a Timer
	*/
	reqidt	= chan;
	if ( !(1 & (status = sys$setimr (0,&iotmo, timer_ast,&reqidt,0))) )
                        return  status;

	/*
	** Read data block
	*/
	if ( !(1 & (status = sys$qiow( 0, chan, IO$_READVBLK, &netiosb,
	 0, 0, buf, bufsz, &rsck_adrs, 0, 0, 0))) &&
	 ((status == SS$_CANCEL) || (netiosb.iosb$w_status == SS$_CANCEL)) )
		*retlen = 0;
	else	*retlen	= netiosb.iosb$w_bcnt;

	/*
	** Cancel a timer request
	*/
	status = sys$cantim(&reqidt,0);

	return  (1 & status)?netiosb.iosb$w_status:status;
}

/*
**++
**  FUNCTIONAL DESCRIPTION:
**
**      Perform TFTP GET from remote node.
**
**  FORMAL PARAMETERS:
**
**	chan:		An network I/O channel number
**	ipaddr:		A remote node IP address
**	filename:	A filename on remote node
**	remhost:	Socket Address IN structure
**	rab:		A RAB of the has been opened localy file
**	tmode:		A mode of transfering
**	bytes:		Total sent bytes
**
**  RETURN VALUE:
**
**      VMS condition code
**
**--
*/
int	tftp_do_get	(
		unsigned short	 chan,
		unsigned	 ipaddr,
	struct dsc$descriptor	*filename,
	struct sockaddr_in	*remhost,
		struct RAB	*rab,
		unsigned	 tmode,
		unsigned	*bytes
			)

{
unsigned status;
char	buf [ 1024];
struct tftp_pdu *pdu = (struct tftp_pdu *)&buf;
struct	dsc$descriptor buf_dsc;
$DESCRIPTOR(fao_dsc,"!AS\x00!AZ\x00");
unsigned short lbn,retlen = 0;

	/*
	** Send Read ReQuest (RRQ)
	*/
	pdu->tftp_pdu$w_opcode	= htons(TFTP_OPC$K_RRQ);
	INIT_SDESC(buf_dsc,sizeof(pdu->tftp_pdu$b_args),&pdu->tftp_pdu$b_args);

	if ( !(1 & (status = sys$fao(&fao_dsc,&retlen,&buf_dsc,filename,
			(tmode==TFTP__$K_NETASCII)?"netascii":"octet"))) )
		lib$signal(status);

	if ( !(1 & (status = net_write (chan,buf,2+retlen,remhost))) )
			return	status;

	/*
	** Start main loop processing input stream of blocks
	*/
	rab->rab$l_rbf = (char *)pdu->tftp_pdu$b_data;
	for (lbn = 1;;lbn++)
		{
		if ( !(1 & (status = net_read( chan, &buf, sizeof(buf),
		 &retlen, remhost))) )
                        return  status;

		status 	= ntohs(pdu->tftp_pdu$w_opcode);

		DEBUG("Got %u bytes, opcode = %u\n",retlen,status);
		retlen	-= 4;

		switch ( status )
			{
			case	TFTP_OPC$K_ERROR:
				status	= ntohs(pdu->tftp_pdu$w_code);
				tftp__log( TFTP_ERROR, status,
				 pdu->tftp_pdu$b_msg);

				{
				int errt [] = {2, SS$_NOSUCHFILE,
				 SS$_NOPRIV, SS$_DEVICEFULL,
				 SS$_PROTOCOL, SS$_NOSUCHTID,
				 SS$_DUPFILENAME, SS$_NOSUCHUSER};

				if ( status < 8 )
					return	errt[status];
				return	SS$_ABORT;
				}

			case	TFTP_OPC$K_DATA:		
				break;
			
			default:
				return  SS$_ABORT;
			}

		if ( lbn !=  ntohs(pdu->tftp_pdu$w_lbn) )
			{
			tftp__log(TFTP_ERRLBN,lbn,ntohs(pdu->tftp_pdu$w_lbn));
			return	SS$_DATALOST;
			}

		/*
		** Write a gotten datablock to a file
		*/
		if ( retlen )
			{
			rab->rab$w_rsz	= retlen;
			if ( !(1 & (status = sys$write(rab))) )
				return	status;
			}

		/*
		** Send ACKnoledgment of received data block
		*/
		pdu->tftp_pdu$w_opcode	= htons(TFTP_OPC$K_ACK);
		if ( !(1 & (status = net_write (chan,pdu,4,remhost))) )
			return	status;


		/*
		** If a data block is not 512 bytes, then it's the last
                ** block of the file.
		*/
		*bytes	+= retlen;
		if ( retlen < sizeof(pdu->tftp_pdu$b_data) )
			break;
		}

	return	status;
}

/*
**++
**  FUNCTIONAL DESCRIPTION:
**
**      Perform TFTP GET from remote node.
**
**  FORMAL PARAMETERS:
**
**	chan:		An network I/O channel number
**	ipaddr:		A remote node IP address
**	filename:	A filename on remote node
**	remhost:	Socket Address IN structure
**	rab:		A RAB of the has been opened localy file
**	tmode:		A mode of transfering
**	bytes:		Total sent bytes
**	ebk:		A total number of blocks in local file
**	ffb:		A first free byte
**
**  RETURN VALUE:
**
**      VMS condition code
**
**--
*/
int	tftp_do_put	(
		unsigned short	 chan,
		unsigned	 ipaddr,
	struct dsc$descriptor	*filename,
	struct sockaddr_in	*remhost,
	struct RAB		*rab,
		unsigned	 tmode,
		unsigned	*bytes,
		unsigned         ebk,
                unsigned short   ffb
			)

{
unsigned status;
char	buf [ 1024];
struct tftp_pdu *pdu = (struct tftp_pdu *)&buf;
struct	dsc$descriptor buf_dsc;
$DESCRIPTOR(fao_dsc,"!AS\x00!AZ\x00");
unsigned short lbn,retlen = 0,acklbn = 0;


	DEBUG("ebk = %u, ffb = %u\n",ebk,ffb);

	/*
	** Send Write ReQuest (WRQ)
	*/
	pdu->tftp_pdu$w_opcode	= htons(TFTP_OPC$K_WRQ);
	INIT_SDESC(buf_dsc,sizeof(pdu->tftp_pdu$b_args),&pdu->tftp_pdu$b_args);

	if ( !(1 & (status = sys$fao(&fao_dsc,&retlen,&buf_dsc,filename,
			(tmode==TFTP__$K_NETASCII)?"netascii":"octet"))) )
		lib$signal(status);

	if ( !(1 & (status = net_write (chan,buf,2+retlen,remhost))) )
			return	status;

	/*
	** Start main loop processing input stream of blocks
	*/
	rab->rab$l_ubf = (char *)pdu->tftp_pdu$b_data;
	rab->rab$w_usz = sizeof(pdu->tftp_pdu$b_data);

	for (lbn = 1;;lbn++)
		{
		if ( !(1 & (status = net_read( chan, &buf, sizeof(buf),
		 &retlen, remhost))) )
                        return  status;

		status 	= ntohs(pdu->tftp_pdu$w_opcode);

		DEBUG("Got %u bytes, opcode = %u, lbn = %u\n", retlen,
		 status, ntohs(pdu->tftp_pdu$w_lbn));
		retlen	-= 4;

		switch ( status )
			{
			case	TFTP_OPC$K_ERROR:
				status	= ntohs(pdu->tftp_pdu$w_code);
				tftp__log( TFTP_ERROR, status,
				 pdu->tftp_pdu$b_msg);

				{
				int errt [] = {2, SS$_NOSUCHFILE,
				 SS$_NOPRIV, SS$_DEVICEFULL,
				 SS$_PROTOCOL, SS$_NOSUCHTID,
				 SS$_DUPFILENAME, SS$_NOSUCHUSER};

				if ( status < 8 )
					return	errt[status];
				return	SS$_ABORT;
				}

			case	TFTP_OPC$K_ACK:
				break;
			
			default:
				return  SS$_ABORT;
			}

		/*
		** Check a gottent LBN
		*/
		acklbn	= ntohs(pdu->tftp_pdu$w_lbn);
		if ( acklbn )
			{
			if ( lbn <  acklbn )
				{
				tftp__log(TFTP_ERRLBN,lbn,acklbn);
				return	SS$_DATALOST;
				}
			else if ( acklbn < (lbn-1) )
				continue;
			}

		/*
		** Read a data block from the file
		*/
		if ( !(1 & (status = sys$read(rab))) && (status != RMS$_EOF) )
			return	status;
		else if ( status == RMS$_EOF ) 
			rab->rab$w_usz = 0;

		/*
		** Send DATAblock
		*/
		pdu->tftp_pdu$w_opcode	= htons(TFTP_OPC$K_DATA);
		pdu->tftp_pdu$w_lbn	= htons(lbn);
		retlen	= (lbn == ebk)?ffb:rab->rab$w_usz;

		if ( !(1 & (status = net_write (chan,pdu,4 + retlen,remhost))) )
			return	status;

		DEBUG("Sent %u bytes, lbn = %u\n",retlen,lbn);

		*bytes	+= retlen;

		if ( lbn == ebk )
                        break;
		}

	return	status;
}

/*
**++
**  FUNCTIONAL DESCRIPTION:
**
**      Parse a command line, extract a parameter set and dispatch to 
**	operation-specific routine.
**
**  FORMAL PARAMETERS:
**	
**	None.
**
**  RETURN VALUE:
**
**      VMS condition code
**
**--
*/
int	tftp_cmd_run	(void)
{
unsigned status,tmode = TFTP__$K_OCTET,bytes = 0,ebk = 0,ffb = 0;
int	ipaddr;
short	chan = 0, port = TFTP__$K_PORT;
char	buf[256];
struct dsc$descriptor	host,cmd,mode,fromfile,tofile;
struct	FAB	fab;
struct	RAB	rab;
struct	XABFHC  fhc;
struct  {
        	short   proto;
	        char    type;
        	char    domain;
	        } sck_parm = {INET$C_UDP,INET_PROTYP$C_DGRAM,INET$C_AF_INET};
iosb    netiosb;
struct sockaddr_in	sock_loc_host = {INET$C_AF_INET,0,INET$C_INADDR_ANY,0},
			sock_rem_host = {INET$C_AF_INET,0,INET$C_INADDR_ANY,0};
ile3	loc_host = {sizeof(struct sockaddr_in),0,&sock_loc_host};
struct hostent  *hp;

	/*
	** A TFTP command: GET/PUT
	*/
	if ( CLI$_PRESENT != cli$present (&p_cmd) )
		return	SS$_INSFARG;

	INIT_DDESC(cmd);
	if ( !(1 & (status = cli$get_value(&p_cmd,&cmd,NULL))) )
		return	status;

	/*
	** HOST(s)
	*/
	if ( CLI$_PRESENT != cli$present (&p_host) )
		return	SS$_INSFARG;

	INIT_DDESC(host);
	if ( !(1 & (status = cli$get_value(&p_host,&host,&host.dsc$w_length))) )
		return	status;

	/*
	** From File & To File
	*/
	if ( CLI$_PRESENT != cli$present (&p_fromfile) )
		return	SS$_INSFARG;

	INIT_DDESC(fromfile);

	if ( !(1 & (status = cli$get_value(&p_fromfile,&fromfile,NULL))) )
		return	status;

	if ( *fromfile.dsc$a_pointer == '"' )
		{
		fromfile.dsc$a_pointer++;
		fromfile.dsc$w_length	-= 2;
		}

	INIT_DDESC(tofile);

	if ( CLI$_PRESENT != cli$present (&p_tofile) )
		{
/*		return	SS$_INSFARG; */
		tofile.dsc$w_length = fromfile.dsc$w_length;
		tofile.dsc$a_pointer = fromfile.dsc$a_pointer;
		}
	else
		{

		if ( !(1 & (status = cli$get_value(&p_tofile,&tofile,NULL))) )
			return	status;

		if ( *tofile.dsc$a_pointer == '"' )
			{
			tofile.dsc$a_pointer++;
			tofile.dsc$w_length	-= 2;
			}
		}

	/*
	** MODE
	*/
	if ( CLI$_PRESENT == cli$present (&q_mode) )
		{
		INIT_DDESC(mode);
		if ( !(1 & (status = cli$get_value(&q_mode,&mode,NULL))) )
			return	status;

		if ( (*mode.dsc$a_pointer == 'N') ||
		 (*mode.dsc$a_pointer == 'A') )
			tmode = TFTP__$K_NETASCII;
		else	tmode = TFTP__$K_OCTET;
		}

	/*
	** /LOG & /DEBUG
	*/
	debug_flag = CLI$_PRESENT == cli$present (&q_debug);
	log_flag = CLI$_PRESENT == cli$present (&q_log);

	/*
	** Initialize a network stuff
	** Resolve a host name/ip to IP address
	*/
	strncpy(buf,host.dsc$a_pointer,host.dsc$w_length);
	buf[host.dsc$w_length] = '\0';

	if ( -1 !=  (ipaddr = inet_addr(buf)) )
		sock_rem_host.sin_addr.s_addr = ipaddr;
	else if ( hp = gethostbyname(buf) )
		sock_rem_host.sin_addr.s_addr = ipaddr = *(int *)hp->h_addr;
	else    return SS$_NOSUCHNODE;

        sock_rem_host.sin_port = htons(TFTP__$K_PORT);

        /*
        ** Initialize network stuff
        */
        if ( !(1 & (status = sys$assign( &dsc_ucxdev,&chan,0,0))) )
                {
                if ( status != SS$_NOSUCHDEV )
                        return  status;
                if ( !(1 & (status = sys$assign( &dsc_tcpipdev,&chan,0,0))) )
                        return  status;
                }

        /*
        ** Create a UDP device
        */
        status = sys$qiow( 0, chan, IO$_SETMODE, &netiosb, 0, 0,
	 &sck_parm, 0, &loc_host, 0, 0, 0);

        if ( !(status & 1) || !(netiosb.iosb$w_status & 1) )
                {
                sys$dassgn (chan);
                return  (1 & status)?netiosb.iosb$w_status:status;
                }

	/*
	** Open/create  file for using RMS Block I/O
	*/
	fab = cc$rms_fab;
	fhc = cc$rms_xabfhc;
	fab.fab$l_xab = &fhc;
	fab.fab$b_rfm = FAB$C_FIX;
	fab.fab$w_mrs = fhc.xab$w_lrl = 512;
	fab.fab$b_shr = FAB$M_NIL;
	fab.fab$v_dfw  = fab.fab$v_sqo = 1;

	rab = cc$rms_rab;
	rab.rab$l_fab = &fab;
	rab.rab$v_bio = rab.rab$v_rah = rab.rab$v_wbh = 1;

	if ( *cmd.dsc$a_pointer == 'G' )
		{
		fab.fab$b_fac = FAB$M_PUT | FAB$M_BIO;
		fab.fab$l_fna = tofile.dsc$a_pointer;
		fab.fab$b_fns = tofile.dsc$w_length;

		if ( tmode == TFTP__$K_NETASCII )
			fab.fab$b_rfm = FAB$C_STM;

		if ( !(1 & (status = sys$create (&fab))) )
			return	status;
		}
	else	{
		fab.fab$b_fac = FAB$M_GET | FAB$M_BIO;
		fab.fab$l_fna = fromfile.dsc$a_pointer;
		fab.fab$b_fns = fromfile.dsc$w_length;

		if ( !(1 & (status = sys$open (&fab))) )
			return	status;
		}

	if ( !(1 & (status = sys$connect(&rab))) )
		return	status;

	/*
	** Now we ready to dispatch processing of entered command
	** to a specifc routine.
	*/
	if ( *cmd.dsc$a_pointer == 'G' )
		{
		if ( log_flag )
			{
			tftp__log( TFTP_GETSTART, &fromfile, &host,
			 &tofile, ((tmode == TFTP__$K_NETASCII) ?
			 &amode_dsc : &bmode_dsc),
			 &iotmo);
			}
		status = tftp_do_get( chan, ipaddr, &fromfile,
		 &sock_rem_host, &rab, tmode, &bytes);

		if ( (1 & status) && log_flag )
			tftp__log( TFTP_GETSTOP, &fromfile, bytes,
			 &tofile, &host);
		else if ( !(1 & status) )
			tftp__log(TFTP_GETERR,status);
		}
	else	{
		if ( log_flag )
			tftp__log( TFTP_PUTSTART, &fromfile, &tofile, &host,
				((tmode == TFTP__$K_NETASCII) ?
				 &amode_dsc : &bmode_dsc), &iotmo);

		status = tftp_do_put( chan, ipaddr, &tofile,
		 &sock_rem_host, &rab, tmode, &bytes,
		 fhc.xab$l_ebk, fhc.xab$w_ffb);

		if ( (1 & status) && log_flag )
			tftp__log( TFTP_PUTSTOP, &fromfile, bytes,
			 &tofile, &host);
		else if ( !(1 & status) )
			tftp__log(TFTP_PUTERR,status);
		}

	/*
	** Cleanup & return status;
	*/
	sys$close(&fab);
        sys$dassgn (chan);

	return	status;
}

/*
**++
**  FUNCTIONAL DESCRIPTION:
**
**      A main procedure performs reading input, parsing and dispatch a
**      command processing.
**
**  FORMAL PARAMETERS:
**
**      None
**
**  RETURN VALUE:
**
**      VMS condition code
**
**--
*/
int	main	(int	argc,char **argv)
{
int	status,flag = 0;
char	buf[256] = {"RUN "};
struct dsc$descriptor cmd_dsc;

	/*
	** Check the presence of the comand line agruments
	*/
	INIT_SDESC(cmd_dsc,sizeof(buf)-4,buf+4);
	if ( !(1 & (status = lib$get_foreign( &cmd_dsc, 0,
	 &cmd_dsc.dsc$w_length, &flag))) )
		return status;

	/*
	** No arguments - display usage info and exit
	*/
	if ( !cmd_dsc.dsc$w_length )
		return	lib$put_output(&usage);

	cmd_dsc.dsc$w_length    += 4;
	cmd_dsc.dsc$a_pointer   -= 4;

	if ( cmd_dsc.dsc$w_length )
		if ( CLI$_NORMAL == (status = cli$dcl_parse( &cmd_dsc,
		 &TFTP_CLD, 0, 0, 0)) )
			status = cli$dispatch();

	return	status;
}
