/*********************************************************
 * Copyright (C) 1998 VMware, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation version 2.1 and no later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the Lesser GNU General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA.
 *
 *********************************************************/


/*
 * systemLinux.c --
 *
 *    System-specific routines for all guest applications.
 *
 *    Linux implementation
 *
 */

#if !defined(__linux__) && !defined(__FreeBSD__) && !defined(sun) && !defined(__APPLE__)
#   error This file should not be compiled
#endif


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/times.h>
#include <netdb.h>
#ifdef sun
# include <sys/sockio.h>
#endif

#include <sys/types.h>
#include <sys/socket.h>
/* <netinet/in.h> must precede <arpa/in.h> for FreeBSD to compile. */
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <sys/ioctl.h>

#ifdef __FreeBSD__
#include "ifaddrs.h"
#endif

#include "vm_assert.h"
#include "system.h"
#include "debug.h"
#include "posix.h"
#include "unicode.h"
#include "dynbuf.h"
#include "hashTable.h"
#include "strutil.h"

#define MAX_IFACES      4
#define LOOPBACK        "lo"
#ifndef INET_ADDRSTRLEN
#define INET_ADDRSTRLEN 16
#endif


/*
 * Data types
 */

/*
 * DynBuf container used by SNEForEachCallback.
 *
 * The SNE prefix is short for "System Native Environ".
 */

typedef struct {
   DynBuf *nativeEnvironStrings;        // FOO=BAR<NUL>BAZ=BOOM<NUL><NUL>
   DynBuf *nativeEnvironOffsets;        // off_ts relative to nativeEnvironStrings->data
} SNEBufs;


/*
 * Local function prototypes
 */


/*
 * "System Native Environ" (SNE) helpers.
 */
static HashTable *SNEBuildHash(const char **nativeEnviron);
static const char **SNEHashToEnviron(HashTable *environTable);
static int SNEForEachCallback(const char *key, void *value, void *clientData);


/*
 * Global functions
 */


/*
 * System_Uptime --
 *
 *    Retrieve the time (in hundredth of s.) since the system has started.
 *
 *    Note: On 32-bit Linux, whether you read /proc/uptime (2 system calls: seek(2)
 *          and read(2)) or times(2) (1 system call), the uptime information
 *          comes from the 'jiffies' kernel variable, whose type is 'unsigned
 *          long'. This means that on a ix86 with HZ == 100, it will wrap after
 *          497 days. This function can detect the wrapping and still return
 *          a correct, monotonic, 64 bit wide value if it is called at least
 *          once every 497 days.
 *
 * Result:
 *    The value on success
 *    -1 on failure (never happens in this implementation)
 *
 * Side effects:
 *    None
 *
 *----------------------------------------------------------------------
 */

uint64
System_Uptime(void)
{
   /*
    * Dummy variable b/c times(NULL) segfaults on FreeBSD 3.2 --greg
    */
   struct tms tp;

#if !defined (VM_X86_64)
   static uint64 base = 0;
   static unsigned long last = 0;
   uint32  current;


   ASSERT(sizeof(current) == sizeof(clock_t));

   current = times(&tp);     // 100ths of a second

   if (current < last) {
      /* The 'jiffies' kernel variable wrapped */
      base += (uint64)1 << (sizeof(current) * 8);
   }

   return base + (last = current);
#else  // VM_X86_64

   return times(&tp);
#endif
}


/*
 *-----------------------------------------------------------------------------
 *
 * System_GetTimeAsString --
 *
 *      Returns the current time as a formatted string, useful for prepending
 *      to debugging output.
 *
 *      For example: "Oct 05 18:03:24.948"
 *
 * Results:
 *      On success, allocates and returns a string containing the formatted
 *      time.
 *      On failure, returns NULL.
 *
 * Side effects:
 *      None.
 *
 *-----------------------------------------------------------------------------
 */

Unicode
System_GetTimeAsString(void)
{
   struct timeval tv;
   time_t sec;
   int msec;
   size_t charsWritten;
   size_t bufSize = 8; // Multiplied by 2 for the initial allocation.
   char *buf = NULL;
   Unicode dateTime = NULL;
   Unicode output = NULL;

   if (gettimeofday(&tv, NULL)) {
      goto out;
   }
   sec = tv.tv_sec;
   msec = tv.tv_usec / 1000;

   /*
    * Loop repeatedly trying to format the time into a buffer, doubling the
    * buffer with each failure. This should be safe as the manpage for
    * strftime(3) seems to suggest that it only fails if the buffer isn't large
    * enough.
    *
    * The resultant string is encoded according to the current locale.
    */
   do {
      char *newBuf;
      bufSize *= 2;

      newBuf = realloc(buf, bufSize);
      if (newBuf == NULL) {
         goto out;
      }
      buf = newBuf;
      charsWritten = strftime(buf, bufSize, "%b %d %H:%M:%S", localtime(&sec));
   } while (charsWritten == 0);

   /*
    * Append the milliseconds field, but only after converting the date/time
    * string from encoding specified in the current locale to an opaque type.
    */
   dateTime = Unicode_Alloc(buf, STRING_ENCODING_DEFAULT);
   if (dateTime == NULL) {
      goto out;
   }
   output = Unicode_Format("%s.%03d", dateTime, msec);

  out:
   free(buf);
   Unicode_Free(dateTime);
   return output;
}


