~wb-munzinger/+junk/ocfs2-tools

« back to all changes in this revision

Viewing changes to libtools-internal/progress.c

  • Committer: Bazaar Package Importer
  • Author(s): Jeremy Lainé
  • Date: 2009-07-06 07:26:30 UTC
  • mfrom: (1.1.7 upstream) (0.1.5 squeeze)
  • Revision ID: james.westby@ubuntu.com-20090706072630-59335sl51k3rvu74
Tags: 1.4.2-1
* New upstream release (Closes: #535471).
* Drop patch for limits.h, included upstream.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- mode: c; c-basic-offset: 8; -*-
 
2
 * vim: noexpandtab sw=8 ts=8 sts=0:
 
3
 *
 
4
 * progress.c
 
5
 *
 
6
 * Internal routines progress output.
 
7
 *
 
8
 * Copyright (C) 2008 Oracle.  All rights reserved.
 
9
 *
 
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.
 
13
 *
 
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.
 
18
 */
 
19
 
 
20
#define _LARGEFILE64_SOURCE
 
21
 
 
22
#include <stdio.h>
 
23
#include <stdlib.h>
 
24
#include <string.h>
 
25
#include <sys/time.h>
 
26
#include <assert.h>
 
27
#include <unistd.h>
 
28
#include <inttypes.h>
 
29
 
 
30
#include "ocfs2-kernel/kernel-list.h"
 
31
#include "tools-internal/progress.h"
 
32
#include "libtools-internal.h"
 
33
 
 
34
enum progress_length {
 
35
        PROGRESS_TRUNC,
 
36
        PROGRESS_SHORT,
 
37
        PROGRESS_LONG,
 
38
};
 
39
 
 
40
#define TRUNC_LEN 3
 
41
#define PERCENTAGE_LEN 5
 
42
#define SPINNER_LEN 2
 
43
 
 
44
struct tools_progress {
 
45
        struct list_head p_list;
 
46
        enum progress_length p_len;
 
47
        char *p_long_name;
 
48
        unsigned int p_long_name_len;
 
49
        char *p_short_name;
 
50
        unsigned int p_short_name_len;
 
51
        uint64_t p_current;
 
52
        uint64_t p_count;
 
53
        unsigned int p_percent;
 
54
        int p_spinner_pos;
 
55
};
 
56
 
 
57
#define PROGRESS_OPEN "["
 
58
#define PROGRESS_SEP " > "
 
59
#define PROGRESS_CLOSE "]"
 
60
#define PROGRESS_ELIPS "... "
 
61
 
 
62
static const char spinner[] = "\\|/-";
 
63
static char nextline;
 
64
 
 
65
static LIST_HEAD(progresses);
 
66
 
 
67
/* When did we last update the progress */
 
68
static unsigned int last_tick;
 
69
 
 
70
/* Are we displaying progress statistics */
 
71
static int progress_on = 0;
 
72
 
 
73
/* A fake progress structure to pass around when we're disabled */
 
74
static struct tools_progress disabled_prog;
 
75
 
 
76
/*
 
77
 * A buffer for storing the current progress output.  That way, we can
 
78
 * replay it.
 
79
 *
 
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.
 
83
 */
 
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;
 
89
 
 
90
/*
 
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.
 
93
 */
 
94
static int check_tick(void)
 
95
{
 
96
        unsigned int tick;
 
97
        struct timeval tv;
 
98
 
 
99
        gettimeofday(&tv, NULL);
 
100
        tick = (tv.tv_sec << 3) + (tv.tv_usec / (1000000 / 8));
 
101
        if (tick == last_tick)
 
102
                return 0;
 
103
 
 
104
        last_tick = tick;
 
105
 
 
106
        return 1;
 
107
}
 
108
 
 
109
static unsigned int calc_percent(uint64_t num, uint64_t dem)
 
110
{
 
111
        double percent = ((double)num) / ((double)dem);
 
112
 
 
113
        return (unsigned int)((100.0 * percent) + 0.5);
 
114
}
 
115
 
 
116
/* If the visual percentage hasn't change, there's no point in updating. */
 
117
static int check_percent(struct tools_progress *prog)
 
118
{
 
119
        unsigned int new_percent;
 
120
 
 
121
        /* An unbounded progress always steps */
 
122
        if (!prog->p_count)
 
123
                return 1;
 
124
 
 
125
        if (prog->p_current >= prog->p_count)
 
126
                prog->p_current = prog->p_count;
 
127
 
 
128
        new_percent = calc_percent(prog->p_current, prog->p_count);
 
129
 
 
130
        if (new_percent == prog->p_percent)
 
131
                return 0;
 
132
 
 
133
        prog->p_percent = new_percent;
 
134
        return 1;
 
135
}
 
136
 
 
137
static void step_spinner(struct tools_progress *prog)
 
138
{
 
139
        prog->p_spinner_pos = (prog->p_spinner_pos + 1) & 3;
 
140
}
 
141
 
 
142
static void progress_length_reset(void)
 
143
{
 
144
        struct list_head *p;
 
145
        struct tools_progress *prog;
 
146
 
 
147
        list_for_each(p, &progresses) {
 
148
                prog = list_entry(p, struct tools_progress, p_list);
 
149
                prog->p_len = PROGRESS_LONG;
 
150
        }
 
151
}
 
152
 
 
153
static size_t length_one_prog(struct tools_progress *prog)
 
154
{
 
155
        size_t len = 0;
 
156
 
 
157
        switch (prog->p_len) {
 
158
                case PROGRESS_LONG:
 
159
                        len += prog->p_long_name_len;
 
160
                        break;
 
161
                case PROGRESS_SHORT:
 
162
                        len += prog->p_short_name_len;
 
163
                        break;
 
164
                case PROGRESS_TRUNC:
 
165
                        len += TRUNC_LEN;
 
166
                        break;
 
167
                default:
 
168
                        assert(0);
 
169
                        break;
 
170
        }
 
171
 
 
172
        if (prog->p_count)
 
173
                len += PERCENTAGE_LEN;
 
174
        else
 
175
                len += SPINNER_LEN;
 
176
 
 
177
        return len;
 
178
}
 
179
 
 
180
static unsigned int progress_length_check(void)
 
181
{
 
182
        unsigned int len = 0;
 
183
        int first = 1;
 
184
        struct list_head *p;
 
185
        struct tools_progress *prog;
 
186
 
 
187
        assert(!list_empty(&progresses));
 
188
 
 
189
        list_for_each(p, &progresses) {
 
190
                prog = list_entry(p, struct tools_progress, p_list);
 
191
 
 
192
                if (first) {
 
193
                        len += strlen(PROGRESS_OPEN);
 
194
                        first = 0;
 
195
                } else
 
196
                        len += strlen(PROGRESS_SEP);
 
197
 
 
198
                len += length_one_prog(prog);
 
199
        }
 
200
        len += strlen(PROGRESS_CLOSE);
 
201
 
 
202
        return len;
 
203
}
 
204
 
 
205
static int progress_length_shrink(void)
 
206
{
 
207
        struct list_head *p;
 
208
        struct tools_progress *prog = NULL;
 
209
        enum progress_length len = PROGRESS_LONG;
 
210
 
 
211
        /*
 
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.
 
215
         */
 
216
        list_for_each(p, &progresses) {
 
217
                prog = list_entry(p, struct tools_progress, p_list);
 
218
                if (len > prog->p_len)
 
219
                        len = prog->p_len;
 
220
                else if (len < prog->p_len)
 
221
                        break;
 
222
                prog = NULL;
 
223
        }
 
224
 
 
225
        /*
 
226
         * If there was no boundary, all progresses had the same length.
 
227
         * shrink the first one.
 
228
         */
 
229
        if (!prog)
 
230
                prog = list_entry(progresses.next, struct tools_progress,
 
231
                                  p_list);
 
232
 
 
233
        /*
 
234
         * If the one we want to shrink already is at PROGRESS_TRUNC, we
 
235
         * can shrink no more.  Return false.
 
236
         */
 
237
        if (prog->p_len == PROGRESS_TRUNC)
 
238
                return 0;
 
239
 
 
240
        prog->p_len--;
 
241
        return 1;
 
242
}
 
243
 
 
244
 
 
245
static unsigned int check_display(void)
 
246
{
 
247
        char *cols = getenv("COLUMNS");
 
248
        char *tmpbuf;
 
249
        unsigned int tmp, columns = DEFAULT_WIDTH;
 
250
 
 
251
        if (cols) {
 
252
                tmp = atoi(cols);
 
253
                if (tmp)
 
254
                        columns = tmp;
 
255
        }
 
256
 
 
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);
 
261
                if (tmpbuf) {
 
262
                        progbuf_len = tmp;
 
263
                        memset(tmpbuf, 0, tmp);
 
264
                        if (progbuf != static_progbuf)
 
265
                                free(progbuf);
 
266
                        progbuf = tmpbuf;
 
267
                        /*
 
268
                         * We just grew the buffer, so try long progress
 
269
                         * output again.
 
270
                         */
 
271
                        progress_length_reset();
 
272
                } else {
 
273
                        /*
 
274
                         * We couldn't allocate enough space, so report
 
275
                         * what we can actually use.
 
276
                         */
 
277
                        columns = progbuf_len - PROGBUF_EXTRA;
 
278
                }
 
279
        }
 
