Subversion Repositories SvarDOS

Rev

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