Subversion Repositories SvarDOS

Rev

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