Subversion Repositories SvarDOS

Rev

Rev 444 | Rev 449 | 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
#define FLAG_EXEC_AND_QUIT 1
83
 
349 mateuszvis 84
struct config {
443 mateuszvis 85
  unsigned char flags;
86
  char *execcmd;
410 mateuszvis 87
  unsigned short envsiz;
443 mateuszvis 88
};
349 mateuszvis 89
 
90
 
443 mateuszvis 91
/* parses command line the hard way (directly from PSP) */
92
static void parse_argv(struct config *cfg) {
93
  unsigned short i;
94
  const unsigned char *cmdlinelen = (unsigned char *)0x80;
95
  char *cmdline = (char *)0x81;
96
 
349 mateuszvis 97
  memset(cfg, 0, sizeof(*cfg));
350 mateuszvis 98
 
443 mateuszvis 99
  /* set a NULL terminator on cmdline */
100
  cmdline[*cmdlinelen] = 0;
101
 
102
  for (i = 0;;) {
103
 
104
    /* skip over any leading spaces */
105
    for (;; i++) {
106
      if (cmdline[i] == 0) return;
107
      if (cmdline[i] != ' ') break;
349 mateuszvis 108
    }
443 mateuszvis 109
 
110
    if (cmdline[i] != '/') {
111
      output("Invalid parameter: ");
112
      outputnl(cmdline + i);
113
      /* exit(1); */
114
    } else {
115
      i++;        /* skip the slash */
116
      switch (cmdline[i]) {
117
        case 'c': /* /C = execute command and quit */
118
        case 'C':
119
          cfg->flags |= FLAG_EXEC_AND_QUIT;
120
          /* FALLTHRU */
121
        case 'k': /* /K = execute command and keep running */
122
        case 'K':
123
          cfg->execcmd = cmdline + i + 1;
124
          return;
125
 
126
        case 'e': /* preset the initial size of the environment block */
127
        case 'E':
128
          i++;
129
          if (cmdline[i] == ':') i++; /* could be /E:size */
130
          atous(&(cfg->envsiz), cmdline + i);
131
          if (cfg->envsiz < 64) cfg->envsiz = 0;
132
          break;
133
 
444 mateuszvis 134
        case '?':
135
          outputnl("Starts the SvarCOM command interpreter");
136
          outputnl("");
137
          outputnl("COMMAND /E:nnn [/[C|K] command]");
138
          outputnl("");
139
          outputnl("/E:nnn     Sets the environment size to nnn bytes");
140
          outputnl("/C         Executes the specified command and returns");
141
          outputnl("/K         Executes the specified command and continues running");
142
          exit(1);
143
          break;
144
 
443 mateuszvis 145
        default:
146
          output("Invalid switch:");
147
          output(" ");
148
          outputnl(cmdline + i);
149
          exit(1);
150
          break;
410 mateuszvis 151
      }
350 mateuszvis 152
    }
443 mateuszvis 153
 
154
    /* move to next argument or quit processing if end of cmdline */
155
    for (i++; (cmdline[i] != 0) && (cmdline[i] != ' ') && (cmdline[i] != '/'); i++);
156
 
349 mateuszvis 157
  }
158
}
159
 
160
 
