/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is lineterm. * * The Initial Developer of the Original Code is * Ramalingam Saravanan. * Portions created by the Initial Developer are Copyright (C) 1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* ltermManager.c: LTERM control operations */ /* public declarations */ #include "lineterm.h" /* private declarations */ #include "ltermPrivate.h" /* LTERM global variables */ LtermGlobal ltermGlobal; #define CHECK_IF_VALID_LTERM_NUMBER(procname,lterm) \ if ((lterm < 0) || (lterm >= MAXTERM)) { \ LTERM_ERROR("procname: Error - LTERM index %d out of range\n", \ lterm); \ return -1; \ } /* Macros for use in thread synchronization */ #define GLOBAL_LOCK MUTEX_LOCK(ltermGlobal.listMutex) #define GLOBAL_UNLOCK MUTEX_UNLOCK(ltermGlobal.listMutex) /* The following macros assume that ltermGlobal.listMutex has already been * locked. * A UNILOCK is lock that can be locked only by one thread at a time; * attempts to lock it by another thread result in an error return. * This can be used to detect violations of thread discipline. */ #define SWITCH_GLOBAL_TO_UNILOCK(procname,mutex,mutex_flag) \ if (lts->mutex_flag) { \ LTERM_ERROR("procname: Error - MUTEX mutex already locked\n", \ lts->mutex); \ MUTEX_UNLOCK(ltermGlobal.listMutex); \ return -1; \ } \ MUTEX_LOCK(lts->mutex); \ lts->mutex_flag = 1; \ MUTEX_UNLOCK(ltermGlobal.listMutex); #define RELEASE_UNILOCK(mutex,mutex_flag) \ lts->mutex_flag = 0; \ MUTEX_UNLOCK(lts->mutex); int ltermClose(struct lterms *lts); static int ltermShellInit(struct lterms *lts, int all); static int ltermCreatePipe(FILEDESC *writePIPE, FILEDESC *readPIPE); static int ltermCreateProcess(struct LtermProcess *ltp, char *const argv[], int nostderr, int noexport); static void ltermDestroyProcess(struct LtermProcess *ltp); /** Initializes all LTERM operations * (documented in the LTERM interface) * @return 0 on success, or -1 on error. */ int lterm_init(int messageLevel) { if (!ltermGlobal.initialized) { int result, j; /* Assert that INTs have at least 32-bit precision. * This assumption underlies the choice of various bit codes. */ assert(sizeof(int) >= 4); if (sizeof(UNICHAR) == 2) { /* Assert that the maximum column count fits into an UNICHAR. * This assumption is used for input buffer pipe I/O by lterm_write * and ltermWrite, but could be relaxed by using different buffer record * structure. */ assert(MAXCOL <= 0x7FFF); } /* Initialize lock for accessing LTERMs array */ if (MUTEX_INITIALIZE(ltermGlobal.listMutex) != 0) return -1; /* Initialize trace/log module for STDERR; * this could perhaps be eliminated, if TLOG_PRINT is redefined to PR_LOG * and so on, with the USE_NSPR_IO option enabled. */ tlog_init(stderr); result = tlog_set_level(LTERM_TLOG_MODULE, messageLevel, NULL); /* Meta input delimiter */ ltermGlobal.metaDelimiter = U_COLON; /* String of five ASCII characters escaped in XML; * only the first four characters are escaped in HTML; * it is sufficient to escape just the first three characters, "&<>" * when inserting plain text (outside markup) in XML/HTML fragments */ ltermGlobal.escapeChars[LTERM_AMP_ESCAPE] = '&'; ltermGlobal.escapeChars[LTERM_LT_ESCAPE] = '<'; ltermGlobal.escapeChars[LTERM_GT_ESCAPE] = '>'; ltermGlobal.escapeChars[LTERM_QUOT_ESCAPE] = '\"'; ltermGlobal.escapeChars[LTERM_APOS_ESCAPE] = '\''; ltermGlobal.escapeChars[LTERM_XML_ESCAPES] = 0; /* List of XML character escape sequences (Unicode) */ ucscopy(ltermGlobal.escapeSeq[LTERM_AMP_ESCAPE], "&", LTERM_MAXCHAR_ESCAPE+1); ucscopy(ltermGlobal.escapeSeq[LTERM_LT_ESCAPE], "<", LTERM_MAXCHAR_ESCAPE+1); ucscopy(ltermGlobal.escapeSeq[LTERM_GT_ESCAPE], ">", LTERM_MAXCHAR_ESCAPE+1); ucscopy(ltermGlobal.escapeSeq[LTERM_QUOT_ESCAPE], """, LTERM_MAXCHAR_ESCAPE+1); ucscopy(ltermGlobal.escapeSeq[LTERM_APOS_ESCAPE], "'", LTERM_MAXCHAR_ESCAPE+1); /* Escape sequence lengths (including delimiters) */ for (j=0; j= 0) on success, or * -1 on error */ int lterm_new() { int lterm; struct lterms *lts; if (!ltermGlobal.initialized) { LTERM_ERROR("lterm_new: Error - call lterm_init() to initialize LTERM library\n"); return -1; } LTERM_LOG(lterm_new,10,("Creating LTERM ...\n")); /* Allocate memory for new LTERMS structure: needs clean-up */ lts = (struct lterms *) MALLOC(sizeof(struct lterms)); if (lts == NULL) { LTERM_ERROR("lterm_new: Error - failed to allocate memory for LTERM\n"); return -1; } GLOBAL_LOCK; for (lterm=0; lterm < MAXTERM; lterm++) { if (ltermGlobal.termList[lterm] == NULL) break; } if (lterm == MAXTERM) { LTERM_ERROR( "lterm_new: Error - too many LTERMS; recompile with increased MAXTERM\n"); FREE(lts); /* Clean up memory allocation for LTERMS structure */ GLOBAL_UNLOCK; return -1; } /* Insert LTERM pointer in list of LTERMs */ ltermGlobal.termList[lterm] = lts; MUTEX_INITIALIZE(lts->adminMutex); /* Administrative thread inactive */ lts->adminMutexLocked = 0; /* LTERM not yet opened for I/O */ lts->opened = 0; GLOBAL_UNLOCK; LTERM_LOG(lterm_new,11,("created lterm = %d\n", lterm)); return lterm; } /* Macro to close and delete LTERM */ #define LTERM_OPEN_ERROR_RETURN(lterm,lts,errmsg) \ LTERM_ERROR(errmsg); \ ltermClose(lts); \ RELEASE_UNILOCK(adminMutex,adminMutexLocked) \ lterm_delete(lterm); \ return -1; /** Opens an LTERM for I/O * (documented in the LTERM interface) * @return 0 on success, or -1 on error */ int lterm_open(int lterm, char *const argv[], const char* cookie, const char* init_command, const UNICHAR* prompt_regexp, int options, int process_type, int rows, int cols, int x_pixels, int y_pixels, lterm_callback_func_t callback_func, void *callback_data) { int nostderr, noPTY, j; struct ptys ptyStruc; struct lterms *lts; struct LtermInput *lti; struct LtermOutput *lto; struct LtermProcess *ltp; char *const *cargv; char *defaultArgs[3]; if (!ltermGlobal.initialized) { LTERM_ERROR("lterm_open: Error - call lterm_init() to initialize LTERM library\n"); return -1; } CHECK_IF_VALID_LTERM_NUMBER(lterm_open,lterm) LTERM_LOG(lterm_open,10,("Opening LTERM %d\n", lterm)); GLOBAL_LOCK; lts = ltermGlobal.termList[lterm]; if (lts == NULL) { LTERM_ERROR("lterm_open: Error - LTERM %d not created using lterm_new\n", lterm); GLOBAL_UNLOCK; return -1; } if (lts->opened) { LTERM_ERROR("lterm_open: Error - LTERM %d is already open\n", lterm); GLOBAL_UNLOCK; return -1; } SWITCH_GLOBAL_TO_UNILOCK(lterm_open,adminMutex,adminMutexLocked) /* Pointers to LTERM process/input/output structures */ ltp = &(lts->ltermProcess); lti = &(lts->ltermInput); lto = &(lts->ltermOutput); /* Initialize flags/pointers/file descriptors that need to be cleaned up */ lts->suspended = 0; lts->ptyMode = 0; /* Initialize TTY control characters */ assert(TTYWERASE < MAXTTYCONTROL); lts->control[TTYINTERRUPT] = U_CTL_C; lts->control[TTYERASE] = U_DEL; lts->control[TTYKILL] = U_CTL_U; lts->control[TTYEOF] = U_CTL_D; lts->control[TTYSUSPEND] = U_CTL_Z; lts->control[TTYREPRINT] = U_CTL_R; lts->control[TTYDISCARD] = U_CTL_O; lts->control[TTYWERASE] = U_CTL_W; lts->writeBUFFER = NULL_FILEDESC; lts->readBUFFER = NULL_FILEDESC; /* Clear input line break flag */ lts->inputLineBreak = 0; /* Clear input buffer */ lti->inputBufBytes = 0; lts->inputBufRecord = 0; /* Clear styleMask */ lto->styleMask = 0; /* Clear incomplete raw output buffers */ lto->rawOUTBytes = 0; lto->rawERRBytes = 0; /* Clear decoded output buffer */ lto->decodedChars = 0; lto->incompleteEscapeSequence = 0; /* Set initial screen size */ lts->nRows = rows; lts->nCols = cols; lts->xPixels = y_pixels; lts->yPixels = x_pixels; /* Clear screen buffer */ lto->screenChar = NULL; lto->screenStyle = NULL; lts->outputMutexLocked = 0; MUTEX_INITIALIZE(lts->outputMutex); /* AT THIS POINT, LTERMCLOSE MAY BE CALLED FOR CLEAN-UP */ /* Create input buffer pipe; needs clean-up */ if (ltermCreatePipe(&(lts->writeBUFFER), &(lts->readBUFFER)) != 0) { LTERM_OPEN_ERROR_RETURN(lterm,lts, "lterm_open: Error - input buffer pipe creation failed\n") } LTERM_LOG(lterm_open,11,("Created input buffer pipe\n", lterm)); defaultArgs[0] = NULL; defaultArgs[1] = "-i"; defaultArgs[2] = NULL; if (argv != NULL) { /* Use supplied command arguments */ cargv= argv; } else { /* Default command arguments */ defaultArgs[0] = (char *) getenv("SHELL"); if (defaultArgs[0] == NULL) defaultArgs[0] = "/bin/sh"; cargv= defaultArgs; } if (process_type >= 0) { /* Specified process type code */ lts->processType = process_type; } else { /* Determine process type code from path name */ char *lastSlash; char *tailStr; /* Find the last occurrence of slash */ lastSlash = (char *) strrchr(cargv[0], '/'); if (lastSlash == NULL) { tailStr = cargv[0]; } else { tailStr = lastSlash + 1; } if (strcmp(tailStr,"sh") == 0) { lts->processType = LTERM_SH_PROCESS; } else if (strcmp(tailStr,"ksh") == 0) { lts->processType = LTERM_KSH_PROCESS; } else if (strcmp(tailStr,"bash") == 0) { lts->processType = LTERM_BASH_PROCESS; } else if (strcmp(tailStr,"csh") == 0) { lts->processType = LTERM_CSH_PROCESS; } else if (strcmp(tailStr,"tcsh") == 0) { lts->processType = LTERM_TCSH_PROCESS; } else { lts->processType = LTERM_UNKNOWN_PROCESS; } } LTERM_LOG(lterm_open,11,("process type code = %d\n", lts->processType)); /* TTY options */ lts->options = options; lts->noTTYEcho = (options & LTERM_NOECHO_FLAG) != 0; lts->disabledInputEcho = 1; lts->restoreInputEcho = 1; nostderr = (options & LTERM_STDERR_FLAG) == 0; #if defined(NO_PTY) || defined(USE_NSPR_IO) /* Current implementation of pseudo-TTY is incompatible with the NSPR I/O option; set the USE_NSPR_IO option only on platforms where PTYSTREAM is not implemented. Reason for incompatibility: cannot combine the UNIX-style integral PTY file descriptor and NSPR's PRFileDesc in the same poll function call. */ noPTY = 1; #else noPTY = (options & LTERM_NOPTY_FLAG); #endif if (noPTY) { /* Create pipe process: needs clean-up */ if (ltermCreateProcess(ltp, cargv, nostderr, 0) == -1) { LTERM_OPEN_ERROR_RETURN(lterm,lts, "lterm_open: Pipe process creation failed\n") } } else { /* Create pseudo-TTY with blocking I/O: needs clean-up */ int noblock = 1; int noexport = 0; int debugPTY = (tlogGlobal.messageLevel[LTERM_TLOG_MODULE] > 1); int errfd = nostderr?-1:-2; LTERM_LOG(lterm_open,11, ("errfd=%d, noecho=%d, noblock=%d, noexport=%d, debugPTY=%d\n", errfd, lts->noTTYEcho, noblock, noexport, debugPTY)); #ifndef NO_PTY if (pty_create(&ptyStruc, cargv, lts->nRows, lts->nCols, lts->xPixels, lts->yPixels, errfd, noblock, lts->noTTYEcho, noexport, debugPTY) == -1) { LTERM_OPEN_ERROR_RETURN(lterm,lts, "lterm_open: Error - PTY creation failed\n") } #endif /* !NO_PTY */ /* Copy PTY structure */ lts->pty = ptyStruc; lts->ptyMode = 1; } /* Set up LtermOutput polling structure */ lto->pollFD[POLL_INPUTBUF].fd = lts->readBUFFER; lto->pollFD[POLL_INPUTBUF].POLL_EVENTS = POLL_READ; if (lts->ptyMode) { #ifndef USE_NSPR_IO lto->pollFD[POLL_STDOUT].fd = lts->pty.ptyFD; lto->pollFD[POLL_STDOUT].POLL_EVENTS = POLL_READ; if (VALID_FILEDESC(lts->pty.errpipeFD)) { lto->pollFD[POLL_STDERR].fd = lts->pty.errpipeFD; lto->pollFD[POLL_STDERR].POLL_EVENTS = POLL_READ; lto->nfds = POLL_COUNT; } else { lto->nfds = POLL_COUNT-1; } #else /* Current implementation of pseudo-TTY incompatible with USE_NSPR_IO */ LTERM_OPEN_ERROR_RETURN(lterm,lts, "lterm_open: Error - pseudo-TTY incompatible with USE_NSPR_IO\n") #endif } else { lto->pollFD[POLL_STDOUT].fd = ltp->processOUT; lto->pollFD[POLL_STDOUT].POLL_EVENTS = POLL_READ; if (VALID_FILEDESC(ltp->processERR)) { lto->pollFD[POLL_STDERR].fd = ltp->processERR; lto->pollFD[POLL_STDERR].POLL_EVENTS = POLL_READ; lto->nfds = POLL_COUNT; } else { lto->nfds = POLL_COUNT-1; } } /* No callbacks initially associated with this LTERM */ for (j=0; jnfds; j++) lto->callbackTag[j] = 0; /* Determine whether STDERR should be read before STDOUT */ if (lts->processType == LTERM_TCSH_PROCESS) { lts->readERRfirst = 0; } else { lts->readERRfirst = 1; } /* Determine whether STDERR and STDOUT should be interleaved */ lts->interleave = 1; /* Determine maximum value for input mode */ if (options & LTERM_NOCANONICAL_FLAG) lts->maxInputMode = LTERM0_RAW_MODE; else if (options & LTERM_NOEDIT_FLAG) lts->maxInputMode = LTERM1_CANONICAL_MODE; else if ((options & LTERM_NOCOMPLETION_FLAG) || lts->noTTYEcho) lts->maxInputMode = LTERM2_EDIT_MODE; else { if ((lts->processType == LTERM_BASH_PROCESS) || (lts->processType == LTERM_TCSH_PROCESS)) lts->maxInputMode = LTERM3_COMPLETION_MODE; else lts->maxInputMode = LTERM2_EDIT_MODE; } LTERM_LOG(lterm_open,11,("options=0x%x, processType=%d, readERRfirst=%d, maxInputMode=%d\n", lts->options, lts->processType, lts->readERRfirst, lts->maxInputMode)); LTERM_LOGUNICODE(lterm_open,11,(prompt_regexp, (int) ucslen(prompt_regexp) )); if (cookie != NULL) { /* Copy cookie string (no more than MAXCOOKIESTR-1 characters) */ if (strlen(cookie) > MAXCOOKIESTR-1) { LTERM_WARNING("lterm_open: Warning - cookie string truncated\n"); } strncpy(lts->cookie, cookie, MAXCOOKIESTR-1); lts->cookie[MAXCOOKIESTR-1] = '\0'; } else { /* No cookie */ lts->cookie[0] = '\0'; } LTERM_LOG(lterm_open,11,("cookie='%s'\n",lts->cookie)); /* Shell initialization commands have not been sent yet */ lts->shellInitCommands = 0; /* NOTE: Shell init commands are stored in reverse order of execution */ if (init_command != NULL) { /* User supplied shell init command */ if (strlen(init_command) <= MAXSHELLINITSTR-1) { int cmd = lts->shellInitCommands++; assert(cmd < MAXSHELLINITCMD); (void) strncpy(lts->shellInitStr[cmd], init_command, MAXSHELLINITSTR-1); lts->shellInitStr[cmd][MAXSHELLINITSTR-1] = '\0'; } else { LTERM_WARNING("lterm_open: Warning - user init command too long\n"); } } if (lts->processType != LTERM_UNKNOWN_PROCESS) { /* Process dependent shell init command */ int result; char* shellInitFormat; if ((lts->processType == LTERM_CSH_PROCESS) || (lts->processType == LTERM_TCSH_PROCESS)) { /* C-shell family */ shellInitFormat = "setenv LTERM_COOKIE '%s'\n"; } else { /* Bourne-shell family */ shellInitFormat = "LTERM_COOKIE='%s'; export LTERM_COOKIE\n"; } /* **** WATCH OUT FOR BUFFER OVERFLOW!!! *** */ if (strlen(shellInitFormat)-4+strlen(lts->cookie) <= MAXSHELLINITSTR-1) { int cmd = lts->shellInitCommands++; assert(cmd < MAXSHELLINITCMD); sprintf(lts->shellInitStr[cmd], shellInitFormat, lts->cookie); lts->shellInitStr[cmd][MAXSHELLINITSTR-1] = '\0'; } else { LTERM_WARNING("lterm_open: Warning - process init command too long\n"); } } for (j=lts->shellInitCommands-1; j>0; j--) { LTERM_LOG(lterm_open,11,("shellInitStr%d='%s'\n",j,lts->shellInitStr[j])); } /* Initialize regular expression string matching command prompt */ if ((ucslen(prompt_regexp) == 0) || (ucslen(prompt_regexp) > MAXPROMPT-1)) { LTERM_OPEN_ERROR_RETURN(lterm,lts, "lterm_open: Error - prompt_regexp string too short/long\n") } ucsncpy( lts->promptRegexp, prompt_regexp, MAXPROMPT); /* Command number counter */ lts->lastCommandNum = 0; /* Command completion request code */ lts->completionRequest = LTERM_NO_COMPLETION; /* Clear output line buffer and switch to line output mode */ ltermClearOutputLine(lts); lto->outputMode = LTERM2_LINE_MODE; lto->styleMask = 0; /* Reset terminal modes */ lto->insertMode = 0; lto->automaticNewline = 0; /* Clear input line buffer */ ltermClearInputLine(lts); /* Clear input line flag not yet set */ lti->clearInputLine = 0; /* Reset input opcodes */ lti->inputOpcodes = 0; /* Open LTERM */ lts->opened = 1; /* Initialize any callbacks associated with this LTERM */ if (callback_func != NULL) { #if !defined(USE_NSPR_IO) && defined(USE_GTK_WIDGETS) LTERM_LOG(lterm_open,11,("Initializing %d callbacks for LTERM %d\n", lto->nfds, lterm)); for (j=0; jnfds; j++) { lto->callbackTag[j] = gdk_input_add(lto->pollFD[j].fd, GDK_INPUT_READ, callback_func, (gpointer) callback_data); if (lto->callbackTag[j] == 0) { LTERM_OPEN_ERROR_RETURN(lterm,lts, "lterm_open: Error - unable to add callback\n") } } #else LTERM_OPEN_ERROR_RETURN(lterm,lts, "lterm_open: Error - callbacks not implemented for this configuration\n") #endif } RELEASE_UNILOCK(adminMutex,adminMutexLocked) LTERM_LOG(lterm_open,11, ("outputMode=%d, inputMode=%d, maxInputMode=%d\n", lto->outputMode, lti->inputMode,lts->maxInputMode )); return 0; } /** Closes an LTERM described by structure pointed to by LTS. * (Auxiliary routine for lterm_close, lterm_delete, and lterm_close_all) * NOTE: This routine should not attempt to acquire the global lock, * since it is called from LTERM_CLOSE_ALL under a global lock. * @return 0 on success, or -1 on error. */ int ltermClose(struct lterms *lts) { struct LtermInput *lti; struct LtermOutput *lto; UNICHAR closeMessage[2] = {0, LTERM_WRITE_CLOSE_MESSAGE}; int j; LTERM_LOG(ltermClose,10,("Closing LTERM\n")); assert(lts != NULL); /* Admin thread must be active */ assert(lts->adminMutexLocked); /* Suspend LTERM to prevent any further I/O operations */ lts->suspended = 1; /* Pointers to LTERM input/output structures */ lti = &(lts->ltermInput); lto = &(lts->ltermOutput); /* Send close signal to any terminate blocked read operation */ WRITE(lts->writeBUFFER, closeMessage, (SIZE_T)(2*sizeof(UNICHAR)) ); /* Wait for read operations to complete */ MUTEX_LOCK(lts->outputMutex); MUTEX_UNLOCK(lts->outputMutex); /* Destroy output mutex */ MUTEX_DESTROY(lts->outputMutex); /* Close input buffer pipe */ if (VALID_FILEDESC(lts->writeBUFFER)) CLOSE(lts->writeBUFFER); if (VALID_FILEDESC(lts->readBUFFER)) CLOSE(lts->readBUFFER); #ifdef USE_GTK_WIDGETS /* Remove any callbacks associated with this LTERM */ for (j=0; jnfds; j++) { if (lto->callbackTag[j] != 0) { gdk_input_remove((gint) lto->callbackTag[j]); lto->callbackTag[j] = 0; } } #endif if (lts->ptyMode) { /* Close PTY */ #ifndef NO_PTY pty_close(&(lts->pty)); #endif /* !NO_PTY */ } else { /* Destroy process */ ltermDestroyProcess(&(lts->ltermProcess)); } /* Free full screen buffers */ if (lto->screenChar != NULL) FREE(lto->screenChar); if (lto->screenStyle != NULL) FREE(lto->screenStyle); /* Close LTERM */ lts->opened = 0; LTERM_LOG(ltermClose,11,("LTERM closed\n")); return 0; } /** Closes an LTERM * (documented in the LTERM interface) * @return 0 on success, or -1 on error. */ int lterm_close(int lterm) { struct lterms *lts; int returnCode; CHECK_IF_VALID_LTERM_NUMBER(lterm_close,lterm) LTERM_LOG(lterm_close,10,("Closing LTERM %d\n", lterm)); GLOBAL_LOCK; lts = ltermGlobal.termList[lterm]; if (lts == NULL) { /* Forgiving return for non-existent LTERM */ GLOBAL_UNLOCK; return 0; } if (!lts->opened) { LTERM_WARNING("lterm_close: Error - LTERM %d not opened\n", lterm); GLOBAL_UNLOCK; return -1; } SWITCH_GLOBAL_TO_UNILOCK(lterm_close,adminMutex,adminMutexLocked) returnCode = ltermClose(lts); RELEASE_UNILOCK(adminMutex,adminMutexLocked) return returnCode; } /** Deletes an LTERM object, closing it if necessary * (documented in the LTERM interface) * @return 0 on success, or -1 on error. */ int lterm_delete(int lterm) { struct lterms *lts; int returnCode; CHECK_IF_VALID_LTERM_NUMBER(lterm_delete,lterm) LTERM_LOG(lterm_delete,10,("Closing LTERM %d\n", lterm)); GLOBAL_LOCK; lts = ltermGlobal.termList[lterm]; if (lts == NULL) { /* Forgiving return for non-existent LTERM */ GLOBAL_UNLOCK; return 0; } /* Remove LTERM structure from list */ ltermGlobal.termList[lterm] = NULL; SWITCH_GLOBAL_TO_UNILOCK(lterm_open,adminMutex,adminMutexLocked) returnCode = 0; if (lts->opened) { /* Opened LTERM; close it */ returnCode = ltermClose(lts); } RELEASE_UNILOCK(adminMutex,adminMutexLocked) MUTEX_DESTROY(lts->adminMutex); /* Free memory for LTERMS structure */ FREE(lts); LTERM_LOG(lterm_delete,11,("LTERM deleted\n")); return returnCode; } /* closes all LTERMs (documented in the LTERM interface) */ void lterm_close_all() { int lterm; struct lterms *lts; LTERM_LOG(lterm_close_all,10,("\n")); GLOBAL_LOCK; for (lterm=0; lterm < MAXTERM; lterm++) { lts = ltermGlobal.termList[lterm]; if ((lts != NULL) && lts->opened) { /* Opened LTERM; close it, disregarding return code */ lts->adminMutexLocked = 1; MUTEX_LOCK(lts->adminMutex); /* NOTE,10, ("lterm_close_all:\n"******** This call is being made from within a global lock; * POTENTIAL FOR DEADLOCK? */ ltermClose(lts); lts->adminMutexLocked = 0; MUTEX_UNLOCK(lts->adminMutex); } } GLOBAL_UNLOCK; } /** Sets input echo flag for LTERM (documented in the LTERM interface) */ int lterm_setecho(int lterm, int echo_flag) { struct lterms *lts; CHECK_IF_VALID_LTERM_NUMBER(lterm_setecho,lterm) LTERM_LOG(lterm_setecho,10,("LTERM=%d, echo_flag=%d\n", lterm, echo_flag)); GLOBAL_LOCK; lts = ltermGlobal.termList[lterm]; if (lts == NULL || !lts->opened || lts->suspended) { /* LTERM is deleted/closed/suspended */ if (lts == NULL) LTERM_WARNING("lterm_setecho: Warning - LTERM %d not active\n", lterm); GLOBAL_UNLOCK; return -2; } if (lts->shellInitCommands > 0) { /* send shell initialization string */ if (ltermShellInit(lts,1) != 0) { GLOBAL_UNLOCK; return -1; } } lts->disabledInputEcho = !echo_flag; lts->restoreInputEcho = 0; GLOBAL_UNLOCK; return 0; } /** Resizes the line terminal indexed by LTERM to new row/column count. * Called from the output thread of LTERM. * @return 0 on success, or -1 on error. */ int lterm_resize(int lterm, int rows, int cols) { struct lterms *lts; struct LtermOutput *lto; CHECK_IF_VALID_LTERM_NUMBER(lterm_resize,lterm) LTERM_LOG(lterm_resize,10,("Resizing LTERM=%d, rows=%d, cols=%d\n", lterm, rows, cols)); if ((rows <= 0) || (cols <= 0)) return -1; GLOBAL_LOCK; lts = ltermGlobal.termList[lterm]; if (lts == NULL || !lts->opened || lts->suspended) { /* LTERM is deleted/closed/suspended */ if (lts == NULL) LTERM_WARNING("lterm_resize: Warning - LTERM %d not active\n", lterm); GLOBAL_UNLOCK; return -2; } if ((rows == lts->nRows) && (cols == lts->nCols)) { /* Nothing to do */ GLOBAL_UNLOCK; return 0; } lto = &(lts->ltermOutput); LTERM_LOG(lterm_resize,12,("lto->outputMode=%d\n", lto->outputMode)); /* Free full screen buffers */ if (lto->screenChar != NULL) FREE(lto->screenChar); if (lto->screenStyle != NULL) FREE(lto->screenStyle); lto->screenChar = NULL; lto->screenStyle = NULL; /* Resize screen */ lts->nRows = rows; lts->nCols = cols; if (lto->outputMode == LTERM1_SCREEN_MODE) { /* Clear screen */ if (ltermClearOutputScreen(lts) != 0) return -1; } if (lts->ptyMode) { /* Resize PTY */ #ifndef NO_PTY if (pty_resize(&(lts->pty), lts->nRows, lts->nCols, 0, 0) != 0) { GLOBAL_UNLOCK; return -1; } #endif /* !NO_PTY */ } GLOBAL_UNLOCK; return 0; } /** Sets cursor position in line terminal indexed by LTERM. * NOT YET IMPLEMENTED * Row numbers increase upward, starting from 0. * Column numbers increase rightward, starting from 0. * Called from the output thread of LTERM. * @return 0 on success, or -1 on error. */ int lterm_setcursor(int lterm, int row, int col) { struct lterms *lts; CHECK_IF_VALID_LTERM_NUMBER(lterm_setcursor,lterm) LTERM_LOG(lterm_setcursor,10, ("Setting cursor, LTERM=%d row=%d, col=%d (NOT YET IMPLEMENTED)\n", lterm, row, col)); GLOBAL_LOCK; lts = ltermGlobal.termList[lterm]; if (lts == NULL || !lts->opened || lts->suspended) { /* LTERM is deleted/closed/suspended */ if (lts == NULL) LTERM_WARNING("lterm_setcursor: Warning - LTERM %d not active\n", lterm); GLOBAL_UNLOCK; return -2; } GLOBAL_UNLOCK; return 0; } /** Writes a string to LTERM (documented in the LTERM interface) */ int lterm_write(int lterm, const UNICHAR *buf, int count, int dataType) { struct lterms *lts; FILEDESC writeBUFFER; int byteCount, sentCount, packetCount, j; UNICHAR temLine[PIPEHEADER+MAXCOL], uch; CHECK_IF_VALID_LTERM_NUMBER(lterm_write,lterm) LTERM_LOG(lterm_write,10,("Writing to LTERM %d\n", lterm)); GLOBAL_LOCK; lts = ltermGlobal.termList[lterm]; if (lts == NULL || !lts->opened || lts->suspended) { /* LTERM is deleted/closed/suspended */ if (lts == NULL) LTERM_WARNING("lterm_write: Warning - LTERM %d not active\n", lterm); GLOBAL_UNLOCK; return -2; } assert(VALID_FILEDESC(lts->writeBUFFER)); /* Save copy of input buffer file descriptor */ writeBUFFER = lts->writeBUFFER; if (lts->restoreInputEcho == 1) { /* Restore TTY echo on input */ lts->restoreInputEcho = 0; lts->disabledInputEcho = 0; } if (lts->shellInitCommands > 0) { /* send shell initialization string */ if (ltermShellInit(lts,1) != 0) { GLOBAL_UNLOCK; return -1; } } GLOBAL_UNLOCK; assert(count >= 0); sentCount = 0; while (sentCount < count) { packetCount = count - sentCount; /* always > 0 */ if (packetCount > MAXCOLM1) packetCount = MAXCOLM1; if ( (dataType == LTERM_WRITE_PLAIN_INPUT) || (dataType == LTERM_WRITE_PLAIN_OUTPUT) ) { /* Plain text data; break at control characters, * but break at ESCape character only if not the first character; * this ensures that escape sequences fit into one record */ for (j=0; j 0))) break; } if (j 1) && (temLine[PIPEHEADER+packetCount-1] == U_ESCAPE) ) { /* Save any terminal ESCape character for next packet, unless alone */ packetCount--; } } else { /* XML element data (must represent complete element) */ for (j=0; jwriteBUFFER,temLine,(SIZE_T)byteCount) != byteCount) { LTERM_ERROR("lterm_write: Error in writing to input pipe buffer\n"); return -1; } LTERM_LOG(lterm_write,11, ("wrote %d characters of type %d data to input buffer pipe\n", packetCount, dataType)); sentCount += packetCount; } return 0; } /** Reads a (possibly incomplete) line from LTERM * (documented in the LTERM interface) */ int lterm_read(int lterm, int timeout, UNICHAR *buf, int count, UNISTYLE *style, int *opcodes, int *opvals, int *buf_row, int *buf_col, int *cursor_row, int *cursor_col) { struct lterms *lts; int returnCode, temCode; struct LtermRead ltrStruc; CHECK_IF_VALID_LTERM_NUMBER(lterm_read,lterm) LTERM_LOG(lterm_read,10,("Reading from LTERM %d\n", lterm)); GLOBAL_LOCK; lts = ltermGlobal.termList[lterm]; if (lts == NULL || !lts->opened || lts->suspended) { /* LTERM is deleted/closed/suspended */ if (lts == NULL) LTERM_WARNING("lterm_read: Warning - LTERM %d not active\n", lterm); *opcodes = 0; *opvals = 0; *buf_row = 0; *buf_col = 0; *cursor_row = 0; *cursor_col = 0; GLOBAL_UNLOCK; return -2; } SWITCH_GLOBAL_TO_UNILOCK(lterm_read,outputMutex,outputMutexLocked) /* Copy "input" parameters to LtermRead structure */ ltrStruc.buf = buf; ltrStruc.style = style; ltrStruc.max_count = count; /* Call auxiliary function to read data */ temCode = ltermRead(lts, <rStruc, timeout); if (temCode == 0) { /* Return count of characters read */ returnCode = ltrStruc.read_count; } else { /* Error return */ returnCode = temCode; } /* Copy "output" parameters from LtermRead structure */ *opcodes = ltrStruc.opcodes; *opvals = ltrStruc.opvals; *buf_row = ltrStruc.buf_row; *buf_col = ltrStruc.buf_col; *cursor_row = ltrStruc.cursor_row; *cursor_col = ltrStruc.cursor_col; if (returnCode == -1) { /* Error return code; suspend TTY */ LTERM_WARNING("lterm_read: Warning - LTERM %d suspended due to error\n", lterm); lts->suspended = 1; } RELEASE_UNILOCK(outputMutex,outputMutexLocked) GLOBAL_LOCK; if ((*opcodes != 0) && (lts->shellInitCommands > 0)) { /* send shell initialization string */ if (ltermShellInit(lts,0) != 0) { GLOBAL_UNLOCK; return -1; } } GLOBAL_UNLOCK; LTERM_LOG(lterm_read,11,("return code = %d, opcodes=0x%x, opvals=%d\n", returnCode, *opcodes, *opvals)); return returnCode; } /** Sends shell initialization commands, if any * (WORKS UNDER GLOBAL_LOCK) * @param all if true, send all init commands, else send just one command * @return 0 on success and -1 on error. */ static int ltermShellInit(struct lterms *lts, int all) { int shellInitLen, lowCommand, j; if (lts->shellInitCommands <= 0) return 0; LTERM_LOG(ltermShellInit,20,("sending shell initialization string\n")); if (all) { /* Send all commands */ lowCommand = 0; } else { /* Send single command */ lowCommand = lts->shellInitCommands - 1; } for (j=lts->shellInitCommands-1; j>=lowCommand; j--) { /* Send shell init command and decrement count */ lts->shellInitCommands--; shellInitLen = strlen(lts->shellInitStr[j]); if (shellInitLen > 0) { /* Send shell initialization string */ UNICHAR temLine[PIPEHEADER+MAXCOL]; int remaining, encoded, byteCount; utf8toucs(lts->shellInitStr[j], shellInitLen, temLine+PIPEHEADER, MAXCOL, 1, &remaining, &encoded); if (remaining > 0) { LTERM_ERROR( "ltermShellInit: Shell init command %d string too long\n", j+1); return -1; } temLine[0] = (UNICHAR) encoded; temLine[PHDR_TYPE] = (UNICHAR) LTERM_WRITE_PLAIN_INPUT; byteCount = (PIPEHEADER+encoded)*sizeof(UNICHAR); if (WRITE(lts->writeBUFFER, temLine, (SIZE_T)byteCount) != byteCount) { LTERM_ERROR("ltermShellInit: Error in writing to input pipe buffer\n"); return -1; } } } return 0; } /** Creates unidirectional pipe which can be written to and read from using * the file descriptors writePIPE and readPIPE. * @return 0 on success and -1 on error. */ static int ltermCreatePipe(FILEDESC *writePIPE, FILEDESC *readPIPE) { FILEDESC pipeFD[2]; LTERM_LOG(ltermCreatePipe,20,("Creating pipe\n")); /* Assume that pipe is unidirectional, although in some UNIX systems pipes are actually bidirectional */ if (PIPE(pipeFD) == -1) { LTERM_ERROR("ltermCreatePipe: Error - pipe creation failed\n"); return -1; } /* Copy pipe file descriptor */ *readPIPE = pipeFD[0]; *writePIPE = pipeFD[1]; return 0; } /** Creates a new process to execute the command line contained in array * ARGV and redirects its standard I/O and standard error through pipes. * The process details are stored in the structure pointed to by LTP. * If NOSTDERR is true, STDERR is redirected to STDOUT. * If NOEXPORT is true, then the current environment is not exported * to the new process. * @return 0 on success and -1 on error. */ static int ltermCreateProcess(struct LtermProcess *ltp, char *const argv[], int nostderr, int noexport) { FILEDESC stdIN, stdOUT, stdERR; #ifdef USE_NSPR_IO PRProcessAttr *processAttr; #else long child_pid; #endif LTERM_LOG(ltermCreateProcess,20,("Creating process %s, nostderr=%d, noexport=%d\n", argv[0], nostderr, noexport)); if (nostderr) { /* No STDERR pipe */ ltp->processERR = NULL_FILEDESC; stdERR= NULL_FILEDESC; } else { /* Create STDERR pipe: needs clean-up */ FILEDESC pipeFD[2]; if (PIPE(pipeFD) == -1) { LTERM_ERROR("ltermCreateProcess: Error - STDERR pipe creation failed\n"); return -1; } /* Copy pipe file descriptor */ ltp->processERR = pipeFD[0]; stdERR = pipeFD[1]; } /* Create I/O pipes: needs clean up */ if ( (ltermCreatePipe(&(ltp->processIN), &stdIN) != 0) || (ltermCreatePipe(&stdOUT, &(ltp->processOUT)) != 0) ) return -1; #ifdef USE_NSPR_IO /* Create new process attribute structure */ processAttr = PR_NewProcessAttr(); /* Redirect standard error for process */ if (VALID_FILEDESC(stdERR)) PR_ProcessAttrSetStdioRedirect(processAttr, (PRSpecialFD) 2, stdERR); else PR_ProcessAttrSetStdioRedirect(processAttr, (PRSpecialFD) 2, stdOUT); /* Redirect standard I/O for process */ PR_ProcessAttrSetStdioRedirect(processAttr, (PRSpecialFD) 0, stdIN); PR_ProcessAttrSetStdioRedirect(processAttr, (PRSpecialFD) 1, stdOUT); /* Create NSPR process; do not export environment */ ltp->processID = PR_CreateProcess(argv[0], argv, (char *const *)0, processAttr); if (!VALID_PROCESS(ltp->processID)) { LTERM_ERROR("ltermCreateProcess: Error in creating NSPR process %s\n", argv[0]); return -1; } LTERM_LOG(ltermCreateProcess,21,("Created NSPR process\n")); #else /* not USE_NSPR_IO */ /* Fork a child process (FORK) */ child_pid = fork(); if (child_pid < 0) { LTERM_ERROR("ltermCreateProcess: Error - fork failed\n"); return -1; } LTERM_LOG(ltermCreateProcess,21,("vfork child pid = %d\n", child_pid)); if (child_pid == 0) { /* Child process */ int fdMax, fd; /* Redirect to specified descriptor or to PTY */ if (VALID_FILEDESC(stdERR)) { /* Redirect STDERR to specified file descriptor */ if (dup2(stdERR, 2) == -1) { LTERM_ERROR("ltermCreateProcess: Error - failed dup2 for specified stderr\n"); return -1; } } else { /* Redirect STDERR to STDOUT pipe */ if (dup2(stdOUT, 2) == -1) { LTERM_ERROR("ltermCreateProcess: Error - failed dup2 for default stderr\n"); return -1; } } /* Redirect STDIN and STDOUT to pipes */ if (dup2(stdIN, 0) == -1) { LTERM_ERROR("ltermCreateProcess: Error - failed dup2 for stdin\n"); return -1; } if (dup2(stdOUT, 1) == -1) { LTERM_ERROR("ltermCreateProcess: Error - failed dup2 for stdout\n"); return -1; } /* Close all other file descriptors in child process */ fdMax = sysconf(_SC_OPEN_MAX); for (fd = 3; fd < fdMax; fd++) close(fd); if (argv != NULL) { /* Execute specified command with arguments */ if (noexport) execve(argv[0], argv, NULL); else execvp(argv[0], argv); LTERM_ERROR("ltermCreateProcess: Error in executing command %s\n", argv[0]); return -1; } } /* Parent process; copy child pid */ ltp->processID = child_pid; /* Close child-side pipe end(s) */ if (stdIN != stdOUT) close(stdIN); close(stdOUT); /* Close writable end of STDERR pipe in parent process */ if (VALID_FILEDESC(stdERR)) close(stdERR); #endif /* not USE_NSPR_IO */ LTERM_LOG(ltermCreateProcess,21,("return code = 0\n")); return 0; } /** Destroys process pointed by LTP */ static void ltermDestroyProcess(struct LtermProcess *ltp) { LTERM_LOG(ltermDestroyProcess,20,("Destroying process\n")); if (VALID_PROCESS(ltp->processID)) { /* Kill process */ #ifdef USE_NSPR_IO /* **NOTE** Possible memory leak here, because processID is not freed? */ PR_KillProcess(ltp->processID); #else /* not USE_NSPR_IO */ LTERM_LOG(ltermDestroyProcess,21,("Killing process %d\n",ltp->processID)); kill(ltp->processID, SIGKILL); #endif /* not USE_NSPR_IO */ ltp->processID = NULL_PROCESS; } /* Close process pipes (assumed to be unidirectional) */ if (VALID_FILEDESC(ltp->processERR)) CLOSE(ltp->processERR); if (VALID_FILEDESC(ltp->processOUT)) CLOSE(ltp->processOUT); if (VALID_FILEDESC(ltp->processIN)) CLOSE(ltp->processIN); }