Subversion Repositories SvarDOS

Rev

Rev 449 | Rev 457 | 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
 
364 mateuszvis 271
static void run_as_external(const char far *cmdline) {
272
  char buff[256];
273
  char const *argvlist[256];
274
  int i, n;
275
  /* copy buffer to a near var (incl. trailing CR), insert a space before
276
     every slash to make sure arguments are well separated */
277
  n = 0;
278
  i = 0;
279
  for (;;) {
280
    if (cmdline[i] == '/') buff[n++] = ' ';
281
    buff[n++] = cmdline[i++];
282
    if (buff[n] == 0) break;
283
  }
284
 
285
  cmd_explode(buff, cmdline, argvlist);
286
 
287
  /* for (i = 0; argvlist[i] != NULL; i++) printf("arg #%d = '%s'\r\n", i, argvlist[i]); */
288
 
289
  /* must be an external command then. this call should never return, unless
290
   * the other program failed to be executed. */
291
  execvp(argvlist[0], argvlist);
292
}
293
 
294
 
367 mateuszvis 295
static void set_comspec_to_self(unsigned short envseg) {
296
  unsigned short *psp_envseg = (void *)(0x2c); /* pointer to my env segment field in the PSP */
297
  char far *myenv = MK_FP(*psp_envseg, 0);
298
  unsigned short varcount;
299
  char buff[256] = "COMSPEC=";
300
  char *buffptr = buff + 8;
301
  /* who am i? look into my own environment, at the end of it should be my EXEPATH string */
302
  while (*myenv != 0) {
303
    /* consume a NULL-terminated string */
304
    while (*myenv != 0) myenv++;
305
    /* move to next string */
306
    myenv++;
307
  }
308
  /* get next word, if 1 then EXEPATH follows */
309
  myenv++;
310
  varcount = *myenv;
311
  myenv++;
312
  varcount |= (*myenv << 8);
313
  myenv++;
314
  if (varcount != 1) return; /* NO EXEPATH FOUND */
315
  while (*myenv != 0) {
316
    *buffptr = *myenv;
317
    buffptr++;
318
    myenv++;
319
  }
320
  *buffptr = 0;
321
  /* printf("EXEPATH: '%s'\r\n", buff); */
322
  env_setvar(envseg, buff);
323
}
324
 
325
 
450 mateuszvis 326
/* wait for user input */
327
static void cmdline_getinput(unsigned short inpseg, unsigned short inpoff) {
328
  _asm {
329
    push ax
330
    push bx
331
    push cx
332
    push dx
333
    push ds
334
 
335
    /* is DOSKEY support present? (INT 2Fh, AX=4800h, returns non-zero in AL if present) */
336
    mov ax, 0x4800
337
    int 0x2f
338
    mov bl, al /* save doskey status in BL */
339
 
340
    /* set up buffered input to inpseg:inpoff */
341
    mov ax, inpseg
342
    push ax
343
    pop ds
344
    mov dx, inpoff
345
 
346
    /* execute either DOS input or DOSKEY */
347
    test bl, bl /* zf set if no DOSKEY present */
348
    jnz DOSKEY
349
 
350
    mov ah, 0x0a
351
    int 0x21
352
    jmp short DONE
353
 
354
    DOSKEY:
355
    mov ax, 0x4810
356
    int 0x2f
357
 
358
    DONE:
359
    /* terminate command with a CR/LF */
360
    mov ah, 0x02 /* display character in dl */
361
    mov dl, 0x0d
362
    int 0x21
363
    mov dl, 0x0a
364
    int 0x21
365
 
366
    pop ds
367
    pop dx
368
    pop cx
369
    pop bx
370
    pop ax
371
  }
372
}
373
 
374
 
