/* getProductName.cpp
 *   derived from:
 *     Microsoft example code "Getting the System Version"
 *     strlcat, strlcpy: OpenBSD [BSD license]
 *
 * Modifications Copyright (c) 2008, Charles S. Wilson
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/* must include before windows.h */
#include "getopt.h"

#include <windows.h>
#include <stdio.h>
#include "../version.h"

static const char *VERSION = VERSION_STRING;
static const char *PROGNAME = "winProductName";

#ifndef PRODUCT_IOTENTERPRISES
#define PRODUCT_IOTENTERPRISES 0x000000bf
#endif

static LPTSTR products[] = {
 /* 0x00000000 */ "",
 /* 0x00000001 */ " Ultimate",
 /* 0x00000002 */ " Home Basic",
 /* 0x00000003 */ " Home Premium",
 /* 0x00000004 */ " Enterprise",
 /* 0x00000005 */ " Home Basic N",
 /* 0x00000006 */ " Business",
 /* 0x00000007 */ " Server Standard",
 /* 0x00000008 */ " Server Datacenter",
 /* 0x00000009 */ " Small Business Server",
 /* 0x0000000a */ " Server Enterprise",
 /* 0x0000000b */ " Starter",
 /* 0x0000000c */ " Server Datacenter Core",
 /* 0x0000000d */ " Server Standard Core",
 /* 0x0000000e */ " Server Enterprise Core",
 /* 0x0000000f */ " Server Enterprise for Itanium-based Systems",
 /* 0x00000010 */ " Business N",
 /* 0x00000011 */ " Web Server",
 /* 0x00000012 */ " HPC Edition",
 /* 0x00000013 */ " Home Server",
 /* 0x00000014 */ " Storage Server Express",
 /* 0x00000015 */ " Storage Server Standard",
 /* 0x00000016 */ " Storage Server Workgroup",
 /* 0x00000017 */ " Storage Server Enterprise",
 /* 0x00000018 */ " for Windows Essential Server Solutions",
 /* 0x00000019 */ " Small Business Server Premium",
 /* 0x0000001a */ " Home Premium N",
 /* 0x0000001b */ " Enterprise N",
 /* 0x0000001c */ " Ultimate N",
 /* 0x0000001d */ " Web Server Core",
 /* 0x0000001e */ " Essential Business Server Management Server",
 /* 0x0000001f */ " Essential Business Server Security Server",
 /* 0x00000020 */ " Essential Business Server Messaging Server",
 /* 0x00000021 */ " Server Foundation",
 /* 0x00000022 */ " Home Server 2011",
 /* 0x00000023 */ " without Hyper-V for Windows Essential Server Solutions",
 /* 0x00000024 */ " Server Standard without Hyper-V",
 /* 0x00000025 */ " Server Datacenter without Hyper-V",
 /* 0x00000026 */ " Server Enterprise without Hyper-V",
 /* 0x00000027 */ " Server Datacenter Core without Hyper-V",
 /* 0x00000028 */ " Server Standard Core without Hyper-V",
 /* 0x00000029 */ " Server Enterprise Core without Hyper-V",
 /* 0x0000002a */ " Hyper-V Server",
 /* 0x0000002b */ " Storage Server Express Core",
 /* 0x0000002c */ " Storage Server Standard Core",
 /* 0x0000002d */ " Storage Server Workgroup Core",
 /* 0x0000002e */ " Storage Server Enterprise Core",
 /* 0x0000002f */ " Starter N",
 /* 0x00000030 */ " Professional",
 /* 0x00000031 */ " Professional N",
 /* 0x00000032 */ " Small Business Server 2011 Essentials",
 /* 0x00000033 */ " Server For SB Solutions",
 /* 0x00000034 */ " Server Solutions Premium",
 /* 0x00000035 */ " Server Solutions Premium Core",
 /* 0x00000036 */ " Server For SB Solutions EM", /* per MSDN, 2012-09-01 */
 /* 0x00000037 */ " Server For SB Solutions EM", /* per MSDN, 2012-09-01 */
 /* 0x00000038 */ " Multipoint Server",
 /* 0x00000039 */ "",
 /* 0x0000003a */ "",
 /* 0x0000003b */ " Essential Server Solution Management",
 /* 0x0000003c */ " Essential Server Solution Additional",
 /* 0x0000003d */ " Essential Server Solution Management SVC",
 /* 0x0000003e */ " Essential Server Solution Additional SVC",
 /* 0x0000003f */ " Small Business Server Premium Core",
 /* 0x00000040 */ " Server Hyper Core V",
 /* 0x00000041 */ "",
 /* 0x00000042 */ " Starter E",
 /* 0x00000043 */ " Home Basic E",
 /* 0x00000044 */ " Home Premium E",
 /* 0x00000045 */ " Professional E",
 /* 0x00000046 */ " Enterprise E",
 /* 0x00000047 */ " Ultimate E",
 /* 0x00000048 */ " Server Enterprise (Evaluation inst.)",
 /* 0x00000049 */ "",
 /* 0x0000004a */ "",
 /* 0x0000004b */ "",
 /* 0x0000004c */ " MultiPoint Server Standard",
 /* 0x0000004d */ " MultiPoint Server Premium",
 /* 0x0000004e */ "",
 /* 0x0000004f */ " Server Standard (Evaluation inst.)",
 /* 0x00000050 */ " Server Datacenter (Evaluation inst.)",
 /* 0x00000051 */ "",
 /* 0x00000052 */ "",
 /* 0x00000053 */ "",
 /* 0x00000054 */ " Enterprise N (Evaluation inst.)",
 /* 0x00000055 */ "",
 /* 0x00000056 */ "",
 /* 0x00000057 */ "",
 /* 0x00000058 */ "",
 /* 0x00000059 */ "",
 /* 0x0000005a */ "",
 /* 0x0000005b */ "",
 /* 0x0000005c */ "",
 /* 0x0000005d */ "",
 /* 0x0000005e */ "",
 /* 0x0000005f */ " Storage Server Workgroup (Evaluation inst.)",
 /* 0x00000060 */ " Storage Server Standard (Evaluation inst.)",
 /* 0x00000061 */ "",
 /* 0x00000062 */ " N",
 /* 0x00000063 */ " China",
 /* 0x00000064 */ " Single Language",
 /* 0x00000065 */ " Home",
 /* 0x00000066 */ "",
 /* 0x00000067 */ " Professional with Media Center",
 /* 0x00000068 */ " Mobile",
 /* 0x00000069 */ "",
 /* 0x0000006a */ "",
 /* 0x0000006b */ "",
 /* 0x0000006c */ "",
 /* 0x0000006d */ "",
 /* 0x0000006e */ "",
 /* 0x0000006f */ "",
 /* 0x00000070 */ "",
 /* 0x00000071 */ "",
 /* 0x00000072 */ "",
 /* 0x00000073 */ "",
 /* 0x00000074 */ "",
 /* 0x00000075 */ "",
 /* 0x00000076 */ "",
 /* 0x00000077 */ " Team",
 /* 0x00000078 */ "",
 /* 0x00000079 */ " Education",
 /* 0x0000007a */ " Education N",
 /* 0x0000007b */ "",
 /* 0x0000007c */ "",
 /* 0x0000007d */ " Enterprise 2015 LTSB",
 /* 0x0000007e */ " Enterprise 2015 LTSB N",
 /* 0x0000007f */ "",
 /* 0x00000080 */ "",
 /* 0x00000081 */ " Enterprise 2015 LTSB Evaluation",
 /* 0x00000082 */ " Enterprise 2015 LTSB N Evaluation",
 /* 0x00000083 */ " IoT Core Commercial",
 /* 0x00000084 */ "",
 /* 0x00000085 */ " Mobile Enterprise",
 /* 0x00000086 */ "",
 /* 0x00000087 */ "",
 /* 0x00000088 */ "",
 /* 0x00000089 */ "",
 /* 0x0000008a */ "",
 /* 0x0000008b */ "",
 /* 0x0000008c */ "",
 /* 0x0000008d */ "",
 /* 0x0000008e */ "",
 /* 0x0000008f */ "",
 /* 0x00000090 */ "",
 /* 0x00000091 */ " Server Datacenter, Semi-Annual Channel (core installation)",
 /* 0x00000092 */ " Server Standard, Semi-Annual Channel (core installation)",
 /* 0x00000093 */ "",
 /* 0x00000094 */ "",
 /* 0x00000095 */ "",
 /* 0x00000096 */ "",
 /* 0x00000097 */ "",
 /* 0x00000098 */ "",
 /* 0x00000099 */ "",
 /* 0x0000009a */ "",
 /* 0x0000009b */ "",
 /* 0x0000009c */ "",
 /* 0x0000009d */ "",
 /* 0x0000009e */ "",
 /* 0x0000009f */ "",
 /* 0x000000a0 */ "",
 /* 0x000000a1 */ " Pro for Workstations",
 /* 0x000000a2 */ " Pro for Workstations N",
 /* 0x000000a3 */ "",
 /* 0x000000a4 */ " Pro Education",
 /* 0x000000a5 */ "",
 /* 0x000000a6 */ "",
 /* 0x000000a7 */ "",
 /* 0x000000a8 */ "",
 /* 0x000000a9 */ "",
 /* 0x000000aa */ "",
 /* 0x000000ab */ "",
 /* 0x000000ac */ "",
 /* 0x000000ad */ "",
 /* 0x000000ae */ "",
 /* 0x000000af */ " Enterprise for Virtual Desktops",
 /* 0x000000b0 */ "",
 /* 0x000000b1 */ "",
 /* 0x000000b2 */ "",
 /* 0x000000b3 */ "",
 /* 0x000000b4 */ "",
 /* 0x000000b5 */ "",
 /* 0x000000b6 */ "",
 /* 0x000000b7 */ "",
 /* 0x000000b8 */ "",
 /* 0x000000b9 */ "",
 /* 0x000000ba */ "",
 /* 0x000000bb */ "",
 /* 0x000000bc */ " IoT Enterprise",
 /* 0x000000bd */ "",
 /* 0x000000be */ "",
 /* 0x000000bf */ " IoT Enterprise LTSC",
};

