Subversion Repositories SvarDOS

Rev

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

Rev 533 Rev 538
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 Mateusz Viste
4
 * Copyright (C) 2021 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 <process.h>
31
#include <process.h>
32
 
32
 
33
#include "cmd.h"
33
#include "cmd.h"
34
#include "doserr.h"
-
 
35
#include "env.h"
34
#include "env.h"
36
#include "helpers.h"
35
#include "helpers.h"
37
#include "redir.h"
36
#include "redir.h"
38
#include "rmodinit.h"
37
#include "rmodinit.h"
39
#include "sayonara.h"
38
#include "sayonara.h"
40
 
39
 
41
#include "rmodcore.h" /* rmod binary inside a BUFFER array */
40
#include "rmodcore.h" /* rmod binary inside a BUFFER array */
42
 
41
 
43
struct config {
42
struct config {
44
  unsigned char flags; /* command.com flags, as defined in rmodinit.h */
43
  unsigned char flags; /* command.com flags, as defined in rmodinit.h */
45
  char *execcmd;
44
  char *execcmd;
46
  unsigned short envsiz;
45
  unsigned short envsiz;
47
};
46
};
48
 
47
 
49
/* max length of the cmdline storage (bytes) - includes also max length of
48
/* max length of the cmdline storage (bytes) - includes also max length of
50
 * line loaded from a BAT file (no more than 255 bytes!) */
49
 * line loaded from a BAT file (no more than 255 bytes!) */
51
#define CMDLINE_MAXLEN 255
50
#define CMDLINE_MAXLEN 255
52
 
51
 
53
 
52
 
54
/* sets guard values at a few places in memory for later detection of
53
/* sets guard values at a few places in memory for later detection of
55
 * overflows via memguard_check() */
54
 * overflows via memguard_check() */
56
static void memguard_set(char *cmdlinebuf) {
55
static void memguard_set(char *cmdlinebuf) {
57
  BUFFER[sizeof(BUFFER) - 1] = 0xC7;
56
  BUFFER[sizeof(BUFFER) - 1] = 0xC7;
58
  cmdlinebuf[CMDLINE_MAXLEN] = 0xC7;
57
  cmdlinebuf[CMDLINE_MAXLEN] = 0xC7;
59
}
58
}
60
 
59
 
61
 
60
 
62
/* checks for valguards at specific memory locations, returns 0 on success */
61
/* checks for valguards at specific memory locations, returns 0 on success */
63
static int memguard_check(unsigned short rmodseg, char *cmdlinebuf) {
62
static int memguard_check(unsigned short rmodseg, char *cmdlinebuf) {
64
  /* check RMOD signature (would be overwritten in case of stack overflow */
63
  /* check RMOD signature (would be overwritten in case of stack overflow */
65
  static char msg[] = "!! MEMORY CORRUPTION ## DETECTED !!";
64
  static char msg[] = "!! MEMORY CORRUPTION ## DETECTED !!";
66
  unsigned short far *rmodsig = MK_FP(rmodseg, 0x100 + 6);
65
  unsigned short far *rmodsig = MK_FP(rmodseg, 0x100 + 6);
67
  if (*rmodsig != 0x2019) {
66
  if (*rmodsig != 0x2019) {
68
    msg[22] = '1';
67
    msg[22] = '1';
69
    outputnl(msg);
68
    outputnl(msg);
70
    printf("rmodseg = %04X ; *rmodsig = %04X\r\n", rmodseg, *rmodsig);
69
    printf("rmodseg = %04X ; *rmodsig = %04X\r\n", rmodseg, *rmodsig);
71
    return(1);
70
    return(1);
72
  }
71
  }
73
  /* check last BUFFER byte */
72
  /* check last BUFFER byte */
74
  if (BUFFER[sizeof(BUFFER) - 1] != 0xC7) {
73
  if (BUFFER[sizeof(BUFFER) - 1] != 0xC7) {
75
    msg[22] = '2';
74
    msg[22] = '2';
76
    outputnl(msg);
75
    outputnl(msg);
77
    return(2);
76
    return(2);
78
  }
77
  }
79
  /* check last cmdlinebuf byte */
78
  /* check last cmdlinebuf byte */
80
  if (cmdlinebuf[CMDLINE_MAXLEN] != 0xC7) {
79
  if (cmdlinebuf[CMDLINE_MAXLEN] != 0xC7) {
81
    msg[22] = '3';
80
    msg[22] = '3';
82
    outputnl(msg);
81
    outputnl(msg);
83
    return(3);
82
    return(3);
84
  }
83
  }
85
  /* all good */
84
  /* all good */
86
  return(0);
85
  return(0);
87
}
86
}
88
 
87
 
89
 
88
 
90
/* parses command line the hard way (directly from PSP) */
89
/* parses command line the hard way (directly from PSP) */
91
static void parse_argv(struct config *cfg) {
90
static void parse_argv(struct config *cfg) {
92
  const unsigned char *cmdlinelen = (void *)0x80;
91
  const unsigned char *cmdlinelen = (void *)0x80;
93
  char *cmdline = (void *)0x81;
92
  char *cmdline = (void *)0x81;
94
 
93
 
95
  memset(cfg, 0, sizeof(*cfg));
94
  memset(cfg, 0, sizeof(*cfg));
96
 
95
 
97
  /* set a NULL terminator on cmdline */
96
  /* set a NULL terminator on cmdline */
98
  cmdline[*cmdlinelen] = 0;
97
  cmdline[*cmdlinelen] = 0;
99
 
98
 
100
  while (*cmdline != 0) {
99
  while (*cmdline != 0) {
101
 
100
 
102
    /* skip over any leading spaces */
101
    /* skip over any leading spaces */
103
    if (*cmdline == ' ') {
102
    if (*cmdline == ' ') {
104
      cmdline++;
103
      cmdline++;
105
      continue;
104
      continue;
106
    }
105
    }
107
 
106
 
108
    if (*cmdline != '/') {
107
    if (*cmdline != '/') {
109
      output("Invalid parameter: ");
108
      output("Invalid parameter: ");
110
      outputnl(cmdline);
109
      outputnl(cmdline);
111
      goto SKIP_TO_NEXT_ARG;
110
      goto SKIP_TO_NEXT_ARG;
112
    }
111
    }
113
 
112
 
114
    /* got a slash */
113
    /* got a slash */
115
    cmdline++;  /* skip the slash */
114
    cmdline++;  /* skip the slash */
116
    switch (*cmdline) {
115
    switch (*cmdline) {
117
      case 'c': /* /C = execute command and quit */
116
      case 'c': /* /C = execute command and quit */
118
      case 'C':
117
      case 'C':
119
        cfg->flags |= FLAG_EXEC_AND_QUIT;
118
        cfg->flags |= FLAG_EXEC_AND_QUIT;
120
        /* FALLTHRU */
119
        /* FALLTHRU */
121
      case 'k': /* /K = execute command and keep running */
120
      case 'k': /* /K = execute command and keep running */
122
      case 'K':
121
      case 'K':
123
        cfg->execcmd = cmdline + 1;
122
        cfg->execcmd = cmdline + 1;
124
        return;
123
        return;
125
 
124
 
126
      case 'd': /* /D = skip autoexec.bat processing */
125
      case 'd': /* /D = skip autoexec.bat processing */
127
      case 'D':
126
      case 'D':
128
        cfg->flags |= FLAG_SKIP_AUTOEXEC;
127
        cfg->flags |= FLAG_SKIP_AUTOEXEC;
129
        break;
128
        break;
130
 
129
 
131
      case 'e': /* preset the initial size of the environment block */
130
      case 'e': /* preset the initial size of the environment block */
132
      case 'E':
131
      case 'E':
133
        cmdline++;
132
        cmdline++;
134
        if (*cmdline == ':') cmdline++; /* could be /E:size */
133
        if (*cmdline == ':') cmdline++; /* could be /E:size */
135
        atous(&(cfg->envsiz), cmdline);
134
        atous(&(cfg->envsiz), cmdline);
136
        if (cfg->envsiz < 64) cfg->envsiz = 0;
135
        if (cfg->envsiz < 64) cfg->envsiz = 0;
137
        break;
136
        break;
138
 
137
 
139
      case 'p': /* permanent shell (can't exit + run autoexec.bat) */
138
      case 'p': /* permanent shell (can't exit + run autoexec.bat) */
140
      case 'P':
139
      case 'P':
141
        cfg->flags |= FLAG_PERMANENT;
140
        cfg->flags |= FLAG_PERMANENT;
142
        break;
141
        break;
143
 
142
 
144
      case '?':
143
      case '?':
145
        outputnl("Starts the SvarCOM command interpreter");
144
        outputnl("Starts the SvarCOM command interpreter");
146
        outputnl("");
145
        outputnl("");
147
        outputnl("COMMAND /E:nnn [/[C|K] [/P] [/D] command]");
146
        outputnl("COMMAND /E:nnn [/[C|K] [/P] [/D] command]");
148
        outputnl("");
147
        outputnl("");
149
        outputnl("/D      Skip AUTOEXEC.BAT processing (makes sense only with /P)");
148
        outputnl("/D      Skip AUTOEXEC.BAT processing (makes sense only with /P)");
150
        outputnl("/E:nnn  Sets the environment size to nnn bytes");
149
        outputnl("/E:nnn  Sets the environment size to nnn bytes");
151
        outputnl("/P      Makes the new command interpreter permanent and run AUTOEXEC.BAT");
150
        outputnl("/P      Makes the new command interpreter permanent and run AUTOEXEC.BAT");
152
        outputnl("/C      Executes the specified command and returns");
151
        outputnl("/C      Executes the specified command and returns");
153
        outputnl("/K      Executes the specified command and continues running");
152
        outputnl("/K      Executes the specified command and continues running");
154
        exit(1);
153
        exit(1);
155
        break;
154
        break;
156
 
155
 
157
      default:
156
      default:
158
        output("Invalid switch: /");
157
        output("Invalid switch: /");
159
        outputnl(cmdline);
158
        outputnl(cmdline);
160
        break;
159
        break;
161
    }
160
    }
162
 
161
 
163
    /* move to next argument or quit processing if end of cmdline */
162
    /* move to next argument or quit processing if end of cmdline */
164
    SKIP_TO_NEXT_ARG:
163
    SKIP_TO_NEXT_ARG:
165
    while ((*cmdline != 0) && (*cmdline != ' ') && (*cmdline != '/')) cmdline++;
164
    while ((*cmdline != 0) && (*cmdline != ' ') && (*cmdline != '/')) cmdline++;
166
  }
165
  }
167
}
166
}
168
 
