/*
 * sysinfo.c :  information about the running system
 *
 * ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one
 *    or more contributor license agreements.  See the NOTICE file
 *    distributed with this work for additional information
 *    regarding copyright ownership.  The ASF licenses this file
 *    to you 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.
 * ====================================================================
 */



#define APR_WANT_STRFUNC
#include <apr_want.h>

#include <apr_lib.h>
#include <apr_pools.h>
#include <apr_file_info.h>
#include <apr_signal.h>
#include <apr_strings.h>
#include <apr_thread_proc.h>
#include <apr_version.h>
#include <apu_version.h>

#include "svn_pools.h"
#include "svn_ctype.h"
#include "svn_dirent_uri.h"
#include "svn_error.h"
#include "svn_io.h"
#include "svn_string.h"
#include "svn_utf.h"
#include "svn_version.h"

#include "private/svn_sqlite.h"
#include "private/svn_subr_private.h"
#include "private/svn_utf_private.h"

#include "sysinfo.h"
#include "svn_private_config.h"

#if HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#if HAVE_SYS_UTSNAME_H
#include <sys/utsname.h>
#endif

#if HAVE_UNISTD_H
#include <unistd.h>
#endif

#if HAVE_ELF_H
#include <elf.h>
#endif

#ifdef SVN_HAVE_MACOS_PLIST
#include <CoreFoundation/CoreFoundation.h>
#include <AvailabilityMacros.h>
# ifndef MAC_OS_X_VERSION_10_6
#  define MAC_OS_X_VERSION_10_6  1060
# endif
#endif

#ifdef SVN_HAVE_MACHO_ITERATE
#include <mach-o/dyld.h>
#include <mach-o/loader.h>
#endif

#if HAVE_UNAME
static const char *canonical_host_from_uname(apr_pool_t *pool);
# ifndef SVN_HAVE_MACOS_PLIST
static const char *release_name_from_uname(apr_pool_t *pool);
# endif
#endif

#ifdef WIN32
static const char *win32_canonical_host(apr_pool_t *pool);
static const char *win32_release_name(apr_pool_t *pool);
static const apr_array_header_t *win32_shared_libs(apr_pool_t *pool);
#endif /* WIN32 */

#ifdef SVN_HAVE_MACOS_PLIST
static const char *macos_release_name(apr_pool_t *pool);
#endif

#ifdef SVN_HAVE_MACHO_ITERATE
static const apr_array_header_t *macos_shared_libs(apr_pool_t *pool);
#endif


#if __linux__
static const char *linux_release_name(apr_pool_t *pool);
static const apr_array_header_t *linux_shared_libs(apr_pool_t *pool);
#endif

const char *
svn_sysinfo__canonical_host(apr_pool_t *pool)
{
#ifdef WIN32
  return win32_canonical_host(pool);
#elif HAVE_UNAME
  return canonical_host_from_uname(pool);
#else
  return "unknown-unknown-unknown";
#endif
}


const char *
svn_sysinfo__release_name(apr_pool_t *pool)
{
#ifdef WIN32
  return win32_release_name(pool);
#elif defined(SVN_HAVE_MACOS_PLIST)
  return macos_release_name(pool);
#elif __linux__
  return linux_release_name(pool);
#elif HAVE_UNAME
  return release_name_from_uname(pool);
#else
  return NULL;
#endif
}

const apr_array_header_t *
svn_sysinfo__linked_libs(apr_pool_t *pool)
{
  svn_version_ext_linked_lib_t *lib;
  apr_array_header_t *array = apr_array_make(pool, 7, sizeof(*lib));
  int lz4_version = svn_lz4__runtime_version();

  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
  lib->name = "APR";
  lib->compiled_version = APR_VERSION_STRING;
  lib->runtime_version = apr_pstrdup(pool, apr_version_string());

/* Don't list APR-Util if it isn't linked in, which it may not be if
 * we're using APR 2.x+ which combined APR-Util into APR. */
#ifdef APU_VERSION_STRING
  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
  lib->name = "APR-Util";
  lib->compiled_version = APU_VERSION_STRING;
  lib->runtime_version = apr_pstrdup(pool, apu_version_string());
#endif

  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
  lib->name = "Expat";
  lib->compiled_version = apr_pstrdup(pool, svn_xml__compiled_version());
  lib->runtime_version = apr_pstrdup(pool, svn_xml__runtime_version());

  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
  lib->name = "SQLite";
  lib->compiled_version = apr_pstrdup(pool, svn_sqlite__compiled_version());
#ifdef SVN_SQLITE_INLINE
  lib->runtime_version = NULL;
#else
  lib->runtime_version = apr_pstrdup(pool, svn_sqlite__runtime_version());
#endif

  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
  lib->name = "Utf8proc";
  lib->compiled_version = apr_pstrdup(pool, svn_utf__utf8proc_compiled_version());
  lib->runtime_version = apr_pstrdup(pool, svn_utf__utf8proc_runtime_version());

  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
  lib->name = "ZLib";
  lib->compiled_version = apr_pstrdup(pool, svn_zlib__compiled_version());
  lib->runtime_version = apr_pstrdup(pool, svn_zlib__runtime_version());

  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
  lib->name = "LZ4";
  lib->compiled_version = apr_pstrdup(pool, svn_lz4__compiled_version());

  lib->runtime_version = apr_psprintf(pool, "%d.%d.%d",
                                      lz4_version / 100 / 100,
                                      (lz4_version / 100) % 100,
                                      lz4_version % 100);

  return array;
}

const apr_array_header_t *
svn_sysinfo__loaded_libs(apr_pool_t *pool)
{
#ifdef WIN32
  return win32_shared_libs(pool);
#elif defined(SVN_HAVE_MACHO_ITERATE)
  return macos_shared_libs(pool);
#elif __linux__
  return linux_shared_libs(pool);
#else
  return NULL;
#endif
}