#define BUFSIZE 256

static size_t strlcpy (char *dst, const char *src, size_t siz);
static size_t strlcat (char *dst, const char *src, size_t siz);
void usage (FILE *os, char *progname, int exitCode);
void help (FILE *os, char *progname, int exitCode);
void license (FILE *os, char *progname, int exitCode);
void version (FILE *os, char *progname, int exitCode);

typedef BOOL (WINAPI *PGPI) (DWORD, DWORD, DWORD, DWORD, PDWORD);

extern NTSTATUS NTAPI RtlGetVersion (PRTL_OSVERSIONINFOEXW);

BOOL
GetOSDisplayString (LPTSTR pszOS)
{
  RTL_OSVERSIONINFOEXW osvi;
  SYSTEM_INFO si;
  PGPI pGPI;
  DWORD dwType;

  ZeroMemory (&si, sizeof (SYSTEM_INFO));
  ZeroMemory (&osvi, sizeof (RTL_OSVERSIONINFOEXW));

  osvi.dwOSVersionInfoSize = sizeof (RTL_OSVERSIONINFOEXW);

  /* DON'T use GetVersionEx.  It returns the wrong OS version if the
     executable is missing the latest application manifest GUIDs, starting
     with Windows 8.1.  Why does Microsoft make such a complete mess out
     of this? */
  RtlGetVersion ((PRTL_OSVERSIONINFOEXW) & osvi);

  GetNativeSystemInfo (&si);

  strlcpy (pszOS, "Microsoft Windows ", BUFSIZE);

  if (osvi.dwMajorVersion <= 5)
    {
      printf ("This sample does not support this version of Windows.\n");
      return FALSE;
    }
  else
    {
      // Test for the specific product.
      pGPI = (PGPI) GetProcAddress (GetModuleHandle ("ntdll.dll"),
				    "RtlGetProductInfo");
      if (osvi.dwMajorVersion == 6)
	switch (osvi.dwMinorVersion)
	  {
	  case 0:
	    strlcat (pszOS, osvi.wProductType == VER_NT_WORKSTATION
		     ? "Vista" : "Server 2008", BUFSIZE);
	    break;
	  case 1:
	    strlcat (pszOS, osvi.wProductType == VER_NT_WORKSTATION
		     ? "7" : "Server 2008 R2", BUFSIZE);
	    break;
	  case 2:
	    strlcat (pszOS, osvi.wProductType == VER_NT_WORKSTATION
		     ? "8" : "Server 2012", BUFSIZE);
	    break;
	  case 3:
	    strlcat (pszOS, osvi.wProductType == VER_NT_WORKSTATION
		     ? "8.1" : "Server 2012 R2", BUFSIZE);
	    break;
	  case 4:
	    strlcat (pszOS, osvi.wProductType == VER_NT_WORKSTATION
		     ? "10 Preview" : "Server 2016 Preview", BUFSIZE);
	    break;
	  }
      else if (osvi.dwMajorVersion == 10)
	{
	  if (osvi.wProductType == VER_NT_WORKSTATION)
	    strlcat (pszOS, osvi.dwBuildNumber >= 22000 ? "11" : "10", BUFSIZE);
	  else
	    {
	      if (osvi.dwBuildNumber <= 14393)
		strlcat (pszOS, "Server 2016", BUFSIZE);
	      else if (osvi.dwBuildNumber <= 17763)
		strlcat (pszOS, "Server 2019", BUFSIZE);
	      else if (osvi.dwBuildNumber <= 20348)
		strlcat (pszOS, "Server 2022", BUFSIZE);
	      else
		strlcat (pszOS, "Server 20??", BUFSIZE);
	    }
	}
      pGPI (osvi.dwMajorVersion,
	    osvi.dwMinorVersion,
	    osvi.wServicePackMajor, osvi.wServicePackMinor, &dwType);

      if (dwType == PRODUCT_UNLICENSED)
	strlcat (pszOS, " Unlicensed", BUFSIZE);
      else if (dwType > PRODUCT_IOTENTERPRISES)
	strlcat (pszOS, " <unknown>", BUFSIZE);
      else
	strlcat (pszOS, products[dwType], BUFSIZE);

      if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)
	strlcat (pszOS, ", 64-bit", BUFSIZE);
    }

  // Include service pack (if any) and build number.

  char buf[80];

  if (wcslen (osvi.szCSDVersion) > 0)
    {
      wcstombs (buf, osvi.szCSDVersion, sizeof buf);
      strlcat (pszOS, " ", BUFSIZE);
      strlcat (pszOS, buf, BUFSIZE);
    }

  snprintf (buf, 80, " (build %ld)", osvi.dwBuildNumber);
  strlcat (pszOS, buf, BUFSIZE);

  return TRUE;
}

