Subversion Repositories SvarDOS

Rev

Rev 410 | Rev 426 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
421 mateuszvis 1
/* This file is part of the SvarCOM project and is published under the terms
2
 * of the MIT license.
3
 *
4
 * Copyright (C) 2021 Mateusz Viste
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a
7
 * copy of this software and associated documentation files (the "Software"),
8
 * to deal in the Software without restriction, including without limitation
9
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10
 * and/or sell copies of the Software, and to permit persons to whom the
11
 * Software is furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22
 * DEALINGS IN THE SOFTWARE.
23
 */
24
 
349 mateuszvis 25
/*
26
 * SvarCOM is a command-line interpreter.
27
 *
28
 * a little memory area is allocated as high as possible. it contains:
29
 *  - a signature (like XMS drivers do)
30
 *  - a routine for exec'ing programs
31
 *  - a "last command" buffer for input history
32
 *
33
 * when svarcom starts, it tries locating the routine in memory.
34
 *
35
 * if found:
36
 *   waits for user input and processes it. if execing something is required, set the "next exec" field in routine's memory and quit.
37
 *
38
 * if not found:
39
 *   installs it by creating a new PSP, set int 22 vector to the routine, set my "parent PSP" to the routine
40
 *   and quit.
41
 *
42
 * PSP structure
43
 * http://www.piclist.com/techref/dos/psps.htm
44
 *
45
 *
46
 *
47
 * === MCB ===
48
 *
49
 * each time that DOS allocates memory, it prefixes the allocated memory with
50
 * a 16-bytes structure called a "Memory Control Block" (MCB). This control
51
 * block has the following structure:
52
 *
53
 * Offset  Size     Description
54
 *   00h   byte     'M' =  non-last member of the MCB chain
55
 *                  'Z' = indicates last entry in MCB chain
56
 *                  other values cause "Memory Allocation Failure" on exit
57
 *   01h   word     PSP segment address of the owner (Process Id)
58
 *                  possible values:
59
 *                    0 = free
60
 *                    8 = Allocated by DOS before first user pgm loaded
61
 *                    other = Process Id/PSP segment address of owner
62
 *   03h  word      number of paragraphs related to this MCB (excluding MCB)
63
 *   05h  11 bytes  reserved
64
 *   10h  ...       start of actual allocated memory block
65
 */
66
 
67
#include <i86.h>
68
#include <dos.h>
69
#include <stdio.h>
350 mateuszvis 70
#include <stdlib.h>
349 mateuszvis 71
#include <string.h>
72
 
73
#include <process.h>
74
 
352 mateuszvis 75
#include "cmd.h"
366 mateuszvis 76
#include "env.h"
352 mateuszvis 77
#include "helpers.h"
402 mateuszvis 78
#include "redir.h"
351 mateuszvis 79
#include "rmodinit.h"
349 mateuszvis 80
 
81
struct config {
82
  int locate;
83
  int install;
410 mateuszvis 84
  unsigned short envsiz;
349 mateuszvis 85
} cfg;
86
 
87
 
88
static void parse_argv(struct config *cfg, int argc, char **argv) {
89
  int i;
90
  memset(cfg, 0, sizeof(*cfg));
350 mateuszvis 91
 
349 mateuszvis 92
  for (i = 1; i < argc; i++) {
93
    if (strcmp(argv[i], "/locate") == 0) {
94
      cfg->locate = 1;
95
    }
350 mateuszvis 96
    if (strstartswith(argv[i], "/e:") == 0) {
410 mateuszvis 97
      if ((atouns(&(cfg->envsiz), argv[i] + 3) != 0) || (cfg->envsiz < 64)) {
98
        cfg->envsiz = 0;
99
      }
350 mateuszvis 100
    }
349 mateuszvis 101
  }
102
}
103
 
104
 
