Skip to content

File util_func.c

File List > base > util_func.c

Go to the documentation of this file

/*
 * Copyright 2008 Search Solution Corporation
 * Copyright 2016 CUBRID Corporation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

/*
 * util_func.c : miscellaneous utility functions
 *
 */

#ident "$Id$"

#include "config.h"

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
#include <chrono>
#include <time.h>
#include <stdarg.h>
#include <sys/timeb.h>
#if !defined (WINDOWS)
#include <sys/time.h>
#endif /* !WINDOWS */

#include "util_func.h"
#include "porting.h"
#include "error_code.h"
#include "utility.h"
#include "system_parameter.h"
#include "environment_variable.h"
// XXX: SHOULD BE THE LAST INCLUDE HEADER
#include "memory_wrapper.hpp"

#define UTIL_LOG_MAX_HEADER_LEN    (40)
#define UTIL_LOG_MAX_MSG_SIZE       (1024)
#define UTIL_LOG_BUFFER_SIZE   \
  (UTIL_LOG_MAX_MSG_SIZE + UTIL_LOG_MAX_HEADER_LEN)

#define UTIL_LOG_FILENAME  "cubrid_utility.log"

static char *util_Log_filename = NULL;
static char util_Log_filename_buf[PATH_MAX];
static char util_Log_buffer[UTIL_LOG_BUFFER_SIZE];

static FILE *util_log_file_backup (FILE * fp, const char *path);
static FILE *util_log_file_fopen (const char *path);
static FILE *fopen_and_lock (const char *path);
static int util_log_header (char *buf, size_t buf_len);
static int util_log_write_internal (const char *msg, const char *prefix_str);

// *INDENT-OFF*
template <typename Duration>
static void util_get_seconds_and_rest_since_epoch (std::chrono::seconds &secs, Duration &rest);
// *INDENT-ON*

/*
 * hashpjw() - returns hash value of given string
 *   return: hash value
 *   s(in)
 *
 * Note:
 *   This function is adapted from the hashpjw function in Aho, Sethi, and
 *   Ullman (Red Dragon), p. 436.  Unlike that version, this one does not
 *   mod the result; consequently, the result could be any 32-bit value.
 *   The caller should mod the result with a value appropriate for its
 *   environment.
 *
 */
unsigned int
hashpjw (const char *s)
{
  unsigned int h, g;

  assert (s != NULL);

  for (h = 0; *s != '\0'; ++s)
    {
      h = (h << 4) + (*s);

      g = (h & 0xf0000000);

      if (g != 0)
    {
      h ^= g >> 24;
      h ^= g;
    }
    }
  return h;
}

/*
 * util_compare_filepath -
 *   return:
 *   file1(in):
 *   file2(in):
 */
int
util_compare_filepath (const char *file1, const char *file2)
{
#if defined (WINDOWS)
  char path1[PATH_MAX], path2[PATH_MAX];
  char *p;

  if (GetLongPathName (file1, path1, sizeof (path1)) == 0 || GetLongPathName (file2, path2, sizeof (path2)) == 0)
    {
      return (stricmp (file1, file2));
    }

  for (p = path1; *p; p++)
    if (*p == '/')
      *p = '\\';
  for (p = path2; *p; p++)
    if (*p == '/')
      *p = '\\';

  return (stricmp (path1, path2));
#else /* WINDOWS */
  return (strcmp (file1, file2));
#endif /* !WINDOWS */
}

/*
 * Signal Handling
 */

static void system_interrupt_handler (int sig);
static void system_quit_handler (int sig);

/*
 * user_interrupt_handler, user_quit_handler -
 *   These variables contain pointers to the user specified handler
 *   functions for the interrupt and quit signals.
 *   If they are NULL, no handlers have been defined.
 */
static SIG_HANDLER user_interrupt_handler = NULL;
static SIG_HANDLER user_quit_handler = NULL;

