Subversion Repositories SvarDOS

Rev

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