int
main (int argc, char **argv)
{
  char szOS[BUFSIZE];
  int c;

  opterr = 0;
  while (1)
    {
      static struct option long_options[] = {
	{"help", no_argument, 0, 'h'},
	{"version", no_argument, 0, 'V'},
	{"license", no_argument, 0, 'L'},
	{0, 0, 0, 0}
      };
      int option_index = 0;
      c = getopt_long (argc, argv, ":hVL", long_options, &option_index);

      if (c == -1)
	break;

      switch (c)
	{
	case 'h':
	  help (stdout, argv[0], 0);
	  /* not reached */
	  break;
	case 'V':
	  version (stdout, argv[0], 0);
	  /* not reached */
	  break;
	case 'L':
	  license (stdout, argv[0], 0);
	  /* not reached */
	  break;

	case ':':
	  fprintf (stderr, "option -%c missing required arguments\n", optopt);
	  usage (stderr, argv[0], 1);
	  /* not reached */
	  break;

	case '?':
	  {
	    if (isprint (optopt))
	      fprintf (stderr, "Unknown option `-%c'.\n", optopt);
	    else
	      fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt);
	    usage (stderr, argv[0], 1);
	  }
	  /* not reached */
	  break;

	default:
	  fprintf (stderr, "internal getopt error\n");
	  exit (1);
	}			/* switch */
    }				/* while */

  if (optind < argc)
    {
      fprintf (stderr, "additional arguments ignored: ");
      while (optind < argc)
	{
	  fprintf (stderr, "\"%s", argv[optind++]);
	  if (optind < argc)
	    fprintf (stderr, "\", ");
	  else
	    fprintf (stderr, "\"\n");
	}
      usage (stderr, argv[0], 1);
      /* not reached */
    }

  if (GetOSDisplayString (szOS))
    {
      printf ("%s\n", szOS);
      return 0;
    }
  /* failure */
  return 1;
}



