/* GKrellM
|  Copyright (C) 1999-2019 Bill Wilson
|                2002      Bill Nalen
|                2007-2019 Stefan Gehn
|
|  Authors:  Bill Wilson    billw@gkrellm.net
|            Bill Nalen     bill@nalens.com
|            Stefan Gehn    stefan+gkrellm@srcbox.net
|  Latest versions might be found at:  http://gkrellm.net
|
|
|  GKrellM is free software: you can redistribute it and/or modify it
|  under the terms of the GNU General Public License as published by
|  the Free Software Foundation, either version 3 of the License, or
|  (at your option) any later version.
|
|  GKrellM 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.
|
|  You should have received a copy of the GNU General Public License
|  along with this program. If not, see http://www.gnu.org/licenses/
|
|
|  Additional permission under GNU GPL version 3 section 7
|
|  If you modify this program, or any covered work, by linking or
|  combining it with the OpenSSL project's OpenSSL library (or a
|  modified version of that library), containing parts covered by
|  the terms of the OpenSSL or SSLeay licenses, you are granted
|  additional permission to convey the resulting work.
|  Corresponding Source for a non-source form of such a combination
|  shall include the source code for the parts of OpenSSL used as well
|  as that of the covered work.
*/

#include "../inet.h" // For struct ActiveTCP
#if defined(GKRELLM_SERVER)
	#include "../../server/win32-plugin.h"
#else
	#include "../win32-plugin.h"
#endif

#include <winioctl.h> // For cdrom eject
#include <iphlpapi.h> // For tcp connection stats

// Following two are for cpu, proc, disk and network stats
// which are queried via "performance data counters"
#include <pdh.h>
#include <pdhmsg.h>

// Following two are used to determine number of logged in users and
// pagefile usage via NT-APIs
#include <subauth.h>
#include <ntsecapi.h>

#if _WIN32_WINNT >= 0x501 // Windows XP or newer
#include <wtsapi32.h>
#endif // _WIN32_WINNT >= 0x501


// ----------------------------------------------------------------------------
// Needed to determine pagefile usage
//
// These definitions were taken from MinGW include/ddk/ntapi.h because you
// cannot mix ddk includes with normal windows includes although you can call
// these functions without being a driver.
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
#define SystemPagefileInformation 18

typedef NTSTATUS (NTAPI *pfZwQuerySystemInformation)(
  /*IN*/ UINT SystemInformationClass,
  /*IN OUT*/ VOID *SystemInformation,
  /*IN*/ ULONG SystemInformationLength,
  /*OUT*/ ULONG *ReturnLength /*OPTIONAL*/);

typedef struct _SYSTEM_PAGEFILE_INFORMATION
	{
	ULONG  NextEntryOffset;
	ULONG  CurrentSize;
	ULONG  TotalUsed;
	ULONG  PeakUsed;
	UNICODE_STRING  FileName;
	}
	SYSTEM_PAGEFILE_INFORMATION;


// ----------------------------------------------------------------------------
/* Structs and typedefs used to determine the number of logged in users.
 * These should be in ntsecapi.h but are missing in MinGW currently, they
 * are present in the headers provided by mingw-w64.
 * Docs: http://msdn.microsoft.com/en-us/library/aa378290(VS.85).aspx
 */ 
#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)
typedef struct _SECURITY_LOGON_SESSION_DATA
	{
	ULONG Size;
	LUID LogonId;
	LSA_UNICODE_STRING UserName;
	LSA_UNICODE_STRING LogonDomain;
	LSA_UNICODE_STRING AuthenticationPackage;
	ULONG LogonType;
	ULONG Session;
	PSID Sid;
	LARGE_INTEGER LogonTime;
	LSA_UNICODE_STRING LogonServer;
	LSA_UNICODE_STRING DnsDomainName;
	LSA_UNICODE_STRING Upn;
	}
	SECURITY_LOGON_SESSION_DATA;
#endif

// Definitions for function pointers (functions resolved manually at runtime)
typedef NTSTATUS (NTAPI *pfLsaEnumerateLogonSessions)(
		ULONG *LogonSessionCount, LUID **LogonSessionList);
typedef NTSTATUS (NTAPI *pfLsaGetLogonSessionData)(
		LUID *LogonId, SECURITY_LOGON_SESSION_DATA **ppLogonSessionData);
typedef NTSTATUS (NTAPI *pfLsaFreeReturnBuffer)(VOID *Buffer);


// ----------------------------------------------------------------------------
// Missing definitions from MinGW iphlpapi.h

typedef struct {
  DWORD dwState; // one of MIB_TCP_STATE_*
  IN6_ADDR LocalAddr;
  DWORD dwLocalScopeId;
  DWORD dwLocalPort;
  IN6_ADDR RemoteAddr;
  DWORD dwRemoteScopeId;
  DWORD dwRemotePort;
} MIB_TCP6ROW, *PMIB_TCP6ROW;

typedef struct {
  DWORD dwNumEntries;
  MIB_TCP6ROW table[ANY_SIZE];
} MIB_TCP6TABLE, *PMIB_TCP6TABLE;

typedef DWORD (WINAPI *GetTcp6TableFunc)(PMIB_TCP6TABLE TcpTable,
	PDWORD SizePointer, BOOL Order);


// ----------------------------------------------------------------------------
// Max len of device names returned by clean_dev_name().
// Value taken from net.c load_net_config() and disk.c load_disk_config().
#define MAX_DEV_NAME 31

#define ARR_SZ(x) (sizeof(x) / sizeof(x[0]))

static PDH_HQUERY pdhQueryHandle = NULL;

static const wchar_t* PDHDLL = L"PDH.DLL";
static const wchar_t* NTDLL = L"NTDLL.DLL";
static const wchar_t* WTSAPI32 = L"WTSAPI32.DLL";


// ----------------------------------------------------------------------------
// Own cleanup functions, called in gkrellm_sys_main_cleanup() to cleanup
// resources allocated by gkrellm_sys_*_init()

static void gkrellm_sys_cpu_cleanup(void);
static void gkrellm_sys_disk_cleanup(void);
static void gkrellm_sys_mem_cleanup(void);
static void gkrellm_sys_inet_cleanup(void);
static void gkrellm_sys_net_cleanup(void);
static void gkrellm_sys_proc_cleanup(void);


// ----------------------------------------------------------------------------

//! print a warning and (if possible) decode a windows error number
static void
win32_warning(const wchar_t *dll_name, DWORD status, const gchar *format, ...)
	{
	va_list varargs;
	wchar_t *status_msg = NULL;
	gchar *formatted_msg = NULL;
	HMODULE dll_handle = NULL;
	DWORD flags =   FORMAT_MESSAGE_ALLOCATE_BUFFER
	              | FORMAT_MESSAGE_FROM_SYSTEM
	              | FORMAT_MESSAGE_IGNORE_INSERTS;

	va_start(varargs, format);
	// Format passed message string
	formatted_msg = g_strdup_vprintf(format, varargs);
	va_end(varargs);

	// Load library for message strings if one was passed
	if (dll_name != NULL)
	{
		dll_handle = LoadLibraryW(dll_name);
		if (dll_handle != NULL)
			flags |= FORMAT_MESSAGE_FROM_HMODULE;
	}

	// NOTE: yes, this call takes a wchar_t **, it's a known flaw in the
	//       WIN32 API, you can ignore any compiler warning about arg 5
	if (FormatMessageW(
		  flags // dwFlags
        , dll_handle // lpSource
        , status // dwMessageId
        , MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) // dwLanguageId
		, (LPWSTR)&status_msg // lpBuffer
		, 0 // nSize
		, NULL // varargs
		) > 0)
		{
		g_log(NULL, G_LOG_LEVEL_WARNING, "%s; Error 0x%lX: %ls",
				formatted_msg, status, status_msg);
		LocalFree(status_msg);
		}
	else
		{
		g_log(NULL, G_LOG_LEVEL_WARNING, "%s; Error 0x%lX\n",
				formatted_msg, status);
		}

	g_free(formatted_msg);
	if (dll_handle != NULL)
		FreeLibrary(dll_handle);
	}


// ----------------------------------------------------------------------------

// Simple wrapper around PdhAddCounter with error/debug handling
static gboolean
add_counter(const wchar_t *counter_path, PDH_HCOUNTER *counter_handle)
	{
	PDH_STATUS st;

	if (pdhQueryHandle == NULL || !counter_path || !counter_handle)
		return FALSE;

	st = PdhAddCounterW(pdhQueryHandle, counter_path, 0, counter_handle);
	if (st != ERROR_SUCCESS)
		{
		win32_warning(PDHDLL, st, "Failed adding pdh-counter for path '%ls'",
				counter_path);
		return FALSE;
		}

	gkrellm_debug(DEBUG_SYSDEP, "Added pdh-counter for path '%ls'\n",
			counter_path);
	return TRUE;
	}

