Subversion Repositories SvarDOS

Rev

Rev 1730 | 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
 
1797 mateusz.vi 352
static void dos_fname2fcb(char far *fcb, const char near *cmd);
353
#pragma aux dos_fname2fcb = \
354
"mov ax, 0x2900"   /* DOS 1+ - parse filename into FCB (DS:SI=fname, ES:DI=FCB) */ \
355
"int 0x21" \
356
parm [es di] [si] \
357
modify [ax si]
1156 mateusz.vi 358
 
359
 
360
/* parses cmdtail and fills fcb1 and fcb2 with first and second arguments,
361
 * respectively. an FCB is 12 bytes long:
362
 * drive (0=default, 1=A, 2=B, etc)
363
 * fname (8 chars, blank-padded)
364
 * fext (3 chars, blank-padded) */
365
static void cmdtail_to_fcb(char far *fcb1, char far *fcb2, const char *cmdtail) {
366
 
367
  /* skip any leading spaces */
368
  while (*cmdtail == ' ') cmdtail++;
369
 
370
  /* convert first arg */
371
  dos_fname2fcb(fcb1, cmdtail);
372
 
373
  /* skip to next arg */
374
  while ((*cmdtail != ' ') && (*cmdtail != 0)) cmdtail++;
375
  while (*cmdtail == ' ') cmdtail++;
376
 
377
  /* convert second arg */
378
  dos_fname2fcb(fcb2, cmdtail);
379
}
380
 
381
 
957 mateusz.vi 382
/* a few internal flags */
383
#define DELETE_STDIN_FILE 1
384
#define CALL_FLAG         2
1730 mateusz.vi 385
#define LOADHIGH_FLAG     4
957 mateusz.vi 386
 
387
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 388
  char *cmdfile = buff + 512;
458 mateuszvis 389
  const char far *pathptr;
390
  int lookup;
391
  unsigned short i;
392
  const char *ext;
508 mateuszvis 393
  char *cmd = buff + 1024;
479 mateuszvis 394
  const char *cmdtail;
461 mateuszvis 395
  char far *rmod_execprog = MK_FP(rmod->rmodseg, RMOD_OFFSET_EXECPROG);
396
  char far *rmod_cmdtail = MK_FP(rmod->rmodseg, 0x81);
397
  _Packed struct {
398
    unsigned short envseg;
399
    unsigned long cmdtail;
400
    unsigned long fcb1;
401
    unsigned long fcb2;
402
  } far *ExecParam = MK_FP(rmod->rmodseg, RMOD_OFFSET_EXECPARAM);
364 mateuszvis 403
 
472 mateuszvis 404
  /* find cmd and cmdtail */
405
  i = 0;
406
  cmdtail = cmdline;
407
  while (*cmdtail == ' ') cmdtail++; /* skip any leading spaces */
408
  while ((*cmdtail != ' ') && (*cmdtail != '/') && (*cmdtail != '+') && (*cmdtail != 0)) {
409
    cmd[i++] = *cmdtail;
410
    cmdtail++;
411
  }
412
  cmd[i] = 0;
364 mateuszvis 413
 
458 mateuszvis 414
  /* is this a command in curdir? */
472 mateuszvis 415
  lookup = lookup_cmd(cmdfile, cmd, NULL, &ext);
458 mateuszvis 416
  if (lookup == 0) {
417
    /* printf("FOUND LOCAL EXEC FILE: '%s'\r\n", cmdfile); */
418
    goto RUNCMDFILE;
419
  } else if (lookup == -2) {
420
    /* puts("NOT FOUND"); */
421
    return;
422
  }
423
 
424
  /* try matching something in PATH */
425
  pathptr = env_lookup_val(envseg, "PATH");
426
 
427
  /* try each path in %PATH% */
571 mateuszvis 428
  while (pathptr) {
458 mateuszvis 429
    for (i = 0;; i++) {
430
      buff[i] = *pathptr;
431
      if ((buff[i] == 0) || (buff[i] == ';')) break;
432
      pathptr++;
433
    }
434
    buff[i] = 0;
472 mateuszvis 435
    lookup = lookup_cmd(cmdfile, cmd, buff, &ext);
571 mateuszvis 436
    if (lookup == 0) goto RUNCMDFILE;
458 mateuszvis 437
    if (lookup == -2) return;
438
    if (*pathptr == ';') {
439
      pathptr++;
440
    } else {
571 mateuszvis 441
      break;
458 mateuszvis 442
    }
443
  }
444
 
571 mateuszvis 445
  /* last chance: is it an executable link? (trim extension from cmd first) */
446
  for (i = 0; (cmd[i] != 0) && (cmd[i] != '.') && (i < 9); i++) buff[128 + i] = cmd[i];
447
  buff[128 + i] = 0;
