Subversion Repositories SvarDOS

Rev

Rev 577 | Rev 957 | 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
 
576 mateuszvis 318
static void run_as_external(char *buff, const char *cmdline, unsigned short envseg, struct rmod_props far *rmod, struct redir_data *redir, unsigned char delete_stdin_file) {
472 mateuszvis 319
  char *cmdfile = buff + 512;
458 mateuszvis 320
  const char far *pathptr;
321
  int lookup;
322
  unsigned short i;
323
  const char *ext;
508 mateuszvis 324
  char *cmd = buff + 1024;
479 mateuszvis 325
  const char *cmdtail;
461 mateuszvis 326
  char far *rmod_execprog = MK_FP(rmod->rmodseg, RMOD_OFFSET_EXECPROG);
327
  char far *rmod_cmdtail = MK_FP(rmod->rmodseg, 0x81);
328
  _Packed struct {
329
    unsigned short envseg;
330
    unsigned long cmdtail;
331
    unsigned long fcb1;
332
    unsigned long fcb2;
333
  } far *ExecParam = MK_FP(rmod->rmodseg, RMOD_OFFSET_EXECPARAM);
364 mateuszvis 334
 
472 mateuszvis 335
  /* find cmd and cmdtail */
336
  i = 0;
337
  cmdtail = cmdline;
338
  while (*cmdtail == ' ') cmdtail++; /* skip any leading spaces */
339
  while ((*cmdtail != ' ') && (*cmdtail != '/') && (*cmdtail != '+') && (*cmdtail != 0)) {
340
    cmd[i++] = *cmdtail;
341
    cmdtail++;
342
  }
343
  cmd[i] = 0;
364 mateuszvis 344
 
458 mateuszvis 345
  /* is this a command in curdir? */
472 mateuszvis 346
  lookup = lookup_cmd(cmdfile, cmd, NULL, &ext);
458 mateuszvis 347
  if (lookup == 0) {
348
    /* printf("FOUND LOCAL EXEC FILE: '%s'\r\n", cmdfile); */
349
    goto RUNCMDFILE;
350
  } else if (lookup == -2) {
351
    /* puts("NOT FOUND"); */
352
    return;
353
  }
354
 
355
  /* try matching something in PATH */
356
  pathptr = env_lookup_val(envseg, "PATH");
357
 
358
  /* try each path in %PATH% */
571 mateuszvis 359
  while (pathptr) {
458 mateuszvis 360
    for (i = 0;; i++) {
361
      buff[i] = *pathptr;
362
      if ((buff[i] == 0) || (buff[i] == ';')) break;
363
      pathptr++;
364
    }
365
    buff[i] = 0;
472 mateuszvis 366
    lookup = lookup_cmd(cmdfile, cmd, buff, &ext);
571 mateuszvis 367
    if (lookup == 0) goto RUNCMDFILE;
458 mateuszvis 368
    if (lookup == -2) return;
369
    if (*pathptr == ';') {
370
      pathptr++;
371
    } else {
571 mateuszvis 372
      break;
458 mateuszvis 373
    }
374
  }
375
 
571 mateuszvis 376
  /* last chance: is it an executable link? (trim extension from cmd first) */
377
  for (i = 0; (cmd[i] != 0) && (cmd[i] != '.') && (i < 9); i++) buff[128 + i] = cmd[i];
378
  buff[128 + i] = 0;
379
  if ((i < 9) && (link_computefname(buff, buff + 128, envseg) == 0)) {
380
    /* try opening the link file (if it exists) and read it into buff */
381
    i = 0;
382
    _asm {
383
      push ax
384
      push bx
385
      push cx
386
      push dx
387
 
388
      mov ax, 0x3d00  /* DOS 2+ - OPEN EXISTING FILE, READ-ONLY */
389
      mov dx, buff    /* file name */
390
      int 0x21
391
      jc ERR_FOPEN
392
      /* file handle in AX, read from file now */
393
      mov bx, ax      /* file handle */
394
      mov ah, 0x3f    /* Read from file via handle bx */
395
      mov cx, 128     /* up to 128 bytes */
396
      /* mov dx, buff */ /* dest buffer (already set) */
397
      int 0x21        /* read up to 256 bytes from file and write to buff */
398
      jc ERR_READ
399
      mov i, ax
400
      ERR_READ:
401
      mov ah, 0x3e    /* close file handle in BX */
402
      int 0x21
403
      ERR_FOPEN:
404
 
405
      pop dx
406
      pop cx
407
      pop bx
408
      pop ax
409
    }
410
 
411
    /* did I read anything? */
412
    if (i != 0) {
413
      buff[i] = 0;
414
      /* trim buff at first \n or \r, just in case someone fiddled with the
415
       * link file using a text editor */
416
      for (i = 0; (buff[i] != 0) && (buff[i] != '\r') && (buff[i] != '\n'); i++);
417
      buff[i] = 0;
418
      /* lookup check */
419
      if (buff[0] != 0) {
420
        lookup = lookup_cmd(cmdfile, cmd, buff, &ext);
421
        if (lookup == 0) goto RUNCMDFILE;
422
      }
423
    }
424
  }
425
 
426
  /* all failed (ie. executable file not found) */
427
  return;
428
 
458 mateuszvis 429
  RUNCMDFILE:
430
 
469 mateuszvis 431
  /* special handling of batch files */
432
  if ((ext != NULL) && (imatch(ext, "bat"))) {
949 mateusz.vi 433
    /* free the bat-context linked list, if present, and replace it with a new entry */
434
    while (rmod->bat != NULL) {
435
      struct batctx far *victim = rmod->bat;
436
      rmod->bat = rmod->bat->parent;
437
      rmod_ffree(victim);
438
    }
439
    rmod->bat = rmod_fmalloc(sizeof(struct batctx), rmod->rmodseg, "SVBATCTX");
440
    if (rmod->bat == NULL) {
441
      outputnl("INTERNAL ERR: OUT OF MEMORY");
442
      return;
443
    }
444
    _fmemset(rmod->bat, 0, sizeof(struct batctx));
445
 
469 mateuszvis 446
    /* copy truename of the bat file to rmod buff */
949 mateusz.vi 447
    _fstrcpy(rmod->bat->fname, cmdfile);
508 mateuszvis 448
 
449
    /* explode args of the bat file and store them in rmod buff */
450
    cmd_explode(buff, cmdline, NULL);
949 mateusz.vi 451
    _fmemcpy(rmod->bat->argv, buff, sizeof(rmod->bat->argv));
508 mateuszvis 452
 
469 mateuszvis 453
    /* reset the 'next line to execute' counter */
949 mateusz.vi 454
    rmod->bat->nextline = 0;
474 mateuszvis 455
    /* remember the echo flag (in case bat file disables echo) */
456
    rmod->flags &= ~FLAG_ECHO_BEFORE_BAT;
475 mateuszvis 457
    if (rmod->flags & FLAG_ECHOFLAG) rmod->flags |= FLAG_ECHO_BEFORE_BAT;
469 mateuszvis 458
    return;
459
  }
460
 
517 mateuszvis 461
  /* copy full filename to execute, along with redirected files (if any) */
548 mateuszvis 462
  _fstrcpy(rmod_execprog, cmdfile);
463
 
464
  /* copy stdin file if a redirection is needed */
517 mateuszvis 465
  if (redir->stdinfile) {
548 mateuszvis 466
    char far *farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDINFILE);
576 mateuszvis 467
    char far *delstdin = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDIN_DEL);
