Subversion Repositories SvarDOS

Rev

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

Rev Author Line No. Line
349 mateuszvis 1
/*
2
 * SvarCOM is a command-line interpreter.
3
 *
4
 * a little memory area is allocated as high as possible. it contains:
5
 *  - a signature (like XMS drivers do)
6
 *  - a routine for exec'ing programs
7
 *  - a "last command" buffer for input history
8
 *
9
 * when svarcom starts, it tries locating the routine in memory.
10
 *
11
 * if found:
12
 *   waits for user input and processes it. if execing something is required, set the "next exec" field in routine's memory and quit.
13
 *
14
 * if not found:
15
 *   installs it by creating a new PSP, set int 22 vector to the routine, set my "parent PSP" to the routine
16
 *   and quit.
17
 *
18
 * PSP structure
19
 * http://www.piclist.com/techref/dos/psps.htm
20
 *
21
 *
22
 *
23
 * === MCB ===
24
 *
25
 * each time that DOS allocates memory, it prefixes the allocated memory with
26
 * a 16-bytes structure called a "Memory Control Block" (MCB). This control
27
 * block has the following structure:
28
 *
29
 * Offset  Size     Description
30
 *   00h   byte     'M' =  non-last member of the MCB chain
31
 *                  'Z' = indicates last entry in MCB chain
32
 *                  other values cause "Memory Allocation Failure" on exit
33
 *   01h   word     PSP segment address of the owner (Process Id)
34
 *                  possible values:
35
 *                    0 = free
36
 *                    8 = Allocated by DOS before first user pgm loaded
37
 *                    other = Process Id/PSP segment address of owner
38
 *   03h  word      number of paragraphs related to this MCB (excluding MCB)
39
 *   05h  11 bytes  reserved
40
 *   10h  ...       start of actual allocated memory block
41
 */
42
 
43
#include <i86.h>
44
#include <dos.h>
45
#include <stdio.h>
350 mateuszvis 46
#include <stdlib.h>
349 mateuszvis 47
#include <string.h>
48
 
49
#include <process.h>
50
 
352 mateuszvis 51
#include "cmd.h"
366 mateuszvis 52
#include "env.h"
352 mateuszvis 53
#include "helpers.h"
402 mateuszvis 54
#include "redir.h"
351 mateuszvis 55
#include "rmodinit.h"
349 mateuszvis 56
 
57
struct config {
58
  int locate;
59
  int install;
350 mateuszvis 60
  int envsiz;
349 mateuszvis 61
} cfg;
62
 
63
 
64
static void parse_argv(struct config *cfg, int argc, char **argv) {
65
  int i;
66
  memset(cfg, 0, sizeof(*cfg));
350 mateuszvis 67
 
349 mateuszvis 68
  for (i = 1; i < argc; i++) {
69
    if (strcmp(argv[i], "/locate") == 0) {
70
      cfg->locate = 1;
71
    }
350 mateuszvis 72
    if (strstartswith(argv[i], "/e:") == 0) {
73
      cfg->envsiz = atoi(argv[i]+3);
74
      if (cfg->envsiz < 64) cfg->envsiz = 0;
75
    }
349 mateuszvis 76
  }
77
}
78
 
79
 
