Subversion Repositories SvarDOS

Rev

Rev 2212 | Rev 2219 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
421 mateuszvis 1
/* This file is part of the SvarCOM project and is published under the terms
2
 * of the MIT license.
3
 *
1714 mateusz.vi 4
 * Copyright (C) 2021-2024 Mateusz Viste
421 mateuszvis 5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a
7
 * copy of this software and associated documentation files (the "Software"),
8
 * to deal in the Software without restriction, including without limitation
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
11
 * Software is furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
15
 *
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,
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
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
22
 * DEALINGS IN THE SOFTWARE.
23
 */
24
 
2213 mateusz.vi 25
#include <dos.h> /* MK_FP(), FP_SEG(), FP_OFF()... */
349 mateuszvis 26
#include <string.h>
27
 
1001 mateusz.vi 28
#include "svarlang.lib/svarlang.h"
349 mateuszvis 29
 
352 mateuszvis 30
#include "cmd.h"
366 mateuszvis 31
#include "env.h"
352 mateuszvis 32
#include "helpers.h"
402 mateuszvis 33
#include "redir.h"
351 mateuszvis 34
#include "rmodinit.h"
448 mateuszvis 35
#include "sayonara.h"
1822 mateusz.vi 36
#include "version.h"
349 mateuszvis 37
 
479 mateuszvis 38
#include "rmodcore.h" /* rmod binary inside a BUFFER array */
443 mateuszvis 39
 
572 mateuszvis 40
/* this version byte is used to tag RMOD so I can easily make sure that
41
 * the RMOD struct I find in memory is one that I know. Should the version
42
 * mismatch, then it would likely mean that SvarCOM has been upgraded and
43
 * RMOD should not be accessed as its structure might no longer be in sync
44
 * with what I think it is.
1715 mateusz.vi 45
 *          *** INCREMENT THIS AT EACH NEW SVARCOM RELEASE! ***
46
 *            (or at least whenever RMOD's struct is changed)            */
1987 mateusz.vi 47
#define BYTE_VERSION 7
572 mateuszvis 48
 
49
 
349 mateuszvis 50
struct config {
449 mateuszvis 51
  unsigned char flags; /* command.com flags, as defined in rmodinit.h */
443 mateuszvis 52
  char *execcmd;
410 mateuszvis 53
  unsigned short envsiz;
443 mateuszvis 54
};
349 mateuszvis 55
 
490 mateuszvis 56
/* max length of the cmdline storage (bytes) - includes also max length of
57
 * line loaded from a BAT file (no more than 255 bytes!) */
58
#define CMDLINE_MAXLEN 255
349 mateuszvis 59
 
490 mateuszvis 60
 
61
/* sets guard values at a few places in memory for later detection of
62
 * overflows via memguard_check() */
500 mateuszvis 63
static void memguard_set(char *cmdlinebuf) {
490 mateuszvis 64
  BUFFER[sizeof(BUFFER) - 1] = 0xC7;
500 mateuszvis 65
  cmdlinebuf[CMDLINE_MAXLEN] = 0xC7;
490 mateuszvis 66
}
67
 
68
 
69
/* checks for valguards at specific memory locations, returns 0 on success */
500 mateuszvis 70
static int memguard_check(unsigned short rmodseg, char *cmdlinebuf) {
490 mateuszvis 71
  /* check RMOD signature (would be overwritten in case of stack overflow */
72
  static char msg[] = "!! MEMORY CORRUPTION ## DETECTED !!";
73
  unsigned short far *rmodsig = MK_FP(rmodseg, 0x100 + 6);
548 mateuszvis 74
  unsigned char far *rmod = MK_FP(rmodseg, 0);
75
 
490 mateuszvis 76
  if (*rmodsig != 0x2019) {
77
    msg[22] = '1';
548 mateuszvis 78
    goto FAIL;
490 mateuszvis 79
  }
548 mateuszvis 80
 
500 mateuszvis 81
  /* check last BUFFER byte */
490 mateuszvis 82
  if (BUFFER[sizeof(BUFFER) - 1] != 0xC7) {
83
    msg[22] = '2';
548 mateuszvis 84
    goto FAIL;
490 mateuszvis 85
  }
548 mateuszvis 86
 
500 mateuszvis 87
  /* check last cmdlinebuf byte */
88
  if (cmdlinebuf[CMDLINE_MAXLEN] != 0xC7) {
490 mateuszvis 89
    msg[22] = '3';
548 mateuszvis 90
    goto FAIL;
490 mateuszvis 91
  }
548 mateuszvis 92
 
93
  /* check rmod exec buf */
94
  if (rmod[RMOD_OFFSET_EXECPROG + 127] != 0) {
95
    msg[22] = '4';
96
    goto FAIL;
97
  }
98
 
99
  /* check rmod exec stdin buf */
100
  if (rmod[RMOD_OFFSET_STDINFILE + 127] != 0) {
101
    msg[22] = '5';
102
    goto FAIL;
103
  }
104
 
105
  /* check rmod exec stdout buf */
106
  if (rmod[RMOD_OFFSET_STDOUTFILE + 127] != 0) {
107
    msg[22] = '6';
108
    goto FAIL;
109
  }
110
 
111
  /* else all good */
490 mateuszvis 112
  return(0);
548 mateuszvis 113
 
114
  /* error handling */
115
  FAIL:
116
  outputnl(msg);
117
  return(1);
490 mateuszvis 118
}
119
 
120
 
2212 mateusz.vi 121
/* exit do DOS with exist code */
122
static void EXIT(char code);
123
#pragma aux EXIT = \
124
"mov ah, 0x4c" \
125
"int 0x21" \
126
parm [al]
127
 
128
 
1840 mateusz.vi 129
/* DR-DOS specific boot processing: check for F5/F8 boot key presses and reset
130
 * the wild pointer to DR-DOS kernel (CONFIG.SYS) environment because it is not
131
 * allocated memory hence will be overwritten soon.
132
 * details: https://github.com/SvarDOS/edrdos/issues/83
133
 * this function returns 0, FLAG_SKIP_AUTOEXEC or FLAG_STEPBYSTEP */
1877 mateusz.vi 134
static void drdos_init(struct config *cfg) {
1840 mateusz.vi 135
  unsigned short kernenvseg = 0;
136
  unsigned char far *e;
137
  unsigned short far *scancode;
138
 
139
  /* If I am init then query kernel's private data via INT 21,4458 (DR-DOS).
140
   * On success (CF not set) ES:BX contains a pointer to the private data.
141
   * Segment of kernel's environment is at offset 12h. This environment may
142
   * be terminated by a 1Ah code followed by a boot key scan code to record
143
   * an F5 or F8 key press during boot time. */
144
  _asm {
145
    push ax
146
    push bx
147
    push es
148
 
149
    mov ax, 0x4458       /* DR-DOS 5+ get ptr to internal variable table */
150
    int 0x21             /* ES:BX contains the ptr to the var table */
151
    jc FAIL              /* not DR-DOS */
152
 
153
    add bx, 0x12
154
    mov ax, es:[bx]      /* read the segment of the kernel environment */
155
    mov kernenvseg, ax   /* save the kern env segment for later */
1906 mateusz.vi 156
    mov word ptr [es:bx], 0  /* reset the pointer to kernel env as done by DR COMMAND.COM */
1840 mateusz.vi 157
 
1861 mateusz.vi 158
    /* if DR-DOS env is at seg 0x60 then overwrite my own env in PSP with this.
159
     * Seg 0x60 is used since https://github.com/SvarDOS/edrdos/issues/88 and
160
     * it is safe to be used as it won't be overwritten */
161
    cmp ax, 0x60
1877 mateusz.vi 162
    jne FAIL
1861 mateusz.vi 163
    mov bx, 0x2C         /* environment segment field in my PSP */
164
    mov [bx], ax
165
 
1840 mateusz.vi 166
    FAIL:
167
    pop es
168
    pop bx
169
    pop ax
170
  }
171
 
1877 mateusz.vi 172
  if (kernenvseg == 0) return; /* either not DR-DOS, or kern env was read already or something failed */
1840 mateusz.vi 173
 
1877 mateusz.vi 174
  /* now I know that 1) I am running under (E)DR-DOS and 2) I am init, so /P implied */
175
  cfg->flags |= FLAG_PERMANENT;
176
 
177
  /* DR-DOS kernel environment present: make sure to ask SvarCOM to alloc its
178
   * own environment, because the kernel's environment might vanish eventually */
179
  if (cfg->envsiz < 256) cfg->envsiz = 256;
180
 
1840 mateusz.vi 181
  e = MK_FP(kernenvseg, 0);
182
 
183
/*
184
  printf("kernel env seg is at %04X and starts with bytes 0x%02X 0x%02X 0x%02X 0x%02X\r\n", kernenvseg, e[0], e[1], e[2], e[3]);
185
  {
186
    int i;
187
    printf("=== KERNEL ENV BEGINS ===\r\n");
188
    for (i = 0; i < 100; i++) {
189
      printf("%c", e[i]);
190
    }
191
    printf("\r\n=== KERNEL ENV ENDS, DUMP FOLLOWS ===\r\n");
192
    for (i = 0; i < 260; i++) {
193
      if ((i > 0) && ((i % 26) == 0)) printf("\r\n");
194
      printf("%02X ", e[i]);
195
    }
196
    printf("\r\n=== DUMP ENDS ===\r\n");
197
  }
198
*/
1952 mateusz.vi 199
 
200
  /* move forward until the DRDOS' environment 1Ah terminator is found */
201
  while (*e != 0x1A) e++;
202
  e++;
203
 
204
  /* next I have the boot key press scancode: either 0x0000, 0x3F00 or 0x4200
205
   * 0x3F00 means "F5 was pressed" while 0x4200 is for F8 */
206
  scancode = (void far *)e;
207
  if (*scancode == 0x3F00) {
208
    cfg->flags |= FLAG_SKIP_AUTOEXEC;
209
  } else if (*scancode == 0x4200) {
210
    cfg->flags |= FLAG_STEPBYSTEP;
211
  }
1840 mateusz.vi 212
}
213
 