/*
 *-----------------------------------------------------------------------------
 *
 * System_Shutdown --
 *
 *   Initiate system shutdown.
 *
 * Return value:
 *    None.
 *
 * Side effects:
 *    None.
 *
 *-----------------------------------------------------------------------------
 */

void
System_Shutdown(Bool reboot)  // IN: "reboot or shutdown" flag
{
   char *cmd;

   if (reboot) {
#if defined(sun)
      cmd = "/usr/sbin/shutdown -g 0 -i 6 -y";
#else
      cmd = "/sbin/shutdown -r now";
#endif
   } else {
#if __FreeBSD__
      cmd = "/sbin/shutdown -p now";
#elif defined(sun)
      cmd = "/usr/sbin/shutdown -g 0 -i 5 -y";
#else
      cmd = "/sbin/shutdown -h now";
#endif
   }
   if (system(cmd) == -1) {
      fprintf(stderr, "Unable to execute %s command: \"%s\"\n",
              reboot ? "reboot" : "shutdown", cmd);
   }
}


/*
 *-----------------------------------------------------------------------------
 *
 * System_IsUserAdmin --
 *
 *    On Windows this functions checks if the calling user has membership in
 *    the Administrators group (for NT platforms). On POSIX machines, we simply
 *    check if the user's effective UID is root.
 *
 * Return value:
 *    TRUE if the user has an effective UID of root.
 *    FALSE if not.
 *
 * Side effects:
 *    None.
 *
 *-----------------------------------------------------------------------------
 */

Bool
System_IsUserAdmin(void)
{
   return geteuid() == 0;
}


/*
 *----------------------------------------------------------------------
 *
 * System_GetEnv --
 *    Read environment variables.
 *
 * Results:
 *    A copy of the environment variable encoded in UTF-8.
 *
 * Side effects:
 *    None.
 *
 *----------------------------------------------------------------------
 */

char *
System_GetEnv(Bool global,           // IN
              const char *valueName) // IN: UTF-8
{
   char *result;

#if defined(sun)
   result = NULL;
#else
   result = Posix_Getenv(valueName);
#endif

   if (NULL != result) {
      result = strdup(result);
   }

   return(result);
} // System_GetEnv


/*
 *----------------------------------------------------------------------
 *
 * System_SetEnv --
 *
 *    Write environment variables.
 *
 *    On Linux, this only affects the local process. The global flag
 *    is ignored.
 *
 * Results:
 *    0 if success, -1 otherwise.
 *
 * Side effects:
 *    Changes the environment variable.
 *
 *----------------------------------------------------------------------
 */

int
System_SetEnv(Bool global,      // IN
              const char *valueName,  // IN: UTF-8
              const char *value)      // IN: UTF-8
{
#if defined(sun)
   return(-1);
#else
   return Posix_Setenv(valueName, value, 1);
#endif
} // System_SetEnv


/*
 *-----------------------------------------------------------------------------
 *
 * System_GetNodeName --
 *
 *      Returns the guest's configured node name.  Does not necessarily
 *      correspond to a proper DNS host name.
 *
 * Results:
 *      On success, returns TRUE with node name written to outBuf.
 *
 * Side effects:
 *      None.
 *
 *-----------------------------------------------------------------------------
 */

Bool
System_GetNodeName(size_t outBufSize, // IN:  size of output buffer
                   char *outBuf)      // OUT: output buffer
{
   ASSERT(outBuf);

   if (gethostname(outBuf, outBufSize) < 0) {
      Debug("Error, gethostname failed\n");
      return FALSE;
   }

   return TRUE;
}


