Subversion Repositories SvarDOS

Rev

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