Subversion Repositories SvarDOS

Rev

Rev 1797 | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

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