370 mateuszvis 105
static void buildprompt(char *s, unsigned short envseg) {
106
  /* locate the prompt variable or use the default pattern */
107
  const char far *fmt = env_lookup(envseg, "PROMPT");
108
  while ((fmt != NULL) && (*fmt != 0)) {
109
    fmt++;
110
    if (fmt[-1] == '=') break;
111
  }
112
  if ((fmt == NULL) || (*fmt == 0)) fmt = "$p$g"; /* fallback to default if empty */
113
  /* build the prompt string based on pattern */
354 mateuszvis 114
  for (; *fmt != 0; fmt++) {
115
    if (*fmt != '$') {
116
      *s = *fmt;
117
      s++;
118
      continue;
119
    }
120
    /* escape code ($P, etc) */
121
    fmt++;
122
    switch (*fmt) {
123
      case 'Q':  /* $Q = = (equal sign) */
124
      case 'q':
125
        *s = '=';
126
        s++;
127
        break;
128
      case '$':  /* $$ = $ (dollar sign) */
129
        *s = '$';
130
        s++;
131
        break;
132
      case 'T':  /* $t = current time */
133
      case 't':
134
        s += sprintf(s, "00:00"); /* TODO */
135
        break;
136
      case 'D':  /* $D = current date */
137
      case 'd':
138
        s += sprintf(s, "1985-07-29"); /* TODO */
139
        break;
140
      case 'P':  /* $P = current drive and path */
141
      case 'p':
142
        _asm {
143
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
144
          int 0x21
145
          mov bx, s
146
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
147
        }
148
        *s += 'A';
149
        s++;
150
        *s = ':';
151
        s++;
152
        *s = '\\';
153
        s++;
154
        _asm {
155
          mov ah, 0x47    /* DOS 2+ - CWD - GET CURRENT DIRECTORY */
156
          xor dl,dl       /* DL = drive number (00h = default, 01h = A:, etc) */
157
          mov si, s       /* DS:SI -> 64-byte buffer for ASCIZ pathname */
158
          int 0x21
159
        }
160
        while (*s != 0) s++; /* move ptr forward to end of pathname */
161
        break;
162
      case 'V':  /* $V = DOS version number */
163
      case 'v':
164
        s += sprintf(s, "VER"); /* TODO */
165
        break;
166
      case 'N':  /* $N = current drive */
167
      case 'n':
168
        _asm {
169
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
170
          int 0x21
171
          mov bx, s
172
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
173
        }
174
        *s += 'A';
175
        s++;
176
        break;
177
      case 'G':  /* $G = > (greater-than sign) */
178
      case 'g':
179
        *s = '>';
180
        s++;
181
        break;
182
      case 'L':  /* $L = < (less-than sign) */
183
      case 'l':
184
        *s = '<';
185
        s++;
186
        break;
187
      case 'B':  /* $B = | (pipe) */
188
      case 'b':
189
        *s = '|';
190
        s++;
191
        break;
192
      case 'H':  /* $H = backspace (erases previous character) */
193
      case 'h':
194
        *s = '\b';
195
        s++;
196
        break;
197
      case 'E':  /* $E = Escape code (ASCII 27) */
198
      case 'e':
199
        *s = 27;
200
        s++;
201
        break;
202
      case '_':  /* $_ = CR+LF */
203
        *s = '\r';
204
        s++;
205
        *s = '\n';
206
        s++;
207
        break;
208
    }
209
  }
210
  *s = '$';
211
}
349 mateuszvis 212
 
213
 
364 mateuszvis 214
static void run_as_external(const char far *cmdline) {
215
  char buff[256];
216
  char const *argvlist[256];
217
  int i, n;
218
  /* copy buffer to a near var (incl. trailing CR), insert a space before
219
     every slash to make sure arguments are well separated */
220
  n = 0;
221
  i = 0;
222
  for (;;) {
223
    if (cmdline[i] == '/') buff[n++] = ' ';
224
    buff[n++] = cmdline[i++];
225
    if (buff[n] == 0) break;
226
  }
227
 
228
  cmd_explode(buff, cmdline, argvlist);
229
 
230
  /* for (i = 0; argvlist[i] != NULL; i++) printf("arg #%d = '%s'\r\n", i, argvlist[i]); */
231
 
232
  /* must be an external command then. this call should never return, unless
233
   * the other program failed to be executed. */
234
  execvp(argvlist[0], argvlist);
235
}
236
 