static gboolean
get_formatted_counter_value(
		PDH_HCOUNTER counter_handle, const gchar *counter_name,
		DWORD format, PDH_FMT_COUNTERVALUE *val)
	{
	PDH_STATUS st;

	st = PdhGetFormattedCounterValue(counter_handle, format, NULL, val);
	if ((st != ERROR_SUCCESS) || (val->CStatus != PDH_CSTATUS_VALID_DATA))
		{
		win32_warning(PDHDLL, st,
			"Getting pdh-counter (%s) failed; CStatus %lX",
			counter_name, val->CStatus);
		return FALSE;
		}
	return TRUE;
	}

// Simple wrapper around PdhLookupPerfNameByIndex with error handling
static gboolean
lookup_perfname(DWORD index, wchar_t *perfname, DWORD perfname_max_len)
	{
	PDH_STATUS st;

	if (!perfname || perfname_max_len == 0)
		return FALSE;

	st = PdhLookupPerfNameByIndexW(NULL, index, perfname, &perfname_max_len);
	if (st != ERROR_SUCCESS)
		{
		win32_warning(PDHDLL, st, "Could not lookup perfname for index %lu",
				index);
		return FALSE;
		}

	if (perfname[0] == 0)
		{
		g_warning("Got empty perfname for index %lu, performance counters " \
				"appear to be broken on this system!\n", index);
		return FALSE;
		}

	gkrellm_debug(DEBUG_SYSDEP, "Looked up perfname '%ls' for index %lu\n",
			perfname, index);
	return TRUE;
	}

typedef void (*add_counter_cb) (wchar_t *name, PDH_HCOUNTER *c1, PDH_HCOUNTER *c2);

static void
add_counter_list(guint object_index,
		guint counter_index1, guint counter_index2,
		add_counter_cb cb)
	{
	PDH_STATUS  st;
	wchar_t		obj_name[PDH_MAX_COUNTER_NAME];
	wchar_t		c1_name[PDH_MAX_COUNTER_NAME];
	wchar_t		c2_name[PDH_MAX_COUNTER_NAME];
	wchar_t *	obj_list = NULL;
	DWORD		obj_list_size = 0;
	wchar_t *	inst_list = NULL;
	DWORD		inst_list_size = 0;
	wchar_t		counter_path[PDH_MAX_COUNTER_PATH];
	wchar_t *	inst = NULL;
	PDH_HCOUNTER c1;
	PDH_HCOUNTER c2;

	gkrellm_debug(DEBUG_SYSDEP, "add_counter_list()\n");
	if (pdhQueryHandle == NULL)
		return;

	// Get translated name for object_index
	if (!lookup_perfname(object_index, obj_name, PDH_MAX_COUNTER_NAME))
		return;

	if (!lookup_perfname(counter_index1, c1_name, PDH_MAX_COUNTER_NAME))
		return;

	if (!lookup_perfname(counter_index2, c2_name, PDH_MAX_COUNTER_NAME))
		return;

	// Get number of counters/instances that can be queried
	st = PdhEnumObjectItemsW(NULL, NULL, obj_name,
			NULL, &obj_list_size,
			NULL, &inst_list_size,
			PERF_DETAIL_WIZARD, 0);

	if ((st != PDH_MORE_DATA) && (st != ERROR_SUCCESS))
	{
		// Either no data at all or other error
		win32_warning(PDHDLL, st,
				"Failed to get pdh-counter count for object '%ls'", obj_name);
		return;
	}

	// Do nothing if there's no counters
	if (inst_list_size == 0)
		return;

	// Allocate buffers to hold object and instance names
	obj_list = (wchar_t *)g_malloc(sizeof(wchar_t) * obj_list_size);
	inst_list = (wchar_t *)g_malloc(sizeof(wchar_t) * inst_list_size);

	//gkrellm_debug(DEBUG_SYSDEP, "Max instance list size: %lu\n", inst_list_size);
	// Get actual information about counters
	st = PdhEnumObjectItemsW(NULL, NULL, obj_name,
			obj_list, &obj_list_size,
			inst_list, &inst_list_size,
			PERF_DETAIL_WIZARD, 0);
	if (st != ERROR_SUCCESS)
		{
		// Either no data at all or other error
		win32_warning(PDHDLL, st,
				"Failed to enumerate pdh-counters for object '%ls'", obj_name);
		}
	else
		{
		/*gkrellm_debug(DEBUG_SYSDEP, "Returned instance list size: %lu\n",
				inst_list_size);*/
		for (inst = inst_list; *inst != 0; inst += wcslen(inst) + 1)
			{
			//gkrellm_debug(DEBUG_SYSDEP, "counter instance '%ls'\n", inst);

			// Ignore total counter, gkrellm provides that functionality
			if (wcsnicmp(L"_Total", inst, 6) == 0)
				continue;

			// "\Disks(DiskOne)\ReadBytes"
			_snwprintf(counter_path, ARR_SZ(counter_path), L"\\%ls(%ls)\\%ls",
					obj_name, inst, c1_name);
			if (!add_counter(counter_path, &c1))
				continue;

			// "\Disks(DiskOne)\WriteBytes"
			_snwprintf(counter_path, ARR_SZ(counter_path), L"\\%ls(%ls)\\%ls",
					obj_name, inst, c2_name);
			if (!add_counter(counter_path, &c2))
				continue;

			if (c1 && c2)
				cb(inst, &c1, &c2);
			}
		}
	g_free((gpointer)obj_list);
	g_free((gpointer)inst_list);
	}

static
gchar *clean_dev_name(const wchar_t *name)
{
	gchar *clean_name;
	gchar *p;

	clean_name = g_utf16_to_utf8(name, -1, NULL, NULL, NULL);
	//FIXME: handle clean_name being NULL

	p = clean_name;
	while (*p)
		{
		p = g_utf8_next_char(p);
		if (*p == ' ' || *p == '\t')
			*p = '_';
		}

	// limit length of device name, gkrellm can't handle longer names :(
	if (strlen(clean_name) > MAX_DEV_NAME)
		clean_name[MAX_DEV_NAME] = '\0';

	return clean_name;
}


// ----------------------------------------------------------------------------

void gkrellm_sys_main_init(void)
	{
	PDH_STATUS st;
	WSADATA wsdata;
	int err;

	// Remove current working directory from DLL search path
	SetDllDirectoryW(L"");

	gkrellm_debug(DEBUG_SYSDEP, "Starting Winsock\n");
	err = WSAStartup(MAKEWORD(1,1), &wsdata);
	if (err != 0)
		g_warning("Starting Winsock failed with error code %d\n", err);

	gkrellm_debug(DEBUG_SYSDEP, "Opening PDH query\n");
	st = PdhOpenQueryW(NULL, 0, &pdhQueryHandle);
	if ((st != ERROR_SUCCESS) || (pdhQueryHandle == NULL))
		{
		win32_warning(PDHDLL, st, "Opening PDH query failed");
		pdhQueryHandle = 0;
		}

	// we don't have local mail on Windows (yet?)
	gkrellm_mail_local_unsupported();

	// initialize call back structure for plugins
	win32_init_callbacks();
	}


void gkrellm_sys_main_cleanup(void)
	{
	int i;
	PDH_STATUS st;

	gkrellm_debug(DEBUG_SYSDEP, "Waiting for mail checking thread to end.\n");
	i = 0;
	while (gkrellm_mail_get_active_thread() != NULL && (i++ < 120))
		{
		// wait here till it finishes
		// in case we are trying to get mail info
		g_usleep(G_USEC_PER_SEC); // 1 second wait
		}

	// Close PDH query-handle
	gkrellm_debug(DEBUG_SYSDEP, "Closing PDH query\n");
	st = PdhCloseQuery(pdhQueryHandle);
	if (st != ERROR_SUCCESS)
		win32_warning(PDHDLL, st, "Closing PDH query handle failed");
	pdhQueryHandle = NULL;

	gkrellm_sys_cpu_cleanup();
	gkrellm_sys_disk_cleanup();
	gkrellm_sys_net_cleanup();
	gkrellm_sys_inet_cleanup();
	gkrellm_sys_proc_cleanup();
	gkrellm_sys_mem_cleanup();

	gkrellm_debug(DEBUG_SYSDEP, "Closing Winsock\n");
	i = WSACleanup();
	if (i != 0)
		g_warning("Stopping Winsock failed, error %d\n", i);
	}

// only need to collect pdhQueryHandle data once for all those monitors that use it
static
void win32_read_proc_stat(void)
	{
	static gint s_data_read_tick = -1;
	PDH_STATUS st;

	if (pdhQueryHandle == NULL)
		return;

	if (s_data_read_tick == gkrellm_get_timer_ticks())	// One read per tick
		return;
	s_data_read_tick = gkrellm_get_timer_ticks();

	gkrellm_debug(DEBUG_SYSDEP, "Collecting PDH query data\n");
	st = PdhCollectQueryData(pdhQueryHandle);
	if (st != ERROR_SUCCESS)
		win32_warning(PDHDLL, st, "Failed to collect PDH query data");
	}


