X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=utility.cpp;h=43bd6fa8d5d2a0cc00263ef3eda570d0bb6cfb39;hb=bcade6c14a06cfc842b41df91fdc5b9577cd68f1;hp=d5b0a3f7703cb3b23bf854b67dcee3c2690b6a31;hpb=097c50efa94fc656deb062c1fea7935fc5211e26;p=mirror_smartmontools-debian.git diff --git a/utility.cpp b/utility.cpp index d5b0a3f..43bd6fa 100644 --- a/utility.cpp +++ b/utility.cpp @@ -1,9 +1,10 @@ /* * utility.cpp * - * Home page of code is: http://smartmontools.sourceforge.net + * Home page of code is: http://www.smartmontools.org * - * Copyright (C) 2002-6 Bruce Allen + * Copyright (C) 2002-12 Bruce Allen + * Copyright (C) 2008-16 Christian Franke * Copyright (C) 2000 Michael Cornwell * * This program is free software; you can redistribute it and/or modify @@ -12,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 @@ -26,26 +26,34 @@ // 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 "config.h" +#include + +#include "svnversion.h" #include "int64.h" #include "utility.h" -// Any local header files should be represented by a CVSIDX just below. -const char* utility_c_cvsid="$Id: utility.cpp,v 1.62 2006/08/09 20:40:20 chrfranke Exp $" -CONFIG_H_CVSID INT64_H_CVSID UTILITY_H_CVSID; +#include "atacmds.h" +#include "dev_interface.h" + +const char * utility_cpp_cvsid = "$Id: utility.cpp 4194 2016-01-01 13:46:00Z chrfranke $" + UTILITY_H_CVSID INT64_H_CVSID; const char * packet_types[] = { "Direct-access (disk)", @@ -66,17 +74,59 @@ 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"; - - -// hang on to exit code, so we can make use of more generic 'atexit()' -// functionality and still check our exit code -int exitstatus = 0; +// BUILD_INFO can be provided by package maintainers +#ifndef BUILD_INFO +#define BUILD_INFO "(local build)" +#endif -// command-line argument: are we running in debug mode?. -unsigned char debugmode = 0; +// Make version information string +std::string format_version_info(const char * prog_name, bool full /*= false*/) +{ + std::string info = strprintf( + "%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-16, Bruce Allen, Christian Franke, www.smartmontools.org\n", + prog_name, smi()->get_os_version_str().c_str() + ); + if (!full) + return info; + + info += strprintf( + "\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; either\n" + "version 2, or (at your option) any later version.\n" + "See http://www.gnu.org for further details.\n" + "\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; +} // Solaris only: Get site-default timezone. This is called from // UpdateTimezone() when TZ environment variable is unset at startup. @@ -117,9 +167,9 @@ static char *ReadSiteDefaultTimezone(){ void FixGlibcTimeZoneBug(){ #if __GLIBC__ if (!getenv("TZ")) { - putenv("TZ=GMT"); + putenv((char *)"TZ=GMT"); // POSIX prototype is 'int putenv(char *)' tzset(); - putenv("TZ"); + putenv((char *)"TZ"); tzset(); } #elif _WIN32 @@ -214,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 @@ -230,12 +288,9 @@ int isbigendian(){ // timezone info (sigh). void dateandtimezoneepoch(char *buffer, time_t tval){ struct tm *tmval; - char *timezonename; + const char *timezonename; char datebuffer[DATEANDEPOCHLEN]; int lenm1; -#ifdef _WIN32 - char tzfixbuf[6+1]; -#endif FixGlibcTimeZoneBug(); @@ -265,6 +320,8 @@ void dateandtimezoneepoch(char *buffer, time_t tval){ #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 @@ -285,61 +342,6 @@ void dateandtimezone(char *buffer){ return; } -// These are two utility functions for printing CVS IDs. Massagecvs() -// returns distance that it has moved ahead in the input string -int massagecvs(char *out, const char *cvsid){ - char *copy,*filename,*date,*version; - int retVal=0; - const char delimiters[] = " ,$"; - - // make a copy on the heap, go to first token, - if (!(copy=strdup(cvsid))) - return 0; - - if (!(filename=strtok(copy, delimiters))) - goto endmassage; - - // move to first instance of "Id:" - while (strcmp(filename,"Id:")) - if (!(filename=strtok(NULL, delimiters))) - goto endmassage; - - // get filename, skip "v", get version and date - if (!( filename=strtok(NULL, delimiters) ) || - !( strtok(NULL, delimiters) ) || - !( version=strtok(NULL, delimiters) ) || - !( date=strtok(NULL, delimiters) ) ) - goto endmassage; - - sprintf(out,"%-16s revision: %-5s date: %-15s", filename, version, date); - retVal = (date-copy)+strlen(date); - - endmassage: - free(copy); - return retVal; -} - -// prints a single set of CVS ids -void printone(char *block, const char *cvsid){ - char strings[CVSMAXLEN]; - const char *here=cvsid; - int bi=0, len=strlen(cvsid)+1; - - // check that the size of the output block is sufficient - if (len>=CVSMAXLEN) { - pout("CVSMAXLEN=%d must be at least %d\n",CVSMAXLEN,len+1); - EXIT(1); - } - - // loop through the different strings - while (bi= 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; } -// A wrapper for regcomp(). Returns zero for success, non-zero otherwise. -int compileregex(regex_t *compiled, const char *pattern, int cflags) -{ - int errorcode; +// Wrapper class for regex(3) - if ( (errorcode = regcomp(compiled, pattern, cflags)) - || check_regex_nesting(pattern) < 0 ) { - pout("Internal error: unable to compile regular expression \"%s\" ", pattern); - if (errorcode) - printregexwarning(errorcode, compiled); - else - pout("Unmatched ')'\n"); - pout("Please inform smartmontools developers at " PACKAGE_BUGREPORT "\n"); - return 1; +regular_expression::regular_expression() +: m_flags(0) +{ + memset(&m_regex_buf, 0, sizeof(m_regex_buf)); +} + +regular_expression::regular_expression(const char * pattern, int flags, + bool throw_on_error /*= true*/) +{ + memset(&m_regex_buf, 0, sizeof(m_regex_buf)); + 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() +{ + free_buf(); +} + +regular_expression::regular_expression(const regular_expression & x) +{ + memset(&m_regex_buf, 0, sizeof(m_regex_buf)); + copy(x); +} + +regular_expression & regular_expression::operator=(const regular_expression & x) +{ + free_buf(); + copy(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)); } - return 0; +} + +void regular_expression::copy(const regular_expression & x) +{ + m_pattern = x.m_pattern; + m_flags = x.m_flags; + m_errmsg = x.m_errmsg; + + if (!m_pattern.empty() && m_errmsg.empty()) { + // 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())); + } +} + +bool regular_expression::compile(const char * pattern, int flags) +{ + free_buf(); + m_pattern = pattern; + m_flags = flags; + return compile(); +} + +bool regular_expression::compile() +{ + int errcode = regcomp(&m_regex_buf, m_pattern.c_str(), m_flags); + if (errcode) { + char errmsg[512]; + regerror(errcode, &m_regex_buf, errmsg, sizeof(errmsg)); + m_errmsg = errmsg; + free_buf(); + return false; + } + + const char * errmsg = check_regex(m_pattern.c_str()); + if (errmsg) { + m_errmsg = errmsg; + free_buf(); + return false; + } + + m_errmsg.clear(); + return true; } // Splits an argument to the -r option into a name part and an (optional) @@ -443,47 +537,28 @@ 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 6.0) -// Functionality reduced to split_selective_arg()'s requirements. +// Replacement for missing strtoull() (Linux with libc < 6, MSVC) +// Functionality reduced to requirements of smartd and split_selective_arg(). -static uint64_t strtoull(const char * p, char * * endp, int base) +uint64_t strtoull(const char * p, char * * endp, int base) { uint64_t result, maxres; int i = 0; char c = p[i++]; - // assume base == 0 - if (c == '0') { - if (p[i] == 'x' || p[i] == 'X') { - base = 16; i++; + + if (!base) { + if (c == '0') { + if (p[i] == 'x' || p[i] == 'X') { + base = 16; i++; + } + else + base = 8; + c = p[i++]; } else - base = 8; - c = p[i++]; + base = 10; } - else - base = 10; result = 0; maxres = ~(uint64_t)0 / (unsigned)base; @@ -507,7 +582,8 @@ static uint64_t strtoull(const char * p, char * * endp, int base) result = result * (unsigned)base + digit; c = p[i++]; } - *endp = (char *)p + i - 1; + if (endp) + *endp = (char *)p + i - 1; return result; } #endif // HAVE_STRTOLL @@ -517,79 +593,84 @@ static uint64_t strtoull(const char * p, char * * endp, int base) // 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) + uint64_t *stop, int *mode) { char *tailptr; - if (!(s = strchr(s, ','))) return 1; - if (!isdigit((int)(*++s))) - return 1; - 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); + 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; + } + } - s = tailptr; - if (errno || *s++ != '-') - return 1; - *stop = strtoull(s, &tailptr, 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; } +#ifdef OLD_INTERFACE + int64_t bytes = 0; + // Helps debugging. If the second argument is non-negative, then // decrement bytes by that amount. Else decrement bytes by (one plus) // length of null terminated string. -void *FreeNonZero1(void *address, int size, int line, const char* file){ +void *FreeNonZero(void *address, int size, int /*line*/, const char* /*file*/){ if (address) { if (size<0) bytes-=1+strlen((char*)address); else bytes-=size; - return CheckFree1(address, line, file); - } - return NULL; -} - -// To help with memory checking. Use when it is known that address is -// NOT null. -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); -} - -// A custom version of calloc() that tracks memory use -void *Calloc(size_t nmemb, size_t size) { - void *ptr=calloc(nmemb, size); - - if (ptr) - bytes+=nmemb*size; - - return ptr; + return NULL; } // 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(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; } @@ -597,10 +678,8 @@ char *CustomStrDup(char *ptr, int mustexist, int whatline, const char* file){ // 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); @@ -608,48 +687,114 @@ char *CustomStrDup(char *ptr, int mustexist, int whatline, const char* file){ return tmp; } -// Returns nonzero if region of memory contains non-zero entries -int nonempty(unsigned char *testarea,int n){ - int i; - for (i=0;ithousands_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 + +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; } #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 @@ -678,4 +823,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 +}