Subversion Repositories SvarDOS

Rev

Rev 1713 | Rev 1715 | Go to most recent revision | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 1713 Rev 1714
1
/* This file is part of the SvarCOM project and is published under the terms
1
/* This file is part of the SvarCOM project and is published under the terms
2
 * of the MIT license.
2
 * of the MIT license.
3
 *
3
 *
4
 * Copyright (C) 2021-2023 Mateusz Viste
4
 * Copyright (C) 2021-2024 Mateusz Viste
5
 *
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a
6
 * Permission is hereby granted, free of charge, to any person obtaining a
7
 * copy of this software and associated documentation files (the "Software"),
7
 * copy of this software and associated documentation files (the "Software"),
8
 * to deal in the Software without restriction, including without limitation
8
 * to deal in the Software without restriction, including without limitation
9
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10
 * and/or sell copies of the Software, and to permit persons to whom the
10
 * and/or sell copies of the Software, and to permit persons to whom the
11
 * Software is furnished to do so, subject to the following conditions:
11
 * Software is furnished to do so, subject to the following conditions:
12
 *
12
 *
13
 * The above copyright notice and this permission notice shall be included in
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
14
 * all copies or substantial portions of the Software.
15
 *
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22
 * DEALINGS IN THE SOFTWARE.
22
 * DEALINGS IN THE SOFTWARE.
23
 */
23
 */
24
 
24
 
25
#include <i86.h>
25
#include <i86.h>
26
#include <dos.h>
26
#include <dos.h>
27
#include <stdio.h>
27
#include <stdio.h>
28
#include <stdlib.h>
28
#include <stdlib.h>
29
#include <string.h>
29
#include <string.h>
30
 
30
 
31
#include "svarlang.lib/svarlang.h"
31
#include "svarlang.lib/svarlang.h"
32
 
32
 
33
#include "cmd.h"
33
#include "cmd.h"
34
#include "env.h"
34
#include "env.h"
35
#include "helpers.h"
35
#include "helpers.h"
36
#include "redir.h"
36
#include "redir.h"
37
#include "rmodinit.h"
37
#include "rmodinit.h"
38
#include "sayonara.h"
38
#include "sayonara.h"
39
 
39
 
40
#include "rmodcore.h" /* rmod binary inside a BUFFER array */
40
#include "rmodcore.h" /* rmod binary inside a BUFFER array */
41
 
41
 
42
/* this version byte is used to tag RMOD so I can easily make sure that
42
/* this version byte is used to tag RMOD so I can easily make sure that
43
 * the RMOD struct I find in memory is one that I know. Should the version
43
 * the RMOD struct I find in memory is one that I know. Should the version
44
 * mismatch, then it would likely mean that SvarCOM has been upgraded and
44
 * mismatch, then it would likely mean that SvarCOM has been upgraded and
45
 * RMOD should not be accessed as its structure might no longer be in sync
45
 * RMOD should not be accessed as its structure might no longer be in sync
46
 * with what I think it is.
46
 * with what I think it is.
47
 *          *** INCREMENT THIS AT EACH NEW SVARCOM RELEASE! ***          */
47
 *          *** INCREMENT THIS AT EACH NEW SVARCOM RELEASE! ***          */
48
#define BYTE_VERSION 4
48
#define BYTE_VERSION 4
49
 
49
 
50
 
50
 
51
struct config {
51
struct config {
52
  unsigned char flags; /* command.com flags, as defined in rmodinit.h */
52
  unsigned char flags; /* command.com flags, as defined in rmodinit.h */
53
  char *execcmd;
53
  char *execcmd;
54
  unsigned short envsiz;
54
  unsigned short envsiz;
55
};
55
};
56
 
56
 
57
/* max length of the cmdline storage (bytes) - includes also max length of
57
/* max length of the cmdline storage (bytes) - includes also max length of
58
 * line loaded from a BAT file (no more than 255 bytes!) */
58
 * line loaded from a BAT file (no more than 255 bytes!) */
59
#define CMDLINE_MAXLEN 255
59
#define CMDLINE_MAXLEN 255
60
 
60
 
61
 
61
 
62
/* sets guard values at a few places in memory for later detection of
62
/* sets guard values at a few places in memory for later detection of
63
 * overflows via memguard_check() */
63
 * overflows via memguard_check() */
64
static void memguard_set(char *cmdlinebuf) {
64
static void memguard_set(char *cmdlinebuf) {
65
  BUFFER[sizeof(BUFFER) - 1] = 0xC7;
65
  BUFFER[sizeof(BUFFER) - 1] = 0xC7;
66
  cmdlinebuf[CMDLINE_MAXLEN] = 0xC7;
66
  cmdlinebuf[CMDLINE_MAXLEN] = 0xC7;
67
}
67
}
68
 
68
 
69
 
69
 
70
/* checks for valguards at specific memory locations, returns 0 on success */
70
/* checks for valguards at specific memory locations, returns 0 on success */
71
static int memguard_check(unsigned short rmodseg, char *cmdlinebuf) {
71
static int memguard_check(unsigned short rmodseg, char *cmdlinebuf) {
72
  /* check RMOD signature (would be overwritten in case of stack overflow */
72
  /* check RMOD signature (would be overwritten in case of stack overflow */
73
  static char msg[] = "!! MEMORY CORRUPTION ## DETECTED !!";
73
  static char msg[] = "!! MEMORY CORRUPTION ## DETECTED !!";
74
  unsigned short far *rmodsig = MK_FP(rmodseg, 0x100 + 6);
74
  unsigned short far *rmodsig = MK_FP(rmodseg, 0x100 + 6);
75
  unsigned char far *rmod = MK_FP(rmodseg, 0);
75
  unsigned char far *rmod = MK_FP(rmodseg, 0);
76
 
76
 
77
  if (*rmodsig != 0x2019) {
77
  if (*rmodsig != 0x2019) {
78
    msg[22] = '1';
78
    msg[22] = '1';
79
    goto FAIL;
79
    goto FAIL;
80
  }
80
  }
81
 
81
 
82
  /* check last BUFFER byte */
82
  /* check last BUFFER byte */
83
  if (BUFFER[sizeof(BUFFER) - 1] != 0xC7) {
83
  if (BUFFER[sizeof(BUFFER) - 1] != 0xC7) {
84
    msg[22] = '2';
84
    msg[22] = '2';
85
    goto FAIL;
85
    goto FAIL;
86
  }
86
  }
87
 
87
 
88
  /* check last cmdlinebuf byte */
88
  /* check last cmdlinebuf byte */
89
  if (cmdlinebuf[CMDLINE_MAXLEN] != 0xC7) {
89
  if (cmdlinebuf[CMDLINE_MAXLEN] != 0xC7) {
90
    msg[22] = '3';
90
    msg[22] = '3';
91
    goto FAIL;
91
    goto FAIL;
92
  }
92
  }
93
 
93
 
94
  /* check rmod exec buf */
94
  /* check rmod exec buf */
95
  if (rmod[RMOD_OFFSET_EXECPROG + 127] != 0) {
95
  if (rmod[RMOD_OFFSET_EXECPROG + 127] != 0) {
96
    msg[22] = '4';
96
    msg[22] = '4';
97
    goto FAIL;
97
    goto FAIL;
98
  }
98
  }
99
 
99
 
100
  /* check rmod exec stdin buf */
100
  /* check rmod exec stdin buf */
101
  if (rmod[RMOD_OFFSET_STDINFILE + 127] != 0) {
101
  if (rmod[RMOD_OFFSET_STDINFILE + 127] != 0) {
102
    msg[22] = '5';
102
    msg[22] = '5';
103
    goto FAIL;
103
    goto FAIL;
104
  }
104
  }
105
 
105
 
106
  /* check rmod exec stdout buf */
106
  /* check rmod exec stdout buf */
107
  if (rmod[RMOD_OFFSET_STDOUTFILE + 127] != 0) {
107
  if (rmod[RMOD_OFFSET_STDOUTFILE + 127] != 0) {
108
    msg[22] = '6';
108
    msg[22] = '6';
109
    goto FAIL;
109
    goto FAIL;
110
  }
110
  }
111
 
111
 
112
  /* else all good */
112
  /* else all good */
113
  return(0);
113
  return(0);
114
 
114
 
115
  /* error handling */
115
  /* error handling */
116
  FAIL:
116
  FAIL:
117
  outputnl(msg);
117
  outputnl(msg);
118
  return(1);
118
  return(1);
119
}
119
}
120
 
120
 
121
 
121
 
