/*

    Linux-specific Information about the Hardware.

    (C) Copyright 1998-2006 by Helge Deller <deller@gmx.de>
    (C) Copyright 2006 by Jahshan Bhatti <jabhatti91@gmail.com> (CD-ROM Info)

    To do (maybe?):
    - include Information about XFree86 and/or Accelerated X
	(needs to change configure-skript, to see, if Header-files are available !)
    - maybe also include information about the video-framebuffer devices
    - rewrite detection-routines (maybe not to use the /proc-fs)
    - more & better sound-information

    /dev/sndstat support added: 1998-12-08 Duncan Haldane (f.d.m.haldane@cwix.com)

*/

#include <unistd.h>
#include <syscall.h>
#include <stdio.h>
#include <sys/stat.h>
#include <linux/kernel.h>
#include <ctype.h>
#include <config.h>

#ifdef HAVE_FSTAB_H	/* some Linux-versions don't have fstab.h */
#  include <fstab.h>
#  include <sys/statfs.h>
#  define INFO_PARTITIONS_FULL_INFO	/* show complete info */
#elif defined HAVE_MNTENT_H	/* but maybe they have mntent.h ? */
# include <mntent.h>
# include <sys/vfs.h>
#  define INFO_PARTITIONS_FULL_INFO	/* show complete info */
#else
#  undef INFO_PARTITIONS_FULL_INFO	/* no partitions-info */
#endif

#include <qregexp.h>

#include <kapplication.h>
#include <kiconloader.h>
#include <kstdguiitem.h>

#define INFO_CPU_AVAILABLE
#define INFO_CPU "/proc/cpuinfo"

#define INFO_IRQ_AVAILABLE
#define INFO_IRQ "/proc/interrupts"

#define INFO_DMA_AVAILABLE
#define INFO_DMA "/proc/dma"

#define INFO_PCI_AVAILABLE
#define INFO_PCI "/proc/pci"

#define INFO_IOPORTS_AVAILABLE
#define INFO_IOPORTS "/proc/ioports"

#define INFO_SOUND_AVAILABLE
#define INFO_DEV_SNDSTAT "/dev/sndstat"
#define INFO_SOUND "/proc/sound"
#define INFO_ASOUND "/proc/asound/oss/sndstat"
#define INFO_ASOUND09 "/proc/asound/sndstat"

#define INFO_DEVICES_AVAILABLE
#define INFO_DEVICES "/proc/devices"
#define INFO_MISC "/proc/misc"

#define INFO_SCSI_AVAILABLE
#define INFO_SCSI "/proc/scsi/scsi"

#define INFO_PARTITIONS_AVAILABLE
#define INFO_PARTITIONS "/proc/partitions"
#define INFO_MOUNTED_PARTITIONS "/etc/mtab"	/* on Linux... */

#define INFO_XSERVER_AVAILABLE

#define INFO_CD_ROM_AVAILABLE
#define INFO_CD_ROM "/proc/sys/dev/cdrom/info"	/* Feature 47242 */


#define MAXCOLUMNWIDTH 600

bool GetInfo_ReadfromFile(QListView * lbox, const char *FileName,
			  const QChar& splitChar,
			  QListViewItem * lastitem = 0,
			  QListViewItem ** newlastitem = 0)
{
    bool added = false;
    QFile file(FileName);

    if (!file.exists()) {
	return false;
    }

    if (!file.open(IO_ReadOnly)) {
	/*   *GetInfo_ErrorString =
	   i18n("You do not have read-access for the file %1!\nPlease ask your system-administrator for advice!")
	   .arg(FileName);
	 */
	return false;
    }
    QTextStream stream(&file);
    QString line;

    while (!stream.atEnd()) {
	QString s1, s2;
	line = stream.readLine();
	if (!line.isEmpty()) {
	    if (!splitChar.isNull()) {
		int pos = line.find(splitChar);
		s1 = line.left(pos-1).stripWhiteSpace();
		s2 = line.mid(pos+1).stripWhiteSpace();
	    }
	    else
	        s1 = line;
	}
	lastitem = new QListViewItem(lbox, lastitem, s1, s2);
	added = true;
    }

    file.close();
    if (newlastitem)
	*newlastitem = lastitem;

    return added;
}