/* ===================================================================== */
/* Sensor interface */
/* ===================================================================== */

#define MBM_INTERFACE 1 /* MotherBoardMonitor 5 */
#define SF_INTERFACE  2 /* SpeedFan */
#define CT_INTERFACE  3 /* CoreTemp */
#define GPUZ_INTERFACE 4 /* GPU-Z */

/* TODO: Build as separate object */
#include "win32-sensors.c"

gboolean
gkrellm_sys_sensors_get_voltage(gchar *device_name, gint id,
		gint iodev, gint inter, gfloat *volt)
{
	if (inter == MBM_INTERFACE)
		return gkrellm_sys_sensors_mbm_get_value(id, SENSOR_VOLTAGE, volt);
	if (inter == SF_INTERFACE)
		return gkrellm_sys_sensors_sf_get_value(id, SENSOR_VOLTAGE, volt);
	if (inter == GPUZ_INTERFACE)
		return gkrellm_sys_sensors_gpuz_get_value((guint)id, volt);
	return FALSE;
}

gboolean
gkrellm_sys_sensors_get_fan(gchar *device_name, gint id,
		gint iodev, gint inter, gfloat *fan)
	{
	if (inter == MBM_INTERFACE)
		return gkrellm_sys_sensors_mbm_get_value(id, SENSOR_FAN, fan);
	if (inter == SF_INTERFACE)
		return gkrellm_sys_sensors_sf_get_value(id, SENSOR_FAN, fan);
	if (inter == GPUZ_INTERFACE)
		return gkrellm_sys_sensors_gpuz_get_value((guint)id, fan);
	return FALSE;
	}

gboolean
gkrellm_sys_sensors_get_temperature(gchar *device_name, gint id,
		gint iodev, gint inter, gfloat *temp)
	{
	if (inter == MBM_INTERFACE)
		return gkrellm_sys_sensors_mbm_get_value(id, SENSOR_TEMPERATURE, temp);
	if (inter == SF_INTERFACE)
		return gkrellm_sys_sensors_sf_get_value(id, SENSOR_TEMPERATURE, temp);
	if (inter == CT_INTERFACE)
		return gkrellm_sys_sensors_ct_get_temp((guint)id, (guint)iodev, temp);
	if (inter == GPUZ_INTERFACE)
		return gkrellm_sys_sensors_gpuz_get_value((guint)id, temp);
	return FALSE;
	}

gboolean
gkrellm_sys_sensors_init(void)
	{
	gboolean init_ok = FALSE;

	gkrellm_debug(DEBUG_SYSDEP, "INIT sensors\n");
	init_ok |= gkrellm_sys_sensors_sf_init();
	init_ok |= gkrellm_sys_sensors_ct_init();
	init_ok |= gkrellm_sys_sensors_gpuz_init();
	init_ok |= gkrellm_sys_sensors_mbm_init();

	gkrellm_debug(DEBUG_SYSDEP, "INIT sensors finished, result is %d\n", init_ok);
	// returns true if at least one sensors interface has been found
	return init_ok;
	}


/* ===================================================================== */
/* CPU monitor interface */
/* ===================================================================== */

typedef struct _GK_CPU
	{
	PDH_HCOUNTER total_pdh_counter;
	PDH_HCOUNTER sys_pdh_counter;
	gulong user;
	gulong sys;
	gulong idle;
	} GK_CPU;

static GPtrArray *s_cpu_ptr_array = NULL;

void
gkrellm_sys_cpu_read_data(void)
	{
	PDH_FMT_COUNTERVALUE tot;
	PDH_FMT_COUNTERVALUE sys;
	gint i;
	GK_CPU *cpu;

	if (pdhQueryHandle == NULL)
		return;
	win32_read_proc_stat(); // eventually fetch new pdh data
	gkrellm_debug(DEBUG_SYSDEP, "Reading cpu data for %d CPUs\n",
			s_cpu_ptr_array->len);

	for (i = 0; i < s_cpu_ptr_array->len; i++)
		{
		cpu = (GK_CPU *)g_ptr_array_index(s_cpu_ptr_array, i);

		if (!get_formatted_counter_value(cpu->total_pdh_counter, "cpu total time", PDH_FMT_LONG, &tot))
			return;

		if (!get_formatted_counter_value(cpu->sys_pdh_counter, "cpu system time", PDH_FMT_LONG, &sys))
			return;

		// user time = (total time - system time)
		cpu->user += (tot.longValue - sys.longValue);
		cpu->sys  += sys.longValue;
		// idle time = 100% - total time - system time
		cpu->idle += (100 - tot.longValue - sys.longValue);

		gkrellm_cpu_assign_data(i, cpu->user, 0 /*nice*/, cpu->sys, cpu->idle);
		}
	}

static void
gkrellm_sys_cpu_add_cb(wchar_t *name, PDH_HCOUNTER *total, PDH_HCOUNTER *sys)
{
	GK_CPU *cpu;

	gkrellm_debug(DEBUG_SYSDEP, "Adding CPU '%ls'\n", name);

	cpu = g_new0(GK_CPU, 1);
	cpu->total_pdh_counter = *total;
	cpu->sys_pdh_counter = *sys;
	g_ptr_array_add(s_cpu_ptr_array, cpu);
}

gboolean
gkrellm_sys_cpu_init(void)
	{

	gkrellm_debug(DEBUG_SYSDEP, "INIT CPU Monitoring\n");

	s_cpu_ptr_array = g_ptr_array_new();

	gkrellm_cpu_nice_time_unsupported();

	add_counter_list(
			  238 // object_index
			, 6 // counter_index1, cpu time
			, 144 // counter_index2, system time
			, gkrellm_sys_cpu_add_cb);

	gkrellm_debug(DEBUG_SYSDEP, "Found %i CPUs for monitoring.\n",
			s_cpu_ptr_array->len);

	gkrellm_cpu_set_number_of_cpus(s_cpu_ptr_array->len);

	return (s_cpu_ptr_array->len == 0 ? FALSE : TRUE);
	}

static void
gkrellm_sys_cpu_cleanup(void)
	{
	guint i;
	if (!s_cpu_ptr_array)
		return;
	gkrellm_debug(DEBUG_SYSDEP, "Freeing counters for %u cpu(s)\n",
		s_cpu_ptr_array->len);
	for (i = 0; i < s_cpu_ptr_array->len; i++)
		g_free(g_ptr_array_index(s_cpu_ptr_array, i));
	g_ptr_array_free(s_cpu_ptr_array, TRUE);
	s_cpu_ptr_array = NULL;
	}

/* ===================================================================== */
/* Net monitor interface */
/* ===================================================================== */

typedef struct _GK_NET
	{
	PDH_HCOUNTER recv_pdh_counter;
	PDH_HCOUNTER send_pdh_counter;
	gchar *name;
	gulong recv;
	gulong send;
	}
	GK_NET;

static GPtrArray *s_net_ptr_array = NULL;

static GK_NET *
gk_net_new()
{
	return g_new0(GK_NET, 1);
}

static void
gk_net_free(GK_NET *net)
{
	g_free(net->name);
	g_free(net);
}

void
gkrellm_sys_net_read_data(void)
{
	gint i;
	GK_NET *net;
	PDH_FMT_COUNTERVALUE recvVal;
	PDH_FMT_COUNTERVALUE sendVal;

	if (pdhQueryHandle == NULL)
		return;
	win32_read_proc_stat();
	gkrellm_debug(DEBUG_SYSDEP, "Reading net data for %d network devices\n",
			s_net_ptr_array->len);

	for (i = 0; i < s_net_ptr_array->len; i++)
		{
		net = (GK_NET *)g_ptr_array_index(s_net_ptr_array, i);

		if (!get_formatted_counter_value(net->recv_pdh_counter, "net recv", PDH_FMT_LONG, &recvVal))
			continue;

		if (!get_formatted_counter_value(net->send_pdh_counter, "net send", PDH_FMT_LONG, &sendVal))
			continue;

		net->recv += recvVal.longValue / _GK.update_HZ;
		net->send += sendVal.longValue / _GK.update_HZ;

		gkrellm_net_assign_data(net->name, net->recv, net->send);
		}
	}

void
gkrellm_sys_net_check_routes(void)
{
	//TODO: Implement if possible, detects enable/disable of network-interfaces
}

gboolean
gkrellm_sys_net_isdn_online(void)
{
	return FALSE; //TODO: Implement if possible
}