370 mateuszvis 161
static void buildprompt(char *s, unsigned short envseg) {
162
  /* locate the prompt variable or use the default pattern */
438 mateuszvis 163
  const char far *fmt = env_lookup_val(envseg, "PROMPT");
370 mateuszvis 164
  if ((fmt == NULL) || (*fmt == 0)) fmt = "$p$g"; /* fallback to default if empty */
165
  /* build the prompt string based on pattern */
354 mateuszvis 166
  for (; *fmt != 0; fmt++) {
167
    if (*fmt != '$') {
168
      *s = *fmt;
169
      s++;
170
      continue;
171
    }
172
    /* escape code ($P, etc) */
173
    fmt++;
174
    switch (*fmt) {
175
      case 'Q':  /* $Q = = (equal sign) */
176
      case 'q':
177
        *s = '=';
178
        s++;
179
        break;
180
      case '$':  /* $$ = $ (dollar sign) */
181
        *s = '$';
182
        s++;
183
        break;
184
      case 'T':  /* $t = current time */
185
      case 't':
186
        s += sprintf(s, "00:00"); /* TODO */
187
        break;
188
      case 'D':  /* $D = current date */
189
      case 'd':
190
        s += sprintf(s, "1985-07-29"); /* TODO */
191
        break;
192
      case 'P':  /* $P = current drive and path */
193
      case 'p':
194
        _asm {
195
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
196
          int 0x21
197
          mov bx, s
198
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
199
        }
200
        *s += 'A';
201
        s++;
202
        *s = ':';
203
        s++;
204
        *s = '\\';
205
        s++;
206
        _asm {
207
          mov ah, 0x47    /* DOS 2+ - CWD - GET CURRENT DIRECTORY */
208
          xor dl,dl       /* DL = drive number (00h = default, 01h = A:, etc) */
209
          mov si, s       /* DS:SI -> 64-byte buffer for ASCIZ pathname */
210
          int 0x21
211
        }
212
        while (*s != 0) s++; /* move ptr forward to end of pathname */
213
        break;
214
      case 'V':  /* $V = DOS version number */
215
      case 'v':
216
        s += sprintf(s, "VER"); /* TODO */
217
        break;
218
      case 'N':  /* $N = current drive */
219
      case 'n':
220
        _asm {
221
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
222
          int 0x21
223
          mov bx, s
224
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
225
        }
226
        *s += 'A';
227
        s++;
228
        break;
229
      case 'G':  /* $G = > (greater-than sign) */
230
      case 'g':
231
        *s = '>';
232
        s++;
233
        break;
234
      case 'L':  /* $L = < (less-than sign) */
235
      case 'l':
236
        *s = '<';
237
        s++;
238
        break;
239
      case 'B':  /* $B = | (pipe) */
240
      case 'b':
241
        *s = '|';
242
        s++;
243
        break;
244
      case 'H':  /* $H = backspace (erases previous character) */
245
      case 'h':
246
        *s = '\b';
247
        s++;
248
        break;
249
      case 'E':  /* $E = Escape code (ASCII 27) */
250
      case 'e':
251
        *s = 27;
252
        s++;
253
        break;
254
      case '_':  /* $_ = CR+LF */
255
        *s = '\r';
256
        s++;
257
        *s = '\n';
258
        s++;
259
        break;
260
    }
261
  }
262
  *s = '$';
263
}
349 mateuszvis 264
 
265
 
364 mateuszvis 266
static void run_as_external(const char far *cmdline) {
267
  char buff[256];
268
  char const *argvlist[256];
269
  int i, n;
270
  /* copy buffer to a near var (incl. trailing CR), insert a space before
271
     every slash to make sure arguments are well separated */
272
  n = 0;
273
  i = 0;
274
  for (;;) {
275
    if (cmdline[i] == '/') buff[n++] = ' ';
276
    buff[n++] = cmdline[i++];
277
    if (buff[n] == 0) break;
278
  }
279
 
280
  cmd_explode(buff, cmdline, argvlist);
281
 
282
  /* for (i = 0; argvlist[i] != NULL; i++) printf("arg #%d = '%s'\r\n", i, argvlist[i]); */
283
 
284
  /* must be an external command then. this call should never return, unless
285
   * the other program failed to be executed. */
286
  execvp(argvlist[0], argvlist);
287
}
288
 
289
 
