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

/* mingw.org w32api does not yet define these constants */
#ifndef PRODUCT_UNLICENSED
#define PRODUCT_UNLICENSED				0xABCDABCD

#define PRODUCT_UNDEFINED				0x00000000
#define PRODUCT_ULTIMATE				0x00000001
#define PRODUCT_HOME_BASIC				0x00000002
#define PRODUCT_HOME_PREMIUM				0x00000003
#define PRODUCT_ENTERPRISE				0x00000004
#define PRODUCT_HOME_BASIC_N				0x00000005
#define PRODUCT_BUSINESS				0x00000006
#define PRODUCT_STANDARD_SERVER				0x00000007
#define PRODUCT_DATACENTER_SERVER			0x00000008
#define PRODUCT_SMALLBUSINESS_SERVER			0x00000009
#define PRODUCT_ENTERPRISE_SERVER			0x0000000A
#define PRODUCT_STARTER					0x0000000B
#define PRODUCT_DATACENTER_SERVER_CORE			0x0000000C
#define PRODUCT_STANDARD_SERVER_CORE			0x0000000D
#define PRODUCT_ENTERPRISE_SERVER_CORE			0x0000000E
#define PRODUCT_ENTERPRISE_SERVER_IA64			0x0000000F
#define PRODUCT_BUSINESS_N				0x00000010
#define PRODUCT_WEB_SERVER				0x00000011
#define PRODUCT_CLUSTER_SERVER				0x00000012
#define PRODUCT_HOME_SERVER				0x00000013
#define PRODUCT_STORAGE_EXPRESS_SERVER			0x00000014
#define PRODUCT_STORAGE_STANDARD_SERVER			0x00000015
#define PRODUCT_STORAGE_WORKGROUP_SERVER		0x00000016
#define PRODUCT_STORAGE_ENTERPRISE_SERVER		0x00000017
#define PRODUCT_SERVER_FOR_SMALLBUSINESS		0x00000018
#define PRODUCT_SMALLBUSINESS_SERVER_PREMIUM		0x00000019
#define PRODUCT_HOME_PREMIUM_N				0x0000001A
#define PRODUCT_ENTERPRISE_N				0x0000001B
#define PRODUCT_ULTIMATE_N				0x0000001C
#define PRODUCT_WEB_SERVER_CORE				0x0000001D
#define PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT	0x0000001E
#define PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY		0x0000001F
#define PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING		0x00000020
#define PRODUCT_SERVER_FOUNDATION			0x00000021
#define PRODUCT_HOME_PREMIUM_SERVER			0x00000022
#define PRODUCT_SERVER_FOR_SMALLBUSINESS_V		0x00000023
#define PRODUCT_STANDARD_SERVER_V			0x00000024
#define PRODUCT_DATACENTER_SERVER_V			0x00000025
#define PRODUCT_ENTERPRISE_SERVER_V			0x00000026
#define PRODUCT_DATACENTER_SERVER_CORE_V		0x00000027
#define PRODUCT_STANDARD_SERVER_CORE_V			0x00000028
#define PRODUCT_ENTERPRISE_SERVER_CORE_V		0x00000029
#define PRODUCT_HYPERV					0x0000002A
#define PRODUCT_STORAGE_EXPRESS_SERVER_CORE		0x0000002B
#define PRODUCT_STORAGE_STANDARD_SERVER_CORE		0x0000002C
#define PRODUCT_STORAGE_WORKGROUP_SERVER_CORE		0x0000002D
#define PRODUCT_STORAGE_ENTERPRISE_SERVER_CORE		0x0000002E
#define PRODUCT_STARTER_N				0x0000002F
#define PRODUCT_PROFESSIONAL				0x00000030
#define PRODUCT_PROFESSIONAL_N				0x00000031
#define PRODUCT_SB_SOLUTION_SERVER			0x00000032
#define PRODUCT_SERVER_FOR_SB_SOLUTIONS			0x00000033
#define PRODUCT_STANDARD_SERVER_SOLUTIONS		0x00000034
#define PRODUCT_STANDARD_SERVER_SOLUTIONS_CORE		0x00000035
#define PRODUCT_SB_SOLUTION_SERVER_EM			0x00000036
#define PRODUCT_SERVER_FOR_SB_SOLUTIONS_EM		0x00000037
#define PRODUCT_SOLUTION_EMBEDDEDSERVER			0x00000038
#define PRODUCT_SOLUTION_EMBEDDEDSERVER_CORE		0x00000039
/* #define PRODUCT_????					0x0000003A */
#define PRODUCT_ESSENTIALBUSINESS_SERVER_MGMT		0x0000003B
#define PRODUCT_ESSENTIALBUSINESS_SERVER_ADDL		0x0000003C
#define PRODUCT_ESSENTIALBUSINESS_SERVER_MGMTSVC	0x0000003D
#define PRODUCT_ESSENTIALBUSINESS_SERVER_ADDLSVC	0x0000003E
#define PRODUCT_SMALLBUSINESS_SERVER_PREMIUM_CORE	0x0000003F
#define PRODUCT_CLUSTER_SERVER_V			0x00000040
#define PRODUCT_EMBEDDED				0x00000041
#define PRODUCT_STARTER_E				0x00000042
#define PRODUCT_HOME_BASIC_E				0x00000043
#define PRODUCT_HOME_PREMIUM_E				0x00000044
#define PRODUCT_PROFESSIONAL_E				0x00000045
#define PRODUCT_ENTERPRISE_E				0x00000046
#define PRODUCT_ULTIMATE_E				0x00000047
#endif