548 mateuszvis 468
    _fstrcpy(farptr, redir->stdinfile);
576 mateuszvis 469
    if (delete_stdin_file) {
470
      *delstdin = redir->stdinfile[0];
471
    } else {
472
      *delstdin = 0;
473
    }
517 mateuszvis 474
  }
548 mateuszvis 475
 
476
  /* same for stdout file */
517 mateuszvis 477
  if (redir->stdoutfile) {
548 mateuszvis 478
    char far *farptr = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDOUTFILE);
479
    unsigned short far *farptr16 = MK_FP(rmod->rmodseg, RMOD_OFFSET_STDOUTAPP);
480
    _fstrcpy(farptr, redir->stdoutfile);
517 mateuszvis 481
    /* openflag */
548 mateuszvis 482
    *farptr16 = redir->stdout_openflag;
517 mateuszvis 483
  }
461 mateuszvis 484
 
485
  /* copy cmdtail to rmod's PSP and compute its len */
486
  for (i = 0; cmdtail[i] != 0; i++) rmod_cmdtail[i] = cmdtail[i];
487
  rmod_cmdtail[i] = '\r';
488
  rmod_cmdtail[-1] = i;
489
 
490
  /* set up rmod to execute the command */
491
 
464 mateuszvis 492
  ExecParam->envseg = envseg;