280
 
 
281
        return columns;
 
282
}
 
283
 
 
284
static size_t print_one_prog(struct tools_progress *prog, char *buf,
 
285
                             size_t len)
 
286
{
 
287
        int offset = 0;
 
288
        size_t ret;
 
289
 
 
290
        switch (prog->p_len) {
 
291
                case PROGRESS_LONG:
 
292
                        ret = snprintf(buf + offset, len - offset,
 
293
                                       "%s", prog->p_long_name);
 
294
                        break;
 
295
                case PROGRESS_SHORT:
 
296
                        ret = snprintf(buf + offset, len - offset,
 
297
                                       "%s", prog->p_short_name);
 
298
                        break;
 
299
                case PROGRESS_TRUNC:
 
300
                        ret = snprintf(buf + offset, len - offset,
 
301
                                       "%.*s", TRUNC_LEN,
 
302
                                       prog->p_short_name);
 
303
                        break;
 
304
                default:
 
305
                        assert(0);
 
306
                        break;
 
307
        }
 
308
        offset += ret;
 
309
 
 
310
        if (prog->p_count)
 
311
                ret = snprintf(buf + offset, len - offset,
 
312
                               " %3u%%", prog->p_percent);
 
313
        else
 
314
                ret = snprintf(buf + offset, len - offset, " %c",
 
315
                               spinner[prog->p_spinner_pos & 3]);
 
316
        offset += ret;
 
317
 
 
318
        return offset;
 
319
}
 
