Subversion Repositories SvarDOS

Rev

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