122
/* parses command line the hard way (directly from PSP) */
122
/* parses command line the hard way (directly from PSP) */
123
static void parse_argv(struct config *cfg) {
123
static void parse_argv(struct config *cfg) {
124
  const unsigned char *cmdlinelen = (void *)0x80;
-
 
125
  char *cmdline = (void *)0x81;
124
  char *cmdline = (void *)0x81;
126
 
125
 
127
  memset(cfg, 0, sizeof(*cfg));
126
  memset(cfg, 0, sizeof(*cfg));
128
 
127
 
129
  /* set a NULL terminator on cmdline */
-
 
130
  cmdline[*cmdlinelen] = 0;
-
 
131
 
-
 
132
  while (*cmdline != 0) {
128
  while (*cmdline != 0x0d) {
133
 
129
 
134
    /* skip over any leading spaces */
130
    /* skip over any leading spaces */
135
    if (*cmdline == ' ') {
131
    if (*cmdline == ' ') {
136
      cmdline++;
132
      cmdline++;
137
      continue;
133
      continue;
138
    }
134
    }
139
 
135
 
140
    if (*cmdline != '/') {
136
    if (*cmdline != '/') {
141
      nls_output(0,6); /* "Invalid parameter" */
137
      nls_output(0,6); /* "Invalid parameter" */
142
      output(": ");
138
      output(": ");
143
      outputnl(cmdline);
139
      outputnl(cmdline);
144
      goto SKIP_TO_NEXT_ARG;
140
      goto SKIP_TO_NEXT_ARG;
145
    }
141
    }
146
 
142
 
147
    /* got a slash */
143
    /* got a slash */
148
    cmdline++;  /* skip the slash */
144
    cmdline++;  /* skip the slash */
149
    switch (*cmdline) {
145
    switch (*cmdline) {
150
      case 'c': /* /C = execute command and quit */
146
      case 'c': /* /C = execute command and quit */
151
      case 'C':
147
      case 'C':
152
        cfg->flags |= FLAG_EXEC_AND_QUIT;
148
        cfg->flags |= FLAG_EXEC_AND_QUIT;
153
        /* FALLTHRU */
149
        /* FALLTHRU */
154
      case 'k': /* /K = execute command and keep running */
150
      case 'k': /* /K = execute command and keep running */
155
      case 'K':
151
      case 'K':
156
        cmdline++;
152
        cmdline++;
157
        cfg->execcmd = cmdline;
153
        cfg->execcmd = cmdline;
158
        return; /* further arguments are for the executed program, not for me */
154
        goto DONE; /* further arguments are for the executed program, not for me */
159
 
155
 
160
      case 'y': /* /Y = execute batch file step-by-step (with /P, /K or /C) */
156
      case 'y': /* /Y = execute batch file step-by-step (with /P, /K or /C) */
161
      case 'Y':
157
      case 'Y':
162
        cfg->flags |= FLAG_STEPBYSTEP;
158
        cfg->flags |= FLAG_STEPBYSTEP;
163
        break;
159
        break;
164
 
160
 
165
      case 'd': /* /D = skip autoexec.bat processing */
161
      case 'd': /* /D = skip autoexec.bat processing */
166
      case 'D':
162
      case 'D':
167
        cfg->flags |= FLAG_SKIP_AUTOEXEC;
163
        cfg->flags |= FLAG_SKIP_AUTOEXEC;
168
        break;
164
        break;
169
 
165
 
170
      case 'e': /* preset the initial size of the environment block */
166
      case 'e': /* preset the initial size of the environment block */
171
      case 'E':
167
      case 'E':
172
        cmdline++;
168
        cmdline++;
173
        if (*cmdline == ':') cmdline++; /* could be /E:size */
169
        if (*cmdline == ':') cmdline++; /* could be /E:size */
174
        atous(&(cfg->envsiz), cmdline);
170
        atous(&(cfg->envsiz), cmdline);
175
        if (cfg->envsiz < 64) cfg->envsiz = 0;
171
        if (cfg->envsiz < 64) cfg->envsiz = 0;
176
        break;
172
        break;
177
 
173
 
178
      case 'p': /* permanent shell (can't exit + run autoexec.bat) */
174
      case 'p': /* permanent shell (can't exit + run autoexec.bat) */
179
      case 'P':
175
      case 'P':
180
        cfg->flags |= FLAG_PERMANENT;
176
        cfg->flags |= FLAG_PERMANENT;
181
        break;
177
        break;
182
 
178
 
183
      case '?':
179
      case '?':
184
        nls_outputnl(1,0); /* "Starts the SvarCOM command interpreter" */
180
        nls_outputnl(1,0); /* "Starts the SvarCOM command interpreter" */
185
        outputnl("");
181
        outputnl("");
186
        nls_outputnl(1,1); /* "COMMAND /E:nnn [/[C|K] [/P] [/D] command]" */
182
        nls_outputnl(1,1); /* "COMMAND /E:nnn [/[C|K] [/P] [/D] command]" */
187
        outputnl("");
183
        outputnl("");
188
        nls_outputnl(1,2); /* "/D      Skip AUTOEXEC.BAT processing (makes sense only with /P)" */
184
        nls_outputnl(1,2); /* "/D      Skip AUTOEXEC.BAT processing (makes sense only with /P)" */
189
        nls_outputnl(1,3); /* "/E:nnn  Sets the environment size to nnn bytes" */
185
        nls_outputnl(1,3); /* "/E:nnn  Sets the environment size to nnn bytes" */
190
        nls_outputnl(1,4); /* "/P      Makes the new command interpreter permanent and run AUTOEXEC.BAT" */
186
        nls_outputnl(1,4); /* "/P      Makes the new command interpreter permanent and run AUTOEXEC.BAT" */
191
        nls_outputnl(1,5); /* "/C      Executes the specified command and returns" */
187
        nls_outputnl(1,5); /* "/C      Executes the specified command and returns" */
192
        nls_outputnl(1,6); /* "/K      Executes the specified command and continues running" */
188
        nls_outputnl(1,6); /* "/K      Executes the specified command and continues running" */
193
        nls_outputnl(1,7); /* "/Y      Executes the batch program step by step" */
189
        nls_outputnl(1,7); /* "/Y      Executes the batch program step by step" */
194
        exit(1);
190
        exit(1);
195
        break;
191
        break;
196
 
192
 
197
      default:
193
      default:
198
        nls_output(0,2); /* invalid switch */
194
        nls_output(0,2); /* invalid switch */
199
        output(": /");
195
        output(": /");
200
        outputnl(cmdline);
196
        outputnl(cmdline);
201
        break;
197
        break;
202
    }
198
    }
203
 
199
 
204
    /* move to next argument or quit processing if end of cmdline */
200
    /* move to next argument or quit processing if end of cmdline */
205
    SKIP_TO_NEXT_ARG:
201
    SKIP_TO_NEXT_ARG:
206
    while ((*cmdline != 0) && (*cmdline != ' ') && (*cmdline != '/')) cmdline++;
202
    while ((*cmdline != 0x0d) && (*cmdline != ' ') && (*cmdline != '/')) cmdline++;
207
  }
203
  }
-
 
204
 
-
 
205
  DONE:
-
 
206
 
-
 
207
  /* set a nul terminator on cmdline (expected later in the code) */
-
 
208
  while (*cmdline != 0x0d) cmdline++;
-
 
209
  *cmdline = 0;
208
}
210
}
209
 
211
 
210
 
212
 
211
/* builds the prompt string and displays it. buff is filled with a zero-terminated copy of the prompt. */
213
/* builds the prompt string and displays it. buff is filled with a zero-terminated copy of the prompt. */
212
static void build_and_display_prompt(char *buff, unsigned short envseg) {
214
static void build_and_display_prompt(char *buff, unsigned short envseg) {
213
  char *s = buff;
215
  char *s = buff;
214
  /* locate the prompt variable or use the default pattern */
216
  /* locate the prompt variable or use the default pattern */
215
  const char far *fmt = env_lookup_val(envseg, "PROMPT");
217
  const char far *fmt = env_lookup_val(envseg, "PROMPT");
216
  if ((fmt == NULL) || (*fmt == 0)) fmt = "$p$g"; /* fallback to default if empty */
218
  if ((fmt == NULL) || (*fmt == 0)) fmt = "$p$g"; /* fallback to default if empty */
217
  /* build the prompt string based on pattern */
219
  /* build the prompt string based on pattern */
218
  for (; *fmt != 0; fmt++) {
220
  for (; *fmt != 0; fmt++) {
219
    if (*fmt != '$') {
221
    if (*fmt != '$') {
220
      *s = *fmt;
222
      *s = *fmt;
221
      s++;
223
      s++;
222
      continue;
224
      continue;
223
    }
225
    }
224
    /* escape code ($P, etc) */
226
    /* escape code ($P, etc) */
225
    fmt++;
227
    fmt++;
226
    switch (*fmt) {
228
    switch (*fmt) {
227
      case 'Q':  /* $Q = = (equal sign) */
229
      case 'Q':  /* $Q = = (equal sign) */
228
      case 'q':
230
      case 'q':
229
        *s = '=';
231
        *s = '=';
230
        s++;
232
        s++;
231
        break;
233
        break;
232
      case '$':  /* $$ = $ (dollar sign) */
234
      case '$':  /* $$ = $ (dollar sign) */
233
        *s = '$';
235
        *s = '$';
234
        s++;
236
        s++;
235
        break;
237
        break;
236
      case 'T':  /* $t = current time */
238
      case 'T':  /* $t = current time */
237
      case 't':
239
      case 't':
238
        s += sprintf(s, "00:00"); /* TODO */
240
        s += sprintf(s, "00:00"); /* TODO */
239
        break;
241
        break;
240
      case 'D':  /* $D = current date */
242
      case 'D':  /* $D = current date */
241
      case 'd':
243
      case 'd':
242
        s += sprintf(s, "1985-07-29"); /* TODO */
244
        s += sprintf(s, "1985-07-29"); /* TODO */
243
        break;
245
        break;
244
      case 'P':  /* $P = current drive and path */
246
      case 'P':  /* $P = current drive and path */
245
      case 'p':
247
      case 'p':
246
        _asm {
248
        _asm {
247
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
249
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
248
          int 0x21
250
          int 0x21
249
          mov bx, s
251
          mov bx, s
250
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
252
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
251
        }
253
        }
252
        *s += 'A';
254
        *s += 'A';
253
        s++;
255
        s++;
254
        *s = ':';
256
        *s = ':';
255
        s++;
257
        s++;
256
        *s = '\\';
258
        *s = '\\';
257
        s++;
259
        s++;
258
        _asm {
260
        _asm {
259
          mov ah, 0x47    /* DOS 2+ - CWD - GET CURRENT DIRECTORY */
261
          mov ah, 0x47    /* DOS 2+ - CWD - GET CURRENT DIRECTORY */
260
          xor dl,dl       /* DL = drive number (00h = default, 01h = A:, etc) */
262
          xor dl,dl       /* DL = drive number (00h = default, 01h = A:, etc) */
261
          mov si, s       /* DS:SI -> 64-byte buffer for ASCIZ pathname */
263
          mov si, s       /* DS:SI -> 64-byte buffer for ASCIZ pathname */
262
          int 0x21
264
          int 0x21
263
          jc DONE         /* leave path empty on error */
265
          jc DONE         /* leave path empty on error */
264
          /* move s ptr forward to end (0-termintor) of pathname */
266
          /* move s ptr forward to end (0-termintor) of pathname */
265
          NEXTBYTE:
267
          NEXTBYTE:
266
          mov si, s
268
          mov si, s
267
          cmp byte ptr [si], 0
269
          cmp byte ptr [si], 0
268
          je DONE
270
          je DONE
269
          inc s
271
          inc s
270
          jmp NEXTBYTE
272
          jmp NEXTBYTE
271
          DONE:
273
          DONE:
272
        }
274
        }
273
        break;
275
        break;
274
      case 'V':  /* $V = DOS version number */
276
      case 'V':  /* $V = DOS version number */
275
      case 'v':
277
      case 'v':
276
        s += sprintf(s, "VER"); /* TODO */
278
        s += sprintf(s, "VER"); /* TODO */
277
        break;
279
        break;
278
      case 'N':  /* $N = current drive */
280
      case 'N':  /* $N = current drive */
279
      case 'n':
281
      case 'n':
280
        _asm {
282
        _asm {
281
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
283
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
282
          int 0x21
284
          int 0x21
283
          mov bx, s
285
          mov bx, s
284
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
286
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
285
        }
287
        }
286
        *s += 'A';
288
        *s += 'A';
287
        s++;
289
        s++;
288
        break;
290
        break;
289
      case 'G':  /* $G = > (greater-than sign) */
291
      case 'G':  /* $G = > (greater-than sign) */
290
      case 'g':
292
      case 'g':
291
        *s = '>';
293
        *s = '>';
292
        s++;
294
        s++;
293
        break;
295
        break;
294
      case 'L':  /* $L = < (less-than sign) */
296
      case 'L':  /* $L = < (less-than sign) */
295
      case 'l':
297
      case 'l':
296
        *s = '<';
298
        *s = '<';
297
        s++;
299
        s++;
298
        break;
300
        break;
299
      case 'B':  /* $B = | (pipe) */
301
      case 'B':  /* $B = | (pipe) */
300
      case 'b':
302
      case 'b':
301
        *s = '|';
303
        *s = '|';
302
        s++;
304
        s++;
303
        break;
305
        break;
304
      case 'H':  /* $H = backspace (erases previous character) */
306
      case 'H':  /* $H = backspace (erases previous character) */
305
      case 'h':
307
      case 'h':
306
        *s = '\b';
308
        *s = '\b';
307
        s++;
309
        s++;
308
        break;
310
        break;
309
      case 'E':  /* $E = Escape code (ASCII 27) */
311
      case 'E':  /* $E = Escape code (ASCII 27) */
310
      case 'e':
312
      case 'e':
311
        *s = 27;
313
        *s = 27;
312
        s++;
314
        s++;
313
        break;
315
        break;
314
      case '_':  /* $_ = CR+LF */
316
      case '_':  /* $_ = CR+LF */
315
        *s = '\r';
317
        *s = '\r';
316
        s++;
318
        s++;
317
        *s = '\n';
319
        *s = '\n';
318
        s++;
320
        s++;
319
        break;
321
        break;
320
    }
322
    }
321
  }
323
  }
322
  *s = 0;
324
  *s = 0;
323
  output(buff);
325
  output(buff);
324
}
326
}
325
 
327
 
326
 
328
 