/* neither mingw.org w32api nor mingw64 (32bit) w32api define
 * these constants, but mingw64 (64bit) w32api does */
#ifndef PRODUCT_ENTERPRISE_EVALUATION
#define PRODUCT_ENTERPRISE_EVALUATION			0x00000048
/* #define PRODUCT_????					0x00000049 */
/* #define PRODUCT_????					0x0000004A */
/* #define PRODUCT_????					0x0000004B */
#define PRODUCT_MULTIPOINT_STANDARD_SERVER		0x0000004C
#define PRODUCT_MULTIPOINT_PREMIUM_SERVER		0x0000004D
/* #define PRODUCT_????					0x0000004E */
#define PRODUCT_STANDARD_EVALUATION_SERVER		0x0000004F
#define PRODUCT_DATACENTER_EVALUATION_SERVER		0x00000050
/* #define PRODUCT_????					0x00000051 */
/* #define PRODUCT_????					0x00000052 */
/* #define PRODUCT_????					0x00000053 */
#define PRODUCT_ENTERPRISE_N_EVALUATION			0x00000054
/* #define PRODUCT_????					0x00000055 */
/* #define PRODUCT_????					0x00000056 */
/* #define PRODUCT_????					0x00000057 */
/* #define PRODUCT_????					0x00000058 */
/* #define PRODUCT_????					0x00000059 */
/* #define PRODUCT_????					0x0000005A */
/* #define PRODUCT_????					0x0000005B */
/* #define PRODUCT_????					0x0000005C */
/* #define PRODUCT_????					0x0000005D */
/* #define PRODUCT_????					0x0000005E */
#define PRODUCT_STORAGE_WORKGROUP_EVALUATION_SERVER	0x0000005F
#define PRODUCT_STORAGE_STANDARD_EVALUATION_SERVER	0x00000060
/* #define PRODUCT_????					0x00000061 */
#define PRODUCT_CORE_N					0x00000062
#define PRODUCT_CORE_COUNTRYSPECIFIC			0x00000063
#define PRODUCT_CORE_SINGLELANGUAGE			0x00000064
#define PRODUCT_CORE					0x00000065
/* #define PRODUCT_????					0x00000066 */
#define PRODUCT_PROFESSIONAL_WMC			0x00000067
#endif

