Subversion Repositories SvarDOS

Rev

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

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