/* GKrellM
|  Copyright (C) 1999-2004 Bill Wilson
|
|  Author:  Bill Wilson    bill@gkrellm.net
|  Latest versions might be found at:  http://gkrellm.net
|
|  This program is free software which I release under the GNU General Public
|  License. You may redistribute and/or modify this program under the terms
|  of that license as published by the Free Software Foundation; either
|  version 2 of the License, or (at your option) any later version.
|
|  This program 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.  Version 2 is in the
|  COPYRIGHT file in the top level directory of this distribution.
| 
|  To get a copy of the GNU General Puplic License, write to the Free Software
|  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
#include <limits.h>
#include <errno.h>
#include <locale.h>
#include <sys/types.h>
#include <dirent.h>


static gboolean		need_locale_fix,
					have_diskstats,
					have_partition_stats,
					have_sysfs,
					have_sysfs_stats,
					have_sysfs_sensors;
static gchar		locale_decimal_point;


static gboolean		kernel_2_4,
					kernel_2_6;

static gint			os_major,
					os_minor,
					os_rev;

static gboolean
os_release(gint major, gint minor, gint rev)
	{
	if (   os_major > major
		|| (os_major == major && os_minor > minor)
		|| (os_major == os_major && os_minor == minor && os_rev >= rev)
	   )
		return TRUE;
	return FALSE;
	}


void
gkrellm_sys_main_init(void)
	{
	FILE	*f;
	gchar	buf[1024];

	if ((f = fopen("/proc/sys/kernel/osrelease", "r")) != NULL)
		{
		if (fgets(buf, sizeof(buf), f) != NULL)
			sscanf(buf, "%d.%d.%d", &os_major, &os_minor, &os_rev);
		fclose(f);
		kernel_2_4 = os_release(2, 4, 0);
		kernel_2_6 = os_release(2, 6, 0);
		}

	/* Various stats are in sysfs since 2.5.47, but it may not be mounted.
	*/
	if ((f = fopen("/proc/mounts", "r")) != NULL)
		{
		while (fgets(buf, sizeof(buf), f) != NULL)
			if (strstr(buf, "sysfs"))
				{
				have_sysfs = TRUE;
				break;
				}
		fclose(f);
		}
	}

void
gkrellm_sys_main_cleanup(void)
	{
	}


  /* Linux /proc always reports floats with '.' decimal points, but sscanf()
  |  for some locales needs commas in place of periods.  So, if current
  |  locale doesn't use periods, must insert the correct decimal point char.
  */
static void
locale_fix(gchar *buf)
	{
	gchar	*s;

	for (s = buf; *s; ++s)
		if (*s == '.')
			*s = locale_decimal_point;
	}


/* ===================================================================== */
/* CPU, disk, and swap monitor interfaces might all get data from /proc/stat
|  (depending on kernel version) so they will share reading of /proc/stat.
*/

#define	PROC_STAT_FILE	"/proc/stat"

static gint		n_cpus;
static gulong	swapin,
				swapout;

  /* CPU, and Disk monitors call this in their update routine. 
  |  Whoever calls it first will read the data for everyone.
  |
  | /proc/stat has cpu entries like:
  |		cpu		total_user	total_nice	total_sys	total_idle
  |		cpu0	cpu0_user	cpu0_nice	cpu0_sys	cpu0_idle
  |			...
  |		cpuN	cpuN_user	cpuN_nice	cpuN_sys	cpuN_idle
  |  where ticks for cpu are jiffies * smp_num_cpus
  |  and ticks for cpu[i] are jiffies (1/CLK_TCK)
  */
static void
linux_read_proc_stat(void)
	{
	static FILE	*f;
	gint		n, i, ncpu;
	gchar		*s, buf[1024];
	gulong		rblk[4], wblk[4], user, nice, sys, idle, iowait;
	static gint	data_read_tick	= -1;

	n = gkrellm_get_timer_ticks();
	if (data_read_tick == n)
		return;		/* Just one read per tick (multiple monitors call this) */

	if (!f && (f = fopen(PROC_STAT_FILE, "r")) == NULL)
		return;

	data_read_tick = n;
	gkrellm_disk_reset_composite();

	ncpu = 0;
	while ((fgets(buf, sizeof(buf), f)) != NULL)
		{
		if (buf[0] == 'c' && buf[1] == 'p')
			{
			n = sscanf(buf, "%*s %lu %lu %lu %lu %lu",
						&user, &nice, &sys, &idle, &iowait);

			if (n == 5)			/* iowait is new in kernel 2.5	*/
				idle += iowait;

			if (n_cpus > 1)
				{
				if (ncpu == 0)
					gkrellm_cpu_assign_composite_data(user, nice, sys, idle);
				else
					gkrellm_cpu_assign_data(ncpu - 1, user, nice, sys, idle);
				}
			/* If have cpu and cpu0 on single cpu machines, won't use cpu0.
			*/
			else if (ncpu == 0)
				gkrellm_cpu_assign_data(0, user, nice, sys, idle);
			++ncpu;
			continue;
			}
		if (!kernel_2_6 && !strncmp("swap", buf, 4))
			{
			sscanf(buf + 5, "%lu %lu", &swapin, &swapout);
			continue;
			}
		if (buf[0] != 'd')
			continue;

		if (!strncmp("disk_rblk", buf, 9))
			{
			s = buf + 10;
			for (i = 0; i < 4; ++i)
				rblk[i] = strtoul(s, &s, 0);
			}
		else if (!strncmp("disk_wblk", buf, 9))
			{
			s = buf + 10;
			for (i = 0; i < 4; ++ i)
				{
				wblk[i] = strtoul(s, &s, 0);
				gkrellm_disk_assign_data_nth(i, 512 * rblk[i], 512 * wblk[i]);
				}
			}
		else if (!strncmp("disk_io:", buf, 8))		/* Kernel 2.4 only	*/
			{
			gint	major, i_disk;
			gulong	rblk, wblk, rb1, rb2, wb1, wb2;

			s = strtok(buf + 9, " \t\n");
			while (s)
				{
				/* disk_io lines in 2.4.x kernels have had 2 formats */
				n = sscanf(s, "(%d,%d):(%*d,%lu,%lu,%lu,%lu)",
						&major, &i_disk, &rb1, &rb2, &wb1, &wb2);
				if (n == 6)	/* patched as of 2.4.0-test1-ac9 */
					{		/* (major,disk):(total_io,rio,rblk,wio,wblk) */
					rblk = rb2;
					wblk = wb2;
					}
				else	/* 2.3.99-pre8 to 2.4.0-testX */
					{		/* (major,disk):(rio,rblk,wio,wblk) */
					rblk = rb1;
					wblk = wb1;
					}
				/* floppys and CDroms don't show up in /proc/partitions.
				*/
				if (have_partition_stats)
					{
					gchar	name[32];

					name[0] = '\0';
					if (major == 2)
						sprintf(name, "fd%d", i_disk);
					else if (major == 11)
						sprintf(name, "scd%d", i_disk);
					if (name[0])
						gkrellm_disk_assign_data_by_name(name,
								512 * rblk, 512 * wblk);
					}
				else
					gkrellm_disk_assign_data_by_device(major, i_disk,
								512 * rblk, 512 * wblk);
				s = strtok(NULL, " \t\n");
				}
			}
		}
	rewind(f);
	}


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

void
gkrellm_sys_cpu_read_data(void)
	{
	/* One routine reads cpu, disk, and swap data.  All three monitors will
	| call it, but only the first call per timer tick will do the work.
	*/
	linux_read_proc_stat();
	}

gboolean
gkrellm_sys_cpu_init(void)
	{
	FILE	*f;
	gchar	buf[1024];

	if ((f = fopen(PROC_STAT_FILE, "r")) == NULL)
		return FALSE;

	while (fgets(buf, sizeof(buf), f))
		{
		if (strncmp(buf, "cpu", 3) != 0)
			continue;
		++n_cpus;
		}
	fclose(f);

	/* If multiple CPUs, the first one will be a composite.  Report only real.
	*/
	if (n_cpus > 1)
		--n_cpus;
	gkrellm_cpu_set_number_of_cpus(n_cpus);
	return TRUE;
	}


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

#define	PROC_PARTITIONS_FILE	"/proc/partitions"
#define	PROC_DISKSTATS_FILE		"/proc/diskstats"

#include <linux/major.h>
#if ! defined (SCSI_DISK0_MAJOR)
#define SCSI_DISK0_MAJOR	8
#endif
#if ! defined (MD_MAJOR)
#define MD_MAJOR	9
#endif

#if !defined(IDE4_MAJOR)
#define IDE4_MAJOR	56
#endif
#if !defined(IDE5_MAJOR)
#define IDE5_MAJOR	57
#endif
#if !defined(IDE6_MAJOR)
#define IDE6_MAJOR	88
#endif
#if !defined(IDE7_MAJOR)
#define IDE7_MAJOR	89
#endif
#if !defined(IDE8_MAJOR)
#define IDE8_MAJOR	90
#endif
#if !defined(IDE9_MAJOR)
#define IDE9_MAJOR	91
#endif
#if !defined(DAC960_MAJOR)
#define DAC960_MAJOR	48
#endif
#if !defined(COMPAQ_SMART2_MAJOR)
#define COMPAQ_SMART2_MAJOR	72
#endif
#if !defined(COMPAQ_CISS_MAJOR)
#define COMPAQ_CISS_MAJOR	104
#endif
#if !defined(LVM_BLK_MAJOR)
#define LVM_BLK_MAJOR 58
#endif
#if !defined(NBD_MAJOR)
#define NBD_MAJOR 43
#endif


struct _disk_name_map
	{
	gchar	*name;
	gint	major;
	gint	minor_mod;
	gchar	suffix_base;
	};

  /* Disk charts will appear in GKrellM in the same order as this table.
  */