static void
gkrellm_sys_net_add_cb(wchar_t *name, PDH_HCOUNTER *recv, PDH_HCOUNTER *send)
{
	GK_NET *net;
	guint i;
	gchar unique = '0';
	GK_NET *other_net;

	net = gk_net_new();
	net->name = clean_dev_name(name);
	for (i = 0; i < s_net_ptr_array->len; i++)
	{
		other_net = (GK_NET *)(g_ptr_array_index(s_net_ptr_array, i));
		while (strncmp(net->name, other_net->name, MAX_DEV_NAME) == 0)
		{
			gkrellm_debug(DEBUG_SYSDEP,
					"net names '%s' and '%s' conflict, renaming new one\n",
					net->name, other_net->name);
			net->name[strlen(net->name) - 1] = unique++;
			break;
		}
	}
	net->recv_pdh_counter = *recv;
	net->send_pdh_counter = *send;

	gkrellm_debug(DEBUG_SYSDEP, "Adding network interface %s\n", net->name);

	// TODO: determine network type
	gkrellm_net_add_timer_type_ppp(net->name);

	g_ptr_array_add(s_net_ptr_array, net);
}

gboolean
gkrellm_sys_net_init(void)
	{
	gkrellm_debug(DEBUG_SYSDEP, "INIT network monitoring\n");

	s_net_ptr_array = g_ptr_array_new();

	add_counter_list(
			  510 // object_index
			, 264 // counter_index1
			, 506 // counter_index2
			, gkrellm_sys_net_add_cb);

	gkrellm_debug(DEBUG_SYSDEP, "Found %i network adapters for monitoring.\n",
		s_net_ptr_array->len);

	return (s_net_ptr_array->len == 0 ? FALSE : TRUE);
	}

static void
gkrellm_sys_net_cleanup(void)
	{
	guint i;
	if (!s_net_ptr_array)
		return;
	gkrellm_debug(DEBUG_SYSDEP, "Freeing counters for %u network adapter(s)\n",
		s_net_ptr_array->len);
	for (i = 0; i < s_net_ptr_array->len; i++)
		gk_net_free((GK_NET *)g_ptr_array_index(s_net_ptr_array, i));
	g_ptr_array_free(s_net_ptr_array, TRUE);
	}

/* ===================================================================== */
/* Disk monitor interface */
/* ===================================================================== */

typedef struct _GK_DISK
	{
	PDH_HCOUNTER read_pdh_counter;
	PDH_HCOUNTER write_pdh_counter;
	gchar *name;
	gulong read;
	gulong write;
	} GK_DISK;

static GPtrArray *s_disk_ptr_array = NULL;

static GK_DISK *
gk_disk_new()
{
	return g_new0(GK_DISK, 1);
}

static void
gk_disk_free(GK_DISK *disk)
{
	g_free(disk->name);
	g_free(disk);
}

gchar *gkrellm_sys_disk_name_from_device(gint device_number, gint unit_number,
			gint *order)
{
	static gchar name[37];
	GK_DISK *disk;

	disk = g_ptr_array_index(s_disk_ptr_array, device_number);
	snprintf(name, sizeof(name), "Disk%s", disk->name);
	*order = device_number;

	return name;
}

gint gkrellm_sys_disk_order_from_name(const gchar *name)
{
	return 0; // Disk by name not implemented in Windows
}

void gkrellm_sys_disk_read_data(void)
	{
	guint i;
	GK_DISK *disk;
	PDH_FMT_COUNTERVALUE readVal;
	PDH_FMT_COUNTERVALUE writeVal;

	if (pdhQueryHandle == NULL)
		return;
	win32_read_proc_stat();
	gkrellm_debug(DEBUG_SYSDEP, "Reading disk data\n");

	for (i = 0; i < s_disk_ptr_array->len; i++)
		{
		disk = g_ptr_array_index(s_disk_ptr_array, i);

		if (!get_formatted_counter_value(disk->read_pdh_counter, "disk read", PDH_FMT_DOUBLE, &readVal))
			continue;

		if (!get_formatted_counter_value(disk->write_pdh_counter, "disk write", PDH_FMT_DOUBLE, &writeVal))
			continue;

		disk->read  += readVal.doubleValue / _GK.update_HZ;
		disk->write += writeVal.doubleValue / _GK.update_HZ;

		gkrellm_disk_assign_data_by_device(i, 0, disk->read, disk->write, FALSE);
		}
	}

static
void gkrellm_sys_disk_add_cb(wchar_t *name, PDH_HCOUNTER *read, PDH_HCOUNTER *write)
{
	GK_DISK *disk;
	GK_DISK *other_disk;
	guint i;
	gchar unique = '0';

	disk = gk_disk_new();
	disk->name = clean_dev_name(name);
	for (i = 0; i < s_disk_ptr_array->len; i++)
	{
		other_disk = (GK_DISK *)(g_ptr_array_index(s_disk_ptr_array, i));
		while (strncmp(disk->name, other_disk->name, MAX_DEV_NAME) == 0)
		{
			gkrellm_debug(DEBUG_SYSDEP,
					"disk names '%s' and '%s' conflict, renaming new one\n",
					disk->name, other_disk->name);
			disk->name[strlen(disk->name) - 1] = unique++;
			break;
		}
	}
	disk->read_pdh_counter = *read;
	disk->write_pdh_counter = *write;

	gkrellm_debug(DEBUG_SYSDEP, "Adding disk %s\n", disk->name);

	g_ptr_array_add(s_disk_ptr_array, disk);
}

gboolean
gkrellm_sys_disk_init(void)
	{
	gkrellm_debug(DEBUG_SYSDEP, "INIT disk monitoring\n");

	s_disk_ptr_array = g_ptr_array_new();

	add_counter_list(
			  234 // object_index
			, 220 // counter_index1
			, 222 // counter_index2
			, gkrellm_sys_disk_add_cb);


	gkrellm_debug(DEBUG_SYSDEP, "Found %i disk(s) for monitoring.\n",
		s_disk_ptr_array->len);

	return (s_disk_ptr_array->len == 0 ? FALSE : TRUE);
	}

static void
gkrellm_sys_disk_cleanup(void)
	{
	guint i;
	if (!s_disk_ptr_array)
		return;
	gkrellm_debug(DEBUG_SYSDEP, "Freeing counters for %u disk(s)\n",
		s_disk_ptr_array->len);
	for (i = 0; i < s_disk_ptr_array->len; i++)
		gk_disk_free(g_ptr_array_index(s_disk_ptr_array, i));
	g_ptr_array_free(s_disk_ptr_array, TRUE);
	}

/* ===================================================================== */
/* Proc monitor interface */
/* ===================================================================== */

// Counters for proc monitor interface
static PDH_HCOUNTER processCounter = NULL;
static PDH_HCOUNTER waitQueueCounter = NULL;
// Library handle for secur32.dll, lib is loaded at runtime
static HINSTANCE hSecur32 = NULL;
// Function pointers to various functions from secur32.dll
static pfLsaEnumerateLogonSessions pfLELS = NULL;
static pfLsaFreeReturnBuffer pfLFRB = NULL;
static pfLsaGetLogonSessionData pfLGLSD = NULL;

#if 0
// We need to subtract 1 on Win2k, the "idle process" seems to be counted
// as waiting on win2k (not on winxp though)
// TODO: check on vista, win2k3, win2k8 etc.
static long proc_load_correction_val = 0;
#endif

void
gkrellm_sys_proc_read_data(void)
	{
	static gulong last_num_processes = 0;
	static gfloat fload = 0;

	PDH_FMT_COUNTERVALUE value;
	LONG num_processes;
	LONG num_forks = 0;
	LONG num_waiting;
	gfloat a;

	if (pdhQueryHandle == NULL)
		return;
	win32_read_proc_stat();
	gkrellm_debug(DEBUG_SYSDEP, "Reading proc data\n");

	if (!get_formatted_counter_value(processCounter, "process count",
			PDH_FMT_LONG, &value))
		return;
	num_processes = value.longValue;
	if ((last_num_processes) > 0 && (last_num_processes < num_processes))
		num_forks = num_processes - last_num_processes;
	last_num_processes = num_processes;

	if (!get_formatted_counter_value(waitQueueCounter, "wait queue size",
			PDH_FMT_LONG, &value))
		return;
#if 0
	num_waiting = (value.longValue + proc_load_correction_val);
#else
	num_waiting = value.longValue;
#endif

	//fload - is the system load average, an exponential moving average over a
	//period of a minute of n_running. It measures how heavily a system is
	//loaded with processes or threads competing for cpu time slices.
	//
	//All the unix OSs have a system call for getting the load average. But if
	//you don't and can get a n_running number, you can calculate fload. An
	//exponential moving average (ema) is done like:
	//
	//    a = 2 / (period + 1)
	//    ema = ema + a * (new_value - ema)
	//
	// See also
	// http://en.wikipedia.org/wiki/Load_(computing)
	a = 2.0 / ((_GK.update_HZ * 60.) + 1.);
	fload += a * (num_waiting - fload);
	if (fload < 0)
		fload = 0;

	gkrellm_debug(DEBUG_SYSDEP, "num_forks %ld; num_waiting %ld; a %f; fload %f\n",
			num_forks, num_waiting, a, fload);

	gkrellm_proc_assign_data(num_processes, 0 /*n_running*/,
			num_forks /*n_forks*/, fload);
	}