461 mateuszvis 493
  ExecParam->cmdtail = (unsigned long)MK_FP(rmod->rmodseg, 0x80); /* farptr, must be in PSP format (lenbyte args \r) */
494
  ExecParam->fcb1 = 0; /* TODO farptr */
495
  ExecParam->fcb2 = 0; /* TODO farptr */
496
  exit(0); /* let rmod do the job now */
364 mateuszvis 497
}
498
 
499
 
367 mateuszvis 500
static void set_comspec_to_self(unsigned short envseg) {
501
  unsigned short *psp_envseg = (void *)(0x2c); /* pointer to my env segment field in the PSP */
502
  char far *myenv = MK_FP(*psp_envseg, 0);
503
  unsigned short varcount;
504
  char buff[256] = "COMSPEC=";
505
  char *buffptr = buff + 8;
506
  /* who am i? look into my own environment, at the end of it should be my EXEPATH string */
507
  while (*myenv != 0) {
508
    /* consume a NULL-terminated string */
509
    while (*myenv != 0) myenv++;
510
    /* move to next string */
511
    myenv++;
512
  }
513
  /* get next word, if 1 then EXEPATH follows */
514
  myenv++;
515
  varcount = *myenv;
516
  myenv++;
517
  varcount |= (*myenv << 8);
518
  myenv++;
519
  if (varcount != 1) return; /* NO EXEPATH FOUND */
520
  while (*myenv != 0) {
521
    *buffptr = *myenv;
522
    buffptr++;
523
    myenv++;
524
  }
525
  *buffptr = 0;
526
  /* printf("EXEPATH: '%s'\r\n", buff); */
527
  env_setvar(envseg, buff);
528
}
529
 
530
 
450 mateuszvis 531
/* wait for user input */
532
static void cmdline_getinput(unsigned short inpseg, unsigned short inpoff) {
533
  _asm {
534
    push ax
535
    push bx
536
    push cx
537
    push dx
538
    push ds
539
 
540
    /* is DOSKEY support present? (INT 2Fh, AX=4800h, returns non-zero in AL if present) */
541
    mov ax, 0x4800
542
    int 0x2f
543
    mov bl, al /* save doskey status in BL */
544
 
545
    /* set up buffered input to inpseg:inpoff */
546
    mov ax, inpseg
547
    push ax
548
    pop ds
549
    mov dx, inpoff
550
 
551
    /* execute either DOS input or DOSKEY */
552
    test bl, bl /* zf set if no DOSKEY present */
553
    jnz DOSKEY
554
 
555
    mov ah, 0x0a
556
    int 0x21
557
    jmp short DONE
558
 
559
    DOSKEY:
560
    mov ax, 0x4810
561
    int 0x2f
562
 
563
    DONE:
564
    /* terminate command with a CR/LF */
565
    mov ah, 0x02 /* display character in dl */
566
    mov dl, 0x0d
567
    int 0x21
568
    mov dl, 0x0a
569
    int 0x21
570
 
571
    pop ds
572
    pop dx
573
    pop cx
574
    pop bx
575
    pop ax
576
  }
577
}
578
 
579
 
479 mateuszvis 580
/* fetches a line from batch file and write it to buff (NULL-terminated),
581
 * increments rmod counter and returns 0 on success. */