320
 
 
321
static void print_trailer(char *buf, size_t len)
 
322
{
 
323
        size_t ret;
 
324
        unsigned int offset = 0;
 
325
 
 
326
        ret = snprintf(buf + offset, len - offset, "%s",
 
327
                       PROGRESS_CLOSE);
 
328
        offset += ret;
 
329
        assert(offset <= len);
 
330
        ret = snprintf(buf + offset, len - offset, "%c", nextline);
 
331
        assert(ret < (len - offset));
 
332
}
 
333
 
 
334
static void progress_printf(unsigned int columns)
 
335
{
 
336
        unsigned int offset = 0;
 
337
        size_t ret;
 
338
        int first = 1;
 
339
        struct list_head *p;
 
340
        struct tools_progress *prog = NULL;
 
341
 
 
342
        if (list_empty(&progresses))
 
343
                return;
 
344
 
 
345
        list_for_each(p, &progresses) {
 
346
                prog = list_entry(p, struct tools_progress, p_list);
 
347
 
 
348
                if (first) {
 
349
                        ret = snprintf(progbuf + offset,
 
350
                                       columns - offset,
 
351
                                       "%s", PROGRESS_OPEN);
 
352
                        first = 0;
 
353
                } else
 
354
                        ret = snprintf(progbuf + offset,
 
355
                                       columns - offset,
 
356
                                       "%s", PROGRESS_SEP);
 
357
                offset += ret;
 
358
 
 
359
                offset += print_one_prog(prog, progbuf + offset,
 
360
                                         columns - offset);
 
361
                assert(offset < columns);
 
362
        }
 
363
 
 
364
        /*
 
365
         * From here on out, we use progbuf_len instead of columns.  Our
 
366
         * earlier calculations should have gotten this right.
 
367
         */
 
368
        assert(offset < columns);
 
369
        print_trailer(progbuf + offset, progbuf_len - offset);
 
370
}
 
