Subversion Repositories SvarDOS

Rev

Rev 354 | Rev 357 | Go to most recent revision | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

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