static struct _disk_name_map
	disk_name_map[] =
	{
	{"hd",	IDE0_MAJOR,					64,	'a' },	/* 3:  hda, hdb */
	{"hd",	IDE1_MAJOR,					64,	'c' },	/* 22: hdc, hdd */
	{"hd",	IDE2_MAJOR,					64,	'e' },	/* 33: hde, hdf */
	{"hd",	IDE3_MAJOR,					64,	'g' },	/* 34: hdg, hdh */
	{"hd",	IDE4_MAJOR,					64,	'i' },	/* 56: hdi, hdj */
	{"hd",	IDE5_MAJOR,					64,	'k' },	/* 57: hdk, hdl */
	{"hd",	IDE6_MAJOR,					64,	'm' },	/* 88: hdm, hdn */
	{"hd",	IDE7_MAJOR,					64,	'o' },	/* 89: hdo, hdp */
	{"hd",	IDE8_MAJOR,					64,	'q' },	/* 90: hdq, hdr */
	{"hd",	IDE9_MAJOR,					64,	's' },	/* 91: hds, hdt */
	{"sd",	SCSI_DISK0_MAJOR,			16,	'a' },	/* 8:  sda-sdh */
	{"sg",	SCSI_GENERIC_MAJOR,			16,	'0' },	/* 21: sg0-sg16 */
	{"scd",	SCSI_CDROM_MAJOR,			16,	'0' },	/* 11: scd0-scd16 */
	{"sr",	SCSI_CDROM_MAJOR,			16,	'0' },	/* 11: sr0-sr16 */
	{"md",	MD_MAJOR,					0,	'0' },	/* 9:  md0-md3 */

	{"c0d",	DAC960_MAJOR,				32,	'0' },	/* 48:  c0d0-c0d31 */
	{"c1d",	DAC960_MAJOR + 1,			32,	'0' },	/* 49:  c1d0-c1d31 */
	{"c2d",	DAC960_MAJOR + 2,			32,	'0' },	/* 50:  c2d0-c2d31 */
	{"c3d",	DAC960_MAJOR + 3,			32,	'0' },	/* 51:  c3d0-c3d31 */
	{"c4d",	DAC960_MAJOR + 4,			32,	'0' },	/* 52:  c4d0-c4d31 */
	{"c5d",	DAC960_MAJOR + 5,			32,	'0' },	/* 53:  c5d0-c5d31 */
	{"c6d",	DAC960_MAJOR + 6,			32,	'0' },	/* 54:  c6d0-c6d31 */
	{"c7d",	DAC960_MAJOR + 7,			32,	'0' },	/* 55:  c7d0-c7d31 */

	{"cs0d", COMPAQ_SMART2_MAJOR,		16,	'0' },	/* 72:  c0d0-c0d15 */
	{"cs1d", COMPAQ_SMART2_MAJOR + 1,	16,	'0' },	/* 73:  c1d0-c1d15 */
	{"cs2d", COMPAQ_SMART2_MAJOR + 2,	16,	'0' },	/* 74:  c2d0-c2d15 */
	{"cs3d", COMPAQ_SMART2_MAJOR + 3,	16,	'0' },	/* 75:  c3d0-c3d15 */
	{"cs4d", COMPAQ_SMART2_MAJOR + 4,	16,	'0' },	/* 76:  c4d0-c4d15 */
	{"cs5d", COMPAQ_SMART2_MAJOR + 5,	16,	'0' },	/* 77:  c5d0-c5d15 */
	{"cs6d", COMPAQ_SMART2_MAJOR + 6,	16,	'0' },	/* 78:  c6d0-c6d15 */
	{"cs7d", COMPAQ_SMART2_MAJOR + 7,	16,	'0' },	/* 79:  c7d0-c7d15 */

	{"cc0d", COMPAQ_CISS_MAJOR,			16,	'0' },	/* 104:  c0d0-c0d15 */
	{"cc1d", COMPAQ_CISS_MAJOR + 1,		16,	'0' },	/* 105:  c1d0-c1d15 */
	{"cc2d", COMPAQ_CISS_MAJOR + 2,		16,	'0' },	/* 106:  c2d0-c2d15 */
	{"cc3d", COMPAQ_CISS_MAJOR + 3,		16,	'0' },	/* 107:  c3d0-c3d15 */
	{"cc4d", COMPAQ_CISS_MAJOR + 4,		16,	'0' },	/* 108:  c4d0-c4d15 */
	{"cc5d", COMPAQ_CISS_MAJOR + 5,		16,	'0' },	/* 109:  c5d0-c5d15 */
	{"cc6d", COMPAQ_CISS_MAJOR + 6,		16,	'0' },	/* 110:  c6d0-c6d15 */
	{"cc7d", COMPAQ_CISS_MAJOR + 7,		16,	'0' },	/* 111:  c7d0-c7d15 */

	{"fd",	FLOPPY_MAJOR,				0,	'0' }	/* 2:  fd0-fd3  */
	};

static gboolean
disk_major_ok(gint major)
	{
	gint		i;

	for (i = 0; i < sizeof(disk_name_map) / sizeof(struct _disk_name_map); ++i)
		{
		if (major == disk_name_map[i].major)
			return TRUE;
		}
	return FALSE;
	}

gchar *
gkrellm_sys_disk_name_from_device(gint device_number, gint unit_number,
			gint *order)
	{
	struct _disk_name_map	*dm	= NULL;
	gint		i;
	gchar		suffix;
	static gchar name[32];

	for (i = 0; i < sizeof(disk_name_map) / sizeof(struct _disk_name_map); ++i)
		{
		if (device_number != disk_name_map[i].major)
			continue;
		dm = &disk_name_map[i];
		break;
		}
	if (dm)
		{
		suffix = dm->suffix_base + unit_number;
		sprintf(name, "%s%c", dm->name, suffix);
		}
	else
		sprintf(name, "(%d,%d)", device_number, unit_number);
	*order = i;
	return name;
	}

gint
gkrellm_sys_disk_order_from_name(gchar *name)
	{
	struct _disk_name_map	*dm, *dm_next;
	gint		i, len, table_size;
	gchar		suffix;

	table_size = sizeof(disk_name_map) / sizeof(struct _disk_name_map);
	for (i = 0; i < table_size; ++i)
		{
		dm = &disk_name_map[i];
		len = strlen(dm->name);
		if (strncmp(dm->name, name, len))
			continue;
		suffix = name[len];			/* So far looked at only for "hd" series */
		if (i < table_size - 1)
			{
			dm_next = &disk_name_map[i + 1];
			if (   !strcmp(dm_next->name, dm->name)
				&& dm_next->suffix_base <= suffix
			   )
				continue;
			}
		break;
		}
	if (i >= table_size)
		i = -1;
	return i;
	}

  /* Given a /proc/partitions or /proc/diskstats line disk name in "partition",
  |  make "disk" have the whole disk name (eg hda) and "partition" have the
  |  partition (eg hda1) or NULL if not a partition.  For simple names,
  |  "disk" is expected to initially have the whole disk name from the
  |  previous call (or NULL if this is the first call per /proc file parse).
  */
static gboolean
disk_get_device_name(gint major, gint minor, gchar *disk, gchar *partition)
	{
	struct _disk_name_map	*dm	= NULL;
	gint					i, unit = 0;
	gchar					*p, *d;
		
	for (p = partition; *p; ++p)
		if (*p == '/')
			break;
	if (!*p)
		{	/* Have a simple name like hda, hda1, sda, ... */
		d = disk;
		p = partition;
		while (*d && *p && *d++ == *p++)
			;
		if (d == disk || *d || *p < '0' || *p > '9')
			{
			strcpy(disk, partition);
			partition[0] = '\0';
			}
		return TRUE;
		}

	/* Have a devfs name like ide/host0/bus0/target0/lun0/part1, so construct
	|  a name based on major, minor numbers and the disk_name_map[].
	*/
	for (i = 0; i < sizeof(disk_name_map) / sizeof(struct _disk_name_map); ++i)
		{
		if (major != disk_name_map[i].major)
			continue;
		dm = &disk_name_map[i];
		if (dm->minor_mod > 0 && minor >= dm->minor_mod)
			{
			unit = minor / dm->minor_mod;
			minor = minor % dm->minor_mod;
			}
		sprintf(disk, "%s%c", dm->name, dm->suffix_base + unit);
		if (minor > 0)
			sprintf(partition, "%s%d", disk, minor);
		else
			partition[0] = '\0';
		return TRUE;
		}
	return FALSE;
	}

  /* Kernels >= 2.5.69 have /proc/diskstats which can be more efficient to
  |  read than getting stats from sysfs.  See:
  |      /usr/src/linux/Documentation/iostats.txt
  |  But gkrellm calls this only for 2.6+ kernels since there were some
  |  format incompatible /proc/diskstats patches for 2.4.
  */
static void
linux_read_proc_diskstats(void)
	{
	static FILE		*f;
	gchar			buf[1024], part[64], disk[64];
	gint			major, minor, n;
	gulong			rd, wr, rd1, wr1;
	gboolean		inactivity_override;
	static gboolean	initial_read = TRUE;

	if (!f && (f = fopen(PROC_DISKSTATS_FILE, "r")) == NULL)
		return;

	disk[0] = '\0';
	part[0] = '\0';
		
	while ((fgets(buf, sizeof(buf), f)) != NULL)
		{
		/* major minor name rio rmerge rsect ruse wio wmerge wsect wuse
		|               running use aveq
		|  --or for a partition--
		|  major minor name rio rsect wio wsect
		*/
		n = sscanf(buf, "%d %d %64s %*d %lu %lu %lu %*d %*d %lu",
					&major, &minor, part, &rd, &rd1, &wr, &wr1);
		if (n == 7)
			{
			rd = rd1;
			wr = wr1;
			}

		/* Make sure all real disks get reported (so they will be added to the
		|  disk monitor in order) the first time this function is called.
		|  Use disk_major_ok() instead of simply initial_read until I'm sure
		|  I'm testing for all the right "major" exclusions.
		|  Note: disk_get_device_name() assumes "part[]" retains results from
		|  previous calls and that disk/subdisk parsing will be in order
		|  (ie hda will be encountered before hda1).
		*/
		inactivity_override = initial_read ? disk_major_ok(major) : FALSE;

		if (   (n != 7 && n != 6)
			|| (rd == 0 && wr == 0 && !inactivity_override)
			|| major == LVM_BLK_MAJOR || major == NBD_MAJOR
			|| major == RAMDISK_MAJOR || major == LOOP_MAJOR
			|| !disk_get_device_name(major, minor, disk, part)
		   )
			continue;
		if (part[0] == '\0')
			gkrellm_disk_assign_data_by_name(disk, 512 * rd, 512 * wr);
		else
			gkrellm_disk_subdisk_assign_data_by_name(part, disk,
						512 * rd, 512 * wr);
		}
	rewind(f);
	initial_read = FALSE;
	}

  /* /proc/partitions can have diskstats in 2.4 kernels or in 2.5+ it's just
  |  a list of disk names which can be used to get names to look for in sysfs.
  |  But, with gkrellm 2.1.15 and for 2.6+kernels /proc/diskstats is
  |  used instead of sysfs.
  */
