Subversion Repositories SvarDOS

Rev

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

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