bool GetInfo_CPU(QListView * lBox)
{
    lBox->addColumn(i18n("Information"));
    lBox->addColumn(i18n("Value"));
    return GetInfo_ReadfromFile(lBox, INFO_CPU, ':');
}


bool GetInfo_IRQ(QListView * lBox)
{
    lBox->setFont(KGlobalSettings::fixedFont());
    return GetInfo_ReadfromFile(lBox, INFO_IRQ, 0);
}

bool GetInfo_DMA(QListView * lBox)
{
    QFile file(INFO_DMA);
    
    lBox->addColumn(i18n("DMA-Channel"));
    lBox->addColumn(i18n("Used By"));

    if (file.exists() && file.open(IO_ReadOnly)) {
	QTextStream stream(&file);
	QString line;
	QListViewItem *child=0L;
	
	while (!stream.atEnd()) {
	    line = stream.readLine();
	    if (!line.isEmpty()) {
		QRegExp rx("^\\s*(\\S+)\\s*:\\s*(\\S+)");
		if (-1 != rx.search(line)) {
		    child = new QListViewItem(lBox,child,rx.cap(1),rx.cap(2));
		}
	    }
	}
	file.close();
    } else {
	return false;
    }

    return true;
}

bool GetInfo_PCI(QListView * lBox)
{
    int num;
    sorting_allowed = false;	/* no sorting by user */

    /* ry to get the output of the lspci package first */
    if ((num = GetInfo_ReadfromPipe(lBox, "lspci -v", true)) ||
        (num = GetInfo_ReadfromPipe(lBox, "/sbin/lspci -v", true)) ||
        (num = GetInfo_ReadfromPipe(lBox, "/usr/sbin/lspci -v", true)) ||
        (num = GetInfo_ReadfromPipe(lBox, "/usr/local/sbin/lspci -v", true)))
	    return num;

    /* if lspci failed, read the contents of /proc/pci */
    return GetInfo_ReadfromFile(lBox, INFO_PCI, 0);
}

bool GetInfo_IO_Ports(QListView * lBox)
{
    lBox->addColumn(i18n("I/O-Range"));
    lBox->addColumn(i18n("Used By"));
    return GetInfo_ReadfromFile(lBox, INFO_IOPORTS, ':');
}

bool GetInfo_Sound(QListView * lBox)
{
    sorting_allowed = false;	/* no sorting by user */
    if (GetInfo_ReadfromFile(lBox, INFO_DEV_SNDSTAT, 0))
	return true;
    else if (GetInfo_ReadfromFile(lBox, INFO_SOUND, 0))
	return true;
    else if (GetInfo_ReadfromFile(lBox, INFO_ASOUND, 0))
	return true;
    else
	return GetInfo_ReadfromFile(lBox, INFO_ASOUND09, 0);
}