327
static void dos_fname2fcb(char far *fcb, const char *cmd) {
329
static void dos_fname2fcb(char far *fcb, const char *cmd) {
328
  unsigned short fcb_seg, fcb_off;
330
  unsigned short fcb_seg, fcb_off;
329
  fcb_seg = FP_SEG(fcb);
331
  fcb_seg = FP_SEG(fcb);
330
  fcb_off = FP_OFF(fcb);
332
  fcb_off = FP_OFF(fcb);
331
  _asm {
333
  _asm {
332
    push ax
334
    push ax
333
    push bx
335
    push bx
334
    push cx
336
    push cx
335
    push dx
337
    push dx
336
    push es
338
    push es
337
    push si
339
    push si
338
 
340
 
339
    mov ax, 0x2900   /* DOS 1+ - parse filename into FCB (DS:SI=fname, ES:DI=FCB) */
341
    mov ax, 0x2900   /* DOS 1+ - parse filename into FCB (DS:SI=fname, ES:DI=FCB) */
340
    mov si, cmd
342
    mov si, cmd
341
    mov es, fcb_seg
343
    mov es, fcb_seg
342
    mov di, fcb_off
344
    mov di, fcb_off
343
    int 0x21
345
    int 0x21
344
 
346
 
345
    pop si
347
    pop si
346
    pop es
348
    pop es
347
    pop dx
349
    pop dx
348
    pop cx
350
    pop cx
349
    pop bx
351
    pop bx
350
    pop ax
352
    pop ax
351
  }
353
  }
352
}
354
}
353
 
355
 
354
 
356
 
355
/* parses cmdtail and fills fcb1 and fcb2 with first and second arguments,
357
/* parses cmdtail and fills fcb1 and fcb2 with first and second arguments,
356
 * respectively. an FCB is 12 bytes long:
358
 * respectively. an FCB is 12 bytes long:
357
 * drive (0=default, 1=A, 2=B, etc)
359
 * drive (0=default, 1=A, 2=B, etc)
358
 * fname (8 chars, blank-padded)
360
 * fname (8 chars, blank-padded)
359
 * fext (3 chars, blank-padded) */
361
 * fext (3 chars, blank-padded) */
360
static void cmdtail_to_fcb(char far *fcb1, char far *fcb2, const char *cmdtail) {
362
static void cmdtail_to_fcb(char far *fcb1, char far *fcb2, const char *cmdtail) {
361
 
363
 
362
  /* skip any leading spaces */
364
  /* skip any leading spaces */
363
  while (*cmdtail == ' ') cmdtail++;
365
  while (*cmdtail == ' ') cmdtail++;
364
 
366
 
365
  /* convert first arg */
367
  /* convert first arg */
366
  dos_fname2fcb(fcb1, cmdtail);
368
  dos_fname2fcb(fcb1, cmdtail);
367
 
369
 
368
  /* skip to next arg */
370
  /* skip to next arg */
369
  while ((*cmdtail != ' ') && (*cmdtail != 0)) cmdtail++;
371
  while ((*cmdtail != ' ') && (*cmdtail != 0)) cmdtail++;
370
  while (*cmdtail == ' ') cmdtail++;
372
  while (*cmdtail == ' ') cmdtail++;
371
 
373
 
372
  /* convert second arg */
374
  /* convert second arg */
373
  dos_fname2fcb(fcb2, cmdtail);
375
  dos_fname2fcb(fcb2, cmdtail);
374
}
376
}
375
 
377
 
376
 
378
 
377
/* a few internal flags */
379
/* a few internal flags */
378
#define DELETE_STDIN_FILE 1
380
#define DELETE_STDIN_FILE 1
379
#define CALL_FLAG         2
381
#define CALL_FLAG         2
380
 
382
 
381
static void run_as_external(char *buff, const char *cmdline, unsigned short envseg, struct rmod_props far *rmod, struct redir_data *redir, unsigned char flags) {
383
static void run_as_external(char *buff, const char *cmdline, unsigned short envseg, struct rmod_props far *rmod, struct redir_data *redir, unsigned char flags) {
382
  char *cmdfile = buff + 512;
384
  char *cmdfile = buff + 512;
383
  const char far *pathptr;
385
  const char far *pathptr;
384
  int lookup;
386
  int lookup;
385
  unsigned short i;
387
  unsigned short i;
386
  const char *ext;
388
  const char *ext;
387
  char *cmd = buff + 1024;
389
  char *cmd = buff + 1024;
388
  const char *cmdtail;
390
  const char *cmdtail;
389
  char far *rmod_execprog = MK_FP(rmod->rmodseg, RMOD_OFFSET_EXECPROG);
391
  char far *rmod_execprog = MK_FP(rmod->rmodseg, RMOD_OFFSET_EXECPROG);
390
  char far *rmod_cmdtail = MK_FP(rmod->rmodseg, 0x81);
392
  char far *rmod_cmdtail = MK_FP(rmod->rmodseg, 0x81);
391
  _Packed struct {
393
  _Packed struct {
392
    unsigned short envseg;
394
    unsigned short envseg;
393
    unsigned long cmdtail;
395
    unsigned long cmdtail;
394
    unsigned long fcb1;
396
    unsigned long fcb1;
395
    unsigned long fcb2;
397
    unsigned long fcb2;
396
  } far *ExecParam = MK_FP(rmod->rmodseg, RMOD_OFFSET_EXECPARAM);
398
  } far *ExecParam = MK_FP(rmod->rmodseg, RMOD_OFFSET_EXECPARAM);
397
 
399
 
398
  /* find cmd and cmdtail */
400
  /* find cmd and cmdtail */
399
  i = 0;
401
  i = 0;
400
  cmdtail = cmdline;
402
  cmdtail = cmdline;
401
  while (*cmdtail == ' ') cmdtail++; /* skip any leading spaces */
403
  while (*cmdtail == ' ') cmdtail++; /* skip any leading spaces */
402
  while ((*cmdtail != ' ') && (*cmdtail != '/') && (*cmdtail != '+') && (*cmdtail != 0)) {
404
  while ((*cmdtail != ' ') && (*cmdtail != '/') && (*cmdtail != '+') && (*cmdtail != 0)) {
403
    cmd[i++] = *cmdtail;
405
    cmd[i++] = *cmdtail;
404
    cmdtail++;
406
    cmdtail++;
405
  }
407
  }
406
  cmd[i] = 0;
408
  cmd[i] = 0;
407
 
409
 
408
  /* is this a command in curdir? */
410
  /* is this a command in curdir? */
409
  lookup = lookup_cmd(cmdfile, cmd, NULL, &ext);
411
  lookup = lookup_cmd(cmdfile, cmd, NULL, &ext);
410
  if (lookup == 0) {
412
  if (lookup == 0) {
411
    /* printf("FOUND LOCAL EXEC FILE: '%s'\r\n", cmdfile); */
413
    /* printf("FOUND LOCAL EXEC FILE: '%s'\r\n", cmdfile); */
412
    goto RUNCMDFILE;
414
    goto RUNCMDFILE;
413
  } else if (lookup == -2) {
415
  } else if (lookup == -2) {
414
    /* puts("NOT FOUND"); */
416
    /* puts("NOT FOUND"); */
415
    return;
417
    return;
416
  }
418
  }
417
 
419
 
418
  /* try matching something in PATH */
420
  /* try matching something in PATH */
419
  pathptr = env_lookup_val(envseg, "PATH");
421
  pathptr = env_lookup_val(envseg, "PATH");
420
 
422
 
421
  /* try each path in %PATH% */
423
  /* try each path in %PATH% */
422
  while (pathptr) {
424
  while (pathptr) {
423
    for (i = 0;; i++) {
425
    for (i = 0;; i++) {
424
      buff[i] = *pathptr;
426
      buff[i] = *pathptr;
425
      if ((buff[i] == 0) || (buff[i] == ';')) break;
427
      if ((buff[i] == 0) || (buff[i] == ';')) break;
426
      pathptr++;
428
      pathptr++;
427
    }
429
    }
428
    buff[i] = 0;
430
    buff[i] = 0;
429
    lookup = lookup_cmd(cmdfile, cmd, buff, &ext);
431
    lookup = lookup_cmd(cmdfile, cmd, buff, &ext);
430
    if (lookup == 0) goto RUNCMDFILE;
432
    if (lookup == 0) goto RUNCMDFILE;
431
    if (lookup == -2) return;
433
    if (lookup == -2) return;
432
    if (*pathptr == ';') {
434
    if (*pathptr == ';') {
433
      pathptr++;
435
      pathptr++;
434
    } else {
436
    } else {
435
      break;
437
      break;
436
    }
438
    }
437
  }
439
  }
438
 
440
 
439
  /* last chance: is it an executable link? (trim extension from cmd first) */
441
  /* last chance: is it an executable link? (trim extension from cmd first) */
440
  for (i = 0; (cmd[i] != 0) && (cmd[i] != '.') && (i < 9); i++) buff[128 + i] = cmd[i];
442
  for (i = 0; (cmd[i] != 0) && (cmd[i] != '.') && (i < 9); i++) buff[128 + i] = cmd[i];
441
  buff[128 + i] = 0;
443
  buff[128 + i] = 0;
442
  if ((i < 9) && (link_computefname(buff, buff + 128, envseg) == 0)) {
444
  if ((i < 9) && (link_computefname(buff, buff + 128, envseg) == 0)) {
443
    /* try opening the link file (if it exists) and read it into buff */
445
    /* try opening the link file (if it exists) and read it into buff */
444
    i = 0;
446
    i = 0;
445
    _asm {
447
    _asm {
446
      push ax
448
      push ax
447
      push bx
449
      push bx
448
      push cx
450
      push cx
449
      push dx
451
      push dx
450
 
452
 
451
      mov ax, 0x3d00  /* DOS 2+ - OPEN EXISTING FILE, READ-ONLY */
453
      mov ax, 0x3d00  /* DOS 2+ - OPEN EXISTING FILE, READ-ONLY */
452
      mov dx, buff    /* file name */
454
      mov dx, buff    /* file name */
453
      int 0x21
455
      int 0x21
454
      jc ERR_FOPEN
456
      jc ERR_FOPEN
455
      /* file handle in AX, read from file now */
457
      /* file handle in AX, read from file now */
456
      mov bx, ax      /* file handle */
458
      mov bx, ax      /* file handle */
457
      mov ah, 0x3f    /* Read from file via handle bx */
459
      mov ah, 0x3f    /* Read from file via handle bx */
458
      mov cx, 128     /* up to 128 bytes */
460
      mov cx, 128     /* up to 128 bytes */
459
      /* mov dx, buff */ /* dest buffer (already set) */
461
      /* mov dx, buff */ /* dest buffer (already set) */
460
      int 0x21        /* read up to 256 bytes from file and write to buff */
462
      int 0x21        /* read up to 256 bytes from file and write to buff */
461
      jc ERR_READ
463
      jc ERR_READ
462
      mov i, ax
464
      mov i, ax
463
      ERR_READ:
465
      ERR_READ:
464
      mov ah, 0x3e    /* close file handle in BX */
466
      mov ah, 0x3e    /* close file handle in BX */
465
      int 0x21
467
      int 0x21
466
      ERR_FOPEN:
468
      ERR_FOPEN:
467
 
469
 
468
      pop dx
470
      pop dx
469
      pop cx
471
      pop cx
470
      pop bx
472
      pop bx
471
      pop ax
473
      pop ax
472
    }
474
    }
473
 
475
 
474
    /* did I read anything? */
476
    /* did I read anything? */
475
    if (i != 0) {
477
    if (i != 0) {
476
      buff[i] = 0;
478
      buff[i] = 0;
477
      /* trim buff at first \n or \r, just in case someone fiddled with the
479
      /* trim buff at first \n or \r, just in case someone fiddled with the
478
       * link file using a text editor */
480
       * link file using a text editor */
479
      for (i = 0; (buff[i] != 0) && (buff[i] != '\r') && (buff[i] != '\n'); i++);
481
      for (i = 0; (buff[i] != 0) && (buff[i] != '\r') && (buff[i] != '\n'); i++);
480
      buff[i] = 0;
482
      buff[i] = 0;
481
      /* lookup check */
483
      /* lookup check */
482
      if (buff[0] != 0) {
484
      if (buff[0] != 0) {
483
        lookup = lookup_cmd(cmdfile, cmd, buff, &ext);
485
        lookup = lookup_cmd(cmdfile, cmd, buff, &ext);
484
        if (lookup == 0) goto RUNCMDFILE;
486
        if (lookup == 0) goto RUNCMDFILE;
485
      }
487
      }
486
    }
488
    }
487
  }
489
  }
488
 
490
 
489
  /* all failed (ie. executable file not found) */
491
  /* all failed (ie. executable file not found) */
490
  return;
492
  return;
491
 
493
 
492
  RUNCMDFILE:
494
  RUNCMDFILE:
493
 
495
 
494
  /* special handling of batch files */
496
  /* special handling of batch files */
495
  if ((ext != NULL) && (imatch(ext, "bat"))) {
497
  if ((ext != NULL) && (imatch(ext, "bat"))) {
496
    struct batctx far *newbat;
498
    struct batctx far *newbat;
497
 
499
 
498
    /* remember the echo flag (in case bat file disables echo, only when starting first bat) */
500
    /* remember the echo flag (in case bat file disables echo, only when starting first bat) */
499
    if (rmod->bat == NULL) {
501
    if (rmod->bat == NULL) {
500
      rmod->flags &= ~FLAG_ECHO_BEFORE_BAT;
502
      rmod->flags &= ~FLAG_ECHO_BEFORE_BAT;
501
      if (rmod->flags & FLAG_ECHOFLAG) rmod->flags |= FLAG_ECHO_BEFORE_BAT;
503
      if (rmod->flags & FLAG_ECHOFLAG) rmod->flags |= FLAG_ECHO_BEFORE_BAT;
502
    }
504
    }
503
 
505
 
504
    /* if bat is not called via a CALL, then free the bat-context linked list */
506
    /* if bat is not called via a CALL, then free the bat-context linked list */
505
    if ((flags & CALL_FLAG) == 0) rmod_free_bat_llist(rmod);
507
    if ((flags & CALL_FLAG) == 0) rmod_free_bat_llist(rmod);
506
 
508
 
507
    /* allocate a new bat context */
509
    /* allocate a new bat context */
508
    newbat = rmod_fcalloc(sizeof(struct batctx), rmod->rmodseg, "SVBATCTX");
510
    newbat = rmod_fcalloc(sizeof(struct batctx), rmod->rmodseg, "SVBATCTX");
509
    if (newbat == NULL) {
511
    if (newbat == NULL) {
510
      nls_outputnl_doserr(8); /* insufficient memory */
512
      nls_outputnl_doserr(8); /* insufficient memory */
511
      return;
513
      return;
512
    }
514
    }
513
 
515
 
514
    /* fill the newly allocated batctx structure */
516
    /* fill the newly allocated batctx structure */
515
    _fstrcpy(newbat->fname, cmdfile); /* truename of the BAT file */
517
    _fstrcpy(newbat->fname, cmdfile); /* truename of the BAT file */
516
    newbat->flags = flags & FLAG_STEPBYSTEP;
518
    newbat->flags = flags & FLAG_STEPBYSTEP;
517
    /* explode args of the bat file and store them in rmod buff */
519
    /* explode args of the bat file and store them in rmod buff */
518
    cmd_explode(buff, cmdline, NULL);
520
    cmd_explode(buff, cmdline, NULL);
519
    _fmemcpy(newbat->argv, buff, sizeof(newbat->argv));
521
    _fmemcpy(newbat->argv, buff, sizeof(newbat->argv));
520
 
522
 
521
    /* push the new bat to the top of rmod's linked list */
523
    /* push the new bat to the top of rmod's linked list */
522
    newbat->parent = rmod->bat;
524
    newbat->parent = rmod->bat;
523
    rmod->bat = newbat;
525
    rmod->bat = newbat;
524
 
526
 
525
    return;
527
    return;
526
  }
528
  }
527
 
529
 
528
  /* copy full filename to execute, along with redirected files (if any) */
530
  /* copy full filename to execute, along with redirected files (if any) */
529
  _fstrcpy(rmod_execprog, cmdfile);
531
  _fstrcpy(rmod_execprog, cmdfile);
530
 
532
 
531
  /* copy stdin file if a redirection is needed */
533
  /* copy stdin file if a redirection is needed */
532
  if (redir->stdinfile) {
534
  if (redir->stdinfile) {
533
    char far *farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDINFILE);
535
    char far *farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDINFILE);
534
    char far *delstdin = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDIN_DEL);
536
    char far *delstdin = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDIN_DEL);
