Subversion Repositories SvarDOS

Rev

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

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