167
 
169
 
168
 
170
/* builds the prompt string and displays it. buff is filled with a zero-terminated copy of the prompt. */
169
/* builds the prompt string and displays it. buff is filled with a zero-terminated copy of the prompt. */
171
static void build_and_display_prompt(char *buff, unsigned short envseg) {
170
static void build_and_display_prompt(char *buff, unsigned short envseg) {
172
  char *s = buff;
171
  char *s = buff;
173
  /* locate the prompt variable or use the default pattern */
172
  /* locate the prompt variable or use the default pattern */
174
  const char far *fmt = env_lookup_val(envseg, "PROMPT");
173
  const char far *fmt = env_lookup_val(envseg, "PROMPT");
175
  if ((fmt == NULL) || (*fmt == 0)) fmt = "$p$g"; /* fallback to default if empty */
174
  if ((fmt == NULL) || (*fmt == 0)) fmt = "$p$g"; /* fallback to default if empty */
176
  /* build the prompt string based on pattern */
175
  /* build the prompt string based on pattern */
177
  for (; *fmt != 0; fmt++) {
176
  for (; *fmt != 0; fmt++) {
178
    if (*fmt != '$') {
177
    if (*fmt != '$') {
179
      *s = *fmt;
178
      *s = *fmt;
180
      s++;
179
      s++;
181
      continue;
180
      continue;
182
    }
181
    }
183
    /* escape code ($P, etc) */
182
    /* escape code ($P, etc) */
184
    fmt++;
183
    fmt++;
185
    switch (*fmt) {
184
    switch (*fmt) {
186
      case 'Q':  /* $Q = = (equal sign) */
185
      case 'Q':  /* $Q = = (equal sign) */
187
      case 'q':
186
      case 'q':
188
        *s = '=';
187
        *s = '=';
189
        s++;
188
        s++;
190
        break;
189
        break;
191
      case '$':  /* $$ = $ (dollar sign) */
190
      case '$':  /* $$ = $ (dollar sign) */
192
        *s = '$';
191
        *s = '$';
193
        s++;
192
        s++;
194
        break;
193
        break;
195
      case 'T':  /* $t = current time */
194
      case 'T':  /* $t = current time */
196
      case 't':
195
      case 't':
197
        s += sprintf(s, "00:00"); /* TODO */
196
        s += sprintf(s, "00:00"); /* TODO */
198
        break;
197
        break;
199
      case 'D':  /* $D = current date */
198
      case 'D':  /* $D = current date */
200
      case 'd':
199
      case 'd':
201
        s += sprintf(s, "1985-07-29"); /* TODO */
200
        s += sprintf(s, "1985-07-29"); /* TODO */
202
        break;
201
        break;
203
      case 'P':  /* $P = current drive and path */
202
      case 'P':  /* $P = current drive and path */
204
      case 'p':
203
      case 'p':
205
        _asm {
204
        _asm {
206
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
205
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
207
          int 0x21
206
          int 0x21
208
          mov bx, s
207
          mov bx, s
209
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
208
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
210
        }
209
        }
211
        *s += 'A';
210
        *s += 'A';
212
        s++;
211
        s++;
213
        *s = ':';
212
        *s = ':';
214
        s++;
213
        s++;
215
        *s = '\\';
214
        *s = '\\';
216
        s++;
215
        s++;
217
        _asm {
216
        _asm {
218
          mov ah, 0x47    /* DOS 2+ - CWD - GET CURRENT DIRECTORY */
217
          mov ah, 0x47    /* DOS 2+ - CWD - GET CURRENT DIRECTORY */
219
          xor dl,dl       /* DL = drive number (00h = default, 01h = A:, etc) */
218
          xor dl,dl       /* DL = drive number (00h = default, 01h = A:, etc) */
220
          mov si, s       /* DS:SI -> 64-byte buffer for ASCIZ pathname */
219
          mov si, s       /* DS:SI -> 64-byte buffer for ASCIZ pathname */
221
          int 0x21
220
          int 0x21
222
          jc DONE         /* leave path empty on error */
221
          jc DONE         /* leave path empty on error */
223
          /* move s ptr forward to end (0-termintor) of pathname */
222
          /* move s ptr forward to end (0-termintor) of pathname */
224
          NEXTBYTE:
223
          NEXTBYTE:
225
          mov si, s
224
          mov si, s
226
          cmp byte ptr [si], 0
225
          cmp byte ptr [si], 0
227
          je DONE
226
          je DONE
228
          inc s
227
          inc s
229
          jmp NEXTBYTE
228
          jmp NEXTBYTE
230
          DONE:
229
          DONE:
231
        }
230
        }
232
        break;
231
        break;
233
      case 'V':  /* $V = DOS version number */
232
      case 'V':  /* $V = DOS version number */
234
      case 'v':
233
      case 'v':
235
        s += sprintf(s, "VER"); /* TODO */
234
        s += sprintf(s, "VER"); /* TODO */
236
        break;
235
        break;
237
      case 'N':  /* $N = current drive */
236
      case 'N':  /* $N = current drive */
238
      case 'n':
237
      case 'n':
239
        _asm {
238
        _asm {
240
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
239
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
241
          int 0x21
240
          int 0x21
242
          mov bx, s
241
          mov bx, s
243
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
242
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
244
        }
243
        }
245
        *s += 'A';
244
        *s += 'A';
246
        s++;
245
        s++;
247
        break;
246
        break;
248
      case 'G':  /* $G = > (greater-than sign) */
247
      case 'G':  /* $G = > (greater-than sign) */
249
      case 'g':
248
      case 'g':
250
        *s = '>';
249
        *s = '>';
251
        s++;
250
        s++;
252
        break;
251
        break;
253
      case 'L':  /* $L = < (less-than sign) */
252
      case 'L':  /* $L = < (less-than sign) */
254
      case 'l':
253
      case 'l':
255
        *s = '<';
254
        *s = '<';
256
        s++;
255
        s++;
257
        break;
256
        break;
258
      case 'B':  /* $B = | (pipe) */
257
      case 'B':  /* $B = | (pipe) */
259
      case 'b':
258
      case 'b':
260
        *s = '|';
259
        *s = '|';
261
        s++;
260
        s++;
262
        break;
261
        break;
263
      case 'H':  /* $H = backspace (erases previous character) */
262
      case 'H':  /* $H = backspace (erases previous character) */
264
      case 'h':
263
      case 'h':
265
        *s = '\b';
264
        *s = '\b';
266
        s++;
265
        s++;
267
        break;
266
        break;
268
      case 'E':  /* $E = Escape code (ASCII 27) */
267
      case 'E':  /* $E = Escape code (ASCII 27) */
269
      case 'e':
268
      case 'e':
270
        *s = 27;
269
        *s = 27;
271
        s++;
270
        s++;
272
        break;
271
        break;
273
      case '_':  /* $_ = CR+LF */
272
      case '_':  /* $_ = CR+LF */
274
        *s = '\r';
273
        *s = '\r';
275
        s++;
274
        s++;
276
        *s = '\n';
275
        *s = '\n';
277
        s++;
276
        s++;
278
        break;
277
        break;
279
    }
278
    }
280
  }
279
  }
281
  *s = 0;
280
  *s = 0;
282
  output(buff);
281
  output(buff);
283
}
282
}
284
 