static LPTSTR products[] = {
/* 0x00000000 */ "",
/* 0x00000001 */ " Ultimate Edition",
/* 0x00000002 */ " Home Basic Edition",
/* 0x00000003 */ " Home Premium Edition",
/* 0x00000004 */ " Enterprise Edition",
/* 0x00000005 */ " Home Basic N",
/* 0x00000006 */ " Business Edition",
/* 0x00000007 */ " Standard Edition",
/* 0x00000008 */ " Datacenter Edition",
/* 0x00000009 */ " Small Business Server",
/* 0x0000000a */ " Enterprise Edition",
/* 0x0000000b */ " Starter Edition",
/* 0x0000000c */ " Datacenter Edition (core installation)",
/* 0x0000000d */ " Standard Edition (core installation)",
/* 0x0000000e */ " Enterprise Edition (core installation)",
/* 0x0000000f */ " Enterprise Edition for Itanium-based Systems",
/* 0x00000010 */ " Business N",
/* 0x00000011 */ " Web Server Edition",
/* 0x00000012 */ " Cluster Server 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 Edition",
/* 0x0000001a */ " Home Premium N",
/* 0x0000001b */ " Enterprise N",
/* 0x0000001c */ " Ultimate N",
/* 0x0000001d */ " Web Server (ore installation)",
/* 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 without Hyper-V (core installation)",
/* 0x00000028 */ " Server Standard without Hyper-V (core installation)",
/* 0x00000029 */ " Server Enterprise without Hyper-V (core installation)",
/* 0x0000002a */ " Hyper-V Server",
/* 0x0000002b */ " Storage Server Express (core installation)",
/* 0x0000002c */ " Storage Server Standard (core installation)",
/* 0x0000002d */ " Storage Server Workgroup (core installation)",
/* 0x0000002e */ " Storage Server Enterprise (core installation)",
/* 0x0000002f */ " Starter N",
/* 0x00000030 */ " Professional",
/* 0x00000031 */ " Professional N",
/* 0x00000032 */ " Small Business Server 2011 Essentials",
/* 0x00000033 */ " Server for Small Business Solutions",
/* 0x00000034 */ " Standard Server Solutions",
/* 0x00000035 */ " Standard Server Solutions (core installation)",
/* 0x00000036 */ " Small Business Server 2011 Essentials Embedded",
/* 0x00000037 */ " Server for Small Business Solutions Embedded",
/* 0x00000038 */ " Multipoint Server",
/* 0x00000039 */ " Multipoint Server (core installation)",
/* 0x0000003a */ "",
/* 0x0000003b */ " Essential Business Server Management",
/* 0x0000003c */ " Essential Business Server Additional",
/* 0x0000003d */ " Essential Business Server Management Services",
/* 0x0000003e */ " Essential Business Server Additional Services",
/* 0x0000003f */ " Small Business Server Premium Edition (core installation)",
/* 0x00000040 */ " Cluster Server Edition without Hyper-V",
/* 0x00000041 */ " Embedded",
/* 0x00000042 */ " Starter E",
/* 0x00000043 */ " Home Basic E",
/* 0x00000044 */ " Home Premium E",
/* 0x00000045 */ " Professional E",
/* 0x00000046 */ " Enterprise E",
/* 0x00000047 */ " Ultimate E"
/* 0x00000048 */ " Server Enterprise (evaluation installation)",
/* 0x00000049 */ "",
/* 0x0000004a */ "",
/* 0x0000004b */ "",
/* 0x0000004c */ " Multipoint Server Standard (full installation)",
/* 0x0000004d */ " Multipoint Server Premium (full installation)",
/* 0x0000004e */ "",
/* 0x0000004f */ " Server Standard (evaluation installation)",
/* 0x00000050 */ " Server Datacenter (evaluation installation)",
/* 0x00000051 */ "",
/* 0x00000052 */ "",
/* 0x00000053 */ "",
/* 0x00000054 */ " Enterprise N (evaluation installation)",
/* 0x00000055 */ "",
/* 0x00000056 */ "",
/* 0x00000057 */ "",
/* 0x00000058 */ "",
/* 0x00000059 */ "",
/* 0x0000005a */ "",
/* 0x0000005b */ "",
/* 0x0000005c */ "",
/* 0x0000005d */ "",
/* 0x0000005e */ "",
/* 0x0000005f */ " Storage Server Workgroup (evaluation installation)",
/* 0x00000060 */ " Storage Server Standard (evaluation installation)",
/* 0x00000061 */ "",
/* 0x00000062 */ " N",
/* 0x00000063 */ " China",
/* 0x00000064 */ " Single Language",
/* 0x00000065 */ "", /* just plain "Windows 8" */
/* 0x00000066 */ "",
/* 0x00000067 */ " Professional with Media Center"
};