448
  if ((i < 9) && (link_computefname(buff, buff + 128, envseg) == 0)) {
449
    /* try opening the link file (if it exists) and read it into buff */
450
    i = 0;
451
    _asm {
452
      push ax
453
      push bx
454
      push cx
455
      push dx
456
 
457
      mov ax, 0x3d00  /* DOS 2+ - OPEN EXISTING FILE, READ-ONLY */
458
      mov dx, buff    /* file name */
459
      int 0x21
460
      jc ERR_FOPEN
461
      /* file handle in AX, read from file now */
462
      mov bx, ax      /* file handle */
463
      mov ah, 0x3f    /* Read from file via handle bx */
464
      mov cx, 128     /* up to 128 bytes */
465
      /* mov dx, buff */ /* dest buffer (already set) */
466
      int 0x21        /* read up to 256 bytes from file and write to buff */
467
      jc ERR_READ
468
      mov i, ax
469
      ERR_READ:
470
      mov ah, 0x3e    /* close file handle in BX */
471
      int 0x21
472
      ERR_FOPEN:
473
 
474
      pop dx
475
      pop cx
476
      pop bx
477
      pop ax
478
    }
479
 
480
    /* did I read anything? */
481
    if (i != 0) {
482
      buff[i] = 0;
483
      /* trim buff at first \n or \r, just in case someone fiddled with the
484
       * link file using a text editor */
485
      for (i = 0; (buff[i] != 0) && (buff[i] != '\r') && (buff[i] != '\n'); i++);
486
      buff[i] = 0;
487
      /* lookup check */
488
      if (buff[0] != 0) {
489
        lookup = lookup_cmd(cmdfile, cmd, buff, &ext);
490
        if (lookup == 0) goto RUNCMDFILE;
491
      }
492
    }
493
  }
494
 
495
  /* all failed (ie. executable file not found) */
496
  return;
497
 
458 mateuszvis 498
  RUNCMDFILE:
499
 
469 mateuszvis 500
  /* special handling of batch files */
501
  if ((ext != NULL) && (imatch(ext, "bat"))) {
957 mateusz.vi 502
    struct batctx far *newbat;
503
 
504
    /* remember the echo flag (in case bat file disables echo, only when starting first bat) */
505
    if (rmod->bat == NULL) {
506
      rmod->flags &= ~FLAG_ECHO_BEFORE_BAT;
507
      if (rmod->flags & FLAG_ECHOFLAG) rmod->flags |= FLAG_ECHO_BEFORE_BAT;
949 mateusz.vi 508
    }
957 mateusz.vi 509
 
510
    /* if bat is not called via a CALL, then free the bat-context linked list */
963 mateusz.vi 511
    if ((flags & CALL_FLAG) == 0) rmod_free_bat_llist(rmod);
512
 
957 mateusz.vi 513
    /* allocate a new bat context */
514
    newbat = rmod_fcalloc(sizeof(struct batctx), rmod->rmodseg, "SVBATCTX");
515
    if (newbat == NULL) {
516
      nls_outputnl_doserr(8); /* insufficient memory */
949 mateusz.vi 517
      return;
518
    }
519
 
957 mateusz.vi 520
    /* fill the newly allocated batctx structure */
521
    _fstrcpy(newbat->fname, cmdfile); /* truename of the BAT file */
1001 mateusz.vi 522
    newbat->flags = flags & FLAG_STEPBYSTEP;
508 mateuszvis 523
    /* explode args of the bat file and store them in rmod buff */
524
    cmd_explode(buff, cmdline, NULL);
957 mateusz.vi 525
    _fmemcpy(newbat->argv, buff, sizeof(newbat->argv));
508 mateuszvis 526
 
957 mateusz.vi 527
    /* push the new bat to the top of rmod's linked list */
528
    newbat->parent = rmod->bat;
529
    rmod->bat = newbat;
530
 
469 mateuszvis 531
    return;
532
  }
533
 
517 mateuszvis 534
  /* copy full filename to execute, along with redirected files (if any) */
548 mateuszvis 535
  _fstrcpy(rmod_execprog, cmdfile);
536
 
537
  /* copy stdin file if a redirection is needed */
517 mateuszvis 538
  if (redir->stdinfile) {
548 mateuszvis 539
    char far *farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDINFILE);
576 mateuszvis 540
    char far *delstdin = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDIN_DEL);
548 mateuszvis 541
    _fstrcpy(farptr, redir->stdinfile);
957 mateusz.vi 542
    if (flags & DELETE_STDIN_FILE) {
576 mateuszvis 543
      *delstdin = redir->stdinfile[0];
544
    } else {
545
      *delstdin = 0;
546
    }
517 mateuszvis 547
  }
548 mateuszvis 548
 
549
  /* same for stdout file */
517 mateuszvis 550
  if (redir->stdoutfile) {
548 mateuszvis 551
    char far *farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDOUTFILE);
552
    unsigned short far *farptr16 = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDOUTAPP);
553
    _fstrcpy(farptr, redir->stdoutfile);
517 mateuszvis 554
    /* openflag */
548 mateuszvis 555
    *farptr16 = redir->stdout_openflag;
517 mateuszvis 556
  }
461 mateuszvis 557
 
558
  /* copy cmdtail to rmod's PSP and compute its len */
559
  for (i = 0; cmdtail[i] != 0; i++) rmod_cmdtail[i] = cmdtail[i];
560
  rmod_cmdtail[i] = '\r';
561
  rmod_cmdtail[-1] = i;
562
 
563
  /* set up rmod to execute the command */
564
 
1730 mateusz.vi 565
  /* loadhigh? */
566
  if (flags & LOADHIGH_FLAG) {
567
    unsigned char far *farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_EXEC_LH);