/*
 *-----------------------------------------------------------------------------
 *
 * System_GetNativeEnviron --
 *
 *      Returns a copy of the native / unwrapped environment.
 *
 *      VMware's compatibility library wrappers override certain environment
 *      variables to make use of shipped libraries.  This creates the
 *      "compatibility environment".  Overridden variables are saved into
 *      corresponding VMWARE_-prefixed variables.  This routine recreates the
 *      "native environment" by restoring VMWARE_-prefixed variable values to
 *      their native equivalents.
 *
 *      Every value created by the wrapper begins with a 1 or 0 to indicate
 *      whether the value was set in the native environment.  Based on this:
 *        VMWARE_FOO="1foo"               -> FOO="foo"
 *        VMWARE_FOO="1"                  -> FOO=""
 *        VMWARE_FOO="0"                  -> FOO is unset in the native environment
 *
 *      Variables without the VMWARE_ prefix are just copied over to the new
 *      environment.  Note, of course, that VMWARE_-prefixed variables take
 *      precedence.  (I.e., when we encounter a VMWARE_ prefix, we'll
 *      unconditionally record the value.  For all other variables, we'll
 *      copy only if that variable had not already been set.)
 *
 * Results:
 *      An array of strings representing the native environment.  (I.e.,
 *      use it as you would environ(7).)  NB:  Memory allocation failures
 *      are considered fatal.
 *
 * Side effects:
 *      This routine allocates memory.  It's up to the caller to free it by
 *      passing the returned value to System_FreeNativeEnviron.
 *
 *      This routine assumes that keys will show up in environ only once each.
 *
 *-----------------------------------------------------------------------------
 */

const char **
System_GetNativeEnviron(const char **compatEnviron)
                           // IN: original "compatibility" environment
{
   HashTable *environTable;
   const char **nativeEnviron;

   environTable = SNEBuildHash(compatEnviron);
   nativeEnviron = SNEHashToEnviron(environTable);

   HashTable_Free(environTable);

   return nativeEnviron;
}


/*
 *-----------------------------------------------------------------------------
 *
 * System_FreeNativeEnviron --
 *
 *      Frees memory allocated by System_GetNativeEnviron.
 *
 * Results:
 *      Frees memory.
 *
 * Side effects:
 *      None.
 *
 *-----------------------------------------------------------------------------
 */

void
System_FreeNativeEnviron(const char **nativeEnviron)
                            // IN: environment returned by System_GetNativeEnviron
{
   /*
    * nativeEnviron is an array of strings, and all of the strings are located in a
    * single contiguous buffer.  This makes freeing both array and buffer easy.
    */
   char *stringBuf = (char *)*nativeEnviron;

   free(stringBuf);
   free(nativeEnviron);
}


/*
 * Local functions
 */


/*
 *-----------------------------------------------------------------------------
 *
 * SNEBuildHash --
 *
 *      Compile hash table of environment variables.  See System_GetNativeEnviron
 *      for rules on precedence.
 *
 * Results:
 *      Pointer to populated hash table.  This cannot fail, as any memory failures
 *      within the HashTable and StrUtil libraries are considered fatal.
 *
 * Side effects:
 *      Caller is responsible for freeing returned table with HashTable_Free.
 *
 *-----------------------------------------------------------------------------
 */

static HashTable *
SNEBuildHash(const char **compatEnviron)
                // IN: original "compatibility" environment
{
   HashTable *environTable;
   const char **p;

   /*
    * Number of buckets picked arbitrarily.  We're more interested in having an
    * associative array than raw table performance.
    */
   environTable = HashTable_Alloc(64, HASH_STRING_KEY | HASH_FLAG_COPYKEY, free);

   for (p = compatEnviron; p && *p; p++) {
      const size_t prefixLength = sizeof "VMWARE_" - 1;
      char *key;
      char *value;
      unsigned int index;

      index = 0;
      key = StrUtil_GetNextToken(&index, *p, "=");
      if (!key) {
         /* XXX Must empty environment variables still contain a '=' delimiter? */
         Debug("%s: Encountered environment entry without '='.\n", __func__);
         continue;
      }

      /*
       * Copy the value beginning after the '=' delimiter (even if it's empty).
       */
      ++index;
      value = Util_SafeStrdup(&(*p)[index]);

      if (StrUtil_StartsWith(key, "VMWARE_") &&
          key[prefixLength] != '\0' &&
          (value[0] == '0' || value[0] == '1')) {
         /*
          * Okay, this appears to be one of the wrapper's variables, so let's
          * figure out the original environment variable name (by just indexing
          * past the prefix) and value (by indexing past the "was this variable
          * in the native environment?" marker).
          *
          * XXX Should we move this marker to a separate header?
          */
         char *realKey = &key[prefixLength];
         char *realValue = (value[0] == '0')
                           ? NULL
                           : Util_SafeStrdup(&value[1]);
         HashTable_ReplaceOrInsert(environTable, realKey, realValue);
      } else {
         HashTable_LookupOrInsert(environTable, key, value);
      }

      /*
       * The hash table makes a copy of our key, and it takes ownership of inserted
       * values (via our passed freeing function).  So that means we're responsible
       * for freeing 'key', but -not- 'value'.
       */
      free(key);
   }

   return environTable;
}


