Index | Thread | Search

From:
"Ted Unangst" <tedu@tedunangst.com>
Subject:
Re: responsive watch wip
To:
"Job Snijders" <job@openbsd.org>
Cc:
tech@openbsd.org
Date:
Sat, 21 Jun 2025 00:46:34 -0400

Download raw body.

Thread
  • Ingo Schwarze:

    responsive watch wip

  • > > There seems to be some confusing about empty lines.
    
    yup.
    
    > It seems that in child_input() on the "if (wc == '\n')" path before the
    > 'continue' statement adding something like "(*buf)[l][c] = wc;" helps a bit.
    
    yeah.
    
    > But 'watch traceroute www.ripe.net' still is slightly strange: starting
    > on the second invocation the 'traceroute to' header line appears.
    
    That is because traceroute prints that to stderr. It just kinda shows
    up anywhere, possibly quickly overwrittern. Officially "not a watch bug".
    At least not at this point, obviously could be handled better.
    
    This is the same as before, but preserves the \n in the buffer.
    
    Index: watch.c
    ===================================================================
    RCS file: /home/cvs/src/usr.bin/watch/watch.c,v
    diff -u -p -r1.28 watch.c
    --- watch.c	31 May 2025 08:26:26 -0000	1.28
    +++ watch.c	21 Jun 2025 04:39:41 -0000
    @@ -72,6 +72,7 @@ int start_line = 0, start_column = 0;	/*
     
     int pause_on_error = 0;
     int paused = 0;
    +int want_update;
     int last_exitcode = 0;
     time_t lastupdate;
     int xflag = 0;
    @@ -83,6 +84,15 @@ static char	 *cmdstr;
     static size_t	  cmdlen;
     static char	**cmdv;
     
    +struct child {
    +	char buf[65000];
    +	size_t bufsiz;
    +	size_t pos;
    +	pid_t pid;
    +	int fd;
    +	struct event evin;
    +};
    +
     typedef wchar_t	  BUFFER[MAXLINE][MAXCOLUMN + 1];
     BUFFER		  buf0, buf1;
     BUFFER		 *cur_buf, *prev_buf;
    @@ -93,17 +103,22 @@ struct event	  ev_timer;
     
     #define ctrl(c)		((c) & 037)
     int display(BUFFER *, BUFFER *, highlight_mode_t);
    -void read_result(BUFFER *);
     kbd_result_t kbd_command(int);
     void show_help(void);
     void untabify(wchar_t *, int);
     void on_signal(int, short, void *);
    +void on_sigchild(int, short, void *);
     void timer(int, short, void *);
     void input(int, short, void *);
     void quit(void);
     void usage(void);
     void swap_buffers(void);
     
    +struct child *running_child;
    +void start_child();
    +void child_input(int, short, void *);
    +void child_done(struct child *);
    +
     int
     set_interval(const char *str)
     {
    @@ -124,6 +139,7 @@ int
     main(int argc, char *argv[])
     {
     	struct event ev_sigint, ev_sighup, ev_sigterm, ev_sigwinch, ev_stdin;
    +	struct event ev_sigchild;
     	size_t len, rem;
     	int i, ch;
     	char *p;
    @@ -216,19 +232,19 @@ main(int argc, char *argv[])
     	signal_set(&ev_sighup, SIGHUP, on_signal, NULL);
     	signal_set(&ev_sigterm, SIGTERM, on_signal, NULL);
     	signal_set(&ev_sigwinch, SIGWINCH, on_signal, NULL);
    +	signal_set(&ev_sigchild, SIGCHLD, on_sigchild, NULL);
     	signal_add(&ev_sigint, NULL);
     	signal_add(&ev_sighup, NULL);
     	signal_add(&ev_sigterm, NULL);
     	signal_add(&ev_sigwinch, NULL);
    +	signal_add(&ev_sigchild, NULL);
     
     	event_set(&ev_stdin, STDIN_FILENO, EV_READ | EV_PERSIST, input, NULL);
     	event_add(&ev_stdin, NULL);
     	evtimer_set(&ev_timer, timer, NULL);
    -	evtimer_add(&ev_timer, &opt_interval.tv);
     
     	cur_buf = &buf0; prev_buf = &buf1;
    -	read_result(cur_buf);
    -	display(cur_buf, prev_buf, highlight_mode);
    +	start_child();
     
     	event_dispatch();
     
    @@ -379,21 +395,21 @@ display(BUFFER * cur, BUFFER * prev, hig
     }
     
     void
    -read_result(BUFFER *buf)
    +start_child()
     {
    -	FILE	*fp;
    -	int	 i, st, fds[2];
    -	pid_t	 pipe_pid, pid;
    +	struct child *child;
    +	int fds[2];
     
    -	/* Clear buffer */
    -	memset(buf, 0, sizeof(*buf));
    +	child = calloc(1, sizeof(*child));
    +	child->bufsiz = sizeof(child->buf);
     
     	if (pipe(fds) == -1)
     		err(1, "pipe()");
     
    -	if ((pipe_pid = vfork()) == -1)
    -		err(1, "vfork()");
    -	else if (pipe_pid == 0) {
    +	child->pid = vfork();
    +	if (child->pid == -1)
    +		err(1, "vfork");
    +	if (child->pid == 0) {
     		close(fds[0]);
     		if (fds[1] != STDOUT_FILENO) {
     			dup2(fds[1], STDOUT_FILENO);
    @@ -407,21 +423,56 @@ read_result(BUFFER *buf)
     		/* use warn(3) + _exit(2) not to call exit(3) */
     		warn("exec(%s)", cmdstr);
     		_exit(1);
    -
     		/* NOTREACHED */
     	}
    -	if ((fp = fdopen(fds[0], "r")) == NULL)
    -		err(1, "fdopen()");
     	close(fds[1]);
    +	child->fd = fds[0];
    +
    +	event_set(&child->evin, child->fd, EV_READ | EV_PERSIST, child_input, child);
    +	event_add(&child->evin, NULL);
    +
    +	running_child = child;
    +}
    +
    +void
    +update()
    +{
    +	if (running_child) {
    +		/* not yet */
    +		want_update = 1;
    +		return;
    +	}
    +	want_update = 0;
    +	swap_buffers();
    +	start_child();
    +}
    +
    +void
    +child_done(struct child *child)
    +{
    +	event_del(&child->evin);
    +	close(child->fd);
    +	free(child);
    +	// assert(running_child == child);
    +	if (running_child == child)
    +		running_child = NULL;
    +	if (want_update)
    +		update();
    +	else if (!paused)
    +		evtimer_add(&ev_timer, &opt_interval.tv);
    +}
    +
    +void
    +on_sigchild(int sig, short event, void *arg)
    +{
    +	pid_t pid;
    +	int st;
     
    -	/* Read command output and convert tab to spaces * */
    -	for (i = 0; i < MAXLINE && fgetws((*buf)[i], MAXCOLUMN, fp) != NULL;
    -	    i++)
    -		untabify((*buf)[i], sizeof((*buf)[i]));
    -	fclose(fp);
     	do {
    -		pid = waitpid(pipe_pid, &st, 0);
    +		pid = waitpid(WAIT_ANY, &st, 0);
     	} while (pid == -1 && errno == EINTR);
    +	if (!running_child || running_child->pid != pid)
    +		return;
     
     	/* Remember update time */
     	time(&lastupdate);
    @@ -430,6 +481,47 @@ read_result(BUFFER *buf)
     		last_exitcode = WEXITSTATUS(st);
     	if (pause_on_error && last_exitcode)
     		paused = 1;
    +
    +	child_done(running_child);
    +}
    +
    +void
    +child_input(int sig, short event, void *arg)
    +{
    +	struct child *child = arg;
    +	ssize_t n;
    +
    +	n = read(child->fd, child->buf + child->pos, child->bufsiz - child->pos);
    +	if (n == -1)
    +		return;
    +	child->pos += n;
    +
    +	size_t l = 0, c = 0;
    +	BUFFER *buf = cur_buf;
    +	memset(*buf, 0, sizeof(*buf));
    +	for (size_t i = 0; i < child->pos;/* i += len */) {
    +		wchar_t wc;
    +		int len = mbtowc(&wc, &child->buf[i], MB_CUR_MAX);
    +		if (len == -1) {
    +			wc = '?';
    +			i += 1;
    +		} else {
    +			i += len;
    +		}
    +		if (wc == '\n') {
    +			if (c < MAXCOLUMN)
    +				(*buf)[l][c] = wc;
    +			l++;
    +			c = 0;
    +			if (l == MAXLINE)
    +				break;
    +			continue;
    +		}
    +		if (c == MAXCOLUMN)
    +			continue;
    +		(*buf)[l][c++] = wc;
    +	}
    +	display(buf, prev_buf, highlight_mode);
     }
     
     kbd_result_t
    @@ -534,12 +626,13 @@ kbd_command(int ch)
     	case 'p':
     		if (paused == 1) {
     			paused = 0;
    -			evtimer_del(&ev_timer);
    -			return (RSLT_REDRAW);
    -		} else {
    -			paused = 1;
     			evtimer_add(&ev_timer, &opt_interval.tv);
     			return (RSLT_UPDATE);
    +		} else {
    +			paused = 1;
    +			want_update = 0;
    +			evtimer_del(&ev_timer);
    +			return (RSLT_REDRAW);
     		}
     
     	case 's':
    @@ -687,11 +780,7 @@ on_signal(int sig, short event, void *ar
     void
     timer(int sig, short event, void *arg)
     {
    -	swap_buffers();
    -	read_result(cur_buf);
    -	display(cur_buf, prev_buf, highlight_mode);
    -	if (!paused)
    -		evtimer_add(&ev_timer, &opt_interval.tv);
    +	update();
     }
     
     void
    @@ -704,11 +793,7 @@ input(int sig, short event, void *arg)
     	kbd_result_t result = kbd_command(ch);
     	switch (result) {
     	case RSLT_UPDATE:	/* update buffer */
    -		swap_buffers();
    -		read_result(cur_buf);
    -		display(cur_buf, prev_buf, highlight_mode);
    -		if (!paused)
    -			evtimer_add(&ev_timer, &opt_interval.tv);
    +		update();
     		break;
     	case RSLT_REDRAW:	/* scroll with current buffer */
     		display(cur_buf, prev_buf, highlight_mode);
    
    
  • Ingo Schwarze:

    responsive watch wip