Subversion Repositories SvarDOS

Rev

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

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