void
gkrellm_sys_proc_read_users(void)
	{
	gint i;
	// Number of interactive users
	gint n_users = 0;

#if _WIN32_WINNT >= 0x501 // Windows XP or newer
	BOOL ret;
	WTS_SESSION_INFOW *pSessionList = NULL;
	DWORD sessionListCount = 0;
	WTS_SESSION_INFOW *pSession = NULL;

	gkrellm_debug(DEBUG_SYSDEP, "Enumerating terminal sessions...\n");
	// Returns list of terminal sessions in pSessionInfo[]
	ret = WTSEnumerateSessionsW(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessionList,
			&sessionListCount);
	if (!ret)
		{
		win32_warning(WTSAPI32,
				GetLastError(),
				"Enumerating terminal sessions failed");
		}
	else if (pSessionList != NULL)
		{
		gkrellm_debug(DEBUG_SYSDEP, "Found %d terminal sessions\n", sessionListCount);
		for (i = 0; i < sessionListCount; i++)
			{
			pSession = &pSessionList[i];
			gkrellm_debug(DEBUG_SYSDEP, "Session %d (%ls) has state %d\n",
			    pSession->SessionId, pSession->pWinStationName, pSession->State);
			if (pSession->State == WTSActive)
				n_users++;
			}
		WTSFreeMemory(pSessionList);
		}
#else // TODO: Remove this code-branch if nobody wants win2k-support anymore 
	// Return value for Lsa functions
	NTSTATUS ntstatus;
	// Arguments for LsaEnumerateLogonSessions()
	ULONG numSessions = 0;
	PLUID pSessions = NULL;
	// Argument for LsaGetLogonSessionData()
	SECURITY_LOGON_SESSION_DATA *pSessionData;
	wchar_t acc_name[256];
	wchar_t acc_dom[256];
	DWORD dwSize;
	SID_NAME_USE sid_type;

	/* Silently fail if secur32.dll is missing functions that we use */
	if ((pfLELS == NULL) || (pfLFRB == NULL) || (pfLGLSD == NULL))
		return;

	gkrellm_debug(DEBUG_SYSDEP, "Getting number of logged in users\n");

	ntstatus = pfLELS(&numSessions, &pSessions);
	if (NT_SUCCESS(ntstatus))
		{
		gkrellm_debug(DEBUG_SYSDEP, "Found %lu user-sessions\n", numSessions);
		for (i = 0; i < (int)numSessions; i++)
			{
			//gkrellm_debug(DEBUG_SYSDEP, "Fetching session-data for session %d\n", i);
			pSessionData = NULL;

			ntstatus = pfLGLSD(&pSessions[i], &pSessionData);
			if (NT_SUCCESS(ntstatus) && (pSessionData != NULL))
				{
				if ((SECURITY_LOGON_TYPE)pSessionData->LogonType == Interactive
					&& (pSessionData->UserName.Buffer != NULL))
					{
					gkrellm_debug(DEBUG_SYSDEP, "Interactive User %d; '%ls'\n",
							i, pSessionData->UserName.Buffer);

					dwSize = 256;
					if (LookupAccountSidW(NULL, pSessionData->Sid,
							acc_name, &dwSize,
							acc_dom, &dwSize,
							&sid_type))
						{
						if (sid_type == 1)
							{
							n_users++;
							}
						else
							{
							gkrellm_debug(DEBUG_SYSDEP,
									"SID type %d is not a normal account\n",
									(int)sid_type);
							}
						}
					else
						{
							win32_warning(NTDLL,
									GetLastError(),
									"Looking up user account id failed");
						}
					}
				}
			else
				{
				win32_warning(NTDLL, ntstatus,
					"Could not get session-data for session %d", i);
				}

			// Free session-data provided by OS, even if function returned
			// an error before
			pfLFRB(pSessionData);
			}
		}
	else
		{
		win32_warning(NTDLL, ntstatus, "Could not enumerate user-sessions\n");
		}

	// Free LUID list provided by OS, even if function returned an error before
	pfLFRB(pSessions);
#endif

	gkrellm_proc_assign_users(n_users);
	}

gboolean
gkrellm_sys_proc_init(void)
	{
	wchar_t system_name[PDH_MAX_COUNTER_NAME];
	wchar_t counter_name[PDH_MAX_COUNTER_NAME];
	wchar_t counter_path[128+128+3];
#if 0
	OSVERSIONINFOEXW vi;
#endif

	gkrellm_debug(DEBUG_SYSDEP, "INIT process monitoring\n");

	if (pdhQueryHandle == NULL)
		return FALSE;

	// Fetch prefix for both counter paths ("System" index is 2)
	if (!lookup_perfname(2, system_name, ARR_SZ(system_name)))
		return FALSE;

	// Add counter for number of processes (index is 248)
	if (!lookup_perfname(248, counter_name, 128))
		return FALSE;
	_snwprintf(counter_path, ARR_SZ(counter_path), L"\\%ls\\%ls",
			system_name, counter_name);
	if (!add_counter(counter_path, &processCounter))
		return FALSE;

	// --- Add counter for waiting queue size (index is 44)
	if (!lookup_perfname(44, counter_name, 128))
		return FALSE;
	_snwprintf(counter_path, ARR_SZ(counter_path), L"\\%ls\\%ls",
			system_name, counter_name);
	if (!add_counter(counter_path, &waitQueueCounter))
		return FALSE;

	// Dynamically load secur32.dll and lookup functions.
	// Needed to determine number of logged in users
	hSecur32 = LoadLibraryW(L"secur32.dll");
	if (hSecur32 != NULL)
		{
		gkrellm_debug(DEBUG_SYSDEP, "Loaded secur32.dll\n");

		pfLELS = (pfLsaEnumerateLogonSessions)GetProcAddress(hSecur32,
			"LsaEnumerateLogonSessions");
		if (pfLELS == NULL)
			{
			g_warning("Could not get address for " \
			"LsaEnumerateLogonSessions() in secur32.dll\n");
			}

		pfLFRB = (pfLsaFreeReturnBuffer)GetProcAddress(hSecur32,
			"LsaFreeReturnBuffer");
		if (pfLFRB == NULL)
			{
			g_warning("Could not get address for " \
				"LsaFreeReturnBuffer() in secur32.dll\n");
			}

		pfLGLSD = (pfLsaGetLogonSessionData)GetProcAddress(hSecur32,
			"LsaGetLogonSessionData");
		if (pfLGLSD == NULL)
			{
			g_warning("Could not get address for " \
				"LsaGetLogonSessionData() in secur32.dll\n");
			}
		}
	else
		{
		win32_warning(NULL, GetLastError(), "Could not load secur32.dll\n");
		}

	// Determine OS for proper load-average computation
	// (wait-queue value on win2k is off by one)
#if 0
	memset(&vi, 0, sizeof(OSVERSIONINFOEXW));
	vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW);
	if (GetVersionExW((OSVERSIONINFOW *)(&vi)))
		{
		gkrellm_debug(DEBUG_SYSDEP, "major %ld; minor %ld\n",
				vi.dwMajorVersion, vi.dwMinorVersion);
		if ((vi.dwMajorVersion == 5) && (vi.dwMinorVersion == 0))
			proc_load_correction_val = -1;
		}
#endif

	return TRUE;
	}

static void
gkrellm_sys_proc_cleanup(void)
	{
	gkrellm_debug(DEBUG_SYSDEP, "Cleanup process monitoring\n");
	// Unload secur32.dll and invalidate function pointers
	pfLELS = NULL;
	pfLFRB = NULL;
	pfLGLSD = NULL;
	if (hSecur32 != NULL)
		FreeLibrary(hSecur32);
	hSecur32 = NULL;
	}


/* ===================================================================== */
/* Memory/Swap monitor interface */
/* ===================================================================== */

typedef struct _PERFORMANCE_INFORMATION {
	DWORD cb;
	SIZE_T CommitTotal;
	SIZE_T CommitLimit;
	SIZE_T CommitPeak;
	SIZE_T PhysicalTotal;
	SIZE_T PhysicalAvailable;
	SIZE_T SystemCache;
	SIZE_T KernelTotal;
	SIZE_T KernelPaged;
	SIZE_T KernelNonpaged;
	SIZE_T PageSize;
	DWORD HandleCount;
	DWORD ProcessCount;
	DWORD ThreadCount;
} PERFORMANCE_INFORMATION;

typedef BOOL (WINAPI *pfGetPerformanceInfo)(PERFORMANCE_INFORMATION *, DWORD);

static HINSTANCE hPsapi = NULL;
static pfGetPerformanceInfo pGPI = NULL;
static DWORD page_size = 1;
static HINSTANCE hNtdll = NULL;
static pfZwQuerySystemInformation pZwQSI = NULL;

