Subversion Repositories SvarDOS

Rev

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