/* $Id$ * Author: Roger A. Cole * Date: 11-26-90 * * Experimental Physics and Industrial Control System (EPICS) * * Copyright 1991, the Regents of the University of California, * and the University of Chicago Board of Governors. * * This software was produced under U.S. Government contracts: * (W-7405-ENG-36) at the Los Alamos National Laboratory, * and (W-31-109-ENG-38) at Argonne National Laboratory. * * Initial development by: * The Controls and Automation Group (AT-8) * Ground Test Accelerator * Accelerator Technology Division * Los Alamos National Laboratory * * Co-developed with * The Controls and Computing Group * Accelerator Systems Division * Advanced Photon Source * Argonne National Laboratory * * Modification Log: * ----------------- * .01 11-26-90 rac initial version * .02 07-30-91 rac installed in SCCS * * make options * -DvxWorks makes a version for VxWorks * -DNDEBUG don't compile assert() checking * -DDEBUG compile various debug code, including checks on * malloc'd memory */ /*+/mod*********************************************************************** * TITLE cmdClient.c - general purpose client for command-based servers * * DESCRIPTION * Connects to a text-command-based server. * * Usage on SunOS: * % cmdClient hostName portNum * or * execl("cmdClient", "cmdClient", "hostName", "portNum", * (char *)0); * * Usage on VxWorks: * > cmdClient "hostName",portNum * * BUGS * o need to clean up structure to be more in line with cmdProto's structure * o the stdout stream from this program contains, intermixed, the * server's stdout and stderr streams * o under VxWorks, if the server is on the same IOC, then the IOC itself * must be added to the host table with hostAdd() * o this program should use tasks.h to establish priorities, stack * sizes, etc. * o not all signals are caught *-***************************************************************************/ #include #include #include #include #ifdef vxWorks /*---------------------------------------------------------------------------- * includes and defines for VxWorks compile *---------------------------------------------------------------------------*/ # include # include # include # include # include # include # include # define MAXPRIO 160 #else /*---------------------------------------------------------------------------- * includes and defines for Sun compile *---------------------------------------------------------------------------*/ # include # include # include # include # include # define MAXPRIO 50 #endif /*/subhead CMDCL_CTX------------------------------------------------------- * CMDCL_CTX - context information * * A cmdcl descriptor is the `master handle' which is used for * handling business. *----------------------------------------------------------------------------*/ #define CmdclLock semTake(pglCmdclCtx->semLock) #define CmdclUnlock semGive(pglCmdclCtx->semLock) #define CmdclLockCheck semClear(pglCmdclCtx->semLock) #define CmdclLockInitAndLock semInit(pglCmdclCtx->semLock) typedef struct { TASK_ID id; /* ID of task */ int status; /* status of task--initially ERROR */ int stop; /* task requested to stop if != 0 */ int stopped; /* task has stopped if != 0 */ int serviceNeeded; /* task needs servicing */ int serviceDone; /* task servicing completed */ jmp_buf sigEnv; /* environment for longjmp at signal time */ } CMDCL_TASK_INFO; typedef struct cmdclCtx { SEM_ID semLock; CMDCL_TASK_INFO cmdclTaskInfo; CMDCL_TASK_INFO cmdclInTaskInfo; int showStack; /* show stack stats on task terminate */ } CMDCL_CTX; /*----------------------------------------------------------------------------- * prototypes *----------------------------------------------------------------------------*/ int cmdcl(); void cmdclCmdProcess(); long cmdclInitAtStartup(); long cmdclInTask(); long cmdclTask(); void cmdclTaskSigHandler(); void cmdclTaskSigInit(); /*----------------------------------------------------------------------------- * global definitions *----------------------------------------------------------------------------*/ CX_CMD glCmdclCxCmd; CX_CMD *pglCmdclCxCmd=NULL; CMDCL_CTX glCmdclCtx; CMDCL_CTX *pglCmdclCtx=NULL; #ifndef vxWorks main(argc, argv) int argc; /* number of command line args */ char *argv[]; /* command line args */ { char *hostName; /* host name for server */ int portNum; /* port number for server */ if (argc != 3) /* must be command and 2 args */ goto mainUsage; hostName = argv[1]; if (sscanf(argv[2], "%d", &portNum) != 1) { printf("error scanning port number\n"); goto mainUsage; } /*---------------------------------------------------------------------------- * do some lwp initialization: * o set allowable priorities between 1 and MAXPRIO * o set up a cache of stacks for MAXTHREAD stacks * * A side effect is that this routine is turned into a lwp thread with a * priority of MAXPRIO. *---------------------------------------------------------------------------*/ (void)pod_setmaxpri(MAXPRIO); lwp_setstkcache(100000, 9); return cmdClient(hostName, portNum); mainUsage: printf("Usage: %s serverHostName serverPortNumber\n", argv[0]); return -1; } #endif /*+/subr********************************************************************** * NAME cmdClient - shell callable interface for cmdClient * * DESCRIPTION * This routine is the only part of cmdClient which is intended to be * called directly from the shell. Several functions are performed here: * o spawn the cmdclTask * o spawn the cmdclInTask * o wait until the cmdclInTask quits, then return to the shell. If * other tasks belonging to cmdClient are being stopped, then this * routine waits until they, too, are stopped before returning to * the shell. * * RETURNS * OK, or * ERROR * * BUGS * o stack size and priority should come from tasks.h * o there are lots of "holes" in detecting whether tasks exist, are * suspended, etc. * *-*/ int cmdClient(hostName, portNum) char *hostName; /* I host name for server */ int portNum; /* I port number for server */ { long stat; /* status return from calls */ pglCmdclCtx = &glCmdclCtx; pglCmdclCxCmd = &glCmdclCxCmd; stat = cmdclInitAtStartup(pglCmdclCtx, pglCmdclCxCmd); assert(stat == OK); #ifdef vxWorks assert(taskNameToId("cmdclTask") == ERROR); assert(taskNameToId("cmdclInTask") == ERROR); #endif pglCmdclCtx->showStack = 0; pglCmdclCtx->cmdclTaskInfo.status = ERROR; pglCmdclCtx->cmdclTaskInfo.stop = 0; pglCmdclCtx->cmdclTaskInfo.stopped = 1; pglCmdclCtx->cmdclInTaskInfo.status = ERROR; pglCmdclCtx->cmdclInTaskInfo.stop = 0; pglCmdclCtx->cmdclInTaskInfo.stopped = 1; pglCmdclCtx->cmdclInTaskInfo.serviceNeeded = 0; pglCmdclCtx->cmdclInTaskInfo.serviceDone = 1; /*----------------------------------------------------------------------------- * cmdclTask * spawn it *----------------------------------------------------------------------------*/ pglCmdclCtx->cmdclTaskInfo.id = taskSpawn("cmdclTask", MAXPRIO, VX_STDIO | VX_FP_TASK, 50000, cmdclTask, &pglCmdclCxCmd, hostName, portNum); if (GenTaskNull(pglCmdclCtx->cmdclTaskInfo.id)) { (void)fprintf(stdout, "error spawning cmdclTask\n"); return ERROR; } pglCmdclCtx->cmdclTaskInfo.status = OK; pglCmdclCtx->cmdclTaskInfo.stopped = 0; /*----------------------------------------------------------------------------- * cmdclInTask * spawn it *----------------------------------------------------------------------------*/ pglCmdclCtx->cmdclInTaskInfo.id = taskSpawn("cmdclInTask", MAXPRIO, VX_STDIO | VX_FP_TASK, 50000, cmdclInTask, &pglCmdclCxCmd); if (GenTaskNull(pglCmdclCtx->cmdclInTaskInfo.id)) { (void)fprintf(stdout, "error spawning cmdclInTask\n"); return ERROR; } pglCmdclCtx->cmdclInTaskInfo.status = OK; pglCmdclCtx->cmdclInTaskInfo.stopped = 0; /*----------------------------------------------------------------------------- * wait for cmdclInTask to exit and then return to the shell. If other * "cmdClient tasks" are also exiting, wait until their wrapups are complete. *----------------------------------------------------------------------------*/ while (!pglCmdclCtx->cmdclInTaskInfo.stopped) taskSleep(SELF, 1, 0); if (pglCmdclCtx->cmdclTaskInfo.stop) { while (!pglCmdclCtx->cmdclTaskInfo.stopped) taskSleep(SELF, 1, 0); } return OK; } /*+/subr********************************************************************** * NAME cmdclTask - main processing task for cmdClient * * DESCRIPTION * * RETURNS * OK, or * ERROR * * BUGS * o text * *-*/ long cmdclTask(ppCxCmd, hostName, portNum) CX_CMD **ppCxCmd; char *hostName; /* I host name for server */ int portNum; /* I port number for server */ { long stat; CX_CMD *pCxCmd; int serverSock=-1; /* socket connected to server */ char serverBuf[80]; FILE *myIn=NULL; FILE *myOut=NULL; char *findNl; char message[80]; int i; pCxCmd = *ppCxCmd; CmdclLockInitAndLock; CmdclUnlock; cmdclTaskSigInit(); if (setjmp(pglCmdclCtx->cmdclTaskInfo.sigEnv) != 0) goto cmdclTaskWrapup; /*----------------------------------------------------------------------------- * attempt to connect to the server. If the connection is successful, * print the message returned by the server. Then open two streams to * the socket, one for characters from keyboard to server, the other * for characters from server to stdout. *----------------------------------------------------------------------------*/ if ((i=ezsConnectToServer(&serverSock, portNum, hostName, serverBuf)) < 0) { if (i == -1) { (void)sprintf(message, "connect to %s port:%d", hostName, portNum); perror(message); } else printf("%s\n", serverBuf); serverSock = -1; goto cmdclTaskWrapup; } printf("%s\n", serverBuf); if (ezsFopenToFd(&myIn, &serverSock) < 0) { perror("myIn"); myIn = NULL; goto cmdclTaskWrapup; } if (ezsFopenToFd(&myOut, &serverSock) < 0) { perror("myOut"); myOut = NULL; goto cmdclTaskWrapup; } /*---------------------------------------------------------------------------- * "processing loop" * do the interactions with the server, attempting as much as possible * to show the messages as they come in from the server. *---------------------------------------------------------------------------*/ while (!pglCmdclCtx->cmdclTaskInfo.stop) { if (pglCmdclCtx->cmdclInTaskInfo.serviceNeeded) { fputs(pCxCmd->line, myOut); fflush(myOut); pglCmdclCtx->cmdclInTaskInfo.serviceNeeded = 0; pglCmdclCtx->cmdclInTaskInfo.serviceDone = 1; } if (ezsCheckFpRead(myIn)) { if (fgets(serverBuf, 80, myIn) != NULL) { if (strncmp(serverBuf, "#p#r#", 5) == 0) { if ((findNl = index(serverBuf, '\n')) != NULL) *findNl = '\0'; fputs(serverBuf+5, stdout); } else fputs(serverBuf, stdout); fflush(stdout); } else { if (strncmp(pCxCmd->line, "quit", 4) == 0) break; else if (strncmp(pCxCmd->line, "close", 5) == 0) break; printf("server gone (?)\n"); break; } } taskSleep(SELF, 0, 100000); /* wait .1 sec */ } cmdclTaskWrapup: if (myIn != NULL) fclose(myIn); if (myOut != NULL) fclose(myOut); if (serverSock >= 0) close(serverSock); while ((*ppCxCmd)->pPrev != NULL) cmdCloseContext(ppCxCmd); pCxCmd = *ppCxCmd; pglCmdclCtx->cmdclInTaskInfo.stop = 1; while (pglCmdclCtx->cmdclInTaskInfo.stopped == 0) { taskSleep(SELF, 1, 0); } #ifdef vxWorks if (pglCmdclCtx->showStack) checkStack(pglCmdclCtx->cmdclTaskInfo.id); #endif pglCmdclCtx->cmdclTaskInfo.stopped = 1; pglCmdclCtx->cmdclTaskInfo.status = ERROR; return 0; } /*+/subr********************************************************************** * NAME cmdclTaskSig - signal handling and initialization * * DESCRIPTION * These routines set up for the signals to be caught for cmdclTask * and handle the signals when they occur. * * RETURNS * void * * BUGS * o not all signals are caught * o under VxWorks, taskDeleteHookAdd isn't used * o it's not clear how useful it is to catch the signals which originate * from the keyboard * *-*/ void cmdclTaskSigHandler(signo) int signo; { printf("entered cmdclTaskSigHandler for signal:%d\n", signo); signal(signo, SIG_DFL); longjmp(pglCmdclCtx->cmdclTaskInfo.sigEnv, 1); } void cmdclTaskSigInit() { (void)signal(SIGTERM, cmdclTaskSigHandler); /* SunOS plain kill (not -9) */ (void)signal(SIGQUIT, cmdclTaskSigHandler); /* SunOS ^\ */ (void)signal(SIGINT, cmdclTaskSigHandler); /* SunOS ^C */ (void)signal(SIGILL, cmdclTaskSigHandler); /* illegal instruction */ #ifndef vxWorks (void)signal(SIGABRT, cmdclTaskSigHandler); /* SunOS assert */ #else (void)signal(SIGUSR1, cmdclTaskSigHandler); /* VxWorks assert */ #endif (void)signal(SIGBUS, cmdclTaskSigHandler); /* bus error */ (void)signal(SIGSEGV, cmdclTaskSigHandler); /* segmentation violation */ (void)signal(SIGFPE, cmdclTaskSigHandler); /* arithmetic exception */ } /*+/subr********************************************************************** * NAME cmdclInTask - handle the keyboard and keyboard commands * * DESCRIPTION * Gets input text and passes the input to cmdclTask. * * This task exists to avoid the possibility of blocking cmdclTask * while waiting for operator input. * * This task waits for input to be available from the keyboard. When * an input line is ready, this task does some preliminary processing: * * o if the command is control-D, "^D" is echoed and the command is * changed to "quit" * * Then cmdclTask is signalled and this task goes into a sleeping loop * until cmdclTask signals that it is ready for the next command. * * RETURNS * OK, or * ERROR * *-*/ long cmdclInTask(ppCxCmd) CX_CMD **ppCxCmd; /* IO ptr to pointer to command context */ { /*---------------------------------------------------------------------------- * wait for input from keyboard. When some is received, signal caller, * wait for caller to process it, and then wait for some more input. * * stay in the main loop until .stop flag is set by cmdclTask. *---------------------------------------------------------------------------*/ while (1) { while (pglCmdclCtx->cmdclInTaskInfo.serviceDone == 0) { if (pglCmdclCtx->cmdclInTaskInfo.stop == 1) goto cmdclInTaskDone; taskSleep(SELF, 0, 500000); /* sleep .5 sec */ } cmdRead(ppCxCmd, &pglCmdclCtx->cmdclInTaskInfo.stop); if (pglCmdclCtx->cmdclInTaskInfo.stop == 1) goto cmdclInTaskDone; if (strncmp((*ppCxCmd)->line, "quit", 4) == 0) { char *prompt; if (*ppCxCmd != (*ppCxCmd)->pCxCmdRoot) { (void)printf("can't use quit command in source file\n"); (*ppCxCmd)->line[0] = '\0'; } else { prompt = (*ppCxCmd)->prompt; (*ppCxCmd)->prompt = "stop the server (y/n) ? "; cmdRead(ppCxCmd, &pglCmdclCtx->cmdclInTaskInfo.stop); if ((*ppCxCmd)->line[0] == 'y' || (*ppCxCmd)->line[0] == 'Y') (void)strcpy((*ppCxCmd)->line, "quit\n"); else (*ppCxCmd)->line[0] = '\0'; (*ppCxCmd)->prompt = prompt; } } pglCmdclCtx->cmdclInTaskInfo.serviceDone = 0; pglCmdclCtx->cmdclInTaskInfo.serviceNeeded = 1; } cmdclInTaskDone: cmdCloseContext(ppCxCmd); #ifdef vxWorks if (pglCmdclCtx->showStack) checkStack(pglCmdclCtx->cmdclInTaskInfo.id); #endif pglCmdclCtx->cmdclInTaskInfo.stop = 1; pglCmdclCtx->cmdclInTaskInfo.stopped = 1; pglCmdclCtx->cmdclInTaskInfo.status = ERROR; return; } /*+/subr********************************************************************** * NAME cmdclInitAtStartup - initialization for cmdClient * * DESCRIPTION * Perform several initialization duties: * o initialize the command context block * * RETURNS * OK, or * ERROR * *-*/ long cmdclInitAtStartup(pCmdclCtx, pCxCmd) CMDCL_CTX *pCmdclCtx; CX_CMD *pCxCmd; { pCxCmd->prompt = NULL; pCxCmd->input = stdin; pCxCmd->inputName = NULL; pCxCmd->dataOut = stdout; pCxCmd->pPrev = NULL; pCxCmd->pCxCmdRoot = pCxCmd; return OK; }