void
gkrellm_sys_mem_read_data(void)
	{
	gkrellm_debug(DEBUG_SYSDEP, "Checking memory utilization\n");

	guint64 total = 0;
	guint64 used  = 0;
	guint64 avail = 0;
	guint64 cache = 0;

	if (pGPI)
		{
		PERFORMANCE_INFORMATION pi;

		// See http://msdn.microsoft.com/en-us/library/ms684824(VS.85).aspx
		// for the confusing description of PERFORMANCE_INFORMATION
		//
		// total   = PhysicalTotal
		// used    = PhysicalTotal - PhysicalAvailable
		// NOTE: (avail value is not exactly correct but we can't know better)
		// avail   = PhysicalAvailable - SystemCache
		// cache   = SystemCache

		if (pGPI(&pi, sizeof(PERFORMANCE_INFORMATION)))
			{
			total = pi.PhysicalTotal * pi.PageSize;
			used  = (pi.PhysicalTotal - pi.PhysicalAvailable) * pi.PageSize;
			avail = (pi.PhysicalAvailable - pi.SystemCache) * pi.PageSize;
			cache = pi.SystemCache * pi.PageSize;
			}
		}
	else
		{
		MEMORYSTATUSEX ms;
		ms.dwLength = sizeof(ms);

		if (GlobalMemoryStatusEx(&ms))
			{
			total = ms.ullTotalPhys;
			used  = ms.ullTotalPhys - ms.ullAvailPhys;
			avail = ms.ullAvailPhys;
			}
		}

	gkrellm_mem_assign_data(total, used, avail, 0, 0, cache);
	}

void
gkrellm_sys_swap_read_data(void)
	{
	guint64 swapTotal = 0;
	guint64 swapUsed  = 0;
	NTSTATUS ntstatus;
	ULONG  szBuf = 3*sizeof(SYSTEM_PAGEFILE_INFORMATION);
	SYSTEM_PAGEFILE_INFORMATION *pInfo;
	LPVOID pBuf = NULL;

	if (pZwQSI == NULL)
		return;

	gkrellm_debug(DEBUG_SYSDEP, "Checking swap utilization\n");

	// it is difficult to determine beforehand which size of the
	// buffer will be enough to retrieve all information, so we
	// start with a minimal buffer and increase its size until we get
	// the information successfully
	do
	{
		pBuf = g_malloc(szBuf);

		ntstatus = pZwQSI(SystemPagefileInformation, pBuf, szBuf, NULL);
		if (ntstatus == STATUS_INFO_LENGTH_MISMATCH)
		{
			// Buffer was too small, double its size and try again
			g_free(pBuf);
			szBuf *= 2;
		}
		else if (!NT_SUCCESS(ntstatus))
		{
			win32_warning(NTDLL, ntstatus, "Could not determine swap usage");
			g_free(pBuf);
			// Some other error occurred, give up
			return;
		}
	}
	while (ntstatus == STATUS_INFO_LENGTH_MISMATCH);

	if (pBuf != NULL)
		{
		// iterate over information for all pagefiles
		pInfo = (SYSTEM_PAGEFILE_INFORMATION *)pBuf;
		for (;;)
			{
			swapTotal += pInfo->CurrentSize * page_size;
			swapUsed  += pInfo->TotalUsed * page_size;
			if (pInfo->NextEntryOffset == 0)
				break; // end of list
			// get pointer to next struct
			pInfo = (SYSTEM_PAGEFILE_INFORMATION *)((BYTE *)pInfo +
				pInfo->NextEntryOffset);
			}
		g_free(pBuf);

		// TODO: calculate swapin/swapout values
		gkrellm_swap_assign_data(swapTotal, swapUsed, 0, 0);
		}
	}

gboolean
gkrellm_sys_mem_init(void)
	{
	SYSTEM_INFO si;

	gkrellm_debug(DEBUG_SYSDEP, "INIT memory monitoring\n");

	GetSystemInfo(&si);
	page_size = si.dwPageSize;

	hPsapi = LoadLibraryW(L"psapi.dll");
	if (hPsapi)
		{
		gkrellm_debug(DEBUG_SYSDEP, "Loaded psapi.dll\n");
		pGPI = (pfGetPerformanceInfo)GetProcAddress(hPsapi, "GetPerformanceInfo");
		if (pGPI == NULL)
			{
			gkrellm_debug(DEBUG_SYSDEP, "No GetPerformanceInfo() in " \
					"psapi.dll, cache-memory will stay at 0!\n");
			}
		}
	else
		{
		win32_warning(NULL, GetLastError(), "Could not load PSAPI.DLL");
		}

	hNtdll = LoadLibraryW(L"ntdll.dll");
	if (hNtdll)
		{
		gkrellm_debug(DEBUG_SYSDEP, "Loaded ntdll.dll\n");
		pZwQSI = (pfZwQuerySystemInformation)GetProcAddress(hNtdll, "ZwQuerySystemInformation");
		if (pZwQSI == NULL)
			{
			gkrellm_debug(DEBUG_SYSDEP, "No ZwQuerySystemInformation() in " \
					"ntdll.dll, pagefile-usage cannot be determined.\n");
			}
		}
	else
		{
		win32_warning(NULL, GetLastError(), "Could not load ntdll.dll");
		}

	return TRUE;
	}

static void
gkrellm_sys_mem_cleanup(void)
	{
	gkrellm_debug(DEBUG_SYSDEP, "Cleanup memory monitoring\n");
	pGPI = NULL;
	if (hPsapi != NULL)
		FreeLibrary(hPsapi);
	hPsapi = NULL;
	
	pZwQSI = NULL;
	if (hNtdll != NULL)
		FreeLibrary(hNtdll);
	hNtdll = NULL;
	}


/* ===================================================================== */
/* Battery monitor interface */
/* ===================================================================== */

void gkrellm_sys_battery_read_data(void)
{
	gboolean available, on_line, charging;
	gint percent, time_left;
	SYSTEM_POWER_STATUS power;

	gkrellm_debug(DEBUG_SYSDEP, "Checking battery state\n");

	GetSystemPowerStatus(&power);
	if (   (power.BatteryFlag & BATTERY_FLAG_NO_BATTERY) == BATTERY_FLAG_NO_BATTERY
	    || (power.BatteryFlag & BATTERY_FLAG_UNKNOWN) == BATTERY_FLAG_UNKNOWN
	   )
		{
		available = FALSE;
		}
	else
		{
		available = TRUE;
		}

	on_line   = ((power.ACLineStatus & AC_LINE_ONLINE) == AC_LINE_ONLINE) ? TRUE : FALSE;
	charging  = ((power.BatteryFlag & BATTERY_FLAG_CHARGING) == BATTERY_FLAG_CHARGING) ? TRUE : FALSE;
	time_left = power.BatteryLifeTime;
	percent   = power.BatteryLifePercent;

	gkrellm_battery_assign_data(0, available, on_line, charging, percent,
		time_left);
}

gboolean gkrellm_sys_battery_init()
	{
	gkrellm_debug(DEBUG_SYSDEP, "INIT battery monitoring\n");
	return TRUE;
	}


/* ===================================================================== */
/* FS monitor interfaces */
/* ===================================================================== */

gboolean gkrellm_sys_fs_fstab_modified(void)
	{
	/* TODO: determine changes in available volumes on win32 using
	FindFirstVolume(), FindNextVolume() etc. */
	return FALSE;
	}

static
void eject_win32_cdrom(gchar *device)
	{
	HANDLE hFile;
	BOOL err;
	char device_path[MAX_PATH];
	DWORD numBytes;

	if (!device || *device == '\0')
		return;

	// FIXME: This assumes device names like "D:"
	snprintf(device_path, MAX_PATH, "\\\\.\\%c:", device[0]);

	hFile = CreateFileA(device_path, GENERIC_READ,
			FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, NULL);
	if (hFile != 0 && hFile != INVALID_HANDLE_VALUE)
		{
		// this should be safe for non-removable drives
		err = DeviceIoControl(hFile, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0,
		                      &numBytes, NULL);
		if (!err)
			{
			err = DeviceIoControl(hFile, IOCTL_STORAGE_EJECT_MEDIA, NULL, 0,
			                      NULL, 0, &numBytes, NULL);
			}
		CloseHandle(hFile);
		}
	}

gboolean gkrellm_sys_fs_init(void)
	{
	gkrellm_debug(DEBUG_SYSDEP, "INIT filesystem monitoring\n");

	gkrellm_fs_mounting_unsupported();
	gkrellm_fs_setup_eject(NULL, NULL, eject_win32_cdrom, NULL);
	return TRUE;
	}

