Subversion Repositories SvarDOS

Rev

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