X-Git-Url: https://git.proxmox.com/?p=mirror_smartmontools-debian.git;a=blobdiff_plain;f=utility.cpp;h=09a4f1f1bb45c86afe9c0b1b37113e0533df4354;hp=670df9e7ad9b5c67f9451ea73561fb95d9101ecf;hb=12d5f9dc1acacc1ef1ab1b84b6ab73ba56d1f2f1;hpb=2127e1931eec4a688d41baf253744fc48ed8c989 diff --git a/utility.cpp b/utility.cpp index 670df9e..09a4f1f 100644 --- a/utility.cpp +++ b/utility.cpp @@ -3,8 +3,8 @@ * * Home page of code is: http://smartmontools.sourceforge.net * - * Copyright (C) 2002-9 Bruce Allen - * Copyright (C) 2008-9 Christian Franke + * Copyright (C) 2002-12 Bruce Allen + * Copyright (C) 2008-14 Christian Franke * Copyright (C) 2000 Michael Cornwell * * This program is free software; you can redistribute it and/or modify @@ -13,8 +13,7 @@ * any later version. * * You should have received a copy of the GNU General Public License - * (for example COPYING); if not, write to the Free - * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * (for example COPYING); If not, see . * * This code was originally developed as a Senior Thesis by Michael Cornwell * at the Concurrent Systems Laboratory (now part of the Storage Systems @@ -27,22 +26,25 @@ // BOTH SCSI AND ATA DEVICES, AND THAT MAY BE USED IN SMARTD, // SMARTCTL, OR BOTH. +#include "config.h" + #include #include #include #include #include #include -#include #include #include +#ifdef HAVE_LOCALE_H +#include +#endif #ifdef _WIN32 #include // _mbsinc() #endif #include -#include "config.h" #include "svnversion.h" #include "int64.h" #include "utility.h" @@ -50,7 +52,7 @@ #include "atacmds.h" #include "dev_interface.h" -const char * utility_cpp_cvsid = "$Id: utility.cpp 2848 2009-07-18 20:14:38Z chrfranke $" +const char * utility_cpp_cvsid = "$Id: utility.cpp 3937 2014-07-05 17:51:21Z chrfranke $" UTILITY_H_CVSID INT64_H_CVSID; const char * packet_types[] = { @@ -72,13 +74,6 @@ const char * packet_types[] = { "Optical card reader/writer" }; -// Whenever exit() status is EXIT_BADCODE, please print this message -const char *reportbug="Please report this bug to the Smartmontools developers at " PACKAGE_BUGREPORT ".\n"; - - -// command-line argument: are we running in debug mode?. -unsigned char debugmode = 0; - // BUILD_INFO can be provided by package maintainers #ifndef BUILD_INFO #define BUILD_INFO "(local build)" @@ -88,10 +83,15 @@ unsigned char debugmode = 0; std::string format_version_info(const char * prog_name, bool full /*= false*/) { std::string info = strprintf( - "%s "PACKAGE_VERSION" "SMARTMONTOOLS_SVN_DATE" r"SMARTMONTOOLS_SVN_REV - " [%s] "BUILD_INFO"\n" - "Copyright (C) 2002-9 by Bruce Allen, http://smartmontools.sourceforge.net\n", - prog_name, smi()->get_os_version_str() + "%s " 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" + "Copyright (C) 2002-14, Bruce Allen, Christian Franke, www.smartmontools.org\n", + prog_name, smi()->get_os_version_str().c_str() ); if (!full) return info; @@ -100,23 +100,30 @@ std::string format_version_info(const char * prog_name, bool full /*= false*/) "\n" "%s 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 Version 2.\n" + "the terms of the GNU General Public License; either\n" + "version 2, or (at your option) any later version.\n" "See http://www.gnu.org for further details.\n" - "\n" - "smartmontools release "PACKAGE_VERSION - " dated "SMARTMONTOOLS_RELEASE_DATE" at "SMARTMONTOOLS_RELEASE_TIME"\n" - "smartmontools SVN rev "SMARTMONTOOLS_SVN_REV - " dated "SMARTMONTOOLS_SVN_DATE" at "SMARTMONTOOLS_SVN_TIME"\n" - "smartmontools build host: "SMARTMONTOOLS_BUILD_HOST"\n" - "smartmontools build configured: "SMARTMONTOOLS_CONFIGURE_DATE "\n" - "%s compile dated "__DATE__" at "__TIME__"\n", - prog_name, prog_name - ); - info += strprintf( - "smartmontools configure arguments: %s\n", - (sizeof(SMARTMONTOOLS_CONFIGURE_ARGS) > 1 ? - SMARTMONTOOLS_CONFIGURE_ARGS : "[no arguments given]") + "\n", + prog_name ); + info += + "smartmontools release " PACKAGE_VERSION + " dated " SMARTMONTOOLS_RELEASE_DATE " at " SMARTMONTOOLS_RELEASE_TIME "\n" +#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" +#if defined(__GNUC__) && defined(__VERSION__) // works also with CLang + "smartmontools build with: GCC " __VERSION__ "\n" +#endif + "smartmontools configure arguments: " + ; + info += (sizeof(SMARTMONTOOLS_CONFIGURE_ARGS) > 1 ? + SMARTMONTOOLS_CONFIGURE_ARGS : "[no arguments given]"); + info += '\n'; return info; } @@ -257,15 +264,23 @@ const char *packetdevicetype(int type){ return "Unknown"; } +// Runtime check of byte ordering, throws if different from isbigendian(). +static void check_endianness() +{ + union { + // Force compile error if int type is not 32bit. + unsigned char c[sizeof(unsigned) == 4 ? 4 : -1]; + unsigned i; + } x = {{1,2,3,4}}; + + int big = -1; + switch (x.i) { + case 0x01020304: big = 1; break; + case 0x04030201: big = 0; break; + } -// Returns 1 if machine is big endian, else zero. This is a run-time -// rather than a compile-time function. We could do it at -// compile-time but in principle there are architectures that can run -// with either byte-ordering. -int isbigendian(){ - short i=0x0100; - char *tmp=(char *)&i; - return *tmp; + if (big != (isbigendian() ? 1 : 0)) + throw std::logic_error("CPU endianness does not match compile time test"); } // Utility function prints date and time and timezone into a character @@ -349,27 +364,62 @@ void syserror(const char *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." // -// Actual '(...)' nesting errors remain undetected on strict POSIX -// implementations (glibc) but an error is reported on others (Cygwin). -// -// The check below is rather incomplete because it does not handle -// e.g. '\)' '[)]'. -// But it should work for the regex subset used in drive database -// and smartd '-s' directives. -static int check_regex_nesting(const char * pattern) +// 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, i; - for (i = 0; pattern[i] && level >= 0; i++) { - switch (pattern[i]) { - case '(': level++; break; - case ')': level--; break; + 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 level; + + return (const char *)0; } // Wrapper class for regex(3) @@ -380,10 +430,14 @@ regular_expression::regular_expression() memset(&m_regex_buf, 0, sizeof(m_regex_buf)); } -regular_expression::regular_expression(const char * pattern, int flags) +regular_expression::regular_expression(const char * pattern, int flags, + bool throw_on_error /*= true*/) { memset(&m_regex_buf, 0, sizeof(m_regex_buf)); - compile(pattern, flags); + if (!compile(pattern, flags) && throw_on_error) + throw std::runtime_error(strprintf( + "error in regular expression \"%s\": %s", + m_pattern.c_str(), m_errmsg.c_str())); } regular_expression::~regular_expression() @@ -446,8 +500,9 @@ bool regular_expression::compile() return false; } - if (check_regex_nesting(m_pattern.c_str()) < 0) { - m_errmsg = "Unmatched ')'"; + const char * errmsg = check_regex(m_pattern.c_str()); + if (errmsg) { + m_errmsg = errmsg; free_buf(); return false; } @@ -483,27 +538,6 @@ int split_report_arg(char *s, int *i) return 0; } -// same as above but sets *i to -1 if missing , argument -int split_report_arg2(char *s, int *i){ - char *tailptr; - s+=6; - - if (*s=='\0' || !isdigit((int)*s)) { - // What's left must be integer - *i=-1; - return 1; - } - - errno = 0; - *i = (int) strtol(s, &tailptr, 10); - if (errno || *tailptr != '\0') { - *i=-1; - return 1; - } - - return 0; -} - #ifndef HAVE_STRTOULL // Replacement for missing strtoull() (Linux with libc < 6, MSVC) // Functionality reduced to requirements of smartd and split_selective_arg(). @@ -597,6 +631,8 @@ int split_selective_arg(char *s, uint64_t *start, return 0; } } + + errno = 0; *stop = strtoull(s+1, &tailptr, 0); if (errno || *tailptr != '\0') return 1; @@ -610,10 +646,6 @@ int split_selective_arg(char *s, uint64_t *start, #ifdef OLD_INTERFACE -// smartd exit codes -#define EXIT_NOMEM 8 // out of memory -#define EXIT_BADCODE 10 // internal error - should NEVER happen - int64_t bytes = 0; // Helps debugging. If the second argument is non-negative, then @@ -632,15 +664,12 @@ void *FreeNonZero1(void *address, int size, int line, const char* file){ // To help with memory checking. Use when it is known that address is // NOT null. -void *CheckFree1(void *address, int whatline, const char* file){ +void *CheckFree1(void *address, int /*whatline*/, const char* /*file*/){ if (address){ free(address); return NULL; } - - PrintOut(LOG_CRIT, "Internal error in CheckFree() at line %d of file %s\n%s", - whatline, file, reportbug); - EXIT(EXIT_BADCODE); + throw std::runtime_error("Internal error in CheckFree()"); } // A custom version of calloc() that tracks memory use @@ -656,16 +685,13 @@ void *Calloc(size_t nmemb, size_t size) { // A custom version of strdup() that keeps track of how much memory is // being allocated. If mustexist is set, it also throws an error if we // try to duplicate a NULL string. -char *CustomStrDup(const char *ptr, int mustexist, int whatline, const char* file){ +char *CustomStrDup(const char *ptr, int mustexist, int /*whatline*/, const char* /*file*/){ char *tmp; // report error if ptr is NULL and mustexist is set if (ptr==NULL){ - if (mustexist) { - PrintOut(LOG_CRIT, "Internal error in CustomStrDup() at line %d of file %s\n%s", - whatline, file, reportbug); - EXIT(EXIT_BADCODE); - } + if (mustexist) + throw std::runtime_error("Internal error in CustomStrDup()"); else return NULL; } @@ -673,10 +699,8 @@ char *CustomStrDup(const char *ptr, int mustexist, int whatline, const char* fil // make a copy of the string... tmp=strdup(ptr); - if (!tmp) { - PrintOut(LOG_CRIT, "No memory to duplicate string %s at line %d of file %s\n", ptr, whatline, file); - EXIT(EXIT_NOMEM); - } + if (!tmp) + throw std::bad_alloc(); // and track memory usage bytes+=1+strlen(ptr); @@ -696,33 +720,78 @@ bool nonempty(const void * data, int size) return false; } +// 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 + } -// This routine converts an integer number of milliseconds into a test -// string of the form Xd+Yh+Zm+Ts.msec. The resulting text string is -// written to the array. -void MsecToText(unsigned int msec, char *txt){ - int start=0; - unsigned int days, hours, min, sec; + char num[64]; + snprintf(num, sizeof(num), "%" PRIu64, val); + int numlen = strlen(num); - days = msec/86400000U; - msec -= days*86400000U; + 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; + } - hours = msec/3600000U; - msec -= hours*3600000U; + return str; +} - min = msec/60000U; - msec -= min*60000U; +// 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 + } - sec = msec/1000U; - msec -= sec*1000U; + const unsigned factor = 1000; // 1024 for KiB,MiB,... + static const char prefixes[] = " KMGTP"; - if (days) { - txt += sprintf(txt, "%2dd+", (int)days); - start=1; + // 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; } - sprintf(txt, "%02d:%02d:%02d.%03d", (int)hours, (int)min, (int)sec, (int)msec); - return; + // 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 @@ -745,8 +814,8 @@ std::string strprintf(const char * fmt, ...) #ifndef HAVE_WORKING_SNPRINTF -// Some versions of (v)snprintf() don't append null char on overflow (MSVCRT.DLL), -// and/or return -1 on overflow (old Linux). +// Some versions of (v)snprintf() don't append null char (MSVCRT.DLL), +// and/or return -1 on output truncation (glibc <= 2.0.6). // Below are sane replacements substituted by #define in utility.h. #undef vsnprintf @@ -775,4 +844,25 @@ int safe_snprintf(char *buf, int size, const char *fmt, ...) return i; } +#else // HAVE_WORKING_SNPRINTF + +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,\n" + "please contact " PACKAGE_BUGREPORT); +} + +#endif // HAVE_WORKING_SNPRINTF + +// Runtime check of ./configure result, throws on error. +void check_config() +{ + check_endianness(); +#ifdef HAVE_WORKING_SNPRINTF + check_snprintf(); #endif +}