CMD.C
上传用户:dcs7469208
上传日期:2010-01-02
资源大小:443k
文件大小:16k
源码类别:

操作系统开发

开发平台:

DOS

  1. /****************************************************************/
  2. /* */
  3. /*        cmd.c */
  4. /* */
  5. /*      command.com Top Level Driver  */
  6. /* */
  7. /*   August 9, 1991 */
  8. /* */
  9. /*      Copyright (c) 1995, 1996 */
  10. /* Pasquale J. Villani */
  11. /* All Rights Reserved */
  12. /* */
  13. /* This file is part of DOS-C. */
  14. /* */
  15. /* DOS-C is free software; you can redistribute it and/or */
  16. /* modify it under the terms of the GNU General Public License */
  17. /* as published by the Free Software Foundation; either version */
  18. /* 2, or (at your option) any later version. */
  19. /* */
  20. /* DOS-C is distributed in the hope that it will be useful, but */
  21. /* WITHOUT ANY WARRANTY; without even the implied warranty of */
  22. /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See */
  23. /* the GNU General Public License for more details. */
  24. /* */
  25. /* You should have received a copy of the GNU General Public */
  26. /* License along with DOS-C; see the file COPYING.  If not, */
  27. /* write to the Free Software Foundation, 675 Mass Ave, */
  28. /* Cambridge, MA 02139, USA. */
  29. /****************************************************************/
  30. /* $Logfile:   C:/dos-c/src/command/cmd.c_v  $ */
  31. /* $Log:   C:/dos-c/src/command/cmd.c_v  $ 
  32.  * 
  33.  *    Rev 1.4   31 Jan 1998  8:12:30   patv
  34.  * Put preprocessor switch for version strings and changed log strings
  35.  * 
  36.  *    Rev 1.3   29 Aug 1996 13:07:04   patv
  37.  * Bug fixes for v0.91b
  38.  * 
  39.  *    Rev 1.2   19 Feb 1996  3:18:54   patv
  40.  * Added NLS, int2f and config.sys processing
  41.  * 
  42.  *    Rev 1.1   01 Sep 1995 18:04:34   patv
  43.  * First GPL release.
  44.  * 
  45.  *    Rev 1.0   02 Jul 1995 10:01:48   patv
  46.  * Initial revision.
  47.  */
  48. /* $EndLog$ */
  49. #include <ctype.h>
  50. #include "../../hdr/portab.h"
  51. #define MAIN
  52. #include "globals.h"
  53. #include "proto.h"
  54. #ifdef VERSION_STRINGS
  55. static BYTE *RcsId = "$Header:   C:/dos-c/src/command/cmd.c_v   1.4   31 Jan 1998  8:12:30   patv  $";
  56. #endif
  57. #ifdef PROTO
  58. struct table
  59. {
  60. BYTE *entry;
  61. BOOL (*func)(COUNT, BYTE **);
  62. };
  63. #else
  64. struct table
  65. {
  66. BYTE *entry;
  67. BOOL (*func)();
  68. };
  69. #endif
  70. #ifdef PROTO
  71. struct table *lookup(struct table *, BYTE *);
  72. VOID err_report(COUNT);
  73. VOID put_prompt(BYTE *);
  74. VOID Redirect(BYTE *, BYTE *, BYTE *, BOOL *);
  75. VOID RestoreIO(COUNT, COUNT);
  76. #else
  77. struct table *lookup();
  78. VOID err_report();
  79. VOID put_prompt();
  80. VOID Redirect();
  81. VOID RestoreIO();
  82. #endif
  83. BOOL ExecCmd();
  84. BOOL prompt();
  85. BOOL cmd_path();
  86. BOOL cmd_exit();
  87. BOOL type();
  88. BOOL cd();
  89. BOOL copy();
  90. BOOL del();
  91. BOOL ren();
  92. BOOL mkdir();
  93. BOOL rmdir();
  94. BOOL cmd_time();
  95. BOOL cmd_date();
  96. BOOL verify();
  97. BOOL ver();
  98. BOOL cmd_break();
  99. BOOL  batch();
  100. BOOL label_bat();
  101. BOOL pause_bat();
  102. BOOL call_bat();
  103. BOOL echo_bat();
  104. BOOL echo_dot_bat();
  105. BOOL for_bat();
  106. BOOL if_bat();
  107. BOOL rem_bat();
  108. BOOL shift_bat();
  109. BOOL goto_bat();
  110. BOOL set_bat();
  111. /* External cmmands */
  112. struct table  commands[] =
  113. {
  114. {"break", cmd_break},
  115. {"copy", copy},
  116. {"cd", cd},
  117. {"chdir", cd},
  118. {"date", cmd_date},
  119. {"del", del},
  120. {"dir", dir},
  121. {"erase", del},
  122. {"exit", cmd_exit},
  123. {"mkdir", mkdir},
  124. {"md", mkdir},
  125. {"path", cmd_path},
  126. {"prompt", prompt},
  127. {"ren", ren},
  128. {"rmdir", rmdir},
  129. {"rd", rmdir},
  130. {"time", cmd_time},
  131. {"type", type},
  132. {"verify", verify},
  133. {"ver", ver},
  134. {"if", if_bat},
  135. {"label", label_bat},
  136. {"pause", pause_bat},
  137. {"call", call_bat},
  138. {"echo", echo_bat},
  139. {"echo.", echo_dot_bat},
  140. {"echo+", echo_dot_bat},
  141. {"echo"", echo_dot_bat},
  142. {"echo/", echo_dot_bat},
  143. {"echo[", echo_dot_bat},
  144. {"echo]", echo_dot_bat},
  145. {"echo:", echo_dot_bat},
  146. {"for", for_bat},
  147. {"rem", rem_bat},
  148. {"shift", shift_bat},
  149. {"goto", goto_bat},
  150. {"set", set_bat},
  151. {"", ExecCmd}
  152. };
  153. static COUNT argc;
  154. static BYTE *argv[NPARAMS];
  155. static BOOL pflag, bootup = FALSE;
  156. VOID main()
  157. {
  158. COUNT nread;
  159. BOOL bool_FLAG = FALSE;
  160. BOOL cflag;
  161. BYTE FAR *cmd_tail;
  162. BYTE *p_ptr;
  163. extern UWORD _psp;
  164. psp FAR *p;
  165. COUNT driveno = -1;
  166. BYTE pattern[MAX_CMDLINE] = "";
  167. BYTE path[MAX_CMDLINE] = "", esize[MAX_CMDLINE] = "";
  168. /* Initialize the interpreter */
  169. p = MK_FP(_psp, 0);
  170. switchchar = '/';
  171. batch_FLAG = FALSE;
  172. argv[0] = args[0];
  173. argv[1] = (BYTE *)0;
  174. args[0][0] = '';
  175. *tail = '';
  176. lpEnviron = (BYTE FAR *)MK_FP(p -> ps_environ, 0);
  177. cmd_tail = MK_FP(_psp, 0x81);
  178. fstrncpy((BYTE FAR *)tail, cmd_tail, 0x7f);
  179. pflag = cflag = FALSE;
  180. dosopt("$d$p*[e:pc]+", (BYTE FAR *)tail,
  181. &driveno, path, pattern, esize, &pflag, &cflag);
  182. /* Get the passed-in Environment size and make certain we */
  183. /* allocate enough space */
  184. EnvSize = EnvSizeUp();
  185. if(EnvSize < ENV_DEFAULT)
  186. EnvSize = ENV_DEFAULT;
  187. if(*esize != '')
  188. {
  189. COUNT size = atoi(esize);
  190. bool_FLAG = EnvAlloc(size);
  191. EnvSize = size;
  192. }
  193. else
  194. bool_FLAG = EnvAlloc(EnvSize);
  195. if(!bool_FLAG)
  196. error_message(OUT_ENV_SPACE);
  197. /* Check what PROMPT is set in env to over ride default  */
  198. p_ptr = EnvLookup("PROMPT");
  199. if(p_ptr != (BYTE *)0)
  200. scopy(p_ptr, prompt_string);
  201. else
  202. scopy(dflt_pr_string, prompt_string);
  203. /* Check what PATH is set in env to over ride default  */
  204. p_ptr = EnvLookup("PATH");
  205. if(p_ptr != (BYTE *)0)
  206. scopy(p_ptr, path);
  207. else
  208. scopy(dflt_path_string, path);
  209. if(!cflag)
  210. {
  211. if(pflag)
  212. {
  213. /* Special MS-DOS compatability initialization, */
  214. /* all command shells terminate onto */
  215. /* themselves, but we always terminate at the */
  216. /* root shell. If anyone complains, we'll */
  217. /* change it. */
  218. #ifndef DEBUG
  219. p -> ps_parent = _psp;
  220. #endif
  221. /* Try to exec autoexec.bat */
  222. bootup = TRUE;
  223. *tail = '';
  224. if(!batch(".\autoexec.bat", tail))
  225. {
  226. *tail = '';
  227. cmd_date(1, argv);
  228. cmd_time(1, argv);
  229. bootup = FALSE;
  230. }
  231. }
  232. else
  233. {
  234. /* Announce our version */
  235. printf(ANNOUNCE, copyright);
  236. #ifdef SHWR
  237. printf("**** Shareware version ****nPlease register your copy.n");
  238. #else
  239. printf("nn");
  240. #endif
  241. }
  242. FOREVER
  243. {
  244. default_drive = DosGetDrive();
  245. put_prompt(prompt_string);
  246. if((nread = DosRead(STDIN, (BYTE FAR *)cmd_line, MAX_CMDLINE)) < 0)
  247. continue;
  248. do_command(nread);
  249. }
  250. }
  251. else
  252. {
  253. BYTE FAR *p;
  254. default_drive = DosGetDrive();
  255. for(p = cmd_tail; *p != 'r'; p++)
  256. {
  257. if(*p == '/' && (*(p + 1) == 'c' || *(p + 1) == 'C'))
  258. break;
  259. }
  260. p += 2;
  261. fstrncpy((BYTE FAR *)cmd_line, p, 0x7f);
  262. for(nread = 0; *p != 'r'; nread++, p++)
  263. ;
  264. ++nread;
  265. do_command(nread);
  266. }
  267. }
  268. VOID Redirect(cmd_line, Input, Output, AppendMode)
  269. BYTE *cmd_line, *Input, *Output;
  270. BOOL *AppendMode;
  271. {
  272. BYTE LocalBuffer[MAX_CMDLINE], *lp, *dp = cmd_line;
  273. /* First - create an image, since we'll be copying back into */
  274. /* the original buffer. */
  275. strcpy(LocalBuffer, cmd_line);
  276. /* Next, start looking for redirect symbols. */
  277. lp = skipwh(LocalBuffer);
  278. while(*lp != '')
  279. {
  280. switch(*lp)
  281. {
  282. case '<':
  283. lp = scan(++lp, Input);
  284. break;
  285. case '>':
  286. if(*(lp + 1) == '>')
  287. {
  288. ++lp;
  289. *AppendMode = TRUE;
  290. }
  291. lp = scan(++lp, Output);
  292. break;
  293. default:
  294. *dp++ = *lp++;
  295. break;
  296. }
  297. }
  298. *dp = '';
  299. }
  300. VOID do_command(nread)
  301. COUNT nread;
  302. {
  303. REG struct table *p;
  304. REG BYTE *lp;
  305. COUNT index = 0;
  306. BYTE Input[MAX_CMDLINE], Output[MAX_CMDLINE];
  307. BOOL AppendMode;
  308. COUNT OldStdin = -1, OldStdout = -1, ErrorCode;
  309. BOOL IORedirected = FALSE;
  310. if(nread <= 0)
  311. return;
  312. cmd_line[nread] = '';
  313. /* Pre-scan the command line and look for any re-directs */
  314. *Input = *Output = '';
  315. AppendMode = FALSE;
  316. Redirect(cmd_line, Input, Output, &AppendMode);
  317. IORedirected = (*Input != '' || *Output != '');
  318. if(*Input != '')
  319. {
  320. COUNT Handle;
  321. if(!DosDupHandle(STDIN, (COUNT FAR *)&OldStdin, (COUNT FAR *)&ErrorCode))
  322. {
  323. RestoreIO(OldStdin, -1);
  324. error_message(INTERNAL_ERR);
  325. return;
  326. }
  327. Handle = DosOpen((BYTE FAR *)Input, O_RDWR);
  328. if((Handle < 0) || (!DosForceDupHandle(Handle, STDIN, (COUNT FAR *)&ErrorCode)))
  329. {
  330. RestoreIO(OldStdin, -1);
  331. error_message(INTERNAL_ERR);
  332. return;
  333. }
  334. DosClose(Handle);
  335. }
  336. if(*Output != '')
  337. {
  338. COUNT Handle;
  339. if(!DosDupHandle(STDOUT, (COUNT FAR *)&OldStdout, (COUNT FAR *)&ErrorCode))
  340. {
  341. RestoreIO(-1, OldStdout);
  342. error_message(INTERNAL_ERR);
  343. return;
  344. }
  345. if(AppendMode)
  346. {
  347. if((Handle = DosOpen((BYTE FAR *)Output, O_RDWR)) < 0)
  348. {
  349. RestoreIO(-1, OldStdout);
  350. error_message(INTERNAL_ERR);
  351. return;
  352. }
  353. DosSeek(Handle, 2, 0l);
  354. }
  355. else
  356. Handle = DosCreat((BYTE FAR *)Output, D_NORMAL | D_ARCHIVE);
  357. if((Handle < 0) || (!DosForceDupHandle(Handle, STDOUT, (COUNT FAR *)&ErrorCode)))
  358. {
  359. RestoreIO(-1, OldStdout);
  360. error_message(INTERNAL_ERR);
  361. return;
  362. }
  363. DosClose(Handle);
  364. }
  365. for(argc = 0; argc < 16; argc++)
  366. {
  367. argv[argc] = (BYTE *)0;
  368. args[argc][0] = '';
  369. }
  370. lp = scanspl(cmd_line, args[0], '/');
  371. if(args[0][0] == '@')
  372. {
  373. at_FLAG = TRUE;
  374. index++;
  375. }
  376. else
  377. at_FLAG = FALSE;
  378. /* If preceeded by a @, swallow it, it was taken care of */
  379. /* elsewhere.  Also, change case so that our command verb is */
  380. /* case sensitive. */
  381. while(args[0][index] != '')
  382. {
  383. if(at_FLAG)
  384. args[0][index-1] = tolower(args[0][index]);
  385. else
  386. args[0][index] = tolower(args[0][index]);
  387. index++;
  388. }
  389. if(at_FLAG)
  390. args[0][index-1] = '';
  391. argv[0] = args[0];
  392. /* this kludge is for an MS-DOS wart emulation */
  393. tail = skipwh(lp);
  394. for(argc = 1; argc < NPARAMS; argc++)
  395. {
  396. lp = scan(lp, args[argc]);
  397. if(*args[argc] == '')
  398. break;
  399. else
  400. argv[argc] = args[argc];
  401. }
  402. if(*argv[0] != '')
  403. {
  404. /* Look for just a drive change command, and execute */
  405. /* it if found. */
  406. if(argv[0][1] == ':' && argv[0][2] == NULL)
  407. {
  408. BYTE c = argv[0][0];
  409. if(c >= 'a' && c <= 'z')
  410. c = c - 'a' + 'A';
  411. if(c >= 'A' && c <= 'Z')
  412. DosSetDrive(c - 'A');
  413. }
  414. /* It may be a help command request. */
  415. else if((argv[1][0] == switchchar) &&
  416.  (argv[1][1] == '?'))
  417. {
  418. strcpy(tail, " ");
  419. strcat(tail, argv[0]);
  420. strcat(tail, "rn");
  421. argc = 2;
  422. argv[1] = argv[0];
  423. argv[0] = "help";
  424. argv[2] = 0;
  425. ExecCmd(argc, argv);
  426. if(IORedirected)
  427. RestoreIO(OldStdin, OldStdout);
  428. }
  429. /* do a normal command execution */
  430. else
  431. {
  432. p = lookup(commands, argv[0]);
  433. (*(p -> func))(argc, argv);
  434. if(IORedirected)
  435. RestoreIO(OldStdin, OldStdout);
  436. }
  437. }
  438. }
  439. BOOL prompt(argc, argv)
  440. WORD argc;
  441. BYTE *argv[];
  442. {
  443. BYTE *p;
  444. BYTE *cmd = "PROMPT";
  445. if(argc == 1)
  446. {
  447. p = EnvLookup(cmd);
  448. if(p != (BYTE *)0)
  449. scopy(p, prompt_string);
  450. else
  451. {
  452. scopy(dflt_pr_string, prompt_string);
  453. EnvClearVar(cmd);
  454. }
  455. }
  456. else
  457. {
  458. /* Trim trailing newline */
  459. for(p = tail; (*p != 'r') && (*p != 'n'); p++)
  460. ;
  461. *p = '';
  462. /* should be scopy(argv[1], &pr_string[1]); but to */
  463. /* emulate an MS-DOS wart, is */
  464. scopy(tail, prompt_string);
  465. /* Now set the environment variable for all children to */
  466. /* see. */
  467. EnvSetVar(cmd, prompt_string);
  468. }
  469. return TRUE;
  470. }
  471. struct table *lookup(p, token)
  472. struct table *p;
  473. BYTE *token;
  474. {
  475. while(*(p -> entry) != '')
  476. {
  477. if(strcmp(p -> entry, token) == 0)
  478. break;
  479. else
  480. ++p;
  481. }
  482. return p;
  483. }
  484. VOID RestoreIO(DupStdin, DupStdout)
  485. {
  486. COUNT ErrorCode;
  487. if(DupStdin >= 0)
  488. {
  489. if(!DosForceDupHandle(DupStdin, STDIN, (COUNT FAR *)&ErrorCode))
  490. error_message(INTERNAL_ERR);
  491. DosClose(DupStdin);
  492. }
  493. if(DupStdout >= 0)
  494. {
  495. if(!DosForceDupHandle(DupStdout, STDOUT, (COUNT FAR *)&ErrorCode))
  496. error_message(INTERNAL_ERR);
  497. DosClose(DupStdout);
  498. }
  499. }
  500. BOOL ExecCmd(argc, argv)
  501. COUNT argc;
  502. BYTE *argv[];
  503. {
  504. exec_blk exb;
  505. COUNT err;
  506. BYTE tmppath[64];
  507. COUNT idx;
  508. BOOL ext = FALSE;
  509. BYTE *extp;
  510. COUNT len;
  511. BYTE *lp;
  512. CommandTail CmdTail;
  513. fcb fcb1, fcb2;
  514. static BYTE *extns[2] =
  515. {
  516. ".com",
  517. ".exe"
  518. };
  519. static BYTE *batfile = ".bat";
  520. BYTE PathString[MAX_CMDLINE];
  521. BYTE Path[MAX_CMDLINE], *pPath;
  522. /* Build the path string and create the full string that */
  523. /* includes "." so that the current directory is searched */
  524. /* first.  Note that Path is initialized outside the loop. */
  525. strcpy(Path, ".\");
  526. strcpy(PathString, EnvLookup("PATH"));
  527. pPath = PathString;
  528. do
  529. {
  530. /* Build a path to the command. */
  531. if(*pPath == ';')
  532. ++pPath;
  533. strcpy(tmppath, Path);
  534. if(*tmppath != '' && !((tmppath[strlen(tmppath) - 1] != '\') == 0))
  535. strcat(tmppath, "\");
  536. strcat(tmppath, argv[0]);
  537. /* batch processing */
  538. /* search for an extension in the specification */
  539. for(idx = len = strlen(argv[0]) ; idx > 0 && idx > (len - FEXT_SIZE - 2); --idx)
  540. {
  541. if(argv[0][idx] == '.')
  542. {
  543. ext = TRUE;
  544. extp = &argv[0][idx];
  545. break;
  546. }
  547. }
  548. /* If no extension was found, the entire path was */
  549. /* specified and we do not append an extension. */
  550. if(!ext)
  551. {
  552. strcat(tmppath, batfile);
  553. extp = batfile;
  554. }
  555. /* if it ends with a '.bat' (either user supplied or */
  556. /* previously added), try to run as a batch. */
  557. if((strcmp(extp, batfile) == 0) && batch(tmppath, tail))
  558. {
  559. if(pflag && bootup)
  560. bootup = FALSE;
  561. return TRUE;
  562. }
  563. /* tail comes in as a string with trailing newline.  */
  564. /* Convert it to a return only and put it into CmdTail */
  565. /* format */
  566. CmdTail.ctCount = (argc > 1) ? strlen(tail) : 1;
  567. strcpy(CmdTail.ctBuffer, " ");
  568. strcpy(&CmdTail.ctBuffer[1], (argc > 1) ? tail : "");
  569. CmdTail.ctBuffer[CmdTail.ctCount] = '';
  570. if(CmdTail.ctCount < LINESIZE)
  571. CmdTail.ctBuffer[CmdTail.ctCount] = '';
  572. rtn_errlvl = 0;
  573. exb.exec.env_seg = FP_SEG(lpEnviron);
  574. exb.exec.cmd_line = (CommandTail FAR *)&CmdTail;
  575. #if 0
  576. if(argc > 1)
  577. {
  578. DosParseFilename((BYTE FAR *)argv[1], (fcb FAR *)&fcb1, 0);
  579. exb.exec.fcb_1 = (fcb FAR *)&fcb1;
  580. }
  581. else
  582. exb.exec.fcb_1 = (fcb FAR *)0;
  583. if(argc > 2)
  584. {
  585. exb.exec.fcb_2 = (fcb FAR *)&fcb2;
  586. DosParseFilename((BYTE FAR *)argv[2], (fcb FAR *)&fcb2, 0);
  587. }
  588. else
  589. exb.exec.fcb_2 = (fcb FAR *)0;
  590. #else
  591. exb.exec.fcb_1 = (fcb FAR *)0;
  592. exb.exec.fcb_2 = (fcb FAR *)0;
  593. #endif
  594. for(idx = 0; idx < 2; idx++)
  595. {
  596. strcpy(tmppath, Path);
  597. if(*tmppath != '' && !((tmppath[strlen(tmppath) - 1] != '\') == 0))
  598. strcat(tmppath, "\");
  599. strcat(tmppath, argv[0]);
  600. if(!ext)
  601. {
  602. strcat(tmppath, extns[idx]);
  603. extp = extns[idx];
  604. }
  605. if(!(strcmp(extp, extns[idx]) == 0))
  606. continue;
  607. if((rtn_errlvl = err = DosExec((BYTE FAR *)tmppath, (exec_blk FAR *)&exb)) != SUCCESS)
  608. {
  609. switch(err)
  610. {
  611. case DE_FILENOTFND:
  612. continue;
  613. case DE_INVLDFUNC:
  614. rtn_errlvl = INV_FUNCTION_PARAM;
  615. goto errmsg;
  616. case DE_PATHNOTFND:
  617. rtn_errlvl = PATH_NOT_FOUND;
  618. goto errmsg;
  619. case DE_TOOMANY:
  620. rtn_errlvl = TOO_FILES_OPEN;
  621. goto errmsg;
  622. case DE_ACCESS:
  623. rtn_errlvl = ACCESS_DENIED;
  624. goto errmsg;
  625. case DE_NOMEM:
  626. rtn_errlvl = INSUFF_MEM;
  627. goto errmsg;
  628. default:
  629. rtn_errlvl = EXEC_ERR;
  630. errmsg:
  631. error_message(rtn_errlvl);
  632. return FALSE;
  633. }
  634. }
  635. else
  636. {
  637. rtn_errlvl = DosRtnValue() & 0xff;
  638. return TRUE;
  639. }
  640. }
  641. if(err < 0 || idx == 2)
  642. {
  643. if(!(err == DE_FILENOTFND || idx == 2))
  644. {
  645. error_message(EXEC_FAIL);
  646. return FALSE;
  647. }
  648. continue;
  649. }
  650. }
  651. while(*Path = '', pPath = scanspl(pPath, Path, ';'), *Path != '');
  652. error_message(BAD_CMD_FILE_NAME);
  653. return FALSE;
  654. }
  655. BOOL cmd_exit(argc, argv)
  656. COUNT argc;
  657. BYTE FAR *argv[];
  658. {
  659. #ifndef DEBUG
  660. /* Don't exit from a permanent shell */
  661. if(pflag)
  662. return TRUE;
  663. #endif
  664. /* If no values passed, return errorvalue = 0 */
  665. if(argc == 1)
  666. DosExit(0);
  667. /* otherwise return what the user asked for */
  668. else
  669. {
  670. COUNT ret_val;
  671. static BYTE nums[] = "0123456789";
  672. BYTE FAR *p;
  673. for(ret_val = 0, p = argv[1]; isdigit(*p); p++)
  674. {
  675. COUNT j;
  676. for(j = 0; j < 10; j++)
  677. if(nums[j] == *p)
  678. break;
  679. ret_val += j;
  680. }
  681. DosExit(ret_val);
  682. }
  683.         return TRUE;
  684. }
  685. VOID sto(c)
  686. COUNT c;
  687. {
  688. BYTE ch[1];
  689. *ch = c;
  690. DosWrite(STDOUT, (BYTE FAR *)ch, 1);
  691. }