371
 
 
372
static void truncate_printf(unsigned int columns)
 
373
{
 
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;
 
378
 
 
379
        if ((len + strlen(PROGRESS_CLOSE) + strlen(PROGRESS_ELIPS)) <=
 
380
            columns) {
 
381
                ret = snprintf(progbuf + offset, columns - offset, "%s",
 
382
                               PROGRESS_ELIPS);
 
383
                offset += ret;
 
384
                ret = print_one_prog(last, progbuf + offset,
 
385
                                     columns - offset);
 
386
                offset += ret;
 
387
                assert(offset < columns);
 
388
                print_trailer(progbuf + offset, progbuf_len - offset);
 
389
        } else {
 
390
                /* Give up, no progress */
 
391
                progbuf[0] = '\0';
 
392
        }
 
393
}
 
394
 
 
395
static void progress_compute(void)
 
396
{
 
397
        unsigned int columns = check_display();
 
398
        int truncate = 0;
 
399
 
 
400
        while (progress_length_check() > columns) {
 
401
                truncate = !progress_length_shrink();
 
402
                if (truncate)
 
403
                        break;
 
404
        }
 
405
 
 
406
        if (truncate)
 
407
                truncate_printf(columns);
 
408
        else
 
409
                progress_printf(columns);
 
410
}
 
411
 
 
412
static void progress_clear(void)
 
413
{
 
414
        unsigned int columns = check_display();
 
415
 
 
416
        memset(progbuf, ' ', columns);
 
417
        snprintf(progbuf + columns, progbuf_len - columns, "%c", nextline);
 
418
}
 
419
 
 
420
static void progress_write(void)
 
421
{
 
422
        printf("%s", progbuf);
 
423
}
 
424
 
 
425
static void tools_progress_free(struct tools_progress *prog)
 
426
{
 
427
        if (prog->p_long_name)
 
428
                free(prog->p_long_name);
 
429
        if (prog->p_short_name)
 
430
                free(prog->p_short_name);
 
431
        free(prog);
 
432
}
 
433
 
 
434
static struct tools_progress *tools_progress_alloc(const char *long_name,
 
435
                                                   const char *short_name,
 
436
                                                   uint64_t count)
 
437
{
 
438
        struct tools_progress *prog;
 
439
 
 
440
        prog = malloc(sizeof(struct tools_progress));
 
441
        if (!prog)
 
442
                goto out;
 
443
 
 
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);
 
449
                prog = NULL;
 
450
                goto out;
 
451
        }
 
452
 
 
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;
 
456
 
 
457
out:
 
458
        return prog;
 
459
}
 
460
 
 
461
 
 
462
/*
 
463
 * API for libtools-internal only
 
464
 */
 
465
 
 
466
void tools_progress_clear(void)
 
467
{
 
468
        if (!progress_on)
 
469
                return;
 
470
 
 
471
        if (list_empty(&progresses))
 
472
                return;
 
473
 
 
474
        /*
 
475
         * We only need to wipe the line if are doing terminal-based
 
476
         * progress.
 
477
         */
 
478
        if (nextline != '\r')
 
479
                return;
 
480
 
 
481
        progress_clear();
 
482
        progress_write();
 
483
}
 
484
 
 
485
void tools_progress_restore(void)
 
486
{
 
487
        if (!progress_on)
 
488
                return;
 
489
 
 
490
        /* Same here */
 
491
        if (list_empty(&progresses))
 
492
                return;
 
493
        if (nextline != '\r')
 
494
                return;
 
495
 
 
496
        progress_compute();
 
497
        progress_write();
 
498
}
 
499
 
 
500
int tools_progress_enabled(void)
 
501
{
 
502
        return progress_on;
 
503
}
 
504
 
 
505
 
 
506
/*
 
507
 * Public API
 
508
 */
 
509
 
 
510
void tools_progress_enable(void)
 
511
{
 
512
        progress_on = 1;
 
513
 
 
514
        if (!list_empty(&progresses))
 
515
                return;
 
516
 
 
517
        if (isatty(STDOUT_FILENO))
 
518
                nextline = '\r';
 
519
        else
 
520
                nextline = '\n';
 
521
 
 
522
}
 
