From 65d568c283ce05fd721836a6c063b473efb71c89 Mon Sep 17 00:00:00 2001 From: Andrew Wood Date: Fri, 4 Oct 2024 13:27:17 +0100 Subject: [PATCH] Refactor display and calculation functions to separate them out more cleanly, move more information into the state structure instead of repeating it as function parameters, and fix a memory leak in the whole-PID mode of --watchfd. --- docs/NEWS.md | 2 + src/include/pv-internal.h | 15 +++-- src/pv/display.c | 132 ++++++++++++++++---------------------- src/pv/loop.c | 85 ++++++++++++------------ 4 files changed, 109 insertions(+), 125 deletions(-) diff --git a/docs/NEWS.md b/docs/NEWS.md index 28cfb7c..ccb4ce4 100644 --- a/docs/NEWS.md +++ b/docs/NEWS.md @@ -3,9 +3,11 @@ * i18n: complete set of German translations supplied by Hartmut Goebel ([#98](https://codeberg.org/a-j-wood/pv/pulls/98)) * fix: resume stopped pipelines when running in the background (part of [#56](https://codeberg.org/a-j-wood/pv/issues/56)) * fix: write UTC timestamps in debugging mode to avoid lockups in signal handlers + * fix: correct a memory leak in "`--watchfd PID`" * cleanup: removed TODO.md, since it's just an outdated copy of the issue tracker * cleanup: re-ordered structure members to reduce padding * cleanup: improved readability of SIGTTOU handling code + * cleanup: refactored to separate display, transfer, and calculation more cleanly * cleanup: instead of moving stderr when backgrounded, set a suspend-output flag ### 1.8.14 - 7 September 2024 diff --git a/src/include/pv-internal.h b/src/include/pv-internal.h index 54f5833..2c0d43a 100644 --- a/src/include/pv-internal.h +++ b/src/include/pv-internal.h @@ -216,18 +216,20 @@ struct pvstate_s { long double prev_elapsed_sec; /* elapsed sec at which rate last calculated */ long double prev_rate; /* last calculated instantaneous transfer rate */ - long double prev_trans; /* bytes transferred since last rate calculation */ + long double prev_trans; /* amount transferred since last rate calculation */ long double current_avg_rate; /* current average rate over last history intervals */ /* Keep track of progress over last intervals to compute current average rate. */ /*@null@*/ struct { /* state at previous intervals (circular buffer) */ long double elapsed_sec; /* time since start of transfer */ - off_t total_bytes; /* amount transferred by that time */ + off_t total_written; /* amount transferred by that time */ } *history; size_t history_len; /* total size of history array */ size_t history_first; /* index of oldest entry */ size_t history_last; /* index of newest entry */ + off_t prev_total_written; /* total amount transferred when called last time */ + int percentage; /* transfer percentage completion */ } calc; @@ -272,6 +274,7 @@ struct pvstate_s { */ struct { /*@only@*/ /*@null@*/ char *transfer_buffer; /* data transfer buffer */ + long double elapsed_seconds; /* how long we have been transferring data for */ size_t buffer_size; /* size of buffer */ size_t read_position; /* amount of data in buffer */ size_t write_position; /* buffered data written */ @@ -279,6 +282,8 @@ struct pvstate_s { ssize_t to_write; /* max to write this time around */ ssize_t written; /* bytes sent to stdout this time */ + off_t total_written; /* total bytes or lines transferred */ + /* * While reading from a file descriptor we keep track of how * many times in a row we've seen errors @@ -336,9 +341,9 @@ typedef struct pvwatchfd_s *pvwatchfd_t; void pv_error(pvstate_t, char *, ...); int pv_main_loop(pvstate_t); -void pv_calculate_transfer_rate(pvstate_t, long double, off_t, off_t); -bool pv_format(pvstate_t, long double, off_t, off_t); -void pv_display(pvstate_t, long double, off_t, off_t); +void pv_calculate_transfer_rate(pvstate_t, bool); +bool pv_format(pvstate_t, bool); +void pv_display(pvstate_t, bool); ssize_t pv_transfer(pvstate_t, int, bool *, bool *, off_t, long *); int pv_next_file(pvstate_t, unsigned int, int); /*@out@*/ const char *pv_current_file_name(pvstate_t); diff --git a/src/pv/display.c b/src/pv/display.c index 6e49602..269204c 100644 --- a/src/pv/display.c +++ b/src/pv/display.c @@ -607,8 +607,7 @@ static long bound_long(long x, long min, long max) * rate, otherwise calulate the average rate from the difference between the * current position + elapsed time pair, and the oldest pair in the buffer. */ -static void pv__update_average_rate_history(pvstate_t state, off_t total_bytes, long double elapsed_sec, - long double rate) +static void pv__update_average_rate_history(pvstate_t state, long double rate) { size_t first = state->calc.history_first; size_t last = state->calc.history_last; @@ -624,7 +623,7 @@ static void pv__update_average_rate_history(pvstate_t state, off_t total_bytes, * elapsed since the previous call yet. */ if ((last_elapsed > 0.0) - && (elapsed_sec < (last_elapsed + state->control.history_interval))) + && (state->transfer.elapsed_seconds < (last_elapsed + state->control.history_interval))) return; /* @@ -641,13 +640,13 @@ static void pv__update_average_rate_history(pvstate_t state, off_t total_bytes, } } - state->calc.history[last].elapsed_sec = elapsed_sec; - state->calc.history[last].total_bytes = total_bytes; + state->calc.history[last].elapsed_sec = state->transfer.elapsed_seconds; + state->calc.history[last].total_written = state->transfer.total_written; if (first == last) { state->calc.current_avg_rate = rate; } else { - off_t bytes = (state->calc.history[last].total_bytes - state->calc.history[first].total_bytes); + off_t bytes = (state->calc.history[last].total_written - state->calc.history[first].total_written); long double sec = (state->calc.history[last].elapsed_sec - state->calc.history[first].elapsed_sec); state->calc.current_avg_rate = (long double) bytes / sec; } @@ -655,12 +654,9 @@ static void pv__update_average_rate_history(pvstate_t state, off_t total_bytes, /* - * Update all calculated transfer state (state->calc), where "elapsed_sec" - * is the seconds elapsed since the transfer started, "bytes_since_last" is - * the number of bytes transferred since the last update, and "total_bytes" - * is the total number of bytes transferred so far. + * Update all calculated transfer state (state->calc). * - * If "bytes_since_last" is negative, this is the final update, so + * If "final" is true, this is the final update, so * state->calc.transfer_rate and state->calc.average_rate are given as an * average over the whole transfer; otherwise they are the current transfer * rate and current average rate. @@ -669,15 +665,20 @@ static void pv__update_average_rate_history(pvstate_t state, off_t total_bytes, * completion if state->control.size is greater than zero, otherwise it will * increase by 2 each call and wrap at 200. */ -void pv_calculate_transfer_rate(pvstate_t state, long double elapsed_sec, off_t bytes_since_last, off_t total_bytes) +void pv_calculate_transfer_rate(pvstate_t state, bool final) { + off_t bytes_since_last; long double time_since_last, transfer_rate, average_rate; - /* Quick sanity check - state must exist, total_bytes must be >= 0. */ + /* Quick sanity check - state must exist. */ if (NULL == state) return; - if (total_bytes < 0) - return; + + bytes_since_last = 0; + if (state->transfer.total_written >= 0) { + bytes_since_last = state->transfer.total_written - state->calc.prev_total_written; + state->calc.prev_total_written = state->transfer.total_written; + } /* * In case the time since the last update is very small, we keep @@ -685,19 +686,19 @@ void pv_calculate_transfer_rate(pvstate_t state, long double elapsed_sec, off_t * adding to that until a reasonable amount of time has passed to * avoid rate spikes or division by zero. */ - time_since_last = elapsed_sec - state->calc.prev_elapsed_sec; + time_since_last = state->transfer.elapsed_seconds - state->calc.prev_elapsed_sec; if (time_since_last <= 0.01) { transfer_rate = state->calc.prev_rate; state->calc.prev_trans += bytes_since_last; } else { transfer_rate = ((long double) bytes_since_last + state->calc.prev_trans) / time_since_last; - state->calc.prev_elapsed_sec = elapsed_sec; + state->calc.prev_elapsed_sec = state->transfer.elapsed_seconds; state->calc.prev_trans = 0; } state->calc.prev_rate = transfer_rate; /* Update history and current average rate for ETA. */ - pv__update_average_rate_history(state, total_bytes, elapsed_sec, transfer_rate); + pv__update_average_rate_history(state, transfer_rate); average_rate = state->calc.current_avg_rate; /* @@ -705,13 +706,13 @@ void pv_calculate_transfer_rate(pvstate_t state, long double elapsed_sec, off_t * recalculate the rate - and the average rate - across the whole * period of the transfer. */ - if (bytes_since_last < 0) { + if (final) { /* Sanity check to avoid division by zero */ - if (elapsed_sec < 0.000001) - elapsed_sec = 0.000001; + if (state->transfer.elapsed_seconds < 0.000001) + state->transfer.elapsed_seconds = 0.000001; average_rate = - (((long double) total_bytes) - - ((long double) state->display.initial_offset)) / (long double) elapsed_sec; + (((long double) state->transfer.total_written) - + ((long double) state->display.initial_offset)) / (long double) (state->transfer.elapsed_seconds); transfer_rate = average_rate; } @@ -732,7 +733,7 @@ void pv_calculate_transfer_rate(pvstate_t state, long double elapsed_sec, off_t if (state->calc.percentage > 199) state->calc.percentage = 0; } else { - state->calc.percentage = pv__calc_percentage(total_bytes, state->control.size); + state->calc.percentage = pv__calc_percentage(state->transfer.total_written, state->control.size); } /* Ensure the percentage is never negative or huge. */ @@ -745,28 +746,18 @@ void pv_calculate_transfer_rate(pvstate_t state, long double elapsed_sec, off_t /* * Update state->display.display_buffer with status information formatted - * according to the state held within the given structure, where - * "elapsed_sec" is the seconds elapsed since the transfer started, - * "bytes_since_last" is the number of bytes transferred since the last - * update, and "total_bytes" is the total number of bytes transferred so - * far. + * according to the state held within the given structure. * - * If "bytes_since_last" is negative, this is the final update so the rate - * is given as an an average over the whole transfer; otherwise the current - * rate is shown. - * - * In line mode, "bytes_since_last" and "total_bytes" are in lines, not bytes. + * If "final" is true, this is the final update so the rate is given as an + * an average over the whole transfer; otherwise the current rate is shown. * * Returns true if the display buffer can be used, false if not. * * When returning true, this function will have also set * state->display.display_string_len to the length of the string in * state->display.display_buffer, in bytes. - * - * If "total_bytes" is negative, then free the display buffer and return - * false. */ -bool pv_format(pvstate_t state, long double elapsed_sec, off_t bytes_since_last, off_t total_bytes) +bool pv_format(pvstate_t state, bool final) { long eta; int static_portion_size; @@ -779,15 +770,6 @@ bool pv_format(pvstate_t state, long double elapsed_sec, off_t bytes_since_last, if (NULL == state) return false; - /* Negative total transfer - free memory and return false. */ - if (total_bytes < 0) { - if (NULL != state->display.display_buffer) - free(state->display.display_buffer); - state->display.display_buffer = NULL; - state->display.display_buffer_size = 0; - return false; - } - /* * If the display options need reparsing, do so to generate new * formatting parameters. @@ -851,17 +833,20 @@ bool pv_format(pvstate_t state, long double elapsed_sec, off_t bytes_since_last, numericprefix[0] = '\0'; if (state->display.component[PV_COMPONENT_TIMER].required) - (void) pv_snprintf(numericprefix, sizeof(numericprefix), "%.4Lf ", elapsed_sec); + (void) pv_snprintf(numericprefix, sizeof(numericprefix), "%.4Lf ", + state->transfer.elapsed_seconds); if (state->display.component[PV_COMPONENT_BYTES].required) { if (state->control.bits) { (void) pv_snprintf(state->display.display_buffer, state->display.display_buffer_size, - "%.99s%lld\n", numericprefix, (long long) (8 * total_bytes)); + "%.99s%lld\n", numericprefix, + (long long) (8 * state->transfer.total_written)); } else { (void) pv_snprintf(state->display.display_buffer, state->display.display_buffer_size, - "%.99s%lld\n", numericprefix, (long long) total_bytes); + "%.99s%lld\n", numericprefix, + (long long) (state->transfer.total_written)); } } else { (void) pv_snprintf(state->display.display_buffer, @@ -932,10 +917,10 @@ bool pv_format(pvstate_t state, long double elapsed_sec, off_t bytes_since_last, /*@-mustfreefresh @ */ if (state->control.bits && !state->control.linemode) { pv__sizestr(component_content, component_buf_size, "%s", - (long double) total_bytes * 8, "", _("b"), count_type); + (long double) (state->transfer.total_written * 8), "", _("b"), count_type); } else { pv__sizestr(component_content, component_buf_size, "%s", - (long double) total_bytes, "", _("B"), count_type); + (long double) (state->transfer.total_written), "", _("B"), count_type); } /*@+mustfreefresh @ */ /* splint: we trust gettext() not to really leak memory. */ @@ -948,26 +933,28 @@ bool pv_format(pvstate_t state, long double elapsed_sec, off_t bytes_since_last, * does mean that the timer will stop at a 100,000 hours, * but since that's 11 years, it shouldn't be a problem. */ - if (elapsed_sec > (long double) 360000000.0L) - elapsed_sec = (long double) 360000000.0L; + if (state->transfer.elapsed_seconds > (long double) 360000000.0L) + state->transfer.elapsed_seconds = (long double) 360000000.0L; /* * If the elapsed time is more than a day, include a day count as * well as hours, minutes, and seconds. */ - if (elapsed_sec > (long double) 86400.0L) { + if (state->transfer.elapsed_seconds > (long double) 86400.0L) { (void) pv_snprintf(component_content, component_buf_size, "%ld:%02ld:%02ld:%02ld", - ((long) elapsed_sec) / 86400, - (((long) elapsed_sec) / 3600) % - 24, (((long) elapsed_sec) / 60) % 60, ((long) elapsed_sec) % 60); + ((long) (state->transfer.elapsed_seconds)) / 86400, + (((long) (state->transfer.elapsed_seconds)) / 3600) % + 24, (((long) (state->transfer.elapsed_seconds)) / 60) % 60, + ((long) (state->transfer.elapsed_seconds)) % 60); } else { (void) pv_snprintf(component_content, component_buf_size, "%ld:%02ld:%02ld", - ((long) elapsed_sec) / 3600, - (((long) elapsed_sec) / 60) % 60, ((long) elapsed_sec) % 60); + ((long) (state->transfer.elapsed_seconds)) / 3600, + (((long) (state->transfer.elapsed_seconds)) / 60) % 60, + ((long) (state->transfer.elapsed_seconds)) % 60); } break; @@ -1005,7 +992,7 @@ bool pv_format(pvstate_t state, long double elapsed_sec, off_t bytes_since_last, case PV_COMPONENT_ETA: /* Estimated time remaining until completion - if size is known. */ eta = - pv__seconds_remaining(((off_t) total_bytes - state->display.initial_offset), + pv__seconds_remaining((state->transfer.total_written - state->display.initial_offset), state->control.size - state->display.initial_offset, state->calc.current_avg_rate); @@ -1037,7 +1024,7 @@ bool pv_format(pvstate_t state, long double elapsed_sec, off_t bytes_since_last, * If this is the final update, show a blank space where the * ETA used to be. */ - if (bytes_since_last < 0) { + if (final) { size_t erase_idx; for (erase_idx = 0; erase_idx < component_buf_size && component_content[erase_idx] != '\0'; @@ -1060,7 +1047,7 @@ bool pv_format(pvstate_t state, long double elapsed_sec, off_t bytes_since_last, */ eta = - pv__seconds_remaining((off_t) (total_bytes - state->display.initial_offset), + pv__seconds_remaining(state->transfer.total_written - state->display.initial_offset, state->control.size - state->display.initial_offset, state->calc.current_avg_rate); @@ -1364,28 +1351,21 @@ bool pv_format(pvstate_t state, long double elapsed_sec, off_t bytes_since_last, /* - * Output status information on standard error, where "elapsed_sec" is the - * seconds elapsed since the transfer started, "bytes_since_last" is the - * number of bytes transferred since the last update, and "total_bytes" is - * the total number of bytes transferred so far. + * Output status information on standard error. * - * If "bytes_since_last" is negative, this is the final update so the rate - * is given as an an average over the whole transfer; otherwise the current - * rate is shown. - * - * In line mode, "bytes_since_last" and "total_bytes" are in lines, not - * bytes. + * If "final" is true, this is the final update, so the rate is given as an + * an average over the whole transfer; otherwise the current rate is shown. */ -void pv_display(pvstate_t state, long double elapsed_sec, off_t bytes_since_last, off_t total_bytes) +void pv_display(pvstate_t state, bool final) { if (NULL == state) return; pv_sig_checkbg(); - pv_calculate_transfer_rate(state, elapsed_sec, bytes_since_last, total_bytes); + pv_calculate_transfer_rate(state, final); - if (!pv_format(state, elapsed_sec, bytes_since_last, total_bytes)) + if (!pv_format(state, final)) return; if (NULL == state->display.display_buffer) diff --git a/src/pv/loop.c b/src/pv/loop.c index 510edb0..726c649 100644 --- a/src/pv/loop.c +++ b/src/pv/loop.c @@ -34,13 +34,12 @@ int pv_main_loop(pvstate_t state) { long lineswritten; - off_t total_written, transferred_since_last, cansend; + off_t cansend; ssize_t written; 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; int fd; unsigned int file_idx; @@ -50,12 +49,9 @@ int pv_main_loop(pvstate_t state) * "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. + * "state->transfer.total_written" is the total bytes written since + * the start, or in line mode, the total lines written since the + * start. * * The remaining variables are all unchanged by linemode. */ @@ -66,9 +62,8 @@ int pv_main_loop(pvstate_t state) eof_in = false; eof_out = false; - total_written = 0; + state->transfer.total_written = 0; lineswritten = 0; - transferred_since_last = 0; state->display.initial_offset = 0; memset(&cur_time, 0, sizeof(cur_time)); @@ -187,10 +182,10 @@ int pv_main_loop(pvstate_t state) * try to write more than we're allowed to. */ if ((0 < state->control.size) && (state->control.stop_at_size)) { - if ((state->control.size < (total_written + cansend)) + if ((state->control.size < (state->transfer.total_written + cansend)) || ((0 == cansend) && (0 == state->control.rate_limit))) { - cansend = state->control.size - total_written; + cansend = state->control.size - state->transfer.total_written; if (0 >= cansend) { debug("%s", "write limit reached (size explicitly set) - setting EOF flags"); eof_in = true; @@ -215,13 +210,11 @@ int pv_main_loop(pvstate_t state) } if (state->control.linemode) { - transferred_since_last += lineswritten; - total_written += lineswritten; + state->transfer.total_written += lineswritten; if (state->control.rate_limit > 0) target -= lineswritten; } else { - transferred_since_last += written; - total_written += written; + state->transfer.total_written += written; if (state->control.rate_limit > 0) target -= written; } @@ -320,10 +313,7 @@ int pv_main_loop(pvstate_t state) 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; + state->transfer.elapsed_seconds = pv_elapsedtime_seconds(&transfer_elapsed); /* Resize the display, if a resize signal was received. */ if (1 == state->flag.terminal_resized) { @@ -341,9 +331,7 @@ int pv_main_loop(pvstate_t state) state->control.height = new_height; } - pv_display(state, elapsed_seconds, transferred_since_last, total_written); - - transferred_since_last = 0; + pv_display(state, final_update); } debug("%s: %s=%s, %s=%s", "loop ended", "eof_in", eof_in ? "true" : "false", "eof_out", @@ -377,10 +365,9 @@ int pv_main_loop(pvstate_t state) int pv_watchfd_loop(pvstate_t state) { struct pvwatchfd_s info; - off_t position_now, total_written, transferred_since_last; + off_t position_now; struct timespec next_update, cur_time; struct timespec init_time, next_remotecheck, transfer_elapsed; - long double elapsed_seconds; bool ended, first_check; int rc; @@ -427,8 +414,6 @@ int pv_watchfd_loop(pvstate_t state) pv_elapsedtime_add_nsec(&next_update, (long long) (1000000000.0 * state->control.interval)); ended = false; - total_written = 0; - transferred_since_last = 0; first_check = true; while (!ended) { @@ -448,12 +433,11 @@ int pv_watchfd_loop(pvstate_t state) if (position_now < 0) { ended = true; } else { - transferred_since_last += position_now - total_written; - total_written = position_now; if (first_check) { state->display.initial_offset = position_now; first_check = false; } + state->transfer.total_written = position_now; } pv_elapsedtime_read(&cur_time); @@ -490,10 +474,7 @@ int pv_watchfd_loop(pvstate_t state) */ pv_elapsedtime_subtract(&transfer_elapsed, &cur_time, &init_time); - elapsed_seconds = pv_elapsedtime_seconds(&transfer_elapsed); - - if (ended) - transferred_since_last = -1; + state->transfer.elapsed_seconds = pv_elapsedtime_seconds(&transfer_elapsed); /* Resize the display, if a resize signal was received. */ if (1 == state->flag.terminal_resized) { @@ -511,9 +492,7 @@ int pv_watchfd_loop(pvstate_t state) state->control.height = new_height; } - pv_display(state, elapsed_seconds, transferred_since_last, total_written); - - transferred_since_last = 0; + pv_display(state, ended); } if (!state->control.numeric) @@ -677,9 +656,8 @@ int pv_watchpid_loop(pvstate_t state) displayed_lines = 0; for (fd = 0; fd < FD_SETSIZE && NULL != info_array; fd++) { - off_t position_now, transferred_since_last; + off_t position_now; struct timespec init_time, transfer_elapsed; - long double elapsed_seconds; if (displayed_lines >= (int) (state->control.height)) break; @@ -697,6 +675,11 @@ int pv_watchpid_loop(pvstate_t state) if (pv_watchfd_changed(&(info_array[idx]))) { fd_to_idx[fd] = -1; info_array[idx].watch_pid = 0; + if (NULL != info_array[idx].state) + pv_state_free(info_array[idx].state); + /*@-mustfreeonly@ *//* not a leak - we've just free()d it. */ + info_array[idx].state = NULL; + /*@+mustfreeonly@ */ debug("%s %d: %s", "fd", fd, "removing"); } continue; @@ -716,11 +699,15 @@ int pv_watchpid_loop(pvstate_t state) if (position_now < 0) { fd_to_idx[fd] = -1; info_array[idx].watch_pid = 0; + if (NULL != info_array[idx].state) + pv_state_free(info_array[idx].state); + /*@-mustfreeonly@ *//* not a leak - we've just free()d it. */ + info_array[idx].state = NULL; + /*@+mustfreeonly@ */ debug("%s %d: %s", "fd", fd, "removing"); continue; } - transferred_since_last = position_now - info_array[idx].position; info_array[idx].position = position_now; memset(&init_time, 0, sizeof(init_time)); @@ -738,18 +725,28 @@ int pv_watchpid_loop(pvstate_t state) */ pv_elapsedtime_subtract(&transfer_elapsed, &cur_time, &init_time); - elapsed_seconds = pv_elapsedtime_seconds(&transfer_elapsed); + if (NULL != info_array[idx].state) { + info_array[idx].state->transfer.elapsed_seconds = + pv_elapsedtime_seconds(&transfer_elapsed); + } if (displayed_lines > 0) { debug("%s", "adding newline"); pv_tty_write(state, "\n", 1); } - debug("%s %d [%d]: %Lf / %Ld / %Ld", "fd", fd, idx, elapsed_seconds, transferred_since_last, - position_now); + if (NULL == info_array[idx].state) { + debug("%s %d [%d]: %s / %Ld", "fd", fd, idx, "(null state)", position_now); + } else { + debug("%s %d [%d]: %Lf / %Ld", "fd", fd, idx, + info_array[idx].state->transfer.elapsed_seconds, position_now); + } - pv_display(info_array[idx].state, elapsed_seconds, transferred_since_last, position_now); - displayed_lines++; + if (NULL != info_array[idx].state) { + info_array[idx].state->transfer.total_written = position_now; + pv_display(info_array[idx].state, false); + displayed_lines++; + } } /*