Subversion Repositories SvarDOS

Rev

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

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