523
 
 
524
void tools_progress_disable(void)
 
525
{
 
526
        progress_on = 0;
 
527
}
 
528
 
 
529
struct tools_progress *tools_progress_start(const char *long_name,
 
530
                                            const char *short_name,
 
531
                                            uint64_t count)
 
532
{
 
533
        struct tools_progress *prog;
 
534
 
 
535
        if (!progress_on)
 
536
                return &disabled_prog;
 
537
 
 
538
        prog = tools_progress_alloc(long_name, short_name, count);
 
539
        if (!prog)
 
540
                goto out;
 
541
 
 
542
        list_add_tail(&prog->p_list, &progresses);
 
543
        tools_progress_clear();
 
544
        progress_length_reset();
 
545
        progress_compute();
 
546
        progress_write();
 
547
 
 
548
out:
 
549
        return prog;
 
550
}
 
551
 
 
552
void tools_progress_step(struct tools_progress *prog, unsigned int step)
 
553
{
 
554
        if (prog == &disabled_prog)
 
555
                return;
 
556
 
 
557
        prog->p_current += step;
 
558
 
 
559
        if (!check_percent(prog))
 
560
                return;
 
561
        if (!check_tick() && (prog->p_percent != 100) &&
 
562
            (!prog->p_count || (prog->p_percent != 0)))
 
563
                return;
 
564
 
 
565
        if (!prog->p_count)
 
566
                step_spinner(prog);
 
567
 
 
568
        progress_compute();
 
569
        progress_write();
 
570
}
 
571
 
 
572
void tools_progress_stop(struct tools_progress *prog)
 
573
{
 
574
        if (prog == &disabled_prog)
 
575
                return;
 
576
 
 
577
        tools_progress_clear();
 
578
 
 
579
        list_del(&prog->p_list);
 
580
        tools_progress_free(prog);
 
581
 
 
582
        if (!list_empty(&progresses)) {
 
583
                progress_length_reset();
 
584
                tools_progress_restore();
 
585
        }
 
586
}
 
587
 
 
588
#ifdef DEBUG_EXE
 
589
#include <time.h>
 
590
 
 
591
static int run_steps(const char *ln, const char *sn, int count,
 
592
                     int (*func)(void))
 
593
{
 
594
        int i, ret = 0;
 
595
        struct tools_progress *prog;
 
596
        struct timespec ts = {
 
597
                .tv_nsec = 100000000,
 
598
        };
 
599
 
 
600
        prog = tools_progress_start(ln, sn, count > 0 ? count : 0);
 
601
        if (!prog)
 
602
                return 1;
 
603
 
 
604
        if (count < 0)
 
605
                count = -count;
 
606
        for (i = 0; i < count; i++) {
 
607
                if (func)
 
608
                        ret = func();
 
609
                if (ret)
 
610
                        break;
 
611
                tools_progress_step(prog, 1);
 
612
                nanosleep(&ts, NULL);
 
613
        }
 
614
        tools_progress_stop(prog);
 
615
 
 
616
        return ret;
 
617
}
 
618
 
 
619
static int middle(void)
 
620
{
 
621
        static int try = 0;
 
622
        char lbuf[100], sbuf[100];
 
623
 
 
624
        try++;
 
625
        snprintf(lbuf, 100, "This is middle %d", try);
 
626
        snprintf(sbuf, 100, "middle%d", try);
 
627
        return run_steps(lbuf, sbuf, -7, NULL);
 
628
}
 
629
 
 
630
static int outer(void)
 
631
{
 
632
        static int try = 0;
 
633
        char lbuf[100], sbuf[100];
 
634
 
 
635
        try++;
 
636
        snprintf(lbuf, 100, "This is outer %d", try);
 
637
        snprintf(sbuf, 100, "outer%d", try);
 
638
        return run_steps(lbuf, sbuf, 10, middle);
 
639
}
 
640
 
 
641
int main(int argc, char *argv[])
 
642
{
 
643
        setbuf(stdout, NULL);
 
644
        setbuf(stderr, NULL);
 
645
        tools_progress_enable();
 
646
        return run_steps("This is a test", "thisis", 5, outer);
 
647
}
 
648
#endif