summaryrefslogtreecommitdiffstats
path: root/utility.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--utility.cpp920
1 files changed, 920 insertions, 0 deletions
diff --git a/utility.cpp b/utility.cpp
new file mode 100644
index 0000000..505e4f0
--- /dev/null
+++ b/utility.cpp
@@ -0,0 +1,920 @@
+/*
+ * utility.cpp
+ *
+ * Home page of code is: https://www.smartmontools.org
+ *
+ * Copyright (C) 2002-12 Bruce Allen
+ * Copyright (C) 2008-23 Christian Franke
+ * Copyright (C) 2000 Michael Cornwell <cornwell@acm.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+// THIS FILE IS INTENDED FOR UTILITY ROUTINES THAT ARE APPLICABLE TO
+// BOTH SCSI AND ATA DEVICES, AND THAT MAY BE USED IN SMARTD,
+// SMARTCTL, OR BOTH.
+
+#include "config.h"
+#define __STDC_FORMAT_MACROS 1 // enable PRI* for C++
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <sys/stat.h>
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+#ifdef _WIN32
+#include <mbstring.h> // _mbsinc()
+#endif
+
+#include <stdexcept>
+
+#include "svnversion.h"
+#include "utility.h"
+
+#include "atacmds.h"
+#include "dev_interface.h"
+#include "sg_unaligned.h"
+
+#ifndef USE_CLOCK_MONOTONIC
+#ifdef __MINGW32__
+// If MinGW-w64 < 9.0.0 or Windows < 8, GetSystemTimeAsFileTime() is used for
+// std::chrono::high_resolution_clock. This provides only 1/64s (>15ms) resolution.
+// CLOCK_MONOTONIC uses QueryPerformanceCounter() which provides <1us resolution.
+#define USE_CLOCK_MONOTONIC 1
+#else
+// Use std::chrono::high_resolution_clock.
+#include <chrono>
+#define USE_CLOCK_MONOTONIC 0
+#endif
+#endif // USE_CLOCK_MONOTONIC
+
+const char * utility_cpp_cvsid = "$Id: utility.cpp 5519 2023-07-24 15:57:54Z chrfranke $"
+ UTILITY_H_CVSID;
+
+const char * packet_types[] = {
+ "Direct-access (disk)",
+ "Sequential-access (tape)",
+ "Printer",
+ "Processor",
+ "Write-once (optical disk)",
+ "CD/DVD",
+ "Scanner",
+ "Optical memory (optical disk)",
+ "Medium changer",
+ "Communications",
+ "Graphic arts pre-press (10)",
+ "Graphic arts pre-press (11)",
+ "Array controller",
+ "Enclosure services",
+ "Reduced block command (simplified disk)",
+ "Optical card reader/writer"
+};
+
+// BUILD_INFO can be provided by package maintainers
+#ifndef BUILD_INFO
+#define BUILD_INFO "(local build)"
+#endif
+
+// Make version information string
+// lines: 1: version only, 2: version+copyright, >=3: full information
+std::string format_version_info(const char * prog_name, int lines /* = 2 */)
+{
+ std::string info = strprintf(
+ "%s "
+#ifndef SMARTMONTOOLS_RELEASE_DATE
+ "pre-"
+#endif
+ PACKAGE_VERSION " "
+#ifdef SMARTMONTOOLS_SVN_REV
+ SMARTMONTOOLS_SVN_DATE " r" SMARTMONTOOLS_SVN_REV
+#else
+ "(build date " __DATE__ ")" // checkout without expansion of Id keywords
+#endif
+ " [%s] " BUILD_INFO "\n",
+ prog_name, smi()->get_os_version_str().c_str()
+ );
+ if (lines <= 1)
+ return info;
+
+ info += "Copyright (C) 2002-23, Bruce Allen, Christian Franke, www.smartmontools.org\n";
+ if (lines == 2)
+ return info;
+
+ info += "\n";
+ info += prog_name;
+ info += " comes with ABSOLUTELY NO WARRANTY. This is free\n"
+ "software, and you are welcome to redistribute it under\n"
+ "the terms of the GNU General Public License; either\n"
+ "version 2, or (at your option) any later version.\n"
+ "See https://www.gnu.org for further details.\n"
+ "\n"
+#ifndef SMARTMONTOOLS_RELEASE_DATE
+ "smartmontools pre-release " PACKAGE_VERSION "\n"
+#else
+ "smartmontools release " PACKAGE_VERSION
+ " dated " SMARTMONTOOLS_RELEASE_DATE " at " SMARTMONTOOLS_RELEASE_TIME "\n"
+#endif
+#ifdef SMARTMONTOOLS_SVN_REV
+ "smartmontools SVN rev " SMARTMONTOOLS_SVN_REV
+ " dated " SMARTMONTOOLS_SVN_DATE " at " SMARTMONTOOLS_SVN_TIME "\n"
+#else
+ "smartmontools SVN rev is unknown\n"
+#endif
+ "smartmontools build host: " SMARTMONTOOLS_BUILD_HOST "\n"
+ "smartmontools build with: "
+
+#define N2S_(s) #s
+#define N2S(s) N2S_(s)
+#if __cplusplus == 202002
+ "C++20"
+#elif __cplusplus == 201703
+ "C++17"
+#elif __cplusplus == 201402
+ "C++14"
+#elif __cplusplus == 201103
+ "C++11"
+#else
+ "C++(" N2S(__cplusplus) ")"
+#endif
+#undef N2S
+#undef N2S_
+
+#if defined(__GNUC__) && defined(__VERSION__) // works also with CLang
+ ", GCC " __VERSION__
+#endif
+#ifdef __MINGW64_VERSION_STR
+ ", MinGW-w64 " __MINGW64_VERSION_STR
+#endif
+ "\n"
+ "smartmontools configure arguments:"
+#ifdef SOURCE_DATE_EPOCH
+ " [hidden in reproducible builds]\n"
+ "reproducible build SOURCE_DATE_EPOCH: "
+#endif
+ ;
+#ifdef SOURCE_DATE_EPOCH
+ char ts[32]; struct tm tmbuf;
+ strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", time_to_tm_local(&tmbuf, SOURCE_DATE_EPOCH));
+ info += strprintf("%u (%s)", (unsigned)SOURCE_DATE_EPOCH, ts);
+#else
+ info += (sizeof(SMARTMONTOOLS_CONFIGURE_ARGS) > 1 ?
+ SMARTMONTOOLS_CONFIGURE_ARGS : " [no arguments given]");
+#endif
+ info += '\n';
+
+ return info;
+}
+
+// Solaris only: Get site-default timezone. This is called from
+// UpdateTimezone() when TZ environment variable is unset at startup.
+#if defined (__SVR4) && defined (__sun)
+static const char *TIMEZONE_FILE = "/etc/TIMEZONE";
+
+static char *ReadSiteDefaultTimezone(){
+ FILE *fp;
+ char buf[512], *tz;
+ int n;
+
+ tz = NULL;
+ fp = fopen(TIMEZONE_FILE, "r");
+ if(fp == NULL) return NULL;
+ while(fgets(buf, sizeof(buf), fp)) {
+ if (strncmp(buf, "TZ=", 3)) // searches last "TZ=" line
+ continue;
+ n = strlen(buf) - 1;
+ if (buf[n] == '\n') buf[n] = 0;
+ if (tz) free(tz);
+ tz = strdup(buf);
+ }
+ fclose(fp);
+ return tz;
+}
+#endif
+
+// Make sure that this executable is aware if the user has changed the
+// time-zone since the last time we polled devices. The canonical
+// example is a user who starts smartd on a laptop, then flies across
+// time-zones with a laptop, and then changes the timezone, WITHOUT
+// restarting smartd. This is a work-around for a bug in
+// GLIBC. Yuk. See bug number 48184 at http://bugs.debian.org and
+// thanks to Ian Redfern for posting a workaround.
+
+// Please refer to the smartd manual page, in the section labeled LOG
+// TIMESTAMP TIMEZONE.
+void FixGlibcTimeZoneBug(){
+#if __GLIBC__
+ if (!getenv("TZ")) {
+ putenv((char *)"TZ=GMT"); // POSIX prototype is 'int putenv(char *)'
+ tzset();
+ putenv((char *)"TZ");
+ tzset();
+ }
+#elif _WIN32
+ const char * tz = getenv("TZ");
+ if (!tz) {
+ putenv("TZ=GMT");
+ tzset();
+ putenv("TZ="); // empty value removes TZ, putenv("TZ") does nothing
+ tzset();
+ }
+ else {
+ static const regular_expression tzrex("[A-Z]{3}[-+]?[0-9]+([A-Z]{3,4})?");
+ // tzset() from MSVCRT only supports the above basic syntax of TZ.
+ // Otherwise the timezone settings are set to bogus values.
+ // Unset TZ and revert to system default timezone in these cases.
+ if (!tzrex.full_match(tz)) {
+ putenv("TZ=");
+ tzset();
+ }
+ }
+#elif defined (__SVR4) && defined (__sun)
+ // In Solaris, putenv("TZ=") sets null string and invalid timezone.
+ // putenv("TZ") does nothing. With invalid TZ, tzset() do as if
+ // TZ=GMT. With TZ unset, /etc/TIMEZONE will be read only _once_ at
+ // first tzset() call. Conclusion: Unlike glibc, dynamic
+ // configuration of timezone can be done only by changing actual
+ // value of TZ environment value.
+ enum tzstate { NOT_CALLED_YET, USER_TIMEZONE, TRACK_TIMEZONE };
+ static enum tzstate state = NOT_CALLED_YET;
+
+ static struct stat prev_stat;
+ static char *prev_tz;
+ struct stat curr_stat;
+ char *curr_tz;
+
+ if(state == NOT_CALLED_YET) {
+ if(getenv("TZ")) {
+ state = USER_TIMEZONE; // use supplied timezone
+ } else {
+ state = TRACK_TIMEZONE;
+ if(stat(TIMEZONE_FILE, &prev_stat)) {
+ state = USER_TIMEZONE; // no TZ, no timezone file; use GMT forever
+ } else {
+ prev_tz = ReadSiteDefaultTimezone(); // track timezone file change
+ if(prev_tz) putenv(prev_tz);
+ }
+ }
+ tzset();
+ } else if(state == TRACK_TIMEZONE) {
+ if(stat(TIMEZONE_FILE, &curr_stat) == 0
+ && (curr_stat.st_ctime != prev_stat.st_ctime
+ || curr_stat.st_mtime != prev_stat.st_mtime)) {
+ // timezone file changed
+ curr_tz = ReadSiteDefaultTimezone();
+ if(curr_tz) {
+ putenv(curr_tz);
+ if(prev_tz) free(prev_tz);
+ prev_tz = curr_tz; prev_stat = curr_stat;
+ }
+ }
+ tzset();
+ }
+#endif
+ // OTHER OS/LIBRARY FIXES SHOULD GO HERE, IF DESIRED. PLEASE TRY TO
+ // KEEP THEM INDEPENDENT.
+ return;
+}
+
+#ifdef _WIN32
+// Fix strings in tzname[] to avoid long names with non-ascii characters.
+// If TZ is not set, tzset() in the MSVC runtime sets tzname[] to the
+// national language timezone names returned by GetTimezoneInformation().
+static char * fixtzname(char * dest, int destsize, const char * src)
+{
+ int i = 0, j = 0;
+ while (src[i] && j < destsize-1) {
+ int i2 = (const char *)_mbsinc((const unsigned char *)src+i) - src;
+ if (i2 > i+1)
+ i = i2; // Ignore multibyte chars
+ else {
+ if ('A' <= src[i] && src[i] <= 'Z')
+ dest[j++] = src[i]; // "Pacific Standard Time" => "PST"
+ i++;
+ }
+ }
+ if (j < 2)
+ j = 0;
+ dest[j] = 0;
+ return dest;
+}
+#endif // _WIN32
+
+// This value follows the peripheral device type value as defined in
+// SCSI Primary Commands, ANSI INCITS 301:1997. It is also used in
+// the ATA standard for packet devices to define the device type.
+const char *packetdevicetype(int type){
+ if (type<0x10)
+ return packet_types[type];
+
+ if (type<0x20)
+ return "Reserved";
+
+ return "Unknown";
+}
+
+// Convert time to broken-down local time, throw on error.
+struct tm * time_to_tm_local(struct tm * tp, time_t t)
+{
+#ifndef _WIN32
+ // POSIX, C23 - missing in MSVRCT, C++ and older C.
+ if (!localtime_r(&t, tp))
+ throw std::runtime_error("localtime_r() failed");
+#elif 0 // defined(__STDC_LIB_EXT1__)
+ // C11 - requires #define __STDC_WANT_LIB_EXT1__ before <time.h>.
+ // Missing in POSIX and C++, MSVCRT variant differs.
+ if (!localtime_s(&t, tp))
+ throw std::runtime_error("localtime_s() failed");
+#else
+ // MSVCRT - 64-bit variant avoids conflict with the above C11 variant.
+ __time64_t t64 = t;
+ if (_localtime64_s(tp, &t64))
+ throw std::runtime_error("_localtime64_s() failed");
+#endif
+ return tp;
+}
+
+// Utility function prints date and time and timezone into a character
+// buffer of length 64. All the fuss is needed to get the right
+// timezone info (sigh).
+void dateandtimezoneepoch(char (& buffer)[DATEANDEPOCHLEN], time_t tval)
+{
+ FixGlibcTimeZoneBug();
+
+ // Get the time structure. We need this to determine if we are in
+ // daylight savings time or not.
+ struct tm tmbuf, * tmval = time_to_tm_local(&tmbuf, tval);
+
+ // Convert to an ASCII string, put in datebuffer.
+ // Same as: strftime(datebuffer, sizeof(datebuffer), "%a %b %e %H:%M:%S %Y\n"),
+ // but always in "C" locale.
+ char datebuffer[32];
+ STATIC_ASSERT(sizeof(datebuffer) >= 26); // assumed by asctime_r()
+#ifndef _WIN32
+ // POSIX (missing in MSVRCT, C and C++)
+ if (!asctime_r(tmval, datebuffer))
+ throw std::runtime_error("asctime_r() failed");
+#else
+ // MSVCRT, C11 (missing in POSIX)
+ if (asctime_s(datebuffer, sizeof(datebuffer), tmval))
+ throw std::runtime_error("asctime_s() failed");
+#endif
+
+ // Remove newline
+ int lenm1 = strlen(datebuffer) - 1;
+ datebuffer[lenm1>=0?lenm1:0]='\0';
+
+#if defined(_WIN32) && defined(_MSC_VER)
+ // tzname is missing in MSVC14
+ #define tzname _tzname
+#endif
+
+ // correct timezone name
+ const char * timezonename;
+ if (tmval->tm_isdst==0)
+ // standard time zone
+ timezonename=tzname[0];
+ else if (tmval->tm_isdst>0)
+ // daylight savings in effect
+ timezonename=tzname[1];
+ else
+ // unable to determine if daylight savings in effect
+ timezonename="";
+
+#ifdef _WIN32
+ // Fix long non-ascii timezone names
+ // cppcheck-suppress variableScope
+ char tzfixbuf[6+1] = "";
+ if (!getenv("TZ"))
+ timezonename=fixtzname(tzfixbuf, sizeof(tzfixbuf), timezonename);
+#endif
+
+ // Finally put the information into the buffer as needed.
+ snprintf(buffer, DATEANDEPOCHLEN, "%s %s", datebuffer, timezonename);
+
+ return;
+}
+
+// A replacement for perror() that sends output to our choice of
+// printing. If errno not set then just print message.
+void syserror(const char *message){
+
+ if (errno) {
+ // Get the correct system error message:
+ const char *errormessage=strerror(errno);
+
+ // Check that caller has handed a sensible string, and provide
+ // appropriate output. See perror(3) man page to understand better.
+ if (message && *message)
+ pout("%s: %s\n",message, errormessage);
+ else
+ pout("%s\n",errormessage);
+ }
+ else if (message && *message)
+ pout("%s\n",message);
+
+ return;
+}
+
+// Check regular expression for non-portable features.
+//
+// POSIX extended regular expressions interpret unmatched ')' ordinary:
+// "The close-parenthesis shall be considered special in this context
+// only if matched with a preceding open-parenthesis."
+//
+// GNU libc and BSD libc support unmatched ')', Cygwin reports an error.
+//
+// POSIX extended regular expressions do not define empty subexpressions:
+// "A vertical-line appearing first or last in an ERE, or immediately following
+// a vertical-line or a left-parenthesis, or immediately preceding a
+// right-parenthesis, produces undefined results."
+//
+// GNU libc and Cygwin support empty subexpressions, BSD libc reports an error.
+//
+static const char * check_regex(const char * pattern)
+{
+ int level = 0;
+ char c;
+
+ for (int i = 0; (c = pattern[i]); i++) {
+ // Skip "\x"
+ if (c == '\\') {
+ if (!pattern[++i])
+ break;
+ continue;
+ }
+
+ // Skip "[...]"
+ if (c == '[') {
+ if (pattern[++i] == '^')
+ i++;
+ if (!pattern[i++])
+ break;
+ while ((c = pattern[i]) && c != ']')
+ i++;
+ if (!c)
+ break;
+ continue;
+ }
+
+ // Check "(...)" nesting
+ if (c == '(')
+ level++;
+ else if (c == ')' && --level < 0)
+ return "Unmatched ')'";
+
+ // Check for leading/trailing '|' or "||", "|)", "|$", "(|", "^|"
+ char c1;
+ if ( (c == '|' && ( i == 0 || !(c1 = pattern[i+1])
+ || c1 == '|' || c1 == ')' || c1 == '$'))
+ || ((c == '(' || c == '^') && pattern[i+1] == '|') )
+ return "Empty '|' subexpression";
+ }
+
+ return (const char *)0;
+}
+
+// Wrapper class for POSIX regex(3) or std::regex
+
+#ifndef WITH_CXX11_REGEX
+
+regular_expression::regular_expression()
+{
+ memset(&m_regex_buf, 0, sizeof(m_regex_buf));
+}
+
+regular_expression::~regular_expression()
+{
+ free_buf();
+}
+
+regular_expression::regular_expression(const regular_expression & x)
+: m_pattern(x.m_pattern),
+ m_errmsg(x.m_errmsg)
+{
+ memset(&m_regex_buf, 0, sizeof(m_regex_buf));
+ copy_buf(x);
+}
+
+regular_expression & regular_expression::operator=(const regular_expression & x)
+{
+ m_pattern = x.m_pattern;
+ m_errmsg = x.m_errmsg;
+ free_buf();
+ copy_buf(x);
+ return *this;
+}
+
+void regular_expression::free_buf()
+{
+ if (nonempty(&m_regex_buf, sizeof(m_regex_buf))) {
+ regfree(&m_regex_buf);
+ memset(&m_regex_buf, 0, sizeof(m_regex_buf));
+ }
+}
+
+void regular_expression::copy_buf(const regular_expression & x)
+{
+ if (nonempty(&x.m_regex_buf, sizeof(x.m_regex_buf))) {
+ // There is no POSIX compiled-regex-copy command.
+ if (!compile())
+ throw std::runtime_error(strprintf(
+ "Unable to recompile regular expression \"%s\": %s",
+ m_pattern.c_str(), m_errmsg.c_str()));
+ }
+}
+
+#endif // !WITH_CXX11_REGEX
+
+regular_expression::regular_expression(const char * pattern)
+: m_pattern(pattern)
+{
+ if (!compile())
+ throw std::runtime_error(strprintf(
+ "error in regular expression \"%s\": %s",
+ m_pattern.c_str(), m_errmsg.c_str()));
+}
+
+bool regular_expression::compile(const char * pattern)
+{
+#ifndef WITH_CXX11_REGEX
+ free_buf();
+#endif
+ m_pattern = pattern;
+ return compile();
+}
+
+bool regular_expression::compile()
+{
+#ifdef WITH_CXX11_REGEX
+ try {
+ m_regex.assign(m_pattern, std::regex_constants::extended);
+ }
+ catch (std::regex_error & ex) {
+ m_errmsg = ex.what();
+ return false;
+ }
+
+#else
+ int errcode = regcomp(&m_regex_buf, m_pattern.c_str(), REG_EXTENDED);
+ if (errcode) {
+ char errmsg[512];
+ regerror(errcode, &m_regex_buf, errmsg, sizeof(errmsg));
+ m_errmsg = errmsg;
+ free_buf();
+ return false;
+ }
+#endif
+
+ const char * errmsg = check_regex(m_pattern.c_str());
+ if (errmsg) {
+ m_errmsg = errmsg;
+#ifdef WITH_CXX11_REGEX
+ m_regex = std::regex();
+#else
+ free_buf();
+#endif
+ return false;
+ }
+
+ m_errmsg.clear();
+ return true;
+}
+
+bool regular_expression::full_match(const char * str) const
+{
+#ifdef WITH_CXX11_REGEX
+ return std::regex_match(str, m_regex);
+#else
+ match_range range;
+ return ( !regexec(&m_regex_buf, str, 1, &range, 0)
+ && range.rm_so == 0 && range.rm_eo == (int)strlen(str));
+#endif
+}
+
+bool regular_expression::execute(const char * str, unsigned nmatch, match_range * pmatch) const
+{
+#ifdef WITH_CXX11_REGEX
+ std::cmatch m;
+ if (!std::regex_search(str, m, m_regex))
+ return false;
+ unsigned sz = m.size();
+ for (unsigned i = 0; i < nmatch; i++) {
+ if (i < sz && *m[i].first) {
+ pmatch[i].rm_so = m[i].first - str;
+ pmatch[i].rm_eo = m[i].second - str;
+ }
+ else
+ pmatch[i].rm_so = pmatch[i].rm_eo = -1;
+ }
+ return true;
+
+#else
+ return !regexec(&m_regex_buf, str, nmatch, pmatch, 0);
+#endif
+}
+
+// Splits an argument to the -t option that is assumed to be of the form
+// "selective,%lld-%lld" (prefixes of "0" (for octal) and "0x"/"0X" (for hex)
+// are allowed). The first long long int is assigned to *start and the second
+// to *stop. Returns zero if successful and non-zero otherwise.
+int split_selective_arg(char *s, uint64_t *start,
+ uint64_t *stop, int *mode)
+{
+ char *tailptr;
+ if (!(s = strchr(s, ',')))
+ return 1;
+ bool add = false;
+ if (!isdigit((int)(*++s))) {
+ *start = *stop = 0;
+ if (!strncmp(s, "redo", 4))
+ *mode = SEL_REDO;
+ else if (!strncmp(s, "next", 4))
+ *mode = SEL_NEXT;
+ else if (!strncmp(s, "cont", 4))
+ *mode = SEL_CONT;
+ else
+ return 1;
+ s += 4;
+ if (!*s)
+ return 0;
+ if (*s != '+')
+ return 1;
+ }
+ else {
+ *mode = SEL_RANGE;
+ errno = 0;
+ // Last argument to strtoull (the base) is 0 meaning that decimal is assumed
+ // unless prefixes of "0" (for octal) or "0x"/"0X" (for hex) are used.
+ *start = strtoull(s, &tailptr, 0);
+ s = tailptr;
+ add = (*s == '+');
+ if (!(!errno && (add || *s == '-')))
+ return 1;
+ if (!strcmp(s, "-max")) {
+ *stop = ~(uint64_t)0; // replaced by max LBA later
+ return 0;
+ }
+ }
+
+ errno = 0;
+ *stop = strtoull(s+1, &tailptr, 0);
+ if (errno || *tailptr != '\0')
+ return 1;
+ if (add) {
+ if (*stop > 0)
+ (*stop)--;
+ *stop += *start; // -t select,N+M => -t select,N,(N+M-1)
+ }
+ return 0;
+}
+
+// Returns true if region of memory contains non-zero entries
+bool nonempty(const void * data, int size)
+{
+ for (int i = 0; i < size; i++)
+ if (((const unsigned char *)data)[i])
+ return true;
+ return false;
+}
+
+// Copy not null terminated char array to null terminated string.
+// Replace non-ascii characters. Remove leading and trailing blanks.
+const char * format_char_array(char * str, int strsize, const char * chr, int chrsize)
+{
+ int b = 0;
+ while (b < chrsize && chr[b] == ' ')
+ b++;
+ int n = 0;
+ while (b+n < chrsize && chr[b+n])
+ n++;
+ while (n > 0 && chr[b+n-1] == ' ')
+ n--;
+
+ if (n >= strsize)
+ n = strsize-1;
+
+ for (int i = 0; i < n; i++) {
+ char c = chr[b+i];
+ str[i] = (' ' <= c && c <= '~' ? c : '?');
+ }
+
+ str[n] = 0;
+ return str;
+}
+
+// Format integer with thousands separator
+const char * format_with_thousands_sep(char * str, int strsize, uint64_t val,
+ const char * thousands_sep /* = 0 */)
+{
+ if (!thousands_sep) {
+ thousands_sep = ",";
+#ifdef HAVE_LOCALE_H
+ setlocale(LC_ALL, "");
+ const struct lconv * currentlocale = localeconv();
+ if (*(currentlocale->thousands_sep))
+ thousands_sep = currentlocale->thousands_sep;
+#endif
+ }
+
+ char num[64];
+ snprintf(num, sizeof(num), "%" PRIu64, val);
+ int numlen = strlen(num);
+
+ int i = 0, j = 0;
+ do
+ str[j++] = num[i++];
+ while (i < numlen && (numlen - i) % 3 != 0 && j < strsize-1);
+ str[j] = 0;
+
+ while (i < numlen && j < strsize-1) {
+ j += snprintf(str+j, strsize-j, "%s%.3s", thousands_sep, num+i);
+ i += 3;
+ }
+
+ return str;
+}
+
+// Format capacity with SI prefixes
+const char * format_capacity(char * str, int strsize, uint64_t val,
+ const char * decimal_point /* = 0 */)
+{
+ if (!decimal_point) {
+ decimal_point = ".";
+#ifdef HAVE_LOCALE_H
+ setlocale(LC_ALL, "");
+ const struct lconv * currentlocale = localeconv();
+ if (*(currentlocale->decimal_point))
+ decimal_point = currentlocale->decimal_point;
+#endif
+ }
+
+ const unsigned factor = 1000; // 1024 for KiB,MiB,...
+ static const char prefixes[] = " KMGTP";
+
+ // Find d with val in [d, d*factor)
+ unsigned i = 0;
+ uint64_t d = 1;
+ for (uint64_t d2 = d * factor; val >= d2; d2 *= factor) {
+ d = d2;
+ if (++i >= sizeof(prefixes)-2)
+ break;
+ }
+
+ // Print 3 digits
+ uint64_t n = val / d;
+ if (i == 0)
+ snprintf(str, strsize, "%u B", (unsigned)n);
+ else if (n >= 100) // "123 xB"
+ snprintf(str, strsize, "%" PRIu64 " %cB", n, prefixes[i]);
+ else if (n >= 10) // "12.3 xB"
+ snprintf(str, strsize, "%" PRIu64 "%s%u %cB", n, decimal_point,
+ (unsigned)(((val % d) * 10) / d), prefixes[i]);
+ else // "1.23 xB"
+ snprintf(str, strsize, "%" PRIu64 "%s%02u %cB", n, decimal_point,
+ (unsigned)(((val % d) * 100) / d), prefixes[i]);
+
+ return str;
+}
+
+// return (v)sprintf() formatted std::string
+__attribute_format_printf(1, 0)
+std::string vstrprintf(const char * fmt, va_list ap)
+{
+ char buf[512];
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ buf[sizeof(buf)-1] = 0;
+ return buf;
+}
+
+std::string strprintf(const char * fmt, ...)
+{
+ va_list ap; va_start(ap, fmt);
+ std::string str = vstrprintf(fmt, ap);
+ va_end(ap);
+ return str;
+}
+
+#if defined(HAVE___INT128)
+// Compiler supports '__int128'.
+
+// Recursive 128-bit to string conversion function
+static int snprint_uint128(char * str, int strsize, unsigned __int128 value)
+{
+ if (strsize <= 0)
+ return -1;
+
+ if (value <= 0xffffffffffffffffULL) {
+ // Print leading digits as 64-bit value
+ return snprintf(str, (size_t)strsize, "%" PRIu64, (uint64_t)value);
+ }
+ else {
+ // Recurse to print leading digits
+ const uint64_t e19 = 10000000000000000000ULL; // 2^63 < 10^19 < 2^64
+ int len1 = snprint_uint128(str, strsize, value / e19);
+ if (len1 < 0)
+ return -1;
+
+ // Print 19 digits remainder as 64-bit value
+ int len2 = snprintf(str + (len1 < strsize ? len1 : strsize - 1),
+ (size_t)(len1 < strsize ? strsize - len1 : 1),
+ "%019" PRIu64, (uint64_t)(value % e19) );
+ if (len2 < 0)
+ return -1;
+ return len1 + len2;
+ }
+}
+
+// Convert 128-bit unsigned integer provided as two 64-bit halves to a string.
+const char * uint128_hilo_to_str(char * str, int strsize, uint64_t value_hi, uint64_t value_lo)
+{
+ snprint_uint128(str, strsize, ((unsigned __int128)value_hi << 64) | value_lo);
+ return str;
+}
+
+#elif defined(HAVE_LONG_DOUBLE_WIDER_PRINTF)
+// Compiler and *printf() support 'long double' which is wider than 'double'.
+
+const char * uint128_hilo_to_str(char * str, int strsize, uint64_t value_hi, uint64_t value_lo)
+{
+ snprintf(str, strsize, "%.0Lf", value_hi * (0xffffffffffffffffULL + 1.0L) + value_lo);
+ return str;
+}
+
+#else // !HAVE_LONG_DOUBLE_WIDER_PRINTF
+// No '__int128' or 'long double' support, use 'double'.
+
+const char * uint128_hilo_to_str(char * str, int strsize, uint64_t value_hi, uint64_t value_lo)
+{
+ snprintf(str, strsize, "%.0f", value_hi * (0xffffffffffffffffULL + 1.0) + value_lo);
+ return str;
+}
+
+#endif // HAVE___INT128
+
+// Get microseconds since some unspecified starting point.
+long long get_timer_usec()
+{
+#if USE_CLOCK_MONOTONIC
+ struct timespec ts;
+ if (clock_gettime(CLOCK_MONOTONIC, &ts))
+ return -1;
+ return ts.tv_sec * 1000000LL + ts.tv_nsec / 1000;
+#else
+ return std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::high_resolution_clock::now().time_since_epoch()
+ ).count();
+#endif
+}
+
+// Runtime check of byte ordering, throws on error.
+static void check_endianness()
+{
+ const union {
+ // Force compile error if int type is not 32bit.
+ unsigned char c[sizeof(int) == 4 ? 8 : -1];
+ uint64_t i;
+ } x = {{1, 2, 3, 4, 5, 6, 7, 8}};
+ const uint64_t le = 0x0807060504030201ULL;
+ const uint64_t be = 0x0102030405060708ULL;
+
+ if (!( x.i == (isbigendian() ? be : le)
+ && sg_get_unaligned_le16(x.c) == (uint16_t)le
+ && sg_get_unaligned_be16(x.c+6) == (uint16_t)be
+ && sg_get_unaligned_le32(x.c) == (uint32_t)le
+ && sg_get_unaligned_be32(x.c+4) == (uint32_t)be
+ && sg_get_unaligned_le64(x.c) == le
+ && sg_get_unaligned_be64(x.c) == be ))
+ throw std::logic_error("CPU endianness does not match compile time test");
+}
+
+#if defined(__GNUC__) && (__GNUC__ >= 7)
+
+// G++ 7+: Assume sane implementation and avoid -Wformat-truncation warning
+static void check_snprintf() {}
+
+#else
+
+static void check_snprintf()
+{
+ char buf[] = "ABCDEFGHI";
+ int n1 = snprintf(buf, 8, "123456789");
+ int n2 = snprintf(buf, 0, "X");
+ if (!(!strcmp(buf, "1234567") && n1 == 9 && n2 == 1))
+ throw std::logic_error("Function snprintf() does not conform to C99");
+}
+
+#endif
+
+// Runtime check of ./configure result, throws on error.
+void check_config()
+{
+ check_endianness();
+ check_snprintf();
+}