568
    *farptr = 1;
569
  }
570
 
464 mateuszvis 571
  ExecParam->envseg = envseg;
461 mateuszvis 572
  ExecParam->cmdtail = (unsigned long)MK_FP(rmod->rmodseg, 0x80); /* farptr, must be in PSP format (lenbyte args \r) */
1156 mateusz.vi 573
  /* far pointers to unopened FCB entries (stored in RMOD's own PSP) */
574
  {
575
    char far *farptr;
576
    /* prep the unopened FCBs */
577
    farptr = MK_FP(rmod->rmodseg, 0x5C);
578
    _fmemset(farptr, 0, 36); /* first FCB is 16 bytes long, second is 20 bytes long */
579
    cmdtail_to_fcb(farptr, farptr + 16, cmdtail);
580
    /* set (far) pointers in the ExecParam block */
581
    ExecParam->fcb1 = (unsigned long)MK_FP(rmod->rmodseg, 0x5C);
582
    ExecParam->fcb2 = (unsigned long)MK_FP(rmod->rmodseg, 0x6C);
583
  }
461 mateuszvis 584
  exit(0); /* let rmod do the job now */
364 mateuszvis 585
}
586
 
587
 
367 mateuszvis 588
static void set_comspec_to_self(unsigned short envseg) {
589
  unsigned short *psp_envseg = (void *)(0x2c); /* pointer to my env segment field in the PSP */
590
  char far *myenv = MK_FP(*psp_envseg, 0);
591
  unsigned short varcount;
592
  char buff[256] = "COMSPEC=";
593
  char *buffptr = buff + 8;
594
  /* who am i? look into my own environment, at the end of it should be my EXEPATH string */
595
  while (*myenv != 0) {
596
    /* consume a NULL-terminated string */
597
    while (*myenv != 0) myenv++;
598
    /* move to next string */
599
    myenv++;
600
  }
601
  /* get next word, if 1 then EXEPATH follows */
602
  myenv++;
603
  varcount = *myenv;
604
  myenv++;
605
  varcount |= (*myenv << 8);
606
  myenv++;
607
  if (varcount != 1) return; /* NO EXEPATH FOUND */
608
  while (*myenv != 0) {
609
    *buffptr = *myenv;
610
    buffptr++;
611
    myenv++;
612
  }
613
  *buffptr = 0;
614
  /* printf("EXEPATH: '%s'\r\n", buff); */
615
  env_setvar(envseg, buff);
616
}
617
 
618
 
450 mateuszvis 619
/* wait for user input */
1797 mateusz.vi 620
static void cmdline_getinput(unsigned short inpseg, unsigned short inpoff);
621
#pragma aux cmdline_getinput = \
622
"push ds" \
623
/* set up buffered input to inpseg:inpoff */ \
624
"push ax" \
625
"pop ds" \
626
\
627
/* is DOSKEY support present? (INT 2Fh, AX=4800h, returns non-zero in AL if present) */ \
628
"mov ax, 0x4800" \
629
"int 0x2f" \
630
\
631
/* execute either DOS input or DOSKEY */ \
632
"test al, al" /* al=0 if no DOSKEY present */ \
633
"jnz DOSKEY" \
634
\
635
/* buffered string input */ \
636
"mov ah, 0x0a" \
637
"int 0x21" \
638
"jmp short DONE" \
639
\
640
"DOSKEY:" \
641
"mov ax, 0x4810" \
642
"int 0x2f" \
643
\
644
"DONE:" \
645
/* terminate command with a CR/LF */ \
646
"mov ah, 0x02" /* display character in dl */ \
647
"mov dl, 0x0d" \
648
"int 0x21" \
649
"mov dl, 0x0a" \
650
"int 0x21" \
651
"pop ds" \
652
parm [ax] [dx] \
653
modify [ax dl]
450 mateuszvis 654
 
655
 
479 mateuszvis 656
/* fetches a line from batch file and write it to buff (NULL-terminated),
657
 * increments rmod counter and returns 0 on success. */