void gkrellm_sys_fs_get_fsusage(gpointer fs, gchar *dir)
	{
	BOOL err = 0;
	ULARGE_INTEGER availToCaller;
	ULARGE_INTEGER totalBytes;
	ULARGE_INTEGER freeBytes;
	gunichar2 *w_dir = NULL;

	if (!dir || *dir == '\0')
		return;
	gkrellm_debug(DEBUG_SYSDEP, "Checking fs usage for %s\n", dir);

	w_dir = g_utf8_to_utf16(dir, -1, NULL, NULL, NULL);
	if (!w_dir)
		return;

	err = GetDiskFreeSpaceExW(w_dir, &availToCaller, &totalBytes, &freeBytes);
	if (err != 0)
		{
		// fs, blocks, avail, free, size
		gkrellm_fs_assign_fsusage_data(fs
			, totalBytes.QuadPart / (ULONGLONG)1024
			, availToCaller.QuadPart / (ULONGLONG)1024 /* free to caller */
			, freeBytes.QuadPart / (ULONGLONG)1024 /* free */
			, 1024 /* block size */
			);
		}
	else
		{
		/* This may happen on cd/dvd drives, ignore error silently */
		}
	g_free(w_dir);
	}


void gkrellm_sys_fs_get_mounts_list(void)
	{
	wchar_t drive_list[4*26];
	wchar_t *drive;
	gchar *drive_utf8;
	DWORD ret;
	DWORD sz;
	UINT drive_type;

	gkrellm_debug(DEBUG_SYSDEP, "Getting list of mounted drives\n");

	drive_list[0] = '\0';
	sz = ARR_SZ(drive_list) - sizeof(drive_list[0]);
	ret = GetLogicalDriveStringsW(sz, drive_list);
	if (ret == 0 || ret > sz)
	{
		win32_warning(NULL, GetLastError(), "Failed enumerating mounted drives");
		return;
	}

	for (drive = drive_list; (*drive) != '\0'; drive += wcslen(drive) + 1)
		{
		drive_type = GetDriveTypeW(drive);

		if (   (drive_type == DRIVE_REMOVABLE)
			&& (!wcsncmp(drive, L"A:\\", 3) || !wcsncmp(drive, L"B:\\", 3)))
			continue;

		gkrellm_debug(DEBUG_SYSDEP, "Found mounted drive '%ls' of type %u\n",
				drive, drive_type);
		drive_utf8 = g_utf16_to_utf8(drive, -1, NULL, NULL, NULL);
		gkrellm_fs_add_to_mounts_list(drive_utf8, drive_utf8,
				(drive_type == DRIVE_REMOTE ? "smbfs" : ""));
		g_free(drive_utf8);
		}
	}

void gkrellm_sys_fs_get_fstab_list(void)
	{
	wchar_t drive_list[4*26];
	wchar_t *drive;
	gchar *drive_utf8;
	DWORD ret;
	DWORD sz;
	UINT drive_type;

	gkrellm_debug(DEBUG_SYSDEP, "Getting list of drives in fstab\n");

	drive_list[0] = '\0';
	sz = ARR_SZ(drive_list) - sizeof(drive_list[0]);
	ret = GetLogicalDriveStringsW(sz, drive_list);
	if (ret == 0 || ret > sz)
	{
		win32_warning(NULL, GetLastError(), "Failed enumerating fstab drives");
		return;
	}

	for (drive = drive_list; (*drive) != '\0'; drive += wcslen(drive) + 1)
		{
		drive_type = GetDriveTypeW(drive);

		if (   (drive_type == DRIVE_REMOVABLE)
			&& (!wcsncmp(drive, L"A:\\", 3) || !wcsncmp(drive, L"B:\\", 3)))
			continue;

		gkrellm_debug(DEBUG_SYSDEP, "Found fstab drive '%ls' of type %u\n",
				drive, drive_type);

		drive_utf8 = g_utf16_to_utf8(drive, -1, NULL, NULL, NULL);
		gkrellm_fs_add_to_fstab_list(drive_utf8, drive_utf8,
				(drive_type == DRIVE_REMOTE ? "smbfs" : ""), "");
		g_free(drive_utf8);
		}
	}


/* ===================================================================== */
/* INET monitor interfaces */
/* ===================================================================== */

// Library handle for Iphlpapi.dll, lib is loaded at runtime
static HINSTANCE hIphlpapi = NULL;
// Function pointer to GetTcp6Table() which only exists on Vista and newer
static GetTcp6TableFunc pfGetTcp6Table = NULL;

gboolean gkrellm_sys_inet_init(void)
	{
	gkrellm_debug(DEBUG_SYSDEP, "INIT inet port monitoring\n");
	hIphlpapi = LoadLibraryW(L"Iphlpapi.dll");
	if (hIphlpapi != NULL)
		{
		gkrellm_debug(DEBUG_SYSDEP, "Loaded Iphlpapi.dll\n");
		pfGetTcp6Table = (GetTcp6TableFunc) GetProcAddress(hIphlpapi,
			"GetTcp6Table");
		if (pfGetTcp6Table == NULL)
			{
			g_warning("Could not get address for " \
				"GetTcp6Table() in Iphlpapi.dll " \
				"(this is ok on windows versions older than vista)\n");
			}
		}
	else
		{
		win32_warning(NULL, GetLastError(), "Could not load Iphlpapi.dll\n");
		}
	return TRUE;
	}
static void
gkrellm_sys_inet_cleanup(void)
	{
	gkrellm_debug(DEBUG_SYSDEP, "Cleanup inet port monitoring\n");
	pfGetTcp6Table = NULL;
	if (hIphlpapi != NULL)
		FreeLibrary(hIphlpapi);
	hIphlpapi = NULL;
	}

static void win32_read_tcp_data(void)
	{
	PMIB_TCPTABLE pTcpTable = NULL;
	DWORD dwTableSize = 0;
	DWORD dwStatus;
	MIB_TCPROW *tcprow;
	ActiveTCP tcp;
	DWORD i;

	gkrellm_debug(DEBUG_SYSDEP, "Fetching list of IPv4 TCP connections\n");

	// Make an initial call to GetTcpTable to
	// get the necessary size into the dwSize variable
	dwStatus = GetTcpTable(NULL, &dwTableSize, FALSE);

	if ((dwStatus == ERROR_INSUFFICIENT_BUFFER) && (dwTableSize > 0))
		{
		pTcpTable = (MIB_TCPTABLE *)g_malloc(dwTableSize);

		// Make a second call to GetTcpTable to get
		// the actual data we require
		dwStatus = GetTcpTable(pTcpTable, &dwTableSize, FALSE);

		if (dwStatus == NO_ERROR)
			{
			for (i = 0; i < pTcpTable->dwNumEntries; i++)
				{
				tcprow = &pTcpTable->table[i];

				// Skip connections that are not fully established
				if (tcprow->dwState != MIB_TCP_STATE_ESTAB)
					continue;

				tcp.family             = AF_INET;
				tcp.local_port         = htons(tcprow->dwLocalPort);
				tcp.remote_addr.s_addr = tcprow->dwRemoteAddr;
#if defined(INET6)
				memset(&tcp.remote_addr6, 0, sizeof(struct in6_addr));
#endif
				tcp.remote_port        = htons(tcprow->dwRemotePort);
				tcp.is_udp             = FALSE;

				gkrellm_inet_log_tcp_port_data(&tcp);
				}
			}
			else
			{
				win32_warning(NULL, dwStatus,
					"Could not fetch list of IPv4 TCP connections");
			}

		g_free(pTcpTable);
		}
		else
		{
			win32_warning(NULL, dwStatus,
					"Could not fetch list of IPv4 TCP connections");
		}
	}

#if defined(INET6)
static void win32_read_tcp6_data(void)
	{
	PMIB_TCP6TABLE pTcpTable = NULL;
	DWORD dwTableSize = 0;
	DWORD dwStatus;
	MIB_TCP6ROW *tcprow;
	ActiveTCP tcp;
	DWORD i;

	if (pfGetTcp6Table == NULL)
		return; // missing GetTcp6Table() on this machine

	gkrellm_debug(DEBUG_SYSDEP, "Fetching list of IPv6 TCP connections\n");

	// Make an initial call to GetTcpTable to
	// get the necessary size into the dwSize variable
	dwStatus = pfGetTcp6Table(NULL, &dwTableSize, FALSE);

	if ((dwStatus == ERROR_INSUFFICIENT_BUFFER) && (dwTableSize > 0))
		{
		pTcpTable = (MIB_TCP6TABLE *)g_malloc(dwTableSize);

		// Make a second call to GetTcpTable to get
		// the actual data we require
		dwStatus = pfGetTcp6Table(pTcpTable, &dwTableSize, FALSE);

		if (dwStatus == NO_ERROR)
			{
			for (i = 0; i < pTcpTable->dwNumEntries; i++)
				{
				tcprow = &pTcpTable->table[i];

				// Skip connections that are not fully established
				if (tcprow->dwState != MIB_TCP_STATE_ESTAB)
					continue;

				tcp.family             = AF_INET6;
				tcp.local_port         = htons(tcprow->dwLocalPort);
				tcp.remote_addr.s_addr = 0;
				tcp.remote_addr6       = tcprow->RemoteAddr;
				tcp.remote_port        = htons(tcprow->dwRemotePort);
				tcp.is_udp             = FALSE;

				gkrellm_inet_log_tcp_port_data(&tcp);
				}
			}
			else
			{
				win32_warning(NULL, dwStatus,
					"Could not fetch list of IPv6 TCP connections");
			}

		g_free(pTcpTable);
		}
		else
		{
			win32_warning(NULL, dwStatus,
					"Could not fetch list of IPv6 TCP connections");
		}
		}