283
 
285
 
284
 
286
/* tries locating executable fname in path and fill res with result. returns 0 on success,
285
/* tries locating executable fname in path and fill res with result. returns 0 on success,
287
 * -1 on failed match and -2 on failed match + "don't even try with other paths"
286
 * -1 on failed match and -2 on failed match + "don't even try with other paths"
288
 * extptr contains a ptr to the extension in fname (NULL if not found) */
287
 * extptr contains a ptr to the extension in fname (NULL if not found) */
289
static int lookup_cmd(char *res, const char *fname, const char *path, const char **extptr) {
288
static int lookup_cmd(char *res, const char *fname, const char *path, const char **extptr) {
290
  unsigned short lastbslash = 0;
289
  unsigned short lastbslash = 0;
291
  unsigned short i, len;
290
  unsigned short i, len;
292
  unsigned char explicitpath = 0;
291
  unsigned char explicitpath = 0;
293
 
292
 
294
  /* does the original fname has an explicit path prefix or explicit ext? */
293
  /* does the original fname has an explicit path prefix or explicit ext? */
295
  *extptr = NULL;
294
  *extptr = NULL;
296
  for (i = 0; fname[i] != 0; i++) {
295
  for (i = 0; fname[i] != 0; i++) {
297
    switch (fname[i]) {
296
    switch (fname[i]) {
298
      case ':':
297
      case ':':
299
      case '\\':
298
      case '\\':
300
        explicitpath = 1;
299
        explicitpath = 1;
301
        *extptr = NULL; /* extension is the last dot AFTER all path delimiters */
300
        *extptr = NULL; /* extension is the last dot AFTER all path delimiters */
302
        break;
301
        break;
303
      case '.':
302
      case '.':
304
        *extptr = fname + i + 1;
303
        *extptr = fname + i + 1;
305
        break;
304
        break;
306
    }
305
    }
307
  }
306
  }
308
 
307
 
309
  /* normalize filename */
308
  /* normalize filename */
310
  if (file_truename(fname, res) != 0) return(-2);
309
  if (file_truename(fname, res) != 0) return(-2);
311
 
310
 
312
  /* printf("truename: %s\r\n", res); */
311
  /* printf("truename: %s\r\n", res); */
313
 
312
 
314
  /* figure out where the command starts and if it has an explicit extension */
313
  /* figure out where the command starts and if it has an explicit extension */
315
  for (len = 0; res[len] != 0; len++) {
314
  for (len = 0; res[len] != 0; len++) {
316
    switch (res[len]) {
315
    switch (res[len]) {
317
      case '?':   /* abort on any wildcard character */
316
      case '?':   /* abort on any wildcard character */
318
      case '*':
317
      case '*':
319
        return(-2);
318
        return(-2);
320
      case '\\':
319
      case '\\':
321
        lastbslash = len;
320
        lastbslash = len;
322
        break;
321
        break;
323
    }
322
    }
324
  }
323
  }
325
 
324
 
326
  /* printf("lastbslash=%u\r\n", lastbslash); */
325
  /* printf("lastbslash=%u\r\n", lastbslash); */
327
 
326
 
328
  /* if no path prefix was found in fname (no colon or backslash) AND we have
327
  /* if no path prefix was found in fname (no colon or backslash) AND we have
329
   * a path arg, then assemble path+filename */
328
   * a path arg, then assemble path+filename */
330
  if ((!explicitpath) && (path != NULL) && (path[0] != 0)) {
329
  if ((!explicitpath) && (path != NULL) && (path[0] != 0)) {
331
    i = strlen(path);
330
    i = strlen(path);
332
    if (path[i - 1] != '\\') i++; /* add a byte for inserting a bkslash after path */
331
    if (path[i - 1] != '\\') i++; /* add a byte for inserting a bkslash after path */
333
    /* move the filename at the place where path will end */
332
    /* move the filename at the place where path will end */
334
    memmove(res + i, res + lastbslash + 1, len - lastbslash);
333
    memmove(res + i, res + lastbslash + 1, len - lastbslash);
335
    /* copy path in front of the filename and make sure there is a bkslash sep */
334
    /* copy path in front of the filename and make sure there is a bkslash sep */
336
    memmove(res, path, i);
335
    memmove(res, path, i);
337
    res[i - 1] = '\\';
336
    res[i - 1] = '\\';
338
  }
337
  }
339
 
338
 
340
  /* if no extension was initially provided, try matching COM, EXE, BAT */
339
  /* if no extension was initially provided, try matching COM, EXE, BAT */
341
  if (*extptr == NULL) {
340
  if (*extptr == NULL) {
342
    const char *ext[] = {".COM", ".EXE", ".BAT", NULL};
341
    const char *ext[] = {".COM", ".EXE", ".BAT", NULL};
343
    len = strlen(res);
342
    len = strlen(res);
344
    for (i = 0; ext[i] != NULL; i++) {
343
    for (i = 0; ext[i] != NULL; i++) {
345
      strcpy(res + len, ext[i]);
344
      strcpy(res + len, ext[i]);
346
      /* printf("? '%s'\r\n", res); */
345
      /* printf("? '%s'\r\n", res); */
347
      *extptr = ext[i] + 1;
346
      *extptr = ext[i] + 1;
348
      if (file_getattr(res) >= 0) return(0);
347
      if (file_getattr(res) >= 0) return(0);
349
    }
348
    }
350
  } else { /* try finding it as-is */
349
  } else { /* try finding it as-is */
351
    /* printf("? '%s'\r\n", res); */
350
    /* printf("? '%s'\r\n", res); */
352
    if (file_getattr(res) >= 0) return(0);
351
    if (file_getattr(res) >= 0) return(0);
353
  }
352
  }
354
 
353
 
355
  /* not found */
354
  /* not found */
356
  if (explicitpath) return(-2); /* don't bother trying other paths, the caller had its own path preset anyway */
355
  if (explicitpath) return(-2); /* don't bother trying other paths, the caller had its own path preset anyway */
357
  return(-1);
356
  return(-1);
358
}
357
}
359
 
358
 
360
 
359
 