237
 
367 mateuszvis 238
static void set_comspec_to_self(unsigned short envseg) {
239
  unsigned short *psp_envseg = (void *)(0x2c); /* pointer to my env segment field in the PSP */
240
  char far *myenv = MK_FP(*psp_envseg, 0);
241
  unsigned short varcount;
242
  char buff[256] = "COMSPEC=";
243
  char *buffptr = buff + 8;
244
  /* who am i? look into my own environment, at the end of it should be my EXEPATH string */
245
  while (*myenv != 0) {
246
    /* consume a NULL-terminated string */
247
    while (*myenv != 0) myenv++;
248
    /* move to next string */
249
    myenv++;
250
  }
251
  /* get next word, if 1 then EXEPATH follows */
252
  myenv++;
253
  varcount = *myenv;
254
  myenv++;
255
  varcount |= (*myenv << 8);
256
  myenv++;
257
  if (varcount != 1) return; /* NO EXEPATH FOUND */
258
  while (*myenv != 0) {
259
    *buffptr = *myenv;
260
    buffptr++;
261
    myenv++;
262
  }
263
  *buffptr = 0;
264
  /* printf("EXEPATH: '%s'\r\n", buff); */
265
  env_setvar(envseg, buff);
266
}
267
 
268
 
349 mateuszvis 269
int main(int argc, char **argv) {
372 mateuszvis 270
  static struct config cfg;
271
  static unsigned short rmod_seg;
272
  static unsigned short far *rmod_envseg;
273
  static unsigned short far *lastexitcode;
274
  static unsigned char BUFFER[4096];
349 mateuszvis 275
 
276
  parse_argv(&cfg, argc, argv);
277
 
350 mateuszvis 278
  rmod_seg = rmod_find();
349 mateuszvis 279
  if (rmod_seg == 0xffff) {
350 mateuszvis 280
    rmod_seg = rmod_install(cfg.envsiz);
349 mateuszvis 281
    if (rmod_seg == 0xffff) {
369 mateuszvis 282
      outputnl("ERROR: rmod_install() failed");
349 mateuszvis 283
      return(1);
284
    }
369 mateuszvis 285
/*    printf("rmod installed at seg 0x%04X\r\n", rmod_seg); */
349 mateuszvis 286
  } else {
369 mateuszvis 287
/*    printf("rmod found at seg 0x%04x\r\n", rmod_seg); */
349 mateuszvis 288
  }
289
 
350 mateuszvis 290
  rmod_envseg = MK_FP(rmod_seg, RMOD_OFFSET_ENVSEG);
353 mateuszvis 291
  lastexitcode = MK_FP(rmod_seg, RMOD_OFFSET_LEXITCODE);
350 mateuszvis 292
 
367 mateuszvis 293
  /* make COMPSEC point to myself */
294
  set_comspec_to_self(*rmod_envseg);
295
 
369 mateuszvis 296
/*  {
350 mateuszvis 297
    unsigned short envsiz;
298
    unsigned short far *sizptr = MK_FP(*rmod_envseg - 1, 3);
299
    envsiz = *sizptr;
300
    envsiz *= 16;
301
    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 302
  }*/
349 mateuszvis 303
 
304
  for (;;) {
350 mateuszvis 305
    char far *cmdline = MK_FP(rmod_seg, RMOD_OFFSET_INPBUFF + 2);
405 mateuszvis 306
    unsigned char far *echostatus = MK_FP(rmod_seg, RMOD_OFFSET_ECHOFLAG);
349 mateuszvis 307
 
405 mateuszvis 308
    if (*echostatus != 0) outputnl(""); /* terminate the previous command with a CR/LF */
364 mateuszvis 309
 
405 mateuszvis 310
    SKIP_NEWLINE:
311
 
312
    /* print shell prompt (only if ECHO is enabled) */
313
    if (*echostatus != 0) {
372 mateuszvis 314
      char *promptptr = BUFFER;
370 mateuszvis 315
      buildprompt(promptptr, *rmod_envseg);
354 mateuszvis 316
      _asm {
364 mateuszvis 317
        push ax
354 mateuszvis 318
        push dx
319
        mov ah, 0x09
320
        mov dx, promptptr
321
        int 0x21
322
        pop dx
364 mateuszvis 323
        pop ax
354 mateuszvis 324
      }
325
    }
349 mateuszvis 326
 
405 mateuszvis 327
    /* revert input history terminator to \r */
328
    if (cmdline[-1] != 0) {
329
      cmdline[(unsigned short)(cmdline[-1])] = '\r';
330
    }
331
 
349 mateuszvis 332
    /* wait for user input */
333
    _asm {
364 mateuszvis 334
      push ax
335
      push bx
336
      push cx
337
      push dx
349 mateuszvis 338
      push ds
339
 
340
      /* is DOSKEY support present? (INT 2Fh, AX=4800h, returns non-zero in AL if present) */
341
      mov ax, 0x4800
342
      int 0x2f
343
      mov bl, al /* save doskey status in BL */
344
 
345
      /* set up buffered input */
346
      mov ax, rmod_seg
347
      push ax
348
      pop ds
350 mateuszvis 349
      mov dx, RMOD_OFFSET_INPBUFF
349 mateuszvis 350
 
351
      /* execute either DOS input or DOSKEY */
352
      test bl, bl /* zf set if no DOSKEY present */
353
      jnz DOSKEY
354
 
355
      mov ah, 0x0a
356
      int 0x21
357
      jmp short DONE
358
 
359
      DOSKEY:
360
      mov ax, 0x4810
361
      int 0x2f
362
 
363
      DONE:
405 mateuszvis 364
      /* terminate command with a CR/LF */
365
      mov ah, 0x02 /* display character in dl */
366
      mov dl, 0x0d
367
      int 0x21
368
      mov dl, 0x0a
369
      int 0x21
370
 
349 mateuszvis 371
      pop ds
364 mateuszvis 372
      pop dx
373
      pop cx
374
      pop bx
375
      pop ax
349 mateuszvis 376
    }
377
 
405 mateuszvis 378
    /* if nothing entered, loop again (but without appending an extra CR/LF) */
379
    if (cmdline[-1] == 0) goto SKIP_NEWLINE;
349 mateuszvis 380
 
364 mateuszvis 381
    /* replace \r by a zero terminator */
382
    cmdline[(unsigned char)(cmdline[-1])] = 0;
349 mateuszvis 383
 
364 mateuszvis 384
    /* move pointer forward to skip over any leading spaces */
385
    while (*cmdline == ' ') cmdline++;
349 mateuszvis 386
 
367 mateuszvis 387
    /* update rmod's ptr to COMPSPEC so it is always up to date */
388
    rmod_updatecomspecptr(rmod_seg, *rmod_envseg);
389
 
402 mateuszvis 390
    /* handle redirections (if any) */
391
    if (redir_parsecmd(cmdline, BUFFER) != 0) {
392
      outputnl("");
393
      continue;
394
    }
395
 
364 mateuszvis 396
    /* try matching (and executing) an internal command */
353 mateuszvis 397
    {
405 mateuszvis 398
      int ecode = cmd_process(rmod_seg, *rmod_envseg, cmdline, BUFFER);
353 mateuszvis 399
      if (ecode >= 0) *lastexitcode = ecode;
377 mateuszvis 400
      if (ecode >= -1) { /* internal command executed */
402 mateuszvis 401
        redir_revert(); /* revert stdout (in case it was redirected) */
377 mateuszvis 402
        continue;
403
      }
353 mateuszvis 404
    }
352 mateuszvis 405
 
364 mateuszvis 406
    /* if here, then this was not an internal command */
407
    run_as_external(cmdline);
349 mateuszvis 408
 
402 mateuszvis 409
    /* revert stdout (in case it was redirected) */
410
    redir_revert();
411
 
349 mateuszvis 412
    /* execvp() replaces the current process by the new one
413
    if I am still alive then external command failed to execute */
369 mateuszvis 414
    outputnl("Bad command or file name");
349 mateuszvis 415
 
416
  }
417
 
418
  return(0);
419
}