/*
 * Appends src to string dst of size siz (unlike strncat, siz is the
 * full size of dst, not space left).  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
 * Returns strlen(src) + MIN(siz, strlen(initial dst)).
 * If retval >= siz, truncation occurred.
 */
size_t
strlcat (char *dst, const char *src, size_t siz)
{
  char *d = dst;
  const char *s = src;
  size_t n = siz;
  size_t dlen;

  /* Find the end of dst and adjust bytes left but don't go past end */
  while (n-- != 0 && *d != '\0')
    d++;
  dlen = d - dst;
  n = siz - dlen;

  if (n == 0)
    return (dlen + strlen (s));
  while (*s != '\0')
    {
      if (n != 1)
	{
	  *d++ = *s;
	  n--;
	}
      s++;
    }
  *d = '\0';

  return (dlen + (s - src));	/* count does not include NUL */
}

/*
 * Copy src to string dst of size siz.  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz == 0).
 * Returns strlen(src); if retval >= siz, truncation occurred.
 */
size_t
strlcpy (char *dst, const char *src, size_t siz)
{
  char *d = dst;
  const char *s = src;
  size_t n = siz;

  /* Copy as many bytes as will fit */
  if (n != 0 && --n != 0)
    {
      do
	{
	  if ((*d++ = *s++) == 0)
	    break;
	}
      while (--n != 0);
    }

  /* Not enough room in dst, add NUL and traverse rest of src */
  if (n == 0)
    {
      if (siz != 0)
	*d = '\0';		/* NUL-terminate dst */
      while (*s++)
	;
    }

  return (s - src - 1);		/* count does not include NUL */
}