361
static void run_as_external(char *buff, const char *cmdline, unsigned short envseg, struct rmod_props far *rmod, struct redir_data *redir) {
360
static void run_as_external(char *buff, const char *cmdline, unsigned short envseg, struct rmod_props far *rmod, struct redir_data *redir) {
362
  char *cmdfile = buff + 512;
361
  char *cmdfile = buff + 512;
363
  const char far *pathptr;
362
  const char far *pathptr;
364
  int lookup;
363
  int lookup;
365
  unsigned short i;
364
  unsigned short i;
366
  const char *ext;
365
  const char *ext;
367
  char *cmd = buff + 1024;
366
  char *cmd = buff + 1024;
368
  const char *cmdtail;
367
  const char *cmdtail;
369
  char far *rmod_execprog = MK_FP(rmod->rmodseg, RMOD_OFFSET_EXECPROG);
368
  char far *rmod_execprog = MK_FP(rmod->rmodseg, RMOD_OFFSET_EXECPROG);
370
  char far *rmod_cmdtail = MK_FP(rmod->rmodseg, 0x81);
369
  char far *rmod_cmdtail = MK_FP(rmod->rmodseg, 0x81);
371
  _Packed struct {
370
  _Packed struct {
372
    unsigned short envseg;
371
    unsigned short envseg;
373
    unsigned long cmdtail;
372
    unsigned long cmdtail;
374
    unsigned long fcb1;
373
    unsigned long fcb1;
375
    unsigned long fcb2;
374
    unsigned long fcb2;
376
  } far *ExecParam = MK_FP(rmod->rmodseg, RMOD_OFFSET_EXECPARAM);
375
  } far *ExecParam = MK_FP(rmod->rmodseg, RMOD_OFFSET_EXECPARAM);
377
 
376
 
378
  /* find cmd and cmdtail */
377
  /* find cmd and cmdtail */
379
  i = 0;
378
  i = 0;
380
  cmdtail = cmdline;
379
  cmdtail = cmdline;
381
  while (*cmdtail == ' ') cmdtail++; /* skip any leading spaces */
380
  while (*cmdtail == ' ') cmdtail++; /* skip any leading spaces */
382
  while ((*cmdtail != ' ') && (*cmdtail != '/') && (*cmdtail != '+') && (*cmdtail != 0)) {
381
  while ((*cmdtail != ' ') && (*cmdtail != '/') && (*cmdtail != '+') && (*cmdtail != 0)) {
383
    cmd[i++] = *cmdtail;
382
    cmd[i++] = *cmdtail;
384
    cmdtail++;
383
    cmdtail++;
385
  }
384
  }
386
  cmd[i] = 0;
385
  cmd[i] = 0;
387
 
386
 
388
  /* is this a command in curdir? */
387
  /* is this a command in curdir? */
389
  lookup = lookup_cmd(cmdfile, cmd, NULL, &ext);
388
  lookup = lookup_cmd(cmdfile, cmd, NULL, &ext);
390
  if (lookup == 0) {
389
  if (lookup == 0) {
391
    /* printf("FOUND LOCAL EXEC FILE: '%s'\r\n", cmdfile); */
390
    /* printf("FOUND LOCAL EXEC FILE: '%s'\r\n", cmdfile); */
392
    goto RUNCMDFILE;
391
    goto RUNCMDFILE;
393
  } else if (lookup == -2) {
392
  } else if (lookup == -2) {
394
    /* puts("NOT FOUND"); */
393
    /* puts("NOT FOUND"); */
395
    return;
394
    return;
396
  }
395
  }
397
 
396
 
398
  /* try matching something in PATH */
397
  /* try matching something in PATH */
399
  pathptr = env_lookup_val(envseg, "PATH");
398
  pathptr = env_lookup_val(envseg, "PATH");
400
  if (pathptr == NULL) return;
399
  if (pathptr == NULL) return;
401
 
400
 
402
  /* try each path in %PATH% */
401
  /* try each path in %PATH% */
403
  for (;;) {
402
  for (;;) {
404
    for (i = 0;; i++) {
403
    for (i = 0;; i++) {
405
      buff[i] = *pathptr;
404
      buff[i] = *pathptr;
406
      if ((buff[i] == 0) || (buff[i] == ';')) break;
405
      if ((buff[i] == 0) || (buff[i] == ';')) break;
407
      pathptr++;
406
      pathptr++;
408
    }
407
    }
409
    buff[i] = 0;
408
    buff[i] = 0;
410
    lookup = lookup_cmd(cmdfile, cmd, buff, &ext);
409
    lookup = lookup_cmd(cmdfile, cmd, buff, &ext);
411
    if (lookup == 0) break;
410
    if (lookup == 0) break;
412
    if (lookup == -2) return;
411
    if (lookup == -2) return;
413
    if (*pathptr == ';') {
412
    if (*pathptr == ';') {
414
      pathptr++;
413
      pathptr++;
415
    } else {
414
    } else {
416
      return;
415
      return;
417
    }
416
    }
418
  }
417
  }
419
 
418
 
420
  RUNCMDFILE:
419
  RUNCMDFILE:
421
 
420
 
422
  /* special handling of batch files */
421
  /* special handling of batch files */
423
  if ((ext != NULL) && (imatch(ext, "bat"))) {
422
  if ((ext != NULL) && (imatch(ext, "bat"))) {
424
    /* copy truename of the bat file to rmod buff */
423
    /* copy truename of the bat file to rmod buff */
425
    for (i = 0; cmdfile[i] != 0; i++) rmod->batfile[i] = cmdfile[i];
424
    for (i = 0; cmdfile[i] != 0; i++) rmod->batfile[i] = cmdfile[i];
426
    rmod->batfile[i] = 0;
425
    rmod->batfile[i] = 0;
427
 
426
 
428
    /* explode args of the bat file and store them in rmod buff */
427
    /* explode args of the bat file and store them in rmod buff */
429
    cmd_explode(buff, cmdline, NULL);
428
    cmd_explode(buff, cmdline, NULL);
430
    _fmemcpy(rmod->batargv, buff, sizeof(rmod->batargv));
429
    _fmemcpy(rmod->batargv, buff, sizeof(rmod->batargv));
431
 
430
 
432
    /* reset the 'next line to execute' counter */
431
    /* reset the 'next line to execute' counter */
433
    rmod->batnextline = 0;
432
    rmod->batnextline = 0;
434
    /* remember the echo flag (in case bat file disables echo) */
433
    /* remember the echo flag (in case bat file disables echo) */
435
    rmod->flags &= ~FLAG_ECHO_BEFORE_BAT;
434
    rmod->flags &= ~FLAG_ECHO_BEFORE_BAT;
436
    if (rmod->flags & FLAG_ECHOFLAG) rmod->flags |= FLAG_ECHO_BEFORE_BAT;
435
    if (rmod->flags & FLAG_ECHOFLAG) rmod->flags |= FLAG_ECHO_BEFORE_BAT;
437
    return;
436
    return;
438
  }
437
  }
439
 
438
 
440
  /* copy full filename to execute, along with redirected files (if any) */
439
  /* copy full filename to execute, along with redirected files (if any) */
441
  for (i = 0; cmdfile[i] != 0; i++) rmod_execprog[i] = cmdfile[i];
440
  for (i = 0; cmdfile[i] != 0; i++) rmod_execprog[i] = cmdfile[i];
442
  rmod_execprog[i++] = 0;
441
  rmod_execprog[i++] = 0;
443
  if (redir->stdinfile) {
442
  if (redir->stdinfile) {
444
    unsigned short far *farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDINFILE);
443
    unsigned short far *farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDINFILE);
445
    unsigned short t;
444
    unsigned short t;
446
    *farptr = i;
445
    *farptr = i;
447
    for (t = 0;; t++) {
446
    for (t = 0;; t++) {
448
      rmod_execprog[i++] = redir->stdinfile[t];
447
      rmod_execprog[i++] = redir->stdinfile[t];
449
      if (redir->stdinfile[t] == 0) break;
448
      if (redir->stdinfile[t] == 0) break;
450
    }
449
    }
451
  }
450
  }
452
  if (redir->stdoutfile) {
451
  if (redir->stdoutfile) {
453
    unsigned short far *farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDOUTFILE);
452
    unsigned short far *farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDOUTFILE);
454
    unsigned short t;
453
    unsigned short t;
455
    *farptr = i;
454
    *farptr = i;
456
    for (t = 0;; t++) {
455
    for (t = 0;; t++) {
457
      rmod_execprog[i++] = redir->stdoutfile[t];
456
      rmod_execprog[i++] = redir->stdoutfile[t];
458
      if (redir->stdoutfile[t] == 0) break;
457
      if (redir->stdoutfile[t] == 0) break;
459
    }
458
    }
460
    /* openflag */
459
    /* openflag */
461
    farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDOUTAPP);
460
    farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDOUTAPP);
462
    *farptr = redir->stdout_openflag;
461
    *farptr = redir->stdout_openflag;
463
  }
462
  }
464
 
463
 
465
  /* copy cmdtail to rmod's PSP and compute its len */
464
  /* copy cmdtail to rmod's PSP and compute its len */
466
  for (i = 0; cmdtail[i] != 0; i++) rmod_cmdtail[i] = cmdtail[i];
465
  for (i = 0; cmdtail[i] != 0; i++) rmod_cmdtail[i] = cmdtail[i];
467
  rmod_cmdtail[i] = '\r';
466
  rmod_cmdtail[i] = '\r';
468
  rmod_cmdtail[-1] = i;
467
  rmod_cmdtail[-1] = i;
469
 
468
 
470
  /* set up rmod to execute the command */
469
  /* set up rmod to execute the command */
471
 
470
 
