/* This file is in the public domain. */ #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "oq.h" #include "errf.h" #include "agent.h" #include "panic.h" #include "nested.h" #include "verbose.h" #include "pollloop.h" #include "pkt-util.h" #include "stdio-util.h" #include "agent-client.h" #include "agent-int.h" /* * External interface to the interactive agent code. * * The only thing exported by this file is agent_interactive_setup(). */ /* * Internal variables. * * afd is the fd to the agent, or -1 if there is no connection. * * oq_agent and oq_stdout are queues of output waiting to be written to * the agent and to stdout. * * agent_id is the poll ID for doing I/O on afd. * * responses is a count of outstanding agent requests from which we * expect responses. This is used to tell when we are no longer * expecting anything back from the agent. * * pendp_fd and pendp_id are used only if AF_TIMER sockets are * available; they are the file descriptor and poll ID for the timer * socket used to delay reprompting waiting for more output. * * respbuf/resplen/respalloc/respfill manage the buffer in which we * accumulate agent response data. respbuf is buffer itself. * respalloc is the number of bytes currently mallocked. resplen is * the length of the response (this is not set until we've read the * first 4 bytes of the response); respfill is the amount of data * accumulated so far. * * linebuf/linelen/linealloc accumulate user input. linebuf is the * buffer itself. linelen is its current length; linealloc is the * number of bytes currently mallocked. */ static int afd; static OQ oq_agent; static OQ oq_stdout; static int agent_id; static int responses; static int pendp_fd; static int pendp_id; static char *respbuf; static int resplen; static int respalloc; static int respfill; static char *linebuf; static int linelen; static int linealloc; /* * Write test routine for the agent fd. Return true if we have * anything pending to be written. */ static int wtest_agent(int id __attribute__((__unused__)), void *oqv __attribute__((__unused__))) { return(oq_nonempty(&oq_agent)); } /* * Write test routine for stdout. Return true if we have anything * pending to be written. */ static int wtest_stdout(int id __attribute__((__unused__)), void *oqv __attribute__((__unused__))) { return(oq_nonempty(&oq_stdout)); } /* * Try to write from an OQ to a descriptor. Return value is -1 if an * error occurred when writing or the number of bytes written if all * went well. Special output queue entries should never occur for the * agent; for stdout, there is only one kind of special queue entry, * which specifies that pending input should be retyped a la ^R. */ static int wr_oq(OQ *q, int fd) { int n; NESTED int spec(void *av __attribute__((__unused__)), int ai __attribute__((__unused__))) { struct termios tio; if (fd != 1) panic("special oq on agent connection"); if (tcgetattr(1,&tio) >= 0) { tio.c_lflag |= PENDIN; tcsetattr(1,TCSADRAIN|TCSASOFT,&tio); } return(0); } if (fd < 0) { oq_flush(q); return(0); } n = oq_writev(q,fd,&spec); if (n < 0) return(-1); if (n) oq_dropdata(q,n); return(n); } /* * Try to write to stdout. Just call wr_oq and handle error returns * (by simply dying, since if stdout is gone there's not much to do; * arguably even trying to print a message is pointless, but it won't * hurt anything, and if stdout and stderr go different places, may * actually be useful). */ static void wr_stdout(int id __attribute__((__unused__)), void *cv __attribute__((__unused__))) { if (wr_oq(&oq_stdout,1) < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: stdout write error: %s\n",__progname,strerror(errno)); exit(1); } } /* * Shut down the agent connection. */ static void shut_agent(void) { remove_poll_id(agent_id); close(afd); afd = -1; } /* * Try to write to the agent. Just call wr_oq and handle error * returns; unlike wr_stdout, handling errors here means printing a * message and shutting down the agent connection. */ static void wr_agent(int id __attribute__((__unused__)), void *cv __attribute__((__unused__))) { int n; n = wr_oq(&oq_agent,afd); if (n < 0) { int e; e = errno; verb(IAGENT,"iagent client write: %s\n",strerror(e)); switch (e) { case EINTR: case EWOULDBLOCK: return; break; } oq_queue_point(&oq_stdout,"[Agent write error: ",OQ_STRLEN); oq_queue_point(&oq_stdout,strerror(e),OQ_STRLEN); oq_queue_point(&oq_stdout,"]\n",OQ_STRLEN); shut_agent(); return; } verb(IAGENT,"iagent client wrote %d\n",n); } /* * Print the command prompt. */ static void prompt(void) { oq_queue_point(&oq_stdout,"agent> ",OQ_STRLEN); } /* * Prompt and retype pending input (by queueing a special block on the * stdout output queue). */ static void prompt_and_retype(void) { prompt(); oq_queue_special(&oq_stdout,0,0); } #ifdef AF_TIMER /* * The reprompt timer has expired. Clean up after the timer, then * reprompt-and-retype. */ static void pendp(int id __attribute__((__unused__)), void *cv __attribute__((__unused__))) { close(pendp_fd); remove_poll_id(pendp_id); pendp_fd = -1; pendp_id = PL_NOID; prompt_and_retype(); } #endif /* * A complete agent response has been accumulated. Handle it. */ static void haveresp(void) { /* Rudimentary sanity check. */ if (resplen < 5) panic("resp too short"); /* This test is basically "if we're sitting at the prompt". */ if ((responses < 1) && (pendp_fd < 0)) oq_queue_point(&oq_stdout,"\n",1); /* Print the output. Skip the length and flag byte. */ oq_queue_copy(&oq_stdout,respbuf+5,resplen-5); if (responses < 1) { /* No responses now pending; retype (if no AF_TIMER) or start the retype timer (if AF_TIMER). */ #ifdef AF_TIMER { struct itimerval itv; if (pendp_fd >= 0) close(pendp_fd); if (pendp_id != PL_NOID) remove_poll_id(pendp_id); pendp_fd = socket(AF_TIMER,SOCK_STREAM,0); itv.it_value.tv_sec = 0; itv.it_value.tv_usec = 200000; itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; write(pendp_fd,&itv,sizeof(itv)); pendp_id = add_poll_fd(pendp_fd,&rwtest_always,&rwtest_never,&pendp,0,0); } #else prompt_and_retype(); #endif } else if (respbuf[4] & AGENT_INTER_REPLY) { /* Response pending, and this is (the last part of) an interactive response; decrement the pending count, and, if no more pending, issue the prompt for more input. */ responses --; if (responses == 0) prompt(); } } /* * Read from the agent. */ static void rd_agent(int id __attribute__((__unused__)), void *cv __attribute__((__unused__))) { __label__ bail; char rbuf[512]; int rn; int want; NESTEDFWD void fail(const char *, ...) __attribute__((__format__(__printf__,1,2))); NESTEDDEF void fail(const char *fmt, ...) { va_list ap; FILE *f; shut_agent(); free(respbuf); f = fopen_alloc(&respbuf,&respfill); fprintf(f,"....%c[",0); va_start(ap,fmt); vfprintf(f,fmt,ap); fprintf(f,"]\n"); fclose(f); resplen = respfill; haveresp(); goto bail; } if (0) { bail:; return; } while (1) { /* Figure out how many bytes we want to read. */ if (respfill < 4) { /* Not even the size figure yet. */ want = 4 - respfill; } else { /* We have the size; get the rest. */ want = resplen - respfill; } if (want <= 0) { /* I think this can't happen now; it's a remnant of the days before we had a flag byte, when a response could have nothing after the size bytes. */ haveresp(); respfill = 0; continue; } /* Clip to the available buffer size. */ if (want > sizeof(rbuf)) want = sizeof(rbuf); /* Try the read! */ rn = read(afd,&rbuf[0],want); if (rn <= 0) { /* Error or EOF. */ int e; e = errno; if (rn < 0) { verb(IAGENT,"iagent read %d got %d: %s\n",want,rn,strerror(e)); switch (e) { case EINTR: case EWOULDBLOCK: return; break; } } else { verb(IAGENT,"iagent read %d got 0\n",want); } if (rn < 0) { fail("Agent connection: %s",strerror(e)); } else { fail("Agent connection EOF"); } } /* We got data. Report it if verbose, then save it. */ if (VERB(IAGENT)) { verb(IAGENT,"iagent read %d got %d: ",want,rn); verb_vis(VERBOSE_IAGENT,&rbuf[0],rn); verb(IAGENT,"\n"); } if (respfill+rn > respalloc) { respbuf = realloc(respbuf,respalloc=respfill+rn); } bcopy(&rbuf[0],respbuf+respfill,rn); respfill += rn; if (respfill == 4) { /* We just filled out the message length. */ resplen = get_uint32(respbuf); if (resplen > 65536) { fail("Insane response size %d",resplen); } /* Add in the size and flag byte length. */ resplen += 5; } } } /* * Handle an input character. On newline, send the line off to the * agent and mark us as expecting another response; otherwise, * accumulate characters into linebuf/linelen/linealloc. (The reason * responses is a count rather than a boolean is that we can read and * send off additional lines while waiting for one line's response to * come back.) */ static void input_char(unsigned char ch) { if (ch == '\n') { if (linelen > 0) { char lenbuf[4]; put_uint32(&lenbuf[0],linelen); oq_queue_copy(&oq_agent,&lenbuf[0],4); oq_queue_copy(&oq_agent,linebuf,linelen); responses ++; verb(IAGENT,"iagent client queued 4+%d %.*s\n",linelen,linelen,linebuf); verb(IAGENT,"iagent client responses %d\n",responses); } else { if (responses == 0) prompt(); } linelen = 0; } else { if (linelen >= linealloc) linebuf = realloc(linebuf,linealloc=linelen+8); linebuf[linelen++] = ch; } } /* * Read from stdin. Just call read and shove the resulting characters * through input_char(). */ static void rd_stdin(int id __attribute__((__unused__)), void *cv __attribute__((__unused__))) { unsigned char rbuf[512]; int rn; int i; while (1) { rn = read(0,&rbuf[0],sizeof(rbuf)); if (rn <= 0) { if (rn < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } } exit(0); } verb(IAGENT,"iagent client stdin read %d\n",rn); for (i=0;i