Subversion Repositories SvarDOS

Rev

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

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