472
  ExecParam->envseg = envseg;
471
  ExecParam->envseg = envseg;
473
  ExecParam->cmdtail = (unsigned long)MK_FP(rmod->rmodseg, 0x80); /* farptr, must be in PSP format (lenbyte args \r) */
472
  ExecParam->cmdtail = (unsigned long)MK_FP(rmod->rmodseg, 0x80); /* farptr, must be in PSP format (lenbyte args \r) */
474
  ExecParam->fcb1 = 0; /* TODO farptr */
473
  ExecParam->fcb1 = 0; /* TODO farptr */
475
  ExecParam->fcb2 = 0; /* TODO farptr */
474
  ExecParam->fcb2 = 0; /* TODO farptr */
476
  exit(0); /* let rmod do the job now */
475
  exit(0); /* let rmod do the job now */
477
}
476
}
478
 
477
 
479
 
478
 
480
static void set_comspec_to_self(unsigned short envseg) {
479
static void set_comspec_to_self(unsigned short envseg) {
481
  unsigned short *psp_envseg = (void *)(0x2c); /* pointer to my env segment field in the PSP */
480
  unsigned short *psp_envseg = (void *)(0x2c); /* pointer to my env segment field in the PSP */
482
  char far *myenv = MK_FP(*psp_envseg, 0);
481
  char far *myenv = MK_FP(*psp_envseg, 0);
483
  unsigned short varcount;
482
  unsigned short varcount;
484
  char buff[256] = "COMSPEC=";
483
  char buff[256] = "COMSPEC=";
485
  char *buffptr = buff + 8;
484
  char *buffptr = buff + 8;
486
  /* who am i? look into my own environment, at the end of it should be my EXEPATH string */
485
  /* who am i? look into my own environment, at the end of it should be my EXEPATH string */
487
  while (*myenv != 0) {
486
  while (*myenv != 0) {
488
    /* consume a NULL-terminated string */
487
    /* consume a NULL-terminated string */
489
    while (*myenv != 0) myenv++;
488
    while (*myenv != 0) myenv++;
490
    /* move to next string */
489
    /* move to next string */
491
    myenv++;
490
    myenv++;
492
  }
491
  }
493
  /* get next word, if 1 then EXEPATH follows */
492
  /* get next word, if 1 then EXEPATH follows */
494
  myenv++;
493
  myenv++;
495
  varcount = *myenv;
494
  varcount = *myenv;
496
  myenv++;
495
  myenv++;
497
  varcount |= (*myenv << 8);
496
  varcount |= (*myenv << 8);
498
  myenv++;
497
  myenv++;
499
  if (varcount != 1) return; /* NO EXEPATH FOUND */
498
  if (varcount != 1) return; /* NO EXEPATH FOUND */
500
  while (*myenv != 0) {
499
  while (*myenv != 0) {
501
    *buffptr = *myenv;
500
    *buffptr = *myenv;
502
    buffptr++;
501
    buffptr++;
503
    myenv++;
502
    myenv++;
504
  }
503
  }
505
  *buffptr = 0;
504
  *buffptr = 0;
506
  /* printf("EXEPATH: '%s'\r\n", buff); */
505
  /* printf("EXEPATH: '%s'\r\n", buff); */
507
  env_setvar(envseg, buff);
506
  env_setvar(envseg, buff);
508
}
507
}
509
 
508
 
510
 
509
 
511
/* wait for user input */
510
/* wait for user input */
512
static void cmdline_getinput(unsigned short inpseg, unsigned short inpoff) {
511
static void cmdline_getinput(unsigned short inpseg, unsigned short inpoff) {
513
  _asm {
512
  _asm {
514
    push ax
513
    push ax
515
    push bx
514
    push bx
516
    push cx
515
    push cx
517
    push dx
516
    push dx
518
    push ds
517
    push ds
519
 
518
 
520
    /* is DOSKEY support present? (INT 2Fh, AX=4800h, returns non-zero in AL if present) */
519
    /* is DOSKEY support present? (INT 2Fh, AX=4800h, returns non-zero in AL if present) */
521
    mov ax, 0x4800
520
    mov ax, 0x4800
522
    int 0x2f
521
    int 0x2f
523
    mov bl, al /* save doskey status in BL */
522
    mov bl, al /* save doskey status in BL */
524
 
523
 
525
    /* set up buffered input to inpseg:inpoff */
524
    /* set up buffered input to inpseg:inpoff */
526
    mov ax, inpseg
525
    mov ax, inpseg
527
    push ax
526
    push ax
528
    pop ds
527
    pop ds
529
    mov dx, inpoff
528
    mov dx, inpoff
530
 
529
 
531
    /* execute either DOS input or DOSKEY */
530
    /* execute either DOS input or DOSKEY */
532
    test bl, bl /* zf set if no DOSKEY present */
531
    test bl, bl /* zf set if no DOSKEY present */
533
    jnz DOSKEY
532
    jnz DOSKEY
534
 
533
 
535
    mov ah, 0x0a
534
    mov ah, 0x0a
536
    int 0x21
535
    int 0x21
537
    jmp short DONE
536
    jmp short DONE
538
 
537
 
539
    DOSKEY:
538
    DOSKEY:
540
    mov ax, 0x4810
539
    mov ax, 0x4810
541
    int 0x2f
540
    int 0x2f
542
 
541
 
543
    DONE:
542
    DONE:
544
    /* terminate command with a CR/LF */
543
    /* terminate command with a CR/LF */
545
    mov ah, 0x02 /* display character in dl */
544
    mov ah, 0x02 /* display character in dl */
546
    mov dl, 0x0d
545
    mov dl, 0x0d
547
    int 0x21
546
    int 0x21
548
    mov dl, 0x0a
547
    mov dl, 0x0a
549
    int 0x21
548
    int 0x21
550
 
549
 
551
    pop ds
550
    pop ds
552
    pop dx
551
    pop dx
553
    pop cx
552
    pop cx
554
    pop bx
553
    pop bx
555
    pop ax
554
    pop ax
556
  }
555
  }
557
}
556
}
558
 
557
 
559
 
558
 
560
/* fetches a line from batch file and write it to buff (NULL-terminated),
559
/* fetches a line from batch file and write it to buff (NULL-terminated),
561
 * increments rmod counter and returns 0 on success. */
560
 * increments rmod counter and returns 0 on success. */