/*
 *-----------------------------------------------------------------------------
 *
 * SNEHashToEnviron --
 *
 *      Builds up an array of strings representing a new environment based on
 *      the caller's hash table.
 *
 * Results:
 *      Pointer to the new environment.  As memory allocation failures are
 *      considered fatal, this routine will not return NULL.
 *
 * Side effects:
 *      This is expected to be returned to System_GetNativeEnviron's caller,
 *      and s/he is to free it via System_FreeNativeEnviron.
 *
 *-----------------------------------------------------------------------------
 */

static const char **
SNEHashToEnviron(HashTable *environTable)   // IN:
{
   DynBuf nativeEnvironOffsets;
   DynBuf nativeEnvironStrings;

   SNEBufs anonBufs = {
      .nativeEnvironStrings = &nativeEnvironStrings,
      .nativeEnvironOffsets = &nativeEnvironOffsets,
   };

   const char **nativeEnviron;
   off_t *offsetIter;
   char *stringsBase;

   unsigned int numStrings;
   unsigned int i;

   DynBuf_Init(&nativeEnvironStrings);
   DynBuf_Init(&nativeEnvironOffsets);

   /*
    * Write out strings and string offsets to the dynbufs.
    */
   HashTable_ForEach(environTable, SNEForEachCallback, &anonBufs);
   ASSERT_MEM_ALLOC(DynBuf_Trim(&nativeEnvironStrings));

   /*
    * Allocate final buffer to contain the string array.  Include extra entry
    * for terminator.
    */
   numStrings = DynBuf_GetSize(&nativeEnvironOffsets) / sizeof *offsetIter;
   nativeEnviron = Util_SafeCalloc(1 + numStrings, sizeof *nativeEnviron);

   stringsBase = DynBuf_Get(&nativeEnvironStrings);
   offsetIter = DynBuf_Get(&nativeEnvironOffsets);

   for (i = 0; i < numStrings; i++, offsetIter++) {
      nativeEnviron[i] = stringsBase + *offsetIter;
   }
   nativeEnviron[i] = NULL;

   /*
    * Cleanup.
    *
    * Note the subtle difference in how these are handled:
    *   - The offsets are used only to build the array of strings, which is why that
    *     dynbuf is destroyed.
    *   - The buffer containing all of the environment strings, however, is persistent
    *     and is actually returned to the caller (indirectly via nativeEnviron).  As
    *     such it's only detached from the DynBuf.
    */
   DynBuf_Destroy(&nativeEnvironOffsets);
   DynBuf_Detach(&nativeEnvironStrings);

   return nativeEnviron;
}


/*
 *-----------------------------------------------------------------------------
 *
 * SNEForEachCallback --
 *
 *      Given a key (environment variable name) and value, appends a string of
 *      "key=value" to the nativeEnvironStrings DynBuf.  Also appends a pointer
 *      to the new string to the nativeEnviron DynBuf.
 *
 * Results:
 *      Always zero.  Memory allocation failures are considered fatal.
 *
 * Side effects:
 *      None.
 *
 *-----------------------------------------------------------------------------
 */

static int
SNEForEachCallback(const char *key,     // IN: environment variable
                   void *value,         // IN: environment value
                   void *clientData)    // IN/OUT: DynBuf container (SNEBufs)
{
   DynBuf *nativeEnvironStrings = ((SNEBufs *)clientData)->nativeEnvironStrings;
   DynBuf *nativeEnvironOffsets = ((SNEBufs *)clientData)->nativeEnvironOffsets;
   size_t itemSize;
   char *itemBuf;
   off_t itemOffset;

   /*
    * A NULL value indicates that this variable is not to be set.
    */
   if (value == NULL) {
      return 0;
   }

   /* Determine the length of the new string inc. '=' delimiter and NUL. */
   itemSize = strlen(key) + strlen(value) + sizeof "=";
   itemBuf = Util_SafeMalloc(itemSize);

   /* Create new "key=value" string. */
   snprintf(itemBuf, itemSize, "%s=%s", key, (char *)value);

   ASSERT_MEM_ALLOC(DynBuf_AppendString(nativeEnvironStrings, itemBuf));

   /*
    * Get the relative offset of our newly added string (relative to the DynBuf's base
    * address), and then append that to nativeEnvironOffsets.
    */
   itemOffset = DynBuf_GetSize(nativeEnvironStrings) - itemSize;
   ASSERT_MEM_ALLOC(DynBuf_Append(nativeEnvironOffsets, &itemOffset, sizeof itemOffset));

   free(itemBuf);

   return 0;
}