#if HAVE_UNAME
static const char*
canonical_host_from_uname(apr_pool_t *pool)
{
  const char *machine = "unknown";
  const char *vendor = "unknown";
  const char *sysname = "unknown";
  const char *sysver = "";
  struct utsname info;

  if (0 <= uname(&info))
    {
      svn_error_t *err;
      const char *tmp;

      err = svn_utf_cstring_to_utf8(&tmp, info.machine, pool);
      if (err)
        svn_error_clear(err);
      else
        machine = tmp;

      err = svn_utf_cstring_to_utf8(&tmp, info.sysname, pool);
      if (err)
        svn_error_clear(err);
      else
        {
          char *lwr = apr_pstrdup(pool, tmp);
          char *it = lwr;
          while (*it)
            {
              if (svn_ctype_isupper(*it))
                *it = apr_tolower(*it);
              ++it;
            }
          sysname = lwr;
        }

      if (0 == strcmp(sysname, "darwin"))
        vendor = "apple";
      if (0 == strcmp(sysname, "linux"))
        sysver = "-gnu";
      else
        {
          err = svn_utf_cstring_to_utf8(&tmp, info.release, pool);
          if (err)
            svn_error_clear(err);
          else
            {
              apr_size_t n = strspn(tmp, ".0123456789");
              if (n > 0)
                {
                  char *ver = apr_pstrdup(pool, tmp);
                  ver[n] = 0;
                  sysver = ver;
                }
              else
                sysver = tmp;
            }
        }
    }

  return apr_psprintf(pool, "%s-%s-%s%s", machine, vendor, sysname, sysver);
}

# ifndef SVN_HAVE_MACOS_PLIST
/* Generate a release name from the uname(3) info, effectively
   returning "`uname -s` `uname -r`". */
static const char *
release_name_from_uname(apr_pool_t *pool)
{
  struct utsname info;
  if (0 <= uname(&info))
    {
      svn_error_t *err;
      const char *sysname;
      const char *sysver;

      err = svn_utf_cstring_to_utf8(&sysname, info.sysname, pool);
      if (err)
        {
          sysname = NULL;
          svn_error_clear(err);
        }


      err = svn_utf_cstring_to_utf8(&sysver, info.release, pool);
      if (err)
        {
          sysver = NULL;
          svn_error_clear(err);
        }

      if (sysname || sysver)
        {
          return apr_psprintf(pool, "%s%s%s",
                              (sysname ? sysname : ""),
                              (sysver ? (sysname ? " " : "") : ""),
                              (sysver ? sysver : ""));
        }
    }
  return NULL;
}
# endif  /* !SVN_HAVE_MACOS_PLIST */
#endif  /* HAVE_UNAME */


#if __linux__
/* Find the first whitespace character in a stringbuf.
   Analogous to svn_stringbuf_first_non_whitespace. */
static apr_size_t
stringbuf_first_whitespace(const svn_stringbuf_t *str)
{
  apr_size_t i;
  for (i = 0; i < str->len; ++i)
    {
      if (svn_ctype_isspace(str->data[i]))
        return i;
    }
  return str->len;
}

/* Skip a whitespace-delimited field in a stringbuf. */
static void
stringbuf_skip_whitespace_field(svn_stringbuf_t *str)
{
  apr_size_t i;
  i = stringbuf_first_whitespace(str);
  svn_stringbuf_leftchop(str, i);
  i = svn_stringbuf_first_non_whitespace(str);
  svn_stringbuf_leftchop(str, i);
}

/* Split a stringbuf into a key/value pair.
   Return the key, leaving the stripped value in the stringbuf. */
static const char *
stringbuf_split_key(svn_stringbuf_t *buffer, char delim)
{
  char *key;
  char *end;

  end = strchr(buffer->data, delim);
  if (!end)
    return NULL;

  svn_stringbuf_strip_whitespace(buffer);

  /* Now we split the currently allocated buffer in two parts:
      - a const char * HEAD
      - the remaining stringbuf_t. */

  /* Create HEAD as '\0' terminated const char * */
  key = buffer->data;
  end = strchr(key, delim);
  *end = '\0';

  /* And update the TAIL to be a smaller, but still valid stringbuf */
  buffer->data = end + 1;
  buffer->len -= 1 + end - key;
  buffer->blocksize -= 1 + end - key;

  svn_stringbuf_strip_whitespace(buffer);

  return key;
}