bool GetInfo_Devices(QListView * lBox)
{
    QFile file;
    QListViewItem *misc=0L;

    lBox->setRootIsDecorated(true);
    lBox->addColumn(i18n("Devices"));
    lBox->addColumn(i18n("Major Number"));
    lBox->addColumn(i18n("Minor Number"));

    file.setName(INFO_DEVICES);
    if (file.exists() && file.open(IO_ReadOnly)) {
	QTextStream stream(&file);
	QString line;
	QListViewItem *parent=0L, *child=0L;
	
	while (!stream.atEnd()) {
	    line = stream.readLine();
	    if (!line.isEmpty()) {
		if (-1 != line.find("character device",0,false)) {
		    parent = new QListViewItem(lBox,parent,i18n("Character Devices"));
		    parent->setPixmap(0,SmallIcon("chardevice"));
		    parent->setOpen(true);
		} else if (-1 != line.find("block device",0,false)) {
		    parent = new QListViewItem(lBox,parent,i18n("Block Devices"));
		    parent->setPixmap(0,SmallIcon("blockdevice"));
		    parent->setOpen(true);
		} else {
		    QRegExp rx("^\\s*(\\S+)\\s+(\\S+)");
		    if (-1 != rx.search(line)) {
			if (parent) {
			    child = new QListViewItem(parent,child,rx.cap(2),rx.cap(1));
			} else {
			    child = new QListViewItem(lBox,parent,rx.cap(2),rx.cap(1));
			}
			if (rx.cap(2)=="misc") {
			    misc=child;
			}
		    }
		}
	    }
	}
	file.close();
    } else {
	return false;
    }
    
    file.setName(INFO_MISC);
    if (misc && file.exists() && file.open(IO_ReadOnly)) {
	QTextStream stream(&file);
	QString line;
	QListViewItem *child=0L;
	
	misc->setText(0,i18n("Miscellaneous Devices"));
	misc->setPixmap(0,SmallIcon("memory"));
	misc->setOpen(true);
	
	while (!stream.atEnd()) {
	    line = stream.readLine();
	    if (!line.isEmpty()) {
		QRegExp rx("^\\s*(\\S+)\\s+(\\S+)");
		if (-1 != rx.search(line)) {
		    child = new QListViewItem(misc,child,rx.cap(2),"10",rx.cap(1));
		}
	    }
	}
	file.close();
    }
    
    return true;
}

bool GetInfo_SCSI(QListView * lBox)
{
    return GetInfo_ReadfromFile(lBox, INFO_SCSI, 0);
}

static void cleanPassword(QString & str)
{
    int index = 0;
    QString passwd("password=");

    while (index >= 0)
    {
	index = str.find(passwd, index, FALSE);
	if (index >= 0) {
	    index += passwd.length();
	    while (index < (int) str.length() &&
		   str[index] != ' ' && str[index] != ',')
		str[index++] = '*';
	}
    }
}

#ifndef INFO_PARTITIONS_FULL_INFO

bool GetInfo_Partitions(QListView * lBox)
{
    return GetInfo_ReadfromFile(lBox, INFO_PARTITIONS, 0);
}

#else	/* INFO_PARTITIONS_FULL_INFO */

// Some Ideas taken from garbazo from his source in info_fbsd.cpp

#if SIZEOF_LONG > 4
#define LONG_TYPE	unsigned long
#else
#ifdef HAVE_LONG_LONG
#define LONG_TYPE	unsigned long long
#else
/* On 32-bit systems we would get an overflow in unsigned int for
   drives bigger than 4GB. Let's use the ugly type double ! */
#define LONG_TYPE	double
#endif
#endif



#if ( defined(HAVE_LINUX_RAW_H) || defined(HAVE_SYS_RAW_H) ) && defined(HAVE_SYS_IOCTL_H) && defined(__GNUC__) && !defined(__STRICT_ANSI__)
#include <sys/ioctl.h>
#include <fcntl.h>

#if defined(HAVE_SYS_RAW_H)
#include <sys/raw.h>
#elif defined(HAVE_LINUX_RAW_H)
#include <linux/raw.h>
#endif

/*
 * get raw device bindings and information
 */