535
    _fstrcpy(farptr, redir->stdinfile);
537
    _fstrcpy(farptr, redir->stdinfile);
536
    if (flags & DELETE_STDIN_FILE) {
538
    if (flags & DELETE_STDIN_FILE) {
537
      *delstdin = redir->stdinfile[0];
539
      *delstdin = redir->stdinfile[0];
538
    } else {
540
    } else {
539
      *delstdin = 0;
541
      *delstdin = 0;
540
    }
542
    }
541
  }
543
  }
542
 
544
 
543
  /* same for stdout file */
545
  /* same for stdout file */
544
  if (redir->stdoutfile) {
546
  if (redir->stdoutfile) {
545
    char far *farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDOUTFILE);
547
    char far *farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDOUTFILE);
546
    unsigned short far *farptr16 = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDOUTAPP);
548
    unsigned short far *farptr16 = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDOUTAPP);
547
    _fstrcpy(farptr, redir->stdoutfile);
549
    _fstrcpy(farptr, redir->stdoutfile);
548
    /* openflag */
550
    /* openflag */
549
    *farptr16 = redir->stdout_openflag;
551
    *farptr16 = redir->stdout_openflag;
550
  }
552
  }
551
 
553
 
552
  /* copy cmdtail to rmod's PSP and compute its len */
554
  /* copy cmdtail to rmod's PSP and compute its len */
553
  for (i = 0; cmdtail[i] != 0; i++) rmod_cmdtail[i] = cmdtail[i];
555
  for (i = 0; cmdtail[i] != 0; i++) rmod_cmdtail[i] = cmdtail[i];
554
  rmod_cmdtail[i] = '\r';
556
  rmod_cmdtail[i] = '\r';
555
  rmod_cmdtail[-1] = i;
557
  rmod_cmdtail[-1] = i;
556
 
558
 
557
  /* set up rmod to execute the command */
559
  /* set up rmod to execute the command */
558
 
560
 
559
  ExecParam->envseg = envseg;
561
  ExecParam->envseg = envseg;
560
  ExecParam->cmdtail = (unsigned long)MK_FP(rmod->rmodseg, 0x80); /* farptr, must be in PSP format (lenbyte args \r) */
562
  ExecParam->cmdtail = (unsigned long)MK_FP(rmod->rmodseg, 0x80); /* farptr, must be in PSP format (lenbyte args \r) */
561
  /* far pointers to unopened FCB entries (stored in RMOD's own PSP) */
563
  /* far pointers to unopened FCB entries (stored in RMOD's own PSP) */
562
  {
564
  {
563
    char far *farptr;
565
    char far *farptr;
564
    /* prep the unopened FCBs */
566
    /* prep the unopened FCBs */
565
    farptr = MK_FP(rmod->rmodseg, 0x5C);
567
    farptr = MK_FP(rmod->rmodseg, 0x5C);
566
    _fmemset(farptr, 0, 36); /* first FCB is 16 bytes long, second is 20 bytes long */
568
    _fmemset(farptr, 0, 36); /* first FCB is 16 bytes long, second is 20 bytes long */
567
    cmdtail_to_fcb(farptr, farptr + 16, cmdtail);
569
    cmdtail_to_fcb(farptr, farptr + 16, cmdtail);
568
    /* set (far) pointers in the ExecParam block */
570
    /* set (far) pointers in the ExecParam block */
569
    ExecParam->fcb1 = (unsigned long)MK_FP(rmod->rmodseg, 0x5C);
571
    ExecParam->fcb1 = (unsigned long)MK_FP(rmod->rmodseg, 0x5C);
570
    ExecParam->fcb2 = (unsigned long)MK_FP(rmod->rmodseg, 0x6C);
572
    ExecParam->fcb2 = (unsigned long)MK_FP(rmod->rmodseg, 0x6C);
571
  }
573
  }
572
  exit(0); /* let rmod do the job now */
574
  exit(0); /* let rmod do the job now */
573
}
575
}
574
 
576
 
575
 
577
 
576
static void set_comspec_to_self(unsigned short envseg) {
578
static void set_comspec_to_self(unsigned short envseg) {
577
  unsigned short *psp_envseg = (void *)(0x2c); /* pointer to my env segment field in the PSP */
579
  unsigned short *psp_envseg = (void *)(0x2c); /* pointer to my env segment field in the PSP */
578
  char far *myenv = MK_FP(*psp_envseg, 0);
580
  char far *myenv = MK_FP(*psp_envseg, 0);
579
  unsigned short varcount;
581
  unsigned short varcount;
580
  char buff[256] = "COMSPEC=";
582
  char buff[256] = "COMSPEC=";
581
  char *buffptr = buff + 8;
583
  char *buffptr = buff + 8;
582
  /* who am i? look into my own environment, at the end of it should be my EXEPATH string */
584
  /* who am i? look into my own environment, at the end of it should be my EXEPATH string */
583
  while (*myenv != 0) {
585
  while (*myenv != 0) {
584
    /* consume a NULL-terminated string */
586
    /* consume a NULL-terminated string */
585
    while (*myenv != 0) myenv++;
587
    while (*myenv != 0) myenv++;
586
    /* move to next string */
588
    /* move to next string */
587
    myenv++;
589
    myenv++;
588
  }
590
  }
589
  /* get next word, if 1 then EXEPATH follows */
591
  /* get next word, if 1 then EXEPATH follows */
590
  myenv++;
592
  myenv++;
591
  varcount = *myenv;
593
  varcount = *myenv;
592
  myenv++;
594
  myenv++;
593
  varcount |= (*myenv << 8);
595
  varcount |= (*myenv << 8);
594
  myenv++;
596
  myenv++;
595
  if (varcount != 1) return; /* NO EXEPATH FOUND */
597
  if (varcount != 1) return; /* NO EXEPATH FOUND */
596
  while (*myenv != 0) {
598
  while (*myenv != 0) {
597
    *buffptr = *myenv;
599
    *buffptr = *myenv;
598
    buffptr++;
600
    buffptr++;
599
    myenv++;
601
    myenv++;
600
  }
602
  }
601
  *buffptr = 0;
603
  *buffptr = 0;
602
  /* printf("EXEPATH: '%s'\r\n", buff); */
604
  /* printf("EXEPATH: '%s'\r\n", buff); */
603
  env_setvar(envseg, buff);
605
  env_setvar(envseg, buff);
604
}
606
}
605
 
607
 
606
 
608
 