562
static int getbatcmd(char *buff, unsigned char buffmaxlen, struct rmod_props far *rmod) {
561
static int getbatcmd(char *buff, unsigned char buffmaxlen, struct rmod_props far *rmod) {
563
  unsigned short i;
562
  unsigned short i;
564
  unsigned short batname_seg = FP_SEG(rmod->batfile);
563
  unsigned short batname_seg = FP_SEG(rmod->batfile);
565
  unsigned short batname_off = FP_OFF(rmod->batfile);
564
  unsigned short batname_off = FP_OFF(rmod->batfile);
566
  unsigned short filepos_cx = rmod->batnextline >> 16;
565
  unsigned short filepos_cx = rmod->batnextline >> 16;
567
  unsigned short filepos_dx = rmod->batnextline & 0xffff;
566
  unsigned short filepos_dx = rmod->batnextline & 0xffff;
568
  unsigned char blen = 0;
567
  unsigned char blen = 0;
569
  unsigned short errv = 0;
568
  unsigned short errv = 0;
570
 
569
 
571
  /* open file, jump to offset filpos, and read data into buff.
570
  /* open file, jump to offset filpos, and read data into buff.
572
   * result in blen (unchanged if EOF or failure). */
571
   * result in blen (unchanged if EOF or failure). */
573
  _asm {
572
  _asm {
574
    push ax
573
    push ax
575
    push bx
574
    push bx
576
    push cx
575
    push cx
577
    push dx
576
    push dx
578
 
577
 
579
    /* open file (read-only) */
578
    /* open file (read-only) */
580
    mov bx, 0xffff        /* preset BX to 0xffff to detect error conditions */
579
    mov bx, 0xffff        /* preset BX to 0xffff to detect error conditions */
581
    mov dx, batname_off
580
    mov dx, batname_off
582
    mov ax, batname_seg
581
    mov ax, batname_seg
583
    push ds     /* save DS */
582
    push ds     /* save DS */
584
    mov ds, ax
583
    mov ds, ax
585
    mov ax, 0x3d00
584
    mov ax, 0x3d00
586
    int 0x21    /* handle in ax on success */
585
    int 0x21    /* handle in ax on success */
587
    pop ds      /* restore DS */
586
    pop ds      /* restore DS */
588
    jc ERR
587
    jc ERR
589
    mov bx, ax  /* save handle to bx */
588
    mov bx, ax  /* save handle to bx */
590
 
589
 
591
    /* jump to file offset CX:DX */
590
    /* jump to file offset CX:DX */
592
    mov ax, 0x4200
591
    mov ax, 0x4200
593
    mov cx, filepos_cx
592
    mov cx, filepos_cx
594
    mov dx, filepos_dx
593
    mov dx, filepos_dx
595
    int 0x21  /* CF clear on success, DX:AX set to cur pos */
594
    int 0x21  /* CF clear on success, DX:AX set to cur pos */
596
    jc ERR
595
    jc ERR
597
 
596
 
598
    /* read the line into buff */
597
    /* read the line into buff */
599
    mov ah, 0x3f
598
    mov ah, 0x3f
600
    xor ch, ch
599
    xor ch, ch
601
    mov cl, buffmaxlen
600
    mov cl, buffmaxlen
602
    mov dx, buff
601
    mov dx, buff
603
    int 0x21 /* CF clear on success, AX=number of bytes read */
602
    int 0x21 /* CF clear on success, AX=number of bytes read */
604
    jc ERR
603
    jc ERR
605
    mov blen, al
604
    mov blen, al
606
    jmp CLOSEANDQUIT
605
    jmp CLOSEANDQUIT
607
 
606
 
608
    ERR:
607
    ERR:
609
    mov errv, ax
608
    mov errv, ax
610
 
609
 
611
    CLOSEANDQUIT:
610
    CLOSEANDQUIT:
612
    /* close file (if bx contains a handle) */
611
    /* close file (if bx contains a handle) */
613
    cmp bx, 0xffff
612
    cmp bx, 0xffff
614
    je DONE
613
    je DONE
615
    mov ah, 0x3e
614
    mov ah, 0x3e
616
    int 0x21
615
    int 0x21
617
 
616
 
618
    DONE:
617
    DONE:
619
    pop dx
618
    pop dx
620
    pop cx
619
    pop cx
621
    pop bx
620
    pop bx
622
    pop ax
621
    pop ax
623
  }
622
  }
624
 
623
 
625
  /* printf("blen=%u filepos_cx=%u filepos_dx=%u\r\n", blen, filepos_cx, filepos_dx); */
624
  /* printf("blen=%u filepos_cx=%u filepos_dx=%u\r\n", blen, filepos_cx, filepos_dx); */
626
 
625
 
627
  if (errv != 0) outputnl(doserr(errv));
626
  if (errv != 0) nls_outputnl_doserr(errv);
628
 
627
 
629
  /* on EOF - abort processing the bat file */
628
  /* on EOF - abort processing the bat file */
630
  if (blen == 0) goto OOPS;
629
  if (blen == 0) goto OOPS;
631
 
630
 
632
  /* find nearest \n to inc batch offset and replace \r by NULL terminator
631
  /* find nearest \n to inc batch offset and replace \r by NULL terminator
633
   * I support all CR/LF, CR- and LF-terminated batch files */
632
   * I support all CR/LF, CR- and LF-terminated batch files */
634
  for (i = 0; i < blen; i++) {
633
  for (i = 0; i < blen; i++) {
635
    if ((buff[i] == '\r') || (buff[i] == '\n')) {
634
    if ((buff[i] == '\r') || (buff[i] == '\n')) {
636
      if ((buff[i] == '\r') && ((i+1) < blen) && (buff[i+1] == '\n')) rmod->batnextline += 1;
635
      if ((buff[i] == '\r') && ((i+1) < blen) && (buff[i+1] == '\n')) rmod->batnextline += 1;
637
      break;
636
      break;
638
    }
637
    }
639
  }
638
  }
640
  buff[i] = 0;
639
  buff[i] = 0;
641
  rmod->batnextline += i + 1;
640
  rmod->batnextline += i + 1;
642
 
641
 
643
  return(0);
642
  return(0);
644
 
643
 
645
  OOPS:
644
  OOPS:
646
  rmod->batfile[0] = 0;
645
  rmod->batfile[0] = 0;
647
  rmod->batnextline = 0;
646
  rmod->batnextline = 0;
648
  return(-1);
647
  return(-1);
649
}
648
}
650
 
649
 
651
 
650
 
652
/* replaces %-variables in a BAT line with resolved values:
651
/* replaces %-variables in a BAT line with resolved values:
653
 * %PATH%       -> replaced by the contend of the PATH env variable
652
 * %PATH%       -> replaced by the contend of the PATH env variable
654
 * %UNDEFINED%  -> undefined variables are replaced by nothing ("")
653
 * %UNDEFINED%  -> undefined variables are replaced by nothing ("")
655
 * %NOTCLOSED   -> NOTCLOSED
654
 * %NOTCLOSED   -> NOTCLOSED
656
 * %1           -> first argument of the batch file (or nothing if no arg) */
655
 * %1           -> first argument of the batch file (or nothing if no arg) */
657
static void batpercrepl(char *res, unsigned short ressz, const char *line, const struct rmod_props far *rmod, unsigned short envseg) {
656
static void batpercrepl(char *res, unsigned short ressz, const char *line, const struct rmod_props far *rmod, unsigned short envseg) {
658
  unsigned short lastperc = 0xffff;
657
  unsigned short lastperc = 0xffff;
659
  unsigned short reslen = 0;
658
  unsigned short reslen = 0;
660
 
659
 
661
  if (ressz == 0) return;
660
  if (ressz == 0) return;
662
  ressz--; /* reserve one byte for the NULL terminator */
661
  ressz--; /* reserve one byte for the NULL terminator */
663
 
662
 
664
  for (; (reslen < ressz) && (*line != 0); line++) {
663
  for (; (reslen < ressz) && (*line != 0); line++) {
665
    /* if not a percent, I don't care */
664
    /* if not a percent, I don't care */
666
    if (*line != '%') {
665
    if (*line != '%') {
667
      res[reslen++] = *line;
666
      res[reslen++] = *line;
668
      continue;
667
      continue;
669
    }
668
    }
670
 
669
 
671
    /* *** perc char handling *** */
670
    /* *** perc char handling *** */
672
 
671
 
673
    /* closing perc? */
672
    /* closing perc? */
674
    if (lastperc != 0xffff) {
673
    if (lastperc != 0xffff) {
675
      /* %% is '%' */
674
      /* %% is '%' */
676
      if (lastperc == reslen) {
675
      if (lastperc == reslen) {
677
        res[reslen++] = '%';
676
        res[reslen++] = '%';
678
      } else {   /* otherwise variable name */
677
      } else {   /* otherwise variable name */
679
        const char far *ptr;
678
        const char far *ptr;
680
        res[reslen] = 0;
679
        res[reslen] = 0;
681
        reslen = lastperc;
680
        reslen = lastperc;
682
        ptr = env_lookup_val(envseg, res + reslen);
681
        ptr = env_lookup_val(envseg, res + reslen);
683
        if (ptr != NULL) {
682
        if (ptr != NULL) {
684
          while ((*ptr != 0) && (reslen < ressz)) {
683
          while ((*ptr != 0) && (reslen < ressz)) {
685
            res[reslen++] = *ptr;
684
            res[reslen++] = *ptr;
686
            ptr++;
685
            ptr++;
687
          }
686
          }
688
        }
687
        }
689
      }
688
      }
690
      lastperc = 0xffff;
689
      lastperc = 0xffff;
691
      continue;
690
      continue;
692
    }
691
    }
693
 
692
 
694
    /* digit? (bat arg) */
693
    /* digit? (bat arg) */
695
    if ((line[1] >= '0') && (line[1] <= '9')) {
694
    if ((line[1] >= '0') && (line[1] <= '9')) {
696
      unsigned short argid = line[1] - '0';
695
      unsigned short argid = line[1] - '0';
697
      unsigned short i;
696
      unsigned short i;
698
      const char far *argv = rmod->batargv;
697
      const char far *argv = rmod->batargv;
699
 
698
 
700
      /* locate the proper arg */
699
      /* locate the proper arg */
701
      for (i = 0; i != argid; i++) {
700
      for (i = 0; i != argid; i++) {
702
        /* if string is 0, then end of list reached */
701
        /* if string is 0, then end of list reached */
703
        if (*argv == 0) break;
702
        if (*argv == 0) break;
704
        /* jump to next arg */
703
        /* jump to next arg */
705
        while (*argv != 0) argv++;
704
        while (*argv != 0) argv++;
706
        argv++;
705
        argv++;
707
      }
706
      }
708
 
707
 
709
      /* copy the arg to result */
708
      /* copy the arg to result */
710
      for (i = 0; (argv[i] != 0) && (reslen < ressz); i++) {
709
      for (i = 0; (argv[i] != 0) && (reslen < ressz); i++) {
711
        res[reslen++] = argv[i];
710
        res[reslen++] = argv[i];
712
      }
711
      }
713
      line++;  /* skip the digit */
712
      line++;  /* skip the digit */
714
      continue;
713
      continue;
715
    }
714
    }
716
 
715
 
717
    /* opening perc */
716
    /* opening perc */
718
    lastperc = reslen;
717
    lastperc = reslen;
719
 
718
 
720
  }
719
  }
721
 
720
 
722
  res[reslen] = 0;
721
  res[reslen] = 0;
723
}
722
}
724
 