214
 
443 mateuszvis 215
/* parses command line the hard way (directly from PSP) */
216
static void parse_argv(struct config *cfg) {
1715 mateusz.vi 217
  unsigned char *cmdlinelen = (void *)0x80;
491 mateuszvis 218
  char *cmdline = (void *)0x81;
443 mateuszvis 219
 
1715 mateusz.vi 220
  /* The arg tail at [81h] needs some care when being processed.
221
   *
222
   * Its length should be provided in [80h], but it is not always exact:
223
   * https://github.com/SvarDOS/bugz/issues/67
224
   *
225
   * The tail string itself is usually terminated by a CR character. But
226
   * sometimes it might be terminated by a nul. Or by nothing at all.
227
   *
228
   * The cautious approach is therefore to read the tail up until the length
229
   * mentionned at [80h] or to first CR or nul, whichever comes first.
230
   */
231
 
2213 mateusz.vi 232
  sv_bzero(cfg, sizeof(*cfg));
350 mateuszvis 233
 
1715 mateusz.vi 234
  /* Make sure that the advertised cmdline length is no more than 126 bytes
235
   * because the PSP ends at [0xff] and there ought to be at least 1 byte of
236
   * room for the CR-terminator.
237
   * According to Matthias Paul cmdlines longer than 126 (and even longer than
238
   * 127) might happen with some buggy implementations. */
239
  if (*cmdlinelen > 126) *cmdlinelen = 126;
443 mateuszvis 240
 
1715 mateusz.vi 241
  /* trim out any trailing CR garbage (see the issue 67 mentioned above) */
242
  while ((*cmdlinelen > 0) && (cmdline[*cmdlinelen - 1] == '\r')) (*cmdlinelen)--;
243
 
244
  /* normalize the cmd so it is nul-terminated - this is expected later in a
245
   * few places in the codeflow, among others in run_as_external() */
246
  cmdline[*cmdlinelen] = 0;
247
 
248
  /* process the parameters given to COMMAND.COM */
249
  while (*cmdline != 0) {
250
 
443 mateuszvis 251
    /* skip over any leading spaces */
491 mateuszvis 252
    if (*cmdline == ' ') {
253
      cmdline++;
254
      continue;
349 mateuszvis 255
    }
443 mateuszvis 256
 
491 mateuszvis 257
    if (*cmdline != '/') {
989 mateusz.vi 258
      nls_output(0,6); /* "Invalid parameter" */
259
      output(": ");
491 mateuszvis 260
      outputnl(cmdline);
261
      goto SKIP_TO_NEXT_ARG;
262
    }
443 mateuszvis 263
 
491 mateuszvis 264
    /* got a slash */
265
    cmdline++;  /* skip the slash */
266
    switch (*cmdline) {
267
      case 'c': /* /C = execute command and quit */
268
      case 'C':
269
        cfg->flags |= FLAG_EXEC_AND_QUIT;
270
        /* FALLTHRU */
271
      case 'k': /* /K = execute command and keep running */
272
      case 'K':
1001 mateusz.vi 273
        cmdline++;
274
        cfg->execcmd = cmdline;
1715 mateusz.vi 275
        return; /* further arguments are for the executed program, not for me */
443 mateuszvis 276
 
1001 mateusz.vi 277
      case 'y': /* /Y = execute batch file step-by-step (with /P, /K or /C) */
278
      case 'Y':
279
        cfg->flags |= FLAG_STEPBYSTEP;
280
        break;
281
 
494 mateuszvis 282
      case 'd': /* /D = skip autoexec.bat processing */
283
      case 'D':
284
        cfg->flags |= FLAG_SKIP_AUTOEXEC;
285
        break;
286
 
491 mateuszvis 287
      case 'e': /* preset the initial size of the environment block */
288
      case 'E':
289
        cmdline++;
290
        if (*cmdline == ':') cmdline++; /* could be /E:size */
291
        atous(&(cfg->envsiz), cmdline);
292
        if (cfg->envsiz < 64) cfg->envsiz = 0;
293
        break;
449 mateuszvis 294
 
491 mateuszvis 295
      case 'p': /* permanent shell (can't exit + run autoexec.bat) */
296
      case 'P':
297
        cfg->flags |= FLAG_PERMANENT;
298
        break;
444 mateuszvis 299
 
491 mateuszvis 300
      case '?':
989 mateusz.vi 301
        nls_outputnl(1,0); /* "Starts the SvarCOM command interpreter" */
491 mateuszvis 302
        outputnl("");
989 mateusz.vi 303
        nls_outputnl(1,1); /* "COMMAND /E:nnn [/[C|K] [/P] [/D] command]" */
491 mateuszvis 304
        outputnl("");
989 mateusz.vi 305
        nls_outputnl(1,2); /* "/D      Skip AUTOEXEC.BAT processing (makes sense only with /P)" */
306
        nls_outputnl(1,3); /* "/E:nnn  Sets the environment size to nnn bytes" */
307
        nls_outputnl(1,4); /* "/P      Makes the new command interpreter permanent and run AUTOEXEC.BAT" */
308
        nls_outputnl(1,5); /* "/C      Executes the specified command and returns" */
309
        nls_outputnl(1,6); /* "/K      Executes the specified command and continues running" */
1001 mateusz.vi 310
        nls_outputnl(1,7); /* "/Y      Executes the batch program step by step" */
2212 mateusz.vi 311
        EXIT(1);
491 mateuszvis 312
        break;
313
 
314
      default:
989 mateusz.vi 315
        nls_output(0,2); /* invalid switch */
316
        output(": /");
491 mateuszvis 317
        outputnl(cmdline);
318
        break;
350 mateuszvis 319
    }
443 mateuszvis 320
 
321
    /* move to next argument or quit processing if end of cmdline */
491 mateuszvis 322
    SKIP_TO_NEXT_ARG:
1715 mateusz.vi 323
    while ((*cmdline != 0) && (*cmdline != ' ') && (*cmdline != '/')) cmdline++;
349 mateuszvis 324
  }
325
}
326
 
327
 
1798 mateusz.vi 328
/* returns current DOS drive (0 = A: ; 1 = B: etc) */
329
static unsigned char _dosgetcurdrive(void);
330
#pragma aux _dosgetcurdrive = \
331
"mov ah, 0x19"    /* DOS 1+ - GET CURRENT DRIVE */ \
332
"int 0x21" \
333
modify [ah] \
334
value [al]
335
 
336
 