484 mateuszvis 582
static int getbatcmd(char *buff, unsigned char buffmaxlen, struct rmod_props far *rmod) {
469 mateuszvis 583
  unsigned short i;
949 mateusz.vi 584
  unsigned short batname_seg = FP_SEG(rmod->bat->fname);
585
  unsigned short batname_off = FP_OFF(rmod->bat->fname);
586
  unsigned short filepos_cx = rmod->bat->nextline >> 16;
587
  unsigned short filepos_dx = rmod->bat->nextline & 0xffff;
474 mateuszvis 588
  unsigned char blen = 0;
505 mateuszvis 589
  unsigned short errv = 0;
474 mateuszvis 590
 
591
  /* open file, jump to offset filpos, and read data into buff.
592
   * result in blen (unchanged if EOF or failure). */
593
  _asm {
594
    push ax
595
    push bx
596
    push cx
597
    push dx
598
 
599
    /* open file (read-only) */
505 mateuszvis 600
    mov bx, 0xffff        /* preset BX to 0xffff to detect error conditions */
474 mateuszvis 601
    mov dx, batname_off
602
    mov ax, batname_seg
603
    push ds     /* save DS */
604
    mov ds, ax
605
    mov ax, 0x3d00
606
    int 0x21    /* handle in ax on success */
607
    pop ds      /* restore DS */
505 mateuszvis 608
    jc ERR
474 mateuszvis 609
    mov bx, ax  /* save handle to bx */
610
 
611
    /* jump to file offset CX:DX */
612
    mov ax, 0x4200
613
    mov cx, filepos_cx
614
    mov dx, filepos_dx
615
    int 0x21  /* CF clear on success, DX:AX set to cur pos */
505 mateuszvis 616
    jc ERR
474 mateuszvis 617
 
618
    /* read the line into buff */
619
    mov ah, 0x3f
484 mateuszvis 620
    xor ch, ch
621
    mov cl, buffmaxlen
474 mateuszvis 622
    mov dx, buff
623
    int 0x21 /* CF clear on success, AX=number of bytes read */
505 mateuszvis 624
    jc ERR
474 mateuszvis 625
    mov blen, al
505 mateuszvis 626
    jmp CLOSEANDQUIT
474 mateuszvis 627
 
505 mateuszvis 628
    ERR:
629
    mov errv, ax
630
 
474 mateuszvis 631
    CLOSEANDQUIT:
505 mateuszvis 632
    /* close file (if bx contains a handle) */
633
    cmp bx, 0xffff
634
    je DONE
474 mateuszvis 635
    mov ah, 0x3e
636
    int 0x21
637
 
638
    DONE:
639
    pop dx
640
    pop cx
641
    pop bx
642
    pop ax
469 mateuszvis 643
  }
470 mateuszvis 644
 
474 mateuszvis 645
  /* printf("blen=%u filepos_cx=%u filepos_dx=%u\r\n", blen, filepos_cx, filepos_dx); */
470 mateuszvis 646
 
538 mateuszvis 647
  if (errv != 0) nls_outputnl_doserr(errv);
505 mateuszvis 648
 
474 mateuszvis 649
  /* on EOF - abort processing the bat file */
650
  if (blen == 0) goto OOPS;
651
 
652
  /* find nearest \n to inc batch offset and replace \r by NULL terminator
653
   * I support all CR/LF, CR- and LF-terminated batch files */
654
  for (i = 0; i < blen; i++) {
655
    if ((buff[i] == '\r') || (buff[i] == '\n')) {
949 mateusz.vi 656
      if ((buff[i] == '\r') && ((i+1) < blen) && (buff[i+1] == '\n')) rmod->bat->nextline += 1;
474 mateuszvis 657
      break;
658
    }
659
  }
660
  buff[i] = 0;
949 mateusz.vi 661
  rmod->bat->nextline += i + 1;
474 mateuszvis 662
 
663
  return(0);
664
 
665
  OOPS:
949 mateusz.vi 666
  rmod->bat->fname[0] = 0;
667
  rmod->bat->nextline = 0;
474 mateuszvis 668
  return(-1);
469 mateuszvis 669
}
670
 
671
 
507 mateuszvis 672
/* replaces %-variables in a BAT line with resolved values:
673
 * %PATH%       -> replaced by the contend of the PATH env variable
674
 * %UNDEFINED%  -> undefined variables are replaced by nothing ("")
675
 * %NOTCLOSED   -> NOTCLOSED
676
 * %1           -> first argument of the batch file (or nothing if no arg) */