484 mateuszvis 658
static int getbatcmd(char *buff, unsigned char buffmaxlen, struct rmod_props far *rmod) {
469 mateuszvis 659
  unsigned short i;
949 mateusz.vi 660
  unsigned short batname_seg = FP_SEG(rmod->bat->fname);
661
  unsigned short batname_off = FP_OFF(rmod->bat->fname);
662
  unsigned short filepos_cx = rmod->bat->nextline >> 16;
663
  unsigned short filepos_dx = rmod->bat->nextline & 0xffff;
474 mateuszvis 664
  unsigned char blen = 0;
505 mateuszvis 665
  unsigned short errv = 0;
474 mateuszvis 666
 
667
  /* open file, jump to offset filpos, and read data into buff.
668
   * result in blen (unchanged if EOF or failure). */
669
  _asm {
670
    push ax
671
    push bx
672
    push cx
673
    push dx
674
 
675
    /* open file (read-only) */
505 mateuszvis 676
    mov bx, 0xffff        /* preset BX to 0xffff to detect error conditions */
474 mateuszvis 677
    mov dx, batname_off
678
    mov ax, batname_seg
679
    push ds     /* save DS */
680
    mov ds, ax
681
    mov ax, 0x3d00
682
    int 0x21    /* handle in ax on success */
683
    pop ds      /* restore DS */
505 mateuszvis 684
    jc ERR
474 mateuszvis 685
    mov bx, ax  /* save handle to bx */
686
 
687
    /* jump to file offset CX:DX */
688
    mov ax, 0x4200
689
    mov cx, filepos_cx
690
    mov dx, filepos_dx
691
    int 0x21  /* CF clear on success, DX:AX set to cur pos */
505 mateuszvis 692
    jc ERR
474 mateuszvis 693
 
694
    /* read the line into buff */
695
    mov ah, 0x3f
484 mateuszvis 696
    xor ch, ch
697
    mov cl, buffmaxlen
474 mateuszvis 698
    mov dx, buff
699
    int 0x21 /* CF clear on success, AX=number of bytes read */
505 mateuszvis 700
    jc ERR
474 mateuszvis 701
    mov blen, al
505 mateuszvis 702
    jmp CLOSEANDQUIT
474 mateuszvis 703
 
505 mateuszvis 704
    ERR:
705
    mov errv, ax
706
 
474 mateuszvis 707
    CLOSEANDQUIT:
505 mateuszvis 708
    /* close file (if bx contains a handle) */
709
    cmp bx, 0xffff
710
    je DONE
474 mateuszvis 711
    mov ah, 0x3e
712
    int 0x21
713
 
714
    DONE:
715
    pop dx
716
    pop cx
717
    pop bx
718
    pop ax
469 mateuszvis 719
  }
470 mateuszvis 720
 
474 mateuszvis 721
  /* printf("blen=%u filepos_cx=%u filepos_dx=%u\r\n", blen, filepos_cx, filepos_dx); */
470 mateuszvis 722
 
538 mateuszvis 723
  if (errv != 0) nls_outputnl_doserr(errv);
505 mateuszvis 724
 
474 mateuszvis 725
  /* on EOF - abort processing the bat file */
726
  if (blen == 0) goto OOPS;
727
 
728
  /* find nearest \n to inc batch offset and replace \r by NULL terminator
729
   * I support all CR/LF, CR- and LF-terminated batch files */
730
  for (i = 0; i < blen; i++) {
731
    if ((buff[i] == '\r') || (buff[i] == '\n')) {
949 mateusz.vi 732
      if ((buff[i] == '\r') && ((i+1) < blen) && (buff[i+1] == '\n')) rmod->bat->nextline += 1;
474 mateuszvis 733
      break;
734
    }
735
  }
736
  buff[i] = 0;
949 mateusz.vi 737
  rmod->bat->nextline += i + 1;
474 mateuszvis 738
 
739
  return(0);
740
 
741
  OOPS:
949 mateusz.vi 742
  rmod->bat->fname[0] = 0;
743
  rmod->bat->nextline = 0;
474 mateuszvis 744
  return(-1);
469 mateuszvis 745
}
746
 
747
 
507 mateuszvis 748
/* replaces %-variables in a BAT line with resolved values:
749
 * %PATH%       -> replaced by the contend of the PATH env variable
750
 * %UNDEFINED%  -> undefined variables are replaced by nothing ("")
751
 * %NOTCLOSED   -> NOTCLOSED
752
 * %1           -> first argument of the batch file (or nothing if no arg) */
753
static void batpercrepl(char *res, unsigned short ressz, const char *line, const struct rmod_props far *rmod, unsigned short envseg) {
754
  unsigned short lastperc = 0xffff;
755
  unsigned short reslen = 0;
756
 
757
  if (ressz == 0) return;
758
  ressz--; /* reserve one byte for the NULL terminator */
759
 
760
  for (; (reslen < ressz) && (*line != 0); line++) {
761
    /* if not a percent, I don't care */
762
    if (*line != '%') {
763
      res[reslen++] = *line;
764
      continue;
765
    }
766
 
767
    /* *** perc char handling *** */
768
 
769
    /* closing perc? */
770
    if (lastperc != 0xffff) {
771
      /* %% is '%' */
772
      if (lastperc == reslen) {
773
        res[reslen++] = '%';
774
      } else {   /* otherwise variable name */
775
        const char far *ptr;
776
        res[reslen] = 0;
777
        reslen = lastperc;
1139 mateusz.vi 778
        nls_strtoup(res + reslen); /* turn varname uppercase before lookup */
507 mateuszvis 779
        ptr = env_lookup_val(envseg, res + reslen);
780
        if (ptr != NULL) {
781
          while ((*ptr != 0) && (reslen < ressz)) {
782
            res[reslen++] = *ptr;
783
            ptr++;
784
          }
785
        }
786
      }
787
      lastperc = 0xffff;
788
      continue;
789
    }
790
 
791
    /* digit? (bat arg) */
792
    if ((line[1] >= '0') && (line[1] <= '9')) {
508 mateuszvis 793
      unsigned short argid = line[1] - '0';
794
      unsigned short i;
949 mateusz.vi 795
      const char far *argv = "";
796
      if ((rmod != NULL) && (rmod->bat != NULL)) argv = rmod->bat->argv;
508 mateuszvis 797
 
798
      /* locate the proper arg */
799
      for (i = 0; i != argid; i++) {
800
        /* if string is 0, then end of list reached */
801
        if (*argv == 0) break;
802
        /* jump to next arg */
803
        while (*argv != 0) argv++;
804
        argv++;
805
      }
806
 
807
      /* copy the arg to result */
808
      for (i = 0; (argv[i] != 0) && (reslen < ressz); i++) {
809
        res[reslen++] = argv[i];
810
      }
507 mateuszvis 811
      line++;  /* skip the digit */
812
      continue;
813
    }
814
 
815
    /* opening perc */
816
    lastperc = reslen;
817
 
818
  }
819
 
820
  res[reslen] = 0;
821
}
822
 