337
static void _dosgetcurdir(char near *s);
338
#pragma aux _dosgetcurdir = \
339
"mov ah, 0x47"    /* DOS 2+ - CWD - GET CURRENT DIRECTORY */ \
340
"xor dl, dl"      /* DL = drive number (00h = default, 01h = A:, etc) */ \
1865 mateusz.vi 341
"mov [si], 0"     /* set empty dir in case of failure (unformatted floppy) */ \
1798 mateusz.vi 342
"int 0x21" \
343
parm [si] \
344
modify [ax dl]
345
 
346
 
474 mateuszvis 347
/* builds the prompt string and displays it. buff is filled with a zero-terminated copy of the prompt. */
348
static void build_and_display_prompt(char *buff, unsigned short envseg) {
349
  char *s = buff;
1823 mateusz.vi 350
 
370 mateuszvis 351
  /* locate the prompt variable or use the default pattern */
438 mateuszvis 352
  const char far *fmt = env_lookup_val(envseg, "PROMPT");
370 mateuszvis 353
  if ((fmt == NULL) || (*fmt == 0)) fmt = "$p$g"; /* fallback to default if empty */
1823 mateusz.vi 354
 
370 mateuszvis 355
  /* build the prompt string based on pattern */
354 mateuszvis 356
  for (; *fmt != 0; fmt++) {
357
    if (*fmt != '$') {
358
      *s = *fmt;
359
      s++;
360
      continue;
361
    }
362
    /* escape code ($P, etc) */
363
    fmt++;
364
    switch (*fmt) {
365
      case 'Q':  /* $Q = = (equal sign) */
366
      case 'q':
367
        *s = '=';
368
        s++;
369
        break;
370
      case '$':  /* $$ = $ (dollar sign) */
371
        *s = '$';
372
        s++;
373
        break;
374
      case 'T':  /* $t = current time */
375
      case 't':
1823 mateusz.vi 376
      {
377
        struct nls_patterns nls;
378
        unsigned char h, m, sec;
379
        if (nls_getpatterns(&nls) != 0) {
2213 mateusz.vi 380
          s += sv_strcpy(s, "ERR");
1823 mateusz.vi 381
        } else {
382
          dos_get_time(&h, &m, &sec);
383
          s += nls_format_time(s, h, m, sec, &nls);
384
        }
354 mateuszvis 385
        break;
1823 mateusz.vi 386
      }
354 mateuszvis 387
      case 'D':  /* $D = current date */
388
      case 'd':
1823 mateusz.vi 389
      {
390
        struct nls_patterns nls;
391
        unsigned short y;
392
        unsigned char m, d;
393
        if (nls_getpatterns(&nls) != 0) {
2213 mateusz.vi 394
          s += sv_strcpy(s, "ERR");
1823 mateusz.vi 395
        } else {
396
          dos_get_date(&y, &m, &d);
397
          s += nls_format_date(s, y, m, d, &nls);
398
        }
354 mateuszvis 399
        break;
1823 mateusz.vi 400
      }
354 mateuszvis 401
      case 'P':  /* $P = current drive and path */
402
      case 'p':
1798 mateusz.vi 403
        *s = _dosgetcurdrive() + 'A';
354 mateuszvis 404
        s++;
405
        *s = ':';
406
        s++;
407
        *s = '\\';
408
        s++;
1798 mateusz.vi 409
        _dosgetcurdir(s);
410
        /* move s ptr forward to end (0-termintor) of pathname */
411
        while (*s != 0) s++;
354 mateuszvis 412
        break;
1822 mateusz.vi 413
      case 'V':  /* $V = version number */
354 mateuszvis 414
      case 'v':
2213 mateusz.vi 415
        s += sv_strcpy(s, PVER);
354 mateuszvis 416
        break;
417
      case 'N':  /* $N = current drive */
418
      case 'n':
1798 mateusz.vi 419
        *s = _dosgetcurdrive() + 'A';
354 mateuszvis 420
        s++;
421
        break;
422
      case 'G':  /* $G = > (greater-than sign) */
423
      case 'g':
424
        *s = '>';
425
        s++;
426
        break;
427
      case 'L':  /* $L = < (less-than sign) */
428
      case 'l':
429
        *s = '<';
430
        s++;
431
        break;
432
      case 'B':  /* $B = | (pipe) */
433
      case 'b':
434
        *s = '|';
435
        s++;
436
        break;
437
      case 'H':  /* $H = backspace (erases previous character) */
438
      case 'h':
439
        *s = '\b';
440
        s++;
441
        break;
442
      case 'E':  /* $E = Escape code (ASCII 27) */
443
      case 'e':
444
        *s = 27;
445
        s++;
446
        break;
447
      case '_':  /* $_ = CR+LF */
448
        *s = '\r';
449
        s++;
450
        *s = '\n';
451
        s++;
452
        break;
453
    }
454
  }
474 mateuszvis 455
  *s = 0;
456
  output(buff);
354 mateuszvis 457
}
349 mateuszvis 458
 
459
 
1797 mateusz.vi 460
static void dos_fname2fcb(char far *fcb, const char near *cmd);
461
#pragma aux dos_fname2fcb = \
462
"mov ax, 0x2900"   /* DOS 1+ - parse filename into FCB (DS:SI=fname, ES:DI=FCB) */ \
463
"int 0x21" \
464
parm [es di] [si] \
465
modify [ax si]
1156 mateusz.vi 466
 
467
 
468
/* parses cmdtail and fills fcb1 and fcb2 with first and second arguments,
469
 * respectively. an FCB is 12 bytes long:
470
 * drive (0=default, 1=A, 2=B, etc)
471
 * fname (8 chars, blank-padded)
472
 * fext (3 chars, blank-padded) */
473
static void cmdtail_to_fcb(char far *fcb1, char far *fcb2, const char *cmdtail) {
474
 
475
  /* skip any leading spaces */
476
  while (*cmdtail == ' ') cmdtail++;
477
 
478
  /* convert first arg */
479
  dos_fname2fcb(fcb1, cmdtail);
480
 
481
  /* skip to next arg */
482
  while ((*cmdtail != ' ') && (*cmdtail != 0)) cmdtail++;
483
  while (*cmdtail == ' ') cmdtail++;
484
 
485
  /* convert second arg */
486
  dos_fname2fcb(fcb2, cmdtail);
487
}
488
 
489
 
957 mateusz.vi 490
/* a few internal flags */
491
#define DELETE_STDIN_FILE 1
492
#define CALL_FLAG         2
1730 mateusz.vi 493
#define LOADHIGH_FLAG     4
957 mateusz.vi 494
 
