Subversion Repositories SvarDOS

Rev

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