1295367Sdes/* $OpenBSD: progressmeter.c,v 1.41 2015/01/14 13:54:13 djm Exp $ */ 2113908Sdes/* 3124208Sdes * Copyright (c) 2003 Nils Nordman. All rights reserved. 4113908Sdes * 5113908Sdes * Redistribution and use in source and binary forms, with or without 6113908Sdes * modification, are permitted provided that the following conditions 7113908Sdes * are met: 8113908Sdes * 1. Redistributions of source code must retain the above copyright 9113908Sdes * notice, this list of conditions and the following disclaimer. 10113908Sdes * 2. Redistributions in binary form must reproduce the above copyright 11113908Sdes * notice, this list of conditions and the following disclaimer in the 12113908Sdes * documentation and/or other materials provided with the distribution. 13113908Sdes * 14113908Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15113908Sdes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16113908Sdes * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17113908Sdes * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18113908Sdes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19113908Sdes * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20113908Sdes * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21113908Sdes * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22113908Sdes * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23113908Sdes * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24113908Sdes */ 25113908Sdes 26113908Sdes#include "includes.h" 27113908Sdes 28162852Sdes#include <sys/types.h> 29162852Sdes#include <sys/ioctl.h> 30162852Sdes#include <sys/uio.h> 31162852Sdes 32162852Sdes#include <errno.h> 33162852Sdes#include <signal.h> 34162852Sdes#include <stdio.h> 35162852Sdes#include <string.h> 36162852Sdes#include <time.h> 37162852Sdes#include <unistd.h> 38162852Sdes 39124208Sdes#include "progressmeter.h" 40113908Sdes#include "atomicio.h" 41124208Sdes#include "misc.h" 42113908Sdes 43124208Sdes#define DEFAULT_WINSIZE 80 44124208Sdes#define MAX_WINSIZE 512 45124208Sdes#define PADDING 1 /* padding between the progress indicators */ 46124208Sdes#define UPDATE_INTERVAL 1 /* update the progress meter every second */ 47124208Sdes#define STALL_TIME 5 /* we're stalled after this many seconds */ 48113908Sdes 49124208Sdes/* determines whether we can output to the terminal */ 50124208Sdesstatic int can_output(void); 51113908Sdes 52124208Sdes/* formats and inserts the specified size into the given buffer */ 53124208Sdesstatic void format_size(char *, int, off_t); 54124208Sdesstatic void format_rate(char *, int, off_t); 55113908Sdes 56149749Sdes/* window resizing */ 57149749Sdesstatic void sig_winch(int); 58149749Sdesstatic void setscreensize(void); 59149749Sdes 60124208Sdes/* updates the progressmeter to reflect the current state of the transfer */ 61124208Sdesvoid refresh_progress_meter(void); 62113908Sdes 63124208Sdes/* signal handler for updating the progress meter */ 64124208Sdesstatic void update_progress_meter(int); 65113908Sdes 66137015Sdesstatic time_t start; /* start progress */ 67137015Sdesstatic time_t last_update; /* last progress update */ 68295367Sdesstatic const char *file; /* name of the file being transferred */ 69262566Sdesstatic off_t start_pos; /* initial position of transfer */ 70137015Sdesstatic off_t end_pos; /* ending position of transfer */ 71137015Sdesstatic off_t cur_pos; /* transfer position as of last refresh */ 72124208Sdesstatic volatile off_t *counter; /* progress counter */ 73137015Sdesstatic long stalled; /* how long we have been stalled */ 74137015Sdesstatic int bytes_per_second; /* current speed in bytes per second */ 75137015Sdesstatic int win_size; /* terminal window size */ 76149749Sdesstatic volatile sig_atomic_t win_resized; /* for window resizing */ 77113908Sdes 78124208Sdes/* units for format_size */ 79124208Sdesstatic const char unit[] = " KMGT"; 80113908Sdes 81124208Sdesstatic int 82124208Sdescan_output(void) 83113908Sdes{ 84124208Sdes return (getpgrp() == tcgetpgrp(STDOUT_FILENO)); 85113908Sdes} 86113908Sdes 87113908Sdesstatic void 88124208Sdesformat_rate(char *buf, int size, off_t bytes) 89113908Sdes{ 90124208Sdes int i; 91113908Sdes 92124208Sdes bytes *= 100; 93124208Sdes for (i = 0; bytes >= 100*1000 && unit[i] != 'T'; i++) 94124208Sdes bytes = (bytes + 512) / 1024; 95124208Sdes if (i == 0) { 96124208Sdes i++; 97124208Sdes bytes = (bytes + 512) / 1024; 98124208Sdes } 99124208Sdes snprintf(buf, size, "%3lld.%1lld%c%s", 100157016Sdes (long long) (bytes + 5) / 100, 101157016Sdes (long long) (bytes + 5) / 10 % 10, 102124208Sdes unit[i], 103124208Sdes i ? "B" : " "); 104113908Sdes} 105113908Sdes 106124208Sdesstatic void 107124208Sdesformat_size(char *buf, int size, off_t bytes) 108113908Sdes{ 109124208Sdes int i; 110113908Sdes 111124208Sdes for (i = 0; bytes >= 10000 && unit[i] != 'T'; i++) 112124208Sdes bytes = (bytes + 512) / 1024; 113124208Sdes snprintf(buf, size, "%4lld%c%s", 114157016Sdes (long long) bytes, 115124208Sdes unit[i], 116124208Sdes i ? "B" : " "); 117113908Sdes} 118113908Sdes 119124208Sdesvoid 120124208Sdesrefresh_progress_meter(void) 121113908Sdes{ 122124208Sdes char buf[MAX_WINSIZE + 1]; 123124208Sdes time_t now; 124124208Sdes off_t transferred; 125113908Sdes double elapsed; 126124208Sdes int percent; 127126274Sdes off_t bytes_left; 128124208Sdes int cur_speed; 129124208Sdes int hours, minutes, seconds; 130124208Sdes int i, len; 131124208Sdes int file_len; 132113908Sdes 133262566Sdes transferred = *counter - (cur_pos ? cur_pos : start_pos); 134124208Sdes cur_pos = *counter; 135255767Sdes now = monotime(); 136124208Sdes bytes_left = end_pos - cur_pos; 137113908Sdes 138124208Sdes if (bytes_left > 0) 139124208Sdes elapsed = now - last_update; 140126274Sdes else { 141124208Sdes elapsed = now - start; 142126274Sdes /* Calculate true total speed when done */ 143262566Sdes transferred = end_pos - start_pos; 144126274Sdes bytes_per_second = 0; 145126274Sdes } 146113908Sdes 147124208Sdes /* calculate speed */ 148124208Sdes if (elapsed != 0) 149124208Sdes cur_speed = (transferred / elapsed); 150124208Sdes else 151126274Sdes cur_speed = transferred; 152113908Sdes 153124208Sdes#define AGE_FACTOR 0.9 154124208Sdes if (bytes_per_second != 0) { 155124208Sdes bytes_per_second = (bytes_per_second * AGE_FACTOR) + 156124208Sdes (cur_speed * (1.0 - AGE_FACTOR)); 157124208Sdes } else 158124208Sdes bytes_per_second = cur_speed; 159113908Sdes 160124208Sdes /* filename */ 161124208Sdes buf[0] = '\0'; 162124208Sdes file_len = win_size - 35; 163124208Sdes if (file_len > 0) { 164124208Sdes len = snprintf(buf, file_len + 1, "\r%s", file); 165124208Sdes if (len < 0) 166124208Sdes len = 0; 167149749Sdes if (len >= file_len + 1) 168149749Sdes len = file_len; 169162852Sdes for (i = len; i < file_len; i++) 170124208Sdes buf[i] = ' '; 171124208Sdes buf[file_len] = '\0'; 172113908Sdes } 173113908Sdes 174124208Sdes /* percent of transfer done */ 175124208Sdes if (end_pos != 0) 176124208Sdes percent = ((float)cur_pos / end_pos) * 100; 177124208Sdes else 178124208Sdes percent = 100; 179124208Sdes snprintf(buf + strlen(buf), win_size - strlen(buf), 180124208Sdes " %3d%% ", percent); 181113908Sdes 182124208Sdes /* amount transferred */ 183124208Sdes format_size(buf + strlen(buf), win_size - strlen(buf), 184124208Sdes cur_pos); 185124208Sdes strlcat(buf, " ", win_size); 186113908Sdes 187124208Sdes /* bandwidth usage */ 188124208Sdes format_rate(buf + strlen(buf), win_size - strlen(buf), 189137015Sdes (off_t)bytes_per_second); 190124208Sdes strlcat(buf, "/s ", win_size); 191124208Sdes 192124208Sdes /* ETA */ 193124208Sdes if (!transferred) 194124208Sdes stalled += elapsed; 195124208Sdes else 196124208Sdes stalled = 0; 197124208Sdes 198124208Sdes if (stalled >= STALL_TIME) 199124208Sdes strlcat(buf, "- stalled -", win_size); 200124208Sdes else if (bytes_per_second == 0 && bytes_left) 201124208Sdes strlcat(buf, " --:-- ETA", win_size); 202124208Sdes else { 203124208Sdes if (bytes_left > 0) 204124208Sdes seconds = bytes_left / bytes_per_second; 205113908Sdes else 206124208Sdes seconds = elapsed; 207113908Sdes 208124208Sdes hours = seconds / 3600; 209124208Sdes seconds -= hours * 3600; 210124208Sdes minutes = seconds / 60; 211124208Sdes seconds -= minutes * 60; 212124208Sdes 213124208Sdes if (hours != 0) 214124208Sdes snprintf(buf + strlen(buf), win_size - strlen(buf), 215124208Sdes "%d:%02d:%02d", hours, minutes, seconds); 216113908Sdes else 217124208Sdes snprintf(buf + strlen(buf), win_size - strlen(buf), 218124208Sdes " %02d:%02d", minutes, seconds); 219124208Sdes 220124208Sdes if (bytes_left > 0) 221124208Sdes strlcat(buf, " ETA", win_size); 222124208Sdes else 223124208Sdes strlcat(buf, " ", win_size); 224113908Sdes } 225124208Sdes 226124287Sdes atomicio(vwrite, STDOUT_FILENO, buf, win_size - 1); 227124208Sdes last_update = now; 228113908Sdes} 229113908Sdes 230162852Sdes/*ARGSUSED*/ 231124208Sdesstatic void 232124208Sdesupdate_progress_meter(int ignore) 233113908Sdes{ 234124208Sdes int save_errno; 235124208Sdes 236124208Sdes save_errno = errno; 237124208Sdes 238149749Sdes if (win_resized) { 239149749Sdes setscreensize(); 240149749Sdes win_resized = 0; 241149749Sdes } 242124208Sdes if (can_output()) 243124208Sdes refresh_progress_meter(); 244124208Sdes 245124208Sdes signal(SIGALRM, update_progress_meter); 246124208Sdes alarm(UPDATE_INTERVAL); 247124208Sdes errno = save_errno; 248124208Sdes} 249124208Sdes 250124208Sdesvoid 251295367Sdesstart_progress_meter(const char *f, off_t filesize, off_t *ctr) 252124208Sdes{ 253255767Sdes start = last_update = monotime(); 254124208Sdes file = f; 255262566Sdes start_pos = *ctr; 256124208Sdes end_pos = filesize; 257124208Sdes cur_pos = 0; 258137015Sdes counter = ctr; 259124208Sdes stalled = 0; 260124208Sdes bytes_per_second = 0; 261124208Sdes 262149749Sdes setscreensize(); 263124208Sdes if (can_output()) 264124208Sdes refresh_progress_meter(); 265124208Sdes 266124208Sdes signal(SIGALRM, update_progress_meter); 267149749Sdes signal(SIGWINCH, sig_winch); 268124208Sdes alarm(UPDATE_INTERVAL); 269113908Sdes} 270124208Sdes 271124208Sdesvoid 272124208Sdesstop_progress_meter(void) 273124208Sdes{ 274124208Sdes alarm(0); 275124208Sdes 276124208Sdes if (!can_output()) 277124208Sdes return; 278124208Sdes 279124208Sdes /* Ensure we complete the progress */ 280124208Sdes if (cur_pos != end_pos) 281124208Sdes refresh_progress_meter(); 282124208Sdes 283124208Sdes atomicio(vwrite, STDOUT_FILENO, "\n", 1); 284124208Sdes} 285149749Sdes 286162852Sdes/*ARGSUSED*/ 287149749Sdesstatic void 288149749Sdessig_winch(int sig) 289149749Sdes{ 290149749Sdes win_resized = 1; 291149749Sdes} 292149749Sdes 293149749Sdesstatic void 294149749Sdessetscreensize(void) 295149749Sdes{ 296149749Sdes struct winsize winsize; 297149749Sdes 298149749Sdes if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) != -1 && 299149749Sdes winsize.ws_col != 0) { 300149749Sdes if (winsize.ws_col > MAX_WINSIZE) 301149749Sdes win_size = MAX_WINSIZE; 302149749Sdes else 303149749Sdes win_size = winsize.ws_col; 304149749Sdes } else 305149749Sdes win_size = DEFAULT_WINSIZE; 306149749Sdes win_size += 1; /* trailing \0 */ 307149749Sdes} 308