495
static void run_as_external(char *buff, const char *cmdline, unsigned short envseg, struct rmod_props far *rmod, struct redir_data *redir, unsigned char flags) {
472 mateuszvis 496
  char *cmdfile = buff + 512;
458 mateuszvis 497
  const char far *pathptr;
498
  int lookup;
499
  unsigned short i;
500
  const char *ext;
508 mateuszvis 501
  char *cmd = buff + 1024;
479 mateuszvis 502
  const char *cmdtail;
461 mateuszvis 503
  char far *rmod_execprog = MK_FP(rmod->rmodseg, RMOD_OFFSET_EXECPROG);
504
  char far *rmod_cmdtail = MK_FP(rmod->rmodseg, 0x81);
505
  _Packed struct {
506
    unsigned short envseg;
507
    unsigned long cmdtail;
508
    unsigned long fcb1;
509
    unsigned long fcb2;
510
  } far *ExecParam = MK_FP(rmod->rmodseg, RMOD_OFFSET_EXECPARAM);
364 mateuszvis 511
 
472 mateuszvis 512
  /* find cmd and cmdtail */
513
  i = 0;
514
  cmdtail = cmdline;
515
  while (*cmdtail == ' ') cmdtail++; /* skip any leading spaces */
516
  while ((*cmdtail != ' ') && (*cmdtail != '/') && (*cmdtail != '+') && (*cmdtail != 0)) {
517
    cmd[i++] = *cmdtail;
518
    cmdtail++;
519
  }
520
  cmd[i] = 0;
364 mateuszvis 521
 
458 mateuszvis 522
  /* is this a command in curdir? */
472 mateuszvis 523
  lookup = lookup_cmd(cmdfile, cmd, NULL, &ext);
458 mateuszvis 524
  if (lookup == 0) {
525
    /* printf("FOUND LOCAL EXEC FILE: '%s'\r\n", cmdfile); */
526
    goto RUNCMDFILE;
527
  } else if (lookup == -2) {
528
    /* puts("NOT FOUND"); */
529
    return;
530
  }
531
 
532
  /* try matching something in PATH */
533
  pathptr = env_lookup_val(envseg, "PATH");
534
 
535
  /* try each path in %PATH% */
571 mateuszvis 536
  while (pathptr) {
458 mateuszvis 537
    for (i = 0;; i++) {
538
      buff[i] = *pathptr;
539
      if ((buff[i] == 0) || (buff[i] == ';')) break;
540
      pathptr++;
541
    }
542
    buff[i] = 0;
472 mateuszvis 543
    lookup = lookup_cmd(cmdfile, cmd, buff, &ext);
571 mateuszvis 544
    if (lookup == 0) goto RUNCMDFILE;
458 mateuszvis 545
    if (lookup == -2) return;
546
    if (*pathptr == ';') {
547
      pathptr++;
548
    } else {
571 mateuszvis 549
      break;
458 mateuszvis 550
    }
551
  }
552
 
571 mateuszvis 553
  /* last chance: is it an executable link? (trim extension from cmd first) */
554
  for (i = 0; (cmd[i] != 0) && (cmd[i] != '.') && (i < 9); i++) buff[128 + i] = cmd[i];
555
  buff[128 + i] = 0;
556
  if ((i < 9) && (link_computefname(buff, buff + 128, envseg) == 0)) {
557
    /* try opening the link file (if it exists) and read it into buff */
558
    i = 0;
559
    _asm {
560
      push ax
561
      push bx
562
      push cx
563
      push dx
564
 
565
      mov ax, 0x3d00  /* DOS 2+ - OPEN EXISTING FILE, READ-ONLY */
566
      mov dx, buff    /* file name */
567
      int 0x21
568
      jc ERR_FOPEN
569
      /* file handle in AX, read from file now */
570
      mov bx, ax      /* file handle */
571
      mov ah, 0x3f    /* Read from file via handle bx */
572
      mov cx, 128     /* up to 128 bytes */
573
      /* mov dx, buff */ /* dest buffer (already set) */
574
      int 0x21        /* read up to 256 bytes from file and write to buff */
575
      jc ERR_READ
576
      mov i, ax
577
      ERR_READ:
578
      mov ah, 0x3e    /* close file handle in BX */
579
      int 0x21
580
      ERR_FOPEN:
581
 
582
      pop dx
583
      pop cx
584
      pop bx
585
      pop ax
586
    }
587
 
588
    /* did I read anything? */
589
    if (i != 0) {
590
      buff[i] = 0;
591
      /* trim buff at first \n or \r, just in case someone fiddled with the
592
       * link file using a text editor */
593
      for (i = 0; (buff[i] != 0) && (buff[i] != '\r') && (buff[i] != '\n'); i++);
594
      buff[i] = 0;
595
      /* lookup check */
596
      if (buff[0] != 0) {
597
        lookup = lookup_cmd(cmdfile, cmd, buff, &ext);
598
        if (lookup == 0) goto RUNCMDFILE;
599
      }
600
    }
601
  }
602
 
603
  /* all failed (ie. executable file not found) */
604
  return;
605
 
458 mateuszvis 606
  RUNCMDFILE:
607
 
469 mateuszvis 608
  /* special handling of batch files */
609
  if ((ext != NULL) && (imatch(ext, "bat"))) {
957 mateusz.vi 610
    struct batctx far *newbat;
611
 
612
    /* remember the echo flag (in case bat file disables echo, only when starting first bat) */
613
    if (rmod->bat == NULL) {
614
      rmod->flags &= ~FLAG_ECHO_BEFORE_BAT;
615
      if (rmod->flags & FLAG_ECHOFLAG) rmod->flags |= FLAG_ECHO_BEFORE_BAT;
949 mateusz.vi 616
    }
957 mateusz.vi 617
 
618
    /* if bat is not called via a CALL, then free the bat-context linked list */
963 mateusz.vi 619
    if ((flags & CALL_FLAG) == 0) rmod_free_bat_llist(rmod);
620
 
957 mateusz.vi 621
    /* allocate a new bat context */
622
    newbat = rmod_fcalloc(sizeof(struct batctx), rmod->rmodseg, "SVBATCTX");
623
    if (newbat == NULL) {
624
      nls_outputnl_doserr(8); /* insufficient memory */
949 mateusz.vi 625
      return;
626
    }
627
 
957 mateusz.vi 628
    /* fill the newly allocated batctx structure */
2213 mateusz.vi 629
    sv_strcpy_far(newbat->fname, cmdfile); /* truename of the BAT file */
1001 mateusz.vi 630
    newbat->flags = flags & FLAG_STEPBYSTEP;
508 mateuszvis 631
    /* explode args of the bat file and store them in rmod buff */
632
    cmd_explode(buff, cmdline, NULL);
2213 mateusz.vi 633
    memcpy_ltr_far(newbat->argv, buff, sizeof(newbat->argv));
508 mateuszvis 634
 
957 mateusz.vi 635
    /* push the new bat to the top of rmod's linked list */
636
    newbat->parent = rmod->bat;
637
    rmod->bat = newbat;
638
 
469 mateuszvis 639
    return;
640
  }
641
 
517 mateuszvis 642
  /* copy full filename to execute, along with redirected files (if any) */
2213 mateusz.vi 643
  sv_strcpy_far(rmod_execprog, cmdfile);
548 mateuszvis 644
 
645
  /* copy stdin file if a redirection is needed */
517 mateuszvis 646
  if (redir->stdinfile) {
548 mateuszvis 647
    char far *farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDINFILE);
576 mateuszvis 648
    char far *delstdin = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDIN_DEL);
2213 mateusz.vi 649
    sv_strcpy_far(farptr, redir->stdinfile);
957 mateusz.vi 650
    if (flags & DELETE_STDIN_FILE) {
576 mateuszvis 651
      *delstdin = redir->stdinfile[0];
652
    } else {
653
      *delstdin = 0;
654
    }
517 mateuszvis 655
  }
548 mateuszvis 656
 
657
  /* same for stdout file */
517 mateuszvis 658
  if (redir->stdoutfile) {
548 mateuszvis 659
    char far *farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDOUTFILE);
660
    unsigned short far *farptr16 = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDOUTAPP);
2213 mateusz.vi 661
    sv_strcpy_far(farptr, redir->stdoutfile);
517 mateuszvis 662
    /* openflag */
548 mateuszvis 663
    *farptr16 = redir->stdout_openflag;
517 mateuszvis 664
  }
461 mateuszvis 665
 
666
  /* copy cmdtail to rmod's PSP and compute its len */
667
  for (i = 0; cmdtail[i] != 0; i++) rmod_cmdtail[i] = cmdtail[i];
668
  rmod_cmdtail[i] = '\r';
669
  rmod_cmdtail[-1] = i;
670
 
671
  /* set up rmod to execute the command */
672
 
1730 mateusz.vi 673
  /* loadhigh? */
674
  if (flags & LOADHIGH_FLAG) {
675
    unsigned char far *farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_EXEC_LH);
676
    *farptr = 1;
677
  }
678
 
464 mateuszvis 679
  ExecParam->envseg = envseg;
461 mateuszvis 680
  ExecParam->cmdtail = (unsigned long)MK_FP(rmod->rmodseg, 0x80); /* farptr, must be in PSP format (lenbyte args \r) */
1156 mateusz.vi 681
  /* far pointers to unopened FCB entries (stored in RMOD's own PSP) */
682
  {
683
    char far *farptr;
684
    /* prep the unopened FCBs */
685
    farptr = MK_FP(rmod->rmodseg, 0x5C);
2213 mateusz.vi 686
    sv_bzero(farptr, 36); /* first FCB is 16 bytes long, second is 20 bytes long */
1156 mateusz.vi 687
    cmdtail_to_fcb(farptr, farptr + 16, cmdtail);
688
    /* set (far) pointers in the ExecParam block */
689
    ExecParam->fcb1 = (unsigned long)MK_FP(rmod->rmodseg, 0x5C);
690
    ExecParam->fcb2 = (unsigned long)MK_FP(rmod->rmodseg, 0x6C);
691
  }