/*
 * system_interrupt_handler - Internal system handler for SIGINT
 *   return: none
 *   sig(in): signal no
 *
 * Note:  Calls the user interrupt handler after re-arming the signal handler.
 */
static void
system_interrupt_handler (int sig)
{
  (void) os_set_signal_handler (SIGINT, system_interrupt_handler);
  if (user_interrupt_handler != NULL)
    {
      (*user_interrupt_handler) ();
    }
}


/*
 * system_quit_handler - Internal system handler for SIGQUIT
 *   return: none
 *   sig(in): signal no
 *
 * Note: Calls the user quit handler after re-arming the signal handler.
 */
static void
system_quit_handler (int sig)
{
#if !defined(WINDOWS)
  (void) os_set_signal_handler (SIGQUIT, system_quit_handler);
  if (user_quit_handler != NULL)
    (*user_quit_handler) ();
#endif
}

/*
 * util_disarm_signal_handlers - Disarms the user interrpt and quit handlers
 *                               if any were specified
 *   return: none
 */
void
util_disarm_signal_handlers (void)
{
  if (user_interrupt_handler != NULL)
    {
      user_interrupt_handler = NULL;
      if (os_set_signal_handler (SIGINT, SIG_IGN) != SIG_IGN)
    {
      (void) os_set_signal_handler (SIGINT, SIG_DFL);
    }
    }
#if !defined(WINDOWS)
  if (user_quit_handler != NULL)
    {
      user_quit_handler = NULL;
      if (os_set_signal_handler (SIGQUIT, SIG_IGN) != SIG_IGN)
    {
      (void) os_set_signal_handler (SIGQUIT, SIG_DFL);
    }
    }
#endif
}

/*
 * util_arm_signal_handlers - Install signal handlers for the two most
 *                            important signals SIGINT and SIGQUIT
 *   return: none
 *   sigint_handler(in): SIGINT signal handler
 *   sigquit_handler(in): SIGQUIT signal handler
 */
void
util_arm_signal_handlers (SIG_HANDLER sigint_handler, SIG_HANDLER sigquit_handler)
{
  /* first disarm any existing handlers */
  util_disarm_signal_handlers ();

  if (sigint_handler != NULL)
    {
      (void) os_set_signal_handler (SIGINT, system_interrupt_handler);
      user_interrupt_handler = sigint_handler;
    }
#if !defined(WINDOWS)
  if (sigquit_handler != NULL)
    {
      /* Is this kind of test necessary for the quit signal ? */
      (void) os_set_signal_handler (SIGQUIT, system_quit_handler);
      user_quit_handler = sigquit_handler;
    }
#endif
}

/*
 *  The returned char** is null terminated char* array;
 *    ex: "a,b" --> { "a", "b", NULL }
 */
char **
util_split_string (const char *str, const char *delim)
{
  char *t, *o;
  char *save, *v;
  char **r = NULL;
  int count = 1;

  if (str == NULL)
    {
      return NULL;
    }

  o = strdup (str);
  if (o == NULL)
    {
      return NULL;
    }

  for (t = o;; t = NULL)
    {
      v = strtok_r (t, delim, &save);
      if (v == NULL)
    {
      break;
    }
      char **const realloc_r = (char **) realloc (r, sizeof (char *) * (count + 1));
      if (realloc_r == NULL)
    {
      free (o);
      return NULL;
    }
      else
    {
      r = realloc_r;
    }
      r[count - 1] = strdup (v);
      r[count] = NULL;
      count++;
    }

  free (o);
  return r;
}

void
util_free_string_array (char **array)
{
  int i;

  for (i = 0; array[i] != NULL; i++)
    {
      free (array[i]);
    }
  free (array);
}

/*
 *  util_str_to_time_since_epoch () - convert time string
 *      return: time since epoch or 0 on error
 *
 *  NOTE: it only accepts YYYY-DD-MM hh:mm:ss format.
 */
