1
/* -*- mode: c; c-basic-offset: 8; -*-
2
* vim: noexpandtab sw=8 ts=8 sts=0:
6
* Internal routines progress output.
8
* Copyright (C) 2008 Oracle. All rights reserved.
10
* This program is free software; you can redistribute it and/or
11
* modify it under the terms of the GNU General Public
12
* License version 2 as published by the Free Software Foundation.
14
* This program is distributed in the hope that it will be useful,
15
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17
* General Public License for more details.
20
#define _LARGEFILE64_SOURCE
30
#include "ocfs2-kernel/kernel-list.h"
31
#include "tools-internal/progress.h"
32
#include "libtools-internal.h"
34
enum progress_length {
41
#define PERCENTAGE_LEN 5
44
struct tools_progress {
45
struct list_head p_list;
46
enum progress_length p_len;
48
unsigned int p_long_name_len;
50
unsigned int p_short_name_len;
53
unsigned int p_percent;
57
#define PROGRESS_OPEN "["
58
#define PROGRESS_SEP " > "
59
#define PROGRESS_CLOSE "]"
60
#define PROGRESS_ELIPS "... "
62
static const char spinner[] = "\\|/-";
65
static LIST_HEAD(progresses);
67
/* When did we last update the progress */
68
static unsigned int last_tick;
70
/* Are we displaying progress statistics */
71
static int progress_on = 0;
73
/* A fake progress structure to pass around when we're disabled */
74
static struct tools_progress disabled_prog;
77
* A buffer for storing the current progress output. That way, we can
80
* If the terminal is 80 characters or less, or we can't allocate an
81
* appropriately sized progbuf, we use a static one. The extra 2 characters
82
* are for nextline and the NUL.
84
#define DEFAULT_WIDTH 80
85
#define PROGBUF_EXTRA 2
86
static char static_progbuf[DEFAULT_WIDTH + PROGBUF_EXTRA];
87
static char *progbuf = static_progbuf;
88
static unsigned int progbuf_len = DEFAULT_WIDTH + PROGBUF_EXTRA;
91
* If we've updated the progress within the last 1/8th of a second, there
92
* is no point in doing it again. Tick algorithm stolen from e2fsck.
94
static int check_tick(void)
99
gettimeofday(&tv, NULL);
100
tick = (tv.tv_sec << 3) + (tv.tv_usec / (1000000 / 8));
101
if (tick == last_tick)
109
static unsigned int calc_percent(uint64_t num, uint64_t dem)
111
double percent = ((double)num) / ((double)dem);
113
return (unsigned int)((100.0 * percent) + 0.5);
116
/* If the visual percentage hasn't change, there's no point in updating. */
117
static int check_percent(struct tools_progress *prog)
119
unsigned int new_percent;
121
/* An unbounded progress always steps */
125
if (prog->p_current >= prog->p_count)
126
prog->p_current = prog->p_count;
128
new_percent = calc_percent(prog->p_current, prog->p_count);
130
if (new_percent == prog->p_percent)
133
prog->p_percent = new_percent;
137
static void step_spinner(struct tools_progress *prog)
139
prog->p_spinner_pos = (prog->p_spinner_pos + 1) & 3;
142
static void progress_length_reset(void)
145
struct tools_progress *prog;
147
list_for_each(p, &progresses) {
148
prog = list_entry(p, struct tools_progress, p_list);
149
prog->p_len = PROGRESS_LONG;
153
static size_t length_one_prog(struct tools_progress *prog)
157
switch (prog->p_len) {
159
len += prog->p_long_name_len;
162
len += prog->p_short_name_len;
173
len += PERCENTAGE_LEN;
180
static unsigned int progress_length_check(void)
182
unsigned int len = 0;
185
struct tools_progress *prog;
187
assert(!list_empty(&progresses));
189
list_for_each(p, &progresses) {
190
prog = list_entry(p, struct tools_progress, p_list);
193
len += strlen(PROGRESS_OPEN);
196
len += strlen(PROGRESS_SEP);
198
len += length_one_prog(prog);
200
len += strlen(PROGRESS_CLOSE);
205
static int progress_length_shrink(void)
208
struct tools_progress *prog = NULL;
209
enum progress_length len = PROGRESS_LONG;
212
* We start from the longest length. We lower that max length
213
* if we see a shorter one. When we then see the boundary (a
214
* longer length after a shorter one), we break out.
216
list_for_each(p, &progresses) {
217
prog = list_entry(p, struct tools_progress, p_list);
218
if (len > prog->p_len)
220
else if (len < prog->p_len)
226
* If there was no boundary, all progresses had the same length.
227
* shrink the first one.
230
prog = list_entry(progresses.next, struct tools_progress,
234
* If the one we want to shrink already is at PROGRESS_TRUNC, we
235
* can shrink no more. Return false.
237
if (prog->p_len == PROGRESS_TRUNC)
245
static unsigned int check_display(void)
247
char *cols = getenv("COLUMNS");
249
unsigned int tmp, columns = DEFAULT_WIDTH;
257
tmp = columns + PROGBUF_EXTRA;
258
/* Do we need more space for this width? */
259
if (tmp > progbuf_len) {
260
tmpbuf = malloc(sizeof(char) * tmp);
263
memset(tmpbuf, 0, tmp);
264
if (progbuf != static_progbuf)
268
* We just grew the buffer, so try long progress
271
progress_length_reset();
274
* We couldn't allocate enough space, so report
275
* what we can actually use.
277
columns = progbuf_len - PROGBUF_EXTRA;
284
static size_t print_one_prog(struct tools_progress *prog, char *buf,
290
switch (prog->p_len) {
292
ret = snprintf(buf + offset, len - offset,
293
"%s", prog->p_long_name);
296
ret = snprintf(buf + offset, len - offset,
297
"%s", prog->p_short_name);
300
ret = snprintf(buf + offset, len - offset,
311
ret = snprintf(buf + offset, len - offset,
312
" %3u%%", prog->p_percent);
314
ret = snprintf(buf + offset, len - offset, " %c",
315
spinner[prog->p_spinner_pos & 3]);
321
static void print_trailer(char *buf, size_t len)
324
unsigned int offset = 0;
326
ret = snprintf(buf + offset, len - offset, "%s",
329
assert(offset <= len);
330
ret = snprintf(buf + offset, len - offset, "%c", nextline);
331
assert(ret < (len - offset));
334
static void progress_printf(unsigned int columns)
336
unsigned int offset = 0;
340
struct tools_progress *prog = NULL;
342
if (list_empty(&progresses))
345
list_for_each(p, &progresses) {
346
prog = list_entry(p, struct tools_progress, p_list);
349
ret = snprintf(progbuf + offset,
351
"%s", PROGRESS_OPEN);
354
ret = snprintf(progbuf + offset,
359
offset += print_one_prog(prog, progbuf + offset,
361
assert(offset < columns);
365
* From here on out, we use progbuf_len instead of columns. Our
366
* earlier calculations should have gotten this right.
368
assert(offset < columns);
369
print_trailer(progbuf + offset, progbuf_len - offset);
372
static void truncate_printf(unsigned int columns)
374
struct tools_progress *last =
375
list_entry(progresses.prev, struct tools_progress, p_list);
376
size_t ret, len = length_one_prog(last);
377
unsigned int offset = 0;
379
if ((len + strlen(PROGRESS_CLOSE) + strlen(PROGRESS_ELIPS)) <=
381
ret = snprintf(progbuf + offset, columns - offset, "%s",
384
ret = print_one_prog(last, progbuf + offset,
387
assert(offset < columns);
388
print_trailer(progbuf + offset, progbuf_len - offset);
390
/* Give up, no progress */
395
static void progress_compute(void)
397
unsigned int columns = check_display();
400
while (progress_length_check() > columns) {
401
truncate = !progress_length_shrink();
407
truncate_printf(columns);
409
progress_printf(columns);
412
static void progress_clear(void)
414
unsigned int columns = check_display();
416
memset(progbuf, ' ', columns);
417
snprintf(progbuf + columns, progbuf_len - columns, "%c", nextline);
420
static void progress_write(void)
422
printf("%s", progbuf);
425
static void tools_progress_free(struct tools_progress *prog)
427
if (prog->p_long_name)
428
free(prog->p_long_name);
429
if (prog->p_short_name)
430
free(prog->p_short_name);
434
static struct tools_progress *tools_progress_alloc(const char *long_name,
435
const char *short_name,
438
struct tools_progress *prog;
440
prog = malloc(sizeof(struct tools_progress));
444
memset(prog, 0, sizeof(struct tools_progress));
445
prog->p_long_name = strdup(long_name ? long_name : "");
446
prog->p_short_name = strdup(short_name ? short_name : long_name);
447
if (!prog->p_long_name || !prog->p_short_name) {
448
tools_progress_free(prog);
453
prog->p_long_name_len = strlen(prog->p_long_name);
454
prog->p_short_name_len = strlen(prog->p_short_name);
455
prog->p_count = count;
463
* API for libtools-internal only
466
void tools_progress_clear(void)
471
if (list_empty(&progresses))
475
* We only need to wipe the line if are doing terminal-based
478
if (nextline != '\r')
485
void tools_progress_restore(void)
491
if (list_empty(&progresses))
493
if (nextline != '\r')
500
int tools_progress_enabled(void)
510
void tools_progress_enable(void)
514
if (!list_empty(&progresses))
517
if (isatty(STDOUT_FILENO))
524
void tools_progress_disable(void)
529
struct tools_progress *tools_progress_start(const char *long_name,
530
const char *short_name,
533
struct tools_progress *prog;
536
return &disabled_prog;
538
prog = tools_progress_alloc(long_name, short_name, count);
542
list_add_tail(&prog->p_list, &progresses);
543
tools_progress_clear();
544
progress_length_reset();
552
void tools_progress_step(struct tools_progress *prog, unsigned int step)
554
if (prog == &disabled_prog)
557
prog->p_current += step;
559
if (!check_percent(prog))
561
if (!check_tick() && (prog->p_percent != 100) &&
562
(!prog->p_count || (prog->p_percent != 0)))
572
void tools_progress_stop(struct tools_progress *prog)
574
if (prog == &disabled_prog)
577
tools_progress_clear();
579
list_del(&prog->p_list);
580
tools_progress_free(prog);
582
if (!list_empty(&progresses)) {
583
progress_length_reset();
584
tools_progress_restore();
591
static int run_steps(const char *ln, const char *sn, int count,
595
struct tools_progress *prog;
596
struct timespec ts = {
597
.tv_nsec = 100000000,
600
prog = tools_progress_start(ln, sn, count > 0 ? count : 0);
606
for (i = 0; i < count; i++) {
611
tools_progress_step(prog, 1);
612
nanosleep(&ts, NULL);
614
tools_progress_stop(prog);
619
static int middle(void)
622
char lbuf[100], sbuf[100];
625
snprintf(lbuf, 100, "This is middle %d", try);
626
snprintf(sbuf, 100, "middle%d", try);
627
return run_steps(lbuf, sbuf, -7, NULL);
630
static int outer(void)
633
char lbuf[100], sbuf[100];
636
snprintf(lbuf, 100, "This is outer %d", try);
637
snprintf(sbuf, 100, "outer%d", try);
638
return run_steps(lbuf, sbuf, 10, middle);
641
int main(int argc, char *argv[])
643
setbuf(stdout, NULL);
644
setbuf(stderr, NULL);
645
tools_progress_enable();
646
return run_steps("This is a test", "thisis", 5, outer);