/* Parse `/usr/bin/lsb_rlease --all` */
static const char *
lsb_release(apr_pool_t *pool)
{
  static const char *const args[3] =
    {
      "/usr/bin/lsb_release",
      "--all",
      NULL
    };

  const char *distributor = NULL;
  const char *description = NULL;
  const char *release = NULL;
  const char *codename = NULL;

  apr_proc_t lsbproc;
  svn_stream_t *lsbinfo;
  svn_error_t *err;

  /* Run /usr/bin/lsb_release --all < /dev/null 2>/dev/null */
  {
    apr_file_t *stdin_handle;
    apr_file_t *stdout_handle;

    err = svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
                           APR_READ, APR_OS_DEFAULT, pool);
    if (!err)
      err = svn_io_file_open(&stdout_handle, SVN_NULL_DEVICE_NAME,
                             APR_WRITE, APR_OS_DEFAULT, pool);
    if (!err)
      err = svn_io_start_cmd3(&lsbproc, NULL, args[0], args, NULL, FALSE,
                              FALSE, stdin_handle,
                              TRUE, NULL,
                              FALSE, stdout_handle,
                              pool);
    if (err)
      {
        svn_error_clear(err);
        return NULL;
      }
  }

  /* Parse the output and try to populate the  */
  lsbinfo = svn_stream_from_aprfile2(lsbproc.out, TRUE, pool);
  if (lsbinfo)
    {
      for (;;)
        {
          svn_boolean_t eof = FALSE;
          svn_stringbuf_t *line;
          const char *key;

          err = svn_stream_readline(lsbinfo, &line, "\n", &eof, pool);
          if (err || eof)
            break;

          key = stringbuf_split_key(line, ':');
          if (!key)
            continue;

          if (0 == svn_cstring_casecmp(key, "Distributor ID"))
            distributor = line->data;
          else if (0 == svn_cstring_casecmp(key, "Description"))
            description = line->data;
          else if (0 == svn_cstring_casecmp(key, "Release"))
            release = line->data;
          else if (0 == svn_cstring_casecmp(key, "Codename"))
            codename = line->data;
        }
      err = svn_error_compose_create(err,
                                     svn_stream_close(lsbinfo));
      if (err)
        {
          svn_error_clear(err);
          apr_proc_kill(&lsbproc, SIGKILL);
          return NULL;
        }
    }

  /* Reap the child process */
  err = svn_io_wait_for_cmd(&lsbproc, "", NULL, NULL, pool);
  if (err)
    {
      svn_error_clear(err);
      return NULL;
    }

  if (description)
    return apr_psprintf(pool, "%s%s%s%s", description,
                        (codename ? " (" : ""),
                        (codename ? codename : ""),
                        (codename ? ")" : ""));
  if (distributor)
    return apr_psprintf(pool, "%s%s%s%s%s%s", distributor,
                        (release ? " " : ""),
                        (release ? release : ""),
                        (codename ? " (" : ""),
                        (codename ? codename : ""),
                        (codename ? ")" : ""));

  return NULL;
}

/* Read /etc/os-release, as documented here:
 * http://www.freedesktop.org/software/systemd/man/os-release.html
 */
static const char *
systemd_release(apr_pool_t *pool)
{
  svn_error_t *err;
  svn_stream_t *stream;

  /* Open the file. */
  err = svn_stream_open_readonly(&stream, "/etc/os-release", pool, pool);
  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
    {
      svn_error_clear(err);
      err = svn_stream_open_readonly(&stream, "/usr/lib/os-release", pool,
                                     pool);
    }
  if (err)
    {
      svn_error_clear(err);
      return NULL;
    }

  /* Look for the PRETTY_NAME line. */
  while (TRUE)
    {
      svn_stringbuf_t *line;
      svn_boolean_t eof;

      err = svn_stream_readline(stream, &line, "\n", &eof, pool);
      if (err)
        {
          svn_error_clear(err);
          return NULL;
        }

      if (!strncmp(line->data, "PRETTY_NAME=", 12))
        {
          svn_stringbuf_t *release_name;

          /* The value may or may not be enclosed by double quotes.  We don't
           * attempt to strip them. */
          release_name = svn_stringbuf_create(line->data + 12, pool);
          svn_error_clear(svn_stream_close(stream));
          svn_stringbuf_strip_whitespace(release_name);
          return release_name->data;
        }

      if (eof)
        break;
    }

  /* The file did not contain a PRETTY_NAME line. */
  svn_error_clear(svn_stream_close(stream));
  return NULL;
}

/* Read the whole contents of a file. */
static svn_stringbuf_t *
read_file_contents(const char *filename, apr_pool_t *pool)
{
  svn_error_t *err;
  svn_stringbuf_t *buffer;

  err = svn_stringbuf_from_file2(&buffer, filename, pool);
  if (err)
    {
      svn_error_clear(err);
      return NULL;
    }

  return buffer;
}

/* Strip everything but the first line from a stringbuf. */
static void
stringbuf_first_line_only(svn_stringbuf_t *buffer)
{
  char *eol = strchr(buffer->data, '\n');
  if (eol)
    {
      *eol = '\0';
      buffer->len = 1 + eol - buffer->data;
    }
  svn_stringbuf_strip_whitespace(buffer);
}

/* Look at /etc/redhat_release to detect RHEL/Fedora/CentOS. */
static const char *
redhat_release(apr_pool_t *pool)
{
  svn_stringbuf_t *buffer = read_file_contents("/etc/redhat-release", pool);
  if (buffer)
    {
      stringbuf_first_line_only(buffer);
      return buffer->data;
    }
  return NULL;
}

/* Look at /etc/SuSE-release to detect non-LSB SuSE. */
static const char *
suse_release(apr_pool_t *pool)
{
  const char *release = NULL;
  const char *codename = NULL;

  svn_stringbuf_t *buffer = read_file_contents("/etc/SuSE-release", pool);
  svn_stringbuf_t *line;
  svn_stream_t *stream;
  svn_boolean_t eof;
  svn_error_t *err;
  if (!buffer)
      return NULL;

  stream = svn_stream_from_stringbuf(buffer, pool);
  err = svn_stream_readline(stream, &line, "\n", &eof, pool);
  if (err || eof)
    {
      svn_error_clear(err);
      return NULL;
    }

  svn_stringbuf_strip_whitespace(line);
  release = line->data;

  for (;;)
    {
      const char *key;

      err = svn_stream_readline(stream, &line, "\n", &eof, pool);
      if (err || eof)
        {
          svn_error_clear(err);
          break;
        }

      key = stringbuf_split_key(line, '=');
      if (!key)
        continue;

      if (0 == strncmp(key, "CODENAME", 8))
        codename = line->data;
    }

  return apr_psprintf(pool, "%s%s%s%s",
                      release,
                      (codename ? " (" : ""),
                      (codename ? codename : ""),
                      (codename ? ")" : ""));
}

/* Look at /etc/debian_version to detect non-LSB Debian. */
static const char *
debian_release(apr_pool_t *pool)
{
  svn_stringbuf_t *buffer = read_file_contents("/etc/debian_version", pool);
  if (!buffer)
      return NULL;

  stringbuf_first_line_only(buffer);
  return apr_pstrcat(pool, "Debian ", buffer->data, SVN_VA_NULL);
}

/* Try to find the Linux distribution name, or return info from uname. */
static const char *
linux_release_name(apr_pool_t *pool)
{
  const char *uname_release = release_name_from_uname(pool);

  /* Try anything that has /usr/bin/lsb_release.
     Covers, for example, Debian, Ubuntu and SuSE.  */
  const char *release_name = lsb_release(pool);

  /* Try the systemd way (covers Arch). */
  if (!release_name)
    release_name = systemd_release(pool);

  /* Try RHEL/Fedora/CentOS */
  if (!release_name)
    release_name = redhat_release(pool);

  /* Try Non-LSB SuSE */
  if (!release_name)
    release_name = suse_release(pool);

  /* Try non-LSB Debian */
  if (!release_name)
    release_name = debian_release(pool);

  if (!release_name)
    return uname_release;

  if (!uname_release)
    return release_name;

  return apr_psprintf(pool, "%s [%s]", release_name, uname_release);
}

#if HAVE_ELF_H
/* Parse a hexadecimal number as a pointer value. */
static const unsigned char *
parse_pointer_value(const char *start, const char *limit, char **end)
{
  const unsigned char *ptr;
  const apr_uint64_t val = (apr_uint64_t)apr_strtoi64(start, end, 16);

  if (errno                     /* overflow */
      || *end == start          /* no valid digits */
      || *end >= limit)         /* representation too long */
    return NULL;

  ptr = (const unsigned char*)val;
  if (val != (apr_uint64_t)ptr)  /* truncated value */
    return NULL;

  return ptr;
}

/* Read the ELF header at the mapping position to check if this is a shared
   library. We only look at the ELF identification and the type. The format is
   described here:
       http://www.skyfree.org/linux/references/ELF_Format.pdf
*/
static svn_boolean_t
check_elf_header(const unsigned char *map_start,
                 const unsigned char *map_end)
{
  /* A union of all known ELF header types, for size checks. */
  union max_elf_header_size_t
  {
    Elf32_Ehdr header_32;
    Elf64_Ehdr header_64;
  };

  /* Check the size of the mapping and the ELF magic tag. */
  if (map_end < map_start
      || map_end - map_start < sizeof(union max_elf_header_size_t)
      || memcmp(map_start, ELFMAG, SELFMAG))
    {
      return FALSE;
    }

  /* Check that this is an ELF shared library or executable file. This also
     implicitly checks that the data encoding of the current process is the
     same as in the loaded library. */
  if (map_start[EI_CLASS] == ELFCLASS32)
    {
      const Elf32_Ehdr *hdr = (void*)map_start;
      return (hdr->e_type == ET_DYN || hdr->e_type == ET_EXEC);
    }
  else if (map_start[EI_CLASS] == ELFCLASS64)
    {
      const Elf64_Ehdr *hdr = (void*)map_start;
      return (hdr->e_type == ET_DYN || hdr->e_type == ET_EXEC);
    }

  return FALSE;
}
#endif  /* HAVE_ELF_H */

static const apr_array_header_t *
linux_shared_libs(apr_pool_t *pool)
{
  /* Read the list of loaded modules from /proc/[pid]/maps
    The format is described here:
        http://man7.org/linux/man-pages/man5/proc.5.html
  */

  const char *maps = apr_psprintf(pool, "/proc/%ld/maps", (long)getpid());
  apr_array_header_t *result = NULL;
  svn_boolean_t eof = FALSE;
  svn_stream_t *stream;
  svn_error_t *err;

  err = svn_stream_open_readonly(&stream, maps, pool, pool);
  if (err)
    {
      svn_error_clear(err);
      return NULL;
    }

  /* Each line in /proc/[pid]/maps consists of whitespace-delimited fields. */
  while (!eof)
    {
      svn_stringbuf_t *line;

#if HAVE_ELF_H
      const unsigned char *map_start;
      const unsigned char *map_end;
#endif

      err = svn_stream_readline(stream, &line, "\n", &eof, pool);
      if (err)
        {
          svn_error_clear(err);
          return NULL;
        }

#if HAVE_ELF_H
      /* Address: The mapped memory address range. */
      {
        const char *const limit = line->data + line->len;
        char *end;

        /* The start of the address range */
        map_start = parse_pointer_value(line->data, limit, &end);
        if (!map_start || *end != '-')
          continue;

        /* The end of the address range */
        map_end = parse_pointer_value(end + 1, limit, &end);
        if (!map_end || !svn_ctype_isspace(*end))
          continue;
      }
#endif

      stringbuf_skip_whitespace_field(line); /* skip address */

      /* Permissions: The memory region must be readable and executable. */
      if (line->len < 4 || line->data[0] != 'r' || line->data[2] != 'x')
        continue;

      stringbuf_skip_whitespace_field(line); /* skip perms */
      stringbuf_skip_whitespace_field(line); /* skip offset */
      stringbuf_skip_whitespace_field(line); /* skip device */

      /* I-Node: If it is 0, there is no file associated with the region. */
      if (line->len < 2
          || (line->data[0] == '0' && svn_ctype_isspace(line->data[1])))
        continue;

      stringbuf_skip_whitespace_field(line); /* skip inode */

      /* Consider only things that look like absolute paths.
         Files that were removed since the process was created (due to an
         upgrade, for example) are marked as '(deleted)'. */
      if (line->data[0] == '/')
        {
          svn_version_ext_loaded_lib_t *lib;

#if HAVE_ELF_H
          if (!check_elf_header(map_start, map_end))
            continue;
#endif

          /* We've done our best to find a mapped shared library. */
          if (!result)
            {
              result = apr_array_make(pool, 32, sizeof(*lib));
            }
          lib = &APR_ARRAY_PUSH(result, svn_version_ext_loaded_lib_t);
          lib->name = line->data;
          lib->version = NULL;
        }
    }

  svn_error_clear(svn_stream_close(stream));
  return result;
}
#endif /* __linux__ */


#ifdef WIN32
typedef DWORD (WINAPI *FNGETNATIVESYSTEMINFO)(LPSYSTEM_INFO);
typedef BOOL (WINAPI *FNENUMPROCESSMODULES) (HANDLE, HMODULE*, DWORD, LPDWORD);

svn_boolean_t
svn_sysinfo___fill_windows_version(OSVERSIONINFOEXW *version_info)
{
  memset(version_info, 0, sizeof(*version_info));

  version_info->dwOSVersionInfoSize = sizeof(*version_info);

  /* Kill warnings with the Windows 8 and later platform SDK */
#if _MSC_VER > 1600 && NTDDI_VERSION >= _0x06020000
  /* Windows 8 deprecated the API to retrieve the Windows version to avoid
     backwards compatibility problems... It might return a constant version
     in future Windows versions... But let's kill the warning.

     We can implementation this using a different function later. */
#pragma warning(push)
#pragma warning(disable: 4996)
#endif

  /* Prototype supports OSVERSIONINFO */
  return GetVersionExW((LPVOID)version_info);
#if _MSC_VER > 1600 && NTDDI_VERSION >= _0x06020000
#pragma warning(pop)
#pragma warning(disable: 4996)
#endif
}

/* Get system info, and try to tell the difference between the native
   system type and the runtime environment of the current process.
   Populate results in SYSINFO and LOCAL_SYSINFO (optional). */
static BOOL
system_info(SYSTEM_INFO *sysinfo,
            SYSTEM_INFO *local_sysinfo)
{
  FNGETNATIVESYSTEMINFO GetNativeSystemInfo_ = (FNGETNATIVESYSTEMINFO)
    GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "GetNativeSystemInfo");

  memset(sysinfo, 0, sizeof *sysinfo);
  if (local_sysinfo)
    {
      memset(local_sysinfo, 0, sizeof *local_sysinfo);
      GetSystemInfo(local_sysinfo);
      if (GetNativeSystemInfo_)
        GetNativeSystemInfo_(sysinfo);
      else
        memcpy(sysinfo, local_sysinfo, sizeof *sysinfo);
    }
  else
    GetSystemInfo(sysinfo);

  return TRUE;
}

/* Map the proccessor type from SYSINFO to a string. */
static const char *
processor_name(SYSTEM_INFO *sysinfo)
{
  switch (sysinfo->wProcessorArchitecture)
    {
    case PROCESSOR_ARCHITECTURE_AMD64:         return "x86_64";
    case PROCESSOR_ARCHITECTURE_IA64:          return "ia64";
    case PROCESSOR_ARCHITECTURE_INTEL:         return "x86";
    case PROCESSOR_ARCHITECTURE_MIPS:          return "mips";
    case PROCESSOR_ARCHITECTURE_ALPHA:         return "alpha32";
    case PROCESSOR_ARCHITECTURE_PPC:           return "powerpc";
    case PROCESSOR_ARCHITECTURE_SHX:           return "shx";
    case PROCESSOR_ARCHITECTURE_ARM:           return "arm";
    case PROCESSOR_ARCHITECTURE_ALPHA64:       return "alpha";
    case PROCESSOR_ARCHITECTURE_MSIL:          return "msil";
    case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64: return "x86_wow64";
    default: return "unknown";
    }
}

/* Return the Windows-specific canonical host name. */
static const char *
win32_canonical_host(apr_pool_t *pool)
{
  SYSTEM_INFO sysinfo;
  SYSTEM_INFO local_sysinfo;
  OSVERSIONINFOEXW osinfo;

  if (system_info(&sysinfo, &local_sysinfo)
      && svn_sysinfo___fill_windows_version(&osinfo))
    {
      const char *arch = processor_name(&local_sysinfo);
      const char *machine = processor_name(&sysinfo);
      const char *vendor = "microsoft";
      const char *sysname = "windows";
      const char *sysver = apr_psprintf(pool, "%u.%u.%u",
                                        (unsigned int)osinfo.dwMajorVersion,
                                        (unsigned int)osinfo.dwMinorVersion,
                                        (unsigned int)osinfo.dwBuildNumber);

      if (sysinfo.wProcessorArchitecture
          == local_sysinfo.wProcessorArchitecture)
        return apr_psprintf(pool, "%s-%s-%s%s",
                            machine, vendor, sysname, sysver);
      return apr_psprintf(pool, "%s/%s-%s-%s%s",
                          arch, machine, vendor, sysname, sysver);
    }

  return "unknown-microsoft-windows";
}

/* Convert a Unicode string to UTF-8. */
static char *
wcs_to_utf8(const wchar_t *wcs, apr_pool_t *pool)
{
  const int bufsize = WideCharToMultiByte(CP_UTF8, 0, wcs, -1,
                                          NULL, 0, NULL, NULL);
  if (bufsize > 0)
    {
      char *const utf8 = apr_palloc(pool, bufsize + 1);
      WideCharToMultiByte(CP_UTF8, 0, wcs, -1, utf8, bufsize, NULL, NULL);
      return utf8;
    }
  return NULL;
}

/* Query the value called NAME of the registry key HKEY. */
static char *
registry_value(HKEY hkey, wchar_t *name, apr_pool_t *pool)
{
  DWORD size;
  wchar_t *value;

  if (RegQueryValueExW(hkey, name, NULL, NULL, NULL, &size))
    return NULL;

  value = apr_palloc(pool, size + sizeof *value);
  if (RegQueryValueExW(hkey, name, NULL, NULL, (void*)value, &size))
    return NULL;
  value[size / sizeof *value] = 0;
  return wcs_to_utf8(value, pool);
}

/* Try to glean the Windows release name and associated info from the
   registry. Failing that, construct a release name from the version
   info. */
static const char *
win32_release_name(apr_pool_t *pool)
{
  SYSTEM_INFO sysinfo;
  OSVERSIONINFOEXW osinfo;
  HKEY hkcv;

  if (!system_info(&sysinfo, NULL)
      || !svn_sysinfo___fill_windows_version(&osinfo))
    return NULL;

  if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                     L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
                     0, KEY_QUERY_VALUE, &hkcv))
    {
      const char *release = registry_value(hkcv, L"ProductName", pool);
      const char *spack = registry_value(hkcv, L"CSDVersion", pool);
      const char *curver = registry_value(hkcv, L"CurrentVersion", pool);
      const char *curtype = registry_value(hkcv, L"CurrentType", pool);
      const char *install = registry_value(hkcv, L"InstallationType", pool);
      const char *curbuild = registry_value(hkcv, L"CurrentBuildNumber", pool);

      if (!spack && *osinfo.szCSDVersion)
        spack = wcs_to_utf8(osinfo.szCSDVersion, pool);

      if (!curbuild)
        curbuild = registry_value(hkcv, L"CurrentBuild", pool);

      if (release || spack || curver || curtype || curbuild)
        {
          const char *bootinfo = "";
          if (curver || install || curtype)
            {
              bootinfo = apr_psprintf(pool, "[%s%s%s%s%s]",
                                      (curver ? curver : ""),
                                      (install ? (curver ? " " : "") : ""),
                                      (install ? install : ""),
                                      (curtype
                                       ? (curver||install ? " " : "")
                                       : ""),
                                      (curtype ? curtype : ""));
            }

          return apr_psprintf(pool, "%s%s%s%s%s%s%s",
                              (release ? release : ""),
                              (spack ? (release ? ", " : "") : ""),
                              (spack ? spack : ""),
                              (curbuild
                               ? (release||spack ? ", build " : "build ")
                               : ""),
                              (curbuild ? curbuild : ""),
                              (bootinfo
                               ? (release||spack||curbuild ? " " : "")
                               : ""),
                              (bootinfo ? bootinfo : ""));
        }
    }

  if (*osinfo.szCSDVersion)
    {
      const char *servicepack = wcs_to_utf8(osinfo.szCSDVersion, pool);

      if (servicepack)
        return apr_psprintf(pool, "Windows NT %u.%u, %s, build %u",
                            (unsigned int)osinfo.dwMajorVersion,
                            (unsigned int)osinfo.dwMinorVersion,
                            servicepack,
                            (unsigned int)osinfo.dwBuildNumber);

      /* Assume wServicePackMajor > 0 if szCSDVersion is not empty */
      if (osinfo.wServicePackMinor)
        return apr_psprintf(pool, "Windows NT %u.%u SP%u.%u, build %u",
                            (unsigned int)osinfo.dwMajorVersion,
                            (unsigned int)osinfo.dwMinorVersion,
                            (unsigned int)osinfo.wServicePackMajor,
                            (unsigned int)osinfo.wServicePackMinor,
                            (unsigned int)osinfo.dwBuildNumber);

      return apr_psprintf(pool, "Windows NT %u.%u SP%u, build %u",
                          (unsigned int)osinfo.dwMajorVersion,
                          (unsigned int)osinfo.dwMinorVersion,
                          (unsigned int)osinfo.wServicePackMajor,
                          (unsigned int)osinfo.dwBuildNumber);
    }

  return apr_psprintf(pool, "Windows NT %u.%u, build %u",
                      (unsigned int)osinfo.dwMajorVersion,
                      (unsigned int)osinfo.dwMinorVersion,
                      (unsigned int)osinfo.dwBuildNumber);
}


/* Get a list of handles of shared libs loaded by the current
   process. Returns a NULL-terminated array alocated from POOL. */
static HMODULE *
enum_loaded_modules(apr_pool_t *pool)
{
  HMODULE psapi_dll = 0;
  HANDLE current = GetCurrentProcess();
  HMODULE dummy[1];
  HMODULE *handles;
  DWORD size;
  FNENUMPROCESSMODULES EnumProcessModules_;

  psapi_dll = GetModuleHandleW(L"psapi.dll");

  if (!psapi_dll)
    {
      /* Load and never unload, just like static linking */
      psapi_dll = LoadLibraryW(L"psapi.dll");
    }

  if (!psapi_dll)
      return NULL;

  EnumProcessModules_ = (FNENUMPROCESSMODULES)
                              GetProcAddress(psapi_dll, "EnumProcessModules");

  /* Before Windows XP psapi was an optional module */
  if (! EnumProcessModules_)
    return NULL;

  if (!EnumProcessModules_(current, dummy, sizeof(dummy), &size))
    return NULL;

  handles = apr_palloc(pool, size + sizeof *handles);
  if (! EnumProcessModules_(current, handles, size, &size))
    return NULL;
  handles[size / sizeof *handles] = NULL;
  return handles;
}

/* Find the version number, if any, embedded in FILENAME. */
static const char *
file_version_number(const wchar_t *filename, apr_pool_t *pool)
{
  VS_FIXEDFILEINFO info;
  unsigned int major, minor, micro, nano;
  void *data;
  DWORD data_size = GetFileVersionInfoSizeW(filename, NULL);
  void *vinfo;
  UINT vinfo_size;

  if (!data_size)
    return NULL;

  data = apr_palloc(pool, data_size);
  if (!GetFileVersionInfoW(filename, 0, data_size, data))
    return NULL;

  if (!VerQueryValueW(data, L"\\", &vinfo, &vinfo_size))
    return NULL;

  if (vinfo_size != sizeof info)
    return NULL;

  memcpy(&info, vinfo, sizeof info);
  major = (info.dwFileVersionMS >> 16) & 0xFFFF;
  minor = info.dwFileVersionMS & 0xFFFF;
  micro = (info.dwFileVersionLS >> 16) & 0xFFFF;
  nano = info.dwFileVersionLS & 0xFFFF;

  if (!nano)
    {
      if (!micro)
        return apr_psprintf(pool, "%u.%u", major, minor);
      else
        return apr_psprintf(pool, "%u.%u.%u", major, minor, micro);
    }
  return apr_psprintf(pool, "%u.%u.%u.%u", major, minor, micro, nano);
}

/* List the shared libraries loaded by the current process. */
static const apr_array_header_t *
win32_shared_libs(apr_pool_t *pool)
{
  apr_array_header_t *array = NULL;
  wchar_t buffer[MAX_PATH + 1];
  HMODULE *handles = enum_loaded_modules(pool);
  HMODULE *module;

  for (module = handles; module && *module; ++module)
    {
      const char *filename;
      const char *version;
      if (GetModuleFileNameW(*module, buffer, MAX_PATH))
        {
          buffer[MAX_PATH] = 0;

          version = file_version_number(buffer, pool);
          filename = wcs_to_utf8(buffer, pool);
          if (filename)
            {
              svn_version_ext_loaded_lib_t *lib;

              if (!array)
                {
                  array = apr_array_make(pool, 32, sizeof(*lib));
                }
              lib = &APR_ARRAY_PUSH(array, svn_version_ext_loaded_lib_t);
              lib->name = svn_dirent_local_style(filename, pool);
              lib->version = version;
            }
        }
    }

  return array;
}
#endif /* WIN32 */


#ifdef SVN_HAVE_MACOS_PLIST
/* implements svn_write_fn_t to copy the data into a CFMutableDataRef that's
 * in the baton. */
static svn_error_t *
write_to_cfmutabledata(void *baton, const char *data, apr_size_t *len)
{
  CFMutableDataRef *resource = (CFMutableDataRef *) baton;

  CFDataAppendBytes(*resource, (UInt8 *)data, *len);

  return SVN_NO_ERROR;
}

/* Load the SystemVersion.plist or ServerVersion.plist file into a
   property list. Set SERVER to TRUE if the file read was
   ServerVersion.plist. */
static CFDictionaryRef
system_version_plist(svn_boolean_t *server, apr_pool_t *pool)
{
  static const char server_version[] =
    "/System/Library/CoreServices/ServerVersion.plist";
  static const char system_version[] =
    "/System/Library/CoreServices/SystemVersion.plist";
  svn_stream_t *read_stream, *write_stream;
  svn_error_t *err;
  CFPropertyListRef plist = NULL;
  CFMutableDataRef resource = CFDataCreateMutable(kCFAllocatorDefault, 0);

  /* failed getting the CFMutableDataRef, shouldn't happen */
  if (!resource)
    return NULL;

  /* Try to open the plist files to get the data */
  err = svn_stream_open_readonly(&read_stream, server_version, pool, pool);
  if (err)
    {
      if (!APR_STATUS_IS_ENOENT(err->apr_err))
        {
          svn_error_clear(err);
          CFRelease(resource);
          return NULL;
        }
      else
        {
          svn_error_clear(err);
          err = svn_stream_open_readonly(&read_stream, system_version,
                                         pool, pool);
          if (err)
            {
              svn_error_clear(err);
              CFRelease(resource);
              return NULL;
            }

          *server = FALSE;
        }
    }
  else
    {
      *server = TRUE;
    }

  /* copy the data onto the CFMutableDataRef to allow us to provide it to
   * the CoreFoundation functions that parse proprerty lists */
  write_stream = svn_stream_create(&resource, pool);
  svn_stream_set_write(write_stream, write_to_cfmutabledata);
  err = svn_stream_copy3(read_stream, write_stream, NULL, NULL, pool);
  if (err)
    {
      svn_error_clear(err);
      return NULL;
    }

#if __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
  /* This function is only available from Mac OS 10.6 onward. */
  plist = CFPropertyListCreateWithData(kCFAllocatorDefault, resource,
                                       kCFPropertyListImmutable,
                                       NULL, NULL);
#else  /* Mac OS 10.5 or earlier */
  /* This function obsolete and deprecated since Mac OS 10.10. */
  plist = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, resource,
                                          kCFPropertyListImmutable,
                                          NULL);
#endif /* MAC_OS_X_VERSION_10_6 */

  if (resource)
    CFRelease(resource);

  if (!plist)
    return NULL;

  if (CFDictionaryGetTypeID() != CFGetTypeID(plist))
    {
      /* Oops ... this really should be a dict. */
      CFRelease(plist);
      return NULL;
    }

  return plist;
}

/* Return the value for KEY from PLIST, or NULL if not available. */
static const char *
value_from_dict(CFDictionaryRef plist, CFStringRef key, apr_pool_t *pool)
{
  CFStringRef valref;
  CFIndex bufsize;
  const void *valptr;
  const char *value;

  if (!CFDictionaryGetValueIfPresent(plist, key, &valptr))
    return NULL;

  valref = valptr;
  if (CFStringGetTypeID() != CFGetTypeID(valref))
    return NULL;

  value = CFStringGetCStringPtr(valref, kCFStringEncodingUTF8);
  if (value)
    return apr_pstrdup(pool, value);

  bufsize =  5 * CFStringGetLength(valref) + 1;
  value = apr_palloc(pool, bufsize);
  if (!CFStringGetCString(valref, (char*)value, bufsize,
                          kCFStringEncodingUTF8))
    value = NULL;

  return value;
}

/* Return the minor version the operating system, given the number in
   a format that matches the regular expression /^10\.\d+(\..*)?$/ */
static int
macos_minor_version(const char *osver)
{
  char *end = NULL;
  unsigned long num = strtoul(osver, &end, 10);

  if (!end || *end != '.' || num != 10)
    return -1;

  osver = end + 1;
  end = NULL;
  num = strtoul(osver, &end, 10);
  if (!end || (*end && *end != '.'))
    return -1;

  return (int)num;
}

/* Return the product name of the operating system. */
static const char *
product_name_from_minor_version(int minor, const char* product_name)
{
  /* We can only do this if we know the official product name. */
  if (0 != strcmp(product_name, "Mac OS X"))
    return product_name;

  if (minor <= 7)
    return product_name;

  if (minor <= 11)
    return "OS X";

  return "macOS";
}

/* Return the commercial name of the operating system. */
static const char *
release_name_from_minor_version(int minor, const char* product_name)
{
  /* We can only do this if we know the official product name. */
  if (0 == strcmp(product_name, "Mac OS X"))
    {
      /* See https://en.wikipedia.org/wiki/MacOS_version_history#Releases */
      switch(minor)
        {
        case  0: return "Cheetah";
        case  1: return "Puma";
        case  2: return "Jaguar";
        case  3: return "Panther";
        case  4: return "Tiger";
        case  5: return "Leopard";
        case  6: return "Snow Leopard";
        case  7: return "Lion";
        case  8: return "Mountain Lion";
        case  9: return "Mavericks";
        case 10: return "Yosemite";
        case 11: return "El Capitan";
        case 12: return "Sierra";
        case 13: return "High Sierra";
        case 14: return "Mojave";
        }
    }
  return NULL;
}

/* Construct the release name from information stored in the Mac OS X
   "SystemVersion.plist" file (or ServerVersion.plist, for Mac Os
   Server. */
static const char *
macos_release_name(apr_pool_t *pool)
{
  svn_boolean_t server;
  CFDictionaryRef plist = system_version_plist(&server, pool);

  if (plist)
    {
      const char *osname = value_from_dict(plist, CFSTR("ProductName"), pool);
      const char *osver = value_from_dict(plist,
                                          CFSTR("ProductUserVisibleVersion"),
                                          pool);
      const char *build = value_from_dict(plist,
                                          CFSTR("ProductBuildVersion"),
                                          pool);
      const char *release;
      int minor_version;

      if (!osver)
        osver = value_from_dict(plist, CFSTR("ProductVersion"), pool);
      minor_version = macos_minor_version(osver);
      release = release_name_from_minor_version(minor_version, osname);
      osname = product_name_from_minor_version(minor_version, osname);

      CFRelease(plist);
      return apr_psprintf(pool, "%s%s%s%s%s%s%s%s",
                          (osname ? osname : ""),
                          (release ? (osname ? " " : "") : ""),
                          (release ? release : ""),
                          (osver ? (osname||release ? " " : "") : ""),
                          (osver ? osver : ""),
                          (build
                           ? (osname||release||osver ? ", " : "")
                           : ""),
                          (build
                           ? (server ? "server build " : "build ")
                           : ""),
                          (build ? build : ""));
    }

  return NULL;
}
#endif  /* SVN_HAVE_MACOS_PLIST */

#ifdef SVN_HAVE_MACHO_ITERATE
/* List the shared libraries loaded by the current process.
   Ignore frameworks and system libraries, they're just clutter. */
static const apr_array_header_t *
macos_shared_libs(apr_pool_t *pool)
{
  static const char slb_prefix[] = "/usr/lib/system/";
  static const char fwk_prefix[] = "/System/Library/Frameworks/";
  static const char pfk_prefix[] = "/System/Library/PrivateFrameworks/";

  const size_t slb_prefix_len = strlen(slb_prefix);
  const size_t fwk_prefix_len = strlen(fwk_prefix);
  const size_t pfk_prefix_len = strlen(pfk_prefix);

  apr_array_header_t *result = NULL;
  apr_array_header_t *dylibs = NULL;

  uint32_t i;
  for (i = 0;; ++i)
    {
      const struct mach_header *header = _dyld_get_image_header(i);
      const char *filename = _dyld_get_image_name(i);
      const char *version;
      char *truename;
      svn_version_ext_loaded_lib_t *lib;

      if (!(header && filename))
        break;

      switch (header->cputype)
        {
        case CPU_TYPE_I386:      version = _("Intel"); break;
        case CPU_TYPE_X86_64:    version = _("Intel 64-bit"); break;
        case CPU_TYPE_POWERPC:   version = _("PowerPC"); break;
        case CPU_TYPE_POWERPC64: version = _("PowerPC 64-bit"); break;
        default:
          version = NULL;
        }

      if (0 == apr_filepath_merge(&truename, "", filename,
                                  APR_FILEPATH_NATIVE
                                  | APR_FILEPATH_TRUENAME,
                                  pool))
        filename = truename;
      else
        filename = apr_pstrdup(pool, filename);

      if (0 == strncmp(filename, slb_prefix, slb_prefix_len)
          || 0 == strncmp(filename, fwk_prefix, fwk_prefix_len)
          || 0 == strncmp(filename, pfk_prefix, pfk_prefix_len))
        {
          /* Ignore frameworks and system libraries. */
          continue;
        }

      if (header->filetype == MH_EXECUTE)
        {
          /* Make sure the program filename is first in the list */
          if (!result)
            {
              result = apr_array_make(pool, 32, sizeof(*lib));
            }
          lib = &APR_ARRAY_PUSH(result, svn_version_ext_loaded_lib_t);
        }
      else
        {
          if (!dylibs)
            {
              dylibs = apr_array_make(pool, 32, sizeof(*lib));
            }
          lib = &APR_ARRAY_PUSH(dylibs, svn_version_ext_loaded_lib_t);
        }

      lib->name = filename;
      lib->version = version;
    }

  /* Gather results into one array. */
  if (dylibs)
    {
      if (result)
        apr_array_cat(result, dylibs);
      else
        result = dylibs;
    }

  return result;
}
#endif  /* SVN_HAVE_MACHO_ITERATE */
