Subversion Repositories SvarDOS

Rev

Rev 437 | Rev 443 | 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) {
426 mateuszvis 97
      if ((atous(&(cfg->envsiz), argv[i] + 3) != 0) || (cfg->envsiz < 64)) {
410 mateuszvis 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 */
438 mateuszvis 107
  const char far *fmt = env_lookup_val(envseg, "PROMPT");
370 mateuszvis 108
  if ((fmt == NULL) || (*fmt == 0)) fmt = "$p$g"; /* fallback to default if empty */
109
  /* build the prompt string based on pattern */
354 mateuszvis 110
  for (; *fmt != 0; fmt++) {
111
    if (*fmt != '$') {
112
      *s = *fmt;
113
      s++;
114
      continue;
115
    }
116
    /* escape code ($P, etc) */
117
    fmt++;
118
    switch (*fmt) {
119
      case 'Q':  /* $Q = = (equal sign) */
120
      case 'q':
121
        *s = '=';
122
        s++;
123
        break;
124
      case '$':  /* $$ = $ (dollar sign) */
125
        *s = '$';
126
        s++;
127
        break;
128
      case 'T':  /* $t = current time */
129
      case 't':
130
        s += sprintf(s, "00:00"); /* TODO */
131
        break;
132
      case 'D':  /* $D = current date */
133
      case 'd':
134
        s += sprintf(s, "1985-07-29"); /* TODO */
135
        break;
136
      case 'P':  /* $P = current drive and path */
137
      case 'p':
138
        _asm {
139
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
140
          int 0x21
141
          mov bx, s
142
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
143
        }
144
        *s += 'A';
145
        s++;
146
        *s = ':';
147
        s++;
148
        *s = '\\';
149
        s++;
150
        _asm {
151
          mov ah, 0x47    /* DOS 2+ - CWD - GET CURRENT DIRECTORY */
152
          xor dl,dl       /* DL = drive number (00h = default, 01h = A:, etc) */
153
          mov si, s       /* DS:SI -> 64-byte buffer for ASCIZ pathname */
154
          int 0x21
155
        }
156
        while (*s != 0) s++; /* move ptr forward to end of pathname */
157
        break;
158
      case 'V':  /* $V = DOS version number */
159
      case 'v':
160
        s += sprintf(s, "VER"); /* TODO */
161
        break;
162
      case 'N':  /* $N = current drive */
163
      case 'n':
164
        _asm {
165
          mov ah, 0x19    /* DOS 1+ - GET CURRENT DRIVE */
166
          int 0x21
167
          mov bx, s
168
          mov [bx], al  /* AL = drive (00 = A:, 01 = B:, etc */
169
        }
170
        *s += 'A';
171
        s++;
172
        break;
173
      case 'G':  /* $G = > (greater-than sign) */
174
      case 'g':
175
        *s = '>';
176
        s++;
177
        break;
178
      case 'L':  /* $L = < (less-than sign) */
179
      case 'l':
180
        *s = '<';
181
        s++;
182
        break;
183
      case 'B':  /* $B = | (pipe) */
184
      case 'b':
185
        *s = '|';
186
        s++;
187
        break;
188
      case 'H':  /* $H = backspace (erases previous character) */
189
      case 'h':
190
        *s = '\b';
191
        s++;
192
        break;
193
      case 'E':  /* $E = Escape code (ASCII 27) */
194
      case 'e':
195
        *s = 27;
196
        s++;
197
        break;
198
      case '_':  /* $_ = CR+LF */
199
        *s = '\r';
200
        s++;
201
        *s = '\n';
202
        s++;
203
        break;
204
    }
205
  }
206
  *s = '$';
207
}
349 mateuszvis 208
 
209
 
364 mateuszvis 210
static void run_as_external(const char far *cmdline) {
211
  char buff[256];
212
  char const *argvlist[256];
213
  int i, n;
214
  /* copy buffer to a near var (incl. trailing CR), insert a space before
215
     every slash to make sure arguments are well separated */
216
  n = 0;
217
  i = 0;
218
  for (;;) {
219
    if (cmdline[i] == '/') buff[n++] = ' ';
220
    buff[n++] = cmdline[i++];
221
    if (buff[n] == 0) break;
222
  }
223
 
224
  cmd_explode(buff, cmdline, argvlist);
225
 
226
  /* for (i = 0; argvlist[i] != NULL; i++) printf("arg #%d = '%s'\r\n", i, argvlist[i]); */
227
 
228
  /* must be an external command then. this call should never return, unless
229
   * the other program failed to be executed. */
230
  execvp(argvlist[0], argvlist);
231
}
232
 
233
 
367 mateuszvis 234
static void set_comspec_to_self(unsigned short envseg) {
235
  unsigned short *psp_envseg = (void *)(0x2c); /* pointer to my env segment field in the PSP */
236
  char far *myenv = MK_FP(*psp_envseg, 0);
237
  unsigned short varcount;
238
  char buff[256] = "COMSPEC=";
239
  char *buffptr = buff + 8;
240
  /* who am i? look into my own environment, at the end of it should be my EXEPATH string */
241
  while (*myenv != 0) {
242
    /* consume a NULL-terminated string */
243
    while (*myenv != 0) myenv++;
244
    /* move to next string */
245
    myenv++;
246
  }
247
  /* get next word, if 1 then EXEPATH follows */
248
  myenv++;
249
  varcount = *myenv;
250
  myenv++;
251
  varcount |= (*myenv << 8);
252
  myenv++;
253
  if (varcount != 1) return; /* NO EXEPATH FOUND */
254
  while (*myenv != 0) {
255
    *buffptr = *myenv;
256
    buffptr++;
257
    myenv++;
258
  }
259
  *buffptr = 0;
260
  /* printf("EXEPATH: '%s'\r\n", buff); */
261
  env_setvar(envseg, buff);
262
}
263
 
264
 
349 mateuszvis 265
int main(int argc, char **argv) {
372 mateuszvis 266
  static struct config cfg;
267
  static unsigned short rmod_seg;
268
  static unsigned short far *rmod_envseg;
269
  static unsigned short far *lastexitcode;
270
  static unsigned char BUFFER[4096];
349 mateuszvis 271
 
272
  parse_argv(&cfg, argc, argv);
273
 
350 mateuszvis 274
  rmod_seg = rmod_find();
349 mateuszvis 275
  if (rmod_seg == 0xffff) {
350 mateuszvis 276
    rmod_seg = rmod_install(cfg.envsiz);
349 mateuszvis 277
    if (rmod_seg == 0xffff) {
369 mateuszvis 278
      outputnl("ERROR: rmod_install() failed");
349 mateuszvis 279
      return(1);
280
    }
369 mateuszvis 281
/*    printf("rmod installed at seg 0x%04X\r\n", rmod_seg); */
349 mateuszvis 282
  } else {
369 mateuszvis 283
/*    printf("rmod found at seg 0x%04x\r\n", rmod_seg); */
349 mateuszvis 284
  }
285
 
350 mateuszvis 286
  rmod_envseg = MK_FP(rmod_seg, RMOD_OFFSET_ENVSEG);
353 mateuszvis 287
  lastexitcode = MK_FP(rmod_seg, RMOD_OFFSET_LEXITCODE);
350 mateuszvis 288
 
367 mateuszvis 289
  /* make COMPSEC point to myself */
290
  set_comspec_to_self(*rmod_envseg);
291
 
369 mateuszvis 292
/*  {
350 mateuszvis 293
    unsigned short envsiz;
294
    unsigned short far *sizptr = MK_FP(*rmod_envseg - 1, 3);
295
    envsiz = *sizptr;
296
    envsiz *= 16;
297
    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 298
  }*/
349 mateuszvis 299
 
300
  for (;;) {
350 mateuszvis 301
    char far *cmdline = MK_FP(rmod_seg, RMOD_OFFSET_INPBUFF + 2);
405 mateuszvis 302
    unsigned char far *echostatus = MK_FP(rmod_seg, RMOD_OFFSET_ECHOFLAG);
349 mateuszvis 303
 
405 mateuszvis 304
    if (*echostatus != 0) outputnl(""); /* terminate the previous command with a CR/LF */
364 mateuszvis 305
 
437 mateuszvis 306
    /* (re)load translation strings if needed */
307
    nls_langreload(BUFFER, *rmod_envseg);
308
 
405 mateuszvis 309
    SKIP_NEWLINE:
310
 
311
    /* print shell prompt (only if ECHO is enabled) */
312
    if (*echostatus != 0) {
372 mateuszvis 313
      char *promptptr = BUFFER;
370 mateuszvis 314
      buildprompt(promptptr, *rmod_envseg);
354 mateuszvis 315
      _asm {
364 mateuszvis 316
        push ax
354 mateuszvis 317
        push dx
318
        mov ah, 0x09
319
        mov dx, promptptr
320
        int 0x21
321
        pop dx
364 mateuszvis 322
        pop ax
354 mateuszvis 323
      }
324
    }
349 mateuszvis 325
 
405 mateuszvis 326
    /* revert input history terminator to \r */
327
    if (cmdline[-1] != 0) {
328
      cmdline[(unsigned short)(cmdline[-1])] = '\r';
329
    }
330
 
349 mateuszvis 331
    /* wait for user input */
332
    _asm {
364 mateuszvis 333
      push ax
334
      push bx
335
      push cx
336
      push dx
349 mateuszvis 337
      push ds
338
 
339
      /* is DOSKEY support present? (INT 2Fh, AX=4800h, returns non-zero in AL if present) */
340
      mov ax, 0x4800
341
      int 0x2f
342
      mov bl, al /* save doskey status in BL */
343
 
344
      /* set up buffered input */
345
      mov ax, rmod_seg
346
      push ax
347
      pop ds
350 mateuszvis 348
      mov dx, RMOD_OFFSET_INPBUFF
349 mateuszvis 349
 
350
      /* execute either DOS input or DOSKEY */
351
      test bl, bl /* zf set if no DOSKEY present */
352
      jnz DOSKEY
353
 
354
      mov ah, 0x0a
355
      int 0x21
356
      jmp short DONE
357
 
358
      DOSKEY:
359
      mov ax, 0x4810
360
      int 0x2f
361
 
362
      DONE:
405 mateuszvis 363
      /* terminate command with a CR/LF */
364
      mov ah, 0x02 /* display character in dl */
365
      mov dl, 0x0d
366
      int 0x21
367
      mov dl, 0x0a
368
      int 0x21
369
 
349 mateuszvis 370
      pop ds
364 mateuszvis 371
      pop dx
372
      pop cx
373
      pop bx
374
      pop ax
349 mateuszvis 375
    }
376
 
405 mateuszvis 377
    /* if nothing entered, loop again (but without appending an extra CR/LF) */
378
    if (cmdline[-1] == 0) goto SKIP_NEWLINE;
349 mateuszvis 379
 
364 mateuszvis 380
    /* replace \r by a zero terminator */
381
    cmdline[(unsigned char)(cmdline[-1])] = 0;
349 mateuszvis 382
 
364 mateuszvis 383
    /* move pointer forward to skip over any leading spaces */
384
    while (*cmdline == ' ') cmdline++;
349 mateuszvis 385
 
367 mateuszvis 386
    /* update rmod's ptr to COMPSPEC so it is always up to date */
387
    rmod_updatecomspecptr(rmod_seg, *rmod_envseg);
388
 
402 mateuszvis 389
    /* handle redirections (if any) */
390
    if (redir_parsecmd(cmdline, BUFFER) != 0) {
391
      outputnl("");
392
      continue;
393
    }
394
 
364 mateuszvis 395
    /* try matching (and executing) an internal command */
353 mateuszvis 396
    {
405 mateuszvis 397
      int ecode = cmd_process(rmod_seg, *rmod_envseg, cmdline, BUFFER);
353 mateuszvis 398
      if (ecode >= 0) *lastexitcode = ecode;
377 mateuszvis 399
      if (ecode >= -1) { /* internal command executed */
402 mateuszvis 400
        redir_revert(); /* revert stdout (in case it was redirected) */
377 mateuszvis 401
        continue;
402
      }
353 mateuszvis 403
    }
352 mateuszvis 404
 
364 mateuszvis 405
    /* if here, then this was not an internal command */
406
    run_as_external(cmdline);
349 mateuszvis 407
 
402 mateuszvis 408
    /* revert stdout (in case it was redirected) */
409
    redir_revert();
410
 
349 mateuszvis 411
    /* execvp() replaces the current process by the new one
412
    if I am still alive then external command failed to execute */
369 mateuszvis 413
    outputnl("Bad command or file name");
349 mateuszvis 414
 
415
  }
416
 
417
  return(0);
418
}