Subversion Repositories SvarDOS

Rev

Rev 353 | Rev 355 | 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"
57
#include "helpers.h"
351 mateuszvis 58
#include "rmodinit.h"
349 mateuszvis 59
 
60
struct config {
61
  int locate;
62
  int install;
350 mateuszvis 63
  int envsiz;
349 mateuszvis 64
} cfg;
65
 
66
 
67
static void parse_argv(struct config *cfg, int argc, char **argv) {
68
  int i;
69
  memset(cfg, 0, sizeof(*cfg));
350 mateuszvis 70
 
349 mateuszvis 71
  for (i = 1; i < argc; i++) {
72
    if (strcmp(argv[i], "/locate") == 0) {
73
      cfg->locate = 1;
74
    }
350 mateuszvis 75
    if (strstartswith(argv[i], "/e:") == 0) {
76
      cfg->envsiz = atoi(argv[i]+3);
77
      if (cfg->envsiz < 64) cfg->envsiz = 0;
78
    }
349 mateuszvis 79
  }
80
}
81
 
82
 
83
static int explode_progparams(char *s, char const **argvlist) {
84
  int si = 0, argc = 0;
85
  for (;;) {
86
    /* skip to next non-space character */
87
    while (s[si] == ' ') si++;
88
    /* end of string? */
89
    if (s[si] == '\r') break;
90
    /* set argv ptr */
91
    argvlist[argc++] = s + si;
92
    /* find next space */
93
    while (s[si] != ' ' && s[si] != '\r') si++;
94
    /* is this end of string? */
95
    if (s[si] == '\r') {
96
      s[si] = 0;
97
      break;
98
    }
99
    /* not end: terminate arg and look for next one */
100
    s[si++] = 0;
101
  }
102
  /* terminate argvlist with a NULL value */
103
  argvlist[argc] = NULL;
104
  return(argc);
105
}
106
 
107
 
354 mateuszvis 108
static void buildprompt(char *s, const char *fmt) {
109
  for (; *fmt != 0; fmt++) {
110
    if (*fmt != '$') {
111
      *s = *fmt;
112
      s++;
113
      continue;
114
    }
115
    /* escape code ($P, etc) */
116
    fmt++;
117
    switch (*fmt) {
118
      case 'Q':  /* $Q = = (equal sign) */
119
      case 'q':
120
        *s = '=';
121
        s++;
122
        break;
123
      case '$':  /* $$ = $ (dollar sign) */
124
        *s = '$';
125
        s++;
126
        break;
127
      case 'T':  /* $t = current time */
128
      case 't':
129
        s += sprintf(s, "00:00"); /* TODO */
130
        break;
131
      case 'D':  /* $D = current date */
132
      case 'd':
133
        s += sprintf(s, "1985-07-29"); /* TODO */
134
        break;
135
      case 'P':  /* $P = current drive and path */
136
      case 'p':
137
        _asm {
138
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
139
          int 0x21
140
          mov bx, s
141
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
142
        }
143
        *s += 'A';
144
        s++;
145
        *s = ':';
146
        s++;
147
        *s = '\\';
148
        s++;
149
        _asm {
150
          mov ah, 0x47    /* DOS 2+ - CWD - GET CURRENT DIRECTORY */
151
          xor dl,dl       /* DL = drive number (00h = default, 01h = A:, etc) */
152
          mov si, s       /* DS:SI -> 64-byte buffer for ASCIZ pathname */
153
          int 0x21
154
        }
155
        while (*s != 0) s++; /* move ptr forward to end of pathname */
156
        break;
157
      case 'V':  /* $V = DOS version number */
158
      case 'v':
159
        s += sprintf(s, "VER"); /* TODO */
160
        break;
161
      case 'N':  /* $N = current drive */
162
      case 'n':
163
        _asm {
164
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
165
          int 0x21
166
          mov bx, s
167
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
168
        }
169
        *s += 'A';
170
        s++;
171
        break;
172
      case 'G':  /* $G = > (greater-than sign) */
173
      case 'g':
174
        *s = '>';
175
        s++;
176
        break;
177
      case 'L':  /* $L = < (less-than sign) */
178
      case 'l':
179
        *s = '<';
180
        s++;
181
        break;
182
      case 'B':  /* $B = | (pipe) */
183
      case 'b':
184
        *s = '|';
185
        s++;
186
        break;
187
      case 'H':  /* $H = backspace (erases previous character) */
188
      case 'h':
189
        *s = '\b';
190
        s++;
191
        break;
192
      case 'E':  /* $E = Escape code (ASCII 27) */
193
      case 'e':
194
        *s = 27;
195
        s++;
196
        break;
197
      case '_':  /* $_ = CR+LF */
198
        *s = '\r';
199
        s++;
200
        *s = '\n';
201
        s++;
202
        break;
203
    }
204
  }
205
  *s = '$';
206
}
349 mateuszvis 207
 