time_t
util_str_to_time_since_epoch (char *str)
{
  int status = NO_ERROR;
  int date_index = 0;
  char *save_ptr, *token, *date_string;
  const char *delim = "-: ";
  struct tm time_data, tmp_time_data;
  time_t result_time;

  if (str == NULL)
    {
      return 0;
    }

  date_string = strdup (str);
  if (date_string == NULL)
    {
      return 0;
    }

  token = strtok_r (date_string, delim, &save_ptr);
  while (status == NO_ERROR && token != NULL)
    {
      switch (date_index)
    {
    case 0:     /* year */
      time_data.tm_year = atoi (token) - 1900;
      if (time_data.tm_year < 0)
        {
          status = ER_GENERIC_ERROR;
        }
      break;
    case 1:     /* month */
      time_data.tm_mon = atoi (token) - 1;
      if (time_data.tm_mon < 0 || time_data.tm_mon > 11)
        {
          status = ER_GENERIC_ERROR;
        }
      break;
    case 2:     /* month-day */
      time_data.tm_mday = atoi (token);
      if (time_data.tm_mday < 1 || time_data.tm_mday > 31)
        {
          status = ER_GENERIC_ERROR;
        }
      break;
    case 3:     /* hour */
      time_data.tm_hour = atoi (token);
      if (time_data.tm_hour < 0 || time_data.tm_hour > 23)
        {
          status = ER_GENERIC_ERROR;
        }
      break;
    case 4:     /* minute */
      time_data.tm_min = atoi (token);
      if (time_data.tm_min < 0 || time_data.tm_min > 59)
        {
          status = ER_GENERIC_ERROR;
        }
      break;
    case 5:     /* second */
      time_data.tm_sec = atoi (token);
      if (time_data.tm_sec < 0 || time_data.tm_sec > 59)
        {
          status = ER_GENERIC_ERROR;
        }
      break;
    default:
      status = ER_GENERIC_ERROR;
      break;
    }
      date_index++;
      token = strtok_r (NULL, delim, &save_ptr);
    }
  time_data.tm_isdst = -1;

  free (date_string);

  if (date_index != 6 || status != NO_ERROR)
    {
      return 0;
    }

  tmp_time_data = time_data;

  result_time = mktime (&tmp_time_data);
  if (result_time < (time_t) 0)
    {
      return 0;
    }

  time_data.tm_isdst = tmp_time_data.tm_isdst;
  result_time = mktime (&time_data);
  if (result_time < (time_t) 0)
    {
      return 0;
    }

  return result_time;
}

void
util_shuffle_string_array (char **array, int count)
{
  struct timeval t;
  int i, j;
  double r;
  struct drand48_data buf;
  char *temp;

  gettimeofday (&t, NULL);

  /* tv_usec returned by gettimeofday on WINDOWS is millisec * 1000 and seeding it would result in generating an even
   * random number at first. To avoid such a pattern in generating a random number, tv_usec/1000 is used on WINDOWS. */
#if defined (WINDOWS)
  srand48_r (t.tv_usec / 1000, &buf);
#else /* WINDOWS */
  srand48_r (t.tv_usec, &buf);
#endif /* !WINDOWS */

  /* Fisher-Yates shuffle */
  for (i = count - 1; i > 0; i--)
    {
      drand48_r (&buf, &r);
      j = (int) ((i + 1) * r);

      temp = array[j];
      array[j] = array[i];
      array[i] = temp;
    }
}

/*
 * util_log_write_result () -
 *
 * error (in) :
 */
int
util_log_write_result (int error)
{
  if (error == NO_ERROR)
    {
      return util_log_write_internal ("SUCCESS\n", NULL);
    }
  else
    {
      /* skip failed log */
    }

  return 0;
}

/*
 * util_log_write_errid () -
 *
 * message_id (in) :
 */