static void
linux_read_proc_partitions_or_sysfs(void)
	{
	FILE		*f, *sf;
	gchar		buf[1024], part[64], disk[64], sysfspath[128];
	gint		major, minor, n;
	gulong		sectors, rd, wr;

	if ((f = fopen(PROC_PARTITIONS_FILE, "r")) != NULL)
		{
		fgets(buf, sizeof(buf), f); /* header */
		fgets(buf, sizeof(buf), f); 
		disk[0] = '\0';
		part[0] = '\0';
		
		while ((fgets(buf, sizeof(buf), f)) != NULL)
			{
			if (have_partition_stats)
				{
				/* major minor  #blocks  name
				|  rio rmerge rsect ruse wio wmerge wsect wuse running use aveq
				*/
				n = sscanf(buf, "%d %d %lu %64s %*d %*d %lu %*d %*d %*d %lu",
							&major, &minor, &sectors, part, &rd, &wr);
				if (   n < 6 || sectors <= 1 || major == LVM_BLK_MAJOR
					|| !disk_get_device_name(major, minor, disk, part)
				   )
					continue;
				}
			if (have_sysfs_stats)
				{
				n = sscanf(buf, "%d %d %lu %64s",
							&major, &minor, &sectors, part);
				if (   n < 4 || sectors <= 1 || major == LVM_BLK_MAJOR
					|| !disk_get_device_name(major, minor, disk, part)
				   )
					continue;
				if (part[0] == '\0')
					sprintf(sysfspath, "/sys/block/%s/stat", disk);
				else
					sprintf(sysfspath, "/sys/block/%s/%s/stat", disk, part);
				if ((sf = fopen(sysfspath, "r")) != NULL)
					{
					fgets(buf, sizeof(buf), sf);
					fclose(sf);
					if (part[0] == '\0')
						n = sscanf(buf,"%*d %*d %lu %*d %*d %*d %lu",&rd, &wr);
					else
						n = sscanf(buf,"%*d %lu %*d %lu", &rd, &wr);
					if (n < 2)
						continue;
					}
				}
			if (part[0] == '\0')
				gkrellm_disk_assign_data_by_name(disk, 512 * rd, 512 * wr);
			else
				gkrellm_disk_subdisk_assign_data_by_name(part, disk,
							512 * rd, 512 * wr);
			}
		fclose(f);
		}
	}

void
gkrellm_sys_disk_read_data(void)
	{
	/* If have_partition_stats, still need to get floppy and CDrom data
	|  from /proc/stat
	*/
	if (!have_sysfs_stats && !have_diskstats)
		linux_read_proc_stat();	

	if (have_diskstats)
		linux_read_proc_diskstats();
	else if (have_partition_stats || have_sysfs_stats)
		linux_read_proc_partitions_or_sysfs();
	}

gboolean
gkrellm_sys_disk_init(void)
	{
	FILE	*f = NULL;
	gchar	buf[1024];

	/* There were some incompatible /proc/diskstats patches for 2.4
	*/
	if (os_release(2,6,0) && (f = fopen(PROC_DISKSTATS_FILE, "r")) != NULL)
		have_diskstats = TRUE;
	else if ((f = fopen(PROC_PARTITIONS_FILE, "r")) != NULL)
		{
		if (fgets(buf, sizeof(buf), f))
			{
			if (strstr(buf, "rsect"))
				have_partition_stats = TRUE;
			else 
				{
				if (have_sysfs)
					have_sysfs_stats = TRUE;
				}
			}
		}
	if (f)
		fclose(f);
	if (_GK.debug_level & DEBUG_SYSDEP)
		printf("diskstats=%d partition_stats=%d sysfs_stats=%d\n",
			have_diskstats, have_partition_stats, have_sysfs_stats);

	return TRUE;
	}


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

#include <utmp.h>
#include <paths.h>

#define	PROC_LOADAVG_FILE	"/proc/loadavg"

void
gkrellm_sys_proc_read_data(void)
	{
	FILE	*f;
	gchar	buf[160];
	gint	n_running = 0, n_processes = 0;
	gulong	n_forks = 0;
	gfloat	fload = 0;

	if ((f = fopen(PROC_LOADAVG_FILE, "r")) != NULL)
		{
		/* sscanf(buf, "%f") might fail to convert because for some locales
		|  commas are used for decimal points.
		*/
		fgets(buf, sizeof(buf), f);
		if (need_locale_fix)
			locale_fix(buf);
		sscanf(buf,"%f %*f %*f %d/%d %lu", &fload,
						&n_running, &n_processes, &n_forks);
		fclose(f);
		gkrellm_proc_assign_data(n_processes, n_running, n_forks, fload);
		}
	}

void
gkrellm_sys_proc_read_users(void)
	{
	struct utmp		*ut;
	struct stat		s;
	static time_t	utmp_mtime;
	gint			n_users = 0;

	if (stat(_PATH_UTMP, &s) == 0 && s.st_mtime != utmp_mtime)
		{
		setutent();
		while ((ut = getutent()) != NULL)
			if (ut->ut_type == USER_PROCESS && ut->ut_name[0] != '\0')
				++n_users;
		endutent();
		utmp_mtime = s.st_mtime;
		gkrellm_proc_assign_users(n_users);
		}
	}

gboolean
gkrellm_sys_proc_init(void)
	{
	struct lconv	*lc;

	lc = localeconv();
	locale_decimal_point = *lc->decimal_point;
	if (locale_decimal_point != '.')
		need_locale_fix = TRUE;

	return TRUE;
	}


/* ===================================================================== */
/* Inet monitor interface */

#include "../inet.h"

#define	PROC_NET_TCP_FILE	"/proc/net/tcp"
#if defined(INET6)
#define	PROC_NET_TCP6_FILE	"/proc/net/tcp6"
#endif

void
gkrellm_sys_inet_read_tcp_data(void)
	{
	FILE		*f;
	ActiveTCP	tcp;
	gchar		buf[512];
	gint		tcp_status;
	gulong		addr;

	if ((f = fopen(PROC_NET_TCP_FILE, "r")) != NULL)
		{
		fgets(buf, sizeof(buf), f);		/* header */

		while (fgets(buf, sizeof(buf), f))
			{
			sscanf(buf, "%*d: %*x:%x %lx:%x %x", &tcp.local_port,
						&addr, &tcp.remote_port, &tcp_status);
			tcp.remote_addr.s_addr = (uint32_t) addr;
			tcp.family = AF_INET;
			if (tcp_status != TCP_ALIVE)
				continue;
			gkrellm_inet_log_tcp_port_data(&tcp);
			}
		fclose(f);
		}
#if defined(INET6)
	if ((f = fopen(PROC_NET_TCP6_FILE, "r")) != NULL)
		{
		fgets(buf, sizeof(buf), f);		/* Waste first line */
		while (fgets(buf, sizeof(buf), f))
			{
			sscanf(buf, "%*d: %*x:%x %8x%8x%8x%8x:%x %x",
			       &tcp.local_port,
			       &tcp.remote_addr6.s6_addr32[0],
			       &tcp.remote_addr6.s6_addr32[1],
			       &tcp.remote_addr6.s6_addr32[2],
			       &tcp.remote_addr6.s6_addr32[3],
			       &tcp.remote_port, &tcp_status);
			tcp.family = AF_INET6;
			if (tcp_status != TCP_ALIVE)
				continue;
			gkrellm_inet_log_tcp_port_data(&tcp);
			}
		fclose(f);
		}
#endif	/* INET6 */
	}

gboolean
gkrellm_sys_inet_init(void)
	{
	return TRUE;
	}


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

#define	PROC_NET_DEV_FILE	"/proc/net/dev"
#define	PROC_NET_ROUTE_FILE	"/proc/net/route"

typedef struct
	{
	gchar		*name;
	gboolean	cur_up,
				up;
	}
	NetUp;

static GList	*net_routed_list;

static gint			rx_bytes_index,
					tx_bytes_index,
					rx_packets_index,
					tx_packets_index;


void
gkrellm_sys_net_check_routes(void)
	{
	static FILE		*f;
	GList			*list;
	NetUp			*net;
	gchar			*s;
	gchar			buf[512];


	for (list = net_routed_list; list; list = list->next)
		((NetUp *) list->data)->cur_up = FALSE;

	if (f || (f = fopen(PROC_NET_ROUTE_FILE, "r")) != NULL)
		{
		fgets(buf, sizeof(buf), f);		/* Waste the first line */
		while (fgets(buf, sizeof(buf), f))
			{
			if (   ((s = strtok(buf, " \t\n")) == NULL)
				|| !strncmp(s, "dummy", 5)
				|| (*s == '*' && *(s+1) == '\0')
			   )
				continue;
			for (list = net_routed_list; list; list = list->next)
				{
				net = (NetUp *) list->data;
				if (!strcmp(net->name, s))
					{
					net->cur_up = TRUE;
					break;
					}
				}
			if (!list)
				{
				net = g_new0(NetUp, 1);
				net_routed_list = g_list_append(net_routed_list, net);
				net->name = g_strdup(s);
				net->cur_up = TRUE;
				}
			}
		rewind(f);
		}
	for (list = net_routed_list; list; list = list->next)
		{
		net = (NetUp *) list->data;
		if (net->up && !net->cur_up)
			gkrellm_net_routed_event(net->name, FALSE);
		else if (!net->up && net->cur_up)
			gkrellm_net_routed_event(net->name, TRUE);
		net->up = net->cur_up;
		}
	}

  /* I read both the bytes (kernel 2.2.x) and packets (all kernels).  Some
  |  net drivers for 2.2.x do not update the bytes counters.
  */
void
gkrellm_sys_net_read_data(void)
	{
	static FILE	*f;
	gchar		buf[512];
	gchar		*name, *s, *s1;
	gint		i;
	gulong		rx, tx;
	gulong		rx_packets	= 0,
				tx_packets	= 0;
	guint64		ll;

	if (!f && (f = fopen(PROC_NET_DEV_FILE, "r")) == NULL)
		return;
	fgets(buf, sizeof(buf), f);		/* 2 line header */
	fgets(buf, sizeof(buf), f);
	while (fgets(buf, sizeof(buf), f))
		{
		/* Virtual net interfaces have a colon in the name, and a colon seps
		|  the name from data, + might be no space between data and name!
		|  Eg. this is possible -> eth2:0:11249029    0 ...
		|  So, replace the colon that seps data from the name with a space.
		*/
		s = strchr(buf, (int) ':');
		if (s)
			{
			s1 = strchr(s + 1, (int) ':');
			if (s1)
				*s1 = ' ';
			else
				*s = ' ';
			}
		if ((name = strtok(buf, " \t\n")) == NULL)	/* Get name of interface */
			{
			fclose(f);
			f = NULL;
			return;
			}
		if (!strncmp(name, "dummy", 5))
			continue;
		rx = tx = 0;
		for (i = 1; (s = strtok(NULL, " \t\n")) != NULL; ++i)
			{
			if (i == rx_bytes_index)
				{
				rx = strtoul(s, NULL, 0);

				/* Can have 32 bit library / 64 bit kernel mismatch.
				|  If so, just using the 32 low bits of a long long is OK.
				*/
				if (   rx == ULONG_MAX && errno == ERANGE
					&& sscanf(s, "%Ld", &ll) == 1
				   )
					rx = (gulong) ll;
				}
			else if (i == tx_bytes_index)
				{
				tx = strtoul(s, NULL, 0);
				if (   tx == ULONG_MAX && errno == ERANGE
					&& sscanf(s, "%Ld", &ll) == 1
				   )
					tx = (gulong) ll;
				}
			else if (i == rx_packets_index)
				rx_packets = strtoul(s, NULL, 0);
			else if (i == tx_packets_index)
				tx_packets = strtoul(s, NULL, 0);
			if (i > tx_bytes_index && i > tx_packets_index)
				break;
			}
		if (rx == 0 && tx == 0)
			{
			rx = rx_packets;
			tx = tx_packets;
			}
		gkrellm_net_assign_data(name, rx, tx);
		}
	rewind(f);
	}