677
static void batpercrepl(char *res, unsigned short ressz, const char *line, const struct rmod_props far *rmod, unsigned short envseg) {
678
  unsigned short lastperc = 0xffff;
679
  unsigned short reslen = 0;
680
 
681
  if (ressz == 0) return;
682
  ressz--; /* reserve one byte for the NULL terminator */
683
 
684
  for (; (reslen < ressz) && (*line != 0); line++) {
685
    /* if not a percent, I don't care */
686
    if (*line != '%') {
687
      res[reslen++] = *line;
688
      continue;
689
    }
690
 
691
    /* *** perc char handling *** */
692
 
693
    /* closing perc? */
694
    if (lastperc != 0xffff) {
695
      /* %% is '%' */
696
      if (lastperc == reslen) {
697
        res[reslen++] = '%';
698
      } else {   /* otherwise variable name */
699
        const char far *ptr;
700
        res[reslen] = 0;
701
        reslen = lastperc;
702
        ptr = env_lookup_val(envseg, res + reslen);
703
        if (ptr != NULL) {
704
          while ((*ptr != 0) && (reslen < ressz)) {
705
            res[reslen++] = *ptr;
706
            ptr++;
707
          }
708
        }
709
      }
710
      lastperc = 0xffff;
711
      continue;
712
    }
713
 
714
    /* digit? (bat arg) */
715
    if ((line[1] >= '0') && (line[1] <= '9')) {
508 mateuszvis 716
      unsigned short argid = line[1] - '0';
717
      unsigned short i;
949 mateusz.vi 718
      const char far *argv = "";
719
      if ((rmod != NULL) && (rmod->bat != NULL)) argv = rmod->bat->argv;
508 mateuszvis 720
 
721
      /* locate the proper arg */
722
      for (i = 0; i != argid; i++) {
723
        /* if string is 0, then end of list reached */
724
        if (*argv == 0) break;
725
        /* jump to next arg */
726
        while (*argv != 0) argv++;
727
        argv++;
728
      }
729
 
730
      /* copy the arg to result */
731
      for (i = 0; (argv[i] != 0) && (reslen < ressz); i++) {
732
        res[reslen++] = argv[i];
733
      }
507 mateuszvis 734
      line++;  /* skip the digit */
735
      continue;
736
    }
737
 
738
    /* opening perc */
739
    lastperc = reslen;
740
 
741
  }
742
 
743
  res[reslen] = 0;
744
}
745
 
746
 