823
 
1024 mateusz.vi 824
/* process the ongoing forloop, returns 0 on success, non-zero otherwise (no
825
   more things to process) */
826
static int forloop_process(char *res, struct forctx far *forloop) {
827
  unsigned short i, t;
828
  struct DTA *dta = (void *)0x80; /* default DTA at 80h in PSP */
1055 mateusz.vi 829
  char *fnameptr = dta->fname;
1070 mateusz.vi 830
  char *pathprefix = BUFFER + 256;
957 mateusz.vi 831
 
1070 mateusz.vi 832
  *pathprefix = 0;
833
 
1024 mateusz.vi 834
  TRYAGAIN:
835
 
836
  /* dta_inited: FindFirst() or FindNext()? */
837
  if (forloop->dta_inited == 0) {
838
 
1065 bttr 839
    /* copy next awaiting pattern to BUFFER (and skip all delimiters until
1054 mateusz.vi 840
     * next pattern or end of list) */
841
    t = 0;
1024 mateusz.vi 842
    for (i = 0;; i++) {
843
      BUFFER[i] = forloop->cmd[forloop->nextpat + i];
1070 mateusz.vi 844
      /* is this a delimiter? (all delimiters are already normalized to a space here) */
845
      if (BUFFER[i] == ' ') {
846
        BUFFER[i] = 0;
847
        t = 1;
848
      } else if (BUFFER[i] == 0) {
849
        /* end of patterns list */
850
        break;
851
      } else {
852
        /* quit if I got a pattern already */
853
        if (t == 1) break;
1024 mateusz.vi 854
      }
855
    }
856
 
857
    if (i == 0) return(-1);
858
 
859
    /* remember position of current pattern */
860
    forloop->curpat = forloop->nextpat;
861
 
862
    /* move nextpat forward to next pattern */
863
    i += forloop->nextpat;
864
    forloop->nextpat = i;
865
 
1055 mateusz.vi 866
    /* if this is a string and not a pattern, skip all the FindFirst business
867
     * a file pattern has a wildcard (* or ?), a message doesn't */
868
    for (i = 0; (BUFFER[i] != 0) && (BUFFER[i] != '?') && (BUFFER[i] != '*'); i++);
869
    if (BUFFER[i] == 0) {
870
      fnameptr = BUFFER;
871
      goto SKIP_DTA;
872
    }
873
 
1024 mateusz.vi 874
    /* FOR in MSDOS 6 includes hidden and system files, but not directories nor volumes */
875
    if (findfirst(dta, BUFFER, DOS_ATTR_RO | DOS_ATTR_HID | DOS_ATTR_SYS | DOS_ATTR_ARC) != 0) {
876
      goto TRYAGAIN;
877
    }
878
    forloop->dta_inited = 1;
879
  } else { /* dta in progress */
880
 
881
    /* copy forloop DTA to my local copy */
882
    _fmemcpy(dta, &(forloop->dta), sizeof(*dta));
883
 
884
    /* findnext() call */
885
    if (findnext(dta) != 0) {
886
      forloop->dta_inited = 0;
887
      goto TRYAGAIN;
888
    }
889
  }
890
 
891
  /* copy updated DTA to rmod */
892
  _fmemcpy(&(forloop->dta), dta, sizeof(*dta));
893
 
1070 mateusz.vi 894
  /* prefill pathprefix with the prefix (path) of the files */
895
  {
896
    short lastbk = -1;
897
    char far *c = forloop->cmd + forloop->curpat;
898
    for (i = 0;; i++) {
899
      pathprefix[i] = c[i];
900
      if (pathprefix[i] == '\\') lastbk = i;
901
      if ((pathprefix[i] == ' ') || (pathprefix[i] == 0)) break;
902
    }
903
    pathprefix[lastbk+1] = 0;
904
  }
905
 
1055 mateusz.vi 906
  SKIP_DTA:
907
 
1024 mateusz.vi 908
  /* fill res with command, replacing varname by actual filename */
909
  /* full filename is to be built with path of curpat and fname from dta */
910
  t = 0;
911
  i = 0;
912
  for (;;) {
913
    if ((forloop->cmd[forloop->exec + t] == '%') && (forloop->cmd[forloop->exec + t + 1] == forloop->varname)) {
1070 mateusz.vi 914
      strcpy(res + i, pathprefix);
915
      strcat(res + i, fnameptr);
916
      for (; res[i] != 0; i++);
1024 mateusz.vi 917
      t += 2;
918
    } else {
919
      res[i] = forloop->cmd[forloop->exec + t];
920
      t++;
921
      if (res[i++] == 0) break;
922
    }
923
  }
924
 
925
  return(0);
926
}
927
 