367 mateuszvis 290
static void set_comspec_to_self(unsigned short envseg) {
291
  unsigned short *psp_envseg = (void *)(0x2c); /* pointer to my env segment field in the PSP */
292
  char far *myenv = MK_FP(*psp_envseg, 0);
293
  unsigned short varcount;
294
  char buff[256] = "COMSPEC=";
295
  char *buffptr = buff + 8;
296
  /* who am i? look into my own environment, at the end of it should be my EXEPATH string */
297
  while (*myenv != 0) {
298
    /* consume a NULL-terminated string */
299
    while (*myenv != 0) myenv++;
300
    /* move to next string */
301
    myenv++;
302
  }
303
  /* get next word, if 1 then EXEPATH follows */
304
  myenv++;
305
  varcount = *myenv;
306
  myenv++;
307
  varcount |= (*myenv << 8);
308
  myenv++;
309
  if (varcount != 1) return; /* NO EXEPATH FOUND */
310
  while (*myenv != 0) {
311
    *buffptr = *myenv;
312
    buffptr++;
313
    myenv++;
314
  }
315
  *buffptr = 0;
316
  /* printf("EXEPATH: '%s'\r\n", buff); */
317
  env_setvar(envseg, buff);
318
}
319
 
320
 
443 mateuszvis 321
int main(void) {
372 mateuszvis 322
  static struct config cfg;
323
  static unsigned short rmod_seg;
324
  static unsigned short far *rmod_envseg;
325
  static unsigned short far *lastexitcode;
326
  static unsigned char BUFFER[4096];
349 mateuszvis 327
 
443 mateuszvis 328
  parse_argv(&cfg);
349 mateuszvis 329
 
350 mateuszvis 330
  rmod_seg = rmod_find();
349 mateuszvis 331
  if (rmod_seg == 0xffff) {
350 mateuszvis 332
    rmod_seg = rmod_install(cfg.envsiz);
349 mateuszvis 333
    if (rmod_seg == 0xffff) {
369 mateuszvis 334
      outputnl("ERROR: rmod_install() failed");
349 mateuszvis 335
      return(1);
336
    }
369 mateuszvis 337
/*    printf("rmod installed at seg 0x%04X\r\n", rmod_seg); */
349 mateuszvis 338
  } else {
369 mateuszvis 339
/*    printf("rmod found at seg 0x%04x\r\n", rmod_seg); */
349 mateuszvis 340
  }
341
 
350 mateuszvis 342
  rmod_envseg = MK_FP(rmod_seg, RMOD_OFFSET_ENVSEG);
353 mateuszvis 343
  lastexitcode = MK_FP(rmod_seg, RMOD_OFFSET_LEXITCODE);
350 mateuszvis 344
 
367 mateuszvis 345
  /* make COMPSEC point to myself */
346
  set_comspec_to_self(*rmod_envseg);
347
 
369 mateuszvis 348
/*  {
350 mateuszvis 349
    unsigned short envsiz;
350
    unsigned short far *sizptr = MK_FP(*rmod_envseg - 1, 3);
351
    envsiz = *sizptr;
352
    envsiz *= 16;
353
    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 354
  }*/
349 mateuszvis 355
 
443 mateuszvis 356
  do {
350 mateuszvis 357
    char far *cmdline = MK_FP(rmod_seg, RMOD_OFFSET_INPBUFF + 2);
405 mateuszvis 358
    unsigned char far *echostatus = MK_FP(rmod_seg, RMOD_OFFSET_ECHOFLAG);
349 mateuszvis 359
 
437 mateuszvis 360
    /* (re)load translation strings if needed */
361
    nls_langreload(BUFFER, *rmod_envseg);
362
 
443 mateuszvis 363
    /* skip user input if I have a command to exec (/C or /K) */
364
    if (cfg.execcmd != NULL) {
365
      cmdline = cfg.execcmd;
366
      cfg.execcmd = NULL;
367
      goto EXEC_CMDLINE;
368
    }
369
 
370
    if (*echostatus != 0) outputnl(""); /* terminate the previous command with a CR/LF */
371
 
405 mateuszvis 372
    SKIP_NEWLINE:
373
 
374
    /* print shell prompt (only if ECHO is enabled) */
375
    if (*echostatus != 0) {
372 mateuszvis 376
      char *promptptr = BUFFER;
370 mateuszvis 377
      buildprompt(promptptr, *rmod_envseg);
354 mateuszvis 378
      _asm {
364 mateuszvis 379
        push ax
354 mateuszvis 380
        push dx
381
        mov ah, 0x09
382
        mov dx, promptptr
383
        int 0x21
384
        pop dx
364 mateuszvis 385
        pop ax
354 mateuszvis 386
      }
387
    }
349 mateuszvis 388
 
405 mateuszvis 389
    /* revert input history terminator to \r */
390
    if (cmdline[-1] != 0) {
391
      cmdline[(unsigned short)(cmdline[-1])] = '\r';
392
    }
393
 
349 mateuszvis 394
    /* wait for user input */
395
    _asm {
364 mateuszvis 396
      push ax
397
      push bx
398
      push cx
399
      push dx
349 mateuszvis 400
      push ds
401
 
402
      /* is DOSKEY support present? (INT 2Fh, AX=4800h, returns non-zero in AL if present) */
403
      mov ax, 0x4800
404
      int 0x2f
405
      mov bl, al /* save doskey status in BL */
406
 
407
      /* set up buffered input */
408
      mov ax, rmod_seg
409
      push ax
410
      pop ds
350 mateuszvis 411
      mov dx, RMOD_OFFSET_INPBUFF
349 mateuszvis 412
 
413
      /* execute either DOS input or DOSKEY */
414
      test bl, bl /* zf set if no DOSKEY present */
415
      jnz DOSKEY
416
 
417
      mov ah, 0x0a
418
      int 0x21
419
      jmp short DONE
420
 
421
      DOSKEY:
422
      mov ax, 0x4810
423
      int 0x2f
424
 
425
      DONE:
405 mateuszvis 426
      /* terminate command with a CR/LF */
427
      mov ah, 0x02 /* display character in dl */
428
      mov dl, 0x0d
429
      int 0x21
430
      mov dl, 0x0a
431
      int 0x21
432
 
349 mateuszvis 433
      pop ds
364 mateuszvis 434
      pop dx
435
      pop cx
436
      pop bx
437
      pop ax
349 mateuszvis 438
    }
439
 
405 mateuszvis 440
    /* if nothing entered, loop again (but without appending an extra CR/LF) */
441
    if (cmdline[-1] == 0) goto SKIP_NEWLINE;
349 mateuszvis 442
 
364 mateuszvis 443
    /* replace \r by a zero terminator */
444
    cmdline[(unsigned char)(cmdline[-1])] = 0;
349 mateuszvis 445
 
443 mateuszvis 446
    /* I jump here when I need to exec an initial command (/C or /K) */
447
    EXEC_CMDLINE:
448
 
364 mateuszvis 449
    /* move pointer forward to skip over any leading spaces */
450
    while (*cmdline == ' ') cmdline++;
349 mateuszvis 451
 
367 mateuszvis 452
    /* update rmod's ptr to COMPSPEC so it is always up to date */
453
    rmod_updatecomspecptr(rmod_seg, *rmod_envseg);
454
 
402 mateuszvis 455
    /* handle redirections (if any) */
456
    if (redir_parsecmd(cmdline, BUFFER) != 0) {
457
      outputnl("");
458
      continue;
459
    }
460
 
364 mateuszvis 461
    /* try matching (and executing) an internal command */
443 mateuszvis 462
    if (cmd_process(rmod_seg, *rmod_envseg, cmdline, BUFFER) >= -1) {
463
      /* internal command executed */
464
      redir_revert(); /* revert stdout (in case it was redirected) */
465
      continue;
353 mateuszvis 466
    }
352 mateuszvis 467
 
364 mateuszvis 468
    /* if here, then this was not an internal command */
469
    run_as_external(cmdline);
349 mateuszvis 470
 
402 mateuszvis 471
    /* revert stdout (in case it was redirected) */
472
    redir_revert();
473
 
349 mateuszvis 474
    /* execvp() replaces the current process by the new one
475
    if I am still alive then external command failed to execute */
369 mateuszvis 476
    outputnl("Bad command or file name");
349 mateuszvis 477
 
443 mateuszvis 478
  } while ((cfg.flags & FLAG_EXEC_AND_QUIT) == 0);
349 mateuszvis 479
 
448 mateuszvis 480
  sayonara(rmod_seg);
349 mateuszvis 481
  return(0);
482
}