443 mateuszvis 747
int main(void) {
372 mateuszvis 748
  static struct config cfg;
749
  static unsigned short far *rmod_envseg;
750
  static unsigned short far *lastexitcode;
449 mateuszvis 751
  static struct rmod_props far *rmod;
500 mateuszvis 752
  static char cmdlinebuf[CMDLINE_MAXLEN + 2]; /* 1 extra byte for 0-terminator and another for memguard */
479 mateuszvis 753
  static char *cmdline;
517 mateuszvis 754
  static struct redir_data redirprops;
533 mateuszvis 755
  static enum cmd_result cmdres;
543 mateuszvis 756
  static unsigned short i; /* general-purpose variable for short-lived things */
576 mateuszvis 757
  static unsigned char delete_stdin_file;
349 mateuszvis 758
 
479 mateuszvis 759
  rmod = rmod_find(BUFFER_len);
449 mateuszvis 760
  if (rmod == NULL) {
485 mateuszvis 761
    /* look at command line parameters (in case env size if set there) */
762
    parse_argv(&cfg);
479 mateuszvis 763
    rmod = rmod_install(cfg.envsiz, BUFFER, BUFFER_len);
449 mateuszvis 764
    if (rmod == NULL) {
369 mateuszvis 765
      outputnl("ERROR: rmod_install() failed");
349 mateuszvis 766
      return(1);
767
    }
475 mateuszvis 768
    /* copy flags to rmod's storage (and enable ECHO) */
769
    rmod->flags = cfg.flags | FLAG_ECHOFLAG;
465 mateuszvis 770
    /* printf("rmod installed at %Fp\r\n", rmod); */
572 mateuszvis 771
    rmod->version = BYTE_VERSION;
349 mateuszvis 772
  } else {
465 mateuszvis 773
    /* printf("rmod found at %Fp\r\n", rmod); */
774
    /* if I was spawned by rmod and FLAG_EXEC_AND_QUIT is set, then I should
775
     * die asap, because the command has been executed already, so I no longer
776
     * have a purpose in life */
469 mateuszvis 777
    if (rmod->flags & FLAG_EXEC_AND_QUIT) sayonara(rmod);
572 mateuszvis 778
    /* */
779
    if (rmod->version != BYTE_VERSION) {
780
      outputnl("SVARCOM VERSION CHANGED. SYSTEM HALTED. PLEASE REBOOT YOUR COMPUTER.");
781
      _asm {
782
        HALT:
783
        hlt
784
        jmp HALT
785
      }
786
    }
349 mateuszvis 787
  }
788
 
490 mateuszvis 789
  /* install a few guardvals in memory to detect some cases of overflows */
500 mateuszvis 790
  memguard_set(cmdlinebuf);
490 mateuszvis 791
 
465 mateuszvis 792
  rmod_envseg = MK_FP(rmod->rmodseg, RMOD_OFFSET_ENVSEG);
793
  lastexitcode = MK_FP(rmod->rmodseg, RMOD_OFFSET_LEXITCODE);
350 mateuszvis 794
 
367 mateuszvis 795
  /* make COMPSEC point to myself */
796
  set_comspec_to_self(*rmod_envseg);
797
 
369 mateuszvis 798
/*  {
350 mateuszvis 799
    unsigned short envsiz;
800
    unsigned short far *sizptr = MK_FP(*rmod_envseg - 1, 3);
801
    envsiz = *sizptr;
802
    envsiz *= 16;
465 mateuszvis 803
    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 804
  }*/
349 mateuszvis 805
 
494 mateuszvis 806
  /* on /P check for the presence of AUTOEXEC.BAT and execute it if found,
807
   * but skip this check if /D was also passed */
808
  if ((cfg.flags & (FLAG_PERMANENT | FLAG_SKIP_AUTOEXEC)) == FLAG_PERMANENT) {
483 mateuszvis 809
    if (file_getattr("AUTOEXEC.BAT") >= 0) cfg.execcmd = "AUTOEXEC.BAT";
810
  }
811
 
443 mateuszvis 812
  do {
480 mateuszvis 813
    /* terminate previous command with a CR/LF if ECHO ON (but not during BAT processing) */
949 mateusz.vi 814
    if ((rmod->flags & FLAG_ECHOFLAG) && (rmod->bat == NULL)) outputnl("");
474 mateuszvis 815
 
816
    SKIP_NEWLINE:
817
 
490 mateuszvis 818
    /* memory check */
500 mateuszvis 819
    memguard_check(rmod->rmodseg, cmdlinebuf);
474 mateuszvis 820
 
500 mateuszvis 821
    /* preset cmdline to point at the dedicated buffer */
822
    cmdline = cmdlinebuf;
490 mateuszvis 823
 
437 mateuszvis 824
    /* (re)load translation strings if needed */
825
    nls_langreload(BUFFER, *rmod_envseg);
826
 
543 mateuszvis 827
    /* load awaiting command, if any (used to run piped commands) */
828
    if (rmod->awaitingcmd[0] != 0) {
829
      _fstrcpy(cmdline, rmod->awaitingcmd);
830
      rmod->awaitingcmd[0] = 0;
576 mateuszvis 831
      delete_stdin_file = 1;
543 mateuszvis 832
      goto EXEC_CMDLINE;
576 mateuszvis 833
    } else {
834
      delete_stdin_file = 0;
543 mateuszvis 835
    }
836
 
443 mateuszvis 837
    /* skip user input if I have a command to exec (/C or /K) */
838
    if (cfg.execcmd != NULL) {
839
      cmdline = cfg.execcmd;
840
      cfg.execcmd = NULL;
841
      goto EXEC_CMDLINE;
842
    }
843
 
469 mateuszvis 844
    /* if batch file is being executed -> fetch next line */
949 mateusz.vi 845
    if (rmod->bat != NULL) {
507 mateuszvis 846
      if (getbatcmd(BUFFER, CMDLINE_MAXLEN, rmod) != 0) { /* end of batch */
949 mateusz.vi 847
        struct batctx far *victim = rmod->bat;
848
        rmod->bat = rmod->bat->parent;
849
        rmod_ffree(victim);
850
        /* end of batch? then restore echo flag as it was before running the bat file */
851
        if (rmod->bat == NULL) {
852
          rmod->flags &= ~FLAG_ECHOFLAG;
853
          if (rmod->flags & FLAG_ECHO_BEFORE_BAT) rmod->flags |= FLAG_ECHOFLAG;
854
        }
474 mateuszvis 855
        continue;
856
      }
507 mateuszvis 857
      /* %-decoding of variables (%PATH%, %1, %%...), result in cmdline */
858
      batpercrepl(cmdline, CMDLINE_MAXLEN, BUFFER, rmod, *rmod_envseg);
480 mateuszvis 859
      /* skip any leading spaces */
860
      while (*cmdline == ' ') cmdline++;
474 mateuszvis 861
      /* output prompt and command on screen if echo on and command is not
862
       * inhibiting it with the @ prefix */
479 mateuszvis 863
      if ((rmod->flags & FLAG_ECHOFLAG) && (cmdline[0] != '@')) {
474 mateuszvis 864
        build_and_display_prompt(BUFFER, *rmod_envseg);
479 mateuszvis 865
        outputnl(cmdline);
474 mateuszvis 866
      }
479 mateuszvis 867
      /* skip the @ prefix if present, it is no longer useful */
868
      if (cmdline[0] == '@') cmdline++;
469 mateuszvis 869
    } else {
474 mateuszvis 870
      /* interactive mode: display prompt (if echo enabled) and wait for user
871
       * command line */
475 mateuszvis 872
      if (rmod->flags & FLAG_ECHOFLAG) build_and_display_prompt(BUFFER, *rmod_envseg);
474 mateuszvis 873
      /* collect user input */
469 mateuszvis 874
      cmdline_getinput(FP_SEG(rmod->inputbuf), FP_OFF(rmod->inputbuf));
479 mateuszvis 875
      /* copy it to local cmdline */
876
      if (rmod->inputbuf[1] != 0) _fmemcpy(cmdline, rmod->inputbuf + 2, rmod->inputbuf[1]);
877
      cmdline[(unsigned)(rmod->inputbuf[1])] = 0; /* zero-terminate local buff (oriignal is '\r'-terminated) */
469 mateuszvis 878
    }
349 mateuszvis 879
 
405 mateuszvis 880
    /* if nothing entered, loop again (but without appending an extra CR/LF) */
479 mateuszvis 881
    if (cmdline[0] == 0) goto SKIP_NEWLINE;
349 mateuszvis 882
 
443 mateuszvis 883
    /* I jump here when I need to exec an initial command (/C or /K) */
884
    EXEC_CMDLINE:
885
 
364 mateuszvis 886
    /* move pointer forward to skip over any leading spaces */
887
    while (*cmdline == ' ') cmdline++;
349 mateuszvis 888
 
367 mateuszvis 889
    /* update rmod's ptr to COMPSPEC so it is always up to date */
465 mateuszvis 890
    rmod_updatecomspecptr(rmod->rmodseg, *rmod_envseg);
367 mateuszvis 891
 
402 mateuszvis 892
    /* handle redirections (if any) */
577 mateuszvis 893
    i = redir_parsecmd(&redirprops, cmdline, rmod->awaitingcmd, *rmod_envseg);
543 mateuszvis 894
    if (i != 0) {
895
      nls_outputnl_doserr(i);
896
      rmod->awaitingcmd[0] = 0;
897
      continue;
898
    }
402 mateuszvis 899
 
364 mateuszvis 900
    /* try matching (and executing) an internal command */
576 mateuszvis 901
    cmdres = cmd_process(rmod, *rmod_envseg, cmdline, BUFFER, sizeof(BUFFER), &redirprops, delete_stdin_file);
533 mateuszvis 902
    if ((cmdres == CMD_OK) || (cmdres == CMD_FAIL)) {
443 mateuszvis 903
      /* internal command executed */
904
      continue;
533 mateuszvis 905
    } else if (cmdres == CMD_CHANGED) { /* cmdline changed, needs to be reprocessed */
906
      goto EXEC_CMDLINE;
907
    } else if (cmdres == CMD_NOTFOUND) {
908
      /* this was not an internal command, try matching an external command */
576 mateuszvis 909
      run_as_external(BUFFER, cmdline, *rmod_envseg, rmod, &redirprops, delete_stdin_file);
533 mateuszvis 910
      /* perhaps this is a newly launched BAT file */
949 mateusz.vi 911
      if ((rmod->bat != NULL) && (rmod->bat->nextline == 0)) goto SKIP_NEWLINE;
533 mateuszvis 912
      /* run_as_external() does not return on success, if I am still alive then
913
       * external command failed to execute */
914
      outputnl("Bad command or file name");
915
      continue;
353 mateuszvis 916
    }
352 mateuszvis 917
 
533 mateuszvis 918
    /* I should never ever land here */
919
    outputnl("INTERNAL ERR: INVALID CMDRES");
349 mateuszvis 920
 
449 mateuszvis 921
  } while ((rmod->flags & FLAG_EXEC_AND_QUIT) == 0);
349 mateuszvis 922
 
449 mateuszvis 923
  sayonara(rmod);
349 mateuszvis 924
  return(0);
925
}