2212 mateusz.vi 692
  EXIT(0); /* let rmod do the job now */
364 mateuszvis 693
}
694
 
695
 
367 mateuszvis 696
static void set_comspec_to_self(unsigned short envseg) {
697
  unsigned short *psp_envseg = (void *)(0x2c); /* pointer to my env segment field in the PSP */
698
  char far *myenv = MK_FP(*psp_envseg, 0);
699
  unsigned short varcount;
700
  char buff[256] = "COMSPEC=";
701
  char *buffptr = buff + 8;
702
  /* who am i? look into my own environment, at the end of it should be my EXEPATH string */
703
  while (*myenv != 0) {
704
    /* consume a NULL-terminated string */
705
    while (*myenv != 0) myenv++;
706
    /* move to next string */
707
    myenv++;
708
  }
709
  /* get next word, if 1 then EXEPATH follows */
710
  myenv++;
711
  varcount = *myenv;
712
  myenv++;
713
  varcount |= (*myenv << 8);
714
  myenv++;
715
  if (varcount != 1) return; /* NO EXEPATH FOUND */
716
  while (*myenv != 0) {
717
    *buffptr = *myenv;
718
    buffptr++;
719
    myenv++;
720
  }
721
  *buffptr = 0;
722
  /* printf("EXEPATH: '%s'\r\n", buff); */
723
  env_setvar(envseg, buff);
724
}
725
 
726
 
450 mateuszvis 727
/* wait for user input */
1797 mateusz.vi 728
static void cmdline_getinput(unsigned short inpseg, unsigned short inpoff);
729
#pragma aux cmdline_getinput = \
730
"push ds" \
731
/* set up buffered input to inpseg:inpoff */ \
732
"push ax" \
733
"pop ds" \
734
\
735
/* is DOSKEY support present? (INT 2Fh, AX=4800h, returns non-zero in AL if present) */ \
736
"mov ax, 0x4800" \
737
"int 0x2f" \
738
\
739
/* execute either DOS input or DOSKEY */ \
740
"test al, al" /* al=0 if no DOSKEY present */ \
741
"jnz DOSKEY" \
742
\
743
/* buffered string input */ \
744
"mov ah, 0x0a" \
745
"int 0x21" \
746
"jmp short DONE" \
747
\
748
"DOSKEY:" \
749
"mov ax, 0x4810" \
750
"int 0x2f" \
751
\
752
"DONE:" \
753
/* terminate command with a CR/LF */ \
754
"mov ah, 0x02" /* display character in dl */ \
755
"mov dl, 0x0d" \
756
"int 0x21" \
757
"mov dl, 0x0a" \
758
"int 0x21" \
759
"pop ds" \
760
parm [ax] [dx] \
761
modify [ax dl]
450 mateuszvis 762
 
763
 
479 mateuszvis 764
/* fetches a line from batch file and write it to buff (NULL-terminated),
765
 * increments rmod counter and returns 0 on success. */
484 mateuszvis 766
static int getbatcmd(char *buff, unsigned char buffmaxlen, struct rmod_props far *rmod) {
469 mateuszvis 767
  unsigned short i;
949 mateusz.vi 768
  unsigned short batname_seg = FP_SEG(rmod->bat->fname);
769
  unsigned short batname_off = FP_OFF(rmod->bat->fname);
770
  unsigned short filepos_cx = rmod->bat->nextline >> 16;
771
  unsigned short filepos_dx = rmod->bat->nextline & 0xffff;
474 mateuszvis 772
  unsigned char blen = 0;
505 mateuszvis 773
  unsigned short errv = 0;
474 mateuszvis 774
 
775
  /* open file, jump to offset filpos, and read data into buff.
776
   * result in blen (unchanged if EOF or failure). */
777
  _asm {
778
    push ax
779
    push bx
780
    push cx
781
    push dx
782
 
783
    /* open file (read-only) */
505 mateuszvis 784
    mov bx, 0xffff        /* preset BX to 0xffff to detect error conditions */
474 mateuszvis 785
    mov dx, batname_off
786
    mov ax, batname_seg
787
    push ds     /* save DS */
788
    mov ds, ax
789
    mov ax, 0x3d00
790
    int 0x21    /* handle in ax on success */
791
    pop ds      /* restore DS */
505 mateuszvis 792
    jc ERR
474 mateuszvis 793
    mov bx, ax  /* save handle to bx */
794
 
795
    /* jump to file offset CX:DX */
796
    mov ax, 0x4200
797
    mov cx, filepos_cx
798
    mov dx, filepos_dx
799
    int 0x21  /* CF clear on success, DX:AX set to cur pos */
505 mateuszvis 800
    jc ERR
474 mateuszvis 801
 
802
    /* read the line into buff */
803
    mov ah, 0x3f
484 mateuszvis 804
    xor ch, ch
805
    mov cl, buffmaxlen
474 mateuszvis 806
    mov dx, buff
807
    int 0x21 /* CF clear on success, AX=number of bytes read */
505 mateuszvis 808
    jc ERR
474 mateuszvis 809
    mov blen, al
505 mateuszvis 810
    jmp CLOSEANDQUIT
474 mateuszvis 811
 
505 mateuszvis 812
    ERR:
813
    mov errv, ax
814
 
474 mateuszvis 815
    CLOSEANDQUIT:
505 mateuszvis 816
    /* close file (if bx contains a handle) */
817
    cmp bx, 0xffff
818
    je DONE
474 mateuszvis 819
    mov ah, 0x3e
820
    int 0x21
821
 
822
    DONE:
823
    pop dx
824
    pop cx
825
    pop bx
826
    pop ax
469 mateuszvis 827
  }
470 mateuszvis 828
 
474 mateuszvis 829
  /* printf("blen=%u filepos_cx=%u filepos_dx=%u\r\n", blen, filepos_cx, filepos_dx); */
470 mateuszvis 830
 
538 mateuszvis 831
  if (errv != 0) nls_outputnl_doserr(errv);
505 mateuszvis 832
 
474 mateuszvis 833
  /* on EOF - abort processing the bat file */
834
  if (blen == 0) goto OOPS;
835
 
836
  /* find nearest \n to inc batch offset and replace \r by NULL terminator
837
   * I support all CR/LF, CR- and LF-terminated batch files */
838
  for (i = 0; i < blen; i++) {
839
    if ((buff[i] == '\r') || (buff[i] == '\n')) {
949 mateusz.vi 840
      if ((buff[i] == '\r') && ((i+1) < blen) && (buff[i+1] == '\n')) rmod->bat->nextline += 1;
474 mateuszvis 841
      break;
842
    }
843
  }
844
  buff[i] = 0;
949 mateusz.vi 845
  rmod->bat->nextline += i + 1;
474 mateuszvis 846
 
847
  return(0);
848
 
849
  OOPS:
949 mateusz.vi 850
  rmod->bat->fname[0] = 0;
851
  rmod->bat->nextline = 0;
474 mateuszvis 852
  return(-1);
469 mateuszvis 853
}
854
 
855
 
507 mateuszvis 856
/* replaces %-variables in a BAT line with resolved values:
857
 * %PATH%       -> replaced by the contend of the PATH env variable
858
 * %UNDEFINED%  -> undefined variables are replaced by nothing ("")
859
 * %NOTCLOSED   -> NOTCLOSED
860
 * %1           -> first argument of the batch file (or nothing if no arg) */
