diff --git a/configure.ac b/configure.ac index 76466ae..7249623 100644 --- a/configure.ac +++ b/configure.ac @@ -29,6 +29,7 @@ AC_CHECK_FUNCS([vsnprintf strlcat strtoul memrchr]) AC_CHECK_FUNCS([fdatasync]) AC_CHECK_FUNCS([fpathconf sysconf posix_memalign posix_fadvise]) AC_CHECK_FUNCS([nanosleep]) +AC_CHECK_FUNCS([setitimer]) AC_CHECK_HEADERS([getopt.h]) AC_CHECK_HEADERS([limits.h]) AC_CHECK_HEADERS([wctype.h]) diff --git a/docs/NEWS.md b/docs/NEWS.md index d7f60d5..8945e10 100644 --- a/docs/NEWS.md +++ b/docs/NEWS.md @@ -1,6 +1,7 @@ ### NOT YET RELEASED * feature: new "`--si`" option to display and interpret size suffixes in multiples of 1000 rather than 1024 (pull request [#85](https://codeberg.org/a-j-wood/pv/pulls/85)) supplied by [kevinruddy](https://codeberg.org/kevinruddy) + * fix: continue producing progress output when the output is blocking writes ([#34](https://codeberg.org/a-j-wood/pv/issues/34), [#86](https://codeberg.org/a-j-wood/pv/issues/86), [#87](https://codeberg.org/a-j-wood/pv/issues/87)) * fix: honour the _TMPDIR_ / _TMP_ environment variables again, rather than hard-coding "`/tmp`", when using a terminal lock file (originally removed in 1.8.0) ([#88](https://codeberg.org/a-j-wood/pv/issues/88)) * i18n: corrections and missing strings added to French translations (pull request [#83](https://codeberg.org/a-j-wood/pv/pulls/83)) supplied by [Thomas Bertels](https://codeberg.org/tbertels) diff --git a/docs/TODO.md b/docs/TODO.md index 7f65574..9c5d706 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -11,7 +11,6 @@ Things still to do. (#n) indicates the issue tracker number. * ([#17](https://codeberg.org/a-j-wood/pv/issues/17)) Allow "`-r`" with "`-l`" and "`-n`" to output lines/sec (Roland Kletzing) * ([#22](https://codeberg.org/a-j-wood/pv/issues/22)) Options to skip input and seek on output (Jason A. Pfeil, Feb 2022) * ([#25](https://codeberg.org/a-j-wood/pv/issues/25)) Normalise progress to 100% on overrun (Andrej Gantvorg) - * ([#34](https://codeberg.org/a-j-wood/pv/issues/34)) Continue timer even if input or output is blocking (Martin Probst - Jun 2017) * ([#35](https://codeberg.org/a-j-wood/pv/issues/35)) Allow decimal values for "`-s`", "`-L`", "`-B`" (Thomas Watson - Aug 2020) * ([#38](https://codeberg.org/a-j-wood/pv/issues/38)) Reset ETA on *SIGUSR1* (Jacek Wielemborek - Jan 2019) * ([#40](https://codeberg.org/a-j-wood/pv/issues/40)) Permit "`-c`" with "`-d PID:FD`", reject "`-N`" with "`-d PID`" (Norman Rasmussen - Nov 2020) diff --git a/src/include/config.h.in b/src/include/config.h.in index f55b9fb..4d0980c 100644 --- a/src/include/config.h.in +++ b/src/include/config.h.in @@ -100,6 +100,9 @@ /* Define to 1 if you have the `select' function. */ #undef HAVE_SELECT +/* Define to 1 if you have the `setitimer' function. */ +#undef HAVE_SETITIMER + /* Define to 1 if you have the `setlocale' function. */ #undef HAVE_SETLOCALE diff --git a/src/include/pv-internal.h b/src/include/pv-internal.h index e9778d5..b769a72 100644 --- a/src/include/pv-internal.h +++ b/src/include/pv-internal.h @@ -157,6 +157,7 @@ struct pvstate_s { #ifdef SA_SIGINFO struct sigaction old_sigusr2; #endif + struct sigaction old_sigalrm; } signal; /******************* diff --git a/src/pv/signal.c b/src/pv/signal.c index 8d79a8e..b519833 100644 --- a/src/pv/signal.c +++ b/src/pv/signal.c @@ -244,6 +244,22 @@ bool pv_sigusr2_received(pvstate_t state, pid_t * pid) #endif +/* + * Handle alarm signals by doing nothing. + * + * Note that we have to use a signal handler like this, instead of using + * SIG_IGN, because if we ignore the signal entirely, it does nothing, + * including not interrupting blocking write() calls - which is what we're + * using alarm signals for in the first place. + */ +static void pv_sig_alrm( /*@unused@ */ __attribute__((unused)) + int s) +{ + debug("%s", "SIGALRM received"); + /* Do nothing. */ +} + + /* * Initialise signal handling. */ @@ -360,6 +376,15 @@ void pv_sig_init(pvstate_t state) * while backgrounded (see the SIGTTOU handler above). */ pv_sig_ensure_tty_tostop(); + + /* + * Handle SIGALRM by doing nothing, so we can use alarms or interval + * timers to interrupt blocking writes (returning EINTR). + */ + sa.sa_handler = pv_sig_alrm; + (void) sigemptyset(&(sa.sa_mask)); + sa.sa_flags = 0; + (void) sigaction(SIGALRM, &sa, &(pv_sig_state->signal.old_sigalrm)); } @@ -389,6 +414,7 @@ void pv_sig_fini( /*@unused@ */ __attribute__((unused)) pvstate_t state) #ifdef SA_SIGINFO (void) sigaction(SIGUSR2, &(pv_sig_state->signal.old_sigusr2), NULL); #endif + (void) sigaction(SIGALRM, &(pv_sig_state->signal.old_sigalrm), NULL); need_to_clear_tostop = pv_sig_state->signal.pv_tty_tostop_added; diff --git a/src/pv/transfer.c b/src/pv/transfer.c index 2dfc9d5..ef8a07e 100644 --- a/src/pv/transfer.c +++ b/src/pv/transfer.c @@ -231,8 +231,8 @@ static ssize_t pv__transfer_write_repeated(int fd, void *buf, size_t count, bool if ((EINTR == errno) || (EAGAIN == errno)) { /* * Interrupted by a signal - probably our - * alarm() - so just return what we've - * written so far. + * alarm or interval timer - so just return + * what we've written so far. */ return total_written; } else { @@ -267,8 +267,9 @@ static ssize_t pv__transfer_write_repeated(int fd, void *buf, size_t count, bool /* * Running the select() here seems to make PV eat a lot of * CPU in some cases, so instead we just go round the loop - * again and rely on our alarm() to interrupt us if we run - * out of time - also on our elapsed time check. + * again and rely on our alarm or interval timer to + * interrupt us if we run out of time - also on our elapsed + * time check. */ if (count > 0) { debug("%s %d: %s (%ld %s, %ld %s)", "fd", fd, @@ -643,17 +644,60 @@ static int pv__transfer_write(pvstate_t state, bool *eof_in, bool *eof_out, long if (state->control.discard_input) { nwritten = state->transfer.to_write; } else if (state->transfer.to_write > 0) { - if (signal(SIGALRM, SIG_IGN) == SIG_ERR) { - pv_error(state, "%s: %s", _("failed to set alarm signal handler"), strerror(errno)); - } else { - (void) alarm(1); + + /* + * Set an interval timer or an alarm to interrupt the write + * with a signal if the write takes too long, so we can + * continue producing progress information. + */ +#if HAVE_SETITIMER + struct itimerval new_timer; + + /*@-unrecog@ */ + /* splint doesn't know setitimer or ITIMER_REAL */ + memset(&new_timer, 0, sizeof(new_timer)); + new_timer.it_value.tv_sec = (time_t) (state->control.interval); + new_timer.it_value.tv_usec = (suseconds_t) (((long) (state->control.interval * 1000000.0)) % 1000000); + + /* + * We have to set the interval so that the timer continues + * to repeat while writes are attempted, especially as it's + * possible that the initial timer run will expire + * immediately if the period is less than 1 second. + */ + + new_timer.it_interval.tv_sec = new_timer.it_value.tv_sec; + new_timer.it_interval.tv_usec = new_timer.it_value.tv_usec; + + debug("%s: [%lds,%ldus]", "setting interval timer", (long) (new_timer.it_value.tv_sec), + (long) (new_timer.it_value.tv_usec)); + + if (0 != setitimer(ITIMER_REAL, &new_timer, NULL)) { + pv_error(state, "%s: %s", _("failed to set interval timer"), strerror(errno)); } + +#else /* ! HAVE_SETITIMER */ + (void) alarm(1); +#endif /* HAVE_SETITIMER */ nwritten = pv__transfer_write_repeated(STDOUT_FILENO, state->transfer.transfer_buffer + state->transfer.write_position, (size_t) (state->transfer.to_write), state->control.sync_after_write); +#if HAVE_SETITIMER + memset(&new_timer, 0, sizeof(new_timer)); + new_timer.it_interval.tv_sec = 0; + new_timer.it_interval.tv_usec = 0; + new_timer.it_value.tv_sec = 0; + new_timer.it_value.tv_usec = 0; + if (0 != setitimer(ITIMER_REAL, &new_timer, NULL)) { + pv_error(state, "%s: %s", _("failed to clear interval timer"), strerror(errno)); + } + + /*@+unrecog@ */ +#else /* ! HAVE_SETITIMER */ (void) alarm(0); +#endif /* HAVE_SETITIMER */ } if (0 == nwritten) {