928
 
443 mateuszvis 929
int main(void) {
372 mateuszvis 930
  static struct config cfg;
931
  static unsigned short far *rmod_envseg;
932
  static unsigned short far *lastexitcode;
449 mateuszvis 933
  static struct rmod_props far *rmod;
500 mateuszvis 934
  static char cmdlinebuf[CMDLINE_MAXLEN + 2]; /* 1 extra byte for 0-terminator and another for memguard */
479 mateuszvis 935
  static char *cmdline;
517 mateuszvis 936
  static struct redir_data redirprops;
533 mateuszvis 937
  static enum cmd_result cmdres;
543 mateuszvis 938
  static unsigned short i; /* general-purpose variable for short-lived things */
957 mateusz.vi 939
  static unsigned char flags;
349 mateuszvis 940
 
479 mateuszvis 941
  rmod = rmod_find(BUFFER_len);
449 mateuszvis 942
  if (rmod == NULL) {
485 mateuszvis 943
    /* look at command line parameters (in case env size if set there) */
944
    parse_argv(&cfg);
479 mateuszvis 945
    rmod = rmod_install(cfg.envsiz, BUFFER, BUFFER_len);
449 mateuszvis 946
    if (rmod == NULL) {
989 mateusz.vi 947
      nls_outputnl_err(2,1); /* "FATAL ERROR: rmod_install() failed" */
349 mateuszvis 948
      return(1);
949
    }
475 mateuszvis 950
    /* copy flags to rmod's storage (and enable ECHO) */
951
    rmod->flags = cfg.flags | FLAG_ECHOFLAG;
465 mateuszvis 952
    /* printf("rmod installed at %Fp\r\n", rmod); */
572 mateuszvis 953
    rmod->version = BYTE_VERSION;
349 mateuszvis 954
  } else {
465 mateuszvis 955
    /* printf("rmod found at %Fp\r\n", rmod); */
956
    /* if I was spawned by rmod and FLAG_EXEC_AND_QUIT is set, then I should
957
     * die asap, because the command has been executed already, so I no longer
958
     * have a purpose in life */
469 mateuszvis 959
    if (rmod->flags & FLAG_EXEC_AND_QUIT) sayonara(rmod);
572 mateuszvis 960
    /* */
961
    if (rmod->version != BYTE_VERSION) {
989 mateusz.vi 962
      nls_outputnl_err(2,0);
572 mateuszvis 963
      _asm {
964
        HALT:
965
        hlt
966
        jmp HALT
967
      }
968
    }
349 mateuszvis 969
  }
970
 
490 mateuszvis 971
  /* install a few guardvals in memory to detect some cases of overflows */
500 mateuszvis 972
  memguard_set(cmdlinebuf);
490 mateuszvis 973
 
465 mateuszvis 974
  rmod_envseg = MK_FP(rmod->rmodseg, RMOD_OFFSET_ENVSEG);
975
  lastexitcode = MK_FP(rmod->rmodseg, RMOD_OFFSET_LEXITCODE);
350 mateuszvis 976
 
1713 bttr 977
  /* make COMSPEC point to myself */
367 mateuszvis 978
  set_comspec_to_self(*rmod_envseg);
979
 
494 mateuszvis 980
  /* on /P check for the presence of AUTOEXEC.BAT and execute it if found,
981
   * but skip this check if /D was also passed */
982
  if ((cfg.flags & (FLAG_PERMANENT | FLAG_SKIP_AUTOEXEC)) == FLAG_PERMANENT) {
483 mateuszvis 983
    if (file_getattr("AUTOEXEC.BAT") >= 0) cfg.execcmd = "AUTOEXEC.BAT";
984
  }
985
 
443 mateuszvis 986
  do {
1023 mateusz.vi 987
 
480 mateuszvis 988
    /* terminate previous command with a CR/LF if ECHO ON (but not during BAT processing) */
949 mateusz.vi 989
    if ((rmod->flags & FLAG_ECHOFLAG) && (rmod->bat == NULL)) outputnl("");
474 mateuszvis 990
 
991
    SKIP_NEWLINE:
992
 
490 mateuszvis 993
    /* memory check */
500 mateuszvis 994
    memguard_check(rmod->rmodseg, cmdlinebuf);
474 mateuszvis 995
 
500 mateuszvis 996
    /* preset cmdline to point at the dedicated buffer */
997
    cmdline = cmdlinebuf;
490 mateuszvis 998
 
437 mateuszvis 999
    /* (re)load translation strings if needed */
1000
    nls_langreload(BUFFER, *rmod_envseg);
1001
 
1024 mateusz.vi 1002
    /* am I inside a FOR loop? */
1003
    if (rmod->forloop) {
1004
      if (forloop_process(cmdlinebuf, rmod->forloop) != 0) {
1005
        rmod_ffree(rmod->forloop);
1006
        rmod->forloop = NULL;
1007
      } else {
1008
        /* output prompt and command on screen if echo on and command is not
1009
         * inhibiting it with the @ prefix */
1010
        if (rmod->flags & FLAG_ECHOFLAG) {
1011
          build_and_display_prompt(BUFFER, *rmod_envseg);
1012
          outputnl(cmdline);
1013
        }
1014
        /* jump to command processing */
1015
        goto EXEC_CMDLINE;
1016
      }
1017
    }
1018
 
543 mateuszvis 1019
    /* load awaiting command, if any (used to run piped commands) */
1020
    if (rmod->awaitingcmd[0] != 0) {
1021
      _fstrcpy(cmdline, rmod->awaitingcmd);
1022
      rmod->awaitingcmd[0] = 0;
957 mateusz.vi 1023
      flags |= DELETE_STDIN_FILE;
543 mateuszvis 1024
      goto EXEC_CMDLINE;
576 mateuszvis 1025
    } else {
957 mateusz.vi 1026
      flags &= ~DELETE_STDIN_FILE;
543 mateuszvis 1027
    }
1028
 
1001 mateusz.vi 1029
    /* skip user input if I have a command to exec (/C or /K or /P) */
443 mateuszvis 1030
    if (cfg.execcmd != NULL) {
1031
      cmdline = cfg.execcmd;
1032
      cfg.execcmd = NULL;
1001 mateusz.vi 1033
      /* */
1034
      if (cfg.flags & FLAG_STEPBYSTEP) flags |= FLAG_STEPBYSTEP;
443 mateuszvis 1035
      goto EXEC_CMDLINE;
1036
    }
1037
 
469 mateuszvis 1038
    /* if batch file is being executed -> fetch next line */
949 mateusz.vi 1039
    if (rmod->bat != NULL) {
507 mateuszvis 1040
      if (getbatcmd(BUFFER, CMDLINE_MAXLEN, rmod) != 0) { /* end of batch */
949 mateusz.vi 1041
        struct batctx far *victim = rmod->bat;
1042
        rmod->bat = rmod->bat->parent;
1043
        rmod_ffree(victim);
957 mateusz.vi 1044
        /* end of batch? then restore echo flag as it was before running the (first) bat file */
949 mateusz.vi 1045
        if (rmod->bat == NULL) {
1046
          rmod->flags &= ~FLAG_ECHOFLAG;
1047
          if (rmod->flags & FLAG_ECHO_BEFORE_BAT) rmod->flags |= FLAG_ECHOFLAG;
1048
        }
474 mateuszvis 1049
        continue;
1050
      }
507 mateuszvis 1051
      /* %-decoding of variables (%PATH%, %1, %%...), result in cmdline */
1052
      batpercrepl(cmdline, CMDLINE_MAXLEN, BUFFER, rmod, *rmod_envseg);
480 mateuszvis 1053
      /* skip any leading spaces */
1054
      while (*cmdline == ' ') cmdline++;
960 mateusz.vi 1055
      /* skip batch labels */
1056
      if (*cmdline == ':') continue;
1001 mateusz.vi 1057
      /* step-by-step execution? */
1058
      if (rmod->bat->flags & FLAG_STEPBYSTEP) {
1059
        if (*cmdline == 0) continue; /* skip empty lines */
1060
        if (askchoice(cmdline, svarlang_str(0,10)) != 0) continue;
1061
      }
474 mateuszvis 1062
      /* output prompt and command on screen if echo on and command is not
1063
       * inhibiting it with the @ prefix */
479 mateuszvis 1064
      if ((rmod->flags & FLAG_ECHOFLAG) && (cmdline[0] != '@')) {
474 mateuszvis 1065
        build_and_display_prompt(BUFFER, *rmod_envseg);
479 mateuszvis 1066
        outputnl(cmdline);
474 mateuszvis 1067
      }
479 mateuszvis 1068
      /* skip the @ prefix if present, it is no longer useful */
1069
      if (cmdline[0] == '@') cmdline++;
469 mateuszvis 1070
    } else {
983 mateusz.vi 1071
      unsigned char far *rmod_inputbuf = MK_FP(rmod->rmodseg, RMOD_OFFSET_INPUTBUF);
1072
      /* invalidate input history if it appears to be damaged (could occur
1073
       * because of a stack overflow, for example if some stack-hungry TSR is
1074
       * being used) */
987 mateusz.vi 1075
      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)) {
1076
        rmod_inputbuf[0] = 128;  /* max allowed input length */
1077
        rmod_inputbuf[1] = 0;    /* string len stored in buffer */
1078
        rmod_inputbuf[2] = '\r'; /* string terminator */
1079
        rmod_inputbuf[3] = 0xCA; /* trailing signature */
1080
        rmod_inputbuf[4] = 0xFE; /* trailing signature */
989 mateusz.vi 1081
        nls_outputnl_err(2,2); /* "stack overflow detected, command history flushed" */
983 mateusz.vi 1082
      }
474 mateuszvis 1083
      /* interactive mode: display prompt (if echo enabled) and wait for user
1084
       * command line */
475 mateuszvis 1085
      if (rmod->flags & FLAG_ECHOFLAG) build_and_display_prompt(BUFFER, *rmod_envseg);
474 mateuszvis 1086
      /* collect user input */
983 mateusz.vi 1087
      cmdline_getinput(rmod->rmodseg, RMOD_OFFSET_INPUTBUF);
987 mateusz.vi 1088
      /* append stack-overflow detection signature to the end of the input buffer */
1089
      rmod_inputbuf[rmod_inputbuf[1] + 3] = 0xCA; /* trailing signature */
1090
      rmod_inputbuf[rmod_inputbuf[1] + 4] = 0xFE; /* trailing signature */
479 mateuszvis 1091
      /* copy it to local cmdline */
983 mateusz.vi 1092
      if (rmod_inputbuf[1] != 0) _fmemcpy(cmdline, rmod_inputbuf + 2, rmod_inputbuf[1]);
1093
      cmdline[rmod_inputbuf[1]] = 0; /* zero-terminate local buff (original is '\r'-terminated) */
469 mateuszvis 1094
    }