607
/* wait for user input */
609
/* wait for user input */
608
static void cmdline_getinput(unsigned short inpseg, unsigned short inpoff) {
610
static void cmdline_getinput(unsigned short inpseg, unsigned short inpoff) {
609
  _asm {
611
  _asm {
610
    push ax
612
    push ax
611
    push bx
613
    push bx
612
    push cx
614
    push cx
613
    push dx
615
    push dx
614
    push ds
616
    push ds
615
 
617
 
616
    /* is DOSKEY support present? (INT 2Fh, AX=4800h, returns non-zero in AL if present) */
618
    /* is DOSKEY support present? (INT 2Fh, AX=4800h, returns non-zero in AL if present) */
617
    mov ax, 0x4800
619
    mov ax, 0x4800
618
    int 0x2f
620
    int 0x2f
619
    mov bl, al /* save doskey status in BL */
621
    mov bl, al /* save doskey status in BL */
620
 
622
 
621
    /* set up buffered input to inpseg:inpoff */
623
    /* set up buffered input to inpseg:inpoff */
622
    mov ax, inpseg
624
    mov ax, inpseg
623
    push ax
625
    push ax
624
    pop ds
626
    pop ds
625
    mov dx, inpoff
627
    mov dx, inpoff
626
 
628
 
627
    /* execute either DOS input or DOSKEY */
629
    /* execute either DOS input or DOSKEY */
628
    test bl, bl /* zf set if no DOSKEY present */
630
    test bl, bl /* zf set if no DOSKEY present */
629
    jnz DOSKEY
631
    jnz DOSKEY
630
 
632
 
631
    mov ah, 0x0a
633
    mov ah, 0x0a
632
    int 0x21
634
    int 0x21
633
    jmp short DONE
635
    jmp short DONE
634
 
636
 
635
    DOSKEY:
637
    DOSKEY:
636
    mov ax, 0x4810
638
    mov ax, 0x4810
637
    int 0x2f
639
    int 0x2f
638
 
640
 
639
    DONE:
641
    DONE:
640
    /* terminate command with a CR/LF */
642
    /* terminate command with a CR/LF */
641
    mov ah, 0x02 /* display character in dl */
643
    mov ah, 0x02 /* display character in dl */
642
    mov dl, 0x0d
644
    mov dl, 0x0d
643
    int 0x21
645
    int 0x21
644
    mov dl, 0x0a
646
    mov dl, 0x0a
645
    int 0x21
647
    int 0x21
646
 
648
 
647
    pop ds
649
    pop ds
648
    pop dx
650
    pop dx
649
    pop cx
651
    pop cx
650
    pop bx
652
    pop bx
651
    pop ax
653
    pop ax
652
  }
654
  }
653
}
655
}
654
 
656
 
655
 
657
 
656
/* fetches a line from batch file and write it to buff (NULL-terminated),
658
/* fetches a line from batch file and write it to buff (NULL-terminated),
657
 * increments rmod counter and returns 0 on success. */
659
 * increments rmod counter and returns 0 on success. */
658
static int getbatcmd(char *buff, unsigned char buffmaxlen, struct rmod_props far *rmod) {
660
static int getbatcmd(char *buff, unsigned char buffmaxlen, struct rmod_props far *rmod) {
659
  unsigned short i;
661
  unsigned short i;
660
  unsigned short batname_seg = FP_SEG(rmod->bat->fname);
662
  unsigned short batname_seg = FP_SEG(rmod->bat->fname);
661
  unsigned short batname_off = FP_OFF(rmod->bat->fname);
663
  unsigned short batname_off = FP_OFF(rmod->bat->fname);
662
  unsigned short filepos_cx = rmod->bat->nextline >> 16;
664
  unsigned short filepos_cx = rmod->bat->nextline >> 16;
663
  unsigned short filepos_dx = rmod->bat->nextline & 0xffff;
665
  unsigned short filepos_dx = rmod->bat->nextline & 0xffff;
664
  unsigned char blen = 0;
666
  unsigned char blen = 0;
665
  unsigned short errv = 0;
667
  unsigned short errv = 0;
666
 
668
 
667
  /* open file, jump to offset filpos, and read data into buff.
669
  /* open file, jump to offset filpos, and read data into buff.
668
   * result in blen (unchanged if EOF or failure). */
670
   * result in blen (unchanged if EOF or failure). */
669
  _asm {
671
  _asm {
670
    push ax
672
    push ax
671
    push bx
673
    push bx
672
    push cx
674
    push cx
673
    push dx
675
    push dx
674
 
676
 
675
    /* open file (read-only) */
677
    /* open file (read-only) */
676
    mov bx, 0xffff        /* preset BX to 0xffff to detect error conditions */
678
    mov bx, 0xffff        /* preset BX to 0xffff to detect error conditions */
677
    mov dx, batname_off
679
    mov dx, batname_off
678
    mov ax, batname_seg
680
    mov ax, batname_seg
679
    push ds     /* save DS */
681
    push ds     /* save DS */
680
    mov ds, ax
682
    mov ds, ax
681
    mov ax, 0x3d00
683
    mov ax, 0x3d00
682
    int 0x21    /* handle in ax on success */
684
    int 0x21    /* handle in ax on success */
683
    pop ds      /* restore DS */
685
    pop ds      /* restore DS */
684
    jc ERR
686
    jc ERR
685
    mov bx, ax  /* save handle to bx */
687
    mov bx, ax  /* save handle to bx */
686
 
688
 
687
    /* jump to file offset CX:DX */
689
    /* jump to file offset CX:DX */
688
    mov ax, 0x4200
690
    mov ax, 0x4200
689
    mov cx, filepos_cx
691
    mov cx, filepos_cx
690
    mov dx, filepos_dx
692
    mov dx, filepos_dx
691
    int 0x21  /* CF clear on success, DX:AX set to cur pos */
693
    int 0x21  /* CF clear on success, DX:AX set to cur pos */
692
    jc ERR
694
    jc ERR
693
 
695
 
694
    /* read the line into buff */
696
    /* read the line into buff */
695
    mov ah, 0x3f
697
    mov ah, 0x3f
696
    xor ch, ch
698
    xor ch, ch
697
    mov cl, buffmaxlen
699
    mov cl, buffmaxlen
698
    mov dx, buff
700
    mov dx, buff
699
    int 0x21 /* CF clear on success, AX=number of bytes read */
701
    int 0x21 /* CF clear on success, AX=number of bytes read */
700
    jc ERR
702
    jc ERR
701
    mov blen, al
703
    mov blen, al
702
    jmp CLOSEANDQUIT
704
    jmp CLOSEANDQUIT
703
 
705
 
704
    ERR:
706
    ERR:
705
    mov errv, ax
707
    mov errv, ax
706
 
708
 
707
    CLOSEANDQUIT:
709
    CLOSEANDQUIT:
708
    /* close file (if bx contains a handle) */
710
    /* close file (if bx contains a handle) */
709
    cmp bx, 0xffff
711
    cmp bx, 0xffff
710
    je DONE
712
    je DONE
711
    mov ah, 0x3e
713
    mov ah, 0x3e
712
    int 0x21
714
    int 0x21
713
 
715
 
714
    DONE:
716
    DONE:
715
    pop dx
717
    pop dx
716
    pop cx
718
    pop cx
717
    pop bx
719
    pop bx
718
    pop ax
720
    pop ax
719
  }
721
  }
720
 
722
 
721
  /* printf("blen=%u filepos_cx=%u filepos_dx=%u\r\n", blen, filepos_cx, filepos_dx); */
723
  /* printf("blen=%u filepos_cx=%u filepos_dx=%u\r\n", blen, filepos_cx, filepos_dx); */
722
 
724
 
723
  if (errv != 0) nls_outputnl_doserr(errv);
725
  if (errv != 0) nls_outputnl_doserr(errv);
724
 
726
 
725
  /* on EOF - abort processing the bat file */
727
  /* on EOF - abort processing the bat file */
726
  if (blen == 0) goto OOPS;
728
  if (blen == 0) goto OOPS;
727
 
729
 
728
  /* find nearest \n to inc batch offset and replace \r by NULL terminator
730
  /* find nearest \n to inc batch offset and replace \r by NULL terminator
729
   * I support all CR/LF, CR- and LF-terminated batch files */
731
   * I support all CR/LF, CR- and LF-terminated batch files */
730
  for (i = 0; i < blen; i++) {
732
  for (i = 0; i < blen; i++) {
731
    if ((buff[i] == '\r') || (buff[i] == '\n')) {
733
    if ((buff[i] == '\r') || (buff[i] == '\n')) {
732
      if ((buff[i] == '\r') && ((i+1) < blen) && (buff[i+1] == '\n')) rmod->bat->nextline += 1;
734
      if ((buff[i] == '\r') && ((i+1) < blen) && (buff[i+1] == '\n')) rmod->bat->nextline += 1;
733
      break;
735
      break;
734
    }
736
    }
735
  }
737
  }
736
  buff[i] = 0;
738
  buff[i] = 0;
737
  rmod->bat->nextline += i + 1;
739
  rmod->bat->nextline += i + 1;
738
 
740
 
739
  return(0);
741
  return(0);
740
 
742
 
741
  OOPS:
743
  OOPS:
742
  rmod->bat->fname[0] = 0;
744
  rmod->bat->fname[0] = 0;
743
  rmod->bat->nextline = 0;
745
  rmod->bat->nextline = 0;
744
  return(-1);
746
  return(-1);
745
}
747
}
746
 
748
 
747
 
749
 
748
/* replaces %-variables in a BAT line with resolved values:
750
/* replaces %-variables in a BAT line with resolved values:
749
 * %PATH%       -> replaced by the contend of the PATH env variable
751
 * %PATH%       -> replaced by the contend of the PATH env variable
750
 * %UNDEFINED%  -> undefined variables are replaced by nothing ("")
752
 * %UNDEFINED%  -> undefined variables are replaced by nothing ("")
751
 * %NOTCLOSED   -> NOTCLOSED
753
 * %NOTCLOSED   -> NOTCLOSED
752
 * %1           -> first argument of the batch file (or nothing if no arg) */
754
 * %1           -> first argument of the batch file (or nothing if no arg) */
753
static void batpercrepl(char *res, unsigned short ressz, const char *line, const struct rmod_props far *rmod, unsigned short envseg) {
755
static void batpercrepl(char *res, unsigned short ressz, const char *line, const struct rmod_props far *rmod, unsigned short envseg) {
754
  unsigned short lastperc = 0xffff;
756
  unsigned short lastperc = 0xffff;
755
  unsigned short reslen = 0;
757
  unsigned short reslen = 0;
756
 
758
 
757
  if (ressz == 0) return;
759
  if (ressz == 0) return;
758
  ressz--; /* reserve one byte for the NULL terminator */
760
  ressz--; /* reserve one byte for the NULL terminator */
759
 
761
 
760
  for (; (reslen < ressz) && (*line != 0); line++) {
762
  for (; (reslen < ressz) && (*line != 0); line++) {
761
    /* if not a percent, I don't care */
763
    /* if not a percent, I don't care */
762
    if (*line != '%') {
764
    if (*line != '%') {
763
      res[reslen++] = *line;
765
      res[reslen++] = *line;
764
      continue;
766
      continue;
765
    }
767
    }
766
 
768
 
767
    /* *** perc char handling *** */
769
    /* *** perc char handling *** */
768
 
770
 
769
    /* closing perc? */
771
    /* closing perc? */
770
    if (lastperc != 0xffff) {
772
    if (lastperc != 0xffff) {
771
      /* %% is '%' */
773
      /* %% is '%' */
772
      if (lastperc == reslen) {
774
      if (lastperc == reslen) {
773
        res[reslen++] = '%';
775
        res[reslen++] = '%';
774
      } else {   /* otherwise variable name */
776
      } else {   /* otherwise variable name */
775
        const char far *ptr;
777
        const char far *ptr;
776
        res[reslen] = 0;
778
        res[reslen] = 0;
777
        reslen = lastperc;
779
        reslen = lastperc;
778
        nls_strtoup(res + reslen); /* turn varname uppercase before lookup */
780
        nls_strtoup(res + reslen); /* turn varname uppercase before lookup */
779
        ptr = env_lookup_val(envseg, res + reslen);
781
        ptr = env_lookup_val(envseg, res + reslen);
780
        if (ptr != NULL) {
782
        if (ptr != NULL) {
781
          while ((*ptr != 0) && (reslen < ressz)) {
783
          while ((*ptr != 0) && (reslen < ressz)) {
782
            res[reslen++] = *ptr;
784
            res[reslen++] = *ptr;
783
            ptr++;
785
            ptr++;
784
          }
786
          }
785
        }
787
        }
786
      }
788
      }
787
      lastperc = 0xffff;
789
      lastperc = 0xffff;
788
      continue;
790
      continue;
789
    }
791
    }
790
 
792
 
791
    /* digit? (bat arg) */
793
    /* digit? (bat arg) */
792
    if ((line[1] >= '0') && (line[1] <= '9')) {
794
    if ((line[1] >= '0') && (line[1] <= '9')) {
793
      unsigned short argid = line[1] - '0';
795
      unsigned short argid = line[1] - '0';
794
      unsigned short i;
796
      unsigned short i;
795
      const char far *argv = "";
797
      const char far *argv = "";
796
      if ((rmod != NULL) && (rmod->bat != NULL)) argv = rmod->bat->argv;
798
      if ((rmod != NULL) && (rmod->bat != NULL)) argv = rmod->bat->argv;
797
 
799
 
798
      /* locate the proper arg */
800
      /* locate the proper arg */
799
      for (i = 0; i != argid; i++) {
801
      for (i = 0; i != argid; i++) {
800
        /* if string is 0, then end of list reached */
802
        /* if string is 0, then end of list reached */
801
        if (*argv == 0) break;
803
        if (*argv == 0) break;
802
        /* jump to next arg */
804
        /* jump to next arg */
803
        while (*argv != 0) argv++;
805
        while (*argv != 0) argv++;
804
        argv++;
806
        argv++;
805
      }
807
      }
806
 
808
 
807
      /* copy the arg to result */
809
      /* copy the arg to result */
808
      for (i = 0; (argv[i] != 0) && (reslen < ressz); i++) {
810
      for (i = 0; (argv[i] != 0) && (reslen < ressz); i++) {
809
        res[reslen++] = argv[i];
811
        res[reslen++] = argv[i];
810
      }
812
      }
811
      line++;  /* skip the digit */
813
      line++;  /* skip the digit */
812
      continue;
814
      continue;
813
    }
815
    }
814
 
816
 
815
    /* opening perc */
817
    /* opening perc */
816
    lastperc = reslen;
818
    lastperc = reslen;
817
 
819
 
818
  }
820
  }
819
 
821
 
820
  res[reslen] = 0;
822
  res[reslen] = 0;
821
}
823
}
822
 
