#include #include #include #include #include #include #include #include #include #include "pline.h" #include "screen.h" #include "less.h" struct less { int fd; pid_t kid; } ; /* * Internal helper: use poll(2) to wait for l->fd to be writable. */ static void wait_write(LESS *l) { struct pollfd pfd; while (1) { pfd.fd = l->fd; pfd.events = POLLOUT | POLLWRNORM; if (poll(&pfd,1,INFTIM) < 0) { if (errno == EINTR) continue; return; } if (pfd.revents & (POLLOUT | POLLWRNORM | POLLERR | POLLHUP | POLLNVAL)) return; } } /* * Start a less instance. * * We use a socketpair for data, rather than a pipe, so we can use * send(2)'s MSG_NOSIGNAL flag in less_send() to avoid fiddling with * SIGPIPE. */ LESS *less_start(void) { LESS *l; pid_t kid; int dp[2]; int xp[2]; int e; int n; if (socketpair(AF_LOCAL,SOCK_STREAM,0,&dp[0]) < 0) { pline("data socketpair(): %s",strerror(errno)); return(0); } do { if (socketpair(AF_LOCAL,SOCK_STREAM,0,&xp[0]) < 0) { pline("exec socketpair(): %s",strerror(errno)); break; } fcntl(xp[0],F_SETFD,1); cleanupscreen(); do <"err"> { fflush(0); kid = fork(); if (kid < 0) { pline("fork(): %s",strerror(errno)); break; } if (kid == 0) { if (dp[0] != 0) { dup2(dp[0],0); close(dp[0]); } close(dp[1]); close(xp[1]); execlp("less","less",(const char *)0); e = errno; write(xp[0],&e,sizeof(int)); _exit(0); } close(xp[0]); xp[0] = -1; do { n = recv(xp[1],&e,sizeof(e),MSG_WAITALL); if (n < 0) { pline("exec recv error: %s",strerror(errno)); break; } if (n == sizeof(e)) { pline("can't exec less: %s",strerror(e)); break; } if (n != 0) { pline("exec protocol error: got %d, not %d",n,(int)sizeof(e)); break; } close(xp[1]); xp[1] = -1; close(dp[0]); dp[0] = -1; fcntl(dp[1],F_SETFL,fcntl(dp[1],F_GETFL,0)|O_NONBLOCK); l = malloc(sizeof(LESS)); l->fd = dp[1]; l->kid = kid; return(l); } while (0); kill(kid,SIGKILL); wait4(kid,0,0,0); } while (0); if (xp[0] >= 0) close(xp[0]); if (xp[1] >= 0) close(xp[1]); initscreen(); } while (0); if (dp[0] >= 0) close(dp[0]); if (dp[1] >= 0) close(dp[1]); return(0); } /* * Send text to a LESS *. */ int less_send(LESS *l, const char *text, int len, unsigned int flags) { int off; int wrote; if (len < 0) len = strlen(text); off = 0; while (off < len) { wrote = send(l->fd,text+off,len-off,MSG_NOSIGNAL); if (wrote < 0) { switch (errno) { case EINTR: continue; break; case EWOULDBLOCK: if (! (flags & LESS_BLOCKING)) return(off); wait_write(l); break; default: return(-1); break; } } if (wrote == 0) { if (! (flags & LESS_BLOCKING)) return(off); wait_write(l); continue; } off += wrote; } return(off); } /* * Finish a less run gracefully. */ void less_done(LESS *l) { pid_t dead; int status; int e; close(l->fd); l->fd = -1; while (1) { dead = wait4(l->kid,&status,0,0); if (dead < 0) { if (errno == EINTR) continue; pline("wait4: %s",strerror(errno)); break; } if (dead != l->kid) continue; // can this happen? l->kid = -1; if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) { pline("less: exit %d",WEXITSTATUS(status)); } } else if (WIFSIGNALED(status)) { e = WTERMSIG(status); if ((e < 1) || (e >= NSIG)) { pline("less: signal %d%s",e,WCOREDUMP(status)?" (core dumped)":""); } else { pline("less: signal %d [%s]%s",e,sys_signame[e],WCOREDUMP(status)?" (core dumped)":""); } } else { pline("less: incomprehensible status %d",status); } break; } if (l->kid > 1) { kill(l->kid,SIGKILL); wait4(l->kid,0,0,0); } initscreen(); free(l); } /* * Abort a less run ungracefully. */ void less_abort(LESS *l) { if (l->fd >= 0) { close(l->fd); l->fd = -1; } if (l->kid > 1) { kill(l->kid,SIGKILL); wait4(l->kid,0,0,0); } initscreen(); free(l); }