void Get_LinuxRawDevices(QListView *lbox)
{
    int f, i, err;
    int new_raw_devs = 1;
    struct raw_config_request rq;
    QString devname;
    QString MB(i18n("MB"));	/* "MB" = "Mega-Byte" */

    /* try to open the raw device control file */
    f = open("/dev/rawctl", O_RDWR);
    if (f == -1) {
	f = open("/dev/raw", O_RDWR);
	new_raw_devs = 0;
    }
    if (f == -1)
	    return;

    for (i=1; i<256; i++) {
	rq.raw_minor = i;
	if (ioctl(f, RAW_GETBIND, &rq))
		continue;
	if (!rq.block_major) /* unbound ? */
		continue;
	unsigned int minor = rq.block_minor;
	char first_letter;
	switch ((int)rq.block_major) {

		/* IDE drives */
		case 3: first_letter = 'a';
			set_ide_name:
			devname = QString("/dev/hd%1%2")
				.arg(QChar(first_letter + minor/64))
				.arg(minor&63);
			break;
		case 22:first_letter = 'c';	goto set_ide_name;
		case 33:first_letter = 'e';	goto set_ide_name;
		case 34:first_letter = 'g';	goto set_ide_name;
		case 56:first_letter = 'i';	goto set_ide_name;
		case 57:first_letter = 'k';	goto set_ide_name;
		case 88:first_letter = 'm';	goto set_ide_name;
		case 89:first_letter = 'o';	goto set_ide_name;
		case 90:first_letter = 'q';	goto set_ide_name;
		case 91:first_letter = 's';	goto set_ide_name;

		/* SCSI drives */
		case 8: first_letter = 'a';
			set_scsi_name:
			devname = QString("/dev/sd%1%2")
				.arg(QChar(first_letter + minor/16))
				.arg(minor&15);
			break;
		case 65:first_letter = 'q';	goto set_scsi_name;

		/* Compaq /dev/cciss devices */
		case 104: case 105: case 106:
		case 107: case 108: case 109:
			devname = QString("/dev/cciss/c%1d%2")
				.arg((int)rq.block_major-104)
				.arg(minor&15);
			break;

		/* Compaq Intelligent Drive Array (ida) */
		case 72: case 73: case 74: case 75:
		case 76: case 77: case 78: case 79:
			devname = QString("/dev/ida/c%1d%2")
				.arg((int)rq.block_major-72)
				.arg(minor&15);
			break;

		default: devname = QString("%1/%2")
			 	.arg((int)rq.block_major)
				.arg(minor);

	}

	/* TODO: get device size */
	QString size = "";

	new QListViewItem(lbox, devname,
		QString(new_raw_devs ? "/dev/raw/raw%1" : "/dev/raw%1").arg(i),
		"raw", size, " ", "");
    }
    close(f);
}
#else
#define Get_LinuxRawDevices(x)	/* nothing */
#endif