824
 
823
 
825
 
824
/* process the ongoing forloop, returns 0 on success, non-zero otherwise (no
826
/* process the ongoing forloop, returns 0 on success, non-zero otherwise (no
825
   more things to process) */
827
   more things to process) */
826
static int forloop_process(char *res, struct forctx far *forloop) {
828
static int forloop_process(char *res, struct forctx far *forloop) {
827
  unsigned short i, t;
829
  unsigned short i, t;
828
  struct DTA *dta = (void *)0x80; /* default DTA at 80h in PSP */
830
  struct DTA *dta = (void *)0x80; /* default DTA at 80h in PSP */
829
  char *fnameptr = dta->fname;
831
  char *fnameptr = dta->fname;
830
  char *pathprefix = BUFFER + 256;
832
  char *pathprefix = BUFFER + 256;
831
 
833
 
832
  *pathprefix = 0;
834
  *pathprefix = 0;
833
 
835
 
834
  TRYAGAIN:
836
  TRYAGAIN:
835
 
837
 
836
  /* dta_inited: FindFirst() or FindNext()? */
838
  /* dta_inited: FindFirst() or FindNext()? */
837
  if (forloop->dta_inited == 0) {
839
  if (forloop->dta_inited == 0) {
838
 
840
 
839
    /* copy next awaiting pattern to BUFFER (and skip all delimiters until
841
    /* copy next awaiting pattern to BUFFER (and skip all delimiters until
840
     * next pattern or end of list) */
842
     * next pattern or end of list) */
841
    t = 0;
843
    t = 0;
842
    for (i = 0;; i++) {
844
    for (i = 0;; i++) {
843
      BUFFER[i] = forloop->cmd[forloop->nextpat + i];
845
      BUFFER[i] = forloop->cmd[forloop->nextpat + i];
844
      /* is this a delimiter? (all delimiters are already normalized to a space here) */
846
      /* is this a delimiter? (all delimiters are already normalized to a space here) */
845
      if (BUFFER[i] == ' ') {
847
      if (BUFFER[i] == ' ') {
846
        BUFFER[i] = 0;
848
        BUFFER[i] = 0;
847
        t = 1;
849
        t = 1;
848
      } else if (BUFFER[i] == 0) {
850
      } else if (BUFFER[i] == 0) {
849
        /* end of patterns list */
851
        /* end of patterns list */
850
        break;
852
        break;
851
      } else {
853
      } else {
852
        /* quit if I got a pattern already */
854
        /* quit if I got a pattern already */
853
        if (t == 1) break;
855
        if (t == 1) break;
854
      }
856
      }
855
    }
857
    }
856
 
858
 
857
    if (i == 0) return(-1);
859
    if (i == 0) return(-1);
858
 
860
 
859
    /* remember position of current pattern */
861
    /* remember position of current pattern */
860
    forloop->curpat = forloop->nextpat;
862
    forloop->curpat = forloop->nextpat;
861
 
863
 
862
    /* move nextpat forward to next pattern */
864
    /* move nextpat forward to next pattern */
863
    i += forloop->nextpat;
865
    i += forloop->nextpat;
864
    forloop->nextpat = i;
866
    forloop->nextpat = i;
865
 
867
 
866
    /* if this is a string and not a pattern, skip all the FindFirst business
868
    /* if this is a string and not a pattern, skip all the FindFirst business
867
     * a file pattern has a wildcard (* or ?), a message doesn't */
869
     * a file pattern has a wildcard (* or ?), a message doesn't */
868
    for (i = 0; (BUFFER[i] != 0) && (BUFFER[i] != '?') && (BUFFER[i] != '*'); i++);
870
    for (i = 0; (BUFFER[i] != 0) && (BUFFER[i] != '?') && (BUFFER[i] != '*'); i++);
869
    if (BUFFER[i] == 0) {
871
    if (BUFFER[i] == 0) {
870
      fnameptr = BUFFER;
872
      fnameptr = BUFFER;
871
      goto SKIP_DTA;
873
      goto SKIP_DTA;
872
    }
874
    }
873
 
875
 
874
    /* FOR in MSDOS 6 includes hidden and system files, but not directories nor volumes */
876
    /* FOR in MSDOS 6 includes hidden and system files, but not directories nor volumes */
875
    if (findfirst(dta, BUFFER, DOS_ATTR_RO | DOS_ATTR_HID | DOS_ATTR_SYS | DOS_ATTR_ARC) != 0) {
877
    if (findfirst(dta, BUFFER, DOS_ATTR_RO | DOS_ATTR_HID | DOS_ATTR_SYS | DOS_ATTR_ARC) != 0) {
876
      goto TRYAGAIN;
878
      goto TRYAGAIN;
877
    }
879
    }
878
    forloop->dta_inited = 1;
880
    forloop->dta_inited = 1;
879
  } else { /* dta in progress */
881
  } else { /* dta in progress */
880
 
882
 
881
    /* copy forloop DTA to my local copy */
883
    /* copy forloop DTA to my local copy */
882
    _fmemcpy(dta, &(forloop->dta), sizeof(*dta));
884
    _fmemcpy(dta, &(forloop->dta), sizeof(*dta));
883
 
885
 
884
    /* findnext() call */
886
    /* findnext() call */
885
    if (findnext(dta) != 0) {
887
    if (findnext(dta) != 0) {
886
      forloop->dta_inited = 0;
888
      forloop->dta_inited = 0;
887
      goto TRYAGAIN;
889
      goto TRYAGAIN;
888
    }
890
    }
889
  }
891
  }
890
 
892
 
891
  /* copy updated DTA to rmod */
893
  /* copy updated DTA to rmod */
892
  _fmemcpy(&(forloop->dta), dta, sizeof(*dta));
894
  _fmemcpy(&(forloop->dta), dta, sizeof(*dta));
893
 
895
 
894
  /* prefill pathprefix with the prefix (path) of the files */
896
  /* prefill pathprefix with the prefix (path) of the files */
895
  {
897
  {
896
    short lastbk = -1;
898
    short lastbk = -1;
897
    char far *c = forloop->cmd + forloop->curpat;
899
    char far *c = forloop->cmd + forloop->curpat;
898
    for (i = 0;; i++) {
900
    for (i = 0;; i++) {
899
      pathprefix[i] = c[i];
901
      pathprefix[i] = c[i];
900
      if (pathprefix[i] == '\\') lastbk = i;
902
      if (pathprefix[i] == '\\') lastbk = i;
901
      if ((pathprefix[i] == ' ') || (pathprefix[i] == 0)) break;
903
      if ((pathprefix[i] == ' ') || (pathprefix[i] == 0)) break;
902
    }
904
    }
903
    pathprefix[lastbk+1] = 0;
905
    pathprefix[lastbk+1] = 0;
904
  }
906
  }
905
 
907
 
906
  SKIP_DTA:
908
  SKIP_DTA:
907
 
909
 
908
  /* fill res with command, replacing varname by actual filename */
910
  /* fill res with command, replacing varname by actual filename */
909
  /* full filename is to be built with path of curpat and fname from dta */
911
  /* full filename is to be built with path of curpat and fname from dta */
910
  t = 0;
912
  t = 0;
911
  i = 0;
913
  i = 0;
912
  for (;;) {
914
  for (;;) {
913
    if ((forloop->cmd[forloop->exec + t] == '%') && (forloop->cmd[forloop->exec + t + 1] == forloop->varname)) {
915
    if ((forloop->cmd[forloop->exec + t] == '%') && (forloop->cmd[forloop->exec + t + 1] == forloop->varname)) {
914
      strcpy(res + i, pathprefix);
916
      strcpy(res + i, pathprefix);
915
      strcat(res + i, fnameptr);
917
      strcat(res + i, fnameptr);
916
      for (; res[i] != 0; i++);
918
      for (; res[i] != 0; i++);
917
      t += 2;
919
      t += 2;
918
    } else {
920
    } else {
919
      res[i] = forloop->cmd[forloop->exec + t];
921
      res[i] = forloop->cmd[forloop->exec + t];
920
      t++;
922
      t++;
921
      if (res[i++] == 0) break;
923
      if (res[i++] == 0) break;
922
    }
924
    }
923
  }
925
  }
924
 
926
 
925
  return(0);
927
  return(0);
926
}
928
}
927
 
929
 
928
 
930
 
