1255767Sdes/* $OpenBSD: progressmeter.c,v 1.39 2013/06/02 13:33:05 dtucker 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 */ 68137015Sdesstatic char *file; /* name of the file being transferred */ 69137015Sdesstatic off_t end_pos; /* ending position of transfer */ 70137015Sdesstatic off_t cur_pos; /* transfer position as of last refresh */ 71124208Sdesstatic volatile off_t *counter; /* progress counter */ 72137015Sdesstatic long stalled; /* how long we have been stalled */ 73137015Sdesstatic int bytes_per_second; /* current speed in bytes per second */ 74137015Sdesstatic int win_size; /* terminal window size */ 75149749Sdesstatic volatile sig_atomic_t win_resized; /* for window resizing */ 76113908Sdes 77124208Sdes/* units for format_size */ 78124208Sdesstatic const char unit[] = " KMGT"; 79113908Sdes 80124208Sdesstatic int 81124208Sdescan_output(void) 82113908Sdes{ 83124208Sdes return (getpgrp() == tcgetpgrp(STDOUT_FILENO)); 84113908Sdes} 85113908Sdes 86113908Sdesstatic void 87124208Sdesformat_rate(char *buf, int size, off_t bytes) 88113908Sdes{ 89124208Sdes int i; 90113908Sdes 91124208Sdes bytes *= 100; 92124208Sdes for (i = 0; bytes >= 100*1000 && unit[i] != 'T'; i++) 93124208Sdes bytes = (bytes + 512) / 1024; 94124208Sdes if (i == 0) { 95124208Sdes i++; 96124208Sdes bytes = (bytes + 512) / 1024; 97124208Sdes } 98124208Sdes snprintf(buf, size, "%3lld.%1lld%c%s", 99157016Sdes (long long) (bytes + 5) / 100, 100157016Sdes (long long) (bytes + 5) / 10 % 10, 101124208Sdes unit[i], 102124208Sdes i ? "B" : " "); 103113908Sdes} 104113908Sdes 105124208Sdesstatic void 106124208Sdesformat_size(char *buf, int size, off_t bytes) 107113908Sdes{ 108124208Sdes int i; 109113908Sdes 110124208Sdes for (i = 0; bytes >= 10000 && unit[i] != 'T'; i++) 111124208Sdes bytes = (bytes + 512) / 1024; 112124208Sdes snprintf(buf, size, "%4lld%c%s", 113157016Sdes (long long) bytes, 114124208Sdes unit[i], 115124208Sdes i ? "B" : " "); 116113908Sdes} 117113908Sdes 118124208Sdesvoid 119124208Sdesrefresh_progress_meter(void) 120113908Sdes{ 121124208Sdes char buf[MAX_WINSIZE + 1]; 122124208Sdes time_t now; 123124208Sdes off_t transferred; 124113908Sdes double elapsed; 125124208Sdes int percent; 126126274Sdes off_t bytes_left; 127124208Sdes int cur_speed; 128124208Sdes int hours, minutes, seconds; 129124208Sdes int i, len; 130124208Sdes int file_len; 131113908Sdes 132124208Sdes transferred = *counter - cur_pos; 133124208Sdes cur_pos = *counter; 134255767Sdes now = monotime(); 135124208Sdes bytes_left = end_pos - cur_pos; 136113908Sdes 137124208Sdes if (bytes_left > 0) 138124208Sdes elapsed = now - last_update; 139126274Sdes else { 140124208Sdes elapsed = now - start; 141126274Sdes /* Calculate true total speed when done */ 142126274Sdes transferred = end_pos; 143126274Sdes bytes_per_second = 0; 144126274Sdes } 145113908Sdes 146124208Sdes /* calculate speed */ 147124208Sdes if (elapsed != 0) 148124208Sdes cur_speed = (transferred / elapsed); 149124208Sdes else 150126274Sdes cur_speed = transferred; 151113908Sdes 152124208Sdes#define AGE_FACTOR 0.9 153124208Sdes if (bytes_per_second != 0) { 154124208Sdes bytes_per_second = (bytes_per_second * AGE_FACTOR) + 155124208Sdes (cur_speed * (1.0 - AGE_FACTOR)); 156124208Sdes } else 157124208Sdes bytes_per_second = cur_speed; 158113908Sdes 159124208Sdes /* filename */ 160124208Sdes buf[0] = '\0'; 161124208Sdes file_len = win_size - 35; 162124208Sdes if (file_len > 0) { 163124208Sdes len = snprintf(buf, file_len + 1, "\r%s", file); 164124208Sdes if (len < 0) 165124208Sdes len = 0; 166149749Sdes if (len >= file_len + 1) 167149749Sdes len = file_len; 168162852Sdes for (i = len; i < file_len; i++) 169124208Sdes buf[i] = ' '; 170124208Sdes buf[file_len] = '\0'; 171113908Sdes } 172113908Sdes 173124208Sdes /* percent of transfer done */ 174124208Sdes if (end_pos != 0) 175124208Sdes percent = ((float)cur_pos / end_pos) * 100; 176124208Sdes else 177124208Sdes percent = 100; 178124208Sdes snprintf(buf + strlen(buf), win_size - strlen(buf), 179124208Sdes " %3d%% ", percent); 180113908Sdes 181124208Sdes /* amount transferred */ 182124208Sdes format_size(buf + strlen(buf), win_size - strlen(buf), 183124208Sdes cur_pos); 184124208Sdes strlcat(buf, " ", win_size); 185113908Sdes 186124208Sdes /* bandwidth usage */ 187124208Sdes format_rate(buf + strlen(buf), win_size - strlen(buf), 188137015Sdes (off_t)bytes_per_second); 189124208Sdes strlcat(buf, "/s ", win_size); 190124208Sdes 191124208Sdes /* ETA */ 192124208Sdes if (!transferred) 193124208Sdes stalled += elapsed; 194124208Sdes else 195124208Sdes stalled = 0; 196124208Sdes 197124208Sdes if (stalled >= STALL_TIME) 198124208Sdes strlcat(buf, "- stalled -", win_size); 199124208Sdes else if (bytes_per_second == 0 && bytes_left) 200124208Sdes strlcat(buf, " --:-- ETA", win_size); 201124208Sdes else { 202124208Sdes if (bytes_left > 0) 203124208Sdes seconds = bytes_left / bytes_per_second; 204113908Sdes else 205124208Sdes seconds = elapsed; 206113908Sdes 207124208Sdes hours = seconds / 3600; 208124208Sdes seconds -= hours * 3600; 209124208Sdes minutes = seconds / 60; 210124208Sdes seconds -= minutes * 60; 211124208Sdes 212124208Sdes if (hours != 0) 213124208Sdes snprintf(buf + strlen(buf), win_size - strlen(buf), 214124208Sdes "%d:%02d:%02d", hours, minutes, seconds); 215113908Sdes else 216124208Sdes snprintf(buf + strlen(buf), win_size - strlen(buf), 217124208Sdes " %02d:%02d", minutes, seconds); 218124208Sdes 219124208Sdes if (bytes_left > 0) 220124208Sdes strlcat(buf, " ETA", win_size); 221124208Sdes else 222124208Sdes strlcat(buf, " ", win_size); 223113908Sdes } 224124208Sdes 225124287Sdes atomicio(vwrite, STDOUT_FILENO, buf, win_size - 1); 226124208Sdes last_update = now; 227113908Sdes} 228113908Sdes 229162852Sdes/*ARGSUSED*/ 230124208Sdesstatic void 231124208Sdesupdate_progress_meter(int ignore) 232113908Sdes{ 233124208Sdes int save_errno; 234124208Sdes 235124208Sdes save_errno = errno; 236124208Sdes 237149749Sdes if (win_resized) { 238149749Sdes setscreensize(); 239149749Sdes win_resized = 0; 240149749Sdes } 241124208Sdes if (can_output()) 242124208Sdes refresh_progress_meter(); 243124208Sdes 244124208Sdes signal(SIGALRM, update_progress_meter); 245124208Sdes alarm(UPDATE_INTERVAL); 246124208Sdes errno = save_errno; 247124208Sdes} 248124208Sdes 249124208Sdesvoid 250137015Sdesstart_progress_meter(char *f, off_t filesize, off_t *ctr) 251124208Sdes{ 252255767Sdes start = last_update = monotime(); 253124208Sdes file = f; 254124208Sdes end_pos = filesize; 255124208Sdes cur_pos = 0; 256137015Sdes counter = ctr; 257124208Sdes stalled = 0; 258124208Sdes bytes_per_second = 0; 259124208Sdes 260149749Sdes setscreensize(); 261124208Sdes if (can_output()) 262124208Sdes refresh_progress_meter(); 263124208Sdes 264124208Sdes signal(SIGALRM, update_progress_meter); 265149749Sdes signal(SIGWINCH, sig_winch); 266124208Sdes alarm(UPDATE_INTERVAL); 267113908Sdes} 268124208Sdes 269124208Sdesvoid 270124208Sdesstop_progress_meter(void) 271124208Sdes{ 272124208Sdes alarm(0); 273124208Sdes 274124208Sdes if (!can_output()) 275124208Sdes return; 276124208Sdes 277124208Sdes /* Ensure we complete the progress */ 278124208Sdes if (cur_pos != end_pos) 279124208Sdes refresh_progress_meter(); 280124208Sdes 281124208Sdes atomicio(vwrite, STDOUT_FILENO, "\n", 1); 282124208Sdes} 283149749Sdes 284162852Sdes/*ARGSUSED*/ 285149749Sdesstatic void 286149749Sdessig_winch(int sig) 287149749Sdes{ 288149749Sdes win_resized = 1; 289149749Sdes} 290149749Sdes 291149749Sdesstatic void 292149749Sdessetscreensize(void) 293149749Sdes{ 294149749Sdes struct winsize winsize; 295149749Sdes 296149749Sdes if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) != -1 && 297149749Sdes winsize.ws_col != 0) { 298149749Sdes if (winsize.ws_col > MAX_WINSIZE) 299149749Sdes win_size = MAX_WINSIZE; 300149749Sdes else 301149749Sdes win_size = winsize.ws_col; 302149749Sdes } else 303149749Sdes win_size = DEFAULT_WINSIZE; 304149749Sdes win_size += 1; /* trailing \0 */ 305149749Sdes} 306