Subversion Repositories SvarDOS

Rev

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