Subversion Repositories SvarDOS

Rev

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