349 mateuszvis 1095
 
405 mateuszvis 1096
    /* if nothing entered, loop again (but without appending an extra CR/LF) */
479 mateuszvis 1097
    if (cmdline[0] == 0) goto SKIP_NEWLINE;
349 mateuszvis 1098
 
443 mateuszvis 1099
    /* I jump here when I need to exec an initial command (/C or /K) */
1100
    EXEC_CMDLINE:
1101
 
364 mateuszvis 1102
    /* move pointer forward to skip over any leading spaces */
1103
    while (*cmdline == ' ') cmdline++;
349 mateuszvis 1104
 
1138 mateusz.vi 1105
    /* sanitize separators into spaces */
1106
    for (i = 0; cmdline[i] != 0; i++) {
1107
      switch (cmdline[i]) {
1108
        case '\t':
1109
          cmdline[i] = ' ';
1110
      }
1111
    }
1112
 
1713 bttr 1113
    /* update rmod's ptr to COMSPEC so it is always up to date */
465 mateuszvis 1114
    rmod_updatecomspecptr(rmod->rmodseg, *rmod_envseg);
367 mateuszvis 1115
 
402 mateuszvis 1116
    /* handle redirections (if any) */
577 mateuszvis 1117
    i = redir_parsecmd(&redirprops, cmdline, rmod->awaitingcmd, *rmod_envseg);
