/* OpenDoors Online Software Programming Toolkit * (C) Copyright 1991 - 1999 by Brian Pirie. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * File: ODKrnl.c * * Description: Contains the OpenDoors kernel, which is responsible for many * of the core functions which continue regardless of what the * client program is doing. The implementation of this file is * central to the OpenDoors architecture. The functionality * implemented by the OpenDoors kernel includes (but is not * limited to): * * - Obtaining and input from the user, through the modem * and possibly the local keyboard. * - Monitoring maximum time and inactivity time limits. * - Responding to loss of carrier. * - Forcing the status line to be updated regularily, * on platforms that it exists. * - Implementing the system operator <-> remote user chat * mode. * * Revisions: Date Ver Who Change * --------------------------------------------------------------- * Jan 01, 1995 6.00 BP Split off from odcore.c * Nov 11, 1995 6.00 BP Removed register keyword. * Nov 14, 1995 6.00 BP Added include of odscrn.h. * Nov 15, 1995 6.00 BP 32-bit portability. * Nov 16, 1995 6.00 BP Removed oddoor.h, added odcore.h. * Nov 17, 1995 6.00 BP Use new input queue mechanism. * Nov 21, 1995 6.00 BP Ported to Win32. * Dec 12, 1995 6.00 BP Added entry, exit and kernel macros. * Dec 13, 1995 6.00 BP Moved chat mode code to ODKrnl.h. * Dec 24, 1995 6.00 BP od_chat_active = TRUE on chat start. * Dec 30, 1995 6.00 BP Added ODCALL for calling convention. * Jan 04, 1996 6.00 BP tODInQueueEvent -> tODInputEvent. * Jan 12, 1996 6.00 BP Added bOnlyShiftArrow. * Jan 30, 1996 6.00 BP Replaced od_yield() with od_sleep(). * Jan 30, 1996 6.00 BP Add semaphore timeout. * Feb 06, 1996 6.00 BP Added od_silent_mode. * Feb 19, 1996 6.00 BP Changed version number to 6.00. * Feb 23, 1996 6.00 BP Only create active semapore once. * Mar 03, 1996 6.10 BP Begin version 6.10. * Mar 06, 1996 6.10 BP Prevent TC generated N_SCOPY@ call. * Mar 13, 1996 6.10 BP bOnlyShiftArrow -> nArrowUseCount. * Mar 19, 1996 6.10 BP MSVC15 source-level compatibility. * Oct 22, 2001 6.21 RS Lowered thread priorities to normal. * Aug 10, 2003 6.23 SH *nix support */ #define BUILDING_OPENDOORS #include #include #include #include #include #include #include "OpenDoor.h" #ifdef ODPLAT_NIX #include #include #include #include #include #endif #include "ODCore.h" #include "ODGen.h" #include "ODPlat.h" #include "ODCom.h" #include "ODKrnl.h" #include "ODScrn.h" #include "ODInQue.h" #include "ODInEx.h" #ifdef ODPLAT_WIN32 #include "ODFrame.h" #endif /* ODPLAT_WIN32 */ /* Multithreading performance tuning. */ #define REMOTE_INPUT_THREAD_PRIORITY OD_PRIORITY_NORMAL /* was ABOVE_NORMAL */ #define NO_CARRIER_THREAD_PRIORITY OD_PRIORITY_NORMAL /* was ABOVE_NORMAL */ #define NO_CARRIER_THREAD_SLEEP_TIME 6000 #define TIME_UPDATE_THREAD_PRIORITY OD_PRIORITY_NORMAL #define TIME_UPDATE_THREAD_SLEEP_TIME 3000 /* Misc performance tuning. */ #define STATUS_UPDATE_PERIOD 3L #define CHAT_YIELD_PERIOD 25L /* Pending command identifiers. */ #define KERNEL_FUNC_CHATTOGGLE 0x0001 /* Private function prototypes. */ static void ODKrnlHandleReceivedChar(char chReceived, BOOL bFromRemote); static void ODKrnlTimeUpdate(void); static void ODKrnlChatCleanup(void); static void ODKrnlChatMode(void); #ifdef ODPLAT_NIX #ifdef USE_KERNEL_SIGNAL static void sig_run_kernel(int sig); static void sig_get_char(int sig); static void sig_no_carrier(int sig); #endif #endif /* Functions specific to the multithreaded implementation of the kernel. */ #ifdef OD_MULTITHREADED /* Thread proceedures. */ DWORD OD_THREAD_FUNC ODKrnlRemoteInputThread(void *pParam); DWORD OD_THREAD_FUNC ODKrnlNoCarrierThread(void *pParam); DWORD OD_THREAD_FUNC ODKrnlTimeUpdateThread(void *pParam); DWORD OD_THREAD_FUNC ODKrnlChatThread(void *pParam); /* Helper functions. */ static void ODKrnlWaitForExclusiveControl(void); static void ODKrnlGiveUpExclusiveControl(void); #endif /* OD_MULTITHREADED */ /* Local working variables. */ #ifdef OD_MULTITHREADED static tODThreadHandle hRemoteInputThread = NULL; static tODThreadHandle hNoCarrierThread = NULL; static tODThreadHandle hTimeUpdateThread = NULL; static tODThreadHandle hClientThread = NULL; static tODThreadHandle hChatThread = NULL; static BOOL bHaveExclusiveControl; static BOOL bChatActivatedInternally; #endif /* OD_MULTITHREADED */ static BOOL bKernelActive = FALSE; static BOOL bWarnedAboutInactivity = FALSE; static INT16 nLastInactivitySetting = 0; static time_t nNextStatusUpdateTime; static INT nKrnlFuncPending; static BOOL bLastStatusSetting; static INT16 nChatOriginalAttrib; /* Global kernel-related variables. */ tODTimer RunKernelTimer; time_t nNextTimeDeductTime; char chLastControlKey = '\0'; INT nArrowUseCount = 0; BOOL bForceStatusUpdate = FALSE; BOOL bIsShell; #ifdef OD_MULTITHREADED tODSemaphoreHandle hODActiveSemaphore = NULL; #endif /* OD_MULTITHREADED */ /* ========================================================================= */ /* Core of the OpenDoors Kernel. */ /* ========================================================================= */ /* ---------------------------------------------------------------------------- * ODKrnlInitialize() * * Initializes kernel activities. In multithreaded versions of OpenDoors, this * is the function that starts the various kernel threads. * * Parameters: kODRCSuccess on success, or an error code on failure. * * Return: void */ tODResult ODKrnlInitialize(void) { #ifdef ODPLAT_NIX sigset_t block; #ifdef USE_KERNEL_SIGNAL struct sigaction act; struct itimerval itv; #endif #endif tODResult Result = kODRCSuccess; #ifdef ODPLAT_NIX #ifdef USE_KERNEL_SIGNAL /* HUP Detection */ act.sa_handler=sig_no_carrier; /* If two HUP signals are recieved, die on the second */ act.sa_flags=SA_RESETHAND|SA_RESTART; sigemptyset(&(act.sa_mask)); sigaction(SIGHUP,&act,NULL); /* Run kernel on SIGALRM (Every .01 seconds) */ act.sa_handler=sig_run_kernel; act.sa_flags=SA_RESTART; sigemptyset(&(act.sa_mask)); sigaction(SIGALRM,&act,NULL); itv.it_interval.tv_sec=0; itv.it_interval.tv_usec=10000; itv.it_value.tv_sec=0; itv.it_value.tv_usec=10000; setitimer(ITIMER_REAL,&itv,NULL); /* Make stdin signal driven. */ // act.sa_handler=sig_get_char; // act.sa_flags=0; // sigemptyset(&(act.sa_mask)); // sigaction(SIGIO,&act,NULL); // // /* Have SIGIO signals delivered to this process */ // fcntl(0,F_SETOWN,getpid()); // // /* Enable SIGIO when read possible on stdin */ // fcntl(0,F_SETFL,fcntl(0,F_GETFL)|O_ASYNC); /* Make sure SIGHUP, SIGALRM, and SIGIO are unblocked */ sigemptyset(&block); sigaddset(&block,SIGHUP); sigaddset(&block,SIGALRM); #if 0 sigaddset(&block,SIGIO); #endif sigprocmask(SIG_UNBLOCK,&block,NULL); #else /* Using ODComCarrier... don't catch HUP signal */ sigemptyset(&block); sigaddset(&block,SIGHUP); sigprocmask(SIG_BLOCK,&block,NULL); #endif #endif /* Initialize time of next status update and next time deduction. */ nNextStatusUpdateTime = time(NULL) + STATUS_UPDATE_PERIOD; nNextTimeDeductTime = time(NULL) + 60L; bLastStatusSetting = od_control.od_status_on = TRUE; /* Initially, no kernel functions are pending. */ nKrnlFuncPending = 0; /* Initially, the kernel is not active. */ bKernelActive = FALSE; #ifdef OD_MULTITHREADED /* Initially, we do not have exclusive control of the application. */ bHaveExclusiveControl = FALSE; /* Obtain a handle to the client thread. */ hClientThread = ODThreadGetCurrent(); /* Create OpenDoors activation semaphore. */ if(hODActiveSemaphore == NULL) { Result = ODSemaphoreAlloc(&hODActiveSemaphore, 0, INT_MAX); if(Result != kODRCSuccess) return(Result); } /* Start the remote input thread if we are not operating in local mode. */ if(od_control.baud != 0) { Result = ODThreadCreate(&hRemoteInputThread, ODKrnlRemoteInputThread, NULL); if(Result != kODRCSuccess) return(Result); ODThreadSetPriority(hRemoteInputThread, REMOTE_INPUT_THREAD_PRIORITY); } /* Start the carrier detection thread if we are not operating in local */ /* mode. */ if(od_control.baud != 0) { Result = ODThreadCreate(&hNoCarrierThread, ODKrnlNoCarrierThread, NULL); if(Result != kODRCSuccess) return(Result); ODThreadSetPriority(hNoCarrierThread, NO_CARRIER_THREAD_PRIORITY); } /* Start the time update thread. */ Result = ODThreadCreate(&hTimeUpdateThread, ODKrnlTimeUpdateThread, 0); if(Result != kODRCSuccess) return(Result); ODThreadSetPriority(hTimeUpdateThread, TIME_UPDATE_THREAD_PRIORITY); #endif /* OD_MULTITHREADED */ /* Return with success. */ return(Result); } /* ---------------------------------------------------------------------------- * ODKrnlShutdown() * * Shuts down kernel activities. * * Parameters: none * * Return: void */ void ODKrnlShutdown(void) { if(bKernelActive) return; #ifdef OD_MULTITHREADED #if defined(OD_DIAGNOSTICS) && defined(ODPLAT_WIN32) if(od_control.od_internal_debug) MessageBox(NULL, "Terminating remote input thread", "OpenDoors Diagnostics", MB_OK); #endif /* Shutdown the remote input thread, if it exists. */ if(hRemoteInputThread != NULL) ODThreadTerminate(hRemoteInputThread); #if defined(OD_DIAGNOSTICS) && defined(ODPLAT_WIN32) if(od_control.od_internal_debug) MessageBox(NULL, "Terminating carrier detection", "OpenDoors Diagnostics", MB_OK); #endif /* Shutdown the carrier detection thread, if it exists. */ if(hNoCarrierThread != NULL) ODThreadTerminate(hNoCarrierThread); #if defined(OD_DIAGNOSTICS) && defined(ODPLAT_WIN32) if(od_control.od_internal_debug) MessageBox(NULL, "Terminating time update thread", "OpenDoors Diagnostics", MB_OK); #endif /* Shutdown the time update thread, if it exists. */ if(hTimeUpdateThread != NULL) ODThreadTerminate(hTimeUpdateThread); #if defined(OD_DIAGNOSTICS) && defined(ODPLAT_WIN32) if(od_control.od_internal_debug) MessageBox(NULL, "Releasing activation semaphore", "OpenDoors Diagnostics", MB_OK); #endif #endif /* OD_MULTITHREADED */ } /* ---------------------------------------------------------------------------- * od_kernel() * * Carries out any kernel tasks that must be performed through regular, * explicit calls to this function, * * Parameters: none * * Return: void */ ODAPIDEF void ODCALL od_kernel(void) { #ifndef OD_MULTITHREADED char ch; #ifdef ODPLAT_DOS WORD wKey; BYTE btShiftStatus; char *pszShellName; #endif BOOL bCarrier; #endif /* OD_MULTITHREADED */ /* Log function entry if running in trace mode. */ TRACE(TRACE_API, "od_kernel()"); /* Initialize OpenDoors if not already done. */ if(!bODInitialized) od_init(); /* If this is an attempt at a re-entrant call to od_kernel() from another */ /* function called by a currently active od_kernel(), then return without */ /* doing anything. */ if(bKernelActive) return; OD_API_ENTRY(); /* Note that kernel is active to prevent recursive calls to the kernel. */ bKernelActive = TRUE; /* Call od_ker_exec function if required. */ if(od_control.od_ker_exec != NULL) { (*od_control.od_ker_exec)(); } /* The remainder of od_kernel() only applies to non-multithreaded */ /* versions of OpenDoors. */ #ifndef OD_MULTITHREADED /* If not operating in local mode, then perform remote-mode specific */ /* activies. */ if(od_control.baud != 0) { #ifndef USE_KERNEL_SIGNAL /* If carrier detection is enabled, then shutdown OpenDoors if */ /* the carrier detect signal is no longer high. */ if(!(od_control.od_disable&DIS_CARRIERDETECT)) { ODComCarrier(hSerialPort, &bCarrier); if(!bCarrier) { ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_NOCARRIER); } } #endif /* Loop, obtaining any new characters from the serial port and */ /* adding them to the common local/remote input queue. */ while(ODComGetByte(hSerialPort, &ch, FALSE) == kODRCSuccess) { ODKrnlHandleReceivedChar(ch, TRUE); } } #ifdef ODPLAT_DOS check_keyboard_again: if(nKrnlFuncPending && !bShellChatActive) { if(nKrnlFuncPending & KERNEL_FUNC_CHATTOGGLE) { nKrnlFuncPending &=~ KERNEL_FUNC_CHATTOGGLE; goto chat_pressed; } } /* Don't check local keyboard if sysop DIS_SYSOP_KEYS is set, or if we */ /* are operatingin silent mode. */ if(od_control.od_disable & DIS_SYSOP_KEYS || od_control.od_silent_mode) { goto after_key_check; } ASM mov ah, 1 ASM push si ASM push di ASM int 0x16 ASM jnz key_waiting ASM pop di ASM pop si ASM jmp after_key_check key_waiting: ASM mov ah, 0 ASM int 0x16 ASM mov wKey, ax ASM mov ah, 2 ASM int 0x16 ASM mov btShiftStatus, al ASM pop di ASM pop si if(nArrowUseCount > 0 && (wKey == 0x4800 || wKey == 0x5000) && !(btShiftStatus & 2)) { /* Pass key on to od_local_input, if it is defined. */ if(od_control.od_local_input != NULL) { (*od_control.od_local_input)(wKey); } /* Add this key to the local/remote input queue. */ ODKrnlHandleLocalKey(wKey); } /* If hangup key is pressed. */ else if(wKey == od_control.key_hangup) { ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_HANGUP); } /* If drop to BBS key is pressed. */ else if(wKey == od_control.key_drop2bbs) { ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_DROPTOBBS); } else if(wKey == od_control.key_dosshell) { if(!bShellChatActive) { if(pfLogWrite != NULL) (*pfLogWrite)(6); /* If function hook is defined. */ if(od_control.od_cbefore_shell != NULL) { /* Then call it. */ bShellChatActive = TRUE; (*od_control.od_cbefore_shell)(); bShellChatActive = FALSE; } if(od_control.od_before_shell != NULL) od_disp_str(od_control.od_before_shell); if((pszShellName = (char *)getenv("COMSPEC")) == NULL) { pszShellName = (char *)"COMMAND.COM"; } bIsShell = TRUE; od_spawnvpe(P_WAIT, pszShellName, NULL, NULL); bIsShell = FALSE; if(od_control.od_after_shell != NULL) od_disp_str(od_control.od_after_shell); /* If a function hook is defined. */ if(od_control.od_cafter_shell != NULL) { /* Then call it. */ bShellChatActive = TRUE; (*od_control.od_cafter_shell)(); bShellChatActive = FALSE; } if(pfLogWrite != NULL) (*pfLogWrite)(7); } } /* If toggle chat mode key is pressed. */ else if(wKey == od_control.key_chat) { chat_pressed: if(!bShellChatActive || od_control.od_chat_active) { /* If chat mode is active. */ if(od_control.od_chat_active) { /* Signal exit of chat mode. */ ODKrnlEndChatMode(); } /* If chat mode is off. */ else { /* Enable second call to kernel. */ bKernelActive = FALSE; /* Enter chat mode. */ ODKrnlChatMode(); /* Disable second call to kernel. */ bKernelActive = TRUE; } } else { if(nKrnlFuncPending & KERNEL_FUNC_CHATTOGGLE) { nKrnlFuncPending &= ~KERNEL_FUNC_CHATTOGGLE; } else { nKrnlFuncPending |= KERNEL_FUNC_CHATTOGGLE; } } } /* If sysop next key is pressed. */ else if(wKey == od_control.key_sysopnext) { /* Toggle sysop next setting. */ od_control.sysop_next = !od_control.sysop_next; /* Update status line. */ goto statup; } /* If ESCape key is pressed and we are in chat mode. */ else if((wKey&0xff) == 27 && od_control.od_chat_active) { /* Signal exit from chat mode. */ od_control.od_chat_active = FALSE; } /* If lockout user key is pressed. */ else if(wKey == od_control.key_lockout) { /* Set the user's access security level to 0. */ od_control.user_security = 0; /* Shutdown OpenDoors. */ ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_HANGUP); } /* If toggle keyboard off key is pressed. */ else if(wKey == od_control.key_keyboardoff) { /* Toggle user keyboard settings. */ od_control.od_user_keyboard_on =! od_control.od_user_keyboard_on; /* Update status line. */ goto statup; } /* If increase time key is pressed. */ else if(wKey == od_control.key_moretime) { /* If time limit is less than maximum possible time limit. */ if(od_control.user_timelimit < 1440) { /* Increase time left online. */ ++od_control.user_timelimit; } /* Update status line. */ goto statup; } /* If decrease time key is pressed. */ else if(wKey == od_control.key_lesstime) { /* Never let user's time limit be set to a negative value. */ if(od_control.user_timelimit > 0) { /* Decrease user's timelimit. */ --od_control.user_timelimit; } /* Update the status line. */ goto statup; } else { for(ch = 0; ch < 9; ++ch) { if(wKey == od_control.key_status[ch]) { if(btCurrentStatusLine != ch && od_control.od_status_on) { od_set_statusline(ch); } goto check_keyboard_again; } } /* Look for user-defined hotkeys. */ for(ch=0; ch= od_control.od_num_keys) { /* Pass key on to od_local_input, if it is defined. */ if(od_control.od_local_input != NULL) { (*od_control.od_local_input)(wKey); } /* Add this key to the local/remote input queue. */ ODKrnlHandleLocalKey(wKey); } } goto check_keyboard_again; after_key_check: /* If status line has been turned on since last call to kernel. */ if(bLastStatusSetting != od_control.od_status_on) { /* Generate the status line. */ od_set_statusline(0); } bLastStatusSetting = od_control.od_status_on; if(od_control.od_update_status_now) { od_set_statusline(btCurrentStatusLine); od_control.od_update_status_now = FALSE; } /* Update status line when needed. */ if(nNextStatusUpdateTime < time(NULL) || bForceStatusUpdate) { statup: nNextStatusUpdateTime = time(NULL) + STATUS_UPDATE_PERIOD; /* Turn off status line update force flag */ bForceStatusUpdate = FALSE; if(od_control.od_status_on && btCurrentStatusLine != 8) { /* Store console settings. */ ODStoreTextInfo(); /* Enable writes to whole screen. */ ODScrnSetBoundary(1, 1, 80, 25); ODScrnEnableCaret(FALSE); (*pfCurrentPersonality)((BYTE)(10 + btCurrentStatusLine)); ODRestoreTextInfo(); ODScrnEnableCaret(TRUE); } } #endif ODKrnlTimeUpdate(); ODTimerStart(&RunKernelTimer, 250); OD_API_EXIT(); bKernelActive = FALSE; #endif /* !OD_MULTITHREADED */ } /* ---------------------------------------------------------------------------- * ODKrnlHandleLocalKey() * * Called when a key is pressed on the local keyboard that should be placed * in the common local/remote input queue. This function is not called for * sysop function keys. * * Parameters: wKeyCode * * Return: void */ void ODKrnlHandleLocalKey(WORD wKeyCode) { /* If local keyboard input by sysop has not been disabled. */ if(!(od_control.od_disable & DIS_LOCAL_INPUT)) { if((wKeyCode & 0xff) == 0) { ODKrnlHandleReceivedChar('\0', FALSE); ODKrnlHandleReceivedChar((char)(wKeyCode >> 8), FALSE); } else { ODKrnlHandleReceivedChar((char)wKeyCode, FALSE); } } } /* ---------------------------------------------------------------------------- * ODKrnlHandleReceivedChar() *** PRIVATE FUNCTION *** * * Called when a character is received from the local or remote system. * * Parameters: chReceived - Character that should be handled. * * bFromRemote - TRUE if this character was received from the * remote system, FALSE if it originated from the * local console. * * Return: void */ static void ODKrnlHandleReceivedChar(char chReceived, BOOL bFromRemote) { tODInputEvent InputEvent; /* If we are operating in remote mode, and remote user keyboard has been */ /* disabled by the sysop, then return, ignoring this character. */ if(bFromRemote && !od_control.od_user_keyboard_on) { return; } /* Add this input event to the local/remote common input queue. */ InputEvent.EventType = EVENT_CHARACTER; InputEvent.bFromRemote = bFromRemote; InputEvent.chKeyPress = chReceived; ODInQueueAddEvent(hODInputQueue, &InputEvent); /* Update last control key information. */ switch(chReceived) { case 's': case 'S': case 3: case 11: case 0x18: chLastControlKey = 's'; break; case 'p': case 'P': chLastControlKey = 'p'; } } /* ---------------------------------------------------------------------------- * ODKrnlTimeUpdate() *** PRIVATE FUNCTION *** * * Performs regular updating of time remaining online, inactivity time, and * forces OpenDoors to exit if a time limit has been exceeded. * * Parameters: None * * Return: void */ static void ODKrnlTimeUpdate(void) { time_t CurrentTime; static char szTemp[80]; /* Obtain the current time. */ CurrentTime = time(NULL); /* If inactivity setting has changed. */ if(nLastInactivitySetting != od_control.od_inactivity) { /* If it was previously disabled. */ if(nLastInactivitySetting == 0) { /* Prevent immediate timeout. */ ODInQueueResetLastActivity(hODInputQueue); } /* Store current value. */ nLastInactivitySetting = od_control.od_inactivity; } /* Check user keyboard inactivity. */ if((ODInQueueGetLastActivity(hODInputQueue) + od_control.od_inactivity) < CurrentTime) { /* If timeout, display message. */ if(od_control.od_inactivity != 0 && !od_control.od_disable_inactivity) { if(od_control.od_time_msg_func == NULL) { od_disp_str(od_control.od_inactivity_timeout); } else { (*od_control.od_time_msg_func)(od_control.od_inactivity_timeout); } /* End connection. */ ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_INACTIVITY); } } /* If less than 5s left of inactivity. */ else if(ODInQueueGetLastActivity(hODInputQueue) + od_control.od_inactivity < CurrentTime + od_control.od_inactive_warning) { if(!bWarnedAboutInactivity && od_control.od_inactivity != 0 && !od_control.od_disable_inactivity) { /* Warn the user. */ if(od_control.od_time_msg_func == NULL) { od_disp_str(od_control.od_inactivity_warning); } else { (*od_control.od_time_msg_func)(od_control.od_inactivity_warning); } /* Don't warn the user a second time. */ bWarnedAboutInactivity = TRUE; } } else { /* Re-enable inactivity warning. */ bWarnedAboutInactivity = FALSE; } /* If chat mode is active. */ if(od_control.od_chat_active) { /* Prevent the user's time from being drained. */ nNextTimeDeductTime = time(NULL) + 60; } /* If 1 minute has passed since last time update. */ if(CurrentTime >= nNextTimeDeductTime) { /* Next time update should occur 60 seconds after this one was */ /* scheduled. */ nNextTimeDeductTime += 60; /* Force status line to be updated immediately. */ bForceStatusUpdate = TRUE; /* Decrement time left. */ --od_control.user_timelimit; /* If the user's time limit is close to expiring, then notify */ /* the user. */ if(od_control.user_timelimit <= 3 && od_control.user_timelimit > 0 && !(od_control.od_disable & DIS_TIMEOUT)) { /* If less than 3 mins left, tell user. */ sprintf(szTemp, od_control.od_time_warning, od_control.user_timelimit); if(od_control.od_time_msg_func == NULL) { od_disp_str(szTemp); } else { (*od_control.od_time_msg_func)(szTemp); } } #ifdef ODPLAT_WIN32 ODFrameUpdateTimeDisplay(); #endif /* ODPLAT_WIN32 */ } /* If user has no time left. */ if(od_control.user_timelimit <= 0 && !(od_control.od_disable & DIS_TIMEOUT)) { /* Notify the user. */ if(od_control.od_time_msg_func == NULL) { od_disp_str(od_control.od_no_time); } else { (*od_control.od_time_msg_func)(od_control.od_no_time); } /* Force OpenDoors to shutdown. */ ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_TIMEOUT); } } /* ---------------------------------------------------------------------------- * ODKrnlForceOpenDoorsShutdown() * * Called to force the application to exit due to some event in OpenDoors, * such as loss of carrier, user inactivity timeout, the hangup command * being chosen by the system operator, etc. The only time when OpenDoors * is shutdown without going through this function should be as a result of * an explicit call to od_exit() by the client application. * * Parameters: btReasonForShutdown - An OpenDoors exit reason code. * * Return: Never returns. */ void ODKrnlForceOpenDoorsShutdown(BYTE btReasonForShutdown) { BOOL bHangup; #ifdef OD_MULTITHREADED /* First, wait until an OpenDoors API is active. This way, we won't */ /* interrupt any client application operations that may leave the */ /* system in an unstable state (for instance, interrupting some file */ /* I/O operations). */ ODKrnlWaitForExclusiveControl(); #endif /* OD_MULTITHREADED */ bKernelActive = TRUE; /* Determine whether we should hangup on the user before exiting. */ if(btReasonForShutdown == ERRORLEVEL_HANGUP || btReasonForShutdown == ERRORLEVEL_INACTIVITY) { bHangup = TRUE; } else { bHangup = FALSE; } /* Record exit reason in global variable. */ btExitReason = btReasonForShutdown - 1; /* Use the client-defined errorlevel, if any. */ if(od_control.od_errorlevel[0]) { od_exit(od_control.od_errorlevel[btReasonForShutdown], bHangup); } /* Otherwise, use the default OpenDoors errorlevel. */ else { od_exit(btReasonForShutdown - 1, bHangup); } } /* ========================================================================= */ /* OpenDoors Kernel multithreaded implementation. */ /* ========================================================================= */ #ifdef OD_MULTITHREADED /* ---------------------------------------------------------------------------- * ODKrnlRemoteInputThread() *** PRIVATE FUNCTION *** * * Code for the remote input thread. This thread executes an infinite loop, * blocking until a character is received from the remote system, and then * adding this character to the common local/remote input queue. This thread * should be given higher than normal priority. * * In non-multithreaded versions of OpenDoors, the task of checking for new * characters from the remote system and adding them to the common input * queue is performed on each call to od_kernel(). * * Parameters: As dictated for any thread function. * * Return: As dictated for any thread function. */ DWORD OD_THREAD_FUNC ODKrnlRemoteInputThread(void *pParam) { char chReceived; /* We keep looping until someone else terminates this thread. */ for(;;) { /* Get next character from the modem, blocking if no character */ /* is waiting. */ ODComGetByte(hSerialPort, &chReceived, TRUE); /* Handle this received character, adding it to the local/remote */ /* common input queue, if appropriate. */ ODKrnlHandleReceivedChar(chReceived, TRUE); } return(0); } /* ---------------------------------------------------------------------------- * ODKrnlNoCarrierThread() *** PRIVATE FUNCTION *** * * Thread which performs carrier detection. Normally, this thread doesn't * execute at all, but instead blocks waiting for a no carrier serial port * event. Only when the carrier detect signal goes low does this thread * execute, performing its one purpose in live - to trigger an OpenDoors * shutdown. This thread should be given higher than normal priority. * * This thread should only be created when OpenDoors is operating in remote * mode. * * In non-multithreaded versions of OpenDoors, this task is performed by * od_kernel(). * * Parameters: As dictated for any thread function. * * Return: As dictated for any thread function. */ DWORD OD_THREAD_FUNC ODKrnlNoCarrierThread(void *pParam) { /* Block until the carrier detect signal goes low with carrier */ /* detection enabled. */ for(;;) { /* Wait for carrier detect signal to go low. */ ODComWaitEvent(hSerialPort, kNoCarrier); /* If carrier detection has not been disabled, then we have found */ /* a condition where OpenDoors should exit. */ if(!(od_control.od_disable&DIS_CARRIERDETECT)) break; /* If we have no carrier but carrier detection is currently */ /* disabled, then we sleep for a while before checking again. */ /* This isn't a very elegant implementation, and perhaps a */ /* better approach will be used for future versions. */ od_sleep(NO_CARRIER_THREAD_SLEEP_TIME); } /* Force OpenDoors to exit. */ ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_NOCARRIER); return(0); } /* ---------------------------------------------------------------------------- * ODKrnlTimeUpdateThread() *** PRIVATE FUNCTION *** * * Thread which performs time limit updating and checking. This thread executes * an infinite loop, sleeping for several seconds, waking up to perform time * limit updating, and then going back to sleep. This thead should typically * operate at normal priority. * * In non-multithreaded versions of OpenDoors, this task is performed by * od_kernel(). * * Parameters: As dictated for any thread function. * * Return: As dictated for any thread function. */ DWORD OD_THREAD_FUNC ODKrnlTimeUpdateThread(void *pParam) { /* We keep looping until someone else terminates this thread. */ for(;;) { /* Sleep until it is time to do the next update. */ od_sleep(TIME_UPDATE_THREAD_SLEEP_TIME); /* Now, perform time update. */ ODKrnlTimeUpdate(); } return(0); } /* ---------------------------------------------------------------------------- * ODKrnlWaitForExclusiveControl() *** PRIVATE FUNCTION *** * * Claims exclusive control of the application by the OpenDoors kernel. This is * required to ensure that the client application is not busy when the * OpenDoors kernel interrupts other operations for one reason or another * (for example, to start chat mode or to force the program to exit). * * Parameters: None * * Return: void */ static void ODKrnlWaitForExclusiveControl(void) { /* If we already have exclusive control, then don't do anything. */ if(bHaveExclusiveControl) return; /* Wait until an OpenDoors API is active. */ ODSemaphoreDown(hODActiveSemaphore, OD_NO_TIMEOUT); /* Now, suspend the client thread. */ ASSERT(hClientThread != NULL); ODThreadSuspend(hClientThread); /* Record that we now have exclusive control. */ bHaveExclusiveControl = TRUE; } /* ---------------------------------------------------------------------------- * ODKrnlGiveUpExclusiveControl() *** PRIVATE FUNCTION *** * * Relinguishes exclusive control of the application by the OpenDoors kernel. * A call to this function should only take place after a previous call to * ODKrnlWaitForExclusiveControl(). * * Parameters: None * * Return: void */ static void ODKrnlGiveUpExclusiveControl(void) { /* If we don't have exclusive control, then this call doesn't do */ /* anything. */ if(!bHaveExclusiveControl) return; /* First, restart the client thread. */ ASSERT(hClientThread != NULL); ODThreadResume(hClientThread); /* Now, allow currently active OpenDoors API to return control */ /* to the client application. */ ODSemaphoreUp(hODActiveSemaphore, 1); /* Note that we no longer have exclusive control. */ bHaveExclusiveControl = FALSE; } #endif /* OD_MULTITHREADED */ /* ========================================================================= */ /* OpenDoors chat mode. */ /* ========================================================================= */ BOOL bChatted; BOOL bSysopColor; #ifdef OD_MULTITHREADED /* ---------------------------------------------------------------------------- * ODKrnlChatThread() *** PRIVATE FUNCTION *** * * Thread which implements sysop <-> remote user chat mode. * * Parameters: As dictated for any thread function. * * Return: As dictated for any thread function. */ DWORD OD_THREAD_FUNC ODKrnlChatThread(void *pParam) { BOOL bTriggeredInsideOpenDoors = bChatActivatedInternally; /* The chat thread doesn't start up chat mode until the kernel has */ /* exclusive control of the client application. */ if(bTriggeredInsideOpenDoors) { ODKrnlWaitForExclusiveControl(); } /* Now, execute the chat mode loop. */ ODKrnlChatMode(); /* If we get here, then we are responsible for relinguishing exclusive */ /* control of the application. */ if(bTriggeredInsideOpenDoors) { ODKrnlGiveUpExclusiveControl(); } /* Exit the chat thread. */ return(0); } /* ---------------------------------------------------------------------------- * ODKrnlStartChatThread() * * Starts the chat mode thread. * * Parameters: bTriggeredInternally - TRUE if chat mode has been triggered * inside OpenDoors, or FALSE if it has * been triggered by a call to od_chat(). * * Return: kODRCSuccess on success, or an error code on failure. */ tODResult ODKrnlStartChatThread(BOOL bTriggeredInternally) { tODResult Result; bChatActivatedInternally = bTriggeredInternally; Result = ODThreadCreate(&hChatThread, ODKrnlChatThread, NULL); if(Result != kODRCSuccess) { return(Result); } /* If chat mode command has been chosen, then toggle chat */ /* mode on or off. */ od_control.od_chat_active = TRUE; #ifdef ODPLAT_WIN32 /* Update the enabled and checked state of commands. */ ODFrameUpdateCmdUI(); #endif /* ODPLAT_WIN32 */ return(kODRCSuccess); } #endif /* OD_MULTITHREADED */ /* ---------------------------------------------------------------------------- * ODKrnlEndChatMode() * * Forces chat mode to exit. * * Parameters: None * * Return: void */ void ODKrnlEndChatMode(void) { #ifdef OD_MULTITHREADED /* Shutdown the chat thread. */ ODThreadTerminate(hChatThread); /* Perform post-chat cleanup operations. */ ODKrnlChatCleanup(); #else /* !OD_MULTITHREADED */ /* Turn off chat mode. */ od_control.od_chat_active = FALSE; #endif /* !OD_MULTITHREADED */ } /* ---------------------------------------------------------------------------- * od_chat() * * Allows the client application to activate the line-by-line default chat * mode provided by OpenDoors, allowing the local sysop and remote user to * communicate with one another in real time. * * Parameters: none * * Return: void */ ODAPIDEF void ODCALL od_chat(void) { /* Log function entry if running in trace mode. */ TRACE(TRACE_API, "od_chat()"); /* Set the main chat active flag in od_control. */ od_control.od_chat_active = TRUE; /* Initialize OpenDoors if it hasn't already been done. */ if(!bODInitialized) od_init(); OD_API_ENTRY(); #ifdef OD_MULTITHREADED /* In multithreaded versions of OpenDoors, od_chat() causes the chat */ /* mode thread to be started, which in turn implements chat mode. */ /* od_chat() only returns when this thread exits. */ if(ODKrnlStartChatThread(FALSE) != kODRCSuccess) { od_control.od_error = ERR_GENERALFAILURE; OD_API_EXIT(); } /* Now, wait for the chat thread to exit. */ ODThreadWaitForExit(hChatThread); /* Now, note that the chat thread no longer exists. */ hChatThread = NULL; #else /* !OD_MULTITHREADED */ /* In non-multithreaded versions, a call to od_chat() maps directly to a */ /* call to ODKrnlChatMode(), which implements chat mode. */ ODKrnlChatMode(); #endif /* !OD_MULTITHREADED */ OD_API_EXIT(); } /* ---------------------------------------------------------------------------- * ODKrnlChatMode() *** PRIVATE FUNCTION *** * * Implements the OpenDoors chat mode. * * Parameters: None * * Return: void */ static void ODKrnlChatMode(void) { BYTE chKeyPressed; char szCurrentWord[79]; BYTE btWordLength = 0; BYTE btCurrentColumn = 0; char *pchCurrent; BYTE btCount; #ifndef OD_MULTITHREADED tODTimer Timer; #endif /* !OD_MULTITHREADED */ /* Empty current word string. */ szCurrentWord[0] = '\0'; /* Save current display color attribute. */ nChatOriginalAttrib = od_control.od_cur_attrib; /* Record that sysop has entered chat mode. */ bChatted = TRUE; /* Turn off "user wants to chat" indicator, and force the status line. */ /* to be updated. */ od_control.user_wantchat = FALSE; #ifdef ODPLAT_WIN32 ODFrameUpdateWantChat(); #endif /* ODPLAT_WIN32 */ bForceStatusUpdate = TRUE; CALL_KERNEL_IF_NEEDED(); /* Note that chat mode is now active. */ od_control.od_chat_active = TRUE; /* If a pre-chat function hook has been defined, then call it. */ if(od_control.od_cbefore_chat!=NULL) { bShellChatActive = TRUE; (*od_control.od_cbefore_chat)(); bShellChatActive = FALSE; /* If chat has been deactivated, then return right away */ if(!od_control.od_chat_active) goto cleanup; } /* Display a message indicating that the sysop has entered chat mode. */ od_set_attrib(od_control.od_chat_color1); if(od_control.od_before_chat != NULL) od_disp_str(od_control.od_before_chat); /* Currently set to sysop color. */ bSysopColor = TRUE; /* If the logfile system is hooked up, then write a log entry */ /* indicating that the sysop has entered chat mode. */ if(pfLogWrite != NULL) { (*pfLogWrite)(9); } #ifndef OD_MULTITHREADED /* Start a timer that will elapse after 25 milliseconds. */ ODTimerStart(&Timer, CHAT_YIELD_PERIOD); #endif /* !OD_MULTITHREADED */ /* Loop while sysop chat mode is stilil on. */ while(od_control.od_chat_active) { /* Obtain the next key from the user. */ #ifdef OD_MULTITHREADED chKeyPressed = od_get_key(TRUE); #else /* !OD_MULTITHREADED */ chKeyPressed = od_get_key(FALSE); #endif /* !OD_MULTITHREADED */ /* If color not set correctly. */ if((od_control.od_last_input && !bSysopColor) || (!od_control.od_last_input && bSysopColor)) { /* If sysop was last person to type. */ if(od_control.od_last_input) { /* Switch to sysop text color. */ od_set_attrib(od_control.od_chat_color1); } else { /* Otherwise, switch to the user text color. */ od_set_attrib(od_control.od_chat_color2); } /* Record current color setting. */ bSysopColor = od_control.od_last_input; } /* If this is a displayable character. */ if(chKeyPressed >= 32) { /* Display the character that was typed. */ od_putch(chKeyPressed); /* If the user pressed spacebar, then this is the end of the */ /* previous word. */ if(chKeyPressed == 32) { btWordLength = 0; szCurrentWord[0] = 0; } /* Add this character to the current word, if we haven't exceeded */ /* the maximum word length. */ else if(btWordLength < 70) { szCurrentWord[btWordLength++] = chKeyPressed; szCurrentWord[btWordLength] = '\0'; } /* If we are not yet at the end of the line, then increment the */ /* current column number. */ if(btCurrentColumn < 75) { ++btCurrentColumn; } /* If we are at the end of the line. */ else { /* If the current word should be wrapped to the next line. */ if(btWordLength < 70 && btWordLength > 0) { /* Generate a string to erase the word from the current line. */ pchCurrent = (char *)szODWorkString; for(btCount = 0; btCount < btWordLength; ++btCount) { *(pchCurrent++) = 8; } for(btCount = 0; btCount < btWordLength; ++btCount) { *(pchCurrent++) = ' '; } *pchCurrent = '\0'; /* Display the string to erase the old word. */ od_disp_str(szODWorkString); /* Move to the next line. */ od_disp_str("\n\r"); /* Redisplay the word on the next line. */ od_disp_str(szCurrentWord); /* Update current column number. */ btCurrentColumn = btWordLength; } /* If we have reached the end of the line, but word wrap should */ /* not be performed. */ else { /* Move to the next line. */ od_disp_str("\n\r"); /* Update the current column number. */ btCurrentColumn = 0; } /* Reset the current word information. */ btWordLength = 0; szCurrentWord[0] = 0; } } /* If the backspace key was pressed. */ else if(chKeyPressed == 8) { /* Send backspace sequence. */ od_disp_str(szBackspaceWithDelete); /* If we are in the middle of a word, then we must remove the */ /* last character of the word. */ if(btWordLength > 0) { szCurrentWord[--btWordLength] = '\0'; } /* Update the current column number. */ if(btCurrentColumn > 0) --btCurrentColumn; } /* If the enter key was pressed. */ else if(chKeyPressed == 13) { /* Send carriage return / line feed sequence. */ od_disp_str("\n\r"); /* Reset the current word contents. */ btWordLength = 0; szCurrentWord[0] = 0; /* Update the current column number. */ btCurrentColumn = 0; } /* If the sysop pressed the escape key. */ else if(chKeyPressed == 27 && od_control.od_last_input) { /* Exit chat mode. */ goto cleanup; } #ifndef OD_MULTITHREADED /* Give up processor after 25 milliseconds elapsed. */ else if(ODTimerElapsed(&Timer)) { od_sleep(0); /* Restart the timer, so that it will elapse after another */ /* 25 milliseconds. */ ODTimerStart(&Timer, CHAT_YIELD_PERIOD); } #endif /* !OD_MULTITHREADED */ } cleanup: ODKrnlChatCleanup(); } /* ---------------------------------------------------------------------------- * ODKrnlChatCleanup() *** PRIVATE FUNCTION *** * * Performs post-chat operations, such as resetting the original display * color, etc. * * Parameters: None * * Return: void */ static void ODKrnlChatCleanup(void) { od_set_attrib(od_control.od_chat_color1); /* Indicate that chat mode is exiting. */ if(od_control.od_after_chat != NULL) { od_disp_str(od_control.od_after_chat); } /* If an after chat function has been provided, then call it. */ if(od_control.od_cafter_chat != NULL) { bShellChatActive = TRUE; (*od_control.od_cafter_chat)(); bShellChatActive = FALSE; } /* If the logfile system is hooked up, then write a line to the log */ /* indicating that chat mode has been exited. */ if(pfLogWrite != NULL) { (*pfLogWrite)(10); } /* Restore original display color attribute. */ od_set_attrib(nChatOriginalAttrib); /* Record that chat mode is no longer active. */ od_control.od_chat_active = FALSE; #ifdef ODPLAT_WIN32 /* Update the enabled and checked state of commands. */ ODFrameUpdateCmdUI(); #endif /* ODPLAT_WIN32 */ #ifdef OD_MULTITHREADED if(bChatActivatedInternally) { ODKrnlGiveUpExclusiveControl(); } #endif } #ifdef ODPLAT_NIX #ifdef USE_KERNEL_SIGNAL /* ---------------------------------------------------------------------------- * sig_run_kernel(sig) *** PRIVATE FUNCTION *** * * Runs od_kernel() on a SIGALRM * */ static void sig_run_kernel(int sig) { od_kernel(); } /* ---------------------------------------------------------------------------- * sig_run_kernel(sig) *** PRIVATE FUNCTION *** * * Runs od_kernel() on a SIGALRM * */ static void sig_get_char(int sig) { static char ch; /* Loop, obtaining any new characters from the serial port and */ /* adding them to the common local/remote input queue. */ while(ODComGetByte(hSerialPort, &ch, FALSE) == kODRCSuccess) { ODKrnlHandleReceivedChar(ch, TRUE); } } static void sig_no_carrier(int sig) { if(od_control.baud != 0 && ) { if(!(od_control.od_disable&DIS_CARRIERDETECT)) ODKrnlForceOpenDoorsShutdown(ERRORLEVEL_NOCARRIER); } } #endif #endif