Files
pv/src/pv/loop.c
T

776 lines
20 KiB
C

/*
* Functions providing the main transfer or file descriptor watching loop.
*
* Copyright 2002-2008, 2010, 2012-2015, 2017, 2021, 2023 Andrew Wood
*
* Distributed under the Artistic License v2.0; see `docs/COPYING'.
*/
#include "config.h"
#include "pv.h"
#include "pv-internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define _GNU_SOURCE 1
#include <limits.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
/*
* Pipe data from a list of files to standard output, giving information
* about the transfer on standard error according to the given options.
*
* Returns nonzero on error.
*/
int pv_main_loop(pvstate_t state)
{
long lineswritten;
off_t written, total_written, transferred_since_last, cansend;
long double target;
bool eof_in, eof_out, final_update;
struct timespec start_time, next_update, next_ratecheck, cur_time;
struct timespec init_time, next_remotecheck, transfer_elapsed;
long double elapsed_seconds;
struct stat sb;
int fd;
unsigned int file_idx;
/*
* "written" is ALWAYS bytes written by the last transfer.
*
* "lineswritten" is the lines written by the last transfer,
* but is only updated in line mode.
*
* "total_written" is the total bytes written since the start,
* or in line mode, the total lines written since the start.
*
* "transferred_since_last" is the bytes written since the last
* display, or in line mode, the lines written since the last
* display.
*
* The remaining variables are all unchanged by linemode.
*/
fd = -1;
pv_crs_init(state);
eof_in = false;
eof_out = false;
total_written = 0;
lineswritten = 0;
transferred_since_last = 0;
state->initial_offset = 0;
memset(&cur_time, 0, sizeof(cur_time));
memset(&start_time, 0, sizeof(start_time));
pv_elapsedtime_read(&cur_time);
pv_elapsedtime_copy(&start_time, &cur_time);
memset(&next_ratecheck, 0, sizeof(next_ratecheck));
memset(&next_remotecheck, 0, sizeof(next_remotecheck));
memset(&next_update, 0, sizeof(next_update));
pv_elapsedtime_copy(&next_ratecheck, &cur_time);
pv_elapsedtime_copy(&next_remotecheck, &cur_time);
pv_elapsedtime_copy(&next_update, &cur_time);
if ((state->delay_start > 0)
&& (state->delay_start > state->interval)) {
pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->delay_start));
} else {
pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->interval));
}
target = 0;
final_update = false;
file_idx = 0;
/*
* Open the first readable input file.
*/
fd = -1;
while (fd < 0 && file_idx < state->input_file_count) {
fd = pv_next_file(state, file_idx, -1);
if (fd < 0)
file_idx++;
}
/*
* Exit early if there was no readable input file.
*/
if (fd < 0) {
if (state->cursor)
pv_crs_fini(state);
return state->exit_status;
}
#if HAVE_POSIX_FADVISE
/* Advise the OS that we will only be reading sequentially. */
(void) posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
#endif
#ifdef O_DIRECT
/*
* Set or clear O_DIRECT on the output.
*/
fcntl(STDOUT_FILENO, F_SETFL, (state->direct_io ? O_DIRECT : 0) | fcntl(STDOUT_FILENO, F_GETFL));
state->direct_io_changed = false;
#endif /* O_DIRECT */
#if HAVE_STRUCT_STAT_ST_BLKSIZE
/*
* Set target buffer size if the initial file's block size can be
* read and we weren't given a target buffer size.
*/
if ((0 == fstat(fd, &sb)) && (0 == state->target_buffer_size)) {
size_t sz;
sz = (size_t) (sb.st_blksize * 32);
if (sz > BUFFER_SIZE_MAX)
sz = BUFFER_SIZE_MAX;
state->target_buffer_size = sz;
}
#endif
if (0 == state->target_buffer_size)
state->target_buffer_size = BUFFER_SIZE;
while ((!(eof_in && eof_out)) || (!final_update)) {
cansend = 0;
/*
* Check for remote messages from -R every short while.
*/
if (pv_elapsedtime_compare(&cur_time, &next_remotecheck) > 0) {
pv_remote_check(state);
pv_elapsedtime_add_nsec(&next_remotecheck, REMOTE_INTERVAL);
}
if (1 == state->pv_sig_abort)
break;
if (state->rate_limit > 0) {
pv_elapsedtime_read(&cur_time);
if (pv_elapsedtime_compare(&cur_time, &next_ratecheck) > 0) {
target +=
((long double) (state->rate_limit)) / (long double) (1000000000.0 /
(long
double) (RATE_GRANULARITY));
long double burst_max = ((long double) (state->rate_limit * RATE_BURST_WINDOW));
if (target > burst_max) {
target = burst_max;
}
pv_elapsedtime_add_nsec(&next_ratecheck, RATE_GRANULARITY);
}
cansend = (off_t) target;
}
/*
* If we have to stop at "size" bytes, make sure we don't
* try to write more than we're allowed to.
*/
if ((0 < state->size) && (state->stop_at_size)) {
if ((state->size < (total_written + cansend))
|| ((0 == cansend)
&& (0 == state->rate_limit))) {
cansend = state->size - total_written;
if (0 >= cansend) {
eof_in = true;
eof_out = true;
}
}
}
if ((0 < state->size) && (state->stop_at_size)
&& (0 >= cansend) && eof_in && eof_out) {
written = 0;
} else {
written = pv_transfer(state, fd, &eof_in, &eof_out, cansend, &lineswritten);
}
if (written < 0) {
if (state->cursor)
pv_crs_fini(state);
return state->exit_status;
}
if (state->linemode) {
transferred_since_last += lineswritten;
total_written += lineswritten;
if (state->rate_limit > 0)
target -= lineswritten;
} else {
transferred_since_last += written;
total_written += written;
if (state->rate_limit > 0)
target -= written;
}
/*
* EOF, and files remain - advance to the next file.
*/
while (eof_in && eof_out && file_idx < (state->input_file_count - 1)) {
file_idx++;
fd = pv_next_file(state, file_idx, fd);
if (fd >= 0) {
eof_in = false;
eof_out = false;
}
}
/* Now check the current time. */
pv_elapsedtime_read(&cur_time);
/* If full EOF, final update, and force a display updaate. */
if (eof_in && eof_out) {
final_update = true;
if ((state->display_visible)
|| (state->delay_start < 0.001)) {
pv_elapsedtime_copy(&next_update, &cur_time);
}
}
/* Just go round the loop again if there's no display. */
if (state->no_display)
continue;
/*
* If -W was given, we don't output anything until we have
* written a byte (or line, in line mode), at which point
* we then count time as if we started when the first byte
* was received.
*/
if (state->wait) {
/* Restart the loop if nothing written yet. */
if (state->linemode) {
if (lineswritten < 1)
continue;
} else {
if (written < 1)
continue;
}
state->wait = 0;
/*
* Reset the timer offset counter now that data
* transfer has begun, otherwise if we had been
* stopped and started (with ^Z / SIGTSTOP)
* previously (while waiting for data), the timers
* will be wrongly offset.
*
* While we reset the offset counter we must disable
* SIGTSTOP so things don't mess up.
*/
pv_sig_nopause();
pv_elapsedtime_read(&start_time);
pv_elapsedtime_zero(&(state->pv_sig_toffset));
pv_sig_allowpause();
/*
* Start the display, but only at the next interval,
* not immediately.
*/
pv_elapsedtime_copy(&next_update, &start_time);
pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->interval));
}
/* Restart the loop if it's not time to update the display. */
if (pv_elapsedtime_compare(&cur_time, &next_update) < 0) {
continue;
}
pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->interval));
/* Set the "next update" time to now, if it's in the past. */
if (pv_elapsedtime_compare(&next_update, &cur_time) < 0)
pv_elapsedtime_copy(&next_update, &cur_time);
/*
* Calculate the effective start time: the time we actually
* started, plus the total time we spent stopped.
*/
memset(&init_time, 0, sizeof(init_time));
pv_elapsedtime_add(&init_time, &start_time, &(state->pv_sig_toffset));
/*
* Now get the effective elapsed transfer time - current
* time minus effective start time.
*/
memset(&transfer_elapsed, 0, sizeof(transfer_elapsed));
pv_elapsedtime_subtract(&transfer_elapsed, &cur_time, &init_time);
elapsed_seconds = pv_elapsedtime_seconds(&transfer_elapsed);
if (final_update)
transferred_since_last = -1;
/* Resize the display, if a resize signal was received. */
if (1 == state->pv_sig_newsize) {
unsigned int new_width, new_height;
state->pv_sig_newsize = 0;
new_width = state->width;
new_height = state->height;
pv_screensize(&new_width, &new_height);
if (!state->width_set_manually)
state->width = new_width;
if (!state->height_set_manually)
state->height = new_height;
}
pv_display(state, elapsed_seconds, transferred_since_last, total_written);
transferred_since_last = 0;
}
if (state->cursor) {
pv_crs_fini(state);
} else {
if ((!state->numeric) && (!state->no_display)
&& (state->display_visible))
pv_write_retry(STDERR_FILENO, "\n", 1);
}
if (1 == state->pv_sig_abort)
state->exit_status |= 32;
if (fd >= 0)
close(fd);
return state->exit_status;
}
/*
* Watch the progress of file descriptor state->watch_fd in process
* state->watch_pid and show details about the transfer on standard error
* according to the given options.
*
* Returns nonzero on error.
*/
int pv_watchfd_loop(pvstate_t state)
{
struct pvwatchfd_s info;
long long position_now, total_written, transferred_since_last;
struct timespec next_update, cur_time;
struct timespec init_time, next_remotecheck, transfer_elapsed;
long double elapsed_seconds;
int ended;
int first_check;
int rc;
memset(&info, 0, sizeof(info));
info.watch_pid = state->watch_pid;
info.watch_fd = state->watch_fd;
rc = pv_watchfd_info(state, &info, 0);
if (0 != rc) {
state->exit_status |= 2;
return state->exit_status;
}
/*
* Use a size if one was passed, otherwise use the total size
* calculated.
*/
if (0 >= state->size)
state->size = info.size;
if (state->size < 1) {
char *fmt;
while (NULL != (fmt = strstr(state->default_format, "%e"))) {
debug("%s", "zero size - removing ETA");
/* strlen-1 here to include trailing NUL */
memmove(fmt, fmt + 2, strlen(fmt) - 1);
state->reparse_display = 1;
}
}
memset(&cur_time, 0, sizeof(cur_time));
memset(&next_remotecheck, 0, sizeof(next_remotecheck));
memset(&next_update, 0, sizeof(next_update));
pv_elapsedtime_read(&cur_time);
pv_elapsedtime_copy(&(info.start_time), &cur_time);
pv_elapsedtime_copy(&next_remotecheck, &cur_time);
pv_elapsedtime_copy(&next_update, &cur_time);
pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->interval));
ended = 0;
total_written = 0;
transferred_since_last = 0;
first_check = 1;
while (!ended) {
/*
* Check for remote messages from -R every short while.
*/
if (pv_elapsedtime_compare(&cur_time, &next_remotecheck) > 0) {
pv_remote_check(state);
pv_elapsedtime_add_nsec(&next_remotecheck, REMOTE_INTERVAL);
}
if (1 == state->pv_sig_abort)
break;
position_now = pv_watchfd_position(&info);
if (position_now < 0) {
ended = 1;
} else {
transferred_since_last += position_now - total_written;
total_written = position_now;
if (first_check) {
state->initial_offset = position_now;
first_check = 0;
}
}
pv_elapsedtime_read(&cur_time);
/* Ended - force a display update. */
if (ended) {
ended = 1;
pv_elapsedtime_copy(&next_update, &cur_time);
}
/*
* Restart the loop after a brief delay, if it's not time to
* update the display.
*/
if (pv_elapsedtime_compare(&cur_time, &next_update) < 0) {
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
select(0, NULL, NULL, NULL, &tv);
continue;
}
pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->interval));
/* Set the "next update" time to now, if it's in the past. */
if (pv_elapsedtime_compare(&next_update, &cur_time) < 0)
pv_elapsedtime_copy(&next_update, &cur_time);
/*
* Calculate the effective start time: the time we actually
* started, plus the total time we spent stopped.
*/
pv_elapsedtime_add(&init_time, &(info.start_time), &(state->pv_sig_toffset));
/*
* Now get the effective elapsed transfer time - current
* time minus effective start time.
*/
pv_elapsedtime_subtract(&transfer_elapsed, &cur_time, &init_time);
elapsed_seconds = pv_elapsedtime_seconds(&transfer_elapsed);
if (ended)
transferred_since_last = -1;
/* Resize the display, if a resize signal was received. */
if (1 == state->pv_sig_newsize) {
unsigned int new_width, new_height;
state->pv_sig_newsize = 0;
new_width = state->width;
new_height = state->height;
pv_screensize(&new_width, &new_height);
if (!state->width_set_manually)
state->width = new_width;
if (!state->height_set_manually)
state->height = new_height;
}
pv_display(state, elapsed_seconds, transferred_since_last, total_written);
transferred_since_last = 0;
}
if (!state->numeric)
pv_write_retry(STDERR_FILENO, "\n", 1);
if (1 == state->pv_sig_abort)
state->exit_status |= 32;
return state->exit_status;
}
/*
* Watch the progress of all file descriptors in process state->watch_pid
* and show details about the transfers on standard error according to the
* given options.
*
* Returns nonzero on error.
*/
int pv_watchpid_loop(pvstate_t state)
{
struct pvstate_s state_copy;
const char *original_format_string;
char new_format_string[512] = { 0, };
struct pvwatchfd_s *info_array = NULL;
struct pvstate_s *state_array = NULL;
int array_length = 0;
int fd_to_idx[FD_SETSIZE] = { 0, };
struct timespec next_update, cur_time;
int idx;
int prev_displayed_lines, blank_lines;
bool first_pass = true;
/*
* Make sure the process exists first, so we can give an error if
* it's not there at the start.
*/
if (kill(state->watch_pid, 0) != 0) {
pv_error(state, "%s %u: %s", _("pid"), state->watch_pid, strerror(errno));
state->exit_status |= 2;
return 2;
}
/*
* Make a copy of our state, ready to change in preparation for
* duplication.
*/
memcpy(&state_copy, state, sizeof(state_copy));
/*
* Make sure there's a format string, and then insert %N into it if
* it's not present.
*/
original_format_string = state->format_string ? state->format_string : state->default_format;
if (NULL == strstr(original_format_string, "%N")) {
(void) pv_snprintf(new_format_string, sizeof(new_format_string), "%%N %s", original_format_string);
} else {
(void) pv_snprintf(new_format_string, sizeof(new_format_string), "%s", original_format_string);
}
new_format_string[sizeof(new_format_string) - 1] = '\0';
state_copy.format_string = NULL;
(void) pv_snprintf(state_copy.default_format, PV_SIZEOF_DEFAULT_FORMAT, "%.510s", new_format_string);
state_copy.default_format[PV_SIZEOF_DEFAULT_FORMAT - 1] = '\0';
/*
* Get things ready for the main loop.
*/
memset(&cur_time, 0, sizeof(cur_time));
memset(&next_update, 0, sizeof(next_update));
pv_elapsedtime_read(&cur_time);
pv_elapsedtime_copy(&next_update, &cur_time);
pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->interval));
for (idx = 0; idx < FD_SETSIZE; idx++) {
fd_to_idx[idx] = -1;
}
prev_displayed_lines = 0;
while (true) {
int rc, fd, displayed_lines;
if (1 == state->pv_sig_abort)
break;
pv_elapsedtime_read(&cur_time);
if (kill(state->watch_pid, 0) != 0) {
if (first_pass) {
pv_error(state, "%s %u: %s", _("pid"), state->watch_pid, strerror(errno));
state->exit_status |= 2;
if (NULL != info_array)
free(info_array);
if (NULL != state_array)
free(state_array);
return 2;
}
break;
}
/*
* Restart the loop after a brief delay, if it's not time to
* update the display.
*/
if (pv_elapsedtime_compare(&cur_time, &next_update) < 0) {
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
select(0, NULL, NULL, NULL, &tv);
continue;
}
pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->interval));
/* Set the "next update" time to now, if it's in the past. */
if (pv_elapsedtime_compare(&next_update, &cur_time) < 0)
pv_elapsedtime_copy(&next_update, &cur_time);
/* Resize the display, if a resize signal was received. */
if (1 == state->pv_sig_newsize) {
state->pv_sig_newsize = 0;
pv_screensize(&(state->width), &(state->height));
for (idx = 0; idx < array_length; idx++) {
state_array[idx].width = state->width;
state_array[idx].height = state->height;
pv_watchpid_setname(state, &(info_array[idx]));
state_array[idx].reparse_display = 1;
}
}
rc = pv_watchpid_scanfds(state, &state_copy,
state->watch_pid, &array_length, &info_array, &state_array, fd_to_idx);
if (rc != 0) {
if (first_pass) {
pv_error(state, "%s %u: %s", _("pid"), state->watch_pid, strerror(errno));
state->exit_status |= 2;
if (NULL != info_array)
free(info_array);
if (NULL != state_array)
free(state_array);
return 2;
}
break;
}
first_pass = false;
displayed_lines = 0;
for (fd = 0; fd < FD_SETSIZE; fd++) {
long long position_now, transferred_since_last;
struct timespec init_time, transfer_elapsed;
long double elapsed_seconds;
if (displayed_lines >= (int) (state->height))
break;
idx = fd_to_idx[fd];
if (idx < 0)
continue;
if (info_array[idx].watch_fd < 0) {
/*
* Non-displayable fd - just remove if
* changed
*/
if (pv_watchfd_changed(&(info_array[idx]))) {
fd_to_idx[fd] = -1;
info_array[idx].watch_pid = 0;
debug("%s %d: %s", "fd", fd, "removing");
}
continue;
}
/*
* Displayable fd - display, or remove if changed
*/
position_now = pv_watchfd_position(&(info_array[idx]));
if (position_now < 0) {
fd_to_idx[fd] = -1;
info_array[idx].watch_pid = 0;
debug("%s %d: %s", "fd", fd, "removing");
continue;
}
transferred_since_last = position_now - info_array[idx].position;
info_array[idx].position = position_now;
/*
* Calculate the effective start time: the time we actually
* started, plus the total time we spent stopped.
*/
pv_elapsedtime_add(&init_time, &(info_array[idx].start_time), &(state->pv_sig_toffset));
/*
* Now get the effective elapsed transfer time - current
* time minus effective start time.
*/
pv_elapsedtime_subtract(&transfer_elapsed, &cur_time, &init_time);
elapsed_seconds = pv_elapsedtime_seconds(&transfer_elapsed);
if (displayed_lines > 0) {
debug("%s", "adding newline");
pv_write_retry(STDERR_FILENO, "\n", 1);
}
debug("%s %d [%d]: %Lf / %Ld / %Ld", "fd", fd, idx, elapsed_seconds, transferred_since_last,
position_now);
pv_display(&(state_array[idx]), elapsed_seconds, transferred_since_last, position_now);
displayed_lines++;
}
/*
* Write blank lines if we're writing fewer lines than last
* time.
*/
blank_lines = prev_displayed_lines - displayed_lines;
prev_displayed_lines = displayed_lines;
if (blank_lines > 0)
debug("%s: %d", "adding blank lines", blank_lines);
while (blank_lines > 0) {
unsigned int x;
if (displayed_lines > 0)
pv_write_retry(STDERR_FILENO, "\n", 1);
for (x = 0; x < state->width; x++)
pv_write_retry(STDERR_FILENO, " ", 1);
pv_write_retry(STDERR_FILENO, "\r", 1);
blank_lines--;
displayed_lines++;
}
debug("%s: %d", "displayed lines", displayed_lines);
while (displayed_lines > 1) {
pv_write_retry(STDERR_FILENO, "\033[A", 3);
displayed_lines--;
}
}
/*
* Clean up our displayed lines on exit.
*/
blank_lines = prev_displayed_lines;
while (blank_lines > 0) {
unsigned int x;
for (x = 0; x < state->width; x++)
pv_write_retry(STDERR_FILENO, " ", 1);
pv_write_retry(STDERR_FILENO, "\r", 1);
blank_lines--;
if (blank_lines > 0)
pv_write_retry(STDERR_FILENO, "\n", 1);
}
while (prev_displayed_lines > 1) {
pv_write_retry(STDERR_FILENO, "\033[A", 3);
prev_displayed_lines--;
}
if (NULL != info_array)
free(info_array);
if (NULL != state_array)
free(state_array);
return 0;
}
/* EOF */