929
int main(void) {
931
int main(void) {
930
  static struct config cfg;
932
  static struct config cfg;
931
  static unsigned short far *rmod_envseg;
933
  static unsigned short far *rmod_envseg;
932
  static unsigned short far *lastexitcode;
934
  static unsigned short far *lastexitcode;
933
  static struct rmod_props far *rmod;
935
  static struct rmod_props far *rmod;
934
  static char cmdlinebuf[CMDLINE_MAXLEN + 2]; /* 1 extra byte for 0-terminator and another for memguard */
936
  static char cmdlinebuf[CMDLINE_MAXLEN + 2]; /* 1 extra byte for 0-terminator and another for memguard */
935
  static char *cmdline;
937
  static char *cmdline;
936
  static struct redir_data redirprops;
938
  static struct redir_data redirprops;
937
  static enum cmd_result cmdres;
939
  static enum cmd_result cmdres;
938
  static unsigned short i; /* general-purpose variable for short-lived things */
940
  static unsigned short i; /* general-purpose variable for short-lived things */
939
  static unsigned char flags;
941
  static unsigned char flags;
940
 
942
 
941
  rmod = rmod_find(BUFFER_len);
943
  rmod = rmod_find(BUFFER_len);
942
  if (rmod == NULL) {
944
  if (rmod == NULL) {
943
    /* look at command line parameters (in case env size if set there) */
945
    /* look at command line parameters (in case env size if set there) */
944
    parse_argv(&cfg);
946
    parse_argv(&cfg);
945
    rmod = rmod_install(cfg.envsiz, BUFFER, BUFFER_len);
947
    rmod = rmod_install(cfg.envsiz, BUFFER, BUFFER_len);
946
    if (rmod == NULL) {
948
    if (rmod == NULL) {
947
      nls_outputnl_err(2,1); /* "FATAL ERROR: rmod_install() failed" */
949
      nls_outputnl_err(2,1); /* "FATAL ERROR: rmod_install() failed" */
948
      return(1);
950
      return(1);
949
    }
951
    }
950
    /* copy flags to rmod's storage (and enable ECHO) */
952
    /* copy flags to rmod's storage (and enable ECHO) */
951
    rmod->flags = cfg.flags | FLAG_ECHOFLAG;
953
    rmod->flags = cfg.flags | FLAG_ECHOFLAG;
952
    /* printf("rmod installed at %Fp\r\n", rmod); */
954
    /* printf("rmod installed at %Fp\r\n", rmod); */
953
    rmod->version = BYTE_VERSION;
955
    rmod->version = BYTE_VERSION;
954
  } else {
956
  } else {
955
    /* printf("rmod found at %Fp\r\n", rmod); */
957
    /* printf("rmod found at %Fp\r\n", rmod); */
956
    /* if I was spawned by rmod and FLAG_EXEC_AND_QUIT is set, then I should
958
    /* if I was spawned by rmod and FLAG_EXEC_AND_QUIT is set, then I should
957
     * die asap, because the command has been executed already, so I no longer
959
     * die asap, because the command has been executed already, so I no longer
958
     * have a purpose in life */
960
     * have a purpose in life */
959
    if (rmod->flags & FLAG_EXEC_AND_QUIT) sayonara(rmod);
961
    if (rmod->flags & FLAG_EXEC_AND_QUIT) sayonara(rmod);
960
    /* */
962
    /* */
961
    if (rmod->version != BYTE_VERSION) {
963
    if (rmod->version != BYTE_VERSION) {
962
      nls_outputnl_err(2,0);
964
      nls_outputnl_err(2,0);
963
      _asm {
965
      _asm {
964
        HALT:
966
        HALT:
965
        hlt
967
        hlt
966
        jmp HALT
968
        jmp HALT
967
      }
969
      }
968
    }
970
    }
969
  }
971
  }
970
 
972
 
971
  /* install a few guardvals in memory to detect some cases of overflows */
973
  /* install a few guardvals in memory to detect some cases of overflows */
972
  memguard_set(cmdlinebuf);
974
  memguard_set(cmdlinebuf);
973
 
975
 
974
  rmod_envseg = MK_FP(rmod->rmodseg, RMOD_OFFSET_ENVSEG);
976
  rmod_envseg = MK_FP(rmod->rmodseg, RMOD_OFFSET_ENVSEG);
975
  lastexitcode = MK_FP(rmod->rmodseg, RMOD_OFFSET_LEXITCODE);
977
  lastexitcode = MK_FP(rmod->rmodseg, RMOD_OFFSET_LEXITCODE);
976
 
978
 
977
  /* make COMSPEC point to myself */
979
  /* make COMSPEC point to myself */
978
  set_comspec_to_self(*rmod_envseg);
980
  set_comspec_to_self(*rmod_envseg);
979
 
981
 
980
  /* on /P check for the presence of AUTOEXEC.BAT and execute it if found,
982
  /* on /P check for the presence of AUTOEXEC.BAT and execute it if found,
981
   * but skip this check if /D was also passed */
983
   * but skip this check if /D was also passed */
982
  if ((cfg.flags & (FLAG_PERMANENT | FLAG_SKIP_AUTOEXEC)) == FLAG_PERMANENT) {
984
  if ((cfg.flags & (FLAG_PERMANENT | FLAG_SKIP_AUTOEXEC)) == FLAG_PERMANENT) {
983
    if (file_getattr("AUTOEXEC.BAT") >= 0) cfg.execcmd = "AUTOEXEC.BAT";
985
    if (file_getattr("AUTOEXEC.BAT") >= 0) cfg.execcmd = "AUTOEXEC.BAT";
984
  }
986
  }
985
 
987
 
986
  do {
988
  do {
987
 
989
 
988
    /* terminate previous command with a CR/LF if ECHO ON (but not during BAT processing) */
990
    /* terminate previous command with a CR/LF if ECHO ON (but not during BAT processing) */
989
    if ((rmod->flags & FLAG_ECHOFLAG) && (rmod->bat == NULL)) outputnl("");
991
    if ((rmod->flags & FLAG_ECHOFLAG) && (rmod->bat == NULL)) outputnl("");
990
 
992
 
991
    SKIP_NEWLINE:
993
    SKIP_NEWLINE:
992
 
994
 
993
    /* memory check */
995
    /* memory check */
994
    memguard_check(rmod->rmodseg, cmdlinebuf);
996
    memguard_check(rmod->rmodseg, cmdlinebuf);
995
 
997
 
996
    /* preset cmdline to point at the dedicated buffer */
998
    /* preset cmdline to point at the dedicated buffer */
997
    cmdline = cmdlinebuf;
999
    cmdline = cmdlinebuf;
998
 
1000
 
999
    /* (re)load translation strings if needed */
1001
    /* (re)load translation strings if needed */
1000
    nls_langreload(BUFFER, *rmod_envseg);
1002
    nls_langreload(BUFFER, *rmod_envseg);
1001
 
1003
 
1002
    /* am I inside a FOR loop? */
1004
    /* am I inside a FOR loop? */
1003
    if (rmod->forloop) {
1005
    if (rmod->forloop) {
1004
      if (forloop_process(cmdlinebuf, rmod->forloop) != 0) {
1006
      if (forloop_process(cmdlinebuf, rmod->forloop) != 0) {
1005
        rmod_ffree(rmod->forloop);
1007
        rmod_ffree(rmod->forloop);
1006
        rmod->forloop = NULL;
1008
        rmod->forloop = NULL;
1007
      } else {
1009
      } else {
1008
        /* output prompt and command on screen if echo on and command is not
1010
        /* output prompt and command on screen if echo on and command is not
1009
         * inhibiting it with the @ prefix */
1011
         * inhibiting it with the @ prefix */
1010
        if (rmod->flags & FLAG_ECHOFLAG) {
1012
        if (rmod->flags & FLAG_ECHOFLAG) {
1011
          build_and_display_prompt(BUFFER, *rmod_envseg);
1013
          build_and_display_prompt(BUFFER, *rmod_envseg);
1012
          outputnl(cmdline);
1014
          outputnl(cmdline);
1013
        }
1015
        }
1014
        /* jump to command processing */
1016
        /* jump to command processing */
1015
        goto EXEC_CMDLINE;
1017
        goto EXEC_CMDLINE;
1016
      }
1018
      }
1017
    }
1019
    }
1018
 
1020
 
1019
    /* load awaiting command, if any (used to run piped commands) */
1021
    /* load awaiting command, if any (used to run piped commands) */
1020
    if (rmod->awaitingcmd[0] != 0) {
1022
    if (rmod->awaitingcmd[0] != 0) {
1021
      _fstrcpy(cmdline, rmod->awaitingcmd);
1023
      _fstrcpy(cmdline, rmod->awaitingcmd);
1022
      rmod->awaitingcmd[0] = 0;
1024
      rmod->awaitingcmd[0] = 0;
1023
      flags |= DELETE_STDIN_FILE;
1025
      flags |= DELETE_STDIN_FILE;
1024
      goto EXEC_CMDLINE;
1026
      goto EXEC_CMDLINE;
1025
    } else {
1027
    } else {
1026
      flags &= ~DELETE_STDIN_FILE;
1028
      flags &= ~DELETE_STDIN_FILE;
1027
    }
1029
    }
1028
 
1030
 
1029
    /* skip user input if I have a command to exec (/C or /K or /P) */
1031
    /* skip user input if I have a command to exec (/C or /K or /P) */
1030
    if (cfg.execcmd != NULL) {
1032
    if (cfg.execcmd != NULL) {
1031
      cmdline = cfg.execcmd;
1033
      cmdline = cfg.execcmd;
1032
      cfg.execcmd = NULL;
1034
      cfg.execcmd = NULL;
1033
      /* */
1035
      /* */
1034
      if (cfg.flags & FLAG_STEPBYSTEP) flags |= FLAG_STEPBYSTEP;
1036
      if (cfg.flags & FLAG_STEPBYSTEP) flags |= FLAG_STEPBYSTEP;
1035
      goto EXEC_CMDLINE;
1037
      goto EXEC_CMDLINE;
1036
    }
1038
    }
1037
 
1039
 
1038
    /* if batch file is being executed -> fetch next line */
1040
    /* if batch file is being executed -> fetch next line */
1039
    if (rmod->bat != NULL) {
1041
    if (rmod->bat != NULL) {
1040
      if (getbatcmd(BUFFER, CMDLINE_MAXLEN, rmod) != 0) { /* end of batch */
1042
      if (getbatcmd(BUFFER, CMDLINE_MAXLEN, rmod) != 0) { /* end of batch */
1041
        struct batctx far *victim = rmod->bat;
1043
        struct batctx far *victim = rmod->bat;
1042
        rmod->bat = rmod->bat->parent;
1044
        rmod->bat = rmod->bat->parent;
1043
        rmod_ffree(victim);
1045
        rmod_ffree(victim);
1044
        /* end of batch? then restore echo flag as it was before running the (first) bat file */
1046
        /* end of batch? then restore echo flag as it was before running the (first) bat file */
1045
        if (rmod->bat == NULL) {
1047
        if (rmod->bat == NULL) {
1046
          rmod->flags &= ~FLAG_ECHOFLAG;
1048
          rmod->flags &= ~FLAG_ECHOFLAG;
1047
          if (rmod->flags & FLAG_ECHO_BEFORE_BAT) rmod->flags |= FLAG_ECHOFLAG;
1049
          if (rmod->flags & FLAG_ECHO_BEFORE_BAT) rmod->flags |= FLAG_ECHOFLAG;
1048
        }
1050
        }
1049
        continue;
1051
        continue;
1050
      }
1052
      }
1051
      /* %-decoding of variables (%PATH%, %1, %%...), result in cmdline */
1053
      /* %-decoding of variables (%PATH%, %1, %%...), result in cmdline */
1052
      batpercrepl(cmdline, CMDLINE_MAXLEN, BUFFER, rmod, *rmod_envseg);
1054
      batpercrepl(cmdline, CMDLINE_MAXLEN, BUFFER, rmod, *rmod_envseg);
1053
      /* skip any leading spaces */
1055
      /* skip any leading spaces */
1054
      while (*cmdline == ' ') cmdline++;
1056
      while (*cmdline == ' ') cmdline++;
1055
      /* skip batch labels */
1057
      /* skip batch labels */
1056
      if (*cmdline == ':') continue;
1058
      if (*cmdline == ':') continue;
1057
      /* step-by-step execution? */
1059
      /* step-by-step execution? */
1058
      if (rmod->bat->flags & FLAG_STEPBYSTEP) {
1060
      if (rmod->bat->flags & FLAG_STEPBYSTEP) {
1059
        if (*cmdline == 0) continue; /* skip empty lines */
1061
        if (*cmdline == 0) continue; /* skip empty lines */
1060
        if (askchoice(cmdline, svarlang_str(0,10)) != 0) continue;
1062
        if (askchoice(cmdline, svarlang_str(0,10)) != 0) continue;
1061
      }
1063
      }
1062
      /* output prompt and command on screen if echo on and command is not
1064
      /* output prompt and command on screen if echo on and command is not
1063
       * inhibiting it with the @ prefix */
1065
       * inhibiting it with the @ prefix */
1064
      if ((rmod->flags & FLAG_ECHOFLAG) && (cmdline[0] != '@')) {
1066
      if ((rmod->flags & FLAG_ECHOFLAG) && (cmdline[0] != '@')) {
1065
        build_and_display_prompt(BUFFER, *rmod_envseg);
1067
        build_and_display_prompt(BUFFER, *rmod_envseg);
1066
        outputnl(cmdline);
1068
        outputnl(cmdline);
1067
      }
1069
      }
1068
      /* skip the @ prefix if present, it is no longer useful */
1070
      /* skip the @ prefix if present, it is no longer useful */
1069
      if (cmdline[0] == '@') cmdline++;
1071
      if (cmdline[0] == '@') cmdline++;
1070
    } else {
1072
    } else {
1071
      unsigned char far *rmod_inputbuf = MK_FP(rmod->rmodseg, RMOD_OFFSET_INPUTBUF);
1073
      unsigned char far *rmod_inputbuf = MK_FP(rmod->rmodseg, RMOD_OFFSET_INPUTBUF);
1072
      /* invalidate input history if it appears to be damaged (could occur
1074
      /* invalidate input history if it appears to be damaged (could occur
1073
       * because of a stack overflow, for example if some stack-hungry TSR is
1075
       * because of a stack overflow, for example if some stack-hungry TSR is
1074
       * being used) */
1076
       * being used) */
1075
      if ((rmod_inputbuf[0] != 128) || (rmod_inputbuf[rmod_inputbuf[1] + 2] != '\r') || (rmod_inputbuf[rmod_inputbuf[1] + 3] != 0xCA) || (rmod_inputbuf[rmod_inputbuf[1] + 4] != 0xFE)) {
1077
      if ((rmod_inputbuf[0] != 128) || (rmod_inputbuf[rmod_inputbuf[1] + 2] != '\r') || (rmod_inputbuf[rmod_inputbuf[1] + 3] != 0xCA) || (rmod_inputbuf[rmod_inputbuf[1] + 4] != 0xFE)) {
1076
        rmod_inputbuf[0] = 128;  /* max allowed input length */
1078
        rmod_inputbuf[0] = 128;  /* max allowed input length */
1077
        rmod_inputbuf[1] = 0;    /* string len stored in buffer */
1079
        rmod_inputbuf[1] = 0;    /* string len stored in buffer */
1078
        rmod_inputbuf[2] = '\r'; /* string terminator */
1080
        rmod_inputbuf[2] = '\r'; /* string terminator */
1079
        rmod_inputbuf[3] = 0xCA; /* trailing signature */
1081
        rmod_inputbuf[3] = 0xCA; /* trailing signature */
1080
        rmod_inputbuf[4] = 0xFE; /* trailing signature */
1082
        rmod_inputbuf[4] = 0xFE; /* trailing signature */
1081
        nls_outputnl_err(2,2); /* "stack overflow detected, command history flushed" */
1083
        nls_outputnl_err(2,2); /* "stack overflow detected, command history flushed" */
1082
      }
1084
      }
1083
      /* interactive mode: display prompt (if echo enabled) and wait for user
1085
      /* interactive mode: display prompt (if echo enabled) and wait for user
1084
       * command line */
1086
       * command line */
1085
      if (rmod->flags & FLAG_ECHOFLAG) build_and_display_prompt(BUFFER, *rmod_envseg);
1087
      if (rmod->flags & FLAG_ECHOFLAG) build_and_display_prompt(BUFFER, *rmod_envseg);
1086
      /* collect user input */
1088
      /* collect user input */
1087
      cmdline_getinput(rmod->rmodseg, RMOD_OFFSET_INPUTBUF);
1089
      cmdline_getinput(rmod->rmodseg, RMOD_OFFSET_INPUTBUF);
1088
      /* append stack-overflow detection signature to the end of the input buffer */
1090
      /* append stack-overflow detection signature to the end of the input buffer */
1089
      rmod_inputbuf[rmod_inputbuf[1] + 3] = 0xCA; /* trailing signature */
1091
      rmod_inputbuf[rmod_inputbuf[1] + 3] = 0xCA; /* trailing signature */
1090
      rmod_inputbuf[rmod_inputbuf[1] + 4] = 0xFE; /* trailing signature */
1092
      rmod_inputbuf[rmod_inputbuf[1] + 4] = 0xFE; /* trailing signature */
1091
      /* copy it to local cmdline */
1093
      /* copy it to local cmdline */
1092
      if (rmod_inputbuf[1] != 0) _fmemcpy(cmdline, rmod_inputbuf + 2, rmod_inputbuf[1]);
1094
      if (rmod_inputbuf[1] != 0) _fmemcpy(cmdline, rmod_inputbuf + 2, rmod_inputbuf[1]);
1093
      cmdline[rmod_inputbuf[1]] = 0; /* zero-terminate local buff (original is '\r'-terminated) */
1095
      cmdline[rmod_inputbuf[1]] = 0; /* zero-terminate local buff (original is '\r'-terminated) */
1094
    }
1096
    }
1095
 
1097
 
1096
    /* if nothing entered, loop again (but without appending an extra CR/LF) */
1098
    /* if nothing entered, loop again (but without appending an extra CR/LF) */
1097
    if (cmdline[0] == 0) goto SKIP_NEWLINE;
1099
    if (cmdline[0] == 0) goto SKIP_NEWLINE;
1098
 
1100
 
1099
    /* I jump here when I need to exec an initial command (/C or /K) */
1101
    /* I jump here when I need to exec an initial command (/C or /K) */
1100
    EXEC_CMDLINE:
1102
    EXEC_CMDLINE:
1101
 
1103
 
1102
    /* move pointer forward to skip over any leading spaces */
1104
    /* move pointer forward to skip over any leading spaces */
1103
    while (*cmdline == ' ') cmdline++;
1105
    while (*cmdline == ' ') cmdline++;
1104
 
1106
 
1105
    /* sanitize separators into spaces */
1107
    /* sanitize separators into spaces */
1106
    for (i = 0; cmdline[i] != 0; i++) {
1108
    for (i = 0; cmdline[i] != 0; i++) {
1107
      switch (cmdline[i]) {
1109
      switch (cmdline[i]) {
1108
        case '\t':
1110
        case '\t':
1109
          cmdline[i] = ' ';
1111
          cmdline[i] = ' ';
1110
      }
1112
      }
1111
    }
1113
    }
1112
 
1114
 
1113
    /* update rmod's ptr to COMSPEC so it is always up to date */
1115
    /* update rmod's ptr to COMSPEC so it is always up to date */
1114
    rmod_updatecomspecptr(rmod->rmodseg, *rmod_envseg);
1116
    rmod_updatecomspecptr(rmod->rmodseg, *rmod_envseg);
1115
 
1117
 
1116
    /* handle redirections (if any) */
1118
    /* handle redirections (if any) */
1117
    i = redir_parsecmd(&redirprops, cmdline, rmod->awaitingcmd, *rmod_envseg);
1119
    i = redir_parsecmd(&redirprops, cmdline, rmod->awaitingcmd, *rmod_envseg);
1118
    if (i != 0) {
1120
    if (i != 0) {
1119
      nls_outputnl_doserr(i);
1121
      nls_outputnl_doserr(i);
1120
      rmod->awaitingcmd[0] = 0;
1122
      rmod->awaitingcmd[0] = 0;
1121
      continue;
1123
      continue;
1122
    }
1124
    }
1123
 
1125
 
1124
    /* try matching (and executing) an internal command */
1126
    /* try matching (and executing) an internal command */
1125
    cmdres = cmd_process(rmod, *rmod_envseg, cmdline, BUFFER, sizeof(BUFFER), &redirprops, flags & DELETE_STDIN_FILE);
1127
    cmdres = cmd_process(rmod, *rmod_envseg, cmdline, BUFFER, sizeof(BUFFER), &redirprops, flags & DELETE_STDIN_FILE);
1126
    if ((cmdres == CMD_OK) || (cmdres == CMD_FAIL)) {
1128
    if ((cmdres == CMD_OK) || (cmdres == CMD_FAIL)) {
1127
      /* internal command executed */
1129
      /* internal command executed */
1128
    } else if (cmdres == CMD_CHANGED) { /* cmdline changed, needs to be reprocessed */
1130
    } else if (cmdres == CMD_CHANGED) { /* cmdline changed, needs to be reprocessed */
1129
      goto EXEC_CMDLINE;
1131
      goto EXEC_CMDLINE;
1130
    } else if (cmdres == CMD_CHANGED_BY_CALL) { /* cmdline changed *specifically* by CALL */
1132
    } else if (cmdres == CMD_CHANGED_BY_CALL) { /* cmdline changed *specifically* by CALL */
1131
      /* the distinction is important since it changes the way batch files are processed */
1133
      /* the distinction is important since it changes the way batch files are processed */
1132
      flags |= CALL_FLAG;
1134
      flags |= CALL_FLAG;
1133
      goto EXEC_CMDLINE;
1135
      goto EXEC_CMDLINE;
1134
    } else if (cmdres == CMD_NOTFOUND) {
1136
    } else if (cmdres == CMD_NOTFOUND) {
1135
      /* this was not an internal command, try matching an external command */
1137
      /* this was not an internal command, try matching an external command */
1136
      run_as_external(BUFFER, cmdline, *rmod_envseg, rmod, &redirprops, flags);
1138
      run_as_external(BUFFER, cmdline, *rmod_envseg, rmod, &redirprops, flags);
1137
 
1139
 
1138
      /* is it a newly launched BAT file? */
1140
      /* is it a newly launched BAT file? */
1139
      if ((rmod->bat != NULL) && (rmod->bat->nextline == 0)) goto SKIP_NEWLINE;
1141
      if ((rmod->bat != NULL) && (rmod->bat->nextline == 0)) goto SKIP_NEWLINE;
1140
      /* run_as_external() does not return on success, if I am still alive then
1142
      /* run_as_external() does not return on success, if I am still alive then
1141
       * external command failed to execute */
1143
       * external command failed to execute */
1142
      nls_outputnl(0,5); /* "Bad command or file name" */
1144
      nls_outputnl(0,5); /* "Bad command or file name" */
1143
    } else {
1145
    } else {
1144
      /* I should never ever land here */
1146
      /* I should never ever land here */
1145
      outputnl("INTERNAL ERR: INVALID CMDRES");
1147
      outputnl("INTERNAL ERR: INVALID CMDRES");
1146
    }
1148
    }
1147
 
1149
 
1148
    /* reset one-time only flags */
1150
    /* reset one-time only flags */
1149
    flags &= ~CALL_FLAG;
1151
    flags &= ~CALL_FLAG;
1150
    flags &= ~FLAG_STEPBYSTEP;
1152
    flags &= ~FLAG_STEPBYSTEP;
1151
 
1153
 
1152
    /* repeat unless /C was asked - but always finish running an ongoing batch
1154
    /* repeat unless /C was asked - but always finish running an ongoing batch
1153
     * file (otherwise only first BAT command would be executed with /C) */
1155
     * file (otherwise only first BAT command would be executed with /C) */
1154
  } while (((rmod->flags & FLAG_EXEC_AND_QUIT) == 0) || (rmod->bat != NULL) || (rmod->forloop != NULL));
1156
  } while (((rmod->flags & FLAG_EXEC_AND_QUIT) == 0) || (rmod->bat != NULL) || (rmod->forloop != NULL));
1155
 
1157
 
1156
  sayonara(rmod);
1158
  sayonara(rmod);
1157
  return(0);
1159
  return(0);
1158
}
1160
}
1159
 
1161