#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);
extern NTSTATUS NTAPI RtlGetProductInfo (ULONG, ULONG, ULONG, ULONG, PULONG);

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)
    {
      switch (osvi.dwMinorVersion)
	{
	case 1:
	  strlcat (pszOS, "XP", BUFSIZE);
	  if (GetSystemMetrics (SM_MEDIACENTER))
	    strlcat (pszOS, " Media Center Edition", BUFSIZE);
	  else if (GetSystemMetrics (SM_TABLETPC))
	    strlcat (pszOS, " Tablet PC Edition", BUFSIZE);
	  else if (GetSystemMetrics (SM_STARTER))
	    strlcat (pszOS, " Starter Edition", BUFSIZE);
	  else if (osvi.wSuiteMask & VER_SUITE_PERSONAL)
	    strlcat (pszOS, " Home Edition", BUFSIZE);
	  else
	    strlcat (pszOS, " Professional", BUFSIZE);
	  break;
	case 2:
	  if (GetSystemMetrics (SM_SERVERR2))
	    strlcat (pszOS, "Server 2003 R2, ", BUFSIZE);
	  else if (osvi.wSuiteMask == VER_SUITE_STORAGE_SERVER)
	    strlcat (pszOS, "Storage Server 2003, ", BUFSIZE);
	  else if (osvi.wProductType == VER_NT_WORKSTATION &&
		   si.wProcessorArchitecture ==
		   PROCESSOR_ARCHITECTURE_AMD64)
	    strlcat (pszOS, "XP Professional x64 Edition", BUFSIZE);
	  else
	    strlcat (pszOS, "Server 2003, ", BUFSIZE);

	  // Test for the server type.
	  if (osvi.wProductType != VER_NT_WORKSTATION)
	    {
	      if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64)
		{
		  if (osvi.wSuiteMask & VER_SUITE_DATACENTER)
		    strlcat (pszOS,
			     "Datacenter Edition for Itanium-based Systems",
			     BUFSIZE);
		  else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE)
		    strlcat (pszOS,
			     "Enterprise Edition for Itanium-based Systems",
			     BUFSIZE);
		}
	      else if (si.wProcessorArchitecture ==
		       PROCESSOR_ARCHITECTURE_AMD64)
		{
		  if (osvi.wSuiteMask & VER_SUITE_DATACENTER)
		    strlcat (pszOS, "Datacenter x64 Edition", BUFSIZE);
		  else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE)
		    strlcat (pszOS, "Enterprise x64 Edition", BUFSIZE);
		  else
		    strlcat (pszOS, "Standard x64 Edition", BUFSIZE);
		}
	      else
		{
		  if (osvi.wSuiteMask & VER_SUITE_COMPUTE_SERVER)
		    strlcat (pszOS, "Compute Cluster Edition", BUFSIZE);
		  else if (osvi.wSuiteMask & VER_SUITE_DATACENTER)
		    strlcat (pszOS, "Datacenter Edition", BUFSIZE);
		  else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE)
		    strlcat (pszOS, "Enterprise Edition", BUFSIZE);
		  else if (osvi.wSuiteMask & VER_SUITE_BLADE)
		    strlcat (pszOS, "Web Edition", BUFSIZE);
		  else
		    strlcat (pszOS, "Standard Edition", BUFSIZE);
		}
	    }
	  break;
	default:
	  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" : "Server 2014", BUFSIZE);
	    break;
	  }
      else if (osvi.dwMajorVersion == 10)
	strlcat (pszOS, osvi.wProductType == VER_NT_WORKSTATION
		 ? "10" : "Server 2014", BUFSIZE);

      pGPI (osvi.dwMajorVersion,
	    osvi.dwMinorVersion,
	    osvi.wServicePackMajor, osvi.wServicePackMinor, &dwType);

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

      if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)
	strlcat (pszOS, ", 64-bit", BUFSIZE);
      else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL)
	strlcat (pszOS, ", 32-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 %d)", 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);
}
