diff --git a/criter/context.x b/criter/context.x index adcc74c9..202fd8a3 100644 --- a/criter/context.x +++ b/criter/context.x @@ -35,7 +35,8 @@ typedef kswap_t # Type currently defined word envSegm 0 # segment to be passed forth as environment pointer prg 0 # program to be executed pointer cmdline 0 # command line arguments of program -word execErr 0 # exit code of DOS-4B of external program +word execErr 0 # error code of DOS-4B API for external program +word childStatus 0 # successful child's full DOS-4D termination status pointer shell 0 # absolute path to shell to be executed word canexit 0 # true unless FreeCOM was invoked with /P word dfltSwap 0 # default value of swapOnExec diff --git a/docs/k-swap.txt b/docs/k-swap.txt index 034b1130..1a64c31b 100644 --- a/docs/k-swap.txt +++ b/docs/k-swap.txt @@ -32,7 +32,10 @@ Swapping is NOT supported in: - called batch files accessing any shell functions or patch the master environment. -Swapping does not preserve the history. +The matching repaired FreeCOM/KSSF pair preserves the dynamic context, +including aliases and history, across a supported interactive swap. Build +and use both programs together; saved state is not a cross-version format. +Swapping remains unsupported in the cases listed above. === How to use KSSF? diff --git a/include/context.h b/include/context.h index c3531ddd..f006a6e2 100644 --- a/include/context.h +++ b/include/context.h @@ -36,6 +36,7 @@ enum { CTXT_SWAPINFO_PRGNAME = 1 , CTXT_SWAPINFO_CMDLINE , CTXT_SWAPINFO_SHELLNAME + , CTXT_SWAPINFO_STATUS }; #define ctxtAlias ctxt diff --git a/include/misc.h b/include/misc.h index 48e9eaea..d0c106b5 100644 --- a/include/misc.h +++ b/include/misc.h @@ -150,6 +150,7 @@ void convert(unsigned long num, unsigned int billions, char * const des); void goxy(const unsigned char x, const unsigned char y); void setErrorLevel(int rc); +void setChildStatus(unsigned status); void execute(char *first, char *rest, int lh_lf); int exec(const char *, char *, const unsigned); void restoreSession(void); diff --git a/lib/exec1.c b/lib/exec1.c index 7b53f411..fb0994b0 100644 --- a/lib/exec1.c +++ b/lib/exec1.c @@ -59,31 +59,36 @@ void displayExitcode(void); /* also in include/misc.h */ #endif +/* Decode a successful child's saved AH=4Dh status without treating its AL + as an EXEC API error or consuming another child's return code. */ +void setChildStatus(unsigned status) +{ +#ifndef DISP_EXITCODE + int exitReason; +#endif + int rc = status & 0xFF; + exitReason = (status >> 8) & 0xFF; + dprintf(("[exec: exit code: %u:%u]\n", exitReason, rc)); + if(exitReason == 0x1 || exitReason == 0x2) + ctrlBreak = 1; + if(ctrlBreak && !rc) + rc = CBREAK_ERRORLEVEL; + errorlevel = rc; +#ifdef DISP_EXITCODE + displayExitcode(); +#endif +} + void setErrorLevel(int rc) { IREGS rp; dprintf(("[exec: DOS error code of exec(): %d]\n", rc)); if(!rc) { -#ifndef DISP_EXITCODE - int exitReason; /* else we use the global variable */ -#endif - rp.r_ax = 0x4d00; /* get return code */ - intrpt(0x21, &rp); - rc = rp.r_ax & 0xFF; - exitReason = (rp.r_ax >> 8) & 0xFF; - /* 0 -> normal - 1 -> ^Break / ^C - 2 -> Critical Error - 3 -> TSR - */ - dprintf(("[exec: exit code: %u:%u]\n", exitReason, rc)); - if(exitReason == 0x1) - ctrlBreak = 1; - if(exitReason == 0x2) /* Shallt change in the future */ - ctrlBreak = 1; - if(ctrlBreak && !rc) /* Make sure this condition is reflected */ - rc = CBREAK_ERRORLEVEL; + rp.r_ax = 0x4d00; + intrpt(0x21, &rp); + setChildStatus(rp.r_ax); + return; } else { #ifdef DISP_EXITCODE diff --git a/shell/command.c b/shell/command.c index 965c62dd..19495534 100644 --- a/shell/command.c +++ b/shell/command.c @@ -247,6 +247,12 @@ void execute(char *first, char *rest, int lh_lf) /* Execute the external program */ #ifdef FEATURE_KERNEL_SWAP_SHELL + /* The swap ends this process, so DOS closes redirected and piped handles + and the batch or FOR context is lost. docs/k-swap.txt lists these cases + as unsupported; execute them without swapping, also under /SWAP. */ + if(swapOnExec == TRUE + && (oldinfd != -1 || oldoutfd != -1 || bc != 0)) + swapOnExec = FALSE; if(swapOnExec == TRUE && kswapMkStruc(fullname, rest)) { /* The Criter and ^Break handlers has been installed within diff --git a/shell/kswap.c b/shell/kswap.c index a8d559e3..7ab3e2c5 100644 --- a/shell/kswap.c +++ b/shell/kswap.c @@ -72,7 +72,7 @@ static void kswapSetISR(void) void kswapRegister(kswap_p ctxt) { IREGS r; - dprintf(("[KSWAP: Registering static context at: 0x%04x]\n", (word)ctxt)); + dprintf(("[KSWAP: Registering static context at: 0x%04x]\n", FP_SEG((void far *)ctxt))); assert(ctxt); /* our own PSP gets patched in order to load the values of the Criter and ^Break handlers of the context on termination @@ -83,7 +83,7 @@ void kswapRegister(kswap_p ctxt) kswapSetISR(); r.r_ax = 0x4bfd; /* Set kswap argument structure segm */ - r.r_bx = (word)ctxt; + r.r_bx = FP_SEG((void far *)ctxt); r.r_dx = FD_MAGIC; intrpt(0x21, &r); if(r.r_flags & 1) { /* failed */ @@ -91,35 +91,88 @@ void kswapRegister(kswap_p ctxt) dprintf(("[KSWAP: Registering failed, kernel swap deactivated]\n")); return; } - /* Initialize the constant values */ - /* environment block required only, if swapping is avail */ - ctxt->envSize = mcb_length(env_glbSeg); - ctxt->envSegm = allocSysBlk(ctxt->envSize, 0x82); - if(!ctxt->envSegm) { - error_kswap_allocmem(); - swapOnExec = ERROR; - } - else - dprintf(("[KSWAP: master environment allocated at 0x%04x]\n" - , ctxt->envSegm)); + /* Allocate the saved environment at swap time: initialization and SET + may still change its size after this registration. */ + ctxt->envSize = 0; + ctxt->envSegm = 0; } void kswapDeRegister(kswap_p ctxt) { if(swapOnExec != ERROR) { /* context belongs to this FreeCOM */ - dprintf(("[KSWAP: DeRegistering static context at: 0x%04x]\n", (word)ctxt)); + dprintf(("[KSWAP: DeRegistering static context at: 0x%04x]\n", FP_SEG((void far *)ctxt))); assert(ctxt); ctxt->shell = 0; /* causes the kernel swap support to exit */ } } +/* Context variables are strings. Encode the counters in hex, after reserving + the entry, so that the saved counters include the entry's own allocation. + This avoids reconstructing history/dirstack numbering from their contents. */ +#define KSWAP_INFO_BYTES (sizeof(ctxt_info_t) * (CTXT_TAG_ALIAS - CTXT_FIRST_TAG + 1)) +#define KSWAP_STATUS_BYTES (KSWAP_INFO_BYTES + 1) + +static int kswapSaveStatus(void) +{ + static const char hex[] = "0123456789abcdef"; + char reserve[2 * KSWAP_STATUS_BYTES + 1]; + unsigned i, value; + char far *p; + + memset(reserve, '0', sizeof(reserve) - 1); + reserve[sizeof(reserve) - 1] = 0; + if(ctxtSet(CTXT_TAG_SWAPINFO, CTXT_SWAPINFO_STATUS, reserve)) + return FALSE; + p = ctxtAddress(CTXT_TAG_SWAPINFO, CTXT_SWAPINFO_STATUS); + if(!p) + return FALSE; + for(i = 0; i < KSWAP_STATUS_BYTES; ++i) { + value = i < KSWAP_INFO_BYTES ? ((unsigned char *)ctxt_info)[i] + : (forceLow != 0); + *p++ = hex[value >> 4]; + *p++ = hex[value & 15]; + } + return TRUE; +} + +static int kswapRestoreStatus(void) +{ + unsigned char saved[KSWAP_STATUS_BYTES]; + unsigned i, value; + char c; + char far *p = ctxtAddress(CTXT_TAG_SWAPINFO, CTXT_SWAPINFO_STATUS); + + if(!p) + return FALSE; + for(i = 0; i < 2 * KSWAP_STATUS_BYTES; ++i) { + c = *p++; + if(c >= '0' && c <= '9') + value = c - '0'; + else if(c >= 'a' && c <= 'f') + value = c - 'a' + 10; + else + return FALSE; + if(!(i & 1)) + saved[i / 2] = value << 4; + else + saved[i / 2] |= value; + } + if(*p || saved[KSWAP_INFO_BYTES] > 1) + return FALSE; + memcpy(ctxt_info, saved, KSWAP_INFO_BYTES); + forceLow = saved[KSWAP_INFO_BYTES]; + return TRUE; +} + /* Update the kswap argument block Return: 0 on error <-> no swapping possible else: segment of structure */ unsigned kswapMkStruc(const char * const prg, const char * const cmdline) { word shellname; - word segm; + word segm, envSize; + int shellInContext; + struct MCB _seg *mcb; char *q, *h; assert(prg); @@ -134,20 +187,38 @@ unsigned kswapMkStruc(const char * const prg, const char * const cmdline) swap after all */ /* preserve the environment */ - assert(kswapContext->envSegm); - assert(kswapContext->envSize); + envSize = env_glbSeg ? mcb_length(env_glbSeg) : 0; + if(!envSize) + return FALSE; segm = kswapContext->envSegm; + if(!segm || mcb_length(segm) < envSize) { + word replacement = allocSysBlk(envSize, forceLow ? 0x02 : 0x82); + if(!replacement) { + static const char message[] = + "KSWAP: Not enough memory to save the environment; not swapping.\r\n"; + /* Loading STRINGS can itself run out of memory and prompt for + another resource file. Also avoid CRT stdio: release FreeCOM + uses handle-valued FILE pointers, not CRT FILE structures. */ + dos_write(fileno(stderr), message, sizeof(message) - 1); + return FALSE; /* Execute normally; never overrun the backup. */ + } + if(segm) + freeSysBlk(segm); + segm = kswapContext->envSegm = replacement; + } + kswapContext->envSize = envSize; dprintf(("[KSWAP: Updating master environment at 0x%04x]\n", segm)); assert(isMCB(SEG2MCB(segm))); assert(isMCB(SEG2MCB(env_glbSeg))); assert(mcb_length(env_glbSeg) <= mcb_length(segm)); - _fmemcpy(MK_FP(segm, 0), MK_FP(env_glbSeg, 0), mcb_length(env_glbSeg)); + _fmemcpy(MK_FP(segm, 0), MK_FP(env_glbSeg, 0), envSize); /* Update the shell name as maybe %COMSPEC% was changed */ /* COMSPEC is the central and traditionally the only place of the name of the shell */ - if(isSwapFile - || (shellname = env_findVar(segm, "COMSPEC") + 8) == (unsigned)-1 + 8) { + shellInContext = isSwapFile + || (shellname = env_findVar(segm, "COMSPEC") + 8) == (unsigned)-1 + 8; + if(shellInContext) { char *p = comResFile(); ctxtSet(CTXT_TAG_SWAPINFO, CTXT_SWAPINFO_SHELLNAME, p); free(p); @@ -183,10 +254,24 @@ unsigned kswapMkStruc(const char * const prg, const char * const cmdline) ctxtSet(CTXT_TAG_SWAPINFO, CTXT_SWAPINFO_CMDLINE, "\1 \r"); ctxtSet(CTXT_TAG_SWAPINFO, CTXT_SWAPINFO_PRGNAME, prg); - return (kswapContext->cmdline /* fetch first in case of failure */ - = ctxtAddress(CTXT_TAG_SWAPINFO, CTXT_SWAPINFO_CMDLINE)) != 0 - && (kswapContext->prg - = ctxtAddress(CTXT_TAG_SWAPINFO, CTXT_SWAPINFO_PRGNAME)) != 0; + if(!kswapSaveStatus()) + return FALSE; + /* Adding entries can relocate ctxt; take all pointers only afterwards. */ + if(shellInContext) + kswapContext->shell = ctxtAddress(CTXT_TAG_SWAPINFO, CTXT_SWAPINFO_SHELLNAME); + kswapContext->cmdline = ctxtAddress(CTXT_TAG_SWAPINFO, CTXT_SWAPINFO_CMDLINE); + if(!kswapContext->shell || !kswapContext->cmdline) + return FALSE; + kswapContext->prg = ctxtAddress(CTXT_TAG_SWAPINFO, CTXT_SWAPINFO_PRGNAME); + if(!kswapContext->prg) + return FALSE; + + /* The child and the reloaded shell need these strings and counters. + DOS must not release the context when this shell exits to KSSF. */ + mcb = MK_SEG_PTR(struct MCB, SEG2MCB(ctxt)); + mcb->mcb_ownerPSP = 8; + kswapContext->dyn_ctxt = ctxt; + return TRUE; } @@ -201,15 +286,25 @@ int kswapLoadStruc(void) /* kswapContext->prg had been disabled in kswapInit() */ assert(!kswapContext->prg); - setErrorLevel(kswapContext->execErr); - canexit = kswapContext->canexit; defaultToSwap = kswapContext->dfltSwap; #ifndef NDEBUG fddebug = kswapContext->debug; #endif grabComFilename(1, kswapContext->shell); - if((ctxt = kswapContext->dyn_ctxt) == 0) { + ctxt = kswapContext->dyn_ctxt; + kswapContext->dyn_ctxt = 0; + if(ctxt) { + struct MCB _seg *mcb = MK_SEG_PTR(struct MCB, SEG2MCB(ctxt)); + /* While the shell runs, ordinary context relocation and DOS exit + own this allocation again. KSSF owns it only across the swap. */ + mcb->mcb_ownerPSP = _psp; + if(!kswapRestoreStatus()) { + freeBlk(ctxt); + ctxt = 0; + } + } + if(!ctxt) { error_no_context_after_swap(); ctxtCreate(); } @@ -223,6 +318,13 @@ int kswapLoadStruc(void) /* if the size does not change this function performs no actions */ env_setsize(0, kswapContext->envSize); + /* Message resources and context must be ready before an EXEC error is + diagnosed. A successful child's nonzero AL is not a DOS-4B error. */ + if(kswapContext->execErr) + setErrorLevel(kswapContext->execErr); + else + setChildStatus(kswapContext->childStatus); + return TRUE; } diff --git a/tools/kssf.asm b/tools/kssf.asm index aec40efa..f93e7607 100644 --- a/tools/kssf.asm +++ b/tools/kssf.asm @@ -28,6 +28,7 @@ cmd_buf: SEGMENT .text +CPU 8086 ; expand out-of-range conditionals without 386-only opcodes ORG 100h ; this is an executable program, but as small as possible start: @@ -92,13 +93,22 @@ mainloop: mov ax, 4b00h int 21h ; execute external program jc execPrg1 - xor ax, ax ; no error + mov ah, 4dh ; keep status separate from a failed DOS-4B API + int 21h + mov sp, tsrend + STACK_SIZE + mov dx, cs + mov ds, dx + mov es, [context] + mov [ES:?childStatus], ax ; AL exit code, AH termination reason + mov WORD [ES:?execErr], 0 + jmp execShell execPrg1: mov sp, tsrend + STACK_SIZE ; standard stack position mov dx, cs mov ds, dx mov es, [context] mov [ES:?execErr], ax ; error code of DOS-4B API + mov WORD [ES:?childStatus], 0 execShell: mov ax, shellCmdLine mov [eb_cmdline], ax @@ -136,6 +146,13 @@ dbg_1: mov es, ax ; MCB of context mov WORD [ES:1], ds ; owner of context := kssf mov ax, [ES:10h+?envSegm] ; environment segm + mov bx, [ES:10h+?dyn_ctxt] ; pending context if shell reload failed + or bx, bx + jz exit_noDynamic + dec bx + mov es, bx + mov WORD [ES:1], ds ; release it on KSSF termination, too +exit_noDynamic: or ax, ax jz exit_noCtxt dec ax