gboolean
gkrellm_sys_net_isdn_online(void)
	{
	gint	f = 0;
	gchar	buffer[BUFSIZ], *p, *end;

	if (   (f = open("/dev/isdninfo", O_RDONLY)) == -1
		&& (f = open("/dev/isdn/isdninfo", O_RDONLY)) == -1
	   ) 
		{
		if (_GK.debug_level & DEBUG_NET)
			printf("sys_net_isdn__online: no /dev/isdninfo?\n");
		return FALSE;
		}
	memset(buffer, 0, BUFSIZ);

	if (read(f, buffer, BUFSIZ) <= 0)
		{
		close(f);
		return FALSE;
		}
	close(f);

	if ((p = strstr(buffer, "flags:")) == NULL)
		return FALSE;

	for(p += 6; *p; )
		{
		if (isspace(*p))
			{
			p++;
			continue;
			}
		for	(end = p; *end && !isspace(*end); end++)
			;
		if (*end == '\0' || *end == '\t')
			break;
		else
			*end = 0;
		if (!strcmp(p, "?") || !strcmp(p, "0"))
			{
			p = end+1;
			continue;
			}
		return TRUE;	/* ISDN is online */
		}
	return FALSE;	/* ISDN is off line */
	}

static const char	*delim	= " :|\t\n";

static void
get_io_indices(void)
	{
	FILE	*f;
	gchar	*s;
	gchar	buf[184];
	gint	i;

	if ((f = fopen(PROC_NET_DEV_FILE, "r")))
		{
		fgets(buf, sizeof(buf), f);		/* Waste the first line.	*/
		fgets(buf, sizeof(buf), f);		/* Look for "units" in this line */
		s = strtok(buf, delim);
		for (i = 0; s; ++i)
			{
			if (strcmp(s, "bytes") == 0)
				{
				if (rx_bytes_index == 0)
					rx_bytes_index = i;
				else
					tx_bytes_index = i;
				}
			if (strcmp(s, "packets") == 0)
				{
				if (rx_packets_index == 0)
					rx_packets_index = i;
				else
					tx_packets_index = i;
				}
			s = strtok(NULL, delim);
			}
		fclose(f);
		}
	if (_GK.debug_level & DEBUG_NET)
		printf(_("rx_bytes=%d tx_bytes=%d rx_packets=%d tx_packets=%d\n"),
				rx_bytes_index, tx_bytes_index,
				rx_packets_index, tx_packets_index);
	}

gboolean
gkrellm_sys_net_init(void)
	{
	get_io_indices();
	gkrellm_net_set_lock_directory("/var/lock");
	gkrellm_net_add_timer_type_ppp("ppp0");
	gkrellm_net_add_timer_type_ippp("ippp0");
	gkrellm_net_use_routed(TRUE /* Always TRUE from sysdep code */);
	return TRUE;
	}


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

#define	PROC_MEMINFO_FILE	"/proc/meminfo"
#define	PROC_VMSTAT_FILE	"/proc/vmstat"

static guint64	swap_total, swap_used;

  /* Kernels >= 2.5.x have tagged formats only in kb units.
  */
static void
tagged_format_meminfo(gchar *buf, guint64 *mint)
	{
	sscanf(buf,"%Lu", mint);
	*mint *= 1024;
	}

void
gkrellm_sys_mem_read_data(void)
	{
	FILE		*f;
	gchar		buf[160];
	gboolean	using_tagged = FALSE;
	guint64		total, used, x_used, free, shared, buffers, cached;

	/* Default  0, so we don't get arbitrary values if not found,
	|  e.g. MemShared is not present in 2.6.
	*/
	total = used = x_used = free = shared = buffers = cached = 0LL;

	if ((f = fopen(PROC_MEMINFO_FILE, "r")) == NULL)
		return;
	while ((fgets(buf, sizeof(buf), f)) != NULL)
		{
		if (buf[0] == 'M')
			{
			if (!strncmp(buf, "Mem:", 4))
				sscanf(buf + 5, "%Lu %Lu %Lu %Lu %Lu %Lu",
						&total, &x_used, &free,
						&shared, &buffers, &cached);
			else if (!strncmp(buf, "MemTotal:", 9))
				{
				tagged_format_meminfo(buf + 10, &total);
				using_tagged = TRUE;
				}
			else if (!strncmp(buf, "MemFree:", 8))
				tagged_format_meminfo(buf + 9, &free);
			else if (!strncmp(buf, "MemShared:", 10))
				tagged_format_meminfo(buf + 11, &shared);
			}
		else if (buf[0] == 'S')
			{
			if (!strncmp(buf, "Swap:", 5))
				sscanf(buf + 6,"%Lu %Lu", &swap_total, &swap_used);
			else if (!strncmp(buf, "SwapTotal:", 10))
				tagged_format_meminfo(buf + 11, &swap_total);
			else if (!strncmp(buf, "SwapFree:", 9))
				tagged_format_meminfo(buf + 10, &swap_used);
			}
		else if (buf[0] == 'B' && !strncmp(buf, "Buffers:", 8))
			tagged_format_meminfo(buf + 9, &buffers);
		else if (buf[0] == 'C' && !strncmp(buf, "Cached:", 7))
			tagged_format_meminfo(buf + 8, &cached);
		}
	fclose(f);
	if (using_tagged)
		{
		x_used = total - free;
		swap_used = swap_total - swap_used;
		}
	used = x_used - buffers - cached;
	gkrellm_mem_assign_data(total, used, free, shared, buffers, cached);
	}

  /* Kernel >= 2.6 swap page in/out is in /proc/vmstat.
  |  Kernel <= 2.4 swap page in/out is in /proc/stat read in read_proc_stat().
  |  Swap total/used for all kernels is read in mem_read_data() above.
  */
void
gkrellm_sys_swap_read_data(void)
	{
	static FILE		*f;
	gchar			buf[128];

	if (!f && kernel_2_6)
		f = fopen(PROC_VMSTAT_FILE, "r");

	if (f)
		{
		while (fgets(buf, sizeof(buf), f) != NULL)
			{
			if (buf[0] != 'p' || buf[1] != 's')
				continue;
			sscanf(buf, "pswpin %lu", &swapin);
			if (fgets(buf, sizeof(buf), f) == NULL)
				break;
			sscanf(buf, "pswpout %lu", &swapout);
			}
		rewind(f);
		}
	gkrellm_swap_assign_data(swap_total, swap_used, swapin, swapout);
	}

gboolean
gkrellm_sys_mem_init(void)
	{
	return TRUE;
	}


/* ===================================================================== */
/* FS monitor interface */

#include <sys/vfs.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <linux/cdrom.h>
#include <mntent.h>

#define	PROC_MOUNTS_FILE	"/proc/mounts"

  /* A list of mounted file systems can be read from /proc/mounts or
  |  /etc/mtab (getmntent).  Using /proc/mounts eliminates disk accesses,
  |  but for some reason /proc/mounts reports a "." for the mounted
  |  directory for smbfs types.  So I use /proc/mounts with a fallback
  |  to using getmntent().
  */
#if !defined (_PATH_MOUNTED)
#define _PATH_MOUNTED   "/etc/mtab"
#endif


static void
fix_fstab_name(gchar *buf)
	{
	gchar	*rp,
			*wp;

	if (buf[0] == '\0')
		return;
	rp = buf;
	wp = buf;
	do	/* This loop same as in libc6 getmntent()	*/
		if (rp[0] == '\\' && rp[1] == '0' && rp[2] == '4' && rp[3] == '0')
			{
			*wp++ = ' ';		/* \040 is a SPACE.  */
			rp += 3;
			}
		else if (rp[0] == '\\' && rp[1] == '0' && rp[2] == '1' && rp[3] == '2')
			{
			*wp++ = '\t';		/* \012 is a TAB.  */
			rp += 3;
			}
		else if (rp[0] == '\\' && rp[1] == '\\')
			{
			*wp++ = '\\';		/* \\ is a \	*/
			rp += 1;
			}
		else
			*wp++ = *rp;
	while (*rp++ != '\0');
	}

gboolean
gkrellm_sys_fs_fstab_modified(void)
	{
	struct stat		s;
	static time_t	fstab_mtime;
	gint			modified = FALSE;

	if (stat("/etc/fstab", &s) == 0 && s.st_mtime != fstab_mtime)
		modified = TRUE;
	fstab_mtime = s.st_mtime;
	return modified;
	}

void
gkrellm_sys_fs_get_fstab_list(void)
	{
	FILE	*f;
	gchar	buf[1024], *s;
	gchar	dev[64], dir[128], type[64], opt[128];

	if ((f = fopen("/etc/fstab", "r")) == NULL)
		return;
	while (fgets(buf, sizeof(buf), f))
		{
		s = buf;
		while (*s == ' ' || *s == '\t')
			++s;
		if (*s == '\0' || *s == '#' || *s == '\n')
			continue;
		dev[0] = dir[0] = type[0] = opt[0] = '\0';
		sscanf(s, "%64s %128s %64s %128s", dev, dir, type, opt);
		fix_fstab_name(dev);
		fix_fstab_name(dir);
		fix_fstab_name(type);
		fix_fstab_name(opt);

		if (   type[0] == '\0'
			|| !strcmp(type, "devpts")
			|| !strcmp(type, "swap")
			|| !strcmp(type, "proc")
			|| !strcmp(type, "sysfs")
			|| !strcmp(type, "usbdevfs")
			|| !strcmp(type, "usbfs")
			|| !strcmp(type, "ignore")
		   )
			continue;
		gkrellm_fs_add_to_fstab_list(dir, dev, type, opt);
		}
	fclose(f);
	}

static void
getmntent_fallback(gchar *dir, gint dirsize, gchar *dev)
	{
	FILE			*f;
	struct mntent	*mnt;

	if ((f = setmntent(_PATH_MOUNTED, "r")) == NULL)
		return;
	while ((mnt = getmntent(f)) != NULL)
		{
		if (!strcmp(dev, mnt->mnt_fsname))
			{
			snprintf(dir, dirsize, "%s", mnt->mnt_dir);
			break;
			}
		}
	endmntent(f);
	}

void
gkrellm_sys_fs_get_mounts_list(void)
	{
	FILE		*f;
	gchar		*s, buf[128], dev[64], dir[128], type[32];

	if ((f = fopen(PROC_MOUNTS_FILE, "r")) == NULL)
		return;
	while (fgets(buf, sizeof(buf), f))
		{
		sscanf(buf, "%63s %127s %31s", dev, dir, type);
		if (   !strcmp(type, "devpts")
			|| !strcmp(type, "proc")
			|| !strcmp(type, "usbdevfs")
			|| !strcmp(type, "usbfs")
			|| !strcmp(type, "sysfs")
		   )
			continue;
		/* Strip trailing / from the directory.
		*/
		s = strrchr(dir, (int) '/');
		if (s && s != dir && *(s+1) == '\0')
			*s = '\0';
		if (dir[0] == '.')
			getmntent_fallback(dir, sizeof(dir), dev);
		gkrellm_fs_add_to_mounts_list(dir, dev, type);
		}
	fclose(f);
	}

void
gkrellm_sys_fs_get_fsusage(gpointer fs, gchar *dir)
	{
	struct statfs	st;

	if (!statfs(dir, &st))
		gkrellm_fs_assign_fsusage_data(fs,
					(glong) st.f_blocks, (glong) st.f_bavail,
					(glong) st.f_bfree, (glong) st.f_bsize);
	else
		gkrellm_fs_assign_fsusage_data(fs, 0, 0, 0, 0);
	}


static void
eject_linux_cdrom(gchar *device)
	{
#if defined(CDROMEJECT)
	gint	d;

	if ((d = open(device, O_RDONLY|O_NONBLOCK)) >= 0)
		{
		ioctl(d, CDROMEJECT);
		close(d);
		}
#endif
	}

gboolean
gkrellm_sys_fs_init(void)
	{
	gchar	*eject_command = NULL,
			*close_command = NULL;

#if defined(WEXITSTATUS)
	gint	n;

	n = system("eject -d > /dev/null 2>&1");
	if (WEXITSTATUS(n) == 0)
		{
		eject_command = "eject %s";
		close_command = "eject -t %s";
		}
#endif
	gkrellm_fs_setup_eject(eject_command, close_command,
				eject_linux_cdrom, NULL);
	return TRUE;
	}

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

/* ---------------------------- */
/* ACPI battery interface		*/

#define	ACPI_BATTERY_DIR			"/proc/acpi/battery/"
#define	ACPI_AC_ADAPTOR_DIR			"/proc/acpi/ac_adapter/"

typedef struct
	{
	gint		id;
	gchar		*info,
				*state;
	gint		full_cap;
	gboolean	got_full_cap,
				full_cap_bug;
	}
	BatteryFile;

static gchar	*acpi_ac_state_file;
static GList	*acpi_battery_list;

#if !defined(F_OK)
#define	F_OK	0
#endif

static gchar *
get_acpi_battery_file(gchar *dir, gchar *subdir, gchar *name)
	{
	gchar 	*path;

	/* dir is expected to have trailing '/'
	*/
	path = g_strconcat(dir, subdir, "/", name, NULL);
	if (!access(path, F_OK))
		return path;
	g_free(path);
	return NULL;
	}

static gboolean
setup_acpi_battery(gchar *bat)
	{
	BatteryFile	*bf; 
	gchar		*info;
	static gint	id;

	info = g_strconcat(ACPI_BATTERY_DIR, bat, "/info", NULL);
	if (_GK.debug_level & DEBUG_BATTERY)
		printf("setup_acpi_battery: %s\n", info);
	if (!access(info, F_OK))
		{
		bf = g_new0(BatteryFile, 1);
		bf->id = id++;
		bf->info = info;
		bf->state = get_acpi_battery_file(ACPI_BATTERY_DIR, bat, "state");
		if (!bf->state)
			bf->state = get_acpi_battery_file(ACPI_BATTERY_DIR, bat, "status");
		acpi_battery_list = g_list_append(acpi_battery_list, bf);
		if (_GK.debug_level & DEBUG_BATTERY)
			printf("setup_acpi_battery: %s\n",
						bf->state ? bf->state : "no state");
		return TRUE;
		}
	g_free(info);
	return FALSE;
	}

static gboolean
setup_ac_adapter(gchar **state, gchar *ac)
	{
	gchar	*path;

	path = get_acpi_battery_file(ACPI_AC_ADAPTOR_DIR, ac, "state");
	if (!path)
		path = get_acpi_battery_file(ACPI_AC_ADAPTOR_DIR, ac, "status");
	*state = path;
	if (_GK.debug_level & DEBUG_BATTERY)
		printf("setup_ac_adaptor: %s\n", path ? path : "no state");

	return path ? TRUE : FALSE;
	}

static void
acpi_setup(void)
	{
	DIR				*d;
	struct dirent	*de;

	if ((d = opendir(ACPI_BATTERY_DIR)) == NULL)
		return;
	
	while ((de = readdir(d)) != NULL)
		{
		if (   strcmp(de->d_name, ".")
			&& strcmp(de->d_name, "..")
		   )
			setup_acpi_battery(de->d_name);
		}
	closedir(d);

	if (!acpi_battery_list)
		return;
	
	if ((d = opendir(ACPI_AC_ADAPTOR_DIR)) != NULL)
		{
		while ((de = readdir(d)) != NULL)
			{
			if (   strcmp(de->d_name, ".")
				&& strcmp(de->d_name, "..")
				&& setup_ac_adapter(&acpi_ac_state_file, de->d_name)
			   )
				break;
			}
		closedir(d);
		}
	}

static gboolean
fgets_lower_case(gchar *buf, gint len, FILE *f)
	{
	guchar	*s;

	if (!fgets(buf, len, f))
		return FALSE;
	s = (guchar *) buf;
	if (isupper(*s))
		while (*s)
			{
			if (isupper(*s))
				*s = tolower(*s);
			++s;
			}
	return TRUE;
	}

static gboolean
acpi_battery_data(BatteryFile *bf)
	{
	FILE			*f;
	gchar			buf[128], s1[32];
	gboolean		on_line, charging, available, have_charging_state;
	gint			percent = 0, time_left = -1;
	gint			cur_cap = 0, cur_rate = 0;
	extern gint		gkrellm_battery_full_cap_fallback(void);

	if (!bf->got_full_cap)	/* get battery capacity */
		{
		if (_GK.debug_level & DEBUG_BATTERY)
			printf("getting full capacity: %s\n", bf->info);
		if ((f = fopen(bf->info, "r")) == NULL)
			return FALSE;
		bf->got_full_cap = TRUE;
		while (fgets_lower_case(buf, sizeof(buf), f))
			{
			/* present:					{yes|no}
			|  design capacity:			53280 mWh
			|  last full capacity:		51282 mWh
			|  battery technology:		rechargeable
			|  design voltage:			14800 mV
			|  design capacity warning:	5120 mWh
			|  design capacity low:		0 mWh
			|  capacity granularity 1:	1480 mWh
			|  capacity granularity 2:	1480 mWh
			|  model number:			6000
			|  serial number:			1
			|  battery type:			4
			|  OEM info:				XXXX
			*/
			if (sscanf(buf, "design capacity: %d", &bf->full_cap) == 1)
				if (_GK.debug_level & DEBUG_BATTERY)
					printf("%s: %d <- %s", bf->info, bf->full_cap, buf);
			if (sscanf(buf, "last full capacity: %d", &bf->full_cap) == 1)
				if (_GK.debug_level & DEBUG_BATTERY)
					printf("%s: %d <- %s", bf->info, bf->full_cap, buf);
			}
		fclose(f);
		}
	if (bf->full_cap == 0)
		{
		bf->full_cap = gkrellm_battery_full_cap_fallback();
		bf->full_cap_bug = TRUE;
		}

	on_line = FALSE;

	if (   acpi_ac_state_file
		&& (f = fopen(acpi_ac_state_file, "r")) != NULL
	   )
		{
		while (fgets_lower_case(buf, sizeof(buf), f))
			{
			/*	state: {on-line|off-line}
			*/
			if (   (   sscanf (buf, "state: %s", s1) == 1
					|| sscanf (buf, "status: %s", s1) == 1
				   )
				&& !strcmp(s1, "on-line")
			   )
				on_line = TRUE;
			}
		fclose(f);
		}

	if ((f = fopen(bf->state, "r")) == NULL)
		return FALSE;
	charging = FALSE;
	available = FALSE;
	have_charging_state = FALSE;
	while (fgets_lower_case(buf, sizeof(buf), f))
		{
		/*	present:			{yes|no}
		|	capacity state:		ok
		|	charging state:		{charging|discharging|unknown}
		|	present rate:		15000 mW
		|	remaining capacity:	31282 mWh
		|	present voltage:	16652 mV
		*/
		if (sscanf(buf, "charging state: %s", s1) == 1)
			{
			have_charging_state = TRUE;
			if (   (!strcmp(s1, "unknown") && on_line)
				|| !strcmp(s1, "charging")
			   )
				charging = TRUE;
			continue;
			}
		if (sscanf(buf, "remaining capacity: %d", &cur_cap) == 1)
			{
			if (_GK.debug_level & DEBUG_BATTERY)
				printf("%s: %d <- %s", bf->state, cur_cap, buf);
			continue;
			}
		if (sscanf(buf, "present rate: %d", &cur_rate) == 1)
			continue;
		if (   sscanf(buf, "present: %s", s1) == 1
			&& !strcmp(s1, "yes")
		   )
			available = TRUE;
		}
	fclose(f);

	if (_GK.debug_level & DEBUG_BATTERY)
		printf(
			"Battery: on_line=%d charging=%d have_charging=%d state_file=%d\n",
			on_line, charging, have_charging_state,
			acpi_ac_state_file ? TRUE : FALSE);

	if (!acpi_ac_state_file && charging)	/* Assumption for buggy ACPI */
		on_line = TRUE;

	if (!have_charging_state && on_line)	/* Another buggy ACPI */
		charging = TRUE;

	if (charging)
		bf->got_full_cap = FALSE;			/* reread full_cap */

	percent = cur_cap * 100 / bf->full_cap;
	if (percent > 100)
		{
		percent = 100;
		if (bf->full_cap_bug)
			bf->full_cap = cur_cap;
		else
			bf->got_full_cap = FALSE;			/* reread full_cap */
		}

	if (cur_rate > 0)
		{
		if (charging)
			time_left = 60 * (bf->full_cap - cur_cap) / cur_rate;
		else
			time_left = 60 * cur_cap / cur_rate;
		}

	if (_GK.debug_level & DEBUG_BATTERY)
		{
		printf("Battery %d: percent=%d time_left=%d cur_cap=%d full_cap=%d\n",
				bf->id, percent, time_left, cur_cap, bf->full_cap);
		printf("            available=%d on_line=%d charging=%d fc_bug=%d\n",
			available, on_line, charging, bf->full_cap_bug);
		}
	gkrellm_battery_assign_data(bf->id, available, on_line, charging,
				percent, time_left);
	return TRUE;
	}

/* ---------------------------- */
/* APM battery interface		*/

#define	PROC_APM_FILE				"/proc/apm"

/* From: arch/i386/kernel/apm.c
|
| 0) Linux driver version (this will change if format changes)
| 1) APM BIOS Version.  Usually 1.0, 1.1 or 1.2.
| 2) APM flags from APM Installation Check (0x00):
|	bit 0: APM_16_BIT_SUPPORT
|	bit 1: APM_32_BIT_SUPPORT
|	bit 2: APM_IDLE_SLOWS_CLOCK
|	bit 3: APM_BIOS_DISABLED
|	bit 4: APM_BIOS_DISENGAGED
| 3) AC line status
|	0x00: Off-line
|	0x01: On-line
|	0x02: On backup power (BIOS >= 1.1 only)
|	0xff: Unknown
| 4) Battery status
|	0x00: High
|	0x01: Low
|	0x02: Critical
|	0x03: Charging
|	0x04: Selected battery not present (BIOS >= 1.2 only)
|	0xff: Unknown
| 5) Battery flag
|	bit 0: High
|	bit 1: Low
|	bit 2: Critical
|	bit 3: Charging
|	bit 7: No system battery
|	0xff: Unknown
| 6) Remaining battery life (percentage of charge):
|	0-100: valid
|	-1: Unknown
| 7) Remaining battery life (time units):
|	Number of remaining minutes or seconds
|	-1: Unknown
| 8) min = minutes; sec = seconds
*/

#define APM_BIOS_VERSION(major, minor)	\
			(   bios_major > (major)	\
			 || (bios_major == (major) && bios_minor >= (minor))	\
			)

  /* AC line status values */
#define	APM_ON_LINE		1

  /* Battery status values */
#define	APM_CHARGING	3
#define	APM_NOT_PRESENT	4

  /* Battery flag bits	*/
#define	APM_NO_BATTERY	0x80

#define APM_UNKNOWN		0xFF


static void
apm_battery_assign(gint id, gint bios_major, gint bios_minor,
			gint ac_line_status, gint battery_status, gint battery_flag,
			gint percent, gint time_left, gchar *units)
	{
	gboolean	available, on_line, charging;

	if (   (battery_flag != APM_UNKNOWN && (battery_flag & APM_NO_BATTERY))
		|| (battery_status == APM_UNKNOWN && ac_line_status == APM_UNKNOWN)
		|| (battery_status == APM_NOT_PRESENT && APM_BIOS_VERSION(1,2))
	   )
		available = FALSE;
	else
		available = TRUE;

	if (ac_line_status != APM_UNKNOWN)
		on_line = (ac_line_status == APM_ON_LINE) ? TRUE : FALSE;
	else
		on_line = (battery_status == APM_CHARGING) ? FALSE : TRUE;

	if (battery_status != APM_UNKNOWN)
		charging= (battery_status == APM_CHARGING) ? TRUE : FALSE;
	else
		charging = (ac_line_status == APM_ON_LINE) ? TRUE : FALSE;

	if (!strcmp(units, "sec") && time_left > 0)
		time_left /= 60;

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

static gboolean
apm_battery_data(void)
	{
	FILE		*f;
	gchar		buf[128], units[32];
	gint		percent = 0, time_left = 0;
	gint		ac_line_status, battery_status, battery_flag;
	gint		bios_major, bios_minor;
	gint		id, n_batteries = 1;

	if ((f = fopen(PROC_APM_FILE, "r")) == NULL)
		return FALSE;
	fgets(buf, sizeof(buf), f);

	sscanf(buf, "%*s %d.%d %*x %x %x %x %d%% %d %32s\n",
				&bios_major, &bios_minor,
				&ac_line_status, &battery_status, &battery_flag,
				&percent, &time_left, units);

	/* If have APM dual battery patch, next line will be number of batteries.
	*/
	if (fgets(buf, sizeof(buf), f))
		sscanf(buf, "%d\n", &n_batteries);

	if (n_batteries < 2)
		apm_battery_assign(0, bios_major, bios_minor, ac_line_status,
					battery_status, battery_flag, percent, time_left, units);
	else
		{
		apm_battery_assign(GKRELLM_BATTERY_COMPOSITE_ID,
					bios_major, bios_minor, ac_line_status,
					battery_status, battery_flag, percent, time_left, units);
		while (n_batteries-- > 0 && fgets(buf, sizeof(buf), f))
			{
			sscanf(buf, "%d %x %x %d%% %d %32s\n", &id,
					&battery_status, &battery_flag,
					&percent, &time_left, units);
			apm_battery_assign(id - 1, bios_major, bios_minor, ac_line_status,
					battery_status, battery_flag, percent, time_left, units);
			}
		}

	fclose(f);
	return TRUE;
	}

void
gkrellm_sys_battery_read_data(void)
	{
	GList	*list;

	if (acpi_battery_list)
		for (list = acpi_battery_list; list; list = list->next)
			acpi_battery_data((BatteryFile *)(list->data));
	else
		apm_battery_data();
	}

gboolean
gkrellm_sys_battery_init()
	{
	acpi_setup();
	return TRUE;
	}



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

/* Calculating an uptime based on system time has a fuzzy meaning for
|  laptops since /proc/uptime does not include time system has been
|  sleeping.  So, read /proc/uptime always.
*/
time_t
gkrellm_sys_uptime_read_uptime(void)
    {
	FILE			*f;
	gulong			l	= 0;

	if ((f = fopen("/proc/uptime", "r")) != NULL)
		{
		fscanf(f, "%lu", &l);
		fclose(f);
		}
	return (time_t) l;
    }

gboolean
gkrellm_sys_uptime_init(void)
    {
	return TRUE;
    }


/* ===================================================================== */
/* Sensor monitor interface */
/* ------- Linux ------------------------------------------------------- */


#define	THERMAL_ZONE_DIR	"/proc/acpi/thermal_zone"
#define	THERMAL_DIR			"/proc/acpi/thermal"
#define	SENSORS_DIR			"/proc/sys/dev/sensors"
#define SYSFS_I2C_DIR		"/sys/bus/i2c/devices"

  /* mbmon and hddtemp sensor interfaces are handled in sensors-common.c
  */
#define	LM_SENSOR_INTERFACE			0
#define	THERMAL_INTERFACE			1
#define	THERMAL_ZONE_INTERFACE		2
#define	NVIDIA_SETTINGS_INTERFACE	3

typedef struct
	{
	gchar	*name;
	gfloat	factor;
	gfloat	offset;
	gchar	*vref;
	}
	VoltDefault;

  /* Tables of voltage correction factors and offsets derived from the
  |  compute lines in sensors.conf.  See the README file.
  */
	/* "lm78-*" "lm78-j-*" "lm79-*" "w83781d-*" "sis5595-*" "as99127f-*" */
	/* Values from LM78/LM79 data sheets	*/
static VoltDefault	voltdefault0[] =
	{
	{ "Vcor1",	1.0,    0, NULL },
	{ "Vcor2",	1.0,    0, NULL },
	{ "+3.3V",	1.0,    0, NULL },
	{ "+5V",	1.68,   0, NULL },		/* in3 ((6.8/10)+1)*@	*/
	{ "+12V",	4.0,    0, NULL },		/* in4 ((30/10)+1)*@	*/
	{ "-12V",	-4.0,   0, NULL },		/* in5 -(240/60)*@		*/
	{ "-5V",	-1.667, 0, NULL }		/* in6 -(100/60)*@		*/
	};

	/* "w83782d-*" "w83783s-*" "w83627hf-*" */
static VoltDefault	voltdefault1[] =
	{
	{ "Vcor1",	1.0,   0, NULL },
	{ "Vcor2",	1.0,   0, NULL },
	{ "+3.3V",	1.0,   0, NULL },
	{ "+5V",	1.68,  0, NULL },		/* in3 ((6.8/10)+1)*@		*/
	{ "+12V",	3.80,  0, NULL },		/* in4 ((28/10)+1)*@		*/
	{ "-12V",	5.14 , -14.91, NULL },	/* in5 (5.14 * @) - 14.91	*/
	{ "-5V",	3.14 , -7.71,  NULL },	/* in6 (3.14 * @) -  7.71	*/
	{ "V5SB",	1.68,  0, NULL },		/* in7 ((6.8/10)+1)*@		*/
	{ "VBat",	1.0,   0, NULL }
	};

	/* lm80-*	*/
static VoltDefault	voltdefault2[] =
	{
	{ "+5V",	2.633, 0, NULL },		/* in0 (24/14.7 + 1) * @	*/
	{ "VTT",	1.0,   0, NULL },
	{ "+3.3V",	1.737, 0, NULL },		/* in2 (22.1/30 + 1) * @	*/
	{ "Vcore",	1.474, 0, NULL },		/* in3 (2.8/1.9) * @		*/
	{ "+12V",	6.316, 0, NULL },		/* in4 (160/30.1 + 1) * @	*/
	{ "-12V",	5.482, -4.482, "in0" },	/* in5 (160/35.7)*(@ - in0) + @	*/
	{ "-5V",	3.222, -2.222, "in0" }	/* in6 (36/16.2)*(@ - in0) + @	*/
	};

	/* gl518sm-*	*/
static VoltDefault	voltdefault3[] =
	{
	{ "+5V",	1.0,   0, NULL },		/* vdd */
	{ "+3.3V",	1.0,   0, NULL },		/* vin1 */
	{ "+12V",	4.192, 0, NULL },		/* vin2 (197/47)*@	*/
	{ "Vcore",	1.0,   0, NULL }		/* vin3 */
	};

	/* gl520sm-*	*/
static VoltDefault	voltdefault4[] =
	{
	{ "+5V",	1.0,   0,    NULL },	/* vdd */
	{ "+3.3V",	1.0,   0,    NULL },	/* vin1 */
	{ "+12V",	4.192, 0,    NULL },	/* vin2 (197/47)*@	*/
	{ "Vcore",	1.0,   0,    NULL },	/* vin3 */
	{ "-12V",	5.0,   -4.0, "vdd" }	/* vin4 (5*@)-(4*vdd)	*/
	};

	/* via686a-*	*/
static VoltDefault	voltdefault5[] =
	{
	{ "Vcor",	1.02,  0, NULL },		/* in0 */
	{ "+2.5V",	1.0,   0, NULL },		/* in1 */
	{ "I/O",	1.02,  0, NULL },		/* in2 */
	{ "+5V",	1.009, 0, NULL },		/* in3 */
	{ "+12V",	1.04,  0, NULL }		/* in4 */
	};

	/* mtp008-*	*/
static VoltDefault	voltdefault6[] =
	{
	{ "Vcor1",	1.0,   0,       NULL },		/* in0 */
	{ "+3.3V",	1.0,   0,       NULL },		/* in1 */
	{ "+12V",	3.8,   0,       NULL },		/* in2 @ * 38 / 10*/
	{ "Vcor2",	1.0,   0,       NULL },		/* in3 */
	{ "?",		1.0,   0,       NULL },		/* in4 */
	{ "-12V",	5.143, -16.944, NULL },		/* in5 (@ * 36 - 118.61) / 7*/
	{ "Vtt",	1.0,   0,       NULL }		/* in6 */
	};

	/* adm1025-*	adm9240-*	lm87-*	lm81-* ds1780-* */
static VoltDefault	voltdefault7[] =
	{
	{ "2.5V",	1.0,   0,       NULL },		/* in0 */
	{ "Vccp",	1.0,   0,       NULL },		/* in1 */
	{ "3.3V",	1.0,   0,       NULL },		/* in2 */
	{ "5V",		1.0,   0,       NULL },		/* in3 */
	{ "12V",	1.0,   0,       NULL },		/* in4 */
	{ "Vcc",	1.0,   0,       NULL }		/* in5 */
	};

	/* it87-*	it8705-*	it8712	*/
static VoltDefault	voltdefault8[] =
	{
	{ "Vcor1",	1.0,    0, 		NULL },
	{ "Vcor2",	1.0,    0, 		NULL },
	{ "+3.3V",	2.0,    0, 		NULL },		/* in2 (1 + 1)*@		*/
	{ "+5V",	1.68,   0, 		NULL },		/* in3 ((6.8/10)+1)*@	*/
	{ "+12V",	4.0,    0, 		NULL },		/* in4 ((30/10)+1)*@	*/
	{ "-12V",	7.67,   -27.36, NULL },		/* in5 (7.67 * @) - 27.36 */
	{ "-5V",	4.33,   -13.64, NULL },		/* in6 (4.33 * @) - 13.64 */
	{ "Stby",	1.68,   0, 		NULL },		/* in7 ((6.8/10)+1)*@	*/
	{ "Vbat",	1.0,    0, 		NULL }		/* in8					*/
	};

	/* fscpos	*/
static VoltDefault	voltdefault9[] =
	{
	{ "+12V",	1.0,    0, 		NULL },
	{ "+5V",	1.0,    0, 		NULL },
	{ "+3.3V",	1.0,    0, 		NULL }
	};



gboolean
gkrellm_sys_sensors_get_temperature(gchar *sensor_path, gint id,
		gint iodev, gint interface, gfloat *temp)
	{
	FILE		*f;
	gchar		buf[64], units[32];
	gint		n;
	gfloat		T, t[5];
	gboolean	result = FALSE;

	if (   interface == THERMAL_INTERFACE
	    || interface == THERMAL_ZONE_INTERFACE
	   )
		{
		f = fopen(sensor_path, "r");
		if (f)
			{
			while (fgets(buf, sizeof(buf), f) != NULL)
				{
				if (need_locale_fix)
					locale_fix(buf);
				if ((n = sscanf(buf, "temperature: %f %31s", &T, units)) > 0)
					{
					if (n == 2 && !strcmp(units, "dK"))
						T = T / 10.0 - 273.0;
					*temp = T;
					result = TRUE;
					}
				}
			fclose(f);
			}
		return result;
		}

	if (interface == HDDTEMP_INTERFACE)
		{
		gkrellm_sys_sensors_hddtemp_check();
		return gkrellm_sys_sensors_hddtemp_get_value(sensor_path, temp);
		}

	if (interface == MBMON_INTERFACE)
		{
		gkrellm_sys_sensors_mbmon_check(FALSE);
		return gkrellm_sys_sensors_mbmon_get_value(sensor_path, temp);
		}

	if (interface == NVIDIA_SETTINGS_INTERFACE)
		{
#if GLIB_CHECK_VERSION(2,0,0)
		char *args[] = { "nvidia-settings", "-q", sensor_path, NULL };
		char *output = NULL;

		result = g_spawn_sync(NULL, args, NULL,
					G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL,
					NULL, NULL, &output, NULL, NULL, NULL);

		if(result && output)
			{
			gfloat dummy;
			if(!temp) temp = &dummy;
			result = (sscanf(output, " Attribute %*s %*s %f", temp) == 1);
			}

		g_free(output);
		return result;
#else
		return FALSE;
#endif
		}

	if ((f = fopen(sensor_path, "r")) == NULL)
		return FALSE;
	fgets(buf, sizeof(buf), f);
	fclose(f);

	if (!have_sysfs_sensors && need_locale_fix)
		locale_fix(buf);
	n = sscanf(buf, "%f %f %f %f %f", &t[0], &t[1], &t[2], &t[3], &t[4]);
	if (n < 1)
		return FALSE;
	T = t[n - 1];

	if (have_sysfs_sensors)
		T /= 1000.0;
	else if (T > 254.0)		/* Bogus read from BIOS if CHAR_MAX */
		return FALSE;

	if (temp)
		*temp = T;
	return TRUE;
	}

gboolean
gkrellm_sys_sensors_get_fan(gchar *sensor_path, gint id,
		gint iodev, gint interface, gfloat *fan)
	{
	FILE	*f;
	gchar	buf[64];
	gint	n;
	gfloat	t[4];

	if (interface == MBMON_INTERFACE)
		{
		gkrellm_sys_sensors_mbmon_check(FALSE);
		return gkrellm_sys_sensors_mbmon_get_value(sensor_path, fan);
		}

	if ((f = fopen(sensor_path, "r")) == NULL)
		return FALSE;
	fgets(buf, sizeof(buf), f);
	fclose(f);

	if (!have_sysfs_sensors && need_locale_fix)
		locale_fix(buf);
	n = sscanf(buf, "%f %f %f %f", &t[0], &t[1], &t[2], &t[3]);
	if (n < 1)
		return FALSE;

	if (fan)
		*fan = t[n - 1];
	return TRUE;
	}

gboolean
gkrellm_sys_sensors_get_voltage(gchar *sensor_path, gint id,
		gint iodev, gint interface, gfloat *volt)
	{
	FILE	*f;
	gchar	buf[64];
	gfloat	V, t[3];
	gint	n;

	if (interface == MBMON_INTERFACE)
		{
		gkrellm_sys_sensors_mbmon_check(FALSE);
		return gkrellm_sys_sensors_mbmon_get_value(sensor_path, volt);
		}

	if ((f = fopen(sensor_path, "r")) == NULL)
		return FALSE;
	fgets(buf, sizeof(buf), f);
	fclose(f);

	if (!have_sysfs_sensors && need_locale_fix)
		locale_fix(buf);
	n = sscanf(buf, "%f %f %f", &t[0], &t[1], &t[2]);
	if (n < 1)
		return FALSE;
	V = t[n - 1];

	if (have_sysfs_sensors)
		V /= 1000.0;
	else if (V > 254.0)		/* Bogus read from BIOS if CHAR_MAX */
		return FALSE;

	if (volt)
		*volt = V;
	return TRUE;
	}

static void
get_volt_default(gchar *chip_name, VoltDefault **vdf, gint *vdfsize)
	{
	if (!strncmp(chip_name, "it87", 4))
		{
		*vdf = &voltdefault8[0];
		*vdfsize = sizeof (voltdefault8) / sizeof (VoltDefault);
		}
	else if (   !strncmp(chip_name, "adm1025", 7)
			 || !strncmp(chip_name, "adm9240", 7)
			 || !strncmp(chip_name, "lm87", 4)
			 || !strncmp(chip_name, "lm81", 4)
			 || !strncmp(chip_name, "ds1780", 6)
			)
		{
		*vdf = &voltdefault7[0];
		*vdfsize = sizeof (voltdefault7) / sizeof (VoltDefault);
		}
	else if (!strncmp(chip_name, "mtp008", 6))
		{
		*vdf = &voltdefault6[0];
		*vdfsize = sizeof (voltdefault6) / sizeof (VoltDefault);
		}
	else if (!strncmp(chip_name, "via686", 6))
		{
		*vdf = &voltdefault5[0];
		*vdfsize = sizeof (voltdefault5) / sizeof (VoltDefault);
		}
	else if (!strncmp(chip_name, "gl520", 5))
		{
		*vdf = &voltdefault4[0];
		*vdfsize = sizeof (voltdefault4) / sizeof (VoltDefault);
		}
	else if (!strncmp(chip_name, "gl518", 5))
		{
		*vdf = &voltdefault3[0];
		*vdfsize = sizeof (voltdefault3) / sizeof (VoltDefault);
		}
	else if (!strncmp(chip_name, "lm80", 4))
		{
		*vdf = &voltdefault2[0];
		*vdfsize = sizeof (voltdefault2) / sizeof (VoltDefault);
		}
	else if (!strncmp(chip_name, "w83", 3) && strncmp(chip_name, "w83781", 6))
		{
		*vdf = &voltdefault1[0];
		*vdfsize = sizeof (voltdefault1) / sizeof (VoltDefault);
		}
	else if (!strncmp(chip_name, "fscpos", 6))
		{
		*vdf = &voltdefault9[0];
		*vdfsize = sizeof (voltdefault9) / sizeof (VoltDefault);
		}
	else
		{
		*vdf = &voltdefault0[0];
		*vdfsize = sizeof (voltdefault0) / sizeof (VoltDefault);
		}
	}


static gchar *
sysfs_get_chip_name(gchar *dir)
	{
	gchar	*name, buf[256], *p, *chip;
	FILE 	*f;

	name = g_strdup_printf("%s/%s", dir, "name");
	f = fopen(name, "r");
	g_free(name);
	if (!f)
		return NULL;

	buf[0] = '\0';
	fscanf(f, "%255[^\n]", buf);
	fclose(f);
	if (buf[0] == '\0')
		return NULL;

	if ((p = strchr(buf, ' ')) != NULL)		/* Remove when 2.6.0 is out */
		{									/* "w83627hf chip" -> "w83627hf" */
		*p++ = '\0';
		if (strcmp(p, "chip") && strcmp(p, "subclient"))
			return NULL;
		}

	chip = g_strdup(buf);
	for (p = chip; *p; p++)
		*p = tolower(*p);
	return chip;
	}

static void
sysfs_sensors_init(void)
	{
	GDir			*dir, *chip_dir;
	VoltDefault		*voltdefault;
	gint			id = 0;
	gint			type, voltdefaultsize;
	gfloat			factor, offset;
	gchar			*name, *bus_name, *default_label, *vref,
					*id_name,  *chip_name, *s, *d, *sensor_path;
	gchar			path[256], buf[256];

	if (!have_sysfs)
		return;

	if ((dir = g_dir_open(SYSFS_I2C_DIR, 0, NULL)) == NULL)
		return;
	while ((bus_name = (gchar *) g_dir_read_name(dir)) != NULL)
		{
		snprintf(path, sizeof(path), "%s/%s", SYSFS_I2C_DIR, bus_name);
		if ((chip_dir = g_dir_open(path, 0, NULL)) == NULL)
			continue;
		if ((chip_name = sysfs_get_chip_name(path)) == NULL)
			{
			g_dir_close(chip_dir);
			continue;
			}
		have_sysfs_sensors = TRUE;
		if (_GK.debug_level & DEBUG_SENSORS)
				printf("sysfs sensors dir: %s\n", path);

		get_volt_default(chip_name, &voltdefault, &voltdefaultsize);
		while ((name = (gchar *) g_dir_read_name(chip_dir)) != NULL)
			{
			snprintf(buf, sizeof(buf), "%s", name);
			if ((s = strstr(buf, "_input")) == NULL || s - buf > 6)
				continue;
			d = s + 6;		/* Can have xxxN_input, xxx_inputN, or xxx_input */
			while (isdigit(*d))
				*s++ = *d++;
			*s = '\0';
			while (isdigit(*(s-1)))
				--s;
			id = atoi(s);

			if (!strncmp(buf, "temp", 4))
				type = SENSOR_TEMPERATURE;
			else if (!strncmp(buf, "fan", 3))
				type = SENSOR_FAN;
			else if (!strncmp(buf, "in", 2))
				type = SENSOR_VOLTAGE;
			else
				continue;

			factor = 1.0;
			offset = 0;
			default_label = vref = NULL;

			if (type == SENSOR_VOLTAGE)
				{
				if (id < voltdefaultsize)
					{
					default_label = voltdefault[id].name;
					factor = voltdefault[id].factor;
					offset = voltdefault[id].offset;
					vref = voltdefault[id].vref;
					}
				else
					default_label =  buf;
				}
			id_name = g_strdup_printf("%s-%s/%s", chip_name, bus_name, buf);
			sensor_path = g_strdup_printf("%s/%s", path, name);
			gkrellm_sensors_add_sensor(type, sensor_path, id_name,
						id, 0, 0,
						factor, offset, vref, default_label);
			if (_GK.debug_level & DEBUG_SENSORS)
				printf("%s %s %d %d\n",
							sensor_path, id_name, id, type);
			g_free(id_name);
			g_free(sensor_path);
			}
		g_free(chip_name);
		g_dir_close(chip_dir);
		}
	g_dir_close(dir);
	}

  /* Eventually, move this function from sensors.c to here where
  |  it belongs.
  */
extern void		gkrellm_sensors_linux_name_fix(gchar *chip_name);

  /* lm_sensors can have "-isa-" or "-i2c-" embedded in /proc chip names so
  |  they are of form w83627hf-isa-0290.  Sensors from sysfs are derived from
  |  a bus directory "0-0290" and "name" file with content like "w83627hf"
  |  which leads to chip names like "w83627hf-0-0290" without the i2c or
  |  isa.  To have a chance at config compatibility between the two,
  |  remove the "i2c" and replace the "isa" with "0" in lm_sensors name
  |  when generating the id_name.  I don't want to do any munging on the
  |  sysfs names because I can't guess if the lm_sensors names had "i2c" or
  |  "isa", and besides, probably sysfs will eventually be more common
  |  so I'll just make its names the standard now (gkrellm 2.1.20).
  */
gboolean
gkrellm_sys_sensors_init(void)
	{
	GDir			*dir, *chip_dir;
	VoltDefault		*voltdefault;
	gint			id = 0;
	gint			type, voltdefaultsize;
	gfloat			factor, offset;
	gchar			*name, *chip_name, *fixed_chip_name, *path, *default_label;
	gchar			*vref, *sensor_path, *sensor_name, id_name[128];
	struct lconv	*lc;

	lc = localeconv();
	locale_decimal_point = *lc->decimal_point;
	if (locale_decimal_point != '.')
		need_locale_fix = TRUE;

	if ((dir = g_dir_open(THERMAL_ZONE_DIR, 0, NULL)) != NULL)
		{
		while ((name = (gchar *) g_dir_read_name(dir)) != NULL)
			{
			path = g_build_filename(THERMAL_ZONE_DIR, name,
						"temperature", NULL);
			if (g_file_test(path, G_FILE_TEST_IS_REGULAR))
				{
				snprintf(id_name, sizeof(id_name), "thermal_zone/%s", name);
				gkrellm_sensors_add_sensor(SENSOR_TEMPERATURE,
							path, id_name,
							id, 0, THERMAL_ZONE_INTERFACE,
							1.0, 0.0, NULL, name);
				}
			g_free(path);
			}
		g_dir_close(dir);
		}

	if ((dir = g_dir_open(THERMAL_DIR, 0, NULL)) != NULL)
		{
		while ((name = (gchar *) g_dir_read_name(dir)) != NULL)
			{
			path = g_build_filename(THERMAL_DIR, name, "status", NULL);
			if (g_file_test(path, G_FILE_TEST_IS_REGULAR))
				{
				snprintf(id_name, sizeof(id_name), "thermal/%s", name);
				gkrellm_sensors_add_sensor(SENSOR_TEMPERATURE,
							path, id_name,
							id, 0, THERMAL_INTERFACE,
							1.0, 0.0, NULL, name);
				}
			g_free(path);
			}
		g_dir_close(dir);
		}

	/* Do intial daemon reads to get sensors loaded into sensors.c
	*/
	gkrellm_sys_sensors_hddtemp_check();
	gkrellm_sys_sensors_mbmon_check(TRUE);

	if (gkrellm_sys_sensors_get_temperature("GPUCoreTemp", id, 0,
					NVIDIA_SETTINGS_INTERFACE, NULL))
		{
		gkrellm_sensors_add_sensor(SENSOR_TEMPERATURE,
					"GPUCoreTemp", "nVidia GPU Core",
					id, 0, NVIDIA_SETTINGS_INTERFACE,
					1.0, 0.0, NULL, "GPU C");
		}
	if (gkrellm_sys_sensors_get_temperature("GPUAmbientTemp", id, 0,
				NVIDIA_SETTINGS_INTERFACE, NULL))
		{
		gkrellm_sensors_add_sensor(SENSOR_TEMPERATURE,
					"GPUAmbientTemp", "nVidia GPU Ambient",
					id, 0, NVIDIA_SETTINGS_INTERFACE,
					1.0, 0.0, NULL, "GPU A");
		}

	if ((dir = g_dir_open(SENSORS_DIR, 0, NULL)) == NULL)
		{
		sysfs_sensors_init();
		return TRUE;
		}
	while ((chip_name = (gchar *) g_dir_read_name(dir)) != NULL)
		{
		fixed_chip_name = g_strdup(chip_name);
		gkrellm_sensors_linux_name_fix(fixed_chip_name);
		
		path = g_build_filename(SENSORS_DIR, chip_name, NULL);
		chip_dir = g_dir_open(path, 0, NULL);
		if (!chip_dir)
			{
			g_free(path);
			continue;
			}
		if (_GK.debug_level & DEBUG_SENSORS)
				printf("lm_sensors dir: %s\n", path);

		get_volt_default(chip_name, &voltdefault, &voltdefaultsize);
		while ((sensor_name = (gchar *) g_dir_read_name(chip_dir)) != NULL)
			{
			if (!strncmp(sensor_name, "temp", 4))
				type = SENSOR_TEMPERATURE;
			else if (   !strncmp(sensor_name, "fan", 3)
					 && (  !sensor_name[3]
						|| (isdigit(sensor_name[3]) && !sensor_name[4])
						)
					)
				type = SENSOR_FAN;
			else if (!strncmp(sensor_name, "in", 2) && isdigit(sensor_name[2]))
				{
				type = SENSOR_VOLTAGE;
				id = sensor_name[2] - '0';
				}
			else if (!strncmp(sensor_name, "vi", 2) && isdigit(sensor_name[3]))
				{
				type = SENSOR_VOLTAGE;
				id = sensor_name[3] - '0';
				}
			else if (!strcmp(sensor_name, "vdd"))
				{
				type = SENSOR_VOLTAGE;
				id = 0;
				}
			else
				continue;

			factor = 1.0;
			offset = 0;
			default_label = vref = NULL;

			if (type == SENSOR_VOLTAGE)
				{
				if (id < voltdefaultsize)
					{
					default_label = voltdefault[id].name;
					factor = voltdefault[id].factor;
					offset = voltdefault[id].offset;
					vref = voltdefault[id].vref;
					}
				else
					default_label =  sensor_name;
				}
			sensor_path = g_strdup_printf("%s/%s", path, sensor_name);
			snprintf(id_name, sizeof(id_name), "%s/%s",
						fixed_chip_name, sensor_name);
			gkrellm_sensors_add_sensor(type, sensor_path, id_name,
					id, 0, 0,
					factor, offset, vref, default_label);

			if (_GK.debug_level & DEBUG_SENSORS)
				printf("%s %s %d %d\n",
							sensor_path, id_name, id, type);
			g_free(sensor_path);
			}
		g_dir_close(chip_dir);
		g_free(path);
		g_free(fixed_chip_name);
		}
	g_dir_close(dir);
	return TRUE;
	}