443 mateuszvis 375
int main(void) {
372 mateuszvis 376
  static struct config cfg;
377
  static unsigned short rmod_seg;
378
  static unsigned short far *rmod_envseg;
379
  static unsigned short far *lastexitcode;
380
  static unsigned char BUFFER[4096];
449 mateuszvis 381
  static struct rmod_props far *rmod;
349 mateuszvis 382
 
443 mateuszvis 383
  parse_argv(&cfg);
349 mateuszvis 384
 
449 mateuszvis 385
  rmod = rmod_find();
386
  if (rmod == NULL) {
387
    rmod = rmod_install(cfg.envsiz);
388
    if (rmod == NULL) {
369 mateuszvis 389
      outputnl("ERROR: rmod_install() failed");
349 mateuszvis 390
      return(1);
391
    }
449 mateuszvis 392
    /* copy flags to rmod's storage */
393
    rmod->flags = cfg.flags;
369 mateuszvis 394
/*    printf("rmod installed at seg 0x%04X\r\n", rmod_seg); */
349 mateuszvis 395
  } else {
369 mateuszvis 396
/*    printf("rmod found at seg 0x%04x\r\n", rmod_seg); */
349 mateuszvis 397
  }
398
 
449 mateuszvis 399
  rmod_seg = rmod->rmodseg;
350 mateuszvis 400
  rmod_envseg = MK_FP(rmod_seg, RMOD_OFFSET_ENVSEG);
353 mateuszvis 401
  lastexitcode = MK_FP(rmod_seg, RMOD_OFFSET_LEXITCODE);
350 mateuszvis 402
 
367 mateuszvis 403
  /* make COMPSEC point to myself */
404
  set_comspec_to_self(*rmod_envseg);
405
 
369 mateuszvis 406
/*  {
350 mateuszvis 407
    unsigned short envsiz;
408
    unsigned short far *sizptr = MK_FP(*rmod_envseg - 1, 3);
409
    envsiz = *sizptr;
410
    envsiz *= 16;
411
    printf("rmod_inpbuff at %04X:%04X, env_seg at %04X:0000 (env_size = %u bytes)\r\n", rmod_seg, RMOD_OFFSET_INPBUFF, *rmod_envseg, envsiz);
369 mateuszvis 412
  }*/
349 mateuszvis 413
 
443 mateuszvis 414
  do {
450 mateuszvis 415
    char far *cmdline = rmod->inputbuf + 2;
349 mateuszvis 416
 
437 mateuszvis 417
    /* (re)load translation strings if needed */
418
    nls_langreload(BUFFER, *rmod_envseg);
419
 
443 mateuszvis 420
    /* skip user input if I have a command to exec (/C or /K) */
421
    if (cfg.execcmd != NULL) {
422
      cmdline = cfg.execcmd;
423
      cfg.execcmd = NULL;
424
      goto EXEC_CMDLINE;
425
    }
426
 
450 mateuszvis 427
    if (rmod->echoflag != 0) outputnl(""); /* terminate the previous command with a CR/LF */
443 mateuszvis 428
 
405 mateuszvis 429
    SKIP_NEWLINE:
430
 
431
    /* print shell prompt (only if ECHO is enabled) */
450 mateuszvis 432
    if (rmod->echoflag != 0) {
372 mateuszvis 433
      char *promptptr = BUFFER;
370 mateuszvis 434
      buildprompt(promptptr, *rmod_envseg);
354 mateuszvis 435
      _asm {
364 mateuszvis 436
        push ax
354 mateuszvis 437
        push dx
438
        mov ah, 0x09
439
        mov dx, promptptr
440
        int 0x21
441
        pop dx
364 mateuszvis 442
        pop ax
354 mateuszvis 443
      }
444
    }
349 mateuszvis 445
 
405 mateuszvis 446
    /* revert input history terminator to \r */
447
    if (cmdline[-1] != 0) {
448
      cmdline[(unsigned short)(cmdline[-1])] = '\r';
449
    }
450
 
450 mateuszvis 451
    /* wait for user command line */
452
    cmdline_getinput(FP_SEG(rmod->inputbuf), FP_OFF(rmod->inputbuf));
349 mateuszvis 453
 
405 mateuszvis 454
    /* if nothing entered, loop again (but without appending an extra CR/LF) */
455
    if (cmdline[-1] == 0) goto SKIP_NEWLINE;
349 mateuszvis 456
 
364 mateuszvis 457
    /* replace \r by a zero terminator */
458
    cmdline[(unsigned char)(cmdline[-1])] = 0;
349 mateuszvis 459
 
443 mateuszvis 460
    /* I jump here when I need to exec an initial command (/C or /K) */
461
    EXEC_CMDLINE:
462
 
364 mateuszvis 463
    /* move pointer forward to skip over any leading spaces */
464
    while (*cmdline == ' ') cmdline++;
349 mateuszvis 465
 
367 mateuszvis 466
    /* update rmod's ptr to COMPSPEC so it is always up to date */
467
    rmod_updatecomspecptr(rmod_seg, *rmod_envseg);
468
 
402 mateuszvis 469
    /* handle redirections (if any) */
470
    if (redir_parsecmd(cmdline, BUFFER) != 0) {
471
      outputnl("");
472
      continue;
473
    }
474
 
364 mateuszvis 475
    /* try matching (and executing) an internal command */
449 mateuszvis 476
    if (cmd_process(rmod, *rmod_envseg, cmdline, BUFFER) >= -1) {
443 mateuszvis 477
      /* internal command executed */
478
      redir_revert(); /* revert stdout (in case it was redirected) */
479
      continue;
353 mateuszvis 480
    }
352 mateuszvis 481
 
364 mateuszvis 482
    /* if here, then this was not an internal command */
483
    run_as_external(cmdline);
349 mateuszvis 484
 
402 mateuszvis 485
    /* revert stdout (in case it was redirected) */
486
    redir_revert();
487
 
349 mateuszvis 488
    /* execvp() replaces the current process by the new one
489
    if I am still alive then external command failed to execute */
369 mateuszvis 490
    outputnl("Bad command or file name");
349 mateuszvis 491
 
449 mateuszvis 492
  } while ((rmod->flags & FLAG_EXEC_AND_QUIT) == 0);
349 mateuszvis 493
 
449 mateuszvis 494
  sayonara(rmod);
349 mateuszvis 495
  return(0);
496
}