int
util_log_write_errid (int message_id, ...)
{
  int n;
  char msg_buf[UTIL_LOG_MAX_MSG_SIZE];
  const char *format;
  va_list arg_list;

  format = utility_get_generic_message (message_id);
  va_start (arg_list, message_id);
  n = vsnprintf (msg_buf, UTIL_LOG_MAX_MSG_SIZE, format, arg_list);
  if (n >= UTIL_LOG_MAX_MSG_SIZE)
    {
      msg_buf[UTIL_LOG_MAX_MSG_SIZE - 1] = '\0';
    }
  va_end (arg_list);

  return util_log_write_internal (msg_buf, "FAILURE: ");
}

/*
 * util_log_write_errstr () -
 *
 * format (in) :
 */
int
util_log_write_errstr (const char *format, ...)
{
  int n;
  char msg_buf[UTIL_LOG_MAX_MSG_SIZE];
  va_list arg_list;

  va_start (arg_list, format);
  n = vsnprintf (msg_buf, UTIL_LOG_MAX_MSG_SIZE, format, arg_list);
  if (n >= UTIL_LOG_MAX_MSG_SIZE)
    {
      msg_buf[UTIL_LOG_MAX_MSG_SIZE - 1] = '\0';
    }
  va_end (arg_list);

  return util_log_write_internal (msg_buf, "FAILURE: ");
}

/*
 * util_log_write_warnstr () -
 *
 * format (in) :
 */
int
util_log_write_warnstr (const char *format, ...)
{
  int n;
  char msg_buf[UTIL_LOG_MAX_MSG_SIZE];
  va_list arg_list;

  va_start (arg_list, format);
  n = vsnprintf (msg_buf, UTIL_LOG_MAX_MSG_SIZE, format, arg_list);
  if (n >= UTIL_LOG_MAX_MSG_SIZE)
    {
      msg_buf[UTIL_LOG_MAX_MSG_SIZE - 1] = '\0';
    }
  va_end (arg_list);

  return util_log_write_internal (msg_buf, "WARNING: ");
}

/*
 * util_log_write_command () -
 *
 * argc (in) :
 * argv (in) :
 * util_name_pos (in) : This is the position information that specifies the tool name in the argv[] array.
 *                      If the tool does not take a password as an argument, specify 0 or less.
 */
int
util_log_write_command (int argc, char *argv[], int util_name_pos)
{
  int i, long_pwd_arg_len;
  size_t remained_buf_length, str_len;
  char command_buf[UTIL_LOG_MAX_MSG_SIZE];
  char *p;
  const char *argv_str;
  const char *util_pwd_1 = NULL;
  const char *util_pwd_2 = NULL;

  memset (command_buf, '\0', UTIL_LOG_MAX_MSG_SIZE);
  p = command_buf;
  remained_buf_length = UTIL_LOG_MAX_MSG_SIZE - 1;

  long_pwd_arg_len = 0;
  if (util_name_pos > 0)
    {
      if ((strcasecmp ("unloaddb", argv[util_name_pos]) == 0) || (strcasecmp ("loaddb", argv[util_name_pos]) == 0))
    {
      long_pwd_arg_len = strlen ("--password");
      util_pwd_1 = "--password=****";
      util_pwd_2 = "--password ****";
    }
      else if ((strcasecmp ("tde", argv[util_name_pos]) == 0)
           || (strcasecmp ("killtran", argv[util_name_pos]) == 0)
           || (strcasecmp ("flashback", argv[util_name_pos]) == 0))
    {
      long_pwd_arg_len = strlen ("--dba-password");
      util_pwd_1 = "--dba-password=****";
      util_pwd_2 = "--dba-password ****";
    }
    }

  for (i = 0; i < argc && remained_buf_length > 0; i++)
    {
      argv_str = argv[i];

      if ((long_pwd_arg_len > 0) && (i > util_name_pos))
    {
      // check patterns that can be extracted normally using utility_get_option_string_value()      
      if (memcmp ("-p", argv_str, 2) == 0)
        {
          // -p <pwd>, -p<pwd>
          if (argv_str[2] == '\0')
        {
          i++;      // skip next argument
          argv_str = "-p ****";
        }
          else
        {
          argv_str = "-p****";
        }
        }
      else if (memcmp (util_pwd_1, argv_str, long_pwd_arg_len) == 0)
        {
          // --password <pwd>, --password=<pwd>, --password=
          if (argv_str[long_pwd_arg_len] == '\0')
        {
          i++;      // skip next argument
          argv_str = util_pwd_2;
        }
          else if (argv_str[long_pwd_arg_len] == '=')
        {
          argv_str = util_pwd_1;
        }
        }
    }

      str_len = strlen (argv_str);
      if (str_len > remained_buf_length)
    {
      break;
    }

      strcpy (p, argv_str);
      remained_buf_length -= str_len;
      p += str_len;
      if (i < argc - 1 && remained_buf_length > 0)
    {
      /* add white space */
      *p = ' ';
      p++;
      remained_buf_length--;
    }
      else if (i == argc - 1 && remained_buf_length > 0)
    {
      *p = '\n';
      p++;
      remained_buf_length--;
    }
    }

  return util_log_write_internal (command_buf, NULL);
}