370 mateuszvis 80
static void buildprompt(char *s, unsigned short envseg) {
81
  /* locate the prompt variable or use the default pattern */
82
  const char far *fmt = env_lookup(envseg, "PROMPT");
83
  while ((fmt != NULL) && (*fmt != 0)) {
84
    fmt++;
85
    if (fmt[-1] == '=') break;
86
  }
87
  if ((fmt == NULL) || (*fmt == 0)) fmt = "$p$g"; /* fallback to default if empty */
88
  /* build the prompt string based on pattern */
354 mateuszvis 89
  for (; *fmt != 0; fmt++) {
90
    if (*fmt != '$') {
91
      *s = *fmt;
92
      s++;
93
      continue;
94
    }
95
    /* escape code ($P, etc) */
96
    fmt++;
97
    switch (*fmt) {
98
      case 'Q':  /* $Q = = (equal sign) */
99
      case 'q':
100
        *s = '=';
101
        s++;
102
        break;
103
      case '$':  /* $$ = $ (dollar sign) */
104
        *s = '$';
105
        s++;
106
        break;
107
      case 'T':  /* $t = current time */
108
      case 't':
109
        s += sprintf(s, "00:00"); /* TODO */
110
        break;
111
      case 'D':  /* $D = current date */
112
      case 'd':
113
        s += sprintf(s, "1985-07-29"); /* TODO */
114
        break;
115
      case 'P':  /* $P = current drive and path */
116
      case 'p':
117
        _asm {
118
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
119
          int 0x21
120
          mov bx, s
121
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
122
        }
123
        *s += 'A';
124
        s++;
125
        *s = ':';
126
        s++;
127
        *s = '\\';
128
        s++;
129
        _asm {
130
          mov ah, 0x47    /* DOS 2+ - CWD - GET CURRENT DIRECTORY */
131
          xor dl,dl       /* DL = drive number (00h = default, 01h = A:, etc) */
132
          mov si, s       /* DS:SI -> 64-byte buffer for ASCIZ pathname */
133
          int 0x21
134
        }
135
        while (*s != 0) s++; /* move ptr forward to end of pathname */
136
        break;
137
      case 'V':  /* $V = DOS version number */
138
      case 'v':
139
        s += sprintf(s, "VER"); /* TODO */
140
        break;
141
      case 'N':  /* $N = current drive */
142
      case 'n':
143
        _asm {
144
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
145
          int 0x21
146
          mov bx, s
147
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
148
        }
149
        *s += 'A';
150
        s++;
151
        break;
152
      case 'G':  /* $G = > (greater-than sign) */
153
      case 'g':
154
        *s = '>';
155
        s++;
156
        break;
157
      case 'L':  /* $L = < (less-than sign) */
158
      case 'l':
159
        *s = '<';
160
        s++;
161
        break;
162
      case 'B':  /* $B = | (pipe) */
163
      case 'b':
164
        *s = '|';
165
        s++;
166
        break;
167
      case 'H':  /* $H = backspace (erases previous character) */
168
      case 'h':
169
        *s = '\b';
170
        s++;
171
        break;
172
      case 'E':  /* $E = Escape code (ASCII 27) */
173
      case 'e':
174
        *s = 27;
175
        s++;
176
        break;
177
      case '_':  /* $_ = CR+LF */
178
        *s = '\r';
179
        s++;
180
        *s = '\n';
181
        s++;
182
        break;
183
    }
184
  }
185
  *s = '$';
186
}
349 mateuszvis 187
 
188
 
364 mateuszvis 189
static void run_as_external(const char far *cmdline) {
190
  char buff[256];
191
  char const *argvlist[256];
192
  int i, n;
193
  /* copy buffer to a near var (incl. trailing CR), insert a space before
194
     every slash to make sure arguments are well separated */
195
  n = 0;
196
  i = 0;
197
  for (;;) {
198
    if (cmdline[i] == '/') buff[n++] = ' ';
199
    buff[n++] = cmdline[i++];
200
    if (buff[n] == 0) break;
201
  }
202
 
203
  cmd_explode(buff, cmdline, argvlist);
204
 
205
  /* for (i = 0; argvlist[i] != NULL; i++) printf("arg #%d = '%s'\r\n", i, argvlist[i]); */
206
 
207
  /* must be an external command then. this call should never return, unless
208
   * the other program failed to be executed. */
209
  execvp(argvlist[0], argvlist);
210
}
211
 
212
 
