Subversion Repositories SvarDOS

Rev

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

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