/*
 *	Copyright (c) 1995, 1996, Sun Microsystems, Inc.
 *
 * This software is provided by Sun ``AS IS'' and any express or implied
 * warranties, including, but not limited to, the implied warranties of
 * merchantability and fitness for a particular purpose are disclaimed.
 * In no event shall Sun Microsystems be liable for any direct, indirect,
 * incidental, special, exemplary, or consequential damages.
 *
 * This software is not a product, and is provided for evaluation purposes
 * only.
 *
 * This software may not be resold without the express permission of
 * Sun Microsystems.
 *
 * Feedback on this tool may be sent to packet-shell-owner@sunroof.eng.sun.com.
 *
 * Packet Shell - syscall 'protocol'.  Provides ability to
 * send system calls ie. ioctl commands.
 *
 */

#pragma ident	"@(#)syscall.c	1.10	99/05/28 SMI"

#include <tcl.h>
#include "psh.h"
#include "proto.h"
#include "name2int.h"

#include <sys/types.h>
#include <sys/telioctl.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stropts.h>
#include <fcntl.h>
#include <termio.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/stat.h>

static char *proto_name;

extern struct packet *pkt;

struct syscall_ep {
	ep_t		se_ep;
	int		se_fd;
	int		se_pd;		/* File descriptor for /dev/pmtx */
};

static int
sc_ioctl(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[]);

static int
sc_ilookup(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[]);

/*
 * Basic Initialization function required by Packet Shell. This
 * simply sets up the protocol name.
 */
int
init(proto_tbl_t *prototbl, Tcl_Interp *interp)
{
	/* set the size of a syscall end-point */
	prototbl->proto_epsize = sizeof (struct syscall_ep);

	/*
	 * Register protocol specific ancilliary commands, if any
	 */
	proto_name = strdup(prototbl->proto_name);
	if (proto_name == NULL) {
		Tcl_AppendResult(interp, "error on strdup", NULL);
		return (TCL_ERROR);
	}
	(void) Tcl_CreateCommand(interp, "sc.ilookup", sc_ilookup, NULL, NULL);
	return (1);
}

/*
 * Open a device. This library specific function is used by popen in psh.c.
 * This function checks the end point passed to it, to see if it's already
 * in use. Then it checks what mode the specified device is to be opened
 * with and checks for the device "/dev/ptmx". If the device is "/dev/ptmx"
 * then the function performs steps specific to that device, this includes
 * opening both the master and slave. If the device in other than "/dev/ptmx"
 * a simple open is performed. The sc.<fd>.pioctl TCL command is then created.
 * The call to popen would look like popen syscall end_point "/dev/tty" and
 * the arguments would be as follows: argv[0] would be end_point and argv[1]
 * would be /dev/tty.
 */
int
propen(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct syscall_ep *ep = (struct syscall_ep *)clientData;
	char *slavename;
	char cmdname[BUFSIZ];
	int fd, rw_mode = O_RDONLY;
	int pd = -1;

	Tcl_ResetResult(interp);

	if (argc < 2 || argc > 3) {
usage:		Tcl_AppendResult(interp, "Usage: popen syscall",
		    " <ep> <filename> [r|w|rw]", NULL);
		return (TCL_ERROR);
	}
	if (argc == 3) {
		if (argv[2][0] == 'w') {
			rw_mode = O_WRONLY;
		} else if ((strcmp(argv[2], "rw")) == 0) {
			rw_mode = O_RDWR;
		} else if (argv[2][0] != 'r') {
			goto usage;
		}
	}
	/*
	 * Commands specific to /dev/ptmx setup and opening of slave pty.
	 */
	if (strcmp(argv[1], "/dev/ptmx") == 0) {
		if ((pd = open("/dev/ptmx", O_RDWR | O_NOCTTY)) == -1) {
			Tcl_AppendResult(interp, "open /dev/ptmx failed",
			    NULL);
			return (TCL_ERROR);
		}
		if (grantpt(pd) == -1) {
			Tcl_AppendResult(interp, "could not grant slave pty",
			    NULL);
			return (TCL_ERROR);
		}
		if (unlockpt(pd) == -1) {
			Tcl_AppendResult(interp, "could not unlock slave pty",
			    NULL);
			return (TCL_ERROR);
		}
		if ((slavename = ptsname(pd)) == NULL) {
			Tcl_AppendResult(interp, "could not enable slave pty",
			    NULL);
			return (TCL_ERROR);
		}
		if ((fd = open(slavename, rw_mode, 0)) == -1) {
			Tcl_AppendResult(interp, "open ", slavename, " failed",
			    NULL);
			return (TCL_ERROR);
		}
	} else if ((fd = open(argv[1], rw_mode, 0)) < 0) {
		Tcl_AppendResult(interp, "popen file: open", strerror(errno),
		    NULL);
		return (TCL_ERROR);
	}

	ep->se_fd = fd;
	ep->se_pd = pd;

	/*
	 * Create sc.<endpoint>.pioctl and ilookup commands.
	 */
	(void) sprintf(cmdname, "sc.%s.pioctl", argv[0]);
	(void) Tcl_CreateCommand(interp, cmdname, sc_ioctl, ep, NULL);

	return (TCL_OK);
}