bool GetInfo_Partitions(QListView * lbox)
{
#define NUMCOLS 6
    QString Title[NUMCOLS];
    QStringList Mounted_Partitions;
    bool found_in_List;
    int n;

#ifdef HAVE_FSTAB_H
    struct fstab *fstab_ent;
# define FS_NAME	fstab_ent->fs_spec	// device-name
# define FS_FILE	fstab_ent->fs_file	// mount-point
# define FS_TYPE	fstab_ent->fs_vfstype	// fs-type
# define FS_MNTOPS 	fstab_ent->fs_mntops	// mount-options
#else
    struct mntent *mnt_ent;
    FILE *fp;
# define FS_NAME	mnt_ent->mnt_fsname	// device-name
# define FS_FILE	mnt_ent->mnt_dir	// mount-point
# define FS_TYPE	mnt_ent->mnt_type	// fs-type
# define FS_MNTOPS 	mnt_ent->mnt_opts	// mount-options
#endif

    struct statfs sfs;
    LONG_TYPE total, avail;
    QString str, mountopts;
    QString MB(i18n("MB"));	/* "MB" = "Mega-Byte" */


#ifdef HAVE_FSTAB_H
    if (setfsent() == 0)	/* Try to open fstab */
	return false;
#else
    if (!(fp = setmntent("/etc/fstab", "r")))
	return false;
#endif

    /* read the list of already mounted file-systems.. */
    QFile *file = new QFile(INFO_MOUNTED_PARTITIONS);
    if (file->open(IO_ReadOnly)) {
	char buf[1024];
	while (file->readLine(buf, sizeof( buf )) > 0) {
	    str = QString::fromLocal8Bit(buf);
	    if (str.length()) {
		int p = str.find(' ');	/* find first space. */
		if (p)
		    str.remove(p, 1024); /* erase all chars including space. */
		Mounted_Partitions.append(str);
	    }
	}
	file->close();
    }
    delete file;

    /* create the header-tables */
    MB = QString(" ") + MB;
    Title[0] = i18n("Device");
    Title[1] = i18n("Mount Point");
    Title[2] = i18n("FS Type");
    Title[3] = i18n("Total Size");
    Title[4] = i18n("Free Size");
    Title[5] = i18n("Mount Options");

    for (n = 0; n < NUMCOLS; ++n)
	lbox->addColumn(Title[n]);

    /* loop through all partitions... */
#ifdef HAVE_FSTAB_H
    while ((fstab_ent = getfsent()) != NULL)
#else
    while ((mnt_ent = getmntent(fp)) != NULL)
#endif
    {
	total = avail = 0;	/* initialize size.. */
	found_in_List = (Mounted_Partitions.contains(FS_NAME) > 0);
	if (found_in_List && statfs(FS_FILE, &sfs) == 0) {
	    total = ((LONG_TYPE) sfs.f_blocks) * sfs.f_bsize;
	    avail = (getuid()? sfs.f_bavail : sfs.f_bfree)
		* ((LONG_TYPE) sfs.f_bsize);
	};
	/*
	   if (stat(fstab_ent->fs_file,&st)!=0)
	   total = 0;
	   if (!S_ISDIR(st.st_mode))
	   total = 0;
	 */
	mountopts = FS_MNTOPS;
	cleanPassword(mountopts);
	if (total)
	    new QListViewItem(lbox, QString(FS_NAME) + "  ",
			      QString(FS_FILE) + "  ",
			      QString(FS_TYPE) + "  ",
			      Value((int) (((total / 1024) + 512) / 1024),
				    6) + MB,
			      Value((int) (((avail / 1024) + 512) / 1024),
				    6) + MB, mountopts);
	else
	    new QListViewItem(lbox, QString(FS_NAME), QString(FS_FILE),
			      QString(FS_TYPE), " ", " ", mountopts);
    }

#ifdef HAVE_FSTAB_H
    endfsent();			/* close fstab.. */
#else
    endmntent(fp);		/* close fstab.. */
#endif

    /* get raw device entires if available... */
    Get_LinuxRawDevices(lbox);

    sorting_allowed = true;	/* sorting by user allowed ! */
    lbox->setSorting(1);

    return true;
}
#endif				/* INFO_PARTITIONS_FULL_INFO */




bool GetInfo_XServer_and_Video(QListView * lBox)
{
    return GetInfo_XServer_Generic(lBox);
}

/* GetInfo for CD-ROM Info by Jahshan Bhatti */
bool GetInfo_CD_ROM(QListView * lBox)
{
	QFile file(INFO_CD_ROM);
	lBox->addColumn(i18n("Information"));
	lBox->addColumn(i18n("Value"));

	if (file.exists() && file.open(IO_ReadOnly)) {
		QRegExp rx("(.+):\\s+(\\S.*)");
		QTextStream stream(&file);
		QString line;
		QListViewItem *child = NULL;

		while (!stream.atEnd()) {
			line = stream.readLine();
			if (!line.isEmpty()) {
				if (-1 != rx.search(line)) {
					QString text = rx.cap(1);
					QString value = rx.cap(2);
					if (!text.contains('#')) {
						if (value == "0")
							value = KStdGuiItem::no().plainText();
						if (value == "1")
							value = KStdGuiItem::yes().plainText();
					}
					child = new QListViewItem(lBox,child,text,value);
				}
			} else {
				child = new QListViewItem(lBox,child,QString::null,QString::null);
			}
		}
		file.close();
	} else {
		return false;
	}

    return true;
}