/*
 * util_log_write_internal () -
 *
 * msg (in) :
 * prefix_str(in) :
 *
 */
static int
util_log_write_internal (const char *msg, const char *prefix_str)
{
  char *p;
  int ret = -1;
  int len, n;
  FILE *fp;

  if (util_Log_filename == NULL)
    {
      util_Log_filename = util_Log_filename_buf;
      envvar_logdir_file (util_Log_filename, PATH_MAX, UTIL_LOG_FILENAME);
    }

  p = util_Log_buffer;
  len = UTIL_LOG_BUFFER_SIZE;
  n = util_log_header (p, len);
  len -= n;
  p += n;

  if (len > 0)
    {
      n = snprintf (p, len, "%s%s", (prefix_str ? prefix_str : ""), msg);
      if (n >= len)
    {
      p[len - 1] = '\0';
    }
    }

  fp = util_log_file_fopen (util_Log_filename);
  if (fp == NULL)
    {
      return -1;
    }

  ret = fprintf (fp, "%s", util_Log_buffer);
  fclose (fp);

  return ret;
}

/*
 * util_log_header () -
 *
 * buf (out) :
 *
 */
static int
util_log_header (char *buf, size_t buf_len)
{
  struct tm tm, *tm_p;
  time_t sec;
  int len;
  char *p;
  const char *pid;
  int millisec;

  if (buf == NULL)
    {
      return 0;
    }

  /* current time */
  util_get_second_and_ms_since_epoch (&sec, &millisec);

  tm_p = localtime_r (&sec, &tm);

  len = (int) strftime (buf, buf_len, "%y-%m-%d %H:%M:%S", tm_p);
  p = buf + len;
  buf_len -= len;

  pid = envvar_get (UTIL_PID_ENVVAR_NAME);
  len += snprintf (p, buf_len, ".%03d (%s) ", millisec, ((pid == NULL) ? "    " : pid));

  assert (len <= UTIL_LOG_MAX_HEADER_LEN);

  return len;
}

/*
 * util_log_file_fopen () -
 *
 * path (in) :
 *
 */
static FILE *
util_log_file_fopen (const char *path)
{
  FILE *fp;

  assert (path != NULL);

  fp = fopen_and_lock (path);
  if (fp != NULL)
    {
      fseek (fp, 0, SEEK_END);
      if (ftell (fp) > prm_get_integer_value (PRM_ID_ER_LOG_SIZE))
    {
      fp = util_log_file_backup (fp, path);
    }
    }

  return fp;
}

/*
 * util_log_file_backup ()
 * fp (in) :
 * path (in) :
 *
 */
