Subversion Repositories SvarDOS

Rev

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

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