Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

723 lines
19 KiB

  1. /*++
  2. Copyright (c) 1988-1999 Microsoft Corporation
  3. Module Name:
  4. cop.c
  5. Abstract:
  6. Conditional/sequential command execution
  7. --*/
  8. #include "cmd.h"
  9. extern int LastRetCode;
  10. extern int ExtCtrlc; /* @@1 */
  11. /* M000 ends */
  12. unsigned PipeCnt ; /* M007 - Active pipe count */
  13. struct pipedata *PdHead = NULL; /* M007 - 1st element of pipedata list */
  14. struct pipedata *PdTail = NULL; /* M007 - Last element of pipedata list */
  15. HANDLE PipePid ; /* M007 - Communication with ECWork */
  16. /*** eComSep - execute a statement containing a command separator
  17. *
  18. * Purpose:
  19. * Execute the left and right hand sides of a command separator
  20. * operator.
  21. *
  22. * int eComSep(struct node *n)
  23. *
  24. * Args:
  25. * n - parse tree node containing the command separator node
  26. *
  27. * Returns:
  28. * Whatever the right hand side returns.
  29. *
  30. * Notes:
  31. * Revised to always supply both args to Dispatch().
  32. */
  33. int eComSep(n)
  34. struct node *n ;
  35. {
  36. Dispatch(RIO_OTHER,n->lhs) ;
  37. if (GotoFlag) {
  38. return SUCCESS;
  39. } else {
  40. return(Dispatch(RIO_OTHER,n->rhs)) ;
  41. }
  42. }
  43. /*** eOr - execute an OR operation
  44. *
  45. * Purpose:
  46. * Execute the left hand side of an OR operator (||). If it succeeds,
  47. * quit. Otherwise execute the right side of the operator.
  48. *
  49. * int eOr(struct node *n)
  50. *
  51. * Args:
  52. * n - parse tree node containing the OR operator node
  53. *
  54. * Returns:
  55. * If the left hand side succeeds, return SUCCESS. Otherwise, return
  56. * whatever the right side returns.
  57. *
  58. * Notes:
  59. * Revised to always supply both args to Dispatch().
  60. */
  61. int eOr(n)
  62. struct node *n ;
  63. {
  64. int i ; /* Retcode from L.H. side of OR */
  65. if ((i = Dispatch(RIO_OTHER,n->lhs)) == SUCCESS)
  66. return(SUCCESS) ;
  67. else {
  68. LastRetCode = i;
  69. return(Dispatch(RIO_OTHER,n->rhs)) ;
  70. }
  71. }
  72. /*** eAnd - execute an AND operation
  73. *
  74. * Purpose:
  75. * Execute the left hand side of an AND operator (&&). If it fails,
  76. * quit. Otherwise execute the right side of the operator.
  77. *
  78. * int eAnd(struct node *n)
  79. *
  80. * Args:
  81. * n - parse tree node containing the AND operator node
  82. *
  83. * Returns:
  84. * If the left hand side fails, return its return code. Otherwise, return
  85. * whatever the right side returns.
  86. *
  87. * Notes:
  88. * Revised to always supply both args to Dispatch().
  89. */
  90. int eAnd(n)
  91. struct node *n ;
  92. {
  93. int i ; /* Retcode from L.H. side of AND */
  94. if ((i = Dispatch(RIO_OTHER,n->lhs)) != SUCCESS) {
  95. return(i) ;
  96. } else if (GotoFlag) {
  97. return SUCCESS;
  98. } else {
  99. return(Dispatch(RIO_OTHER,n->rhs)) ;
  100. }
  101. }
  102. /********************* START OF SPECIFICATION **************************/
  103. /* */
  104. /* SUBROUTINE NAME: ePipe */
  105. /* */
  106. /* DESCRIPTIVE NAME: Pipe Process */
  107. /* */
  108. /* FUNCTION: Execute the left side of the pipe and direct its output to*/
  109. /* the right side of the pipe. */
  110. /* */
  111. /* NOTES: None */
  112. /* */
  113. /* */
  114. /* ENTRY POINT: ePipe */
  115. /* LINKAGE: NEAR */
  116. /* */
  117. /* INPUT: n - parse tree node containing the pipe operator */
  118. /* */
  119. /* OUTPUT: None */
  120. /* */
  121. /* EXIT-NORMAL: The return code of the right side process. */
  122. /* */
  123. /* EXIT-ERROR: Failure if no pipe redirection can take place. */
  124. /* */
  125. /* EFFECTS: */
  126. /* */
  127. /* struct pipedata { */
  128. /* unsigned rh ; Pipe read handle */
  129. /* unsigned wh ; Pipe write handle */
  130. /* unsigned shr ; Handles where the normal... */
  131. /* unsigned shw ; ...STDIN/OUT handles are saved */
  132. /* unsigned lPID ; Pipe lh side PID */
  133. /* unsigned rPID ; Pipe rh side PID */
  134. /* unsigned lstart ; Start Information of lh side @@4*/
  135. /* unsigned rstart ; Start Information of rh side @@4*/
  136. /* struct pipedata *prvpds ; Ptr to previous pipedata struct */
  137. /* struct pipedata *nxtpds ; Ptr to next pipedata struct */
  138. /* } */
  139. /* */
  140. /* unsigned PipePID; Pipe Process ID */
  141. /* */
  142. /* unsigned start_type; Start Information */
  143. /* */
  144. /* */
  145. /* INTERNAL REFERENCES: */
  146. /* ROUTINES: */
  147. /* PutStdErr - Print an error message */
  148. /* Abort - Terminate program with abort */
  149. /* SetList - Set Link List for pipedata structure */
  150. /* Cdup - Duplicate supplied handle and save the new handle*/
  151. /* Cdup2 - Duplicate supplied handle and save the new handle*/
  152. /* Dispatch - Execute the program */
  153. /* PipeErr - Handle pipe error */
  154. /* Cclose - Close the specified handle */
  155. /* PipeWait - Wait for the all pipe process completion */
  156. /* */
  157. /* EXTERNAL REFERENCES: */
  158. /* ROUTINES: */
  159. /* DOSMAKEPIPE - Make pipe */
  160. /* */
  161. /********************** END OF SPECIFICATION **************************/
  162. /*** ePipe - Create a pipeline between two processes (M000)
  163. *
  164. * Purpose:
  165. * Execute the left side of the pipe and direct its output to
  166. * the right side of the pipe.
  167. *
  168. * int ePipe(struct node *n)
  169. *
  170. * Args:
  171. * n - parse tree node containing the pipe operator
  172. *
  173. * Returns:
  174. * The return code of the right side process or failure if no
  175. * pipe redirection can take place.
  176. *
  177. * Notes:
  178. * M007 - This function has been completely rewritten for real pipes.
  179. *
  180. */
  181. int ePipe(n)
  182. struct node *n ;
  183. {
  184. struct pipedata *Pd ; /* Pipe struct pointer */
  185. int k = 0 ; /* RH side return code */
  186. struct node *l ; /* Copy of left side arg */
  187. struct node *r ; /* Copy of right side arg */
  188. extern unsigned start_type ; /* API type used to start */
  189. TCHAR cflags ; /* */
  190. l = n->lhs ; /* Equate locals to... */
  191. r = n->rhs ; /* ...L & R operations */
  192. DEBUG((OPGRP,PILVL,"PIPES:LH = %d, RH = %d ",l->type,r->type)) ;
  193. if (!(Pd = (struct pipedata *)mkstr(sizeof(struct pipedata)))) {
  194. DEBUG((OPGRP,PILVL,"PIPES:Couldn't alloc structure!")) ;
  195. return(FAILURE) ;
  196. };
  197. //
  198. // Create a pipe with a read handle and a write handle
  199. //
  200. if (_pipe((int *)Pd, 0, O_BINARY)) {
  201. DEBUG((OPGRP,PILVL,"PIPES:pipe failed!")) ;
  202. PutStdErr(ERROR_NOT_ENOUGH_MEMORY, NOARGS); /* M013 */
  203. return(FAILURE) ;
  204. Abort() ;
  205. };
  206. SetList(Pd->rh) ; /* M009 */
  207. SetList(Pd->wh) ; /* M009 */
  208. DEBUG((OPGRP,PILVL,"PIPES:Pipe built. Handles: rd = %d wt = %d ",Pd->rh, Pd->wh)) ;
  209. DEBUG((OPGRP,PILVL,"PIPES:Pipe (pre-index) count = %d", PipeCnt)) ;
  210. if (!PipeCnt++) { /* Already some pipes? */
  211. PdHead = PdTail = Pd ; /* No, set head/tail ptrs */
  212. Pd->prvpds = NULL ; /* No previous structure */
  213. DEBUG((OPGRP,PILVL,"PIPES:This is first pipe.")) ;
  214. } else {
  215. DEBUG((OPGRP,PILVL,"PIPES:This is pipe %d.", PipeCnt)) ;
  216. PdTail->nxtpds = Pd ;
  217. Pd->prvpds = PdTail ;
  218. Pd->nxtpds = NULL ;
  219. PdTail = Pd ;
  220. } ;
  221. //
  222. // Set up the redirection for the left-hand side; the writing side.
  223. // We do this by saving the current stdout, duplicating the pipe-write
  224. // handle onto stdout, and then invoking the left-hand side of the pipe.
  225. // When we do this, the lefthand side will fill the pipe with data.
  226. //
  227. //
  228. // Save stdout handle
  229. //
  230. if ((Pd->shw = Cdup(STDOUT)) == BADHANDLE) { /* Save STDOUT (M009) */
  231. Pd->shw = BADHANDLE ; /* If err, go process it */
  232. PipeErr() ; /* DOES NOT RETURN */
  233. };
  234. DEBUG((OPGRP,PILVL,"PIPES:STDOUT dup'd to %d.", Pd->shw)) ;
  235. //
  236. // Make stdout point to the write side of the pipe
  237. //
  238. if (Cdup2(Pd->wh, STDOUT) == BADHANDLE) /* Make wh STDOUT (M009) */
  239. PipeErr() ; /* DOES NOT RETURN */
  240. Cclose(Pd->wh) ; /* Close pipe hndl (M009) */
  241. Pd->wh = 0 ; /* And zero the holder */
  242. if (l->type <= CMDTYP) { /* @@5a */
  243. /* @@5a */
  244. FindAndFix( (struct cmdnode *) l, &cflags ) ; /* @@5a */
  245. } /* @@5a */
  246. DEBUG((OPGRP,PILVL,"PIPES:Write pipe now STDOUT")) ;
  247. //
  248. // Execute the left side of the pipe, fillng the pipe
  249. //
  250. k = Dispatch(RIO_PIPE,l) ;
  251. //
  252. // This closes the read handle in the left hand pipe. I don't know
  253. // why we're doing this.
  254. //
  255. if (PipePid != NULL) {
  256. DuplicateHandle( PipePid,
  257. CRTTONT(Pd->rh),
  258. NULL,
  259. NULL,
  260. 0,
  261. FALSE,
  262. DUPLICATE_CLOSE_SOURCE);
  263. }
  264. //
  265. // Restore the saved stdout
  266. //
  267. if (Cdup2(Pd->shw, STDOUT) == BADHANDLE)
  268. PipeErr( );
  269. //
  270. // Closed the saved handle
  271. //
  272. Cclose(Pd->shw) ; /* M009 */
  273. Pd->shw = 0 ;
  274. DEBUG((OPGRP,PILVL,"PIPES:STDOUT now handle 1 again.")) ;
  275. if (k) {
  276. ExtCtrlc = 2; /* @@1 */
  277. Abort() ;
  278. }
  279. Pd->lPID = PipePid ;
  280. Pd->lstart = start_type ; /* Save the start_type in pipedata struct */
  281. PipePid = 0 ;
  282. start_type = NONEPGM ; /* Reset the start_type D64 */
  283. DEBUG((OPGRP,PILVL,"PIPES:Dispatch LH side succeeded - LPID = %d.",Pd->lPID)) ;
  284. //
  285. // Start on the right hand side of the pipe. Save the current stdin,
  286. // copy the pipe read handle to stdin and then execute the right hand side
  287. // of the pipe
  288. //
  289. //
  290. // Save stdin
  291. //
  292. if ((Pd->shr = Cdup(STDIN)) == BADHANDLE) { /* Save STDIN (M009) */
  293. Pd->shr = BADHANDLE ;
  294. PipeErr() ; /* DOES NOT RETURN */
  295. };
  296. DEBUG((OPGRP,PILVL,"PIPES:STDIN dup'd to %d.", Pd->shr)) ;
  297. //
  298. // Point stdin at the pipe read handle
  299. //
  300. if (Cdup2(Pd->rh, STDIN) == BADHANDLE) /* Make rh STDIN (M009) */
  301. PipeErr() ; /* DOES NOT RETURN */
  302. Cclose(Pd->rh) ; /* Close pipe hndl (M009) */
  303. Pd->rh = 0 ; /* And zero the holder */
  304. if (r->type <= CMDTYP) { /* @@5a */
  305. /* @@5a */
  306. FindAndFix( (struct cmdnode *) r, &cflags) ; /* @@5a */
  307. }; /* @@5a */
  308. DEBUG((OPGRP,PILVL,"PIPES:Read pipe now STDIN")) ;
  309. //
  310. // Start off the right hand side of the pipe
  311. //
  312. k = Dispatch(RIO_PIPE,r) ;
  313. //
  314. // Restore the saved stdin
  315. //
  316. if (Cdup2(Pd->shr, STDIN) == BADHANDLE) /* M009 */
  317. PipeErr() ; /* DOES NOT RETURN */
  318. //
  319. // Get rid of the saved stdin
  320. //
  321. Cclose(Pd->shr) ; /* M009 */
  322. Pd->shr = 0 ;
  323. DEBUG((OPGRP,PILVL,"PIPES:STDIN now handle 0 again.")) ;
  324. if (k) {
  325. ExtCtrlc = 2; /* @@1 */
  326. Abort() ;
  327. }
  328. Pd->rPID = PipePid ;
  329. Pd->rstart = start_type ; /* Save the start_type in pipedata struct */
  330. PipePid = 0 ;
  331. start_type = NONEPGM ; /* Reset the start_type D64 */
  332. DEBUG((OPGRP,PILVL,"PIPES:Dispatch RH side succeeded - RPID = %d.",Pd->rPID)) ;
  333. if (!(--PipeCnt)) { /* Additional pipes? */
  334. DEBUG((OPGRP,PILVL,"PIPES:Returning from top level pipe. Cnt = %d", PipeCnt)) ;
  335. return(PipeWait()) ; /* No, return CWAIT */
  336. };
  337. DEBUG((OPGRP,PILVL,"PIPES:Returning from pipe. Cnt = %d", PipeCnt)) ;
  338. return(k) ; /* Else return exec ret */
  339. }
  340. /*** PipeErr - Fixup and error out following pipe error
  341. *
  342. * Purpose:
  343. * To provide single error out point for multiple error conditions.
  344. *
  345. * int PipeErr()
  346. *
  347. * Args:
  348. * None.
  349. *
  350. * Returns:
  351. * DOES NOT RETURN TO CALLER. Instead it causes an internal Abort().
  352. *
  353. */
  354. void PipeErr()
  355. {
  356. PutStdErr(MSG_PIPE_FAILURE, NOARGS) ; /* M013 */
  357. Abort() ;
  358. }
  359. /********************* START OF SPECIFICATION **************************/
  360. /* */
  361. /* SUBROUTINE NAME: PipeWait */
  362. /* */
  363. /* DESCRIPTIVE NAME: Wait and Collect Retcode for All Pipe Completion */
  364. /* */
  365. /* FUNCTION: This routine calls WaitProc or WaitTermQProc for all */
  366. /* pipelined processes until entire pipeline is finished. */
  367. /* The return code of right most element is returned. */
  368. /* */
  369. /* NOTES: If the pipelined process is started by DosExecPgm, */
  370. /* WaitProc is called. If the pipelined process is started */
  371. /* by DosStartSession, WaitTermQProc is called. */
  372. /* */
  373. /* */
  374. /* ENTRY POINT: PipeWait */
  375. /* LINKAGE: NEAR */
  376. /* */
  377. /* INPUT: None */
  378. /* */
  379. /* OUTPUT: None */
  380. /* */
  381. /* EXIT-NORMAL: No error return code */
  382. /* */
  383. /* EXIT-ERROR: Error return code from either WaitTermQProc or WaitProc*/
  384. /* */
  385. /* */
  386. /* EFFECTS: None. */
  387. /* */
  388. /* INTERNAL REFERENCES: */
  389. /* ROUTINES: */
  390. /* WaitProc - wait for the termination of the specified process, */
  391. /* its child process, and related pipelined */
  392. /* processes. */
  393. /* */
  394. /* WaitTermQProc - wait for the termination of the specified */
  395. /* session and related pipelined session. */
  396. /* */
  397. /* EXTERNAL REFERENCES: */
  398. /* ROUTINES: */
  399. /* WINCHANGESWITCHENTRY - Change switch list entry */
  400. /* */
  401. /********************** END OF SPECIFICATION **************************/
  402. /*** PipeWait - wait and collect retcode for all pipe completion (M007)
  403. *
  404. * Purpose:
  405. * To do cwaits on all pipelined processes until entire pipeline
  406. * is finished. The retcode of the rightmost element of the pipe
  407. * is returned.
  408. *
  409. * int PipeWait()
  410. *
  411. * Args:
  412. * None.
  413. *
  414. * Returns:
  415. * Retcode of rightmost pipe process.
  416. *
  417. */
  418. PipeWait()
  419. {
  420. unsigned i ;
  421. DEBUG((OPGRP,PILVL,"PIPEWAIT:Entered - PipeCnt = %d", PipeCnt)) ;
  422. while (PdHead) {
  423. if (PdHead->lPID) {
  424. DEBUG((OPGRP, PILVL, "PIPEWAIT: lPID %d, lstart %d", PdHead->lPID, PdHead->lstart));
  425. if ( PdHead->lstart == EXECPGM )
  426. {
  427. i = WaitProc(PdHead->lPID) ; /* M012 - Wait LH */
  428. DEBUG((OPGRP,PILVL,"PIPEWAIT:CWAIT on LH - Ret = %d, SPID = %d", i, PdHead->lPID)) ;
  429. }
  430. // else
  431. // {
  432. // WaitTermQProc(PdHead->lPID, &i) ;
  433. //
  434. // DEBUG((OPGRP,PILVL,"PIPEWAIT:Read TermQ on LH - Ret = %d, PID = %d", i, PdHead->lPID)) ;
  435. // } ;
  436. //
  437. } ;
  438. if (PdHead->rPID) {
  439. DEBUG((OPGRP, PILVL, "PIPEWAIT: rPID %d, rstart %d", PdHead->rPID, PdHead->rstart));
  440. if ( PdHead->rstart == EXECPGM )
  441. {
  442. i = WaitProc(PdHead->rPID) ; /* M012 - Wait RH */
  443. DEBUG((OPGRP,PILVL,"PIPEWAIT:CWAIT on RH - Ret = %d, PID = %d", i, PdHead->rPID)) ;
  444. }
  445. // else
  446. // {
  447. // WaitTermQProc(PdHead->rPID, &i) ;
  448. //
  449. // DEBUG((OPGRP,PILVL,"PIPEWAIT:Read TermQ on LH - Ret = %d, PID = %d", i, PdHead->rPID)) ;
  450. // } ;
  451. } ;
  452. PdHead = PdHead->nxtpds ;
  453. } ;
  454. DEBUG((OPGRP,PILVL,"PIPEWAIT: complete, Retcode = %d", i)) ;
  455. PdTail = NULL ; /* Cancel linked list... */
  456. PipeCnt = 0 ; /* ...pipe count and pipe PID */
  457. PipePid = 0 ;
  458. LastRetCode = i;
  459. return(i) ;
  460. }
  461. /*** BreakPipes - disconnect all active pipes (M000)
  462. *
  463. * Purpose:
  464. * To remove the temporary pipe files and invalidate the pipedata
  465. * structure when pipes are to be terminated, either through the
  466. * completion of the pipe operation or SigTerm.
  467. *
  468. * This routine is called directly by the signal handler and must
  469. * not generate any additional error conditions.
  470. *
  471. * void BreakPipes()
  472. *
  473. * Args:
  474. * None.
  475. *
  476. * Returns:
  477. * Nothing.
  478. *
  479. * Notes:
  480. * M007 - This function has been completely rewritten for real pipes.
  481. *
  482. * *** W A R N I N G ! ***
  483. * THIS ROUTINE IS CALLED AS A PART OF SIGNAL/ABORT RECOVERY AND
  484. * THEREFORE MUST NOT BE ABLE TO TRIGGER ANOTHER ABORT CONDITION.
  485. *
  486. */
  487. void BreakPipes()
  488. {
  489. unsigned i ;
  490. struct pipedata *pnode;
  491. DEBUG((OPGRP,PILVL,"BRKPIPES:Entered - PipeCnt = %d", PipeCnt)) ;
  492. /* The following two lines have been commented out */
  493. /* because the NULL test on PdTail should be enough, */
  494. /* and more importantly, even if PipeCnt is 0, you */
  495. /* may still have been servicing a pipe in Pipewait */
  496. /* if (!PipeCnt) */ /* If no active pipes... */
  497. /* return ; */ /* ...don't do anything */
  498. pnode = PdTail;
  499. /* First, kill all of the processes */
  500. while (pnode) {
  501. if (pnode->lPID!=(HANDLE) NULL) {
  502. /* M012 */ i = KillProc(pnode->lPID, FALSE) ; /* Kill LH */
  503. DEBUG((OPGRP,PILVL,"BRKPIPES:LH (Pid %d) killed - Retcode = %d", PdTail->lPID, i)) ;
  504. } ;
  505. if (pnode->rPID!=(HANDLE) NULL) {
  506. /* M012 */ i = KillProc(pnode->rPID, FALSE) ; /* Kill RH */
  507. DEBUG((OPGRP,PILVL,"BRKPIPES:RH (Pid %d) killed - Retcode = %d", PdTail->rPID, i)) ;
  508. } ;
  509. pnode = pnode->prvpds ;
  510. }
  511. /* Wait for the processes to die, and clean up file handles */
  512. while (PdTail) {
  513. if (PdTail->lPID) {
  514. if (PdTail->lstart == EXECPGM) {
  515. i = WaitProc(PdTail->lPID);
  516. // } else {
  517. // WaitTermQProc(PdTail->lPID, &i) ;
  518. }
  519. } ;
  520. if (PdTail->rPID) {
  521. if (PdTail->rstart == EXECPGM) {
  522. i = WaitProc(PdTail->rPID);
  523. // } else {
  524. // WaitTermQProc(PdTail->rPID, &i) ;
  525. }
  526. } ;
  527. if (PdTail->rh) {
  528. Cclose(PdTail->rh) ; /* M009 */
  529. DEBUG((OPGRP,PILVL,"BRKPIPES:Pipe read handle closed")) ;
  530. } ;
  531. if (PdTail->wh) {
  532. Cclose(PdTail->wh) ; /* M009 */
  533. DEBUG((OPGRP,PILVL,"BRKPIPES:Pipe write handle closed")) ;
  534. } ;
  535. if(PdTail->shr) {
  536. FlushFileBuffers(CRTTONT(PdTail->shr));
  537. Cdup2(PdTail->shr, STDIN) ; /* M009 */
  538. Cclose(PdTail->shr) ; /* M009 */
  539. DEBUG((OPGRP,PILVL,"BRKPIPES:STDIN restored.")) ;
  540. } ;
  541. if(PdTail->shw) {
  542. Cdup2(PdTail->shw, STDOUT) ; /* M009 */
  543. Cclose(PdTail->shw) ; /* M009 */
  544. DEBUG((OPGRP,PILVL,"BRKPIPES:STDOUT restored.")) ;
  545. } ;
  546. PdTail = PdTail->prvpds ;
  547. } ;
  548. PdHead = NULL ; /* Cancel linked list... */
  549. PipeCnt = 0 ; /* ...pipe count and pipe PID */
  550. PipePid = 0;
  551. DEBUG((OPGRP,PILVL,"BRKPIPES:Action complete, returning")) ;
  552. }
  553. /*** eParen - execute a parenthesized statement group
  554. *
  555. * Purpose:
  556. * Execute the group of statements enclosed by a statement grouping
  557. * operator; parenthesis().
  558. *
  559. * int eParen(struct node *n)
  560. *
  561. * Args:
  562. * n - parse tree node containing the PAREN operator node
  563. *
  564. * Returns:
  565. * Whatever the statement group returns.
  566. *
  567. * Notes:
  568. * M000 - Altered to always supply both args to Dispatch().
  569. * M004 - Debug statements were added for SILTYP operator.
  570. * ** WARNING **
  571. * BOTH THE LEFT PAREN AND THE SILENT OPERATOR (@) USE eParen
  572. * WHEN DISPATCHED. CHANGING ONE WILL AFFECT THE OTHER !!
  573. */
  574. int eParen(n)
  575. struct node *n ;
  576. {
  577. DEBUG((OPGRP,PNLVL,"ePAREN: Operator is %s", (n->type == PARTYP) ? "Paren" : "Silent")) ;
  578. return(Dispatch(RIO_OTHER,n->lhs)) ;
  579. }