| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2018 Oracle. All Rights Reserved. |
| * Author: Darrick J. Wong <darrick.wong@oracle.com> |
| */ |
| #include "xfs.h" |
| #include <dirent.h> |
| #include <pthread.h> |
| #include <sys/statvfs.h> |
| #include <time.h> |
| #include "libfrog/paths.h" |
| #include "disk.h" |
| #include "read_verify.h" |
| #include "xfs_scrub.h" |
| #include "common.h" |
| #include "counter.h" |
| #include "progress.h" |
| |
| /* |
| * Progress Tracking |
| * |
| * For scrub phases that expect to take a long time, this facility uses |
| * the threaded counter and some phase/state information to report the |
| * progress of a particular phase to stdout. Each phase that wants |
| * progress information needs to set up the tracker with an estimate of |
| * the work to be done and periodic updates when work items finish. In |
| * return, the progress tracker will print a pretty progress bar and |
| * twiddle to a tty, or a raw numeric output compatible with fsck -C. |
| */ |
| struct progress_tracker { |
| FILE *fp; |
| const char *tag; |
| struct ptcounter *ptc; |
| uint64_t max; |
| unsigned int phase; |
| int rshift; |
| int twiddle; |
| bool isatty; |
| bool terminate; |
| pthread_t thread; |
| |
| /* static state */ |
| pthread_mutex_t lock; |
| pthread_cond_t wakeup; |
| }; |
| |
| static struct progress_tracker pt = { |
| .lock = PTHREAD_MUTEX_INITIALIZER, |
| .wakeup = PTHREAD_COND_INITIALIZER, |
| }; |
| |
| /* Add some progress. */ |
| void |
| progress_add( |
| uint64_t x) |
| { |
| if (pt.fp) |
| ptcounter_add(pt.ptc, x); |
| } |
| |
| static const char twiddles[] = "|/-\\"; |
| |
| static void |
| progress_report( |
| uint64_t sum) |
| { |
| char buf[81]; |
| int tag_len; |
| int num_len; |
| int pbar_len; |
| int plen; |
| |
| if (!pt.fp) |
| return; |
| |
| if (sum > pt.max) |
| sum = pt.max; |
| |
| /* Emulate fsck machine-readable output (phase, cur, max, label) */ |
| if (!pt.isatty) { |
| snprintf(buf, sizeof(buf), _("%u %"PRIu64" %"PRIu64" %s"), |
| pt.phase, sum, pt.max, pt.tag); |
| fprintf(pt.fp, "%s\n", buf); |
| fflush(pt.fp); |
| return; |
| } |
| |
| /* Interactive twiddle progress bar. */ |
| if (debug) { |
| num_len = snprintf(buf, sizeof(buf), |
| "%c %"PRIu64"/%"PRIu64" (%.1f%%)", |
| twiddles[pt.twiddle], |
| sum >> pt.rshift, |
| pt.max >> pt.rshift, |
| 100.0 * sum / pt.max); |
| } else { |
| num_len = snprintf(buf, sizeof(buf), |
| "%c (%.1f%%)", |
| twiddles[pt.twiddle], |
| 100.0 * sum / pt.max); |
| } |
| memmove(buf + sizeof(buf) - (num_len + 1), buf, num_len + 1); |
| tag_len = snprintf(buf, sizeof(buf), _("Phase %u: |"), pt.phase); |
| pbar_len = sizeof(buf) - (num_len + 1 + tag_len); |
| plen = (int)((double)pbar_len * sum / pt.max); |
| memset(buf + tag_len, '=', plen); |
| memset(buf + tag_len + plen, ' ', pbar_len - plen); |
| pt.twiddle = (pt.twiddle + 1) % 4; |
| fprintf(pt.fp, "%c%s\r%c", START_IGNORE, buf, END_IGNORE); |
| fflush(pt.fp); |
| } |
| |
| static void * |
| progress_report_thread(void *arg) |
| { |
| struct timespec abstime; |
| int ret; |
| |
| rcu_register_thread(); |
| pthread_mutex_lock(&pt.lock); |
| while (1) { |
| uint64_t progress_val; |
| |
| /* Every half second. */ |
| ret = clock_gettime(CLOCK_REALTIME, &abstime); |
| if (ret) |
| break; |
| abstime.tv_nsec += NSEC_PER_SEC / 2; |
| if (abstime.tv_nsec > NSEC_PER_SEC) { |
| abstime.tv_sec++; |
| abstime.tv_nsec -= NSEC_PER_SEC; |
| } |
| ret = pthread_cond_timedwait(&pt.wakeup, &pt.lock, &abstime); |
| if (ret && ret != ETIMEDOUT) |
| break; |
| if (pt.terminate) |
| break; |
| ret = ptcounter_value(pt.ptc, &progress_val); |
| if (!ret) |
| progress_report(progress_val); |
| } |
| pthread_mutex_unlock(&pt.lock); |
| rcu_unregister_thread(); |
| return NULL; |
| } |
| |
| /* End a phase of progress reporting. */ |
| void |
| progress_end_phase(void) |
| { |
| if (!pt.fp) |
| return; |
| |
| pthread_mutex_lock(&pt.lock); |
| pt.terminate = true; |
| pthread_mutex_unlock(&pt.lock); |
| pthread_cond_broadcast(&pt.wakeup); |
| pthread_join(pt.thread, NULL); |
| |
| progress_report(pt.max); |
| ptcounter_free(pt.ptc); |
| pt.max = 0; |
| pt.ptc = NULL; |
| if (pt.fp) { |
| fprintf(pt.fp, CLEAR_EOL); |
| fflush(pt.fp); |
| } |
| pt.fp = NULL; |
| } |
| |
| /* |
| * Set ourselves up to report progress. If errors are encountered, this |
| * function will log them and return nonzero. |
| */ |
| int |
| progress_init_phase( |
| struct scrub_ctx *ctx, |
| FILE *fp, |
| unsigned int phase, |
| uint64_t max, |
| int rshift, |
| unsigned int nr_threads) |
| { |
| int ret; |
| |
| assert(pt.fp == NULL); |
| if (fp == NULL || max == 0) { |
| pt.fp = NULL; |
| return 0; |
| } |
| pt.fp = fp; |
| pt.isatty = isatty(fileno(fp)); |
| pt.tag = ctx->mntpoint; |
| pt.max = max; |
| pt.phase = phase; |
| pt.rshift = rshift; |
| pt.twiddle = 0; |
| pt.terminate = false; |
| |
| ret = ptcounter_alloc(nr_threads, &pt.ptc); |
| if (ret) { |
| str_liberror(ctx, ret, _("allocating progress counter")); |
| goto out_max; |
| } |
| |
| ret = pthread_create(&pt.thread, NULL, progress_report_thread, NULL); |
| if (ret) { |
| str_liberror(ctx, ret, _("creating progress reporting thread")); |
| goto out_ptcounter; |
| } |
| |
| return 0; |
| |
| out_ptcounter: |
| ptcounter_free(pt.ptc); |
| pt.ptc = NULL; |
| out_max: |
| pt.max = 0; |
| pt.fp = NULL; |
| return ret; |
| } |