PageRenderTime 63ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/branches/SN-NG4-svngui/expect/exp_chan.c

https://gitlab.com/OpenSourceMirror/sourcenav
C | 661 lines | 385 code | 75 blank | 201 comment | 67 complexity | de2c1feb26a499e09608a0a2cc1c45f6 MD5 | raw file
  1. /*
  2. * tclUnixChan.c
  3. *
  4. * Channel driver for Expect channels.
  5. * Based on UNIX File channel from TclUnixChan.c
  6. *
  7. */
  8. #include <sys/types.h>
  9. #include <stdio.h>
  10. #include <signal.h>
  11. #include <errno.h>
  12. #include <ctype.h> /* for isspace */
  13. #include <time.h> /* for time(3) */
  14. #include "expect_cf.h"
  15. #ifdef HAVE_SYS_WAIT_H
  16. #include <sys/wait.h>
  17. #endif
  18. #ifdef HAVE_UNISTD_H
  19. # include <unistd.h>
  20. #endif
  21. #include <errno.h>
  22. #include "tclInt.h" /* Internal definitions for Tcl. */
  23. #include "tcl.h"
  24. #include "string.h"
  25. #include "exp_rename.h"
  26. #include "exp_prog.h"
  27. #include "exp_command.h"
  28. #include "exp_log.h"
  29. static int ExpBlockModeProc _ANSI_ARGS_((ClientData instanceData,
  30. int mode));
  31. static int ExpCloseProc _ANSI_ARGS_((ClientData instanceData,
  32. Tcl_Interp *interp));
  33. static int ExpInputProc _ANSI_ARGS_((ClientData instanceData,
  34. char *buf, int toRead, int *errorCode));
  35. static int ExpOutputProc _ANSI_ARGS_((
  36. ClientData instanceData, char *buf, int toWrite,
  37. int *errorCode));
  38. static void ExpWatchProc _ANSI_ARGS_((ClientData instanceData,
  39. int mask));
  40. static int ExpGetHandleProc _ANSI_ARGS_((ClientData instanceData,
  41. int direction, ClientData *handlePtr));
  42. /*
  43. * This structure describes the channel type structure for Expect-based IO:
  44. */
  45. Tcl_ChannelType expChannelType = {
  46. "exp", /* Type name. */
  47. /* Tcl_ChannelType was redefined in 8.3.2 but Tcl does not
  48. advertise its patch level in a useful way so for simplicity,
  49. assume 8.3 is a modern 8.3, i.e. 8.3.2
  50. */
  51. #if (TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 3))
  52. TCL_CHANNEL_VERSION_2,
  53. ExpCloseProc, /* Close proc. */
  54. ExpInputProc, /* Input proc. */
  55. ExpOutputProc, /* Output proc. */
  56. NULL, /* Seek proc. */
  57. NULL, /* Set option proc. */
  58. NULL, /* Get option proc. */
  59. ExpWatchProc, /* Initialize notifier. */
  60. ExpGetHandleProc, /* Get OS handles out of channel. */
  61. NULL, /* Close2 proc */
  62. ExpBlockModeProc, /* Set blocking/nonblocking mode.*/
  63. NULL, /* Flush proc. */
  64. NULL, /* Handle channel event proc. */
  65. #else
  66. /* Expect channels are always non-blocking */
  67. ExpBlockModeProc, /* Set blocking/nonblocking mode.*/
  68. ExpCloseProc, /* Close proc. */
  69. ExpInputProc, /* Input proc. */
  70. ExpOutputProc, /* Output proc. */
  71. NULL, /* Seek proc. */
  72. NULL, /* Set option proc. */
  73. NULL, /* Get option proc. */
  74. ExpWatchProc, /* Initialize notifier. */
  75. ExpGetHandleProc, /* Get OS handles out of channel. */
  76. NULL, /* Close2 proc */
  77. #endif
  78. };
  79. typedef struct ThreadSpecificData {
  80. /*
  81. * List of all exp channels currently open. This is per thread and is
  82. * used to match up fd's to channels, which rarely occurs.
  83. */
  84. ExpState *firstExpPtr;
  85. int channelCount; /* this is process-wide as it is used to
  86. give user some hint as to why a spawn has failed
  87. by looking at process-wide resource usage */
  88. } ThreadSpecificData;
  89. static Tcl_ThreadDataKey dataKey;
  90. /*
  91. *----------------------------------------------------------------------
  92. *
  93. * ExpBlockModeProc --
  94. *
  95. * Helper procedure to set blocking and nonblocking modes on a
  96. * file based channel. Invoked by generic IO level code.
  97. *
  98. * Results:
  99. * 0 if successful, errno when failed.
  100. *
  101. * Side effects:
  102. * Sets the device into blocking or non-blocking mode.
  103. *
  104. *----------------------------------------------------------------------
  105. */
  106. /* ARGSUSED */
  107. static int
  108. ExpBlockModeProc(instanceData, mode)
  109. ClientData instanceData; /* Exp state. */
  110. int mode; /* The mode to set. Can be one of
  111. * TCL_MODE_BLOCKING or
  112. * TCL_MODE_NONBLOCKING. */
  113. {
  114. ExpState *esPtr = (ExpState *) instanceData;
  115. int curStatus;
  116. /*printf("ExpBlockModeProc(%d)\n",mode);
  117. printf("fdin = %d\n",esPtr->fdin);*/
  118. #ifndef USE_FIONBIO
  119. curStatus = fcntl(esPtr->fdin, F_GETFL);
  120. /*printf("curStatus = %d\n",curStatus);*/
  121. if (mode == TCL_MODE_BLOCKING) {
  122. curStatus &= (~(O_NONBLOCK));
  123. } else {
  124. curStatus |= O_NONBLOCK;
  125. }
  126. /*printf("new curStatus %d\n",curStatus);*/
  127. if (fcntl(esPtr->fdin, F_SETFL, curStatus) < 0) {
  128. return errno;
  129. }
  130. curStatus = fcntl(esPtr->fdin, F_GETFL);
  131. #else /* USE_FIONBIO */
  132. if (mode == TCL_MODE_BLOCKING) {
  133. curStatus = 0;
  134. } else {
  135. curStatus = 1;
  136. }
  137. if (ioctl(esPtr->fdin, (int) FIONBIO, &curStatus) < 0) {
  138. return errno;
  139. }
  140. #endif /* !USE_FIONBIO */
  141. return 0;
  142. }
  143. /*
  144. *----------------------------------------------------------------------
  145. *
  146. * ExpInputProc --
  147. *
  148. * This procedure is invoked from the generic IO level to read
  149. * input from an exp-based channel.
  150. *
  151. * Results:
  152. * The number of bytes read is returned or -1 on error. An output
  153. * argument contains a POSIX error code if an error occurs, or zero.
  154. *
  155. * Side effects:
  156. * Reads input from the input device of the channel.
  157. *
  158. *----------------------------------------------------------------------
  159. */
  160. static int
  161. ExpInputProc(instanceData, buf, toRead, errorCodePtr)
  162. ClientData instanceData; /* Exp state. */
  163. char *buf; /* Where to store data read. */
  164. int toRead; /* How much space is available
  165. * in the buffer? */
  166. int *errorCodePtr; /* Where to store error code. */
  167. {
  168. ExpState *esPtr = (ExpState *) instanceData;
  169. int bytesRead; /* How many bytes were actually
  170. * read from the input device? */
  171. *errorCodePtr = 0;
  172. /*
  173. * Assume there is always enough input available. This will block
  174. * appropriately, and read will unblock as soon as a short read is
  175. * possible, if the channel is in blocking mode. If the channel is
  176. * nonblocking, the read will never block.
  177. */
  178. bytesRead = read(esPtr->fdin, buf, (size_t) toRead);
  179. /*printf("ExpInputProc: read(%d,,) = %d\r\n",esPtr->fdin,bytesRead);*/
  180. if (bytesRead > -1) {
  181. /* strip parity if requested */
  182. if (esPtr->parity == 0) {
  183. char *end = buf+bytesRead;
  184. for (;buf < end;buf++) {
  185. *buf &= 0x7f;
  186. }
  187. }
  188. return bytesRead;
  189. }
  190. *errorCodePtr = errno;
  191. return -1;
  192. }
  193. /*
  194. *----------------------------------------------------------------------
  195. *
  196. * ExpOutputProc--
  197. *
  198. * This procedure is invoked from the generic IO level to write
  199. * output to an exp channel.
  200. *
  201. * Results:
  202. * The number of bytes written is returned or -1 on error. An
  203. * output argument contains a POSIX error code if an error occurred,
  204. * or zero.
  205. *
  206. * Side effects:
  207. * Writes output on the output device of the channel.
  208. *
  209. *----------------------------------------------------------------------
  210. */
  211. static int
  212. ExpOutputProc(instanceData, buf, toWrite, errorCodePtr)
  213. ClientData instanceData; /* Exp state. */
  214. char *buf; /* The data buffer. */
  215. int toWrite; /* How many bytes to write? */
  216. int *errorCodePtr; /* Where to store error code. */
  217. {
  218. ExpState *esPtr = (ExpState *) instanceData;
  219. int written = 0;
  220. *errorCodePtr = 0;
  221. if (toWrite < 0) Tcl_Panic("ExpOutputProc: called with negative char count");
  222. written = write(esPtr->fdout, buf, (size_t) toWrite);
  223. if (written == 0) {
  224. /* This shouldn't happen but I'm told that it does
  225. * nonetheless (at least on SunOS 4.1.3). Since this is
  226. * not a documented return value, the most reasonable
  227. * thing is to complain here and retry in the hopes that
  228. * it is some transient condition. */
  229. sleep(1);
  230. expDiagLogU("write() failed to write anything - will sleep(1) and retry...\n");
  231. *errorCodePtr = EAGAIN;
  232. return -1;
  233. } else if (written < 0) {
  234. *errorCodePtr = errno;
  235. return -1;
  236. }
  237. return written;
  238. }
  239. /*
  240. *----------------------------------------------------------------------
  241. *
  242. * ExpCloseProc --
  243. *
  244. * This procedure is called from the generic IO level to perform
  245. * channel-type-specific cleanup when an exp-based channel is closed.
  246. *
  247. * Results:
  248. * 0 if successful, errno if failed.
  249. *
  250. * Side effects:
  251. * Closes the device of the channel.
  252. *
  253. *----------------------------------------------------------------------
  254. */
  255. /*ARGSUSED*/
  256. static int
  257. ExpCloseProc(instanceData, interp)
  258. ClientData instanceData; /* Exp state. */
  259. Tcl_Interp *interp; /* For error reporting - unused. */
  260. {
  261. ExpState *esPtr = (ExpState *) instanceData;
  262. ExpState **nextPtrPtr;
  263. ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  264. esPtr->registered = FALSE;
  265. #if 0
  266. /*
  267. Really should check that we created one first. Since we're sharing fds
  268. with Tcl, perhaps a filehandler was created with a plain tcl file - we
  269. wouldn't want to delete that. Although if user really close Expect's
  270. user_spawn_id, it probably doesn't matter anyway.
  271. */
  272. Tcl_DeleteFileHandler(esPtr->fdin);
  273. #endif /*0*/
  274. Tcl_DecrRefCount(esPtr->buffer);
  275. /* Actually file descriptor should have been closed earlier. */
  276. /* So do nothing here */
  277. /*
  278. * Conceivably, the process may not yet have been waited for. If this
  279. * becomes a requirement, we'll have to revisit this code. But for now, if
  280. * it's just Tcl exiting, the processes will exit on their own soon
  281. * anyway.
  282. */
  283. for (nextPtrPtr = &(tsdPtr->firstExpPtr); (*nextPtrPtr) != NULL;
  284. nextPtrPtr = &((*nextPtrPtr)->nextPtr)) {
  285. if ((*nextPtrPtr) == esPtr) {
  286. (*nextPtrPtr) = esPtr->nextPtr;
  287. break;
  288. }
  289. }
  290. tsdPtr->channelCount--;
  291. if (esPtr->bg_status == blocked ||
  292. esPtr->bg_status == disarm_req_while_blocked) {
  293. esPtr->freeWhenBgHandlerUnblocked = 1;
  294. /*
  295. * If we're in the middle of a bg event handler, then the event
  296. * handler will have to take care of freeing esPtr.
  297. */
  298. } else {
  299. expStateFree(esPtr);
  300. }
  301. return 0;
  302. }
  303. /*
  304. *----------------------------------------------------------------------
  305. *
  306. * ExpWatchProc --
  307. *
  308. * Initialize the notifier to watch the fd from this channel.
  309. *
  310. * Results:
  311. * None.
  312. *
  313. * Side effects:
  314. * Sets up the notifier so that a future event on the channel will
  315. * be seen by Tcl.
  316. *
  317. *----------------------------------------------------------------------
  318. */
  319. static void
  320. ExpWatchProc(instanceData, mask)
  321. ClientData instanceData; /* The exp state. */
  322. int mask; /* Events of interest; an OR-ed
  323. * combination of TCL_READABLE,
  324. * TCL_WRITABLE and TCL_EXCEPTION. */
  325. {
  326. ExpState *esPtr = (ExpState *) instanceData;
  327. /*
  328. * Make sure we only register for events that are valid on this exp.
  329. * Note that we are passing Tcl_NotifyChannel directly to
  330. * Tcl_CreateExpHandler with the channel pointer as the client data.
  331. */
  332. mask &= esPtr->validMask;
  333. if (mask) {
  334. /*printf(" CreateFileHandler: %d (mask = %d)\r\n",esPtr->fdin,mask);*/
  335. Tcl_CreateFileHandler(esPtr->fdin, mask,
  336. (Tcl_FileProc *) Tcl_NotifyChannel,
  337. (ClientData) esPtr->channel);
  338. } else {
  339. /*printf(" DeleteFileHandler: %d (mask = %d)\r\n",esPtr->fdin,mask);*/
  340. Tcl_DeleteFileHandler(esPtr->fdin);
  341. }
  342. }
  343. /*
  344. *----------------------------------------------------------------------
  345. *
  346. * ExpGetHandleProc --
  347. *
  348. * Called from Tcl_GetChannelHandle to retrieve OS handles from
  349. * an exp-based channel.
  350. *
  351. * Results:
  352. * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if
  353. * there is no handle for the specified direction.
  354. *
  355. * Side effects:
  356. * None.
  357. *
  358. *----------------------------------------------------------------------
  359. */
  360. static int
  361. ExpGetHandleProc(instanceData, direction, handlePtr)
  362. ClientData instanceData; /* The exp state. */
  363. int direction; /* TCL_READABLE or TCL_WRITABLE */
  364. ClientData *handlePtr; /* Where to store the handle. */
  365. {
  366. ExpState *esPtr = (ExpState *) instanceData;
  367. if (direction & TCL_WRITABLE) {
  368. *handlePtr = (ClientData) esPtr->fdin;
  369. }
  370. if (direction & TCL_READABLE) {
  371. *handlePtr = (ClientData) esPtr->fdin;
  372. } else {
  373. return TCL_ERROR;
  374. }
  375. return TCL_OK;
  376. }
  377. int
  378. expChannelCountGet()
  379. {
  380. ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  381. return tsdPtr->channelCount;
  382. }
  383. int
  384. expSizeGet(esPtr)
  385. ExpState *esPtr;
  386. {
  387. int len;
  388. Tcl_GetStringFromObj(esPtr->buffer,&len);
  389. return len;
  390. }
  391. int
  392. expSizeZero(esPtr)
  393. ExpState *esPtr;
  394. {
  395. int len;
  396. Tcl_GetStringFromObj(esPtr->buffer,&len);
  397. return (len == 0);
  398. }
  399. /* return 0 for success or negative for failure */
  400. int
  401. expWriteChars(esPtr,buffer,lenBytes)
  402. ExpState *esPtr;
  403. char *buffer;
  404. int lenBytes;
  405. {
  406. int rc;
  407. retry:
  408. rc = Tcl_WriteChars(esPtr->channel,buffer,lenBytes);
  409. if ((rc == -1) && (errno == EAGAIN)) goto retry;
  410. /* just return 0 rather than positive byte counts */
  411. return ((rc > 0) ? 0 : rc);
  412. }
  413. void
  414. expStateFree(esPtr)
  415. ExpState *esPtr;
  416. {
  417. if (esPtr->fdBusy) {
  418. close(esPtr->fdin);
  419. }
  420. esPtr->valid = FALSE;
  421. if (!esPtr->keepForever) {
  422. ckfree((char *)esPtr);
  423. }
  424. }
  425. /* close all connections
  426. *
  427. * The kernel would actually do this by default, however Tcl is going to come
  428. * along later and try to reap its exec'd processes. If we have inherited any
  429. * via spawn -open, Tcl can hang if we don't close the connections first.
  430. */
  431. void
  432. exp_close_all(interp)
  433. Tcl_Interp *interp;
  434. {
  435. ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  436. ExpState *esPtr;
  437. ExpState *esNextPtr;
  438. /* Save the nextPtr in a local variable before calling 'exp_close'
  439. as 'expStateFree' can be called from it under some
  440. circumstances, possibly causing the memory allocator to smash
  441. the value in 'esPtr'. - Andreas Kupries
  442. */
  443. /* no need to keep things in sync (i.e., tsdPtr, count) since we could only
  444. be doing this if we're exiting. Just close everything down. */
  445. for (esPtr = tsdPtr->firstExpPtr;esPtr;esPtr = esNextPtr) {
  446. esNextPtr = esPtr->nextPtr;
  447. exp_close(interp,esPtr);
  448. }
  449. }
  450. /* wait for any of our own spawned processes we call waitpid rather
  451. * than wait to avoid running into someone else's processes. Yes,
  452. * according to Ousterhout this is the best way to do it.
  453. * returns the ExpState or 0 if nothing to wait on */
  454. ExpState *
  455. expWaitOnAny()
  456. {
  457. ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  458. int result;
  459. ExpState *esPtr;
  460. for (esPtr = tsdPtr->firstExpPtr;esPtr;esPtr = esPtr->nextPtr) {
  461. if (esPtr->pid == exp_getpid) continue; /* skip ourself */
  462. if (esPtr->user_waited) continue; /* one wait only! */
  463. if (esPtr->sys_waited) break;
  464. restart:
  465. result = waitpid(esPtr->pid,&esPtr->wait,WNOHANG);
  466. if (result == esPtr->pid) break;
  467. if (result == 0) continue; /* busy, try next */
  468. if (result == -1) {
  469. if (errno == EINTR) goto restart;
  470. else break;
  471. }
  472. }
  473. return esPtr;
  474. }
  475. ExpState *
  476. expWaitOnOne() {
  477. ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  478. ExpState *esPtr;
  479. int pid;
  480. /* should really be recoded using the common wait code in command.c */
  481. WAIT_STATUS_TYPE status;
  482. pid = wait(&status);
  483. for (esPtr = tsdPtr->firstExpPtr;esPtr;esPtr = esPtr->nextPtr) {
  484. if (esPtr->pid == pid) {
  485. esPtr->sys_waited = TRUE;
  486. esPtr->wait = status;
  487. return esPtr;
  488. }
  489. }
  490. }
  491. void
  492. exp_background_channelhandlers_run_all()
  493. {
  494. ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  495. ExpState *esPtr;
  496. /* kick off any that already have input waiting */
  497. for (esPtr = tsdPtr->firstExpPtr;esPtr;esPtr = esPtr->nextPtr) {
  498. /* is bg_interp the best way to check if armed? */
  499. if (esPtr->bg_interp && !expSizeZero(esPtr)) {
  500. exp_background_channelhandler((ClientData)esPtr,0);
  501. }
  502. }
  503. }
  504. ExpState *
  505. expCreateChannel(interp,fdin,fdout,pid)
  506. Tcl_Interp *interp;
  507. int fdin;
  508. int fdout;
  509. int pid;
  510. {
  511. ExpState *esPtr;
  512. int mask;
  513. Tcl_ChannelType *channelTypePtr;
  514. ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  515. channelTypePtr = &expChannelType;
  516. esPtr = (ExpState *) ckalloc((unsigned) sizeof(ExpState));
  517. esPtr->nextPtr = tsdPtr->firstExpPtr;
  518. tsdPtr->firstExpPtr = esPtr;
  519. sprintf(esPtr->name,"exp%d",fdin);
  520. /*
  521. * For now, stupidly assume this. We we will likely have to revisit this
  522. * later to prevent people from doing stupid things.
  523. */
  524. mask = TCL_READABLE | TCL_WRITABLE;
  525. /* not sure about this - what about adopted channels */
  526. esPtr->validMask = mask | TCL_EXCEPTION;
  527. esPtr->fdin = fdin;
  528. esPtr->fdout = fdout;
  529. /* set close-on-exec for everything but std channels */
  530. /* (system and stty commands need access to std channels) */
  531. if (fdin != 0 && fdin != 2) {
  532. expCloseOnExec(fdin);
  533. if (fdin != fdout) expCloseOnExec(fdout);
  534. }
  535. esPtr->fdBusy = FALSE;
  536. esPtr->channel = Tcl_CreateChannel(channelTypePtr, esPtr->name,
  537. (ClientData) esPtr, mask);
  538. Tcl_RegisterChannel(interp,esPtr->channel);
  539. esPtr->registered = TRUE;
  540. Tcl_SetChannelOption(interp,esPtr->channel,"-buffering","none");
  541. Tcl_SetChannelOption(interp,esPtr->channel,"-blocking","0");
  542. Tcl_SetChannelOption(interp,esPtr->channel,"-translation","lf");
  543. esPtr->pid = pid;
  544. esPtr->msize = 0;
  545. /* initialize a dummy buffer */
  546. esPtr->buffer = Tcl_NewStringObj("",0);
  547. Tcl_IncrRefCount(esPtr->buffer);
  548. esPtr->umsize = exp_default_match_max;
  549. /* this will reallocate object with an appropriate sized buffer */
  550. expAdjust(esPtr);
  551. esPtr->printed = 0;
  552. esPtr->echoed = 0;
  553. esPtr->rm_nulls = exp_default_rm_nulls;
  554. esPtr->parity = exp_default_parity;
  555. esPtr->close_on_eof = exp_default_close_on_eof;
  556. esPtr->key = expect_key++;
  557. esPtr->force_read = FALSE;
  558. esPtr->fg_armed = FALSE;
  559. esPtr->channel_orig = 0;
  560. esPtr->fd_slave = EXP_NOFD;
  561. #ifdef HAVE_PTYTRAP
  562. esPtr->slave_name = 0;
  563. #endif /* HAVE_PTYTRAP */
  564. esPtr->open = TRUE;
  565. esPtr->notified = FALSE;
  566. esPtr->user_waited = FALSE;
  567. esPtr->sys_waited = FALSE;
  568. esPtr->bg_interp = 0;
  569. esPtr->bg_status = unarmed;
  570. esPtr->bg_ecount = 0;
  571. esPtr->freeWhenBgHandlerUnblocked = FALSE;
  572. esPtr->keepForever = FALSE;
  573. esPtr->valid = TRUE;
  574. tsdPtr->channelCount++;
  575. return esPtr;
  576. }
  577. void
  578. expChannelInit() {
  579. ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  580. tsdPtr->channelCount = 0;
  581. }