723
 
725
 
724
 
726
int main(void) {
725
int main(void) {
727
  static struct config cfg;
726
  static struct config cfg;
728
  static unsigned short far *rmod_envseg;
727
  static unsigned short far *rmod_envseg;
729
  static unsigned short far *lastexitcode;
728
  static unsigned short far *lastexitcode;
730
  static struct rmod_props far *rmod;
729
  static struct rmod_props far *rmod;
731
  static char cmdlinebuf[CMDLINE_MAXLEN + 2]; /* 1 extra byte for 0-terminator and another for memguard */
730
  static char cmdlinebuf[CMDLINE_MAXLEN + 2]; /* 1 extra byte for 0-terminator and another for memguard */
732
  static char *cmdline;
731
  static char *cmdline;
733
  static struct redir_data redirprops;
732
  static struct redir_data redirprops;
734
  static enum cmd_result cmdres;
733
  static enum cmd_result cmdres;
735
 
734
 
736
  rmod = rmod_find(BUFFER_len);
735
  rmod = rmod_find(BUFFER_len);
737
  if (rmod == NULL) {
736
  if (rmod == NULL) {
738
    /* look at command line parameters (in case env size if set there) */
737
    /* look at command line parameters (in case env size if set there) */
739
    parse_argv(&cfg);
738
    parse_argv(&cfg);
740
    rmod = rmod_install(cfg.envsiz, BUFFER, BUFFER_len);
739
    rmod = rmod_install(cfg.envsiz, BUFFER, BUFFER_len);
741
    if (rmod == NULL) {
740
    if (rmod == NULL) {
742
      outputnl("ERROR: rmod_install() failed");
741
      outputnl("ERROR: rmod_install() failed");
743
      return(1);
742
      return(1);
744
    }
743
    }
745
    /* copy flags to rmod's storage (and enable ECHO) */
744
    /* copy flags to rmod's storage (and enable ECHO) */
746
    rmod->flags = cfg.flags | FLAG_ECHOFLAG;
745
    rmod->flags = cfg.flags | FLAG_ECHOFLAG;
747
    /* printf("rmod installed at %Fp\r\n", rmod); */
746
    /* printf("rmod installed at %Fp\r\n", rmod); */
748
  } else {
747
  } else {
749
    /* printf("rmod found at %Fp\r\n", rmod); */
748
    /* printf("rmod found at %Fp\r\n", rmod); */
750
    /* if I was spawned by rmod and FLAG_EXEC_AND_QUIT is set, then I should
749
    /* if I was spawned by rmod and FLAG_EXEC_AND_QUIT is set, then I should
751
     * die asap, because the command has been executed already, so I no longer
750
     * die asap, because the command has been executed already, so I no longer
752
     * have a purpose in life */
751
     * have a purpose in life */
753
    if (rmod->flags & FLAG_EXEC_AND_QUIT) sayonara(rmod);
752
    if (rmod->flags & FLAG_EXEC_AND_QUIT) sayonara(rmod);
754
  }
753
  }
755
 
754
 
756
  /* install a few guardvals in memory to detect some cases of overflows */
755
  /* install a few guardvals in memory to detect some cases of overflows */
757
  memguard_set(cmdlinebuf);
756
  memguard_set(cmdlinebuf);
758
 
757
 
759
  rmod_envseg = MK_FP(rmod->rmodseg, RMOD_OFFSET_ENVSEG);
758
  rmod_envseg = MK_FP(rmod->rmodseg, RMOD_OFFSET_ENVSEG);
760
  lastexitcode = MK_FP(rmod->rmodseg, RMOD_OFFSET_LEXITCODE);
759
  lastexitcode = MK_FP(rmod->rmodseg, RMOD_OFFSET_LEXITCODE);
761
 
760
 
762
  /* make COMPSEC point to myself */
761
  /* make COMPSEC point to myself */
763
  set_comspec_to_self(*rmod_envseg);
762
  set_comspec_to_self(*rmod_envseg);
764
 
763
 
765
/*  {
764
/*  {
766
    unsigned short envsiz;
765
    unsigned short envsiz;
767
    unsigned short far *sizptr = MK_FP(*rmod_envseg - 1, 3);
766
    unsigned short far *sizptr = MK_FP(*rmod_envseg - 1, 3);
768
    envsiz = *sizptr;
767
    envsiz = *sizptr;
769
    envsiz *= 16;
768
    envsiz *= 16;
770
    printf("rmod_inpbuff at %04X:%04X, env_seg at %04X:0000 (env_size = %u bytes)\r\n", rmod->rmodseg, RMOD_OFFSET_INPBUFF, *rmod_envseg, envsiz);
769
    printf("rmod_inpbuff at %04X:%04X, env_seg at %04X:0000 (env_size = %u bytes)\r\n", rmod->rmodseg, RMOD_OFFSET_INPBUFF, *rmod_envseg, envsiz);
771
  }*/
770
  }*/
772
 
771
 
773
  /* on /P check for the presence of AUTOEXEC.BAT and execute it if found,
772
  /* on /P check for the presence of AUTOEXEC.BAT and execute it if found,
774
   * but skip this check if /D was also passed */
773
   * but skip this check if /D was also passed */
775
  if ((cfg.flags & (FLAG_PERMANENT | FLAG_SKIP_AUTOEXEC)) == FLAG_PERMANENT) {
774
  if ((cfg.flags & (FLAG_PERMANENT | FLAG_SKIP_AUTOEXEC)) == FLAG_PERMANENT) {
776
    if (file_getattr("AUTOEXEC.BAT") >= 0) cfg.execcmd = "AUTOEXEC.BAT";
775
    if (file_getattr("AUTOEXEC.BAT") >= 0) cfg.execcmd = "AUTOEXEC.BAT";
777
  }
776
  }
778
 
777
 
779
  do {
778
  do {
780
    /* terminate previous command with a CR/LF if ECHO ON (but not during BAT processing) */
779
    /* terminate previous command with a CR/LF if ECHO ON (but not during BAT processing) */
781
    if ((rmod->flags & FLAG_ECHOFLAG) && (rmod->batfile[0] == 0)) outputnl("");
780
    if ((rmod->flags & FLAG_ECHOFLAG) && (rmod->batfile[0] == 0)) outputnl("");
782
 
781
 
783
    SKIP_NEWLINE:
782
    SKIP_NEWLINE:
784
 
783
 
785
    /* cancel any redirections that may have been set up before */
784
    /* cancel any redirections that may have been set up before */
786
    redir_revert();
785
    redir_revert();
787
 
786
 
788
    /* memory check */
787
    /* memory check */
789
    memguard_check(rmod->rmodseg, cmdlinebuf);
788
    memguard_check(rmod->rmodseg, cmdlinebuf);
790
 
789
 
791
    /* preset cmdline to point at the dedicated buffer */
790
    /* preset cmdline to point at the dedicated buffer */
792
    cmdline = cmdlinebuf;
791
    cmdline = cmdlinebuf;
793
 
792
 
794
    /* (re)load translation strings if needed */
793
    /* (re)load translation strings if needed */
795
    nls_langreload(BUFFER, *rmod_envseg);
794
    nls_langreload(BUFFER, *rmod_envseg);
796
 
795
 
797
    /* skip user input if I have a command to exec (/C or /K) */
796
    /* skip user input if I have a command to exec (/C or /K) */
798
    if (cfg.execcmd != NULL) {
797
    if (cfg.execcmd != NULL) {
799
      cmdline = cfg.execcmd;
798
      cmdline = cfg.execcmd;
800
      cfg.execcmd = NULL;
799
      cfg.execcmd = NULL;
801
      goto EXEC_CMDLINE;
800
      goto EXEC_CMDLINE;
802
    }
801
    }
803
 
802
 
804
    /* if batch file is being executed -> fetch next line */
803
    /* if batch file is being executed -> fetch next line */
805
    if (rmod->batfile[0] != 0) {
804
    if (rmod->batfile[0] != 0) {
806
      if (getbatcmd(BUFFER, CMDLINE_MAXLEN, rmod) != 0) { /* end of batch */
805
      if (getbatcmd(BUFFER, CMDLINE_MAXLEN, rmod) != 0) { /* end of batch */
807
        /* restore echo flag as it was before running the bat file */
806
        /* restore echo flag as it was before running the bat file */
808
        rmod->flags &= ~FLAG_ECHOFLAG;
807
        rmod->flags &= ~FLAG_ECHOFLAG;
809
        if (rmod->flags & FLAG_ECHO_BEFORE_BAT) rmod->flags |= FLAG_ECHOFLAG;
808
        if (rmod->flags & FLAG_ECHO_BEFORE_BAT) rmod->flags |= FLAG_ECHOFLAG;
810
        continue;
809
        continue;
811
      }
810
      }
812
      /* %-decoding of variables (%PATH%, %1, %%...), result in cmdline */
811
      /* %-decoding of variables (%PATH%, %1, %%...), result in cmdline */
813
      batpercrepl(cmdline, CMDLINE_MAXLEN, BUFFER, rmod, *rmod_envseg);
812
      batpercrepl(cmdline, CMDLINE_MAXLEN, BUFFER, rmod, *rmod_envseg);
814
      /* skip any leading spaces */
813
      /* skip any leading spaces */
815
      while (*cmdline == ' ') cmdline++;
814
      while (*cmdline == ' ') cmdline++;
816
      /* output prompt and command on screen if echo on and command is not
815
      /* output prompt and command on screen if echo on and command is not
817
       * inhibiting it with the @ prefix */
816
       * inhibiting it with the @ prefix */
818
      if ((rmod->flags & FLAG_ECHOFLAG) && (cmdline[0] != '@')) {
817
      if ((rmod->flags & FLAG_ECHOFLAG) && (cmdline[0] != '@')) {
819
        build_and_display_prompt(BUFFER, *rmod_envseg);
818
        build_and_display_prompt(BUFFER, *rmod_envseg);
820
        outputnl(cmdline);
819
        outputnl(cmdline);
821
      }
820
      }
822
      /* skip the @ prefix if present, it is no longer useful */
821
      /* skip the @ prefix if present, it is no longer useful */
823
      if (cmdline[0] == '@') cmdline++;
822
      if (cmdline[0] == '@') cmdline++;
824
    } else {
823
    } else {
825
      /* interactive mode: display prompt (if echo enabled) and wait for user
824
      /* interactive mode: display prompt (if echo enabled) and wait for user
826
       * command line */
825
       * command line */
827
      if (rmod->flags & FLAG_ECHOFLAG) build_and_display_prompt(BUFFER, *rmod_envseg);
826
      if (rmod->flags & FLAG_ECHOFLAG) build_and_display_prompt(BUFFER, *rmod_envseg);
828
      /* collect user input */
827
      /* collect user input */
829
      cmdline_getinput(FP_SEG(rmod->inputbuf), FP_OFF(rmod->inputbuf));
828
      cmdline_getinput(FP_SEG(rmod->inputbuf), FP_OFF(rmod->inputbuf));
830
      /* copy it to local cmdline */
829
      /* copy it to local cmdline */
831
      if (rmod->inputbuf[1] != 0) _fmemcpy(cmdline, rmod->inputbuf + 2, rmod->inputbuf[1]);
830
      if (rmod->inputbuf[1] != 0) _fmemcpy(cmdline, rmod->inputbuf + 2, rmod->inputbuf[1]);
832
      cmdline[(unsigned)(rmod->inputbuf[1])] = 0; /* zero-terminate local buff (oriignal is '\r'-terminated) */
831
      cmdline[(unsigned)(rmod->inputbuf[1])] = 0; /* zero-terminate local buff (oriignal is '\r'-terminated) */
833
    }
832
    }
834
 
833
 
835
    /* if nothing entered, loop again (but without appending an extra CR/LF) */
834
    /* if nothing entered, loop again (but without appending an extra CR/LF) */
836
    if (cmdline[0] == 0) goto SKIP_NEWLINE;
835
    if (cmdline[0] == 0) goto SKIP_NEWLINE;
837
 
836
 
838
    /* I jump here when I need to exec an initial command (/C or /K) */
837
    /* I jump here when I need to exec an initial command (/C or /K) */
839
    EXEC_CMDLINE:
838
    EXEC_CMDLINE:
840
 
839
 
841
    /* move pointer forward to skip over any leading spaces */
840
    /* move pointer forward to skip over any leading spaces */
842
    while (*cmdline == ' ') cmdline++;
841
    while (*cmdline == ' ') cmdline++;
843
 
842
 
844
    /* update rmod's ptr to COMPSPEC so it is always up to date */
843
    /* update rmod's ptr to COMPSPEC so it is always up to date */
845
    rmod_updatecomspecptr(rmod->rmodseg, *rmod_envseg);
844
    rmod_updatecomspecptr(rmod->rmodseg, *rmod_envseg);
846
 
845
 
847
    /* handle redirections (if any) */
846
    /* handle redirections (if any) */
848
    redir_parsecmd(&redirprops, cmdline);
847
    redir_parsecmd(&redirprops, cmdline);
849
 
848
 
850
    /* try matching (and executing) an internal command */
849
    /* try matching (and executing) an internal command */
851
    cmdres = cmd_process(rmod, *rmod_envseg, cmdline, BUFFER, sizeof(BUFFER), &redirprops);
850
    cmdres = cmd_process(rmod, *rmod_envseg, cmdline, BUFFER, sizeof(BUFFER), &redirprops);
852
    if ((cmdres == CMD_OK) || (cmdres == CMD_FAIL)) {
851
    if ((cmdres == CMD_OK) || (cmdres == CMD_FAIL)) {
853
      /* internal command executed */
852
      /* internal command executed */
854
      continue;
853
      continue;
855
    } else if (cmdres == CMD_CHANGED) { /* cmdline changed, needs to be reprocessed */
854
    } else if (cmdres == CMD_CHANGED) { /* cmdline changed, needs to be reprocessed */
856
      goto EXEC_CMDLINE;
855
      goto EXEC_CMDLINE;
857
    } else if (cmdres == CMD_NOTFOUND) {
856
    } else if (cmdres == CMD_NOTFOUND) {
858
      /* this was not an internal command, try matching an external command */
857
      /* this was not an internal command, try matching an external command */
859
      run_as_external(BUFFER, cmdline, *rmod_envseg, rmod, &redirprops);
858
      run_as_external(BUFFER, cmdline, *rmod_envseg, rmod, &redirprops);
860
      /* perhaps this is a newly launched BAT file */
859
      /* perhaps this is a newly launched BAT file */
861
      if ((rmod->batfile[0] != 0) && (rmod->batnextline == 0)) goto SKIP_NEWLINE;
860
      if ((rmod->batfile[0] != 0) && (rmod->batnextline == 0)) goto SKIP_NEWLINE;
862
      /* run_as_external() does not return on success, if I am still alive then
861
      /* run_as_external() does not return on success, if I am still alive then
863
       * external command failed to execute */
862
       * external command failed to execute */
864
      outputnl("Bad command or file name");
863
      outputnl("Bad command or file name");
865
      continue;
864
      continue;
866
    }
865
    }
867
 
866
 
868
    /* I should never ever land here */
867
    /* I should never ever land here */
869
    outputnl("INTERNAL ERR: INVALID CMDRES");
868
    outputnl("INTERNAL ERR: INVALID CMDRES");
870
 
869
 
871
  } while ((rmod->flags & FLAG_EXEC_AND_QUIT) == 0);
870
  } while ((rmod->flags & FLAG_EXEC_AND_QUIT) == 0);
872
 
871
 
873
  sayonara(rmod);
872
  sayonara(rmod);
874
  return(0);
873
  return(0);
875
}
874
}
876
 
875