Subversion Repositories SvarDOS

Rev

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