void
usage (FILE *os, char *progname, int exitCode)
{
  const char *name = (progname ? progname : PROGNAME);
  FILE *s = (os ? os : stderr);
  fprintf (s, "Usage: %s [-hVL]\n", name);
  exit (exitCode);
}

void
help (FILE *os, char *progname, int exitCode)
{
  static const char *helpText =
    "returns the full, official name of the operating system\n"
    "\n"
    "   --help|-h           print this help\n"
    "   --version|-V        print version information\n"
    "   --license|-L        print license information\n";

  const char *name = (progname ? progname : PROGNAME);
  FILE *s = (os ? os : stderr);

  fprintf (s, "%s [-hVL]\n", name);
  fprintf (s, "%s\n", helpText);
  exit (exitCode);
}

void
license (FILE *os, char *progname, int exitCode)
{
  static const char *licenseText =
    "Copyright (c) 2008, Charles S. Wilson\n"
    "\n"
    "Permission is hereby granted, free of charge, to any person obtaining a copy\n"
    "of this software and associated documentation files (the \"Software\"), to deal\n"
    "in the Software without restriction, including without limitation the rights\n"
    "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
    "copies of the Software, and to permit persons to whom the Software is\n"
    "furnished to do so, subject to the following conditions:\n"
    "\n"
    "The above copyright notice and this permission notice shall be included in\n"
    "all copies or substantial portions of the Software.\n"
    "\n"
    "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
    "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
    "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n"
    "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
    "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n"
    "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n"
    "THE SOFTWARE.\n"
    "\n"
    "Portions of this software are provided under independent licensing terms:\n"
    "getopt:      (c) 1989--2001 Free Software Foundation (GNU C Library), LGPLv2";

  const char *name = (progname ? progname : PROGNAME);
  FILE *s = (os ? os : stderr);

  fprintf (s, "%s version %s\n", name, VERSION);
  fprintf (s, "%s\n", licenseText);
  exit (exitCode);
}

void
version (FILE *os, char *progname, int exitCode)
{
  const char *name = (progname ? progname : PROGNAME);
  FILE *s = (os ? os : stderr);
  fprintf (s, "%s version %s\n", name, VERSION);
  exit (exitCode);
}