367 mateuszvis 213
static void set_comspec_to_self(unsigned short envseg) {
214
  unsigned short *psp_envseg = (void *)(0x2c); /* pointer to my env segment field in the PSP */
215
  char far *myenv = MK_FP(*psp_envseg, 0);
216
  unsigned short varcount;
217
  char buff[256] = "COMSPEC=";
218
  char *buffptr = buff + 8;
219
  /* who am i? look into my own environment, at the end of it should be my EXEPATH string */
220
  while (*myenv != 0) {
221
    /* consume a NULL-terminated string */
222
    while (*myenv != 0) myenv++;
223
    /* move to next string */
224
    myenv++;
225
  }
226
  /* get next word, if 1 then EXEPATH follows */
227
  myenv++;
228
  varcount = *myenv;
229
  myenv++;
230
  varcount |= (*myenv << 8);
231
  myenv++;
232
  if (varcount != 1) return; /* NO EXEPATH FOUND */
233
  while (*myenv != 0) {
234
    *buffptr = *myenv;
235
    buffptr++;
236
    myenv++;
237
  }
238
  *buffptr = 0;
239
  /* printf("EXEPATH: '%s'\r\n", buff); */
240
  env_setvar(envseg, buff);
241
}
242
 
243
 
349 mateuszvis 244
int main(int argc, char **argv) {
372 mateuszvis 245
  static struct config cfg;
246
  static unsigned short rmod_seg;
247
  static unsigned short far *rmod_envseg;
248
  static unsigned short far *lastexitcode;
249
  static unsigned char BUFFER[4096];
349 mateuszvis 250
 
251
  parse_argv(&cfg, argc, argv);
252
 
350 mateuszvis 253
  rmod_seg = rmod_find();
349 mateuszvis 254
  if (rmod_seg == 0xffff) {
350 mateuszvis 255
    rmod_seg = rmod_install(cfg.envsiz);
349 mateuszvis 256
    if (rmod_seg == 0xffff) {
369 mateuszvis 257
      outputnl("ERROR: rmod_install() failed");
349 mateuszvis 258
      return(1);
259
    }
369 mateuszvis 260
/*    printf("rmod installed at seg 0x%04X\r\n", rmod_seg); */
349 mateuszvis 261
  } else {
369 mateuszvis 262
/*    printf("rmod found at seg 0x%04x\r\n", rmod_seg); */
349 mateuszvis 263
  }
264
 
350 mateuszvis 265
  rmod_envseg = MK_FP(rmod_seg, RMOD_OFFSET_ENVSEG);
353 mateuszvis 266
  lastexitcode = MK_FP(rmod_seg, RMOD_OFFSET_LEXITCODE);
350 mateuszvis 267
 
367 mateuszvis 268
  /* make COMPSEC point to myself */
269
  set_comspec_to_self(*rmod_envseg);
270
 
369 mateuszvis 271
/*  {
350 mateuszvis 272
    unsigned short envsiz;
273
    unsigned short far *sizptr = MK_FP(*rmod_envseg - 1, 3);
274
    envsiz = *sizptr;
275
    envsiz *= 16;
276
    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 277
  }*/
349 mateuszvis 278
 
279
  for (;;) {
350 mateuszvis 280
    char far *cmdline = MK_FP(rmod_seg, RMOD_OFFSET_INPBUFF + 2);
405 mateuszvis 281
    unsigned char far *echostatus = MK_FP(rmod_seg, RMOD_OFFSET_ECHOFLAG);
349 mateuszvis 282
 
405 mateuszvis 283
    if (*echostatus != 0) outputnl(""); /* terminate the previous command with a CR/LF */
364 mateuszvis 284
 
405 mateuszvis 285
    SKIP_NEWLINE:
286
 
287
    /* print shell prompt (only if ECHO is enabled) */
288
    if (*echostatus != 0) {
372 mateuszvis 289
      char *promptptr = BUFFER;
370 mateuszvis 290
      buildprompt(promptptr, *rmod_envseg);
354 mateuszvis 291
      _asm {
364 mateuszvis 292
        push ax
354 mateuszvis 293
        push dx
294
        mov ah, 0x09
295
        mov dx, promptptr
296
        int 0x21
297
        pop dx
364 mateuszvis 298
        pop ax
354 mateuszvis 299
      }
300
    }
349 mateuszvis 301
 
405 mateuszvis 302
    /* revert input history terminator to \r */
303
    if (cmdline[-1] != 0) {
304
      cmdline[(unsigned short)(cmdline[-1])] = '\r';
305
    }
306
 
349 mateuszvis 307
    /* wait for user input */
308
    _asm {
364 mateuszvis 309
      push ax
310
      push bx
311
      push cx
312
      push dx
349 mateuszvis 313
      push ds
314
 
315
      /* is DOSKEY support present? (INT 2Fh, AX=4800h, returns non-zero in AL if present) */
316
      mov ax, 0x4800
317
      int 0x2f
318
      mov bl, al /* save doskey status in BL */
319
 
320
      /* set up buffered input */
321
      mov ax, rmod_seg
322
      push ax
323
      pop ds
350 mateuszvis 324
      mov dx, RMOD_OFFSET_INPBUFF
349 mateuszvis 325
 
326
      /* execute either DOS input or DOSKEY */
327
      test bl, bl /* zf set if no DOSKEY present */
328
      jnz DOSKEY
329
 
330
      mov ah, 0x0a
331
      int 0x21
332
      jmp short DONE
333
 
334
      DOSKEY:
335
      mov ax, 0x4810
336
      int 0x2f
337
 
338
      DONE:
405 mateuszvis 339
      /* terminate command with a CR/LF */
340
      mov ah, 0x02 /* display character in dl */
341
      mov dl, 0x0d
342
      int 0x21
343
      mov dl, 0x0a
344
      int 0x21
345
 
349 mateuszvis 346
      pop ds
364 mateuszvis 347
      pop dx
348
      pop cx
349
      pop bx
350
      pop ax
349 mateuszvis 351
    }
352
 
405 mateuszvis 353
    /* if nothing entered, loop again (but without appending an extra CR/LF) */
354
    if (cmdline[-1] == 0) goto SKIP_NEWLINE;
349 mateuszvis 355
 
364 mateuszvis 356
    /* replace \r by a zero terminator */
357
    cmdline[(unsigned char)(cmdline[-1])] = 0;
349 mateuszvis 358
 
364 mateuszvis 359
    /* move pointer forward to skip over any leading spaces */
360
    while (*cmdline == ' ') cmdline++;
349 mateuszvis 361
 
367 mateuszvis 362
    /* update rmod's ptr to COMPSPEC so it is always up to date */
363
    rmod_updatecomspecptr(rmod_seg, *rmod_envseg);
364
 
402 mateuszvis 365
    /* handle redirections (if any) */
366
    if (redir_parsecmd(cmdline, BUFFER) != 0) {
367
      outputnl("");
368
      continue;
369
    }
370
 
364 mateuszvis 371
    /* try matching (and executing) an internal command */
353 mateuszvis 372
    {
405 mateuszvis 373
      int ecode = cmd_process(rmod_seg, *rmod_envseg, cmdline, BUFFER);
353 mateuszvis 374
      if (ecode >= 0) *lastexitcode = ecode;
377 mateuszvis 375
      if (ecode >= -1) { /* internal command executed */
402 mateuszvis 376
        redir_revert(); /* revert stdout (in case it was redirected) */
377 mateuszvis 377
        continue;
378
      }
353 mateuszvis 379
    }
352 mateuszvis 380
 
364 mateuszvis 381
    /* if here, then this was not an internal command */
382
    run_as_external(cmdline);
349 mateuszvis 383
 
402 mateuszvis 384
    /* revert stdout (in case it was redirected) */
385
    redir_revert();
386
 
349 mateuszvis 387
    /* execvp() replaces the current process by the new one
388
    if I am still alive then external command failed to execute */
369 mateuszvis 389
    outputnl("Bad command or file name");
349 mateuszvis 390
 
391
  }
392
 
393
  return(0);
394
}