Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion criter/context.x
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion docs/k-swap.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand Down
1 change: 1 addition & 0 deletions include/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ enum {
CTXT_SWAPINFO_PRGNAME = 1
, CTXT_SWAPINFO_CMDLINE
, CTXT_SWAPINFO_SHELLNAME
, CTXT_SWAPINFO_STATUS
};

#define ctxtAlias ctxt
Expand Down
1 change: 1 addition & 0 deletions include/misc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
43 changes: 24 additions & 19 deletions lib/exec1.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions shell/command.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
156 changes: 129 additions & 27 deletions shell/kswap.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -83,43 +83,96 @@ 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 */
swapOnExec = ERROR; /* cannot register -> cannot use */
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);
Expand All @@ -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);
Expand Down Expand Up @@ -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;

}

Expand All @@ -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();
}
Expand All @@ -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;
}

Expand Down
19 changes: 18 additions & 1 deletion tools/kssf.asm
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading