/* GKrellM
|  Copyright (C) 1999-2021 Bill Wilson
|
|  Author:  Bill Wilson
|  Latest versions might be found at:  https://gkrellm.srcbox.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 "gkrellmd.h"
#include "gkrellmd-private.h"
#include <inttypes.h>

GList			*gkrellmd_monitor_list;

static GList	*serveflag_done_list;

static struct tm	gkrellmd_current_tm;

gint
gkrellm_get_timer_ticks(void)
	{
	return GK.timer_ticks;
	}

gboolean
gkrellmd_check_client_version(GkrellmdMonitor *mon,
				gint major, gint minor, gint rev)
	{
	GkrellmdClient	*client = mon->privat->client;

	if (   client->major_version > major
		|| (client->major_version == major && client->minor_version > minor)
	    || (   client->major_version == major && client->minor_version == minor
			&& client->rev_version >= rev
		   )
	   )
		return TRUE;
	return FALSE;
	}

void
gkrellmd_add_serveflag_done(gboolean *flag)
	{
	serveflag_done_list = g_list_append(serveflag_done_list, flag);
	}


void
gkrellmd_set_serve_name(GkrellmdMonitor *mon, const gchar *tag)
	{
	GkrellmdMonitorPrivate	*mp = mon->privat;

	mp->serve_name = tag;
	mp->serve_name_sent = FALSE;
	}

void
gkrellmd_serve_data(GkrellmdMonitor *mon, gchar *line)
	{
	GkrellmdMonitorPrivate	*mp = mon->privat;
	gchar					buf[128];

	if (!line || !*line)
		return;
	if (!mp->serve_name_sent)
		{
		if (mp->serve_name)
			{
			snprintf(buf, sizeof(buf), "<%s>\n", mp->serve_name);
			gkrellm_debug(DEBUG_SERVER, "%s", buf);
			mp->serve_gstring = g_string_append(mp->serve_gstring, buf);
			mp->serve_name_sent = TRUE;
			}
		else
			{
			g_warning("gkrellmd: %s forgot to gkrellmd_set_serve_name()\n",
					mon->name);
			return;
			}
		}
	gkrellm_debug(DEBUG_SERVER,"%s", line);
	mp->serve_gstring = g_string_append(mp->serve_gstring, line);
	}

/* ======================================================= */
typedef struct
	{
	gint	instance;
	gulong	user,
			nice,
			sys,
			idle;
	}
	CpuData;

static gchar	*n_cpus_setup;
static gboolean	nice_time_unsupported;

static GList	*cpu_list;
static GList	*instance_list;

void
gkrellm_cpu_set_number_of_cpus(gint n)
	{
	CpuData	*cpu;
	GList	*list;
	gint	i;

	n_cpus_setup = g_strdup_printf("n_cpus %d\n", n);
	for (i = 0; i < n; ++i)
		{
		cpu = g_new0(CpuData, 1);
		cpu_list = g_list_append(cpu_list, cpu);

		if (instance_list && (list = g_list_nth(instance_list, i)) != NULL)
			cpu->instance = GPOINTER_TO_INT(list->data);
		else
			cpu->instance = i;
		}
	}

void
gkrellm_cpu_add_instance(gint instance)
	{
	instance_list = g_list_append(instance_list, GINT_TO_POINTER(instance));
	}

void
gkrellm_cpu_nice_time_unsupported(void)
	{
	nice_time_unsupported = TRUE;
	}

void
gkrellm_cpu_assign_composite_data(gulong user, gulong nice,
			gulong sys, gulong idle)
	{
	return;		/* let client gkrellm compute it */
	}

void
gkrellm_cpu_assign_data(gint n, gulong user, gulong nice,
			gulong sys, gulong idle)
	{
	CpuData	*cpu = NULL;
	GList	*list;

	for (list = cpu_list; list; list = list->next)
		{
		cpu = (CpuData *) list->data;
		if (cpu->instance == n)
			break;
		}
	if (list)
		{
		cpu->user = user;
		cpu->nice = nice;
		cpu->sys = sys;
		cpu->idle = idle;
		}
	}

static void
update_cpu(GkrellmdMonitor *mon, gboolean first_update)
	{
	gkrellm_sys_cpu_read_data();
	gkrellmd_need_serve(mon);
	}

static void
serve_cpu_data(GkrellmdMonitor *mon, gboolean first_serve)
	{
	CpuData	*cpu;
	GList	*list;
	gchar	buf[128];

	gkrellmd_set_serve_name(mon, "cpu");
	for (list = cpu_list; list; list = list->next)
		{
		cpu = (CpuData *) list->data;
		snprintf(buf, sizeof(buf), "%d %lu %lu %lu %lu\n", cpu->instance,
				cpu->user, cpu->nice, cpu->sys, cpu->idle);
		gkrellmd_serve_data(mon, buf);
		}
	}

static void
serve_cpu_setup(GkrellmdMonitor *mon)
	{
	GkrellmdClient	*client = mon->privat->client;
	GList			*list;
	gchar			buf[64];

	gkrellmd_send_to_client(client, "<cpu_setup>\n");
	for (list = instance_list; list; list = list->next)
		{
		snprintf(buf, sizeof(buf), "cpu_instance %d\n",
				GPOINTER_TO_INT(list->data));
		gkrellmd_send_to_client(client, buf);
		}
	gkrellmd_send_to_client(client, n_cpus_setup);
	if (nice_time_unsupported)
		gkrellmd_send_to_client(client, "nice_time_unsupported\n");
	}

static GkrellmdMonitor cpu_monitor =
	{
	"cpu",
	update_cpu,
	serve_cpu_data,
	serve_cpu_setup,
	NULL
	};


static GkrellmdMonitor *
init_cpu_monitor(void)
	{
	if (gkrellm_sys_cpu_init())
		return &cpu_monitor;
	return NULL;
	}

/* ======================================================= */
struct
	{
	gboolean	changed;
	gint		n_processes,
				n_running,
				n_users;
	gulong		n_forks;
	gfloat		fload;
	}
	proc;

void
gkrellm_proc_assign_data(gint n_processes, gint n_running,
			gulong n_forks, gfloat load)
	{
	if (   proc.n_processes != n_processes
		|| proc.n_running != n_running
		|| proc.n_forks != n_forks
		|| proc.fload != load
	   )
		{
		proc.n_processes = n_processes;
		proc.n_running = n_running;
		proc.n_forks = n_forks;
		proc.fload = load;
		proc.changed = TRUE;
		}
	}

void
gkrellm_proc_assign_users(gint n_users)
	{
	if (proc.n_users != n_users)
		{
		proc.n_users = n_users;
		proc.changed = TRUE;
		}
	}

static void
update_proc(GkrellmdMonitor *mon, gboolean first_update)
	{
	proc.changed = FALSE;
	gkrellm_sys_proc_read_data();
	if (first_update || GK.five_second_tick)
		gkrellm_sys_proc_read_users();

	if (proc.changed)
		gkrellmd_need_serve(mon);
	}

static void
serve_proc_data(GkrellmdMonitor *mon, gboolean first_serve)
	{
	gchar	buf[128];

	gkrellmd_set_serve_name(mon, "proc");
	snprintf(buf, sizeof(buf), "%d %d %lu %.2f %d\n",
			 proc.n_processes, proc.n_running,
			 proc.n_forks, proc.fload, proc.n_users);
	gkrellmd_serve_data(mon, buf);
	}

static GkrellmdMonitor proc_monitor =
	{
	"proc",
	update_proc,
	serve_proc_data,
	NULL,
	NULL
	};


static GkrellmdMonitor *
init_proc_monitor(void)
	{
	if (!gkrellm_sys_proc_init())
		return NULL;
	serveflag_done_list = g_list_append(serveflag_done_list, &proc.changed);
	return &proc_monitor;
	}

/* ======================================================= */
typedef struct
	{
	gchar		*name;
	gchar		*subdisk_parent;
	gint		order,
				subdisk,
				changed;
	gint		device_number,
				unit_number;
	gboolean	virtual;
	guint64		rb,
				wb;
	}
	DiskData;

static GList	*disk_list;
static gint		n_disks;
static gboolean	units_are_blocks;


static DiskData *
add_disk(const gchar *name, gint order, gint device_number, gint unit_number)
	{
	DiskData	*disk;
	GList		*list;

	disk = g_new0(DiskData, 1);
	disk->name = g_strdup(name);
	disk->order = order;
	disk->subdisk = -1;
	disk->device_number = device_number;
	disk->unit_number = unit_number;
	if (order >= 0)
		{
		int disk_pos = 0;
		for (list = disk_list; list; list = list->next)
			{
			if (disk->order < ((DiskData *) list->data)->order)
				break;
			++disk_pos;
			}
		disk_list = g_list_insert(disk_list, disk, disk_pos);
		}
	else
		disk_list = g_list_append(disk_list, disk);
	++n_disks;
	return disk;
	}

static DiskData *
add_subdisk(gchar *subdisk_name, gchar *disk_name, gint subdisk)
	{
	DiskData	*sdisk = NULL;
	DiskData	*disk;
	GList		*list = NULL;

	for (list = disk_list; list; list = list->next)
		{
		disk = (DiskData * ) list->data;
		if (!strcmp(disk_name, disk->name))
			break;
		}
	if (!list)
		return NULL;
	sdisk = g_new0(DiskData, 1);
	sdisk->name = g_strdup(subdisk_name);
	sdisk->subdisk_parent = g_strdup(disk_name);
	sdisk->order = disk->order;
	sdisk->subdisk = subdisk;

	for (list = list->next; list; list = list->next)
		{
		disk = (DiskData * ) list->data;
		if (disk->subdisk == -1 || disk->subdisk > subdisk)
			break;
		}
	disk_list = g_list_insert_before(disk_list, list, sdisk);
	++n_disks;
	return sdisk;
	}

static void
disk_assign_data(DiskData *disk, guint64 rb, guint64 wb, gboolean virtual)
	{
	if (disk)
		{
		if (disk->rb != rb || disk->wb != wb)
			disk->changed = TRUE;
		else
			disk->changed = FALSE;
		disk->rb = rb;
		disk->wb = wb;
		disk->virtual = virtual;
		}
	}

void
gkrellm_disk_reset_composite(void)
	{
	/* Don't handle this. */
	}

void
gkrellm_disk_units_are_blocks(void)
	{
	units_are_blocks = TRUE;
	}

void
gkrellm_disk_add_by_name(const gchar *name, const gchar *label)
	{
	gint	order = -1;

    if (NULL == name) // Cannot add disk without a name
		return;
    order = gkrellm_sys_disk_order_from_name(name);
    /* FIXME: gkrellmd currently has no support for disk labels. Extend
       network-protocol and server to support disks with both name and label. */
    add_disk(name, order, 0, 0);
	}

void
gkrellm_disk_assign_data_by_device(gint device_number, gint unit_number,
			guint64 rb, guint64 wb, gboolean virtual)
	{
	GList		*list;
	DiskData	*disk = NULL;
	gchar		*name;
	gint		order = -1;

	for (list = disk_list; list; list = list->next)
		{
		disk = (DiskData * ) list->data;
		if (   disk->device_number == device_number
			&& disk->unit_number == unit_number
		   )
			break;
		disk = NULL;
		}
	if (!disk)
		{
		name = gkrellm_sys_disk_name_from_device(device_number,
					unit_number, &order);
		if (name)
			disk = add_disk(name, order, device_number, unit_number);
		}
	disk_assign_data(disk, rb, wb, virtual);
	}

void
gkrellm_disk_assign_data_nth(gint n, guint64 rb, guint64 wb, gboolean virtual)
	{
	DiskData	*disk;
	gchar		name[32];

	if (n < n_disks)
		disk = (DiskData *) g_list_nth_data(disk_list, n);
	else
		{
		snprintf(name, sizeof(name), "%s%c", _("Disk"), 'A' + n);
		disk = add_disk(name, n, 0, 0);
		}
	disk_assign_data(disk, rb, wb, virtual);
	}

void
gkrellm_disk_assign_data_by_name(gchar *name,
			guint64 rb, guint64 wb, gboolean virtual)
	{
	GList		*list;
	DiskData	*disk = NULL;
	gint		order = -1;

	for (list = disk_list; list; list = list->next)
		{
		disk = (DiskData * ) list->data;
		if (!strcmp(name, disk->name))
			break;
		disk = NULL;
		}
	if (!disk)
		{
		order = gkrellm_sys_disk_order_from_name(name);
		disk = add_disk(name, order, 0, 0);
		}
	disk_assign_data(disk, rb, wb, virtual);
	}

void
gkrellm_disk_subdisk_assign_data_by_name(gchar *subdisk_name, gchar *disk_name,
				guint64 rb, guint64 wb)
	{
	GList		*list;
	DiskData	*disk = NULL;
	gchar		*s, *endptr;
	gint		subdisk;

	if (!subdisk_name || !disk_name)
		return;
	for (list = disk_list; list; list = list->next)
		{
		disk = (DiskData * ) list->data;
		if (!strcmp(subdisk_name, disk->name))
			break;
		disk = NULL;
		}
	if (!disk)
		{
		/* A subdisk name is expected to be the disk_name with a number string
		|  appended.  Eg. "hda1" is a subdisk_name of disk_name "hda"
		*/
		s = subdisk_name + strlen(disk_name);
		if (*s == 'p')	/* except mmcblkN SD disks have "pN" partition numbers */
			++s;
		subdisk = strtol(s, &endptr, 0);
		if (!*s || *endptr)
			return;
		disk = add_subdisk(subdisk_name, disk_name, subdisk);
		}
	disk_assign_data(disk, rb, wb, FALSE);
	}

static void
update_disk(GkrellmdMonitor *mon, gboolean first_update)
	{
	GList		*list;
	DiskData	*disk = NULL;

	gkrellm_sys_disk_read_data();
	for (list = disk_list; list; list = list->next)
		{
		disk = (DiskData * ) list->data;
		if (disk->changed)
			{
			gkrellmd_need_serve(mon);
			break;
			}
		}
	}


static void
serve_disk_data(GkrellmdMonitor *mon, gboolean first_serve)
	{
	DiskData	*disk;
	GList		*list;
	gchar		*buf = NULL;

	gkrellmd_set_serve_name(mon, "disk");
	for (list = disk_list; list; list = list->next)
		{
		disk = (DiskData *) list->data;
		if (!disk->changed && !first_serve)
			continue;
		if (!disk->subdisk_parent)
			{
			if (gkrellmd_check_client_version(mon, 2, 2, 7) && disk->virtual)
                buf = g_strdup_printf("%s virtual %" PRIu64 " %" PRIu64 "\n",
							disk->name, disk->rb, disk->wb);
			else
				buf = g_strdup_printf("%s %" PRIu64 " %" PRIu64 "\n",
							disk->name, disk->rb, disk->wb);
			}
		else if (mon->privat->client->feature_subdisk)
			buf = g_strdup_printf("%s %s %" PRIu64 " %" PRIu64 "\n",
						disk->name, disk->subdisk_parent, disk->rb, disk->wb);
		else
			continue;
		gkrellmd_serve_data(mon, buf);
        g_free(buf);
        buf = NULL;
		}
	}

static void
serve_disk_setup(GkrellmdMonitor *mon)
	{
	GkrellmdClient	*client = mon->privat->client;

	if (units_are_blocks)
		gkrellmd_send_to_client(client, "<disk_setup>\nunits_are_blocks\n");
	if (gkrellmd_check_client_version(mon, 2,1,3))
		client->feature_subdisk = TRUE;
	}

static GkrellmdMonitor disk_monitor =
	{
	"disk",
	update_disk,
	serve_disk_data,
	serve_disk_setup,
	NULL
	};


static GkrellmdMonitor *
init_disk_monitor(void)
	{
	if (gkrellm_sys_disk_init())
		return &disk_monitor;
	return NULL;
	}

/* ======================================================= */
#include "../src/inet.h"

typedef struct
	{
	ActiveTCP	tcp;
	gboolean	alive,
				new_connection;
	}
	InetData;

static GList	*inet_list,
				*inet_dead_list;

static gboolean	inet_unsupported,
				inet_new;

void
gkrellm_inet_log_tcp_port_data(gpointer data)
	{
	GList		*list;
	InetData	*in;
	ActiveTCP	*tcp, *active_tcp = NULL;
	gchar		*ap, *aap;
	gint		slen;

	tcp = (ActiveTCP *) data;
	for (list = inet_list; list; list = list->next)
		{
		in = (InetData *) list->data;
		active_tcp = &in->tcp;
		if (tcp->family == AF_INET)
			{
			ap = (char *)&tcp->remote_addr;
			aap = (char *)&active_tcp->remote_addr;
			slen = sizeof(struct in_addr);
			}
#if defined(INET6)
		else if (tcp->family == AF_INET6)
			{
			ap = (char *)&tcp->remote_addr6;
			aap = (char *)&active_tcp->remote_addr6;
			slen = sizeof(struct in6_addr);
			}
#endif
		else
			return;
		if (   memcmp(aap, ap, slen) == 0
			&& active_tcp->remote_port == tcp->remote_port
			&& active_tcp->local_port == tcp->local_port
		   )
			{
			in->alive = TRUE;	/* Old alive connection still alive */
			return;
			}
		}
	inet_new = TRUE;
	in = g_new0(InetData, 1);
	in->tcp = *tcp;
	in->alive = TRUE;
	in->new_connection = TRUE;
	inet_list = g_list_append(inet_list, in);
	}

static void
update_inet(GkrellmdMonitor *mon, gboolean first_update)
	{
	GList		*list;
	InetData	*in;
	static gint	check_tcp;


	if (!first_update && !GK.second_tick)
		return;

	if (first_update || check_tcp == 0)
		{
		gkrellm_free_glist_and_data(&inet_dead_list);
		inet_new = FALSE;
		for (list = inet_list; list; list = list->next)
			{
			in = (InetData *) list->data;
			in->alive = FALSE;
			in->new_connection = FALSE;
			}

		gkrellm_sys_inet_read_tcp_data();

		for (list = inet_list; list; )
			{
			in = (InetData *) list->data;
			if (!in->alive)
				{
				if (list == inet_list)
					inet_list = inet_list->next;
				list = g_list_remove(list, in);
				inet_dead_list = g_list_append(inet_dead_list, in);
				}
			else
				list = list->next;
			}
		if (inet_new || inet_dead_list)
			gkrellmd_need_serve(mon);
		}
	check_tcp = (check_tcp + 1) % _GK.inet_interval;
	}

static void
serve_inet_data(GkrellmdMonitor *mon, gboolean first_serve)
	{
	InetData	*in;
	ActiveTCP	*tcp;
	GList		*list;
	gchar		buf[NI_MAXHOST + 128], *cp;
#if defined(INET6)
	struct sockaddr_in6	sin6;
	char		addrbuf[NI_MAXHOST];
#endif

	if (inet_new || first_serve)
		{
		gkrellmd_set_serve_name(mon, "inet");
		for (list = inet_list; list; list = list->next)
			{
			in = (InetData *) list->data;
			tcp = &in->tcp;
			if (   tcp->family == AF_INET
				&& (in->new_connection || first_serve)
			   )
				{
				cp = inet_ntoa(tcp->remote_addr);
				snprintf(buf, sizeof(buf), "+0 %x %s:%x\n",
							tcp->local_port, cp, tcp->remote_port);
				}
#if defined(INET6)
			else if (tcp->family == AF_INET6
				 && (in->new_connection || first_serve))
				{
				memset(&sin6, 0, sizeof(sin6));
				memcpy(&sin6.sin6_addr, &tcp->remote_addr6,
				       sizeof(struct in6_addr));
				sin6.sin6_family = AF_INET6;
#ifdef SIN6_LEN
				sin6.sin6_len = sizeof(struct sockaddr_in6);
#endif
				if (getnameinfo((struct sockaddr *)&sin6,
						sizeof(struct sockaddr_in6),
						addrbuf, sizeof(addrbuf),
						NULL, 0,
						NI_NUMERICHOST|NI_WITHSCOPEID)
				    != 0)
					continue;
				snprintf(buf, sizeof(buf), "+6 %x [%s]:%x\n",
					 tcp->local_port, addrbuf,
					 tcp->remote_port);
				}
#endif
			else
				continue;

			gkrellmd_serve_data(mon, buf);
			}
		}
	if (!first_serve)
		{
		gkrellmd_set_serve_name(mon, "inet");
		for (list = inet_dead_list; list; list = list->next)
			{
			in = (InetData *) list->data;
			tcp = &in->tcp;
			if (tcp->family == AF_INET)
				{
				cp = inet_ntoa(tcp->remote_addr);
				snprintf(buf, sizeof(buf), "-0 %x %s:%x\n",
							tcp->local_port, cp, tcp->remote_port);
				}
#if defined(INET6)
			else if (tcp->family == AF_INET6)
				{
				memset(&sin6, 0, sizeof(sin6));
				memcpy(&sin6.sin6_addr, &tcp->remote_addr6,
				       sizeof(struct in6_addr));
				sin6.sin6_family = AF_INET6;
#ifdef SIN6_LEN
				sin6.sin6_len = sizeof(struct sockaddr_in6);
#endif
				if (getnameinfo((struct sockaddr *)&sin6,
						sizeof(struct sockaddr_in6),
						addrbuf, sizeof(addrbuf),
						NULL, 0,
						NI_NUMERICHOST|NI_WITHSCOPEID)
				    != 0)
					continue;
				snprintf(buf, sizeof(buf), "-6 %x [%s]:%x\n",
					 tcp->local_port, addrbuf,
					 tcp->remote_port);
				}
#endif
			else
				continue;

			gkrellmd_serve_data(mon, buf);
			}
		}
	}

static void
serve_inet_setup(GkrellmdMonitor *mon)
	{
	GkrellmdClient	*client = mon->privat->client;

	if (inet_unsupported)
		gkrellmd_send_to_client(client, "<inet_setup>\ninet_unsupported\n");
	}

static GkrellmdMonitor inet_monitor =
	{
	"inet",
	update_inet,
	serve_inet_data,
	serve_inet_setup,
	NULL
	};


static GkrellmdMonitor *
init_inet_monitor(void)
	{
	if (_GK.inet_interval > 0 && gkrellm_sys_inet_init())
		return &inet_monitor;
	inet_unsupported = TRUE;
	return NULL;
	}

/* ======================================================= */

#define TIMER_TYPE_NONE	0
#define TIMER_TYPE_PPP	1
#define TIMER_TYPE_IPPP	2

typedef struct
	{
	gchar		*name;
	gboolean	changed,
				up,
				up_prev,
				up_event,
				down_event;

	gboolean	timed_changed;
	time_t		up_time;

	gulong		rx,
				tx;
	}
	NetData;

static NetData	*net_timer;

static GList	*net_list,
				*net_sys_list;

static time_t	net_timer0;
static gint		net_timer_type;

static gboolean	net_use_routed;


gchar *
gkrellm_net_mon_first(void)
	{
	gchar	*name = NULL;

	net_sys_list = net_list;
	if (net_sys_list)
		{
		name = ((NetData *) (net_sys_list->data))->name;
		net_sys_list = net_sys_list->next;
		}
	return name;
	}

gchar *
gkrellm_net_mon_next(void)
	{
	gchar	*name = NULL;

	if (net_sys_list)
		{
		name = ((NetData *) (net_sys_list->data))->name;
		net_sys_list = net_sys_list->next;
		}
	return name;
	}

void
gkrellm_net_use_routed(gboolean real_routed /* not applicable in server */)
	{
	net_use_routed = TRUE;
	}

static NetData *
net_new(gchar *name)
	{
	NetData	*net;

	net = g_new0(NetData, 1);
	net->name = g_strdup(name);
	net_list = g_list_append(net_list, net);

	if (net_timer_type != TIMER_TYPE_NONE && !strcmp(_GK.net_timer, net->name))
		net_timer = net;

	return net;
	}

void
gkrellm_net_assign_data(gchar *name, gulong rx, gulong tx)
	{
	GList	*list;
	NetData	*net;

	for (list = net_list; list; list = list->next)
		{
		net = (NetData *) list->data;
		if (!strcmp(net->name, name))
			{
			if (net->rx != rx || net->tx != tx)
				net->changed = TRUE;
			else
				net->changed = FALSE;
			break;
			}
		}
	if (!list)
		net = net_new(name);

	if (GK.second_tick && !net_use_routed)
		net->up = TRUE;
	net->rx = rx;
	net->tx = tx;
	}

void
gkrellm_net_routed_event(gchar *name, gboolean routed)
	{
	GList	*list;
	NetData	*net;

	for (list = net_list; list; list = list->next)
		{
		net = (NetData *) list->data;
		if (!strcmp(net->name, name))
			break;
		}
	if (!list)
		net = net_new(name);

	if (routed)
		net->up_event = TRUE;
	else
		net->down_event = TRUE;
	net->up = routed;
	}

void
gkrellm_net_add_timer_type_ppp(gchar *name)
	{
	if (!_GK.net_timer || !name)
		return;
	if (name && !strncmp(_GK.net_timer, name, strlen(name) - 1))
		net_timer_type = TIMER_TYPE_PPP;
	}

void
gkrellm_net_add_timer_type_ippp(gchar *name)
	{
	if (!_GK.net_timer || !name)
		return;
	if (name && !strncmp(_GK.net_timer, name, strlen(name) - 1))
		net_timer_type = TIMER_TYPE_IPPP;
	}

void
gkrellm_net_set_lock_directory(gchar *dir)
	{
	/* Not supported remotely */
	}

static void
update_net(GkrellmdMonitor *mon, gboolean first_update)
	{
	GList		*list;
	NetData		*net;
	gint		up_time = 0;

	if (GK.second_tick)
		{
		if (!net_use_routed)
			{
			for (list = net_list; list; list = list->next)
				{
				net = (NetData *) list->data;
				net->up_prev = net->up;
				net->up = FALSE;
				}
			}
		else
			gkrellm_sys_net_check_routes();
		}
	gkrellm_sys_net_read_data();

	if (GK.second_tick && !net_use_routed)
		{
		for (list = net_list; list; list = list->next)
			{
			net = (NetData *) list->data;
			if (net->up && !net->up_prev)
				net->up_event = TRUE;
			else if (!net->up && net->up_prev)
				net->down_event = TRUE;
			}
		}

	if (net_timer && GK.second_tick)
		{
		if (net_timer_type == TIMER_TYPE_PPP)
			{
			GStatBuf		st;
			gchar			buf[256];

			if (net_timer->up_event)
				{
				snprintf(buf, sizeof(buf), "/var/run/%s.pid", net_timer->name);
				if (g_stat(buf, &st) == 0)
					net_timer0 = st.st_mtime;
				else
					time(&net_timer0);
				}
			if (net_timer->up)
				up_time = (int) (time(0) - net_timer0);
			}
		else if (net_timer_type == TIMER_TYPE_IPPP)
			{
			/* get all isdn status from its connect state because the
			|  net_timer->up can be UP even with isdn line not connected.
			|  Can't get time history if gkrellmd started after connects.
			*/
			static gboolean		old_connected;
			gboolean			connected;

			connected = gkrellm_sys_net_isdn_online();
			if (connected && !old_connected)
				time(&net_timer0);			/* New session just started */
			old_connected = connected;

			up_time = (int) (time(0) - net_timer0);
			}
		if (up_time != net_timer->up_time)
			net_timer->timed_changed = TRUE;
		net_timer->up_time = up_time;
		}

	gkrellmd_need_serve(mon);	/* serve func checks for changed */
	}

static void
serve_net_data(GkrellmdMonitor *mon, gboolean first_serve)
	{
	NetData		*net;
	GList		*list;
	gchar		buf[128];
	gboolean	fake_up_event;

	gkrellmd_set_serve_name(mon, "net");
	for (list = net_list; list; list = list->next)
		{
		net = (NetData *) list->data;
		if (net->changed || first_serve)
			{
			snprintf(buf, sizeof(buf), "%s %lu %lu\n", net->name, net->rx, net->tx);
			gkrellmd_serve_data(mon, buf);
			}
		}

	/* Since the server transmits changes only, use the routed interface
	|  to the client regardless if the sysdep code uses routed.
	*/
	if (GK.second_tick || first_serve)
		{
		gkrellmd_set_serve_name(mon, "net_routed");
		for (list = net_list; list; list = list->next)
			{
			net = (NetData *) list->data;
			fake_up_event = (first_serve && net->up);
			if (net->up_event || net->down_event || fake_up_event)
				{
				snprintf(buf, sizeof(buf), "%s %d\n", net->name,
						fake_up_event ? TRUE : net->up_event);
				gkrellmd_serve_data(mon, buf);
				}
			if (mon->privat->client->last_client)
				net->up_event = net->down_event = FALSE;
			}
		}

	if (net_timer && GK.second_tick)
		{
		if (net_timer->timed_changed || first_serve)
			{
			gkrellmd_set_serve_name(mon, "net_timer");
			snprintf(buf, sizeof(buf), "%s %d\n", net_timer->name, (gint)net_timer->up_time);
			gkrellmd_serve_data(mon, buf);
			}
		}
	}

static void
serve_net_setup(GkrellmdMonitor *mon)
	{
	GkrellmdClient	*client = mon->privat->client;
	gchar			buf[128];

	/* The client <-> server link always uses routed mode, but the client
	|  needs to know if server sysdep uses routed for config purposes.
	*/
	if (net_use_routed)
		gkrellmd_send_to_client(client, "<net_setup>\nnet_use_routed\n");

	if (net_timer_type != TIMER_TYPE_NONE)
		{
		snprintf(buf, sizeof(buf),
				"<net_setup>\nnet_timer %s\n", _GK.net_timer);
		gkrellmd_send_to_client(client, buf);
		}
	}

static GkrellmdMonitor net_monitor =
	{
	"net",
	update_net,
	serve_net_data,
	serve_net_setup,
	NULL
	};


static GkrellmdMonitor *
init_net_monitor(void)
	{
	net_timer_type = TIMER_TYPE_NONE;
	if (gkrellm_sys_net_init())
		return &net_monitor;
	return NULL;
	}


/* ======================================================= */
struct
	{
	gboolean	mem_changed;
	guint64		total,
				used,
				free,
				shared,
				buffers,
				cached;

	gboolean	swap_changed;
	guint64		swap_total,
				swap_used;
	gulong		swap_in,
				swap_out;
	}
	mem;

void
gkrellm_mem_assign_data(guint64 total, guint64 used, guint64 free,
			guint64 shared, guint64 buffers, guint64 cached)
	{
	if (   mem.total != total
		|| mem.used != used
		|| mem.free != free
		|| mem.shared != shared
		|| mem.buffers != buffers
		|| mem.cached != cached
	   )
		{
		mem.total = total;
		mem.used = used;
		mem.free = free;
		mem.shared = shared;
		mem.buffers = buffers;
		mem.cached = cached;
		mem.mem_changed = TRUE;
		}
	}

void
gkrellm_swap_assign_data(guint64 total, guint64 used,
			gulong swap_in, gulong swap_out)
	{
	if (   mem.swap_total != total
		|| mem.swap_used != used
		|| mem.swap_in != swap_in
		|| mem.swap_out != swap_out
	   )
		{
		mem.swap_total = total;
		mem.swap_used = used;
		mem.swap_in = swap_in;
		mem.swap_out = swap_out;
		mem.swap_changed = TRUE;
		}
	}

static void
update_mem(GkrellmdMonitor *mon, gboolean first_update)
	{
	mem.mem_changed = mem.swap_changed = FALSE;

	gkrellm_sys_swap_read_data();
	if (first_update || GK.five_second_tick)
		gkrellm_sys_mem_read_data();

	if (mem.mem_changed || mem.swap_changed)
		gkrellmd_need_serve(mon);
	}

static void
serve_mem_data(GkrellmdMonitor *mon, gboolean first_serve)
	{
	gchar	buf[128];

	if (mem.mem_changed || first_serve)
		{
		gkrellmd_set_serve_name(mon, "mem");
		snprintf(buf, sizeof(buf), "%" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 "\n",
				mem.total, mem.used, mem.free,
				mem.shared, mem.buffers, mem.cached);
		gkrellmd_serve_data(mon, buf);
		}

	if (mem.swap_changed || first_serve)
		{
		gkrellmd_set_serve_name(mon, "swap");
		snprintf(buf, sizeof(buf), "%" PRIu64 " %" PRIu64 " %lu %lu\n",
				mem.swap_total, mem.swap_used,
				mem.swap_in, mem.swap_out);
		gkrellmd_serve_data(mon, buf);
		}
	}

static GkrellmdMonitor mem_monitor =
	{
	"mem",
	update_mem,
	serve_mem_data,
	NULL,
	NULL
	};


static GkrellmdMonitor *
init_mem_monitor(void)
	{
	if (!gkrellm_sys_mem_init())
		return NULL;
	serveflag_done_list = g_list_append(serveflag_done_list, &mem.mem_changed);
	serveflag_done_list = g_list_append(serveflag_done_list,&mem.swap_changed);
	return &mem_monitor;
	}

/* ======================================================= */
typedef struct
	{
	gboolean	busy,
				deleted,
				is_mounted,
				is_nfs,
				changed;
	gchar		*directory,
				*device,
				*type,
				*options;
	gint64		blocks,
				bavail,
				bfree,
				bsize;
	}
	Mount;

static GList	*mounts_list,
				*fstab_list;

static gboolean	nfs_check,
				fs_check,
				fs_need_serve,
				fstab_list_modified,
				mounts_list_modified,
				mounting_unsupported;

static gchar *remote_fs_types[] =
	{
	"cifs",
	"nfs",
	"smbfs"
	};

void
gkrellm_fs_setup_eject(gchar *eject_tray, gchar *close_tray,
			GkrellmFsEjectFunc eject_func,
			GkrellmFsCloseFunc close_func)
	{
	/* Not supported remotely */
	}

void
gkrellm_fs_add_to_mounts_list(gchar *dir, gchar *dev, gchar *type)
	{
	GList	*list;
	Mount	*m;

	for (list = mounts_list; list; list = list->next)
		{
		m = (Mount *) list->data;
		if (   !strcmp(m->directory, dir)
			&& !strcmp(m->device, dev)
			&& !strcmp(m->type, type)
		   )
			break;
		}
	if (!list)
		{
		m = g_new0(Mount, 1);
		m->directory = g_strdup(dir);
		m->device = g_strdup(dev);
		m->type = g_strdup(type);
		mounts_list = g_list_append(mounts_list, m);
		mounts_list_modified = TRUE;
		serveflag_done_list = g_list_append(serveflag_done_list, &m->changed);

		for (size_t i = 0; i < G_N_ELEMENTS(remote_fs_types); ++i)
			{
			if (!strcmp(m->type, remote_fs_types[i]))
				{
				m->is_nfs = TRUE;
				break;
				}
			}
		}
	m->is_mounted = TRUE;
	}

void
gkrellm_fs_add_to_fstab_list(gchar *dir, gchar *dev, gchar *type, gchar *opt)
	{
	Mount	*m;

	m = g_new0(Mount, 1);
	m->directory = g_strdup(dir);
	m->device = g_strdup(dev);
	m->type = g_strdup(type);
	fstab_list = g_list_append(fstab_list, m);
	}

void
gkrellm_fs_assign_fsusage_data(gpointer pointer,
		gint64 blocks, gint64 bavail, gint64 bfree, gint64 bsize)
	{
	Mount	*m = (Mount *) pointer;

	if (   m->blocks != blocks
		|| m->bavail != bavail
		|| m->bfree  != bfree
		|| m->bsize  != bsize
	   )
		{
		m->blocks = blocks;
		m->bavail = bavail;
		m->bfree  = bfree;
		m->bsize  = bsize;

		m->changed = TRUE;
		}
	}

void
gkrellm_fs_mounting_unsupported(void)
	{
	mounting_unsupported = TRUE;
	}

static void
refresh_mounts_list(void)
	{
	GList	*list;
	Mount	*m;

	for (list = mounts_list; list; list = list->next)
		((Mount *) list->data)->is_mounted = FALSE;

	gkrellm_sys_fs_get_mounts_list();

	for (list = mounts_list; list; )
		{
		m = (Mount *) list->data;
		if (!m->is_mounted)
			{
			if (list == mounts_list)
				mounts_list = mounts_list->next;
			list = g_list_remove_link(list, list);
			g_free(m->directory);
			g_free(m->device);
			g_free(m->type);
			serveflag_done_list = g_list_remove(serveflag_done_list,
						&m->changed);
			if (m->busy)
				m->deleted = TRUE;
			else
				g_free(m);
			mounts_list_modified = TRUE;
			}
		else
			list = list->next;
		}
	}

static void
refresh_fstab_list(void)
	{
	Mount	*m;

	while (fstab_list)
		{
		m = (Mount *) fstab_list->data;
		g_free(m->directory);
		g_free(m->device);
		g_free(m->type);
		g_free(m);
		fstab_list = g_list_remove(fstab_list, fstab_list->data);
		}
	gkrellm_sys_fs_get_fstab_list();
	fstab_list_modified = TRUE;
	}

static gpointer
get_fsusage_thread(void *data)
	{
	Mount	*m = (Mount *) data;

	gkrellm_sys_fs_get_fsusage(m, m->directory);

	if (m->deleted)
		g_free(m);
	else
		{
		if (m->changed)
			fs_need_serve = TRUE;
		m->busy = FALSE;
		}
	return NULL;
	}

static void
update_fs(GkrellmdMonitor *mon, gboolean first_update)
	{
	GThread		*gth;
	GList		*list;
	Mount		*m;
	static gint	check_tick;

	if (fs_need_serve)		/* Asynchronous change in fsusage thread? */
		gkrellmd_need_serve(mon);
	fs_need_serve = FALSE;

	if (GK.second_tick)
		++check_tick;
	fs_check = !(check_tick % _GK.fs_interval);

	if (_GK.nfs_interval > 0)
		nfs_check = !(check_tick % _GK.nfs_interval);
	else
		nfs_check = 0;

	if (!first_update && (!GK.second_tick || (!fs_check && !nfs_check)))
		return;
	refresh_mounts_list();
	for (list = mounts_list; list; list = list->next)
		{
		m = (Mount *) list->data;
		if (fs_check && !m->is_nfs)
			gkrellm_sys_fs_get_fsusage(m, m->directory);
		else if (nfs_check && m->is_nfs && !m->busy)
			{
			m->busy = TRUE;
			gth = g_thread_new("get_fsusage", get_fsusage_thread, m);
			g_thread_unref(gth);
			}
		}
	if (first_update || gkrellm_sys_fs_fstab_modified())
		refresh_fstab_list();

	gkrellmd_need_serve(mon);
	}

static void
serve_fs_data(GkrellmdMonitor *mon, gboolean first_serve)
	{
	Mount	*m;
	GList	*list;
	gchar	buf[128];

	if (mounts_list_modified || first_serve)
		{
		gkrellmd_set_serve_name(mon, "fs_mounts");
		gkrellmd_serve_data(mon, ".clear\n");
		for (list = mounts_list; list; list = list->next)
			{
			m = (Mount *) list->data;
			snprintf(buf, sizeof(buf),
				"%s %s %s %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 "\n",
					m->directory, m->device, m->type,
					m->blocks, m->bavail, m->bfree, m->bsize);
			/*gkrellm_debug(DEBUG_SERVER,
				"Adding mount-line for %s to serve-data\n", m->directory);*/
			gkrellmd_serve_data(mon, buf);
			}
		}
	else
		{
		gkrellmd_set_serve_name(mon, "fs");
		for (list = mounts_list; list; list = list->next)
			{
			m = (Mount *) list->data;
			if (!m->changed)
				continue;
			snprintf(buf, sizeof(buf),
				"%s %s %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64 "\n",
					m->directory, m->device,
					m->blocks, m->bavail, m->bfree, m->bsize);
			/*gkrellm_debug(DEBUG_SERVER,
				"Updating fs %s in serve-data\n", m->directory);*/
			gkrellmd_serve_data(mon, buf);
			}
		}
	if (fstab_list_modified || first_serve)
		{
		gkrellmd_set_serve_name(mon, "fs_fstab");
		gkrellmd_serve_data(mon, ".clear\n");
		for (list = fstab_list; list; list = list->next)
			{
			m = (Mount *) list->data;
			snprintf(buf, sizeof(buf), "%s %s %s\n",
					m->directory, m->device, m->type);
			/*gkrellm_debug(DEBUG_SERVER,
				"Adding fstab-line for %s to serve-data\n", m->directory);*/
			gkrellmd_serve_data(mon, buf);
			}
		}
	}

static void
serve_fs_setup(GkrellmdMonitor *mon)
	{
	GkrellmdClient	*client = mon->privat->client;

	if (mounting_unsupported)
		gkrellmd_send_to_client(client, "<fs_setup>\nmounting_unsupported\n");
	}

static GkrellmdMonitor fs_monitor =
	{
	"fs",
	update_fs,
	serve_fs_data,
	serve_fs_setup,
	NULL
	};


static GkrellmdMonitor *
init_fs_monitor(void)
	{
	if (!gkrellm_sys_fs_init())
		return NULL;
	serveflag_done_list =
				g_list_append(serveflag_done_list, &fstab_list_modified);
	serveflag_done_list =
				g_list_append(serveflag_done_list, &mounts_list_modified);
	return &fs_monitor;
	}

/* ======================================================= */


typedef struct
	{
	gboolean	changed,
				have_data;

	gint		id;
	gboolean	present,
				on_line,
				charging;
	gint		percent;
	gint		time_left;
	}
	Battery;

static GList	*battery_list;

static Battery	*composite_battery;


static Battery *
battery_nth(gint n)
	{
	Battery		*bat;
	static gint	n_batteries;

	if (n > 10)
		return NULL;
	if (n < 0)
		{
		if (!composite_battery)
			{
			bat = g_new0(Battery, 1);
			battery_list = g_list_prepend(battery_list, bat);
			bat->id = GKRELLM_BATTERY_COMPOSITE_ID;
			composite_battery = bat;
			serveflag_done_list = g_list_append(serveflag_done_list,
								&composite_battery->changed);
			}
		return composite_battery;
		}

	if (composite_battery)
		++n;

	while ((bat = (Battery *)g_list_nth_data(battery_list, n)) == NULL)
		{
		bat = g_new0(Battery, 1);
		battery_list = g_list_append(battery_list, bat);
		bat->id = n_batteries++;
		serveflag_done_list = g_list_append(serveflag_done_list,
								&bat->changed);
		}
	return bat;
	}

void
gkrellm_battery_assign_data(gint id, gboolean present, gboolean on_line,
			gboolean charging, gint percent, gint time_left)
	{
	Battery	*bat;

	bat = battery_nth(id);
	if (!bat)
		return;

	if (   present   != bat->present
		|| on_line   != bat->on_line
		|| charging  != bat->charging
		|| percent   != bat->percent
		|| time_left != bat->time_left
	   )
		{
		bat->present = present;
		bat->on_line = on_line;
		bat->charging = charging;
		bat->percent = percent;
		bat->time_left = time_left;
		bat->changed = TRUE;
		}

	bat->have_data = TRUE;
	}

gint
gkrellm_battery_full_cap_fallback(void)
	{
	return 5000;	/* XXX Linux ACPI bug not handled by server */
	}

static void
update_battery(GkrellmdMonitor *mon, gboolean first_update)
	{
	GList	*list;
	Battery	*bat;

	if (!first_update && !GK.five_second_tick)
		return;

	for (list = battery_list; list; list = list->next)
		{
		bat = (Battery *) list->data;
		bat->have_data = FALSE;
		bat->changed = FALSE;
		}
	gkrellm_sys_battery_read_data();

	for (list = battery_list; list; list = list->next)
		{
		bat = (Battery *) list->data;
		if (!bat->have_data && bat->present)
			{
			bat->present = FALSE;
			bat->changed = TRUE;
			}
		if (bat->changed)
			gkrellmd_need_serve(mon);
		}
	}

static void
serve_battery_data(GkrellmdMonitor *mon, gboolean first_serve)
	{
	Battery		*bat;
	GList		*list;
	gchar		buf[128];

	gkrellmd_set_serve_name(mon, "battery");
	for (list = battery_list; list; list = list->next)
		{
		bat = (Battery *) list->data;

		if (   (!bat->changed && !first_serve)
			|| (   !gkrellmd_check_client_version(mon, 2,1,9)
				&& bat->id > 0
			   )
		   )
			continue;
		snprintf(buf, sizeof(buf), "%d %d %d %d %d %d\n",
					bat->present, bat->on_line, bat->charging,
					bat->percent, bat->time_left, bat->id);
		gkrellmd_serve_data(mon, buf);
		}
	}

static void
serve_battery_setup(GkrellmdMonitor *mon)
	{
	GkrellmdClient	*client = mon->privat->client;

	gkrellm_sys_battery_read_data();
	if (battery_list)
		gkrellmd_send_to_client(client,
					"<battery_setup>\nbattery_available\n");
	}

static GkrellmdMonitor battery_monitor =
	{
	"battery",
	update_battery,
	serve_battery_data,
	serve_battery_setup,
	NULL
	};

static GkrellmdMonitor *
init_battery_monitor(void)
	{
	if (!gkrellm_sys_battery_init())
		return NULL;
	return &battery_monitor;
	}

/* ======================================================= */

typedef struct
	{
	gboolean	changed;
	gint		type;

	gchar		*path;			/* Pathname to sensor data or device file */

	gchar		*id_name;		/* These 4 are unique sensor identification */
	gint		id;				/*   of a particular sensor type */
	gint		iodev;			/* One or any combination may be used. */
	gint		inter;

	gchar		*vref;
	gchar		*default_label;
	gint		group;

	gfloat		factor;
	gfloat		offset;
	gfloat		raw_value;
	}
	Sensor;

static GList	*sensors_list;

static gboolean thread_busy,
				sensors_need_serve;

static gpointer
read_sensors(void *data)
	{
	GList		*list;
	Sensor		*sensor;
	gfloat		tmp;
	gboolean	need_serve = FALSE;

	for (list = sensors_list; list; list = list->next)
		{
		sensor = (Sensor *) list->data;
		tmp = sensor->raw_value;
		if (sensor->type == SENSOR_TEMPERATURE)
			gkrellm_sys_sensors_get_temperature(sensor->path, sensor->id,
				sensor->iodev, sensor->inter, &sensor->raw_value);
		else if (sensor->type == SENSOR_FAN)
			gkrellm_sys_sensors_get_fan(sensor->path, sensor->id,
				sensor->iodev, sensor->inter, &sensor->raw_value);
		else if (sensor->type == SENSOR_VOLTAGE)
			gkrellm_sys_sensors_get_voltage(sensor->path, sensor->id,
				sensor->iodev, sensor->inter, &sensor->raw_value);
		else if (sensor->type == SENSOR_POWER)
			gkrellm_sys_sensors_get_power(sensor->path, sensor->id,
				sensor->iodev, sensor->inter, &sensor->raw_value);
		if (sensor->raw_value != tmp)
			{
			sensor->changed = TRUE;
			need_serve = TRUE;
			}
		else
			sensor->changed = FALSE;
		}
	thread_busy = FALSE;
	sensors_need_serve = need_serve; /* Thread, so set after data collected */

	return NULL;
	}

static void
run_sensors_thread(void)
	{
	GThread		*gth;

	if (thread_busy)
		return;
	thread_busy = TRUE;
	gth = g_thread_new("read_sensors", read_sensors, NULL);
	g_thread_unref(gth);
	}


void
gkrellm_sensors_config_migrate_connect(
			GkrellmSensorsConfigMigrateFunc migrate_func,
			gint sysdep_private)
	{
	}

void
gkrellm_sensors_set_group(gpointer sr, gint group)
	{
	Sensor	*sensor = (Sensor *) sr;

	if (sensor)
		sensor->group = group;
	}

void
gkrellm_sensors_sysdep_option(gchar *keyword, gchar *label,
			GkrellmSensorsSysdepOptionFunc func)
	{
	}

  /* A sensor within a type is uniquely identified by its id_name.
  |  A sysdep interface may additionally use any of the triple integer
  |  set (id, iodev, inter) for internal identification.
  |  Monitor code here uses path to read the sensor values, but id_name is only
  |  passed to the client since that is all that is needed for identification
  |  (the client is no longer interfacing to sysdep code).
  */
gpointer
gkrellm_sensors_add_sensor(gint type, gchar *sensor_path, gchar *id_name,
		gint id, gint iodev, gint inter,
		gfloat factor, gfloat offset, gchar *vref, gchar *default_label)
	{
	Sensor	*sensor;

	if (!id_name || !*id_name || type < 0 || type > 2)
		return NULL;

	sensor = g_new0(Sensor, 1);
	sensor->id_name = g_strdup(id_name);

	if (sensor_path)
		sensor->path = g_strdup(sensor_path);
	else
		sensor->path = g_strdup(id_name);

	sensor->vref = g_strdup(vref ? vref : "NONE");
	sensor->default_label = g_strdup(default_label ? default_label : "NONE");

	sensor->factor = factor;
	sensor->offset = offset;
	sensor->type = type;
	sensor->id = id;
	sensor->iodev = iodev;
	sensor->inter = inter;
	sensors_list = g_list_append(sensors_list, sensor);
	return sensor;
	}

static void
update_sensors(GkrellmdMonitor *mon, gboolean first_update)
	{
	if (sensors_need_serve)		/* Asynchronously set in thread */
		gkrellmd_need_serve(mon);
	sensors_need_serve = FALSE;

	if (!GK.five_second_tick && !first_update)
		return;
	if (first_update)
		read_sensors(NULL);		/* No thread on first read */
	else
		run_sensors_thread();
	}

static void
serve_sensors_data(GkrellmdMonitor *mon, gboolean first_serve)
	{
	Sensor			*sr;
	GList			*list;
	gchar			buf[128];
	gboolean		sensor_disk_ok;

	gkrellmd_set_serve_name(mon, "sensors");
	sensor_disk_ok = gkrellmd_check_client_version(mon, 2,2,0);
	for (list = sensors_list; list; list = list->next)
		{
		sr = (Sensor *) list->data;
		if (sr->group == SENSOR_GROUP_DISK && !sensor_disk_ok)
			continue;
		if (sr->changed || first_serve)
			{
			snprintf(buf, sizeof(buf), "%d \"%s\" %d %d %d %.2f\n",
					sr->type, sr->id_name,
					sr->id, sr->iodev, sr->inter, sr->raw_value);
			gkrellmd_serve_data(mon, buf);
			}
		}
	}

static void
serve_sensors_setup(GkrellmdMonitor *mon)
	{
	GkrellmdClient	*client = mon->privat->client;
	GList			*list;
	Sensor			*s;
	gchar			buf[256];
	gboolean		sensor_disk_ok;

	gkrellmd_send_to_client(client, "<sensors_setup>\n");
	sensor_disk_ok = gkrellmd_check_client_version(mon, 2,2,0);
	for (list = sensors_list; list; list = list->next)
		{
		s = (Sensor *) list->data;
		if (s->group == SENSOR_GROUP_DISK && !sensor_disk_ok)
			continue;
		if (sensor_disk_ok)
			snprintf(buf, sizeof(buf), "%d \"%s\" %d %d %d %.4f %.4f \"%s\" \"%s\" %d\n",
					s->type, s->id_name,
					s->id, s->iodev, s->inter,
					s->factor, s->offset, s->vref, s->default_label, s->group);
		else
			snprintf(buf, sizeof(buf), "%d \"%s\" %d %d %d %.4f %.4f \"%s\" \"%s\"\n",
					s->type, s->id_name,
					s->id, s->iodev, s->inter,
					s->factor, s->offset, s->vref, s->default_label);
		gkrellmd_send_to_client(client, buf);
		}
	}

static GkrellmdMonitor sensors_monitor =
	{
	"sensors",
	update_sensors,
	serve_sensors_data,
	serve_sensors_setup,
	NULL
	};

static GkrellmdMonitor *
init_sensors_monitor(void)
	{
	if (!gkrellm_sys_sensors_init())
		return NULL;
	return &sensors_monitor;
	}

/* ======================================================= */
static time_t	base_uptime,
				up_seconds;
static glong	up_minutes = -1;

void
gkrellm_uptime_set_base_uptime(time_t base)
	{
	base_uptime = base;
	}

static void
update_uptime(GkrellmdMonitor *mon, gboolean first_update)
	{
	glong	prev_up;

	if (GK.ten_second_tick || up_minutes < 0 || first_update)
		{
		prev_up = up_minutes;
		up_seconds = gkrellm_sys_uptime_read_uptime();
		if (up_seconds > 0)
			up_minutes = (glong) (up_seconds / 60);
		else
			up_minutes = (glong)(time(0) - _GK.start_time + base_uptime) / 60;
		if (up_minutes != prev_up)
			gkrellmd_need_serve(mon);
		}
	}

static void
serve_uptime_data(GkrellmdMonitor *mon, gboolean first_serve)
	{
	gchar	buf[128];

	gkrellmd_set_serve_name(mon, "uptime");
	snprintf(buf, sizeof(buf), "%ld\n", (glong) (up_minutes >= 0 ? up_minutes : 0));
	gkrellmd_serve_data(mon, buf);
	}

static GkrellmdMonitor uptime_monitor =
	{
	"uptime",
	update_uptime,
	serve_uptime_data,
	NULL,
	NULL
	};

static GkrellmdMonitor *
init_uptime_monitor(void)
	{
	if (!gkrellm_sys_uptime_init())
		return NULL;
	return &uptime_monitor;
	}

/* ======================================================= */
static void
send_time(GkrellmdClient *client)
	{
	struct tm	*t;
	gchar		buf[128];

	t = &gkrellmd_current_tm;
	snprintf(buf, sizeof(buf), "<time>\n%d %d %d %d %d %d %d %d %d\n",
		t->tm_sec, t->tm_min, t->tm_hour,
		t->tm_mday, t->tm_mon, t->tm_year,
		t->tm_wday, t->tm_yday, t->tm_isdst);
	gkrellmd_send_to_client(client, buf);
	}

/* ======================================================= */

void
gkrellmd_plugin_serve_setup(GkrellmdMonitor *mon, gchar *name, gchar *line)
	{
	GkrellmdClient	*client = mon->privat->client;
	gchar			buf[256];

	if (!mon || !name || !line)
		return;
	gkrellmd_send_to_client(client, "<plugin_setup>\n");
	snprintf(buf, sizeof(buf), "%s %s\n", name, line);
	gkrellmd_send_to_client(client, buf);
	}

static void
add_monitor(GkrellmdMonitor *mon)
	{
	if (!mon)
		return;
	mon->privat = g_new0(GkrellmdMonitorPrivate, 1);
	mon->privat->serve_gstring = g_string_new("");
	gkrellmd_monitor_list = g_list_append(gkrellmd_monitor_list, mon);
	}

void
gkrellmd_load_monitors(void)
	{
	GList			*list;
	GkrellmdMonitor	*mon;

	add_monitor(init_sensors_monitor());
	add_monitor(init_cpu_monitor());
	add_monitor(init_proc_monitor());
	add_monitor(init_disk_monitor());
	add_monitor(init_net_monitor());
	add_monitor(init_inet_monitor());
	add_monitor(init_mem_monitor());
	add_monitor(init_fs_monitor());
	add_monitor(gkrellmd_init_mail_monitor());
	add_monitor(init_battery_monitor());
	add_monitor(init_uptime_monitor());

	list = gkrellmd_plugins_load();
	if (_GK.list_plugins)
		exit(0);
	if (_GK.log_plugins)
		g_message("%s\n", plugin_install_log ? plugin_install_log :
				_("No plugins found\n"));
	for (  ; list; list = list->next)
		{
		mon = (GkrellmdMonitor *) list->data;
		mon->privat->serve_gstring = g_string_new("");
		mon->privat->is_plugin = TRUE;
		gkrellmd_monitor_list = g_list_append(gkrellmd_monitor_list, mon);
		}
	}

void
gkrellmd_need_serve(GkrellmdMonitor *mon)
	{
	if (mon)
		mon->privat->need_serve = TRUE;
	}

gint
gkrellmd_update_monitors(void)
	{
	GList			*list, *c_list;
	GkrellmdMonitor			*mon;
	GkrellmdMonitorPrivate	*mp;
	GkrellmdClient			*client;
	struct tm				*pCur;
	static time_t			time_prev;
	gchar					buf[64];
	static gboolean			first_update = TRUE;
	static GString			*serve_gstring;

	time(&_GK.time_now);
	GK.second_tick = (_GK.time_now == time_prev) ? FALSE : TRUE;
	time_prev = _GK.time_now;

	if (GK.second_tick)
		{
		pCur = localtime(&_GK.time_now);
		GK.two_second_tick  = ((pCur->tm_sec % 2) == 0) ? TRUE : FALSE;
		GK.five_second_tick = ((pCur->tm_sec % 5) == 0) ? TRUE : FALSE;
		GK.ten_second_tick  = ((pCur->tm_sec % 10) == 0) ? TRUE : FALSE;
		GK.minute_tick = (pCur->tm_min  != gkrellmd_current_tm.tm_min);
		gkrellmd_current_tm = *pCur;
		}
	else
		{
		GK.two_second_tick = FALSE;
		GK.five_second_tick = FALSE;
		GK.ten_second_tick = FALSE;
		GK.minute_tick = FALSE;
		}

	for (list = gkrellmd_monitor_list; list; list = list->next)
		{
		mon = (GkrellmdMonitor *) list->data;
		if (mon->update_monitor)
			(*(mon->update_monitor))(mon, first_update);
		}
	++GK.timer_ticks;
	if (!serve_gstring)
		serve_gstring = g_string_new("");
	for (c_list = gkrellmd_client_list; c_list; c_list = c_list->next)
		{
		client = (GkrellmdClient *) c_list->data;
		client->last_client = !c_list->next;
		if (!client->served)
			gkrellmd_send_to_client(client, "<initial_update>\n");
		for (list = gkrellmd_monitor_list; list; list = list->next)
			{
			mon = (GkrellmdMonitor *) list->data;
			mp = mon->privat;
			if (!mon->serve_data || (!mp->need_serve && client->served))
				continue;
			mp->client = client;
			mp->serve_name_sent = FALSE;
			(*(mon->serve_data))(mon, client->served ? FALSE : TRUE);
			if (mp->serve_gstring->len > 0)
				{
				serve_gstring =
					g_string_append(serve_gstring, mp->serve_gstring->str);
				mp->serve_gstring = g_string_truncate(mp->serve_gstring, 0);
				}
			}
		gkrellmd_send_to_client(client, serve_gstring->str);
		serve_gstring = g_string_truncate(serve_gstring, 0);

		if (GK.minute_tick || !client->served)
			send_time(client);
		else if (GK.second_tick)
			{
			snprintf(buf, sizeof(buf), "<.%d>\n", gkrellmd_current_tm.tm_sec);
			gkrellmd_send_to_client(client, buf);
			}

		if (!client->served)
			gkrellmd_send_to_client(client, "</initial_update>\n");
		client->served = TRUE;
		}

	for (list = gkrellmd_monitor_list; list; list = list->next)
		{
		mon = (GkrellmdMonitor *) list->data;
		mp = mon->privat;
		mp->need_serve = FALSE;
		}

	for (list = serveflag_done_list; list; list = list->next)
		*((gboolean *) list->data) = FALSE;

	first_update = FALSE;
	return TRUE;
	}

void
gkrellmd_serve_setup(GkrellmdClient *client)
	{
	GList			*list;
	GkrellmdMonitor	*mon;
	struct lconv	*lc;
	gchar			buf[32], *s, *name;

	gkrellmd_send_to_client(client, "<gkrellmd_setup>\n");

	s = g_strdup_printf("<version>\ngkrellmd %d.%d.%d\n",
				GKRELLMD_VERSION_MAJOR, GKRELLMD_VERSION_MINOR,
				GKRELLMD_VERSION_REV);
	gkrellmd_send_to_client(client, s);
	g_free(s);

	lc = localeconv();
	snprintf(buf, sizeof(buf), "%c\n", *lc->decimal_point);
	s = g_strconcat("<decimal_point>\n", buf, "\n", NULL);
	gkrellmd_send_to_client(client, s);
	g_free(s);

	for (list = gkrellmd_monitor_list; list; list = list->next)
		{
		mon = (GkrellmdMonitor *) list->data;
		mon->privat->client = client;
		if (mon->serve_setup)
			(*(mon->serve_setup))(mon);
		}
	name = gkrellm_sys_get_host_name();
	s = g_strconcat("<hostname>\n", name, "\n", NULL);
	gkrellmd_send_to_client(client, s);
	g_free(s);

	name = gkrellm_sys_get_system_name();
	s = g_strconcat("<sysname>\n", name, "\n", NULL);
	gkrellmd_send_to_client(client, s);
	g_free(s);

	send_time(client);

	gkrellmd_send_to_client(client, "<monitors>\n");
	for (list = gkrellmd_monitor_list; list; list = list->next)
		{
		mon = (GkrellmdMonitor *) list->data;
		snprintf(buf, sizeof(buf), "%s\n", mon->name);
		gkrellmd_send_to_client(client, buf);
		}

	snprintf(buf, sizeof(buf), "%d\n", _GK.io_timeout);
	s = g_strconcat("<io_timeout>\n", buf, "\n", NULL);
	gkrellmd_send_to_client(client, s);
	g_free(s);

	snprintf(buf, sizeof(buf), "%d\n", _GK.reconnect_timeout);
	s = g_strconcat("<reconnect_timeout>\n", buf, "\n", NULL);
	gkrellmd_send_to_client(client, s);
	g_free(s);

	gkrellmd_send_to_client(client, "</gkrellmd_setup>\n");
	}

void
gkrellmd_client_input_connect(GkrellmdMonitor *mon,
			void (*func)(GkrellmdClient *, gchar *))
	{
	mon->privat->client_input_func = func;
	}

void
gkrellmd_client_read(gint fd, gint nbytes)
	{
	GList					*list;
	GkrellmdClient			*client = NULL;
	GkrellmdMonitor			*mon;
	GkrellmdMonitorPrivate	*mp;
	gchar					buf[513], *s, *e;
	gint					n, buflen;

	for (list = gkrellmd_client_list; list; list = list->next)
		{
		client = (GkrellmdClient *) list->data;
		if (client->fd != fd)
			continue;

		if (!client->input_gstring)
			client->input_gstring = g_string_new("");

		buflen = sizeof(buf) - 1;
		while (nbytes > 0)
			{
			n = (nbytes > buflen) ? buflen : nbytes;
			n = recv(fd, buf, n, 0);
			if (n <= 0)
				break;
			nbytes -= n;
			buf[n] = '\0';
			client->input_gstring =
						g_string_append(client->input_gstring, buf);
			}
		break;
		}
	if (!list)
		return;

	while (gkrellmd_getline_from_gstring(&client->input_gstring,
				buf, sizeof(buf) - 1))
		{
		if (*buf == '<')
			{
			client->input_func = NULL;
			s = buf + 1;
			for (list = gkrellmd_monitor_list; list; list = list->next)
				{
				mon = (GkrellmdMonitor *) list->data;
				mp = mon->privat;
				if (!mp->serve_name)
					continue;
				n = strlen(mp->serve_name);
				e = s + n;
				if (*e == '>' && !strncmp(mp->serve_name, s, n))
					{
					client->input_func = mp->client_input_func;
					break;
					}
				}
			}
		else if (client->input_func)
			(*client->input_func)(client, buf);
//		printf("%s: %s", client->hostname, buf);
		}
	}
