Subversion Repositories SvarDOS

Rev

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

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