/* * cryptsetup - progress output utilities * * Copyright (C) 2009-2023 Red Hat, Inc. All rights reserved. * Copyright (C) 2009-2023 Milan Broz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include "cryptsetup.h" #define MINUTES_90 UINT64_C(5400000000) /* 90 minutes in microseconds */ #define HOURS_36 UINT64_C(129600000000) /* 36 hours in microseconds */ #define MINUTES(A) (A) / UINT64_C(60000000) /* microseconds to minutes */ #define SECONDS(A) (A) / UINT64_C(1000000) /* microseconds to seconds */ #define HOURS(A) (A) / UINT64_C(3600000000) /* microseconds to hours */ #define DAYS(A) (A) / UINT64_C(86400000000) /* microseconds to days */ #define REMAIN_SECONDS(A) (SECONDS((A))) % 60 #define REMAIN_MINUTES(A) (MINUTES((A))) % 60 /* The difference in microseconds between two times in "timeval" format. */ static uint64_t time_diff(struct timeval *start, struct timeval *end) { return (end->tv_sec - start->tv_sec) * UINT64_C(1000000) + (end->tv_usec - start->tv_usec); } static void tools_clear_line(void) { /* vt100 code clear line */ log_std("\33[2K\r"); } static void bytes_to_units(uint64_t *bytes, const char **units) { if (*bytes < (UINT64_C(1) << 32)) { /* less than 4 GiBs */ *units = "MiB"; *bytes >>= 20; } else if (*bytes < (UINT64_C(1) << 42)) { /* less than 4 TiBs */ *units = "GiB"; *bytes >>= 30; } else if (*bytes < (UINT64_C(1) << 52)) { /* less than 4 PiBs */ *units = "TiB"; *bytes >>= 40; } else if (*bytes < (UINT64_C(1) << 62)) { /* less than 4 EiBs */ *units = "PiB"; *bytes >>= 50; } else { *units = "EiB"; *bytes >>= 60; } } static bool time_to_human_string(uint64_t usecs, char *buf, size_t buf_len) { ssize_t r; if (usecs < MINUTES_90) r = snprintf(buf, buf_len, _("%02" PRIu64 "m%02" PRIu64 "s"), MINUTES(usecs), REMAIN_SECONDS(usecs)); else if (usecs < HOURS_36) r = snprintf(buf, buf_len, _("%02" PRIu64 "h%02" PRIu64 "m%02" PRIu64 "s"), HOURS(usecs), REMAIN_MINUTES(usecs), REMAIN_SECONDS(usecs)); else r = snprintf(buf, buf_len, _("%02" PRIu64 " days"), DAYS(usecs)); if (r < 0 || (size_t)r >= buf_len) return false; return true; } static void log_progress(uint64_t bytes, uint64_t device_size, uint64_t eta, double uib, const char *ustr, const char *eol) { double progress; int r; const char *units; char time[128], written[128], speed[128]; /* * TRANSLATORS: 'time' string with examples: * "12m44s" : meaning 12 minutes 44 seconds * "26h12m44s" : meaning 26 hours 12 minutes 44 seconds * "3 days" */ if (!time_to_human_string(eta, time, sizeof(time))) return; progress = (double)bytes / device_size * 100.0; bytes_to_units(&bytes, &units); r = snprintf(written, sizeof(written), _("%4" PRIu64 " %s written"), bytes, units); if (r < 0 || (size_t)r >= sizeof(written)) return; r = snprintf(speed, sizeof(speed), _("speed %5.1f %s/s"), uib, ustr); if (r < 0 || (size_t)r >= sizeof(speed)) return; /* * TRANSLATORS: 'time', 'written' and 'speed' string are supposed * to get translated as well. 'eol' is always new-line or empty. * See above. */ log_std(_("Progress: %5.1f%%, ETA %s, %s, %s%s"), progress, time, written, speed, eol); } static void log_progress_final(uint64_t time_spent, uint64_t bytes, double uib, const char *ustr) { int r; const char *units; char time[128], written[128], speed[128]; /* * TRANSLATORS: 'time' string with examples: * "12m44s" : meaning 12 minutes 44 seconds * "26h12m44s" : meaning 26 hours 12 minutes 44 seconds * "3 days" */ if (!time_to_human_string(time_spent, time, sizeof(time))) return; bytes_to_units(&bytes, &units); r = snprintf(written, sizeof(written) - 1, _("%4" PRIu64 " %s written"), bytes, units); if (r < 0 || (size_t)r >= sizeof(written)) return; r = snprintf(speed, sizeof(speed) - 1, _("speed %5.1f %s/s"), uib, ustr); if (r < 0 || (size_t)r >= sizeof(speed)) return; /* * TRANSLATORS: 'time', 'written' and 'speed' string are supposed * to get translated as well. See above */ log_std(_("Finished, time %s, %s, %s\n"), time, written, speed); } static bool calculate_tdiff(bool final, uint64_t bytes, struct tools_progress_params *parms, double *r_tdiff) { uint64_t frequency; struct timeval now_time; assert(r_tdiff); gettimeofday(&now_time, NULL); if (parms->start_time.tv_sec == 0 && parms->start_time.tv_usec == 0) { parms->start_time = now_time; parms->end_time = now_time; parms->start_offset = bytes; return false; } if (parms->frequency) frequency = parms->frequency * UINT64_C(1000000); else frequency = 500000; if (!final && time_diff(&parms->end_time, &now_time) < frequency) return false; parms->end_time = now_time; *r_tdiff = time_diff(&parms->start_time, &parms->end_time) / 1E6; if (!*r_tdiff) return false; return true; } static void tools_time_progress(uint64_t device_size, uint64_t bytes, struct tools_progress_params *parms) { uint64_t eta; double tdiff, uib; const char *eol, *ustr; bool final = (bytes == device_size); if (!calculate_tdiff(final, bytes, parms, &tdiff)) return; if (parms->frequency) eol = "\n"; else eol = ""; uib = (double)(bytes - parms->start_offset) / tdiff; eta = (uint64_t)((device_size / uib - tdiff) * 1E6); if (uib > 1073741824.0f) { uib /= 1073741824.0f; ustr = "GiB"; } else if (uib > 1048576.0f) { uib /= 1048576.0f; ustr = "MiB"; } else if (uib > 1024.0f) { uib /= 1024.0f; ustr = "KiB"; } else ustr = "B"; if (!parms->frequency) tools_clear_line(); if (final) log_progress_final((uint64_t)(tdiff * 1E6), bytes, uib, ustr); else log_progress(bytes, device_size, eta, uib, ustr, eol); fflush(stdout); } static void log_progress_json(const char *device, uint64_t bytes, uint64_t device_size, uint64_t eta, uint64_t uib, uint64_t time_spent) { int r; char json[PATH_MAX+256]; r = snprintf(json, sizeof(json) - 1, "{\"device\":\"%s\"," "\"device_bytes\":\"%" PRIu64 "\"," /* in bytes */ "\"device_size\":\"%" PRIu64 "\"," /* in bytes */ "\"speed\":\"%" PRIu64 "\"," /* in bytes per second */ "\"eta_ms\":\"%" PRIu64 "\"," /* in milliseconds */ "\"time_ms\":\"%" PRIu64 "\"}\n", /* in milliseconds */ device, bytes, device_size, uib, eta, time_spent); if (r < 0 || (size_t)r >= sizeof(json) - 1) return; log_std("%s", json); } static void tools_time_progress_json(uint64_t device_size, uint64_t bytes, struct tools_progress_params *parms) { double tdiff, uib; bool final = (bytes == device_size); if (!calculate_tdiff(final, bytes, parms, &tdiff)) return; uib = (double)(bytes - parms->start_offset) / tdiff; log_progress_json(parms->device, bytes, device_size, final ? UINT64_C(0) : (uint64_t)((device_size / uib - tdiff) * 1E3), (uint64_t)uib, (uint64_t)(tdiff * 1E3)); fflush(stdout); } int tools_progress(uint64_t size, uint64_t offset, void *usrptr) { int r = 0; struct tools_progress_params *parms = (struct tools_progress_params *)usrptr; if (parms && parms->json_output) tools_time_progress_json(size, offset, parms); else if (parms && !parms->batch_mode) tools_time_progress(size, offset, parms); check_signal(&r); if (r) { if (!parms || (!parms->frequency && !parms->json_output)) tools_clear_line(); if (parms && parms->interrupt_message) log_err("%s", parms->interrupt_message); } return r; } const char *tools_get_device_name(const char *device, char **r_backing_file) { char *bfile; assert(r_backing_file); bfile = crypt_loop_backing_file(device); if (bfile) { *r_backing_file = bfile; return bfile; } return device; }