543 mateuszvis 1118
    if (i != 0) {
1119
      nls_outputnl_doserr(i);
1120
      rmod->awaitingcmd[0] = 0;
1121
      continue;
1122
    }
402 mateuszvis 1123
 
364 mateuszvis 1124
    /* try matching (and executing) an internal command */
957 mateusz.vi 1125
    cmdres = cmd_process(rmod, *rmod_envseg, cmdline, BUFFER, sizeof(BUFFER), &redirprops, flags & DELETE_STDIN_FILE);
533 mateuszvis 1126
    if ((cmdres == CMD_OK) || (cmdres == CMD_FAIL)) {
443 mateuszvis 1127
      /* internal command executed */
533 mateuszvis 1128
    } else if (cmdres == CMD_CHANGED) { /* cmdline changed, needs to be reprocessed */
1129
      goto EXEC_CMDLINE;
957 mateusz.vi 1130
    } else if (cmdres == CMD_CHANGED_BY_CALL) { /* cmdline changed *specifically* by CALL */
1131
      /* the distinction is important since it changes the way batch files are processed */
1132
      flags |= CALL_FLAG;
1133
      goto EXEC_CMDLINE;
1730 mateusz.vi 1134
    } else if (cmdres == CMD_CHANGED_BY_LH) { /* cmdline changed *specifically* by LH */
1135
      flags |= LOADHIGH_FLAG;
1136
      goto EXEC_CMDLINE;
533 mateuszvis 1137
    } else if (cmdres == CMD_NOTFOUND) {
1138
      /* this was not an internal command, try matching an external command */
957 mateusz.vi 1139
      run_as_external(BUFFER, cmdline, *rmod_envseg, rmod, &redirprops, flags);
1140
 
1141
      /* is it a newly launched BAT file? */
949 mateusz.vi 1142
      if ((rmod->bat != NULL) && (rmod->bat->nextline == 0)) goto SKIP_NEWLINE;
533 mateuszvis 1143
      /* run_as_external() does not return on success, if I am still alive then
1144
       * external command failed to execute */
989 mateusz.vi 1145
      nls_outputnl(0,5); /* "Bad command or file name" */
1001 mateusz.vi 1146
    } else {
1147
      /* I should never ever land here */
1148
      outputnl("INTERNAL ERR: INVALID CMDRES");
353 mateuszvis 1149
    }
352 mateuszvis 1150
 
1001 mateusz.vi 1151
    /* reset one-time only flags */
1152
    flags &= ~CALL_FLAG;
1153
    flags &= ~FLAG_STEPBYSTEP;
1730 mateusz.vi 1154
    flags &= ~LOADHIGH_FLAG;
349 mateuszvis 1155
 
958 mateusz.vi 1156
    /* repeat unless /C was asked - but always finish running an ongoing batch
1157
     * file (otherwise only first BAT command would be executed with /C) */
1024 mateusz.vi 1158
  } while (((rmod->flags & FLAG_EXEC_AND_QUIT) == 0) || (rmod->bat != NULL) || (rmod->forloop != NULL));
349 mateuszvis 1159
 
449 mateuszvis 1160
  sayonara(rmod);
349 mateuszvis 1161
  return(0);
1162
}