Subversion Repositories SvarDOS

Rev

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