208
 
209
int main(int argc, char **argv) {
210
  struct config cfg;
350 mateuszvis 211
  unsigned short rmod_seg;
212
  unsigned short far *rmod_envseg;
353 mateuszvis 213
  unsigned short far *lastexitcode;
349 mateuszvis 214
 
215
  parse_argv(&cfg, argc, argv);
216
 
350 mateuszvis 217
  rmod_seg = rmod_find();
349 mateuszvis 218
  if (rmod_seg == 0xffff) {
350 mateuszvis 219
    rmod_seg = rmod_install(cfg.envsiz);
349 mateuszvis 220
    if (rmod_seg == 0xffff) {
350 mateuszvis 221
      puts("ERROR: rmod_install() failed");
349 mateuszvis 222
      return(1);
223
    } else {
224
      printf("rmod installed at seg 0x%04X\r\n", rmod_seg);
225
    }
226
  } else {
227
    printf("rmod found at seg 0x%04x\r\n", rmod_seg);
228
  }
229
 
350 mateuszvis 230
  rmod_envseg = MK_FP(rmod_seg, RMOD_OFFSET_ENVSEG);
353 mateuszvis 231
  lastexitcode = MK_FP(rmod_seg, RMOD_OFFSET_LEXITCODE);
350 mateuszvis 232
 
233
  {
234
    unsigned short envsiz;
235
    unsigned short far *sizptr = MK_FP(*rmod_envseg - 1, 3);
236
    envsiz = *sizptr;
237
    envsiz *= 16;
238
    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);
349 mateuszvis 239
  }
240
 
241
  for (;;) {
242
    int i, argcount;
350 mateuszvis 243
    char far *cmdline = MK_FP(rmod_seg, RMOD_OFFSET_INPBUFF + 2);
354 mateuszvis 244
    char buff[256];
349 mateuszvis 245
    char const *argvlist[256];
246
 
354 mateuszvis 247
    {
248
      /* print shell prompt */
249
      char *promptptr = buff;
250
      buildprompt(promptptr, "$p$g"); /* TODO: prompt should be configurable via environment */
251
      _asm {
252
        push dx
253
        mov ah, 0x09
254
        mov dx, promptptr
255
        int 0x21
256
        pop dx
257
      }
258
    }
349 mateuszvis 259
 
260
    /* wait for user input */
261
    _asm {
262
      push ds
263
 
264
      /* is DOSKEY support present? (INT 2Fh, AX=4800h, returns non-zero in AL if present) */
265
      mov ax, 0x4800
266
      int 0x2f
267
      mov bl, al /* save doskey status in BL */
268
 
269
      /* set up buffered input */
270
      mov ax, rmod_seg
271
      push ax
272
      pop ds
350 mateuszvis 273
      mov dx, RMOD_OFFSET_INPBUFF
349 mateuszvis 274
 
275
      /* execute either DOS input or DOSKEY */
276
      test bl, bl /* zf set if no DOSKEY present */
277
      jnz DOSKEY
278
 
279
      mov ah, 0x0a
280
      int 0x21
281
      jmp short DONE
282
 
283
      DOSKEY:
284
      mov ax, 0x4810
285
      int 0x2f
286
 
287
      DONE:
288
      pop ds
289
    }
290
    printf("\r\n");
291
 
292
    /* if nothing entered, loop again */
293
    if (cmdline[-1] == 0) continue;
294
 
295
    /* copy buffer to a near var (incl. trailing CR) */
354 mateuszvis 296
    _fmemcpy(buff, cmdline, cmdline[-1] + 1);
349 mateuszvis 297
 
354 mateuszvis 298
    argcount = explode_progparams(buff, argvlist);
349 mateuszvis 299
 
300
    /* if nothing args found (eg. all-spaces), loop again */
301
    if (argcount == 0) continue;
302
 
303
    printf("got %u bytes of cmdline (%d args)\r\n", cmdline[-1], argcount);
304
    for (i = 0; i < argcount; i++) {
305
      printf("arg #%d = '%s'\r\n", i, argvlist[i]);
306
    }
307
 
352 mateuszvis 308
    /* is it about quitting? */
309
    if (imatch(argvlist[0], "exit")) break;
349 mateuszvis 310
 
352 mateuszvis 311
    /* try running it as an internal command */
353 mateuszvis 312
    {
313
      int ecode = cmd_process(argcount, argvlist, *rmod_envseg);
314
      if (ecode >= 0) *lastexitcode = ecode;
315
      if (ecode >= -1) continue; /* command is internal but did not set an exit code */
316
    }
352 mateuszvis 317
 
318
    /* must be an external command then */
349 mateuszvis 319
    execvp(argvlist[0], argvlist);
320
 
321
    /* execvp() replaces the current process by the new one
322
    if I am still alive then external command failed to execute */
323
    puts("Bad command or file name");
324
 
325
  }
326
 
327
  return(0);
328
}