/*
 * utility.cpp
 *
 * Home page of code is: http://smartmontools.sourceforge.net
 *
 * Copyright (C) 2002-7 Bruce Allen <smartmontools-support@lists.sourceforge.net>
 * Copyright (C) 2000 Michael Cornwell <cornwell@acm.org>
 *
 * 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, or (at your option)
 * 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.
 *
 * This code was originally developed as a Senior Thesis by Michael Cornwell
 * at the Concurrent Systems Laboratory (now part of the Storage Systems
 * Research Center), Jack Baskin School of Engineering, University of
 * California, Santa Cruz. http://ssrc.soe.ucsc.edu/
 *
 */

// 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 <stdio.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <stdlib.h>
#include <ctype.h>
#include <syslog.h>
#include <stdarg.h>
#include <sys/stat.h>
#ifdef _WIN32
#include <mbstring.h> // _mbsinc()
#endif

#include "config.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.64 2007/02/03 15:14:14 chrfranke Exp $"
CONFIG_H_CVSID INT64_H_CVSID 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"
};

// 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;

// command-line argument: are we running in debug mode?.
unsigned char debugmode = 0;


// 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 cannonical
// 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("TZ=GMT");
    tzset();
    putenv("TZ");
    tzset();
  }
#elif _WIN32
  if (!getenv("TZ")) {
    putenv("TZ=GMT");
    tzset();
    putenv("TZ=");  // empty value removes TZ, putenv("TZ") does nothing
    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";
}


// 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;
}

// 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, time_t tval){
  struct tm *tmval;
  char *timezonename;
  char datebuffer[DATEANDEPOCHLEN];
  int lenm1;
#ifdef _WIN32
  char tzfixbuf[6+1];
#endif

  FixGlibcTimeZoneBug();
  
  // Get the time structure.  We need this to determine if we are in
  // daylight savings time or not.
  tmval=localtime(&tval);
  
  // Convert to an ASCII string, put in datebuffer
  // same as: asctime_r(tmval, datebuffer);
  strncpy(datebuffer, asctime(tmval), DATEANDEPOCHLEN);
  datebuffer[DATEANDEPOCHLEN-1]='\0';
  
  // Remove newline
  lenm1=strlen(datebuffer)-1;
  datebuffer[lenm1>=0?lenm1:0]='\0';
  
  // correct timezone name
  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
  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;
}

// Date and timezone gets printed into string pointed to by buffer
void dateandtimezone(char *buffer){
  
  // Get the epoch (time in seconds since Jan 1 1970)
  time_t tval=time(NULL);
  
  dateandtimezoneepoch(buffer, tval);
  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<CVSMAXLEN && (len=massagecvs(strings,here))){
    bi+=snprintf(block+bi,CVSMAXLEN-bi,"%s %s\n",(bi==0?"Module:":"  uses:"),strings);
    here+=len;
  }
  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 perrror(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;
}

// Prints a warning message for a failed regular expression compilation from
// regcomp().
void printregexwarning(int errcode, regex_t *compiled){
  size_t length = regerror(errcode, compiled, NULL, 0);
  char *buffer = (char*)malloc(length);
  if (!buffer){
    pout("Out of memory in printregexwarning()\n");
    return;
  }
  regerror(errcode, compiled, buffer, length);
  pout("%s\n", buffer);
  free(buffer);
  return;
}

// 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.
static int check_regex_nesting(const char * pattern)
{
  int level = 0, i;
  for (i = 0; pattern[i] && level >= 0; i++) {
    switch (pattern[i]) {
      case '(': level++; break;
      case ')': level--; break;
    }
  }
  return level;
}

// A wrapper for regcomp().  Returns zero for success, non-zero otherwise.
int compileregex(regex_t *compiled, const char *pattern, int cflags)
{ 
  int errorcode;

  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;
  }
  return 0;
}

// Splits an argument to the -r option into a name part and an (optional) 
// positive integer part.  s is a pointer to a string containing the
// argument.  After the call, s will point to the name part and *i the
// integer part if there is one or 1 otherwise.  Note that the string s may
// be changed by this function.  Returns zero if successful and non-zero
// otherwise.
int split_report_arg(char *s, int *i)
{
  if ((s = strchr(s, ','))) {
    // Looks like there's a name part and an integer part.
    char *tailptr;

    *s++ = '\0';
    if (*s == '0' || !isdigit((int)*s))  // The integer part must be positive
      return 1;
    errno = 0;
    *i = (int) strtol(s, &tailptr, 10);
    if (errno || *tailptr != '\0')
      return 1;
  } else {
    // There's no integer part.
    *i = 1;
  }

  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.

static 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++;
    }
    else
      base = 8;
    c = p[i++];
  }
  else
    base = 10;

  result = 0;
  maxres = ~(uint64_t)0 / (unsigned)base;
  for (;;) {
    unsigned digit;
    if ('0' <= c && c <= '9')
      digit = c - '0';
    else if ('A' <= c && c <= 'Z')
      digit = c - 'A' + 10;
    else if ('a' <= c && c <= 'z')
      digit = c - 'a' + 10;
    else
      break;
    if (digit >= (unsigned)base)
      break;
    if (!(   result < maxres
          || (result == maxres && digit <= ~(uint64_t)0 % (unsigned)base))) {
      result = ~(uint64_t)0; errno = ERANGE; // return on overflow
      break;
    }
    result = result * (unsigned)base + digit;
    c = p[i++];
  }
  *endp = (char *)p + i - 1;
  return result;
}
#endif // HAVE_STRTOLL

// 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;
    }
  }
  *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;
}

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){
  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;
}

// 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 *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);
    }
    else
      return NULL;
  }

  // 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);
  }
  
  // and track memory usage
  bytes+=1+strlen(ptr);
  
  return tmp;
}

// Returns nonzero if region of memory contains non-zero entries
int nonempty(unsigned char *testarea,int n){
  int i;
  for (i=0;i<n;i++)
    if (testarea[i])
      return 1;
  return 0;
}


// 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;

  days       = msec/86400000U;
  msec      -= days*86400000U;

  hours      = msec/3600000U; 
  msec      -= hours*3600000U;

  min        = msec/60000U;
  msec      -= min*60000U;

  sec        = msec/1000U;
  msec      -= sec*1000U;

  if (days) {
    txt += sprintf(txt, "%2dd+", (int)days);
    start=1;
  }

  sprintf(txt, "%02d:%02d:%02d.%03d", (int)hours, (int)min, (int)sec, (int)msec);  
  return;
}


#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).
// Below are sane replacements substituted by #define in utility.h.

#undef vsnprintf
#if defined(_WIN32) && defined(_MSC_VER)
#define vsnprintf _vsnprintf
#endif

int safe_vsnprintf(char *buf, int size, const char *fmt, va_list ap)
{
  int i;
  if (size <= 0)
    return 0;
  i = vsnprintf(buf, size, fmt, ap);
  if (0 <= i && i < size)
    return i;
  buf[size-1] = 0;
  return strlen(buf); // Note: cannot detect for overflow, not necessary here.
}

int safe_snprintf(char *buf, int size, const char *fmt, ...)
{
  int i; va_list ap;
  va_start(ap, fmt);
  i = safe_vsnprintf(buf, size, fmt, ap);
  va_end(ap);
  return i;
}

#endif