861
static void batpercrepl(char *res, unsigned short ressz, const char *line, const struct rmod_props far *rmod, unsigned short envseg) {
862
  unsigned short lastperc = 0xffff;
863
  unsigned short reslen = 0;
864
 
865
  if (ressz == 0) return;
866
  ressz--; /* reserve one byte for the NULL terminator */
867
 
868
  for (; (reslen < ressz) && (*line != 0); line++) {
869
    /* if not a percent, I don't care */
870
    if (*line != '%') {
871
      res[reslen++] = *line;
872
      continue;
873
    }
874
 
875
    /* *** perc char handling *** */
876
 
877
    /* closing perc? */
878
    if (lastperc != 0xffff) {
879
      /* %% is '%' */
880
      if (lastperc == reslen) {
881
        res[reslen++] = '%';
882
      } else {   /* otherwise variable name */
883
        const char far *ptr;
884
        res[reslen] = 0;
885
        reslen = lastperc;
1139 mateusz.vi 886
        nls_strtoup(res + reslen); /* turn varname uppercase before lookup */
507 mateuszvis 887
        ptr = env_lookup_val(envseg, res + reslen);
888
        if (ptr != NULL) {
889
          while ((*ptr != 0) && (reslen < ressz)) {
890
            res[reslen++] = *ptr;
891
            ptr++;
892
          }
893
        }
894
      }
895
      lastperc = 0xffff;
896
      continue;
897
    }
898
 
899
    /* digit? (bat arg) */
900
    if ((line[1] >= '0') && (line[1] <= '9')) {
508 mateuszvis 901
      unsigned short argid = line[1] - '0';
902
      unsigned short i;
949 mateusz.vi 903
      const char far *argv = "";
904
      if ((rmod != NULL) && (rmod->bat != NULL)) argv = rmod->bat->argv;
508 mateuszvis 905
 
906
      /* locate the proper arg */
907
      for (i = 0; i != argid; i++) {
908
        /* if string is 0, then end of list reached */
909
        if (*argv == 0) break;
910
        /* jump to next arg */
911
        while (*argv != 0) argv++;
912
        argv++;
913
      }
914
 
915
      /* copy the arg to result */
916
      for (i = 0; (argv[i] != 0) && (reslen < ressz); i++) {
917
        res[reslen++] = argv[i];
918
      }
507 mateuszvis 919
      line++;  /* skip the digit */
920
      continue;
921
    }
922
 
923
    /* opening perc */
924
    lastperc = reslen;
925
 
926
  }
927
 
928
  res[reslen] = 0;
929
}
930
 
931
 
1024 mateusz.vi 932
/* process the ongoing forloop, returns 0 on success, non-zero otherwise (no
933
   more things to process) */
934
static int forloop_process(char *res, struct forctx far *forloop) {
935
  unsigned short i, t;
936
  struct DTA *dta = (void *)0x80; /* default DTA at 80h in PSP */
1055 mateusz.vi 937
  char *fnameptr = dta->fname;
1070 mateusz.vi 938
  char *pathprefix = BUFFER + 256;
957 mateusz.vi 939
 
1070 mateusz.vi 940
  *pathprefix = 0;
941
 
1024 mateusz.vi 942
  TRYAGAIN:
943
 
944
  /* dta_inited: FindFirst() or FindNext()? */
945
  if (forloop->dta_inited == 0) {
946
 
1065 bttr 947
    /* copy next awaiting pattern to BUFFER (and skip all delimiters until
1054 mateusz.vi 948
     * next pattern or end of list) */
949
    t = 0;
1024 mateusz.vi 950
    for (i = 0;; i++) {
951
      BUFFER[i] = forloop->cmd[forloop->nextpat + i];
1070 mateusz.vi 952
      /* is this a delimiter? (all delimiters are already normalized to a space here) */
953
      if (BUFFER[i] == ' ') {
954
        BUFFER[i] = 0;
955
        t = 1;
956
      } else if (BUFFER[i] == 0) {
957
        /* end of patterns list */
958
        break;
959
      } else {
960
        /* quit if I got a pattern already */
961
        if (t == 1) break;
1024 mateusz.vi 962
      }
963
    }
964
 
965
    if (i == 0) return(-1);
966
 
967
    /* remember position of current pattern */
968
    forloop->curpat = forloop->nextpat;
969
 
970
    /* move nextpat forward to next pattern */
971
    i += forloop->nextpat;
972
    forloop->nextpat = i;
973
 
1055 mateusz.vi 974
    /* if this is a string and not a pattern, skip all the FindFirst business
975
     * a file pattern has a wildcard (* or ?), a message doesn't */
976
    for (i = 0; (BUFFER[i] != 0) && (BUFFER[i] != '?') && (BUFFER[i] != '*'); i++);
977
    if (BUFFER[i] == 0) {
978
      fnameptr = BUFFER;
979
      goto SKIP_DTA;
980
    }
981
 
1024 mateusz.vi 982
    /* FOR in MSDOS 6 includes hidden and system files, but not directories nor volumes */
983
    if (findfirst(dta, BUFFER, DOS_ATTR_RO | DOS_ATTR_HID | DOS_ATTR_SYS | DOS_ATTR_ARC) != 0) {
984
      goto TRYAGAIN;
985
    }
986
    forloop->dta_inited = 1;
987
  } else { /* dta in progress */
988
 
989
    /* copy forloop DTA to my local copy */
2213 mateusz.vi 990
    memcpy_ltr_far(dta, &(forloop->dta), sizeof(*dta));
1024 mateusz.vi 991
 
992
    /* findnext() call */
993
    if (findnext(dta) != 0) {
994
      forloop->dta_inited = 0;
995
      goto TRYAGAIN;
996
    }
997
  }
998
 
999
  /* copy updated DTA to rmod */
2213 mateusz.vi 1000
  memcpy_ltr_far(&(forloop->dta), dta, sizeof(*dta));
1024 mateusz.vi 1001
 
1070 mateusz.vi 1002
  /* prefill pathprefix with the prefix (path) of the files */
1003
  {
1004
    short lastbk = -1;
1005
    char far *c = forloop->cmd + forloop->curpat;
1006
    for (i = 0;; i++) {
1007
      pathprefix[i] = c[i];
1008
      if (pathprefix[i] == '\\') lastbk = i;
1009
      if ((pathprefix[i] == ' ') || (pathprefix[i] == 0)) break;
1010
    }
1011
    pathprefix[lastbk+1] = 0;
1012
  }
1013
 
1055 mateusz.vi 1014
  SKIP_DTA:
1015
 
1024 mateusz.vi 1016
  /* fill res with command, replacing varname by actual filename */
1017
  /* full filename is to be built with path of curpat and fname from dta */
1018
  t = 0;
1019
  i = 0;
1020
  for (;;) {
1021
    if ((forloop->cmd[forloop->exec + t] == '%') && (forloop->cmd[forloop->exec + t + 1] == forloop->varname)) {
2213 mateusz.vi 1022
      sv_strcpy(res + i, pathprefix);
1023
      sv_strcat(res + i, fnameptr);
1070 mateusz.vi 1024
      for (; res[i] != 0; i++);
1024 mateusz.vi 1025
      t += 2;
1026
    } else {
1027
      res[i] = forloop->cmd[forloop->exec + t];
1028
      t++;
1029
      if (res[i++] == 0) break;
1030
    }
1031
  }
1032
 
1033
  return(0);
1034
}
1035
 
1036
 
