Subject: [RFC/PATCH] Add an implementation of uname(1) for Windows

From: Ramsay Jones <[email-masked]>
Date: Thu, 19 Nov 2009 19:00:19 +0000

Signed-off-by: Ramsay Jones <[email-masked]>
---

While changing the Makefile recently, to fix-up the msvc build on
cygwin, I thought it may be useful to have a uname(1) command on
Windows. Well, it isn't actually needed, but it got me thinking
about how I would implement it...

I started off with some assembler to access the cpuid instruction
on x86 machines... but I soon discarded that, since all the information
I needed was already available via an WIN32 API or from the registry.

So, this is not a serious patch and is not meant for application.
However, some Windows users may find it useful... ;-)

ATB,
Ramsay Jones

 compat/vcbuild/uname.c |  475 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 475 insertions(+), 0 deletions(-)
 create mode 100644 compat/vcbuild/uname.c

diff --git a/compat/vcbuild/uname.c b/compat/vcbuild/uname.c
new file mode 100644
index 0000000..7ca6cdd
--- /dev/null
+++ b/compat/vcbuild/uname.c
@@ -0,0 +1,475 @@
+
+/*
+ * uname(1) for Windows
+ *
+ * This program is licensed under the GPL v2.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This file 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 GNU
+ * General Public License for more details.
+ *
+ */
+
+/*
+ * MSVC:         cl uname.c advapi32.lib
+ *
+ * Open Watcom:  wcl386 uname.c
+ *
+ * Cygwin gcc:   gcc -mno-cygwin -o uname uname.c
+ *
+ */
+
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0500
+#endif
+
+#include <windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+
+#define VERSION_STR "0.1"
+
+static char *prog;
+
+static int all       = 0;
+static int kernel    = 0;
+static int host      = 0;
+static int release   = 0;
+static int version   = 0;
+static int machine   = 0;
+static int processor = 0;
+static int hardware  = 0;
+static int os        = 0;
+
+static void set_program_name(char *argv0, char *def)
+{
+	static char name[256];
+	char *slash;
+	int len;
+
+	if (!argv0 || !*argv0) {
+		prog = def;
+		return;
+	}
+
+	/* get last path component */
+	if ((slash = strrchr(argv0, '/')) == NULL)
+		prog = argv0;
+	else
+		prog = slash+1;
+
+	/* check for win32 separator */
+	if ((slash = strrchr(prog, '\\')) != NULL)
+		prog = slash+1;
+
+	/* strip '.exe' suffix, if any */
+	len = strlen(prog);
+	if (len > 4 && strcmp(&prog[len-4], ".exe") == 0) {
+		if (len < sizeof(name)) {
+			strcpy(name, prog);
+			name[len-4] = '\0';
+			prog = name;
+		}
+	}
+}
+
+static void die(const char *fmt, ...)
+{
+	va_list ap;
+
+	if (prog)
+		fprintf(stderr, "%s: ", prog);
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	fprintf(stderr, "\n");
+	exit(1);
+}
+
+static void usage(void)
+{
+	printf("usage: %s [OPTION]...\n\n", prog);
+	printf("  -a, --all                "
+		      "print all information, except -p and -i\n");
+	printf("  -s, --kernel-name        "
+		      "print the kernel name\n");
+	printf("  -n, --nodename           "
+		      "print the network node hostname\n");
+	printf("  -r, --kernel-release     "
+		      "print the kernel release\n");
+	printf("  -v, --kernel-version     "
+		      "print the kernel version\n");
+	printf("  -m, --machine            "
+		      "print the machine hardware name\n");
+	printf("  -p, --processor          "
+		      "print the processor type\n");
+	printf("  -i, --hardware-platform  "
+		      "print the hardware platform\n");
+	printf("  -o, --operating-system   "
+		      "print the operating system\n");
+	printf("      --help               "
+		      "print this help and exit\n");
+	printf("      --version            "
+		      "print version information and exit\n");
+	printf("\n");
+	exit(0);
+}
+
+static void print_version(void)
+{
+	printf("%s version %s\n", prog, VERSION_STR);
+	exit(0);
+}
+
+static void do_long_option(char *opt)
+{
+	static struct { char *name; int *option; } tab[] = {
+		{ "--all",               &all       },
+		{ "--kernel-name",       &kernel    },
+		{ "--nodename",          &host      },
+		{ "--kernel-release",    &release   },
+		{ "--kernel-version",    &version   },
+		{ "--machine",           &machine   },
+		{ "--processor",         &processor },
+		{ "--hardware-platform", &hardware  },
+		{ "--operating-system",  &os        },
+		{ NULL,                  NULL       }
+	};
+	int i;
+
+	if (!opt || !*opt)
+		die("internal error: !opt || !*opt");
+
+	for (i = 0; tab[i].name; i++) {
+		if (strcmp(opt, tab[i].name) == 0) {
+			*tab[i].option = 1;
+			return;
+		}
+	}
+
+	if (strcmp(opt, "--help") == 0)
+		usage();          /* does not return */
+
+	if (strcmp(opt, "--version") == 0)
+		print_version();  /* does not return */
+
+	die("unknown option: '%s', try --help", opt);
+}
+
+static void do_short_options(char *opts)
+{
+	char *a = &opts[1];
+
+	while (*a) {
+		switch (*a) {
+			case 'a': all = 1;       break;
+			case 's': kernel = 1;    break;
+			case 'n': host = 1;      break;
+			case 'r': release = 1;   break;
+			case 'v': version = 1;   break;
+			case 'm': machine = 1;   break;
+			case 'p': processor = 1; break;
+			case 'i': hardware = 1;  break;
+			case 'o': os = 1;        break;
+			default:
+				  die("unknown option: '%c', try --help", *a);
+				  break;
+		}
+		++a;
+	}
+}
+
+static void process_args(int argc, char *argv[])
+{
+	int argpos = 1;
+
+	if (argc == 1)    /* no options... */
+		kernel = 1;  /* same as -s */
+
+	/* check for [OPTIONS]... */
+	while (argpos < argc) {
+		if (argv[argpos][0] != '-')  /* no option */
+			break;
+		if (argv[argpos][1] == '-')  /* --long option */
+			do_long_option(argv[argpos]);
+		else                         /* -short option(s) */
+			do_short_options(argv[argpos]);
+		argpos++;
+	}
+
+	/* no other args allowed */
+	if (argpos != argc)
+		die("unexpected argument: '%s', try --help", argv[argpos]);
+
+	if (all)
+		kernel = host = release = version = machine = os = 1;
+}
+
+static char *get_kernel(void)
+{
+	return "Windows";
+}
+
+static char *get_hostname(void)
+{
+	static char name[256];
+	unsigned long len = sizeof(name)-1;
+
+	if (GetComputerNameExA(ComputerNameDnsHostname, name, &len))
+		return name;
+	return "unknown";
+}
+
+static OSVERSIONINFO *get_version_info(void)
+{
+	static OSVERSIONINFO ver;
+	static int called;
+
+	if (!called) {
+		ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+		GetVersionEx(&ver);
+		++called;
+	}
+	return &ver;
+}
+static char *get_release(void)
+{
+	static char str[256];
+	OSVERSIONINFO *v = get_version_info();
+	_snprintf(str, sizeof(str)-1, "%d.%d",
+			v->dwMajorVersion, v->dwMinorVersion);
+	return str;
+}
+
+static char *get_version(void)
+{
+	static char str[256];
+	OSVERSIONINFO *v = get_version_info();
+	_snprintf(str, sizeof(str)-1, "%d(%s)",
+			v->dwBuildNumber, v->szCSDVersion);
+	return str;
+}
+
+static SYSTEM_INFO *get_system_info(void)
+{
+	static SYSTEM_INFO sys;
+	static int called;
+
+	if (!called) {
+		GetSystemInfo(&sys);
+		++called;
+	}
+	return &sys;
+}
+
+static char *get_arch(int arch)
+{
+	static struct { int arch_enum; char *name; } tab[] = {
+		{ PROCESSOR_ARCHITECTURE_INTEL,   "INTEL"    },
+		{ PROCESSOR_ARCHITECTURE_MIPS,    "MIPS"     },
+		{ PROCESSOR_ARCHITECTURE_ALPHA,   "ALPHA"    },
+		{ PROCESSOR_ARCHITECTURE_PPC,     "PPC"      },
+		{ PROCESSOR_ARCHITECTURE_SHX,     "SHX"      },
+		{ PROCESSOR_ARCHITECTURE_ARM,     "ARM"      },
+		{ PROCESSOR_ARCHITECTURE_IA64,    "IA-64"    },
+		{ PROCESSOR_ARCHITECTURE_ALPHA64, "ALPHA-64" },
+		{ PROCESSOR_ARCHITECTURE_AMD64,   "AMD-64"   },
+		{ PROCESSOR_ARCHITECTURE_UNKNOWN, NULL       }
+	};
+	int i;
+
+	for (i = 0; tab[i].name; i++)
+		if (tab[i].arch_enum == arch)
+			return tab[i].name;
+	return NULL;
+}
+
+static char *get_x86_machine(int family)
+{
+	static char m[256];
+
+	switch (family) {
+		case 3: case 4: case 5: case 6:
+			_snprintf(m, sizeof(m)-1, "i%c86", family+'0');
+			break;
+		case 15: /* pentium 4 */
+			strcpy(m, "i686");
+			break;
+		default:
+			_snprintf(m, sizeof(m)-1, "x86 Family %d", family);
+			break;
+	}
+	return m;
+}
+
+static char *get_machine(void)
+{
+	SYSTEM_INFO *s = get_system_info();
+	char *arch = get_arch(s->wProcessorArchitecture);
+
+	if (arch) {
+		if (strcmp(arch, "INTEL") == 0)
+			return get_x86_machine(s->wProcessorLevel);
+		return arch;
+	}
+	return "unknown";
+}
+
+static char cpuinfo_key[] =
+	"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0";
+
+static char *get_processor_name(void)
+{
+	static char cpu[256];
+	DWORD t, len = sizeof(cpu)-1;
+	LONG l;
+	HKEY key;
+
+	l = RegOpenKeyA(HKEY_LOCAL_MACHINE, cpuinfo_key, &key);
+	if (l != ERROR_SUCCESS)
+		return NULL;
+
+	l = RegQueryValueExA(key, "ProcessorNameString", NULL, &t, cpu, &len);
+	if (l != ERROR_SUCCESS)
+		l = RegQueryValueExA(key, "Identifier", NULL, &t, cpu, &len);
+
+	RegCloseKey(key);
+
+	if (l != ERROR_SUCCESS)
+		return NULL;
+
+	return cpu;
+}
+
+static char *map_sysinfo_to_cpu(void)
+{
+	static char cpu[256];
+	size_t n = sizeof(cpu)-1;
+	SYSTEM_INFO *s = get_system_info();
+	char *arch = get_arch(s->wProcessorArchitecture);
+
+	if (arch && (strcmp(arch, "INTEL") == 0)) {
+		int family = s->wProcessorLevel;
+		int model = (((s->wProcessorRevision) >> 8) & 0xff);
+		int stepping = ((s->wProcessorRevision) & 0xff);
+
+		_snprintf(cpu, n, "x86 Family %d Model %d Stepping %d",
+				family, model, stepping);
+		return cpu;
+	}
+	return NULL;
+}
+
+static char *get_processor(void)
+{
+	char *cpu = get_processor_name();
+
+	if (cpu || (cpu = map_sysinfo_to_cpu()))
+		return cpu;
+
+	return "unknown";
+}
+
+static char *get_hardware(void)
+{
+	return "unknown";
+}
+
+static char osinfo_key[] =
+	"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
+
+static char *get_product_name(void)
+{
+	static char product[256];
+	DWORD type, len = sizeof(product)-1;
+	LONG l;
+	HKEY key;
+
+	l = RegOpenKeyA(HKEY_LOCAL_MACHINE, osinfo_key, &key);
+	if (l != ERROR_SUCCESS)
+		return NULL;
+
+	l = RegQueryValueExA(key, "ProductName", NULL, &type, product, &len);
+
+	RegCloseKey(key);
+
+	if (l != ERROR_SUCCESS)
+		return NULL;
+
+	return product;
+}
+
+/*
+ * The OS version does not provide a unique mapping to OS product name.
+ * So, use this function only as a last resort.
+ */
+static char *map_version_to_os(void)
+{
+	static struct { int version; char *os_name; } tab[] = {
+		{ 0x500, "Windows 2000"        },
+		{ 0x501, "Windows XP"          },
+		{ 0x502, "Windows Server 2003" }, /* also Server 2003 R2 */
+		{ 0x600, "Windows Vista"       }, /* also Server 2008    */
+		{ 0x601, "Windows 7"           }, /* also Server 2008 R2 */
+		{ 0,     NULL                  }
+	};
+	OSVERSIONINFO *v = get_version_info();
+	int i, version = (v->dwMajorVersion << 8) + v->dwMinorVersion;
+
+	for (i = 0; tab[i].version != 0; i++) {
+		if (version == tab[i].version)
+			return tab[i].os_name;
+	}
+
+	return NULL;
+}
+
+static char *get_os(void)
+{
+	char *os_name = get_product_name();
+
+	if (os_name || (os_name = map_version_to_os()))
+		return os_name;
+
+	return "Windows";
+}
+
+static void print_info(void)
+{
+	static struct { int *opt; char *(*get)(void); } info[] = {
+		{ &kernel,    get_kernel    },
+		{ &host,      get_hostname  },
+		{ &release,   get_release   },
+		{ &version,   get_version   },
+		{ &machine,   get_machine   },
+		{ &processor, get_processor },
+		{ &hardware,  get_hardware  },
+		{ &os,        get_os        },
+		{ NULL,       0             }
+	};
+	char *pad = "";
+	int i;
+
+	for (i = 0; info[i].opt; i++) {
+		if (*(info[i].opt)) {
+			printf("%s%s", pad, (info[i].get)());
+			pad = " ";
+		}
+	}
+	printf("\n");
+}
+
+int main(int argc, char *argv[])
+{
+	set_program_name(argv[0], "uname");
+	process_args(argc,argv);
+	print_info();
+	return 0;
+}
-- 
1.6.5