#endif

void gkrellm_sys_inet_read_tcp_data(void)
	{
	win32_read_tcp_data();
#if defined(INET6)
	win32_read_tcp6_data();
#endif
	}


/* ===================================================================== */
/* Uptime monitor interface */
/* ===================================================================== */

static PDH_HCOUNTER uptimeCounter = NULL;

time_t gkrellm_sys_uptime_read_uptime(void)
{
	PDH_FMT_COUNTERVALUE val;

	if (pdhQueryHandle == NULL)
		return (time_t)0;
	win32_read_proc_stat();
	gkrellm_debug(DEBUG_SYSDEP, "Reading system uptime\n");

	if (!get_formatted_counter_value(uptimeCounter, "uptime", PDH_FMT_LONG, &val))
		return (time_t)0;
	return (time_t)val.longValue;
	}

gboolean gkrellm_sys_uptime_init(void)
	{
	wchar_t system_name[PDH_MAX_COUNTER_NAME];
	wchar_t uptime_name[PDH_MAX_COUNTER_NAME];
	wchar_t counter_path[PDH_MAX_COUNTER_PATH];

	gkrellm_debug(DEBUG_SYSDEP, "INIT uptime monitoring\n");

	if (pdhQueryHandle == NULL)
		return FALSE;

	// Fetch prefix for counter ("System" index is 2)
	if (!lookup_perfname(2, system_name, ARR_SZ(system_name)))
		return FALSE;

	// Fetch name for uptime ("Uptime" index is 674)
	if (!lookup_perfname(674, uptime_name, ARR_SZ(uptime_name)))
		return FALSE;

	_snwprintf(counter_path, ARR_SZ(counter_path), L"\\%ls\\%ls",
			system_name, uptime_name);

	if (!add_counter(counter_path, &uptimeCounter))
		return FALSE;

	return TRUE;
	}


/* ===================================================================== */
/* System name interface */
/* ===================================================================== */


typedef void (WINAPI *PGetNativeSystemInfo)(SYSTEM_INFO *);

gchar *gkrellm_sys_get_system_name(void)
	{
	static gboolean	 have_sys_name;
	static gchar sysname[32];
	OSVERSIONINFOEXW vi;
	SYSTEM_INFO si;
	PGetNativeSystemInfo pGNSI;

	if (have_sys_name)
		return sysname;

	gkrellm_debug(DEBUG_SYSDEP, "Retrieving system name\n");

	// Default value for sysname
	g_strlcpy(sysname, "Windows Unknown", sizeof(sysname));

	// Query version info
	memset(&vi, 0, sizeof(OSVERSIONINFOEXW));
	vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW);
	if (!GetVersionExW((OSVERSIONINFOW *)(&vi)))
		return sysname;

	// We actually only support decoding NT-based version info
	if (vi.dwPlatformId != VER_PLATFORM_WIN32_NT)
		return sysname;

	// Try to call native version first, as this allows detecting
	// 64bit hosts from within a 32bit process.
	pGNSI = (PGetNativeSystemInfo)GetProcAddress(
			GetModuleHandleW(L"kernel32.dll"), "GetNativeSystemInfo");

	if (pGNSI != NULL)
		pGNSI(&si);
	else
		GetSystemInfo(&si);

	/* NT Workstation identifies some Client/Desktop Windows version */
	gboolean isWks = (vi.wProductType == VER_NT_WORKSTATION);

	if (vi.dwMajorVersion == 6)
		{
		if (vi.dwMinorVersion == 0)
			g_strlcpy(sysname, isWks ? "Windows Vista" : "Windows Server 2008", sizeof(sysname));
		else if (vi.dwMinorVersion == 1)
			g_strlcpy(sysname, isWks ? "Windows 7" : "Windows Server 2008 R2", sizeof(sysname));
		else if (vi.dwMinorVersion == 2)
			g_strlcpy(sysname, isWks ? "Windows 8" : "Windows Server 2012", sizeof(sysname));
		else if (vi.dwMinorVersion == 3)
			g_strlcpy(sysname, isWks ? "Windows 8.1" : "Windows Server 2012 R2", sizeof(sysname));
		}
	else if (vi.dwMajorVersion == 5)
		{
		// Windows 5.x aka 2000, XP, Server 2003

		if (vi.dwMinorVersion == 0)
			{
			g_strlcpy(sysname, isWks ? "Windows 2000 Professional" : "Windows 2000 Server", sizeof(sysname));
			}
		else if (vi.dwMinorVersion == 1)
			{
			if (vi.wSuiteMask & VER_SUITE_PERSONAL)
				g_strlcpy(sysname, "Windows XP Home Edition", sizeof(sysname));
			else
				g_strlcpy(sysname, "Windows XP Professional", sizeof(sysname));
			}
		else if (vi.dwMinorVersion == 2)
			{
			if (GetSystemMetrics(SM_SERVERR2))
				g_strlcpy(sysname, "Windows Server 2003 R2", sizeof(sysname));
			else if (vi.wSuiteMask == VER_SUITE_STORAGE_SERVER)
				g_strlcpy(sysname, "Windows Storage Server 2003", sizeof(sysname));
			else if (   vi.wProductType == VER_NT_WORKSTATION
					  && si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)
				g_strlcpy(sysname, "Windows XP Professional x64", sizeof(sysname));
			else
				g_strlcpy(sysname, "Windows Server 2003", sizeof(sysname));
			}
		}

	return sysname;
	}


/* ===================================================================== */
/* Misc functions */
/* ===================================================================== */

/*
* Copyright (c) 1983, 1990, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

/*
* Portions Copyright (c) 1993 by Digital Equipment Corporation.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies, and that
* the name of Digital Equipment Corporation not be used in advertising or
* publicity pertaining to distribution of the document or software without
* specific, written prior permission.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DIGITAL EQUIPMENT
* CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
* ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
* SOFTWARE.
*/

/*
* Portions Copyright (c) 1996-1999 by Internet Software Consortium.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
* ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
* CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
* ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
* SOFTWARE.
*/

/* BeOS doesn't yet have it's own inet_aton and Bind won't be ported
* until R5, so this is from a Bind 8 distribution. It's currently untested.
*/

int inet_aton(const char *cp, struct in_addr *addr) {
u_long val;
int base, n;
char c;
short parts[4];
short *pp = parts;
int digit;

c = *cp;
for (;;) {
/*
* Collect number up to ``.''.
* Values are specified as for C:
* 0x=hex, 0=octal, isdigit=decimal.
*/
if (!isdigit(c))
return (0);
val = 0; base = 10; digit = 0;
if (c == '0') {
c = *++cp;
if (c == 'x' || c == 'X')
base = 16, c = *++cp;
else {
base = 8;
digit = 1 ;
}
}
for (;;) {
if (isascii(c) && isdigit(c)) {
if (base == 8 && (c == '8' || c == '9'))
return (0);
val = (val * base) + (c - '0');
c = *++cp;
digit = 1;
} else if (base == 16 && isascii(c) && isxdigit(c)) {
val = (val << 4) |
(c + 10 - (islower(c) ? 'a' : 'A'));
c = *++cp;
digit = 1;
} else
break;
}
if (c == '.') {
/*
* Internet format:
* a.b.c.d
* a.b.c (with c treated as 16 bits)
* a.b (with b treated as 24 bits)
*/
if (pp >= parts + 3 || val > 0xff)
return (0);
*pp++ = val;
c = *++cp;
} else
break;
}
/*
* Check for trailing characters.
*/
if (c != '\0' && (!isascii(c) || !isspace(c)))
return (0);
/*
* Did we get a valid digit?
*/
if (!digit)
return (0);
/*
* Concoct the address according to
* the number of parts specified.
*/
n = pp - parts + 1;
switch (n) {
case 1: /* a -- 32 bits */
break;

case 2: /* a.b -- 8.24 bits */
if (val > 0xffffff)
return (0);
val |= parts[0] << 24;
break;

case 3: /* a.b.c -- 8.8.16 bits */
if (val > 0xffff)
return (0);
val |= (parts[0] << 24) | (parts[1] << 16);
break;

case 4: /* a.b.c.d -- 8.8.8.8 bits */
if (val > 0xff)
return (0);
val |= (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8);
break;
}
if (addr != NULL)
addr->s_addr = htonl(val);
return (1);
}
