Subversion Repositories SvarDOS

Rev

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