Subversion Repositories SvarDOS

Rev

Rev 493 | Rev 500 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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