443 mateuszvis 1037
int main(void) {
372 mateuszvis 1038
  static struct config cfg;
1039
  static unsigned short far *rmod_envseg;
449 mateuszvis 1040
  static struct rmod_props far *rmod;
500 mateuszvis 1041
  static char cmdlinebuf[CMDLINE_MAXLEN + 2]; /* 1 extra byte for 0-terminator and another for memguard */
479 mateuszvis 1042
  static char *cmdline;
517 mateuszvis 1043
  static struct redir_data redirprops;
533 mateuszvis 1044
  static enum cmd_result cmdres;
543 mateuszvis 1045
  static unsigned short i; /* general-purpose variable for short-lived things */
957 mateusz.vi 1046
  static unsigned char flags;
1854 mateusz.vi 1047
  static unsigned char far *rmod_farptr;
349 mateuszvis 1048
 
479 mateuszvis 1049
  rmod = rmod_find(BUFFER_len);
449 mateuszvis 1050
  if (rmod == NULL) {
1840 mateusz.vi 1051
 
485 mateuszvis 1052
    /* look at command line parameters (in case env size if set there) */
1053
    parse_argv(&cfg);
1840 mateusz.vi 1054
 
1055
    /* DR-DOS specific: if I am the init shell (zeroed env seg) then detect F5/F8 now
1056
     * This must be done BEFORE rmod_install() because DR-DOS's boot environment
1057
     * is located at an unallocated memory location that is likely to be overwritten
1058
     * by rmod_install(). */
1877 mateusz.vi 1059
    drdos_init(&cfg);
1840 mateusz.vi 1060
 
1877 mateusz.vi 1061
    rmod = rmod_install(cfg.envsiz, BUFFER, BUFFER_len, &(cfg.flags));
449 mateuszvis 1062
    if (rmod == NULL) {
989 mateusz.vi 1063
      nls_outputnl_err(2,1); /* "FATAL ERROR: rmod_install() failed" */
349 mateuszvis 1064
      return(1);
1065
    }
475 mateuszvis 1066
    /* copy flags to rmod's storage (and enable ECHO) */
1067
    rmod->flags = cfg.flags | FLAG_ECHOFLAG;
465 mateuszvis 1068
    /* printf("rmod installed at %Fp\r\n", rmod); */
572 mateuszvis 1069
    rmod->version = BYTE_VERSION;
1839 mateusz.vi 1070
 
349 mateuszvis 1071
  } else {
465 mateuszvis 1072
    /* printf("rmod found at %Fp\r\n", rmod); */
1073
    /* if I was spawned by rmod and FLAG_EXEC_AND_QUIT is set, then I should
1074
     * die asap, because the command has been executed already, so I no longer
1824 mateusz.vi 1075
     * have a purpose in life, UNLESS I still have a batch file to run or
1076
     * a FOR loop to execute */
1077
    if ((rmod->flags & FLAG_EXEC_AND_QUIT) && (rmod->bat == NULL) && (rmod->forloop == NULL)) {
1078
      sayonara(rmod);
1079
    }
1846 mateusz.vi 1080
 
1856 mateusz.vi 1081
    /* halt if RMOD version is not the same as myself - this can happen after
1082
     * a SvarCOM update */
572 mateuszvis 1083
    if (rmod->version != BYTE_VERSION) {
989 mateusz.vi 1084
      nls_outputnl_err(2,0);
572 mateuszvis 1085
      _asm {
1086
        HALT:
1087
        hlt
1088
        jmp HALT
1089
      }
1090
    }
349 mateuszvis 1091
  }
1092
 
1854 mateusz.vi 1093
  /* general (far) pointer to RMOD, useful to check some of its internal fields */
1094
  rmod_farptr = MK_FP(rmod->rmodseg, 0);
1095
 
1857 mateusz.vi 1096
  rmod_envseg = MK_FP(rmod->rmodseg, RMOD_OFFSET_ENVSEG);
1097
 
1098
  /* install a few guardvals in memory to detect some cases of overflows */
1099
  memguard_set(cmdlinebuf);
1100
 
1846 mateusz.vi 1101
  /* if last operation was ended by CTRL+C then make sure to abort any
1102
   * ongoing BAT file or FOR loop */
1854 mateusz.vi 1103
  if (rmod_farptr[RMOD_OFFSET_CTRLCFLAG] != 0) {
1859 mateusz.vi 1104
    /* reset the flag */
1854 mateusz.vi 1105
    rmod_farptr[RMOD_OFFSET_CTRLCFLAG] = 0;
1859 mateusz.vi 1106
 
1107
    /* clear up the forloop node */
1108
    if (rmod->forloop != NULL) {
1109
      rmod_ffree(rmod->forloop);
1110
      rmod->forloop = NULL;
1111
    }
1112
 
1113
    /* clear up the batch linked list */
1114
    if (rmod->bat != NULL) {
1115
      while (rmod->bat != NULL) {
1116
        struct batctx far *batnode;
1117
        batnode = rmod->bat;
1118
        rmod->bat = rmod->bat->parent;
1119
        rmod_ffree(batnode);
1120
      }
1121
      rmod->flags &= ~FLAG_ECHOFLAG;
1122
      if (rmod->flags & FLAG_ECHO_BEFORE_BAT) rmod->flags |= FLAG_ECHOFLAG;
1123
    }
1846 mateusz.vi 1124
  }
1125
 
1713 bttr 1126
  /* make COMSPEC point to myself */
367 mateuszvis 1127
  set_comspec_to_self(*rmod_envseg);
1128
 
494 mateuszvis 1129
  /* on /P check for the presence of AUTOEXEC.BAT and execute it if found,
1130
   * but skip this check if /D was also passed */
1131
  if ((cfg.flags & (FLAG_PERMANENT | FLAG_SKIP_AUTOEXEC)) == FLAG_PERMANENT) {
483 mateuszvis 1132
    if (file_getattr("AUTOEXEC.BAT") >= 0) cfg.execcmd = "AUTOEXEC.BAT";
1133
  }
1134
 
443 mateuszvis 1135
  do {
1023 mateusz.vi 1136
 
1852 mateusz.vi 1137
    /* update rmod's ptr to COMSPEC so it is always up to date - this needs to be
1138
     * done early so it is up to date even if this instance of SvarCOM dies
1139
     * early (for example because of a CTRL+C event) */
1140
    rmod_updatecomspecptr(rmod->rmodseg, *rmod_envseg);
1141
 
480 mateuszvis 1142
    /* terminate previous command with a CR/LF if ECHO ON (but not during BAT processing) */
949 mateusz.vi 1143
    if ((rmod->flags & FLAG_ECHOFLAG) && (rmod->bat == NULL)) outputnl("");
474 mateuszvis 1144
 
1145
    SKIP_NEWLINE:
1146
 
490 mateuszvis 1147
    /* memory check */
500 mateuszvis 1148
    memguard_check(rmod->rmodseg, cmdlinebuf);
474 mateuszvis 1149
 
500 mateuszvis 1150
    /* preset cmdline to point at the dedicated buffer */
1151
    cmdline = cmdlinebuf;
490 mateuszvis 1152
 
437 mateuszvis 1153
    /* (re)load translation strings if needed */
1881 mateusz.vi 1154
    nls_langreload(BUFFER, rmod->rmodseg);
437 mateuszvis 1155
 
1024 mateusz.vi 1156
    /* am I inside a FOR loop? */
1157
    if (rmod->forloop) {
1158
      if (forloop_process(cmdlinebuf, rmod->forloop) != 0) {
1159
        rmod_ffree(rmod->forloop);
1160
        rmod->forloop = NULL;
1824 mateusz.vi 1161
        continue; /* needed so we quit if the FOR loop was ran through COMMAND/C */
1024 mateusz.vi 1162
      } else {
1163
        /* output prompt and command on screen if echo on and command is not
1164
         * inhibiting it with the @ prefix */
1165
        if (rmod->flags & FLAG_ECHOFLAG) {
1166
          build_and_display_prompt(BUFFER, *rmod_envseg);
1167
          outputnl(cmdline);
1168
        }
1169
        /* jump to command processing */
1170
        goto EXEC_CMDLINE;
1171
      }
1172
    }
1173
 
543 mateuszvis 1174
    /* load awaiting command, if any (used to run piped commands) */
1175
    if (rmod->awaitingcmd[0] != 0) {
2213 mateusz.vi 1176
      sv_strcpy_far(cmdline, rmod->awaitingcmd);
543 mateuszvis 1177
      rmod->awaitingcmd[0] = 0;
957 mateusz.vi 1178
      flags |= DELETE_STDIN_FILE;
543 mateuszvis 1179
      goto EXEC_CMDLINE;
576 mateuszvis 1180
    } else {
957 mateusz.vi 1181
      flags &= ~DELETE_STDIN_FILE;
543 mateuszvis 1182
    }
1183
 
1001 mateusz.vi 1184
    /* skip user input if I have a command to exec (/C or /K or /P) */
443 mateuszvis 1185
    if (cfg.execcmd != NULL) {
1186
      cmdline = cfg.execcmd;
1187
      cfg.execcmd = NULL;
1001 mateusz.vi 1188
      /* */
1189
      if (cfg.flags & FLAG_STEPBYSTEP) flags |= FLAG_STEPBYSTEP;
443 mateuszvis 1190
      goto EXEC_CMDLINE;
1191
    }
1192
 
469 mateuszvis 1193
    /* if batch file is being executed -> fetch next line */
949 mateusz.vi 1194
    if (rmod->bat != NULL) {
507 mateuszvis 1195
      if (getbatcmd(BUFFER, CMDLINE_MAXLEN, rmod) != 0) { /* end of batch */
949 mateusz.vi 1196
        struct batctx far *victim = rmod->bat;
1197
        rmod->bat = rmod->bat->parent;
1198
        rmod_ffree(victim);
957 mateusz.vi 1199
        /* end of batch? then restore echo flag as it was before running the (first) bat file */
949 mateusz.vi 1200
        if (rmod->bat == NULL) {
1201
          rmod->flags &= ~FLAG_ECHOFLAG;
1202
          if (rmod->flags & FLAG_ECHO_BEFORE_BAT) rmod->flags |= FLAG_ECHOFLAG;
1203
        }
474 mateuszvis 1204
        continue;
1205
      }
507 mateuszvis 1206
      /* %-decoding of variables (%PATH%, %1, %%...), result in cmdline */
1207
      batpercrepl(cmdline, CMDLINE_MAXLEN, BUFFER, rmod, *rmod_envseg);
480 mateuszvis 1208
      /* skip any leading spaces */
1209
      while (*cmdline == ' ') cmdline++;
960 mateusz.vi 1210
      /* skip batch labels */
1211
      if (*cmdline == ':') continue;
1001 mateusz.vi 1212
      /* step-by-step execution? */
1213
      if (rmod->bat->flags & FLAG_STEPBYSTEP) {
1214
        if (*cmdline == 0) continue; /* skip empty lines */
1215
        if (askchoice(cmdline, svarlang_str(0,10)) != 0) continue;
1216
      }
474 mateuszvis 1217
      /* output prompt and command on screen if echo on and command is not
1218
       * inhibiting it with the @ prefix */
479 mateuszvis 1219
      if ((rmod->flags & FLAG_ECHOFLAG) && (cmdline[0] != '@')) {
474 mateuszvis 1220
        build_and_display_prompt(BUFFER, *rmod_envseg);
479 mateuszvis 1221
        outputnl(cmdline);
474 mateuszvis 1222
      }
479 mateuszvis 1223
      /* skip the @ prefix if present, it is no longer useful */
1224
      if (cmdline[0] == '@') cmdline++;
469 mateuszvis 1225
    } else {
983 mateusz.vi 1226
      unsigned char far *rmod_inputbuf = MK_FP(rmod->rmodseg, RMOD_OFFSET_INPUTBUF);
1227
      /* invalidate input history if it appears to be damaged (could occur
1228
       * because of a stack overflow, for example if some stack-hungry TSR is
1229
       * being used) */
987 mateusz.vi 1230
      if ((rmod_inputbuf[0] != 128) || (rmod_inputbuf[rmod_inputbuf[1] + 2] != '\r') || (rmod_inputbuf[rmod_inputbuf[1] + 3] != 0xCA) || (rmod_inputbuf[rmod_inputbuf[1] + 4] != 0xFE)) {
1231
        rmod_inputbuf[0] = 128;  /* max allowed input length */
1232
        rmod_inputbuf[1] = 0;    /* string len stored in buffer */
1233
        rmod_inputbuf[2] = '\r'; /* string terminator */
1234
        rmod_inputbuf[3] = 0xCA; /* trailing signature */
1235
        rmod_inputbuf[4] = 0xFE; /* trailing signature */
989 mateusz.vi 1236
        nls_outputnl_err(2,2); /* "stack overflow detected, command history flushed" */
983 mateusz.vi 1237
      }
474 mateuszvis 1238
      /* interactive mode: display prompt (if echo enabled) and wait for user
1239
       * command line */
475 mateuszvis 1240
      if (rmod->flags & FLAG_ECHOFLAG) build_and_display_prompt(BUFFER, *rmod_envseg);
474 mateuszvis 1241
      /* collect user input */
983 mateusz.vi 1242
      cmdline_getinput(rmod->rmodseg, RMOD_OFFSET_INPUTBUF);
987 mateusz.vi 1243
      /* append stack-overflow detection signature to the end of the input buffer */
1244
      rmod_inputbuf[rmod_inputbuf[1] + 3] = 0xCA; /* trailing signature */
1245
      rmod_inputbuf[rmod_inputbuf[1] + 4] = 0xFE; /* trailing signature */
479 mateuszvis 1246
      /* copy it to local cmdline */
2213 mateusz.vi 1247
      if (rmod_inputbuf[1] != 0) memcpy_ltr_far(cmdline, rmod_inputbuf + 2, rmod_inputbuf[1]);
983 mateusz.vi 1248
      cmdline[rmod_inputbuf[1]] = 0; /* zero-terminate local buff (original is '\r'-terminated) */
469 mateuszvis 1249
    }
349 mateuszvis 1250
 
405 mateuszvis 1251
    /* if nothing entered, loop again (but without appending an extra CR/LF) */
479 mateuszvis 1252
    if (cmdline[0] == 0) goto SKIP_NEWLINE;
349 mateuszvis 1253
 
443 mateuszvis 1254
    /* I jump here when I need to exec an initial command (/C or /K) */
1255
    EXEC_CMDLINE:
1256
 
364 mateuszvis 1257
    /* move pointer forward to skip over any leading spaces */
1258
    while (*cmdline == ' ') cmdline++;
349 mateuszvis 1259
 
1138 mateusz.vi 1260
    /* sanitize separators into spaces */
1261
    for (i = 0; cmdline[i] != 0; i++) {
1262
      switch (cmdline[i]) {
1263
        case '\t':
1264
          cmdline[i] = ' ';
1265
      }
1266
    }
1267
 
402 mateuszvis 1268
    /* handle redirections (if any) */
577 mateuszvis 1269
    i = redir_parsecmd(&redirprops, cmdline, rmod->awaitingcmd, *rmod_envseg);
543 mateuszvis 1270
    if (i != 0) {
1271
      nls_outputnl_doserr(i);
1272
      rmod->awaitingcmd[0] = 0;
1273
      continue;
1274
    }
402 mateuszvis 1275
 
364 mateuszvis 1276
    /* try matching (and executing) an internal command */
957 mateusz.vi 1277
    cmdres = cmd_process(rmod, *rmod_envseg, cmdline, BUFFER, sizeof(BUFFER), &redirprops, flags & DELETE_STDIN_FILE);
533 mateuszvis 1278
    if ((cmdres == CMD_OK) || (cmdres == CMD_FAIL)) {
443 mateuszvis 1279
      /* internal command executed */
533 mateuszvis 1280
    } else if (cmdres == CMD_CHANGED) { /* cmdline changed, needs to be reprocessed */
1281
      goto EXEC_CMDLINE;
957 mateusz.vi 1282
    } else if (cmdres == CMD_CHANGED_BY_CALL) { /* cmdline changed *specifically* by CALL */
1283
      /* the distinction is important since it changes the way batch files are processed */
1284
      flags |= CALL_FLAG;
1285
      goto EXEC_CMDLINE;
1730 mateusz.vi 1286
    } else if (cmdres == CMD_CHANGED_BY_LH) { /* cmdline changed *specifically* by LH */
1287
      flags |= LOADHIGH_FLAG;
1288
      goto EXEC_CMDLINE;
533 mateuszvis 1289
    } else if (cmdres == CMD_NOTFOUND) {
1290
      /* this was not an internal command, try matching an external command */
957 mateusz.vi 1291
      run_as_external(BUFFER, cmdline, *rmod_envseg, rmod, &redirprops, flags);
1292
 
1293
      /* is it a newly launched BAT file? */
949 mateusz.vi 1294
      if ((rmod->bat != NULL) && (rmod->bat->nextline == 0)) goto SKIP_NEWLINE;
533 mateuszvis 1295
      /* run_as_external() does not return on success, if I am still alive then
1296
       * external command failed to execute */
989 mateusz.vi 1297
      nls_outputnl(0,5); /* "Bad command or file name" */
1001 mateusz.vi 1298
    } else {
1299
      /* I should never ever land here */
1300
      outputnl("INTERNAL ERR: INVALID CMDRES");
353 mateuszvis 1301
    }
352 mateuszvis 1302
 
1001 mateusz.vi 1303
    /* reset one-time only flags */
1304
    flags &= ~CALL_FLAG;
1305
    flags &= ~FLAG_STEPBYSTEP;
1730 mateusz.vi 1306
    flags &= ~LOADHIGH_FLAG;
349 mateuszvis 1307
 
958 mateusz.vi 1308
    /* repeat unless /C was asked - but always finish running an ongoing batch
1309
     * file (otherwise only first BAT command would be executed with /C) */
1024 mateusz.vi 1310
  } while (((rmod->flags & FLAG_EXEC_AND_QUIT) == 0) || (rmod->bat != NULL) || (rmod->forloop != NULL));
349 mateuszvis 1311
 
449 mateuszvis 1312
  sayonara(rmod);
349 mateuszvis 1313
  return(0);
1314
}