Subversion Repositories SvarDOS

Rev

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