static FILE *
util_log_file_backup (FILE * fp, const char *path)
{
  char backup_file[PATH_MAX];

  assert (fp != NULL);
  assert (path != NULL);

  fclose (fp);
  sprintf (backup_file, "%s.bak", path);
  (void) unlink (backup_file);
  (void) rename (path, backup_file);

  return fopen_and_lock (path);
}

/*
 * fopen_and_lock ()
 * path (in) :
 *
 */
static FILE *
fopen_and_lock (const char *path)
{
#define MAX_RETRY_COUNT 10

  int retry_count = 0;
  FILE *fp;

retry:
  fp = fopen (path, "a+");
  if (fp != NULL)
    {
      if (lockf (fileno (fp), F_TLOCK, 0) < 0)
    {
      fclose (fp);

      if (retry_count < MAX_RETRY_COUNT)
        {
          SLEEP_MILISEC (0, 100);
          retry_count++;
          goto retry;
        }

      return NULL;
    }
    }

  return fp;
}

/*
 * util_bsearch () - generic binary search function. besides searching for key, it also returns the right key position
 *                   even when it is not found. the caller may then decide to insert the key.
 *                   if you only want to find key if it exists, it's better to use bsearch.
 *
 * return            : position of found key or the right position if key would be inserted.
 * key (in)          : point to key value
 * base (in)         : pointer to base array
 * n_elems (in)      : number of elements in array
 * size_elem (in)    : memory size of one element
 * func_compare (in) : compare function
 * out_found (out)   : output true if key was found, false otherwise
 *
 * note: the array of elements should be ordered by func_compare. duplicate keys are not allowed.
 */
int
util_bsearch (const void *key, const void *base, int n_elems, unsigned int sizeof_elem,
          int (*func_compare) (const void *, const void *), bool * out_found)
{
  int min = 0;
  int max = n_elems - 1;
  int mid = 0;
  int compare = 0;

  const void *elem;

  *out_found = false;

  /* binary search */
  /* keep searching and reducing the range until key is found, or until the range is reduced to 0 */
  while (min <= max)
    {
      /* get range midpoint */
      mid = (min + max) >> 1;

      /* get mid element */
      elem = (char *) base + (mid * sizeof_elem);

      /* compare with key */
      compare = func_compare (elem, key);

      /* did we find key? */
      if (compare == 0)
    {
      *out_found = true;
      return mid;
    }
      /* not found */
      /* reduce the search range */
      if (compare > 0)
    {
      /* search in lower range */
      max = mid - 1;
    }
      else
    {
      /* search in upper range */
      /* we also have to increment mid. if range is reduced to 0, the right position for key is next. */
      min = ++mid;
    }
    }

  /* not found */
  /* mid is the right position for key */
  return mid;
}

// *INDENT-OFF*
template <typename Duration>
void
util_get_seconds_and_rest_since_epoch (std::chrono::seconds &secs, Duration &rest)
{
  using clock_t = std::chrono::system_clock;
  using timept_secs = std::chrono::time_point<clock_t, std::chrono::seconds>;
  auto now_timepoint = clock_t::now ();
  timept_secs now_in_secs = std::chrono::time_point_cast<std::chrono::seconds> (now_timepoint);
  secs = now_in_secs.time_since_epoch ();
  rest = std::chrono::duration_cast<Duration> (now_timepoint - now_in_secs);
}

void
util_get_second_and_ms_since_epoch (time_t * secs, int *msec)
{
  assert (secs != NULL && msec != NULL);
  std::chrono::seconds secs_since_epoch;
  std::chrono::milliseconds rest_in_msec;
  util_get_seconds_and_rest_since_epoch<std::chrono::milliseconds> (secs_since_epoch, rest_in_msec);
  *secs = static_cast<time_t> (secs_since_epoch.count ());
  *msec = static_cast<int> (rest_in_msec.count ());
  assert (*msec < 1000);
}
// *INDENT-ON*