Subversion Repositories SvarDOS

Rev

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