/*
 * Close end point created in propen. This library specific function
 * is used by popen in psh.c. This function checks the end point for
 * validity, and deletes the TCL commands pioctl and ilookup created
 * in propen. Then the file descriptors are closed and the end point
 * is deleted. The arguments to this are "pclose syscall end_point"
 * where syscall is used by the packet shell and argv[0] is end_point.
 */
int
prclose(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct syscall_ep *ep = (struct syscall_ep *)clientData;
	char tmp[BUFSIZ];

	if (argc < 1) {
		Tcl_AppendResult(interp, "Usage: pclose syscall",
		    " <end-point>", NULL);
		return (TCL_ERROR);
	}

	/*
	 * Delete the TCL commands created in propen.
	 */
	(void) sprintf(tmp, "sc.%s.pioctl", argv[0]);
	(void) Tcl_DeleteCommand(interp, tmp);

	/*
	 * Close the file discriptors from propen.
	 */
	(void) close(ep->se_fd);
	if (ep->se_pd != -1)
		(void) close(ep->se_pd);

	return (TCL_OK);
}

/*
 * Function for passing ioctl commands from TCL command <ep>.pioctl to ioctl
 * command. This function is the ioctl wrapper used by the pioctl TCL command.
 * the function checks to insure that the packet passed to it is valid. Then
 * the ioctl request that was passed in is looked up in the ioctl_map table
 * in syscal.h and req is set to the integer value of the ioctl. The end
 * point is then set from the clientData. The ioctl call is then made, if
 * successful TCL_OK is returned. The arguments for thsi command are
 * sc.end_point.pioctl TCGETA pckt where arv[0] is sc.end_point.pioctl,
 * argv[1] is TCGETA and argv[2] is pckt.
 */
static int
sc_ioctl(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	char tmp[BUFSIZ];
	char *req_str;
	struct syscall_ep *ep;
	int retval, req;
	struct packet *pckt;


	if (argc != 3) {
		Tcl_AppendResult(interp, "Usage: sc.<fd>.pioctl ",
		    " <request> <pkt-or-bufname> ", NULL);
		return (TCL_ERROR);
	}
	if ((pckt = pckt_lookup(argv[2])) == NULL) {
		Tcl_AppendResult(interp, "pckt_lookup: invalid packet name: ",
		    argv[2], NULL);
		return (TCL_ERROR);
	}

	/*
	 * Searches ioctl_map from syscall.proto for ioctl and returns the
	 * ioctl value if found. If a number is passed the number is returned.
	 * If the ioctl is not found TCL_ERROR is returned with the message
	 * "ioctl not found".
	 */
	if (isdigit((int)*argv[1])) {
		if (Tcl_GetInt(interp, argv[1], &req) != TCL_OK) {
			Tcl_AppendResult(interp, "ioctl value is invalid",
			    NULL);
			return (TCL_ERROR);
		}
	} else {
		(void) sprintf(tmp, "ioctl_map(%s)", argv[1]);
		if ((req_str = Tcl_GetVar(interp, tmp, 0)) == NULL) {
			Tcl_AppendResult(interp, "Ioctl not found", NULL);
			return (TCL_ERROR);
		} else
			req = atoi(req_str);
	}
	ep = (struct syscall_ep *)clientData;

	/*
	 * ioctl call,  on error returns error information to Tcl_AppendResult
	 * and returns TCL_ERROR.
	 */
	if ((retval = ioctl(ep->se_fd, req, pckt->p_buf)) < 0) {
		Tcl_AppendResult(interp, strerror(errno), NULL);
		return (TCL_ERROR);
	}
	(void) sprintf(tmp, "%d", retval);
	Tcl_AppendResult(interp, tmp, NULL);
	return (TCL_OK);
}


/*
 * ioctl lookup function. Searches the ioctl_map table in syscal.h and
 * returns the defined value for the given ioctl. If not found returns
 * TCL_ERROR.
 */

static int
sc_ilookup(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	char tmp[BUFSIZ];
	char *req_str;

	if (argc < 2) {
		Tcl_AppendResult(interp, "Usage: sc.ilookup <ioctl name> ",
		    NULL);
		return (TCL_ERROR);
	}
	(void) sprintf(tmp, "ioctl_map(%s)", argv[1]);
	if ((req_str = Tcl_GetVar(interp, tmp, 0)) == NULL) {
		Tcl_AppendResult(interp, tmp, " Ioctl not found", NULL);
		return (TCL_ERROR);
	}
	Tcl_AppendResult(interp, req_str, NULL);
	return (TCL_OK);
}
