Răsfoiți Sursa

Von gfaipc in ein eigenes Repo verschoben

Rind 3 ani în urmă
comite
7c97ba48c2
14 a modificat fișierele cu 3652 adăugiri și 0 ștergeri
  1. 5 0
      .gitignore
  2. 41 0
      gfasysinfo.pro
  3. 29 0
      src/gfaspi.h
  4. 902 0
      src/main.cpp
  5. 565 0
      src/mysqlinfo.cpp
  6. 47 0
      src/mysqlinfo.h
  7. 418 0
      src/mysqlwrap.cpp
  8. 191 0
      src/mysqlwrap.h
  9. 157 0
      src/procfile.cpp
  10. 121 0
      src/procfile.h
  11. 285 0
      src/spiinfo.cpp
  12. 51 0
      src/spiinfo.h
  13. 795 0
      src/stgdevinfo.cpp
  14. 45 0
      src/stgdevinfo.h

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+Debug/
+Release/
+Profile/
+*.pro.user
+*.bak

+ 41 - 0
gfasysinfo.pro

@@ -0,0 +1,41 @@
+TEMPLATE = app
+CONFIG += console c++11 thread
+CONFIG -= qt app_bundle
+
+QMAKE_LIBDIR += $$[QT_SYSROOT]/usr/lib/gfa
+QMAKE_RPATHDIR += /usr/lib/gfa
+QMAKE_LIBS += -ludev
+
+CONFIG(debug, debug|release) {
+    QMAKE_CXXFLAGS -= -Os
+    QMAKE_CFLAGS -= -Os
+    QMAKE_CXXFLAGS += -D_DEBUG
+    QMAKE_CFLAGS += -D_DEBUG
+	QMAKE_LIBS += -lgfaipcd -lgfasitarautilsd -pthread -lmysqlclient
+}
+
+CONFIG(release, debug|release) {
+	QMAKE_LIBS += -lgfaipc -lgfasitarautils -pthread -lmysqlclient
+}
+
+linux-buildroot-g++ {
+    QMAKE_CXXFLAGS += -D_TARGET_BUILD
+    QMAKE_CFLAGS += -D_TARGET_BUILD
+    target.path += /opt/GfA/bin
+	INSTALLS += target
+}
+
+SOURCES += \
+    src/main.cpp \
+    src/mysqlinfo.cpp \
+    src/mysqlwrap.cpp \
+    src/procfile.cpp \
+    src/spiinfo.cpp \
+    src/stgdevinfo.cpp
+
+HEADERS += \
+    src/mysqlinfo.h \
+    src/mysqlwrap.h \
+    src/procfile.h \
+    src/spiinfo.h \
+    src/stgdevinfo.h

+ 29 - 0
src/gfaspi.h

@@ -0,0 +1,29 @@
+// gfaspi.h :
+//
+
+#if !defined(AGD_GFASPI_H__F1751711_2AAA_48F4_9959_E6F56DA316B8__INCLUDED_)
+#define AGD_GFASPI_H__F1751711_2AAA_48F4_9959_E6F56DA316B8__INCLUDED_
+
+#ifdef __cplusplus
+extern "C" {
+#endif	//	__cplusplus
+
+/////////////////////////////////////////////////////////////////////////////
+// gfaspi.h - Declarations:
+
+typedef struct _TIVA_ADC
+{
+	int UVers;
+	int UBatV3;
+	int Temp;
+	int UV5Vsys;
+	int UV3V6Bat;
+	int TempTIVA;
+}TIVA_ADC, *LPTIVA_ADC;
+typedef const TIVA_ADC *LPCTIVA_ADC;
+
+/////////////////////////////////////////////////////////////////////////////
+#ifdef __cplusplus
+}
+#endif	//	__cplusplus
+#endif	//	!defined(AGD_GFASPI_H__F1751711_2AAA_48F4_9959_E6F56DA316B8__INCLUDED_)

+ 902 - 0
src/main.cpp

@@ -0,0 +1,902 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <string>
+#include <vector>
+#include <map>
+#include <ext/stdio_filebuf.h>
+#include <iostream>
+#include <fstream>
+#include <signal.h>
+#include <sys/statvfs.h>
+#include <sys/statfs.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <libudev.h>
+#include <poll.h>
+#include <errno.h>
+#include <getopt.h>
+#include <gfa/gfasitarautils.h>
+#include <gfa/gfaipc.h>
+#include "stgdevinfo.h"
+#include "mysqlinfo.h"
+#include "spiinfo.h"
+
+/////////////////////////////////////////////////////////////////////////////
+
+#ifdef _DEBUG
+#define TRACE(...)							fprintf(stdout, __VA_ARGS__), fflush(stdout)
+#else	//	_DEBUG
+#define TRACE(...)
+#endif	//	_DEBUG
+
+#define UNUSED(v)							(void)v
+#define _countof(a)							(sizeof(a) / sizeof(*a))
+
+/////////////////////////////////////////////////////////////////////////////
+
+#define _APPID								GFA_APPCTRL_APPID_SYSINFO
+#define _APPNAME							"SysInfo"
+#define _CYCLE_INTV							500
+
+/////////////////////////////////////////////////////////////////////////////
+
+static volatile bool						g_fRun		= false;
+static volatile bool						g_fPause	= false;
+static volatile bool						g_fZombie	= false;
+static sigset_t								g_set;
+
+/////////////////////////////////////////////////////////////////////////////
+
+#ifdef _DEBUG
+static const char *g_pszStateNames[] =
+{
+	"Not running",
+	"Initializing",
+	"Running",
+	"Paused",
+	"Hanging",
+	"Terminating",
+	"Invalid"
+};
+#endif	//	_DEBUG
+
+/////////////////////////////////////////////////////////////////////////////
+#if 0
+static long long	_NumberFromString(const char *pszString, int base = 10, bool *pbErr = NULL);
+static const char*	_ReadDevPropertyValue(struct udev_device* dev, const char *pszKey, char *pszValue, size_t nCChValue, bool bTruncate = false);
+static long long	_ReadDevPropertyValue(struct udev_device* dev, const char *pszKey, int base = 10, bool *pbErr = NULL);
+
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+static int _LookupPartition(const GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, const char *pszDevNode)
+{
+	int nIndex = -1;
+
+	if(pszDevNode && *pszDevNode)
+	{
+		for(size_t i = 0; i < _countof(sdm.parts); ++i)
+		{
+			if(sdm.parts[i].valid && !strcmp(sdm.parts[i].szDevNode, pszDevNode))
+			{
+				nIndex = i;	// found partition
+				break;
+			}
+		}
+	}
+
+	return nIndex;
+}
+
+static int _AddPartition(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, const GFA_SYSINFO_PARTITION &part, bool &bChange)
+{
+	int nIndex = -1;
+	bChange = false;
+
+	if((nIndex = _LookupPartition(sdm, part.szDevNode)) >= 0)
+		return nIndex; // partition already exists
+
+	for(size_t i = 0; i < _countof(sdm.parts); ++i)
+	{
+		if(!sdm.parts[i].valid)
+		{
+			memcpy(&sdm.parts[i], &part, sizeof(GFA_SYSINFO_PARTITION));
+			sdm.parts[i].valid = true;
+			bChange = true;
+			nIndex = i;
+			break;
+		}
+	}
+
+	return nIndex;
+}
+
+static int _RemovePartition(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, int nIndex, bool &bChange)
+{
+	bChange = false;
+
+	if(nIndex >= 0 && nIndex < (int)_countof(sdm.parts))
+	{
+		bChange = sdm.parts[nIndex].valid;
+		sdm.parts[nIndex].valid = false;
+	}
+
+	return nIndex;
+}
+
+static bool _PartitionSetDisk(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, int nPartIdx, int nDiskIdx, bool &bChange)
+{
+	bChange = false;
+
+	if(	(nPartIdx >= 0) && (nPartIdx < (int)_countof(sdm.parts)) &&
+		(nDiskIdx >= 0) && (nDiskIdx < (int)_countof(sdm.disks)))
+	{
+		if(sdm.parts[nPartIdx].nDiskIdx != nDiskIdx)
+		{
+			sdm.parts[nPartIdx].nDiskIdx = nDiskIdx;
+			bChange = true;
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+static int _PartitionGetDisk(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, int nPartIdx)
+{
+	if((nPartIdx >= 0) && (nPartIdx < (int)_countof(sdm.parts)))
+		return sdm.parts[nPartIdx].nDiskIdx;
+	return -1;
+}
+
+static void _ClearMapChanges(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm)
+{
+	sdm.nPartChangeMask = 0;
+}
+
+static bool _DeviceMapChanged(const GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm)
+{
+	return !!sdm.nPartChangeMask;
+}
+
+static unsigned int _SetPartitionChange(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, int nPartIdx)
+{
+	if((nPartIdx >= 0) && (nPartIdx < (int)_countof(sdm.parts)))
+	{
+		unsigned int nChanged = (0x01 << nPartIdx);
+		sdm.nPartChangeMask |= nChanged;
+	}
+
+	return sdm.nPartChangeMask;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+static int _LookupDisk(const GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, const char *pszDevNode)
+{
+	int nIndex = -1;
+
+	if(pszDevNode && *pszDevNode)
+	{
+		for(size_t i = 0; i < _countof(sdm.disks); ++i)
+		{
+			if(sdm.disks[i].valid && !strcmp(sdm.disks[i].szDevNode, pszDevNode))
+			{
+				nIndex = i;	// found partition
+				break;
+			}
+		}
+	}
+
+	return nIndex;
+}
+
+static int _AddDisk(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, const GFA_SYSINFO_DISK &disk, bool &bChange)
+{
+	int nIndex = -1;
+	bChange = false;
+
+	if((nIndex = _LookupDisk(sdm, disk.szDevNode)) >= 0)
+		return nIndex; // partition already exists
+
+	for(size_t i = 0; i < _countof(sdm.disks); ++i)
+	{
+		if(!sdm.disks[i].valid)
+		{
+			memcpy(&sdm.disks[i], &disk, sizeof(GFA_SYSINFO_DISK));
+			sdm.disks[i].valid = true;
+			bChange = true;
+			nIndex = i;
+			break;
+		}
+	}
+
+	return nIndex;
+}
+
+static int _RemoveDisk(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, int nIndex, bool &bChange)
+{
+	bChange = false;
+
+	if(nIndex >= 0 && nIndex < (int)_countof(sdm.disks))
+	{
+		bChange = sdm.disks[nIndex].valid;
+		sdm.disks[nIndex].valid = false;
+	}
+
+	return nIndex;
+}
+
+static unsigned int _DiskAddPartition(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, int nDiskIdx, int nPartIdx, bool &bChange)
+{
+	bChange = false;
+
+	if(	(nPartIdx >= 0) && (nPartIdx < (int)_countof(sdm.parts)) &&
+		(nDiskIdx >= 0) && (nDiskIdx < (int)_countof(sdm.disks)))
+	{
+		GFA_SYSINFO_DISK &disk = sdm.disks[nDiskIdx];
+
+		for(unsigned int i = 0; i < disk.nPartCount; ++i)
+		{
+			if(disk.aPartIdx[i] == nPartIdx)
+				return disk.nPartCount;
+		}
+
+		if(disk.nPartCount < _countof(disk.aPartIdx))
+		{
+			disk.aPartIdx[disk.nPartCount++] = nPartIdx;
+			bChange = true;
+			return disk.nPartCount;
+		}
+	}
+
+	return 0;
+}
+
+static unsigned int _DiskRemovePartition(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, int nDiskIdx, int nPartIdx, bool &bChange)
+{
+	bChange = false;
+
+	if(	(nPartIdx >= 0) && (nPartIdx < (int)_countof(sdm.parts)) &&
+		(nDiskIdx >= 0) && (nDiskIdx < (int)_countof(sdm.disks)))
+	{
+		unsigned int i, j;
+		GFA_SYSINFO_DISK &disk = sdm.disks[nDiskIdx];
+
+		for(i = 0; i < disk.nPartCount; ++i)
+		{
+			if(disk.aPartIdx[i] == nPartIdx)
+				break;
+		}
+
+		if(i < disk.nPartCount)
+		{
+			for(j = i + 1; j < disk.nPartCount; ++i, ++j)
+			{
+				disk.aPartIdx[i] = disk.aPartIdx[j];
+			}
+			bChange = true;
+			return --disk.nPartCount;
+		}
+
+		return disk.nPartCount;
+	}
+
+	return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+static bool _LookupMountPoint(MountMap &mm, const char *pszNode, char *pszMntPoint, size_t nCChMntPoint)
+{
+	MountMap::const_iterator it = mm.find(pszNode);
+	if(it == mm.end())
+		return false;
+	const std::string &s = it->second;
+
+	if(s.length() < nCChMntPoint)
+	{
+		strcpy(pszMntPoint, s.c_str());
+		return true;
+	}
+
+	return false;
+}
+
+static bool _UpdatePartitionFsInfo(MountMap &mm, GFA_SYSINFO_PARTITION &part)
+{
+	if(_LookupMountPoint(mm, part.szDevNode, part.szMntPoint, sizeof(part.szMntPoint)))
+	{
+		struct statvfs stvs;
+
+		if(!statvfs(part.szMntPoint, &stvs))
+		{
+			part.nKiBSize = stvs.f_bsize * stvs.f_blocks	/ 1024;
+			part.nKiBFree = stvs.f_bsize * stvs.f_bfree	/ 1024;
+			part.nKiBUsed = part.nKiBSize - part.nKiBFree;
+		}
+		else
+		{
+			TRACE("statvfs failed on \"%s\": %s\n", part.szMntPoint, strerror(errno));
+			memset(part.szMntPoint, 0, sizeof(part.szMntPoint));
+			part.nKiBSize = 0;
+			part.nKiBFree = 0;
+			part.nKiBUsed = 0;
+		}
+
+		return true;
+	}
+	else
+	{
+		memset(part.szMntPoint, 0, sizeof(part.szMntPoint));
+		part.nKiBSize = 0;
+		part.nKiBFree = 0;
+		part.nKiBUsed = 0;
+	}
+
+	return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+static std::string _StrReplace(std::string &s, const char *pszFind, const char *pszRepl)
+{
+	std::string r = s;
+
+	if(pszFind && *pszFind && pszRepl)
+	{
+		size_t nFind, nLen = strlen(pszFind);
+
+		while((nFind = r.find(pszFind)) !=  std::string::npos)
+		{
+			r = r.replace(nFind, nLen, pszRepl);
+		}
+	}
+	
+	return r;
+}
+	
+static std::string _UnescapeMountpointString(std::string &s)
+{
+	std::string r = s;
+	r = _StrReplace(r, "\\040", " ");
+	r = _StrReplace(r, "\\011", "\t");
+	r = _StrReplace(r, "\\012", "\n");
+	r = _StrReplace(r, "\\134", "\\");
+	return r;
+}
+
+static void _UpdateMountMap(MountMap &mm)
+{
+	char szLine[512], szNode[512], szMount[512];
+	std::ifstream mounts(_MOUNTS_FILE);
+	mm.clear();
+
+	while(mounts.getline(szLine, sizeof(szLine)).good())
+	{
+		if((sscanf(szLine, "%s %s", szNode, szMount) == 2))
+		{
+			std::string key(szNode);
+			key = _UnescapeMountpointString(key);
+			std::string val(szMount);
+			val = _UnescapeMountpointString(val);
+			mm.emplace(key, val);
+		}
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+static bool _ProcessMounts(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, MountMap &mm)
+{
+	bool bChange = false;
+	char szMntPoint[_countof(GFA_SYSINFO_PARTITION::szMntPoint)];
+
+	for(size_t i = 0; i < _countof(sdm.parts); ++i)
+	{
+		GFA_SYSINFO_PARTITION &part = sdm.parts[i];
+
+		if(part.valid)
+		{
+			bool bMountedOld = !!*part.szMntPoint;
+			bool bMountedNew = _LookupMountPoint(mm, part.szDevNode, szMntPoint, sizeof(szMntPoint));
+
+			if(!bMountedOld && bMountedNew)
+			{
+				memcpy(part.szMntPoint, szMntPoint, sizeof(part.szMntPoint));
+				_UpdatePartitionFsInfo(mm, part);
+				_SetPartitionChange(sdm, i);
+				bChange = true;
+			}
+			else if(bMountedOld && !bMountedNew)
+			{
+				memset(part.szMntPoint, 0, sizeof(part.szMntPoint));
+				_UpdatePartitionFsInfo(mm, part);
+				_SetPartitionChange(sdm, i);
+				bChange = true;
+			}
+			else if(bMountedOld && bMountedNew)
+			{
+				if(strcmp(part.szMntPoint, szMntPoint))
+				{
+					memcpy(part.szMntPoint, szMntPoint, sizeof(part.szMntPoint));
+					_UpdatePartitionFsInfo(mm, part);
+					_SetPartitionChange(sdm, i);
+					bChange = true;
+				}
+			}
+		}
+	}
+
+	return bChange;
+}
+
+static const char* _GetFileSpec(const char *pszPathname)
+{
+	if(pszPathname && *pszPathname)
+	{
+		const char *pSlash =  strrchr(pszPathname, '/');
+		if(pSlash)
+			return  ++pSlash;
+		else
+			return pszPathname;
+	}
+
+	return NULL;
+}
+
+static bool _IsInternalEmmc(const char *pszDevNode)
+{
+	if(pszDevNode)
+	{
+		const char *pszDevName = _GetFileSpec(pszDevNode);
+		return _STR_EQUALS(pszDevName, _INTERNAL_EMMC_PART2) || _STR_EQUALS(pszDevName, _INTERNAL_EMMC_PART1);
+	}
+
+	return false;
+}
+
+static long long _NumberFromString(const char *pszString, int base, bool *pbErr)
+{
+	if(!pszString || !*pszString)
+	{
+		if(pbErr)
+			*pbErr = true;
+		return 0;
+	}
+
+	char *endptr;
+	long long nRet = strtoll(pszString, &endptr, base);
+
+	if( (((nRet == LLONG_MAX) || (nRet == LLONG_MIN)) && (errno == ERANGE)) ||
+		((nRet == 0) && (errno == EINVAL)) ||
+		(!!*endptr))
+	{
+		if(pbErr)
+			*pbErr = true;
+		return 0;
+	}
+
+	if(pbErr)
+		*pbErr = false;
+	return nRet;
+}
+
+static long long _ReadDevPropertyValue(struct udev_device* dev, const char *pszKey, int base, bool *pbErr)
+{
+	char szNum[64];
+	return _NumberFromString(_ReadDevPropertyValue(dev, pszKey, szNum, sizeof(szNum), false), base, pbErr);
+}
+
+static const char* _ReadDevPropertyValue(struct udev_device* dev, const char *pszKey, char *pszValue, size_t nCChValue, bool bTruncate)
+{
+	if(!pszValue || !nCChValue)
+	    return NULL;
+   memset(pszValue, 0, nCChValue);
+
+    const char *pszVal = ::udev_device_get_property_value(dev, pszKey);
+
+    if(pszVal)
+    {
+    	size_t nLen = strlen(pszVal);
+
+    	if(nLen < nCChValue)
+    	{
+    		strcpy(pszValue, pszVal);
+    		return pszValue;
+    	}
+    	else if(bTruncate)
+    	{
+    		memcpy(pszValue, pszVal, nCChValue);
+    		pszValue[nCChValue - 1] = '\0';
+    		return pszValue;
+    	}
+    }
+
+    return NULL;
+}
+
+static void _ProcessPartition(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, MountMap &mm, struct udev_device* dev)
+{
+	if(dev)
+	{
+		bool bChange;
+		GFA_SYSINFO_PARTITION part;
+		memset(&part, 0, sizeof(part));
+		part.nDiskIdx = -1;
+		int nPartIdx = -1;
+		int nDiskIdx = -1;
+
+		const char *pszDevNode = ::udev_device_get_devnode(dev);
+
+		if(!pszDevNode)
+			return;
+//		if(_IsInternalEmmc(pszDevNode))
+//			return;	// skip internal emmc
+		bool bInternalEmmc = _IsInternalEmmc(pszDevNode);
+		strncpy(part.szDevNode, pszDevNode, sizeof(part.szDevNode) - 1);
+
+		const char *pszAction = ::udev_device_get_action(dev);
+		bool bAdd = _STR_EQUALS(pszAction, "add");
+		bool bRem = _STR_EQUALS(pszAction, "remove");
+		bool bEnum = !pszAction;
+
+		if(bAdd || bEnum)
+		{
+			if(_LookupPartition(sdm, part.szDevNode) >= 0)
+				return;
+
+			part.internal = bInternalEmmc;
+			_ReadDevPropertyValue(dev, "ID_FS_LABEL", part.szFsLabel, sizeof(part.szFsLabel), true);
+			_ReadDevPropertyValue(dev, "ID_FS_TYPE", part.szFsType, sizeof(part.szFsType), true);
+			_ReadDevPropertyValue(dev, "ID_FS_VERSION", part.szFsVersion, sizeof(part.szFsVersion), true);
+			part.nKiBPartSize = _ReadDevPropertyValue(dev, "ID_PART_ENTRY_SIZE") / 2;
+			_UpdatePartitionFsInfo(mm, part);
+
+			struct udev_device* cur = dev;
+
+			while((cur = ::udev_device_get_parent(cur)))
+			{
+				const char *pszSs = ::udev_device_get_subsystem(cur);
+				const char *pszDt = ::udev_device_get_devtype(cur);
+				const char *pszDn = ::udev_device_get_devnode(cur);
+
+				if(!pszDn)
+					break;
+
+				if(_STR_EQUALS(pszSs, "usb") || _STR_EQUALS(pszSs, "block"))
+				{
+					if(_STR_EQUALS(pszDt, "disk"))
+					{
+						GFA_SYSINFO_DISK disk;
+						memset(&disk, 0, sizeof(disk));
+						strncpy(disk.szDevNode, pszDn, sizeof(disk.szDevNode) - 1);
+
+						disk.internal = bInternalEmmc;
+						if(!_ReadDevPropertyValue(cur, "ID_NAME", disk.szName, sizeof(disk.szName), true))
+							_ReadDevPropertyValue(cur, "ID_MODEL", disk.szName, sizeof(disk.szName), true);
+						_ReadDevPropertyValue(cur, "ID_VENDOR", disk.szVendor, sizeof(disk.szVendor), true);
+						_ReadDevPropertyValue(cur, "ID_BUS", disk.szBus, sizeof(disk.szBus), true);
+
+						disk.nVendorID	= _ReadDevPropertyValue(cur, "ID_VENDOR_ID", 16);
+						disk.nProductID	= _ReadDevPropertyValue(cur, "ID_MODEL_ID", 16);
+
+						if((nDiskIdx = _AddDisk(sdm, disk, bChange)) >= 0)
+						{
+							if((nPartIdx = _AddPartition(sdm, part, bChange)) < 0)
+							{
+								if(sdm.disks[nDiskIdx].nPartCount == 0)
+									_RemoveDisk(sdm, nDiskIdx, bChange);
+								break;
+							}
+							_DiskAddPartition(sdm, nDiskIdx, nPartIdx, bChange);
+							_PartitionSetDisk(sdm, nPartIdx, nDiskIdx, bChange);
+							_SetPartitionChange(sdm, nPartIdx);
+						}
+
+						break;
+					}
+				}
+			}
+		}
+		else if(bRem)
+		{
+			if((nPartIdx = _LookupPartition(sdm, pszDevNode)) >= 0)
+			{
+				_RemovePartition(sdm, nPartIdx, bChange);
+				_SetPartitionChange(sdm, nPartIdx);
+
+				if((nDiskIdx = _PartitionGetDisk(sdm, nPartIdx)) >= 0)
+				{
+					if(!_DiskRemovePartition(sdm, nDiskIdx, nPartIdx, bChange))
+					{
+						_RemoveDisk(sdm, nDiskIdx, bChange);
+					}
+				}
+			}
+		}
+	}
+}
+
+static void _EnumStorageDevices(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, MountMap &mm, struct udev *pUdev)
+{
+	struct udev_enumerate *pEnum = ::udev_enumerate_new(pUdev);
+	::udev_enumerate_add_match_subsystem(pEnum, "block");
+	::udev_enumerate_add_match_property(pEnum, "DEVTYPE", "partition");
+	::udev_enumerate_scan_devices(pEnum);
+
+	struct udev_list_entry *devices = ::udev_enumerate_get_list_entry(pEnum);
+	struct udev_list_entry *entry;
+
+	udev_list_entry_foreach(entry, devices)
+	{
+		const char *pszPath = ::udev_list_entry_get_name(entry);
+		struct udev_device* dev = ::udev_device_new_from_syspath(pUdev, pszPath);
+
+		_ProcessPartition(sdm, mm, dev);
+		::udev_device_unref(dev);
+	}
+
+	::udev_enumerate_unref(pEnum);
+}
+#endif
+
+/////////////////////////////////////////////////////////////////////////////
+
+static void _ProcessCtrlMessages(HAPPCTRL hAC, HAPPINFO hAI, bool &bStateTransition)
+{
+    ctrlmsg_t nCtrlMsg;
+
+	while(g_fRun && (nCtrlMsg = ::GfaIpcAppCtrlGetNextCtrlMsg(hAI)))
+	{
+		switch(nCtrlMsg)
+		{
+		case GFA_APPCTRL_CTRLMSG_STOP:
+			bStateTransition = g_fRun;
+			g_fRun = false;
+			TRACE("Received Message: STOP!\n");
+			break;
+		case GFA_APPCTRL_CTRLMSG_PAUSE:
+			if(!g_fPause)
+			{
+				bStateTransition = true;
+				g_fPause = true;
+				::GfaIpcAppCtrlSetState(hAC, GIAS_Paused);
+				TRACE("%-8s: State: %s\n", "Me", g_pszStateNames[GIAS_Paused]);
+			}
+			break;
+		case GFA_APPCTRL_CTRLMSG_RESUME:
+			if(g_fPause)
+			{
+				bStateTransition = true;
+				g_fPause = false;
+				::GfaIpcAppCtrlSetState(hAC, GIAS_Running);
+				TRACE("%-8s: State: %s\n", "Me", g_pszStateNames[GIAS_Running]);
+			}
+			break;
+		default:
+			break;
+		}
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+static void _LockSHM(HSHM hShm)
+{
+	::GfaIpcLockSHMAndSigBlock(hShm, &g_set);
+}
+
+static void _UnlockSHM(HSHM hShm)
+{
+	::GfaIpcUnlockSHMAndSigUnblock(hShm, &g_set);
+}
+
+static void _SigHandler(int sig)
+{
+	UNUSED(sig);
+	g_fPause = false;
+	g_fRun = false;
+	g_fZombie = false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+int main(int argc, char *argv[])
+{
+	int c;
+	HAPPCTRL hAC = NULL;
+	HAPPINFO hAI;
+	CCycleTimer ct(_CYCLE_INTV), perfCnt(0U);
+	cy_time_t wStart, wEnd, wCur = 0;
+	std::string sDbUser, sDbPass;
+	CMySqlInfo mySqlInfo;
+	CStgDevInfo stgDevInfo;
+	CSpiInfo spiInfo;
+	bool bStateTransition;
+
+	/////////////////////////////////////////////////////////////////////////
+	// parse command line options
+
+	while((c = getopt(argc, argv, "p:u:")) != -1)
+	{
+		switch(c)
+		{
+		case 'p':
+			sDbPass = optarg;
+			break;
+		case 'u':
+			sDbUser = optarg;
+			break;
+		}
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+	// signal handling
+
+	struct sigaction sa;
+	memset(&sa, 0, sizeof(sa));
+	::sigfillset(&g_set);
+	::sigdelset(&g_set, SIGSEGV);
+
+	sa.sa_handler = _SigHandler;
+    sigaction(SIGHUP, &sa, NULL);	// handles user's terminal disconnect
+    sigaction(SIGQUIT, &sa, NULL);	// handles Ctrl + '\'
+	sigaction(SIGTERM, &sa, NULL);	// handles normal termination
+	sigaction(SIGABRT, &sa, NULL);	// handles abnormal termination (i.e. abort())
+	sigaction(SIGINT, &sa, NULL);	// handles Ctrl + 'C'
+
+	// ignore signals
+	sa.sa_handler = SIG_IGN;
+    sigaction(SIGTSTP, &sa, NULL);	// ignores Ctrl + 'Z'
+    sigaction(SIGCHLD, &sa, NULL);	// ignores child process termination
+    sigaction(0, &sa, NULL);		// ignores shell termination
+
+	/////////////////////////////////////////////////////////////////////////
+	// initialize
+	
+	CMySqlInfo::EXEC_PARAMS myep;
+	CStgDevInfo::EXEC_PARAMS step;
+	CSpiInfo::EXEC_PARAMS spep;
+
+	do
+	{
+		g_fZombie = true;
+
+		if(!(hAC = ::GfaIpcAppCtrlAcquire(_APPID, _APPNAME, _CYCLE_INTV * 1000, _CYCLE_INTV * 3000)))
+	    {
+	        TRACE("GfaIpcAppCtrlAcquire failed\n");
+			break;
+	    }
+
+		::GfaIpcAppCtrlSetLockUnlockFunctions(hAC, _LockSHM, _UnlockSHM);
+		::GfaIpcAppCtrlSetState(hAC, GIAS_Initializing);
+
+		if(!::GfaIpcAppCtrlCreateSysInfo(hAC))
+	    {
+	        TRACE("GfaIpcAppCtrlCreateSysInfo failed\n");
+			break;
+	    }
+
+		::GfaIpcDumpSHMROT();
+		
+		TRACE("My Name:  %s\n", _APPNAME);
+		TRACE("My AppID: %llu\n", _APPID);
+		TRACE("My PID:   %d\n", getpid());
+		TRACE("My Cycle: %d\n", _CYCLE_INTV);
+
+		myep = {hAC, sDbUser, sDbPass};
+		mySqlInfo.Create(&myep);
+		
+		step = {hAC};
+		stgDevInfo.Create(&step);
+		
+		spep = {hAC};
+		spiInfo.Create(&spep);
+
+		g_fZombie = false;
+		g_fRun = true;
+		::GfaIpcAppCtrlSetState(hAC, GIAS_Running);
+		mySqlInfo.Signal(CMySqlInfo::S_UpdateAll);
+		stgDevInfo.Signal(CStgDevInfo::S_Init);
+		spiInfo.Signal(CSpiInfo::S_Init);
+	}
+	while(false);
+
+	/////////////////////////////////////////////////////////////////////////
+	/////////////////////////////////////////////////////////////////////////
+	// run
+
+	while(g_fRun)
+	{
+		bStateTransition = false;
+
+		/////////////////////////////////////////////////////////////////////
+		// trigger cycle timer
+
+		ct.Trigger();
+
+		/////////////////////////////////////////////////////////////////////
+		// update app control info
+
+		if((hAI = ::GfaIpcAppCtrlInfoUpdate(hAC, wCur)))
+		{
+			_ProcessCtrlMessages(hAC, hAI, bStateTransition);
+		}
+
+		/////////////////////////////////////////////////////////////////////
+		// if not paused, do work
+		
+		if(g_fRun)
+		{
+			if(bStateTransition)
+			{
+				if(g_fPause)
+				{
+					mySqlInfo.Signal(CMySqlInfo::S_Pause);
+					stgDevInfo.Signal(CStgDevInfo::S_Pause);
+					spiInfo.Signal(CSpiInfo::S_Pause);
+				}
+				else
+				{
+					mySqlInfo.Signal(CMySqlInfo::S_Resume);
+					stgDevInfo.Signal(CStgDevInfo::S_Resume);
+					spiInfo.Signal(CSpiInfo::S_Resume);
+				}
+			}
+
+			if(!g_fPause)
+			{
+				wStart = ct.GetMicroTick();
+				::GfaIpcAppCtrlUpdateSysInfo(hAC);
+				wEnd = ct.GetMicroTick();
+				wCur = wEnd - wStart;
+			}
+
+			ct.Sleep1();
+		}
+		else
+		{
+			::GfaIpcAppCtrlSetState(hAC, GIAS_Terminating);
+		}
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+	/////////////////////////////////////////////////////////////////////////
+	// terminate
+
+	mySqlInfo.Signal(CMySqlInfo::S_Terminate);
+	stgDevInfo.Signal(CStgDevInfo::S_Terminate);
+	spiInfo.Signal(CSpiInfo::S_Terminate);
+
+	mySqlInfo.Join(NULL);
+	stgDevInfo.Join(NULL);
+	spiInfo.Join(NULL);
+	
+	if(g_fZombie)
+	{
+		if(hAC)
+			::GfaIpcAppCtrlSetState(hAC, GIAS_Zombie);
+		TRACE("%-8s: State: %s\n", "Me", ::GfaIpcAppCtrlGetStateText(GIAS_Zombie));
+		pause();
+		if(hAC)
+			::GfaIpcAppCtrlSetState(hAC, GIAS_Terminating);
+	}
+
+	if(hAC)
+	{
+		::GfaIpcAppCtrlReleaseSysInfo(hAC);
+		::GfaIpcAppCtrlRelease(hAC);
+	}
+
+	return 0;
+}

+ 565 - 0
src/mysqlinfo.cpp

@@ -0,0 +1,565 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <gfa/gfasitarautils.h>
+#include "mysqlinfo.h"
+#include "mysqlwrap.h"
+#include "procfile.h"
+#include <gfa/procmem.h>
+
+/////////////////////////////////////////////////////////////////////////////
+
+#ifdef _DEBUG
+#define TRACE(...)							fprintf(stdout, __VA_ARGS__), fflush(stdout)
+#else	//	_DEBUG
+#define TRACE(...)
+#endif	//	_DEBUG
+
+#define UNUSED(v)							(void)v
+#define _countof(a)							(sizeof(a) / sizeof(*a))
+
+#define _TIMESPEC_2_US(ts)					(((clock64_t)(ts).tv_sec) * 1000000LL + ((clock64_t)(ts).tv_nsec) / 1000LL)
+#define _MYSQL_PROCESS_NAME					"mysqld"
+
+/////////////////////////////////////////////////////////////////////////////
+
+#define _MYSQL_HOST							"localhost"
+#define _QUICK_UPDATE_INTERVAL_MS			3000LL
+#define _SLOW_UPDATE_INTERVAL_MS			(4LL * _QUICK_UPDATE_INTERVAL_MS)
+#define _LAZY_UPDATE_INTERVAL_MS			(5LL * _SLOW_UPDATE_INTERVAL_MS)
+
+/////////////////////////////////////////////////////////////////////////////
+
+static bool _IsIgnoredDb(const char *pszDb)
+{
+	static const char *pszIgnoredDBs[] = {"information_schema", "mysql", "test"};
+
+	for(size_t i = 0; i < _countof(pszIgnoredDBs); ++i)
+	{
+		if(!strcmp(pszDb, pszIgnoredDBs[i]))
+			return true;
+	}
+
+	return false;
+}
+
+static std::string & _rtrim(std::string &s, const char *sep)
+{
+	std::size_t f = s.find_last_not_of(sep);
+	if(f != std::string::npos)
+		s.erase(f + 1);
+	return s;
+}
+
+static CMySqlVar _QuerySingleServerVariable(CMySqlDB &db, const char *pszVarName)
+{
+	char sql[256];
+	sprintf(sql, "SHOW VARIABLES WHERE `Variable_Name` = '%s';", pszVarName);
+	CMySqlResult res = db.Query(sql);
+	if(!res.error())
+	{
+		CMySqlRow row;
+		if(res.FetchRow(row))
+			return row["Value"];
+	}
+
+	return CMySqlVar();
+}
+
+static CMySqlVar _QuerySingleGlobalStatusValue(CMySqlDB &db, const char *pszValName)
+{
+	char sql[256];
+	sprintf(sql, "SHOW GLOBAL STATUS WHERE `Variable_name` = '%s';", pszValName);
+	CMySqlResult res = db.Query(sql);
+	if(!res.error())
+	{
+		CMySqlRow row;
+		if(res.FetchRow(row))
+			return row["Value"];
+	}
+
+	return CMySqlVar();
+}
+
+static pid_t _ReadPid(const char *pszPidFilePath)
+{
+	std::ifstream fPid(pszPidFilePath);
+	std::string str;
+
+	if(fPid.good())
+	{
+		str.assign((std::istreambuf_iterator<char>(fPid)), std::istreambuf_iterator<char>());
+		return (pid_t)atoi(str.c_str());
+	}
+
+	return (pid_t)-1;
+}
+
+static clock64_t _GetHeartbeatUs(void)
+{
+	struct timespec	ts;
+	::clock_gettime(CLOCK_MONOTONIC, &ts);
+	return _TIMESPEC_2_US(ts);
+}
+
+static unsigned long long _GetDirectoryDiscUsage(const char *pszDirName)
+{
+	if(!pszDirName || !*pszDirName)
+		return false;
+	char szCmd[PATH_MAX];
+	sprintf(szCmd, "du -sk %s", pszDirName);
+	FILE *pf = popen(szCmd, "r");
+	if(pf)
+	{
+		unsigned long long nSize = 0;
+		char szLine[32];
+		fgets(szLine, sizeof(szLine), pf);
+		pclose(pf);
+
+		if(sscanf(szLine, "%llu", &nSize) == 1)
+		{
+			return nSize * 1024ULL;
+		}
+	}
+	return 0;
+}
+
+static bool _GetAppTimes(pid_t &pid, const char *pszProcName, GFA_APPCTRL_APPTIMES &at)
+{
+	CProcPidStatFile ppsf;
+	static double fUtimeOld = 0.0, fStimeOld = 0.0;
+	static clock64_t nHeartbeatOld = 0, nStartTime = 0;
+
+	if(pid)
+	{
+		if(!ppsf.ReadFile(pid))
+		{
+			fUtimeOld = fStimeOld = 0.0;
+			nHeartbeatOld = nStartTime = 0;
+			if(!ppsf.ReadFile(pszProcName))
+				return false;
+			pid = ppsf.pid();
+		}
+	}
+	else
+	{
+		if(!ppsf.ReadFile(pszProcName))
+		{
+			fUtimeOld = fStimeOld = 0.0;
+			nHeartbeatOld = nStartTime = 0;
+			return false;
+		}
+		pid = ppsf.pid();
+	}
+	
+	if(!nStartTime)
+		nStartTime = (clock64_t)(ppsf.starttime() * 1000000.0);
+
+	double fInt, fUse, fUtime, fStime;
+	clock64_t nHeartbeat = _GetHeartbeatUs();
+
+	fUtime = ppsf.utime();
+	fStime = ppsf.stime();
+	at.fCpuTime = fUtime + fStime;
+
+	fInt = (double)llabs(nHeartbeat - nStartTime) / 1000000.0;
+	fUse = fUtime + fStime;
+	at.fCpuAvg = fUse * 100.0 / fInt;
+
+	if(nHeartbeatOld)
+	{
+		fInt = (double)llabs(nHeartbeat - nHeartbeatOld) / 1000000.0;
+		fUse = fUtime - fUtimeOld + fStime - fStimeOld;
+		at.fCpuCur = fUse * 100.0 / fInt;
+	}
+
+	fUtimeOld = fUtime;
+	fStimeOld = fStime;
+	nHeartbeatOld = nHeartbeat;
+	return true;
+}
+
+static bool _GetAppMem(pid_t &pid, const char *pszProcName, GFA_APPCTRL_APPMEM &am)
+{
+	if(!pid && pszProcName && *pszProcName)
+	{
+		std::string cmd = CProcFile::FormatString("pidof -s %s", pszProcName);
+		FILE *pf = popen(cmd.c_str(), "r");
+		if(pf)
+		{
+			char szPid[16];
+			fgets(szPid, sizeof(szPid), pf);
+			std::string sPid(szPid);
+			pid = CProcFile::StrToIntegral(_rtrim(sPid, " \r\n\t\v"));
+			pclose(pf);
+		}
+	}
+
+	if(pid)
+	{
+	    CProcMem pm(pid);
+
+		if(pm.Update())
+		{
+			const VM_VALUE &rVmPeak = pm.VmPeak();
+		    am.vmPeak = rVmPeak.valid ? rVmPeak.cb : 0;
+
+			const VM_VALUE &rVmSize = pm.VmSize();
+		    am.vmSize = rVmSize.valid ? rVmSize.cb : 0;
+
+			const VM_VALUE &rVmHWM = pm.VmHWM();
+		    am.vmHWM = rVmHWM.valid ? rVmHWM.cb : 0;
+
+			const VM_VALUE &rVmRSS = pm.VmRSS();
+		    am.vmRSS = rVmRSS.valid ? rVmRSS.cb : 0;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+static size_t _GetTableFileSize(const char *pszDataDir, const char *pszDbsName, const char *pszTableName, const char *pszExt)
+{
+	std::string sdd(pszDataDir);
+	struct stat s;
+	char szPath[PATH_MAX];
+	memset(&s, 0, sizeof(s));
+	sprintf(szPath, "%s/%s/%s.%s", _rtrim(sdd, "/").c_str(), pszDbsName, pszTableName, pszExt);
+	if(!stat(szPath, &s))
+		return (size_t)s.st_size;
+	return 0;
+}
+
+static void _ZeroSDB(GFA_SYSINFO_DATABASE &sdb, bool bInit, bool bRunning)
+{
+	if(!bInit)
+	{
+		memset(&sdb, 0, sizeof(sdb));
+		return;
+	}
+
+	if(!bRunning)
+	{
+		memset(&sdb.svr, 0, sizeof(sdb.svr));
+		memset(&sdb.res, 0, sizeof(sdb.res));
+		memset(&sdb.dbs, 0, sizeof(sdb.dbs));
+		sdb.nNumDatabases = 0;
+	}
+}
+
+static time_t _GetTableCreationDateTime(CMySqlDB &db, const char *pshSchema, const char *pszTable)
+{
+	char sql[1024];
+	sprintf(sql, "SELECT UNIX_TIMESTAMP(`CREATE_TIME`) as `ct` from `information_schema`.`TABLES` WHERE `TABLE_SCHEMA` = '%s' AND `TABLE_NAME` = '%s';", pshSchema, pszTable);
+	CMySqlResult res = db.Query(sql);
+	if(!res.error())
+	{
+		CMySqlRow row;
+		if(res.FetchRow(row))
+		{
+			time_t t = (uint64_t)row["ct"];
+			return t;
+		}
+	}
+	return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+static bool _GetDbInfo(const std::string &sDbUser, const std::string &sDbPass, GFA_SYSINFO_DATABASE &sdb)
+{
+	static bool bSvrInit = false;
+	static cy_time_t nTsLastQ = 0, nTsLastS = 0, nTsLastL = 0;
+	cy_time_t nTsCur = CCycleTimer::GetMilliTick();
+
+	/////////////////////////////////////////////////////////////////////////
+	/////////////////////////////////////////////////////////////////////////
+	/////////////////////////////////////////////////////////////////////////
+	// quick update section
+	
+	if(nTsLastQ && ((nTsLastQ + _QUICK_UPDATE_INTERVAL_MS) > nTsCur))
+		return true;
+	nTsLastQ = nTsCur;
+	sdb.bIsInit = true;
+
+	/////////////////////////////////////////////////////////////////////////
+	// cpu usage
+
+    if(!_GetAppTimes(sdb.svr.pid, _MYSQL_PROCESS_NAME, sdb.res.at))
+	{
+		_ZeroSDB(sdb, true, false);
+		bSvrInit = false;
+		return false;
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+	// memory usage
+
+    if(!_GetAppMem(sdb.svr.pid, _MYSQL_PROCESS_NAME, sdb.res.am))
+	{
+		_ZeroSDB(sdb, true, false);
+		bSvrInit = false;
+		return false;
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+	// connect
+
+	CMySqlDB db;
+
+	if(!db.Connect(_MYSQL_HOST, sDbUser.c_str(), sDbPass.c_str(), NULL))
+	{
+		_ZeroSDB(sdb, true, false);
+		bSvrInit = false;
+		nTsLastS = nTsLastL = 0;
+		return false;
+	}
+
+    sdb.svr.bRunning = true;
+
+	/////////////////////////////////////////////////////////////////////////
+	/////////////////////////////////////////////////////////////////////////
+	/////////////////////////////////////////////////////////////////////////
+	// slow update section
+	
+	if(nTsLastS && ((nTsLastS + _SLOW_UPDATE_INTERVAL_MS) > nTsCur))
+		return true;
+	nTsLastS = nTsCur;
+
+	/////////////////////////////////////////////////////////////////////////
+	//
+
+	if(!bSvrInit)
+	{
+		CMySqlVar vPidFile = _QuerySingleServerVariable(db, "pid_file");
+
+		if(!vPidFile.IsValid())
+		{
+			_ZeroSDB(sdb, true, false);
+			nTsLastS = nTsLastL = 0;
+			return false;
+		}
+
+        sdb.svr.pid = _ReadPid(vPidFile.StrVal());
+
+		CMySqlVar vSvrVersion = _QuerySingleServerVariable(db, "version");
+
+		if(!vSvrVersion.IsValid())
+		{
+			_ZeroSDB(sdb, true, false);
+			nTsLastS = nTsLastL = 0;
+			return false;
+		}
+
+        vSvrVersion.CopyStrVal(sdb.svr.szVersion, GFA_MYSQL_MAX_SVR_VERSION_LENGTH - 1);
+
+		CMySqlVar vDataDir = _QuerySingleServerVariable(db, "datadir");
+
+		if(!vDataDir.IsValid())
+		{
+			_ZeroSDB(sdb, true, false);
+			nTsLastS = nTsLastL = 0;
+			return false;
+		}
+
+        vDataDir.CopyStrVal(sdb.svr.szDataDir, GFA_MYSQL_MAX_DATADIR_LENGTH - 1);
+
+		/////////////////////////////////////////////////////////////////////
+		// InnoDB table space
+
+		CMySqlVar vInnoDbFpT = _QuerySingleServerVariable(db, "innodb_file_per_table");
+
+		if(!vInnoDbFpT.IsValid())
+		{
+			_ZeroSDB(sdb, true, false);
+			nTsLastS = nTsLastL = 0;
+			return false;
+		}
+
+        sdb.svr.bInnoDbFilePerTable = !vInnoDbFpT.StrValCmp("ON");
+		bSvrInit = true;
+	}
+
+	/////////////////////////////////////////////////////////////////////////
+	// Uptime
+	
+	CMySqlVar vUptime = _QuerySingleGlobalStatusValue(db, "Uptime");
+
+	if(!vUptime.IsValid())
+	{
+		_ZeroSDB(sdb, true, false);
+		nTsLastS = nTsLastL = 0;
+		return false;
+	}
+
+    sdb.svr.nUptimeSec = CProcFile::StrToIntegral(vUptime.StrVal());
+
+	/////////////////////////////////////////////////////////////////////////
+	/////////////////////////////////////////////////////////////////////////
+	/////////////////////////////////////////////////////////////////////////
+	// lazy update section
+	
+	if(nTsLastL && ((nTsLastL + _LAZY_UPDATE_INTERVAL_MS) > nTsCur))
+		return true;
+	nTsLastL = nTsCur;
+
+	sdb.svr.nDiscUsageTotal = _GetDirectoryDiscUsage(sdb.svr.szDataDir);
+
+	/////////////////////////////////////////////////////////////////////////
+	// list databases
+
+	CMySqlResult resDb = db.Query("SHOW DATABASES;");
+	sdb.nNumDatabases = 0;
+
+	if(resDb.error())
+	{
+		_ZeroSDB(sdb, true, false);
+		nTsLastS = nTsLastL = 0;
+		return false;
+	}
+
+	CMySqlRow rowDb;
+	char sql[256];
+
+	while(resDb.FetchRow(rowDb))
+	{
+		if(sdb.nNumDatabases >= GFA_MYSQL_MAX_DATABASES)
+			break;
+
+		const CMySqlVar &var = rowDb["Database"];
+
+		if(var.IsValid() && !var.IsNull())
+		{
+			if(!_IsIgnoredDb(var.StrVal()))
+			{
+				GFA_MYSQL_SCHEMA &curDb = sdb.dbs[sdb.nNumDatabases++];
+				var.CopyStrVal(curDb.szName, GFA_MYSQL_MAX_DB_NAME_LENGTH - 1);
+				sprintf(sql, "SHOW TABLE STATUS IN `%s` WHERE `Comment` != 'VIEW';", var.StrVal());
+
+				CMySqlRow rowTab;
+				CMySqlResult resTab = db.Query(sql);
+				curDb.nNumTables = 0;
+				curDb.nSizeTotal = 0;
+
+				if(!resTab.error())
+				{
+					while(resTab.FetchRow(rowTab))
+					{
+						if(curDb.nNumTables >= GFA_MYSQL_MAX_TABLES_PER_DATABASE)
+							break;
+
+						GFA_MYSQL_TABLE &curTab		= curDb.tables[curDb.nNumTables++];
+						const CMySqlVar &vName		= rowTab["Name"];
+						const CMySqlVar &vEngine	= rowTab["Engine"];
+						const CMySqlVar &vVersion	= rowTab["Version"];
+						const CMySqlVar &vRowFormat	= rowTab["Row_format"];
+						const CMySqlVar &vCollation	= rowTab["Collation"];
+
+						if(vVersion.IsValid())
+							curTab.nVersion = vVersion;
+						if(vName.IsValid())
+							vName.CopyStrVal(curTab.szName, GFA_MYSQL_MAX_TABLE_NAME_LENGTH - 1);
+						if(vEngine.IsValid())
+							vEngine.CopyStrVal(curTab.szEngine, GFA_MYSQL_MAX_ENGINE_NAME_LENGTH - 1);
+						if(vRowFormat.IsValid())
+							vRowFormat.CopyStrVal(curTab.szRowFormat, GFA_MYSQL_MAX_ROW_FORMAT_LENGTH - 1);
+						if(vCollation.IsValid())
+							vCollation.CopyStrVal(curTab.szCollation, GFA_MYSQL_MAX_COLLATION_LENGTH - 1);
+
+                        if(!vEngine.StrValCmp("InnoDB") && sdb.svr.bInnoDbFilePerTable)
+						{
+							// InnoDB sizes are rough estimates and not reliable, so we simply use the size of the table files, which
+							// of course works only in a file-per-table tablespace!
+							curTab.nSizeTotal = 0;
+                            curTab.nSizeTotal += _GetTableFileSize(sdb.svr.szDataDir, curDb.szName, curTab.szName, "ibd");
+                            curTab.nSizeTotal += _GetTableFileSize(sdb.svr.szDataDir, curDb.szName, curTab.szName, "frm");
+                            curDb.nSizeTotal += curTab.nSizeTotal;
+						}
+						
+						curTab.nCreateTime = _GetTableCreationDateTime(db, curDb.szName, curTab.szName);
+					}
+				}
+			}
+		}
+	}
+
+	return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+CMySqlInfo::CMySqlInfo(void) : m_bPaused(false)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void* CMySqlInfo::ThreadRoutine(void *pParam)
+{
+	LPEXEC_PARAMS pep = (LPEXEC_PARAMS)pParam;
+	TRACE("Enter CMySqlInfo::ThreadRoutine: TID: %ld\n", syscall(SYS_gettid));
+
+	if(pep)
+	{
+		bool bRun = true;
+		int nRet, nSig;
+		GFA_SYSINFO_DATABASE sdb;
+
+		while(bRun)
+		{
+			if((nRet = WaitSignalTimeout(_QUICK_UPDATE_INTERVAL_MS * 1000, &nSig)) == ETIMEDOUT)
+			{
+				if(!m_bPaused)
+				{
+					::_GetDbInfo(pep->sUser, pep->sPass, sdb);
+					::GfaIpcAppCtrlUpdateDbInfo(pep->hAC, sdb);
+				}
+			}
+			else if(!nRet) // signal received
+			{
+				TRACE("%s signal %d received.\n", "CMySqlInfo::ThreadRoutine", nSig);
+
+				switch(nSig)
+				{
+				case S_Update:
+					::_GetDbInfo(pep->sUser, pep->sPass, sdb);
+					::GfaIpcAppCtrlUpdateDbInfo(pep->hAC, sdb);
+					break;
+				case S_UpdateAll:
+					::_GetDbInfo(pep->sUser, pep->sPass, sdb);
+					::GfaIpcAppCtrlUpdateDbInfo(pep->hAC, sdb);
+					break;
+				case S_Pause:
+					m_bPaused = true;
+					break;
+				case S_Resume:
+					m_bPaused = false;
+					break;
+				case S_Terminate:
+					memset(&sdb, 0, sizeof(sdb));
+					::GfaIpcAppCtrlUpdateDbInfo(pep->hAC, sdb);
+					bRun = false;
+					break;
+				default:
+					break;
+				}
+			}
+			else
+			{
+				TRACE("%s error %d.\n", "CMySqlInfo::ThreadRoutine", nRet);
+			}
+		}
+	}
+
+	TRACE("%s exit.\n", "CMySqlInfo::ThreadRoutine");
+	return NULL;
+}

+ 47 - 0
src/mysqlinfo.h

@@ -0,0 +1,47 @@
+// mysqlinfo.h :
+//
+
+#if !defined(AGD_MYSQLINFO_H__C9901321_C314_4599_982B_EC457FAFDAA0__INCLUDED_)
+#define AGD_MYSQLINFO_H__C9901321_C314_4599_982B_EC457FAFDAA0__INCLUDED_
+
+#include <string>
+#include <gfa/gfaipc.h>
+#include <gfa/ipcpriv.h>
+#include <gfa/thread.h>
+
+/////////////////////////////////////////////////////////////////////////////
+// mysqlinfo.h - Declarations:
+
+class CMySqlInfo : public CThread
+{
+public:
+	typedef enum
+	{
+		S_Update = 1,
+		S_UpdateAll,
+		S_Pause,
+		S_Resume,
+		S_Terminate
+	}Signals;
+
+public:
+	typedef struct _EXEC_PARAMS
+	{
+		HAPPCTRL hAC;
+		std::string sUser;
+		std::string sPass;
+	}EXEC_PARAMS, *LPEXEC_PARAMS;
+	typedef const EXEC_PARAMS *LPCEXEC_PARAMS;
+
+public:
+	CMySqlInfo(void);
+
+protected:
+	virtual void* ThreadRoutine(void *pParam);
+
+private:
+	bool m_bPaused;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_MYSQLINFO_H__C9901321_C314_4599_982B_EC457FAFDAA0__INCLUDED_)

+ 418 - 0
src/mysqlwrap.cpp

@@ -0,0 +1,418 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <utility>
+#include <math.h>
+#include "mysqlwrap.h"
+
+CMySqlDB::CMySqlDB(void) : m_fIsInit(false)
+{
+	memset(&m_mySql, 0, sizeof(m_mySql));
+	Init();
+}
+
+CMySqlDB::~CMySqlDB(void)
+{
+	Close();
+}
+
+bool CMySqlDB::Init(void) throw()
+{
+	if(!m_fIsInit)
+		m_fIsInit = !!mysql_init(&m_mySql);
+	return m_fIsInit;
+}
+
+void CMySqlDB::Close(void) throw()
+{
+	if(m_fIsInit)
+	{
+		mysql_close(&m_mySql);
+		m_fIsInit = false;
+		memset(&m_mySql, 0, sizeof(m_mySql));
+	}
+}
+
+bool CMySqlDB::Connect(	const char *host,
+						const char *user,
+						const char *pass,
+						const char *db,
+						unsigned int port,
+						const char *unix_socket,
+						unsigned long client_flag)
+{
+	if(!Init())
+		return false;
+	return !!mysql_real_connect(&m_mySql, host, user, pass, db, port, unix_socket, client_flag);
+}
+
+int CMySqlDB::SetCharSet(const char *pszCharset)
+{
+	return !mysql_set_character_set(&m_mySql, pszCharset);
+}
+
+int CMySqlDB::Options(enum mysql_option option, const void *arg)
+{
+	return !mysql_options(&m_mySql, option, arg); 
+}
+
+CMySqlResult CMySqlDB::Query(const char *sql)
+{
+	bool bErr = true;
+	MYSQL_RES *pRes = NULL;
+	
+	if(m_fIsInit)
+	{
+		if(!(bErr = !!mysql_query(&m_mySql, sql)))
+			pRes = mysql_store_result(&m_mySql);
+	}
+
+	return CMySqlResult(pRes, bErr);
+}
+
+int CMySqlDB::SelectDB(const char *db)
+{
+	return mysql_select_db(&m_mySql, db);
+}
+
+std::string CMySqlDB::EscapeStringQuote(const char *from, char quote)
+{
+	if(	m_fIsInit &&
+		from && *from)
+	{
+		size_t len = strlen(from);
+		size_t lenBuf = len * 2 + 1;
+		char *buf = (char*)alloca(lenBuf);
+#if MYSQL_VERSION_ID >= 50706
+		if(mysql_real_escape_string_quote(&m_mySql, buf, from, (unsigned long)len, quote))
+#else	//	MYSQL_VERSION_ID
+        (void)quote;
+		if(mysql_real_escape_string(&m_mySql, buf, from, (unsigned long)len))
+#endif	//	MYSQL_VERSION_ID
+			return buf;
+	}
+
+	return "";
+}
+
+std::string CMySqlDB::EscapeString(const char *from)
+{
+	if(	m_fIsInit &&
+		from && *from)
+	{
+		size_t len = strlen(from);
+		size_t lenBuf = len * 2 + 1;
+		char *buf = (char*)alloca(lenBuf);
+		if(mysql_real_escape_string(&m_mySql, buf, from, (unsigned long)len))
+			return buf;
+	}
+
+	return "";
+}
+
+std::string CMySqlDB::LastError(void)
+{
+	if(m_fIsInit)
+		return mysql_error(&m_mySql);
+	else
+		return "MySQL not initialized!";
+}
+
+unsigned int CMySqlDB::LastErrno(void)
+{
+	if(m_fIsInit)
+		return mysql_errno(&m_mySql);
+	else
+		return -1;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+CMySqlResult::CMySqlResult(MYSQL_RES *pRes, bool err) :	m_pRes(pRes),
+														m_bError(err)
+{
+}
+
+CMySqlResult::~CMySqlResult(void)
+{
+	Free();
+}
+
+void CMySqlResult::Free(void)
+{
+	if(m_pRes)
+	{
+		mysql_free_result(m_pRes);
+		m_pRes = NULL;
+	}
+}
+
+my_ulonglong CMySqlResult::RowCount(void) const
+{
+	if(m_pRes)
+		return mysql_num_rows(m_pRes);
+	return 0;
+}
+
+unsigned int CMySqlResult::FieldCount(void) const
+{
+	if(m_pRes)
+		return mysql_num_fields(m_pRes);
+	return 0;
+}
+
+MYSQL_ROW CMySqlResult::FetchRow(void)
+{
+	if(m_pRes)
+		return mysql_fetch_row(m_pRes);
+	return NULL;
+}
+
+bool CMySqlResult::FetchRow(CMySqlRow &row) const
+{
+	if(m_pRes)
+	{
+		MYSQL_ROW pRow = mysql_fetch_row(m_pRes);
+		if(pRow)
+			return row.Create(FieldCount(), FetchFields(), pRow);
+	}
+
+	return false;
+}
+
+const MYSQL_FIELD * CMySqlResult::FetchFields(void) const
+{
+	if(m_pRes)
+		return mysql_fetch_fields(m_pRes);
+	return NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+const CMySqlVar CMySqlRow::m_vNul;
+
+CMySqlRow::CMySqlRow(void) : m_bValid(false), m_nFieldCount(0)
+{
+}
+
+CMySqlRow::~CMySqlRow(void)
+{
+}
+
+void CMySqlRow::Clear(void)
+{
+	m_bValid = false;
+	m_nFieldCount = 0;
+	m_fields.clear();
+}
+
+bool CMySqlRow::Create(unsigned int nFieldCount, const MYSQL_FIELD *pFields, MYSQL_ROW pRow)
+{
+	Clear();
+	
+	if((nFieldCount > 0) && pFields && pRow)
+	{
+		for(m_nFieldCount = 0; m_nFieldCount < nFieldCount; ++m_nFieldCount)
+		{
+			std::string s(pFields[m_nFieldCount].name);
+			if(!m_fields.emplace(s, std::move(CMySqlVar(pFields[m_nFieldCount], pRow[m_nFieldCount]))).second)
+			{
+				Clear();
+				return false;
+			}
+		}
+		
+		m_bValid = true;
+	}
+	
+	return m_bValid;
+}
+
+const CMySqlVar& CMySqlRow::Value(const char *pszFieldname) const
+{
+	if(m_bValid)
+	{
+		CMySqlFieldMap::const_iterator it;
+		if(FindField(pszFieldname, it))
+			return it->second;
+	}
+
+	return m_vNul;
+}
+
+CMySqlRow::CMySqlFieldMap::const_iterator CMySqlRow::begin(void)
+{
+	return m_fields.begin();
+}
+
+CMySqlRow::CMySqlFieldMap::const_iterator CMySqlRow::end(void)
+{
+	return m_fields.end();
+}
+
+bool CMySqlRow::FindField(const char *pszFieldname, CMySqlFieldMap::const_iterator &it) const
+{
+	if(m_bValid)
+	{
+		it = m_fields.find(pszFieldname);
+		return (it != m_fields.end());
+	}
+
+	return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+CMySqlVar::CMySqlVar(void)
+{
+	Clear();
+}
+
+CMySqlVar::CMySqlVar(const MYSQL_FIELD &rField, const char *pszVal)
+{
+	FromField(rField, pszVal);
+}
+
+CMySqlVar::CMySqlVar(CMySqlVar &&o) : 	m_bValid(std::move(o.m_bValid)), m_bInteger(std::move(o.m_bInteger)), m_bReal(std::move(o.m_bReal)), m_bUnsigned(std::move(o.m_bUnsigned)), m_bString(std::move(o.m_bString)),
+										m_strVal(std::move(o.m_strVal)), m_strFieldname(std::move(o.m_strFieldname)), m_sqlFt(std::move(o.m_sqlFt)), m_numVal(std::move(o.m_numVal))
+{
+	if(this != &o)
+		o.Clear();
+}
+
+CMySqlVar::CMySqlVar(const CMySqlVar &o) : 	m_bValid(o.m_bValid), m_bInteger(o.m_bInteger), m_bReal(o.m_bReal), m_bUnsigned(o.m_bUnsigned), m_bString(o.m_bString),
+											m_strVal(o.m_strVal), m_strFieldname(o.m_strFieldname), m_sqlFt(o.m_sqlFt), m_numVal(o.m_numVal)
+{
+}
+
+CMySqlVar::~CMySqlVar(void)
+{
+}
+
+void CMySqlVar::Clear(void)
+{
+	m_bValid		= false;
+	m_bUnsigned		= false;
+	m_bInteger		= false;
+	m_bReal			= false;
+	m_bString		= false;
+	m_strFieldname.clear();
+	m_strVal.clear();
+	m_sqlFt			= MYSQL_TYPE_NULL;
+	m_numVal.uVal	= 0;
+}
+
+bool CMySqlVar::FromField(const MYSQL_FIELD &rField, const char *pszVal)
+{
+	Clear();
+
+	if(pszVal)
+	{
+		char *pszEndPtr;
+		bool bUnsigned = !!(rField.flags & UNSIGNED_FLAG);
+		m_strFieldname = rField.name;
+
+		switch(rField.type)
+		{
+		case MYSQL_TYPE_BIT:
+		case MYSQL_TYPE_TINY:		//  8 Bit =>  8 Bit
+		case MYSQL_TYPE_SHORT:		// 16 Bit => 16 Bit
+		case MYSQL_TYPE_INT24:		// 24 Bit => 32 Bit
+		case MYSQL_TYPE_LONG:		// 32 Bit => 32 Bit
+		case MYSQL_TYPE_LONGLONG:	// 64 Bit => 64 Bit
+			if(bUnsigned)
+			{
+				int64_t val = strtoll(pszVal, &pszEndPtr, 10);
+				if(!*pszVal || ((val == LLONG_MIN || val == LLONG_MAX) && errno == ERANGE) || *pszEndPtr)
+					m_bValid = false;
+				else
+				{
+					m_sqlFt = rField.type;
+					m_strVal = pszVal;
+					m_numVal.iVal = val;
+					m_bUnsigned = true;
+					m_bInteger = true;
+					m_bValid = true;
+				}
+			}
+			else
+			{
+				uint64_t val = strtoull(pszVal, &pszEndPtr, 10);
+				if(!*pszVal || (val == ULLONG_MAX && errno == ERANGE) || *pszEndPtr)
+					m_bValid = false;
+				else
+				{
+					m_sqlFt = rField.type;
+					m_strVal = pszVal;
+					m_numVal.uVal = val;
+					m_bInteger = true;
+					m_bValid = true;
+				}
+			}
+			break;
+
+		case MYSQL_TYPE_FLOAT:
+		case MYSQL_TYPE_DOUBLE:
+		case MYSQL_TYPE_DECIMAL:
+		case MYSQL_TYPE_NEWDECIMAL:
+			{
+				double val = strtod(pszVal, &pszEndPtr);
+				if(!*pszVal || ((val == HUGE_VAL || val == -HUGE_VAL) && errno == ERANGE) || *pszEndPtr)
+					m_bValid = false;
+				else
+				{
+					m_sqlFt = rField.type;
+					m_strVal = pszVal;
+					m_numVal.fVal = val;
+					m_bReal = true;
+					m_bValid = true;
+				}
+			}
+			break;
+
+		case MYSQL_TYPE_VARCHAR:
+		case MYSQL_TYPE_VAR_STRING:
+		case MYSQL_TYPE_STRING:
+			m_sqlFt = rField.type;
+			m_strVal = pszVal;
+			m_bString = true;
+			m_bValid = true;
+			break;
+
+		case MYSQL_TYPE_DATE:
+		case MYSQL_TYPE_TIME:
+		case MYSQL_TYPE_DATETIME:
+			m_sqlFt = rField.type;
+			m_strVal = pszVal;
+			m_bValid = true;
+			break;
+		case MYSQL_TYPE_TIMESTAMP:
+		case MYSQL_TYPE_YEAR:
+		case MYSQL_TYPE_NEWDATE:
+		case MYSQL_TYPE_ENUM:
+		case MYSQL_TYPE_SET:
+		case MYSQL_TYPE_TINY_BLOB:
+		case MYSQL_TYPE_MEDIUM_BLOB:
+		case MYSQL_TYPE_LONG_BLOB:
+		case MYSQL_TYPE_BLOB:
+		case MYSQL_TYPE_GEOMETRY:
+			break;
+
+		case MYSQL_TYPE_NULL:
+			m_sqlFt = rField.type;
+			m_strVal = "";
+			m_bValid = true;
+			break;
+
+		default:
+			break;
+		}
+	}
+
+	return m_bValid;
+}

+ 191 - 0
src/mysqlwrap.h

@@ -0,0 +1,191 @@
+// mysqlwrap.h :
+//
+
+#if !defined(AGD_MYSQLWRAP_H__0FDF8710_B26A_4631_A025_705731DF316E__INCLUDED_)
+#define AGD_MYSQLWRAP_H__0FDF8710_B26A_4631_A025_705731DF316E__INCLUDED_
+
+#include <stdint.h>
+#include <mysql/mysql.h>
+#include <string>
+#include <unordered_map>
+#include <functional>
+
+/////////////////////////////////////////////////////////////////////////////
+// mysqlwrap.h - Declarations:
+
+class CMySqlVar
+{
+public:
+	CMySqlVar(void);
+	CMySqlVar(CMySqlVar &&o);
+	CMySqlVar(const CMySqlVar &o);
+	CMySqlVar(const MYSQL_FIELD &rField, const char *pszVal);
+	virtual ~CMySqlVar(void);
+
+	void Clear(void);
+
+	bool FromField(const MYSQL_FIELD &rField, const char *pszVal);
+
+	inline bool IsValid(void) const {
+		return m_bValid;}
+
+	inline bool IsNumeric(void) const {
+		return m_bValid && (m_bInteger || m_bReal);}
+
+	inline bool IsReal(void) const {
+		return m_bValid && m_bReal;}
+
+	inline bool IsUnsigned(void) const {
+		return m_bValid && m_bUnsigned;}
+
+	inline bool IsNull(void) const {
+		return m_sqlFt == MYSQL_TYPE_NULL;}
+		
+	inline enum enum_field_types GetSqlFieldType(void) const {
+		return m_sqlFt;}
+
+	inline const char* StrVal(void) const {
+		return m_strVal.c_str();}
+
+	inline int StrValCmp(const char *pszCmp) const {
+		return m_strVal.compare(pszCmp);}
+
+	inline size_t CopyStrVal(char *s, size_t len, size_t pos = 0) const {
+		return m_strVal.copy(s, len, pos);}
+
+	inline const char* Fieldname(void) const {
+		return m_strFieldname.c_str();}
+
+	inline int FieldnameCmp(const char *pszName) const {
+		return m_strFieldname.compare(pszName);}
+
+	operator int (void) const {
+		return m_bInteger ? (int)m_numVal.iVal : 0;}
+
+	operator int64_t (void) const {
+		return m_bInteger ? m_numVal.iVal : 0;}
+
+	operator uint64_t (void) const {
+		return m_bInteger ? m_numVal.uVal : 0;}
+
+#ifndef _TARGET_BUILD
+	operator long long (void) const {
+		return m_bInteger ? (long long)m_numVal.iVal : 0;}
+
+	operator unsigned long long (void) const {
+		return m_bInteger ? (unsigned long long)m_numVal.uVal : 0;}
+#endif  //  _TARGET_BUILD
+
+	operator bool (void) const {
+		return m_bInteger ? !!m_numVal.uVal : false;}
+
+	operator double (void) const {
+		return m_bReal ? m_numVal.fVal : 0.0;}
+
+private:
+	bool m_bValid;
+	bool m_bInteger;
+	bool m_bReal;
+	bool m_bUnsigned;
+	bool m_bString;
+	std::string m_strVal;
+	std::string m_strFieldname;
+	enum enum_field_types m_sqlFt;
+	union
+	{
+		int64_t		iVal;
+		uint64_t	uVal;
+		double		fVal;
+	}m_numVal;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CMySqlRow
+{
+public:
+	typedef std::unordered_map<std::string, CMySqlVar> CMySqlFieldMap;
+
+public:
+	CMySqlRow(void);
+	~CMySqlRow(void);
+
+public:
+	void Clear(void);
+	bool Create(unsigned int nFieldCount, const MYSQL_FIELD *pFields, MYSQL_ROW row);
+	const CMySqlVar& Value(const char *pszFieldname) const;
+
+	inline const CMySqlVar& operator [](const char *pszFieldname) const {
+		return Value(pszFieldname);}
+
+	inline bool IsValid(void) const {
+		return m_bValid;}
+		
+	CMySqlFieldMap::const_iterator begin(void);
+	CMySqlFieldMap::const_iterator end(void);
+
+private:
+	bool FindField(const char *pszFieldname, CMySqlFieldMap::const_iterator &it) const;
+
+private:
+	bool m_bValid;
+	unsigned int m_nFieldCount;
+	CMySqlFieldMap m_fields;
+	static const CMySqlVar m_vNul;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CMySqlResult
+{
+public:
+	CMySqlResult(MYSQL_RES *pRes, bool err);
+	virtual ~CMySqlResult(void);
+
+public:
+	void Free(void);
+
+	my_ulonglong RowCount(void) const;
+	unsigned int FieldCount(void) const;
+	MYSQL_ROW FetchRow(void);
+	bool FetchRow(CMySqlRow &row) const;
+	const MYSQL_FIELD * FetchFields(void) const;
+
+	inline bool error(void) const {
+		return m_bError;}
+
+private:
+	MYSQL_RES *m_pRes;
+	bool m_bError;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CMySqlDB
+{
+public:
+	CMySqlDB(void);
+	virtual ~CMySqlDB(void);
+
+public:
+	bool Init(void) throw();
+	void Close(void) throw();
+
+	bool Connect(const char *host, const char *user, const char *pass, const char *db, unsigned int port = 0, const char *unix_socket = NULL, unsigned long client_flag = 0);
+	CMySqlResult Query(const char *sql);
+	int SelectDB(const char *db);
+	int SetCharSet(const char *pszCharset);
+	int Options(enum mysql_option option, const void *arg);
+
+	std::string EscapeStringQuote(const char *from, char quote);
+	std::string EscapeString(const char *from);
+	std::string LastError(void);
+	unsigned int LastErrno(void);
+
+private:
+	MYSQL m_mySql;
+	bool m_fIsInit;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_MYSQLWRAP_H__0FDF8710_B26A_4631_A025_705731DF316E__INCLUDED_)

+ 157 - 0
src/procfile.cpp

@@ -0,0 +1,157 @@
+#include <limits.h>
+#include <stdarg.h>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <regex>
+#include <cmath>
+#include "procfile.h"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+const std::string CProcFile::m_sEmpty;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+CProcFile::CProcFile(void)
+{
+	m_nClkPerSec = sysconf(_SC_CLK_TCK);
+}
+
+CProcFile::~CProcFile(void)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+std::string CProcFile::FormatString(const char *fmt, ...)
+{
+	int n;
+	std::string s;
+	char *p = NULL;
+	va_list ap;
+	va_start(ap, fmt);
+	n = ::vasprintf(&p, fmt, ap);
+	va_end(ap);
+	if(n >= 0)
+	{
+		s = p;
+		free(p);
+	}
+	return s;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+int CProcFile::SplitLine(const std::string &str, std::vector<std::string> &vec, const char *rxe)
+{
+	if(str.empty())
+		return 0;
+
+	int nElems = 0;
+	std::regex reg(rxe);
+	const std::sregex_token_iterator end;
+
+	for(std::sregex_token_iterator iter(str.begin(), str.end(), reg); iter != end; ++iter)
+	{
+		vec.push_back(*iter);
+		nElems++;
+	}
+
+	return nElems;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+long long CProcFile::StrToIntegral(const std::string &str, long long nDefault, int nBase)
+{
+	if(str.empty())
+		return nDefault;
+
+	char *pszEnd = NULL;
+	long long val = strtoll(str.c_str(), &pszEnd, nBase);
+	
+	if((((val == LLONG_MIN) || (val == LLONG_MAX)) && errno == ERANGE) || (pszEnd && *pszEnd))
+		val = nDefault;
+
+	return val;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+double CProcFile::StrToReal(const std::string &str, double fDefault)
+{
+	if(str.empty())
+		return fDefault;
+
+	char *pszEnd = NULL;
+	double val = strtod(str.c_str(), &pszEnd);
+	
+	if((((val == HUGE_VAL) || (val == -HUGE_VAL)) && errno == ERANGE) || (pszEnd && *pszEnd))
+		val = fDefault;
+
+	return val;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+bool CProcFile::ReadFile(const char *pszFilePath)
+{
+	m_vMap.clear();
+
+	std::string sLine;
+	std::ifstream fs(pszFilePath);
+	bool bRet = fs.good();
+
+	while(fs.good())
+	{
+		std::vector<std::string> v;
+		std::getline(fs, sLine);
+
+		if(SplitLine(sLine, v) > 1)
+		{
+			std::string s(v[0]);
+//			v.erase(v.begin());
+			m_vMap[s] = v;
+		}
+	}
+
+	return bRet;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+static const std::string & _rtrim(std::string &s)
+{
+    s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
+	return s;
+}
+
+bool CProcPidStatFile::ReadFile(const char *pszProcName)
+{
+	if(!pszProcName || !*pszProcName)
+		return false;
+	std::string cmd = FormatString("pidof -s %s", pszProcName);
+	FILE *pf = popen(cmd.c_str(), "r");
+	if(pf)
+	{
+		char szPid[16];
+		fgets(szPid, sizeof(szPid), pf);
+		std::string sPid(szPid);
+		pid_t pid = StrToIntegral(_rtrim(sPid));
+		pclose(pf);
+		return ReadFile(pid);
+	}
+	return false;
+}

+ 121 - 0
src/procfile.h

@@ -0,0 +1,121 @@
+// procfile.h :
+//
+
+#if !defined(AGD_PROCFILE_H__2D6A6629_BE8B_45D1_949D_3A434336E4DF__INCLUDED_)
+#define AGD_PROCFILE_H__2D6A6629_BE8B_45D1_949D_3A434336E4DF__INCLUDED_
+
+#include <unistd.h>
+#include <string>
+#include <vector>
+#include <map>
+
+#ifdef __cplusplus
+
+// https://man7.org/linux/man-pages/man5/proc.5.html
+
+/////////////////////////////////////////////////////////////////////////////
+// procfile.h - Declarations:
+
+#define _DEFAULT_REGEX_EXPRESSION			"[^\\s]+"
+
+class CProcFile
+{
+public:
+	typedef std::map<std::string, std::vector<std::string>> KeyMultiValueMap;
+
+public:
+	CProcFile(void);
+	virtual ~CProcFile(void);
+
+	virtual bool ReadFile(const char *pszFilePath);
+	
+	const std::string & GetValue(const std::string &sKey, int index) {
+		if(index >= 0)
+		{
+			const std::vector<std::string> &m = m_vMap[sKey];
+			if(m.size() > (size_t)index)
+				return m[index];
+		}
+		return m_sEmpty;
+	}
+
+	const std::vector<std::string> & operator[] (const std::string &sKey) {
+		return m_vMap[sKey];
+	}
+
+public:
+	static std::string FormatString(const char *fmt, ...);
+	static int SplitLine(const std::string &str, std::vector<std::string> &vec, const char *rxe = _DEFAULT_REGEX_EXPRESSION);
+	static long long StrToIntegral(const std::string &str, long long nDefault = 0, int nBase = 10);
+	static double StrToReal(const std::string &str, double fDefault = 0.0);
+
+protected:
+	long m_nClkPerSec;
+
+private:
+	static const std::string m_sEmpty;
+	KeyMultiValueMap m_vMap;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CProcStatFile : public CProcFile
+{
+public:
+	virtual bool ReadFile(void) {
+		return CProcFile::ReadFile("/proc/stat");}
+
+	double utime(void) {
+		double val = StrToReal(GetValue("cpu", 1));
+		return (val / (double)m_nClkPerSec);
+	}
+
+	double stime(void) {
+		double val = StrToReal(GetValue("cpu", 3));
+		return (val / (double)m_nClkPerSec);
+	}
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+class CProcPidStatFile : public CProcStatFile
+{
+public:
+	virtual bool ReadFile(pid_t pid) {
+		m_sPid = FormatString("%d", pid);
+		std::string s = FormatString("/proc/%d/stat", pid);
+		return CProcFile::ReadFile(s.c_str());
+	}
+
+	virtual bool ReadFile(const char *pszProcName);
+
+	const std::string & operator[] (int index) {
+		return GetValue(m_sPid, index);
+	}
+	
+	pid_t pid(void) {
+		return (pid_t)StrToIntegral((*this)[0]);
+	}
+	
+	double utime(void) {
+		double val = StrToReal((*this)[13]);
+		return (val / (double)m_nClkPerSec);
+	}
+	
+	double stime(void) {
+		double val = StrToReal((*this)[14]);
+		return (val / (double)m_nClkPerSec);
+	}
+
+	unsigned long long starttime(void) {
+		double val = StrToReal((*this)[21]);
+		return (val / (double)m_nClkPerSec);
+	}
+
+private:
+	std::string m_sPid;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	__cplusplus
+#endif	//	!defined(AGD_PROCFILE_H__2D6A6629_BE8B_45D1_949D_3A434336E4DF__INCLUDED_)

+ 285 - 0
src/spiinfo.cpp

@@ -0,0 +1,285 @@
+#include <stdio.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/statvfs.h>
+#include <sys/statfs.h>
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <poll.h>
+#include <signal.h>
+#include <errno.h>
+#include "spiinfo.h"
+
+#define _SDA_UPDATE_INTERVAL_MS					200ULL
+#define _SYS_FS_TIVA_ROOT						"/sys/gfa/tiva"
+#define _SYS_FS_TIVA_ADC						"/sys/gfa/tiva/adc"
+#define _SYS_FS_TIVA_ADC_BIN					"/sys/gfa/tiva/adc/AdcBin"
+#define _SYS_FS_TIVA_UPTIME						"/sys/gfa/tiva/uptime"
+#define _SYS_FS_TIVA_FIRMWARE_VER				"/sys/gfa/tiva/firmware/version"
+
+/////////////////////////////////////////////////////////////////////////////
+
+#ifdef _DEBUG
+#define TRACE(...)							fprintf(stdout, __VA_ARGS__), fflush(stdout)
+#else	//	_DEBUG
+#define TRACE(...)
+#endif	//	_DEBUG
+
+#define UNUSED(v)							(void)v
+#define _countof(a)							(sizeof(a) / sizeof(*a))
+
+/////////////////////////////////////////////////////////////////////////////
+
+CSpiInfo::CSpiInfo(void) : m_bPaused(false), m_bStateTransition(false)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+int CSpiInfo::ReadADC(int fd, GFA_SYSINFO_SPI &spi) const
+{
+	int nRet;
+	TIVA_ADC tadc;
+
+	if((nRet = read(fd, &tadc, sizeof(tadc))) == sizeof(tadc))
+	{
+		spi.adc.fVoltagePowerSupply		= (float)tadc.UVers / 100.0 + 0.4;
+		spi.adc.fVoltageBackupBattery	= (float)tadc.UBatV3 / 100.0;
+		spi.adc.fVoltageSys				= (float)tadc.UV5Vsys / 100.0;
+		spi.adc.fVoltageBattery			= (float)tadc.UV3V6Bat / 100.0;
+		spi.adc.fTemperatureBoard		= (float)tadc.Temp / 10.0;
+		spi.adc.fTemperatureTiva		= 147.5 - 187.5 * (float)tadc.TempTIVA / 4096.0;
+		nRet = lseek(fd, 0, SEEK_SET);
+	}
+
+	return nRet;
+}
+
+int CSpiInfo::ReadTivaUptime(int fd, GFA_SYSINFO_SPI &spi) const
+{
+	int nRet;
+	char szUptime[64];
+
+	if((nRet = read(fd, szUptime, sizeof(szUptime) - 1)) > 0)
+	{
+		szUptime[nRet] = 0;
+		spi.nUptimeSec = (unsigned int)((unsigned long long)atoll(szUptime) / 1000ULL);
+		nRet = lseek(fd, 0, SEEK_SET);
+	}
+
+	return nRet;
+}
+
+int CSpiInfo::ReadFirmwareVersion(int fd, GFA_SYSINFO_SPI &spi) const
+{
+	int nRet, hw, sw;
+	char szVersion[64];
+
+	if((nRet = read(fd, szVersion, sizeof(szVersion) - 1)) > 0)
+	{
+		szVersion[nRet] = 0;
+		if(sscanf(szVersion, "%d %d", &hw, &sw) == 2)
+		{
+			spi.nHwVersion = hw;
+			spi.nSwVersion = sw;
+		}
+		nRet = lseek(fd, 0, SEEK_SET);
+	}
+
+	return nRet;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void* CSpiInfo::ThreadRoutine(void *pParam)
+{
+	LPEXEC_PARAMS pep = (LPEXEC_PARAMS)pParam;
+    TRACE("Enter CSpiInfo::ThreadRoutine: TID: %ld\n", syscall(SYS_gettid));
+
+	if(pep)
+	{
+		unsigned long long nWaitIntvUs = 0;
+		bool bInitializing = true, bRun = false;
+		int nRet, nSig;
+		GFA_SYSINFO_SPI spi;
+		pollfd pfd[3];
+
+		do
+		{
+			do
+			{
+				for(size_t i = 0; i < _countof(pfd); ++i)
+				{
+					pfd[i].fd = -1;
+				}
+
+				if((pfd[0].fd = open(_SYS_FS_TIVA_ADC_BIN, O_RDONLY, 0)) < 0)
+					break;
+				pfd[0].events = POLLPRI;
+				pfd[0].revents = 0;
+
+				if((pfd[1].fd = open(_SYS_FS_TIVA_UPTIME, O_RDONLY, 0)) < 0)
+					break;
+				pfd[1].events = POLLPRI;
+				pfd[1].revents = 0;
+
+				if((pfd[2].fd = open(_SYS_FS_TIVA_FIRMWARE_VER, O_RDONLY, 0)) < 0)
+					break;
+				pfd[2].events = POLLPRI;
+				pfd[2].revents = 0;
+
+				bInitializing = false;
+				bRun = true;
+			}
+			while(false);
+
+			while(bRun)
+			{
+				nWaitIntvUs = m_bPaused ? (_SDA_UPDATE_INTERVAL_MS * 1000ULL) : 0;
+
+				if((nRet = WaitSignalTimeout(nWaitIntvUs, &nSig)) == ETIMEDOUT)
+				{
+					if(!m_bPaused)
+					{
+						while((nRet = poll(pfd, _countof(pfd), _SDA_UPDATE_INTERVAL_MS)) > 0)
+						{
+							if(pfd[0].revents & POLLPRI)
+							{
+								if(!(nRet = ReadADC(pfd[0].fd, spi)))
+								{
+									::GfaIpcAppCtrlUpdateSpiInfo(pep->hAC, spi, false);
+								}
+								else if(nRet < 0)
+								{
+									TRACE("ReadADC failed: %s\n", strerror(errno));
+									bInitializing = true;
+									bRun = false;
+									break;
+								}
+							}
+
+							if(pfd[1].revents & POLLPRI)
+							{
+								if(!(nRet = ReadTivaUptime(pfd[1].fd, spi)))
+								{
+									::GfaIpcAppCtrlUpdateSpiInfo(pep->hAC, spi, false);
+								}
+								else if(nRet < 0)
+								{
+									TRACE("ReadTivaUptime failed: %s\n", strerror(errno));
+									bInitializing = true;
+									bRun = false;
+									break;
+								}
+							}
+
+							if(pfd[2].revents & POLLPRI)
+							{
+								if(!(nRet = ReadFirmwareVersion(pfd[2].fd, spi)))
+								{
+									::GfaIpcAppCtrlUpdateSpiInfo(pep->hAC, spi, false);
+								}
+								else if(nRet < 0)
+								{
+									TRACE("ReadFirmwareVersion failed: %s\n", strerror(errno));
+									bInitializing = true;
+									bRun = false;
+									break;
+								}
+							}
+						}
+
+						if(nRet < 0)
+						{
+							TRACE("poll failed: %s\n", strerror(errno));
+							bInitializing = true;
+							bRun = false;
+							break;
+						}
+					}
+
+					m_bStateTransition = false;
+				}
+				else if(!nRet) // signal received
+				{
+					TRACE("%s signal %d received.\n", "CSpiInfo::ThreadRoutine", nSig);
+
+					switch(nSig)
+					{
+					case S_Init:
+						break;
+					case S_Update:
+						break;
+					case S_Pause:
+						m_bStateTransition = !m_bPaused;
+						m_bPaused = true;
+						::GfaIpcAppCtrlUpdateSpiInfo(pep->hAC, spi, true);
+						break;
+					case S_Resume:
+						m_bStateTransition = m_bPaused;
+						m_bPaused = false;
+						break;
+					case S_Terminate:
+						bInitializing = false;
+						bRun = false;
+						break;
+					default:
+						break;
+					}
+				}
+				else
+				{
+					TRACE("WaitSignalTimeout failed: %s\n", strerror(errno));
+					bInitializing = true;
+					bRun = false;
+					break;
+				}
+			}
+
+			::GfaIpcAppCtrlUpdateSpiInfo(pep->hAC, spi, true);
+
+			for(size_t i = 0; i < _countof(pfd); ++i)
+			{
+				if(pfd[i].fd >= 0)
+					close(pfd[i].fd);
+			}
+
+			if(!bRun && bInitializing)
+			{
+				if(!WaitSignalTimeout(_SDA_UPDATE_INTERVAL_MS * 5000ULL, &nSig))
+				{
+					TRACE("%s signal %d received.\n", "CSpiInfo::ThreadRoutine", nSig);
+
+					switch(nSig)
+					{
+					case S_Init:
+						break;
+					case S_Update:
+						break;
+					case S_Pause:
+						m_bStateTransition = !m_bPaused;
+						m_bPaused = true;
+						break;
+					case S_Resume:
+						m_bStateTransition = m_bPaused;
+						m_bPaused = false;
+						break;
+					case S_Terminate:
+						bInitializing = false;
+						bRun = false;
+						break;
+					default:
+						break;
+					}
+				}
+			}
+		}
+		while(bInitializing);
+	}
+
+	TRACE("%s exit.\n", "CSpiInfo::ThreadRoutine");
+	return NULL;
+}

+ 51 - 0
src/spiinfo.h

@@ -0,0 +1,51 @@
+// spiinfo.h :
+//
+
+#if !defined(AGD_SPIINFO_H__412BF45C_AE50_4A79_984F_2AE495DF86B1__INCLUDED_)
+#define AGD_SPIINFO_H__412BF45C_AE50_4A79_984F_2AE495DF86B1__INCLUDED_
+
+#include <gfa/gfaipc.h>
+#include <gfa/ipcpriv.h>
+#include <gfa/thread.h>
+#include "gfaspi.h"
+
+/////////////////////////////////////////////////////////////////////////////
+// spiinfo.h - Declarations:
+
+class CSpiInfo : public CThread
+{
+public:
+	typedef enum
+	{
+		S_Init = 1,
+		S_Update,
+		S_Pause,
+		S_Resume,
+		S_Terminate
+	}Signals;
+
+public:
+	typedef struct _EXEC_PARAMS
+	{
+		HAPPCTRL hAC;
+	}EXEC_PARAMS, *LPEXEC_PARAMS;
+	typedef const EXEC_PARAMS *LPCEXEC_PARAMS;
+
+public:
+	CSpiInfo(void);
+
+protected:
+	virtual void* ThreadRoutine(void *pParam);
+
+private:
+	int ReadADC(int fd, GFA_SYSINFO_SPI &spi) const;
+	int ReadTivaUptime(int fd, GFA_SYSINFO_SPI &spi) const;
+	int ReadFirmwareVersion(int fd, GFA_SYSINFO_SPI &spi) const;
+
+private:
+	bool m_bPaused;
+	bool m_bStateTransition;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_SPIINFO_H__412BF45C_AE50_4A79_984F_2AE495DF86B1__INCLUDED_)

+ 795 - 0
src/stgdevinfo.cpp

@@ -0,0 +1,795 @@
+#include <stdio.h>
+#include <stdio.h>
+#include <string.h>
+#include <string>
+#include <vector>
+#include <map>
+#include <ext/stdio_filebuf.h>
+#include <iostream>
+#include <fstream>
+#include <signal.h>
+#include <sys/statvfs.h>
+#include <sys/statfs.h>
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <libudev.h>
+#include <poll.h>
+#include <errno.h>
+#include <getopt.h>
+#include <gfa/gfasitarautils.h>
+#include "stgdevinfo.h"
+
+/////////////////////////////////////////////////////////////////////////////
+
+#ifdef _DEBUG
+#define TRACE(...)							fprintf(stdout, __VA_ARGS__), fflush(stdout)
+#else	//	_DEBUG
+#define TRACE(...)
+#endif	//	_DEBUG
+
+#define UNUSED(v)							(void)v
+#define _countof(a)							(sizeof(a) / sizeof(*a))
+#define _STR_EQUALS(s, t)					(!!s && !strcmp(s, t))
+
+/////////////////////////////////////////////////////////////////////////////
+
+#define _MOUNTS_FILE						"/proc/mounts"
+#define _INTERNAL_EMMC_DISK					"mmcblk1"
+#define _INTERNAL_EMMC_PART1				"mmcblk1p1"
+#define _INTERNAL_EMMC_PART2				"mmcblk1p2"
+
+#define _POLL_INTERVAL_MS					500
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef std::map<std::string, std::string>	MountMap;
+
+/////////////////////////////////////////////////////////////////////////////
+
+static long long	_NumberFromString(const char *pszString, int base = 10, bool *pbErr = NULL);
+static const char*	_ReadDevPropertyValue(struct udev_device* dev, const char *pszKey, char *pszValue, size_t nCChValue, bool bTruncate = false);
+static long long	_ReadDevPropertyValue(struct udev_device* dev, const char *pszKey, int base = 10, bool *pbErr = NULL);
+
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+static int _LookupPartition(const GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, const char *pszDevNode)
+{
+	int nIndex = -1;
+
+	if(pszDevNode && *pszDevNode)
+	{
+		for(size_t i = 0; i < _countof(sdm.parts); ++i)
+		{
+			if(sdm.parts[i].valid && !strcmp(sdm.parts[i].szDevNode, pszDevNode))
+			{
+				nIndex = i;	// found partition
+				break;
+			}
+		}
+	}
+
+	return nIndex;
+}
+
+static int _AddPartition(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, const GFA_SYSINFO_PARTITION &part, bool &bChange)
+{
+	int nIndex = -1;
+	bChange = false;
+
+	if((nIndex = _LookupPartition(sdm, part.szDevNode)) >= 0)
+		return nIndex; // partition already exists
+
+	for(size_t i = 0; i < _countof(sdm.parts); ++i)
+	{
+		if(!sdm.parts[i].valid)
+		{
+			memcpy(&sdm.parts[i], &part, sizeof(GFA_SYSINFO_PARTITION));
+			sdm.parts[i].valid = true;
+			bChange = true;
+			nIndex = i;
+			break;
+		}
+	}
+
+	return nIndex;
+}
+
+static int _RemovePartition(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, int nIndex, bool &bChange)
+{
+	bChange = false;
+
+	if(nIndex >= 0 && nIndex < (int)_countof(sdm.parts))
+	{
+		bChange = sdm.parts[nIndex].valid;
+		sdm.parts[nIndex].valid = false;
+	}
+
+	return nIndex;
+}
+
+static bool _PartitionSetDisk(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, int nPartIdx, int nDiskIdx, bool &bChange)
+{
+	bChange = false;
+
+	if(	(nPartIdx >= 0) && (nPartIdx < (int)_countof(sdm.parts)) &&
+		(nDiskIdx >= 0) && (nDiskIdx < (int)_countof(sdm.disks)))
+	{
+		if(sdm.parts[nPartIdx].nDiskIdx != nDiskIdx)
+		{
+			sdm.parts[nPartIdx].nDiskIdx = nDiskIdx;
+			bChange = true;
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+static int _PartitionGetDisk(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, int nPartIdx)
+{
+	if((nPartIdx >= 0) && (nPartIdx < (int)_countof(sdm.parts)))
+		return sdm.parts[nPartIdx].nDiskIdx;
+	return -1;
+}
+
+static void _ClearMapChanges(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm)
+{
+	sdm.nPartChangeMask = 0;
+}
+
+static bool _DeviceMapChanged(const GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm)
+{
+	return !!sdm.nPartChangeMask;
+}
+
+static unsigned int _SetPartitionChange(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, int nPartIdx)
+{
+	if((nPartIdx >= 0) && (nPartIdx < (int)_countof(sdm.parts)))
+	{
+		unsigned int nChanged = (0x01 << nPartIdx);
+		sdm.nPartChangeMask |= nChanged;
+	}
+
+	return sdm.nPartChangeMask;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+static int _LookupDisk(const GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, const char *pszDevNode)
+{
+	int nIndex = -1;
+
+	if(pszDevNode && *pszDevNode)
+	{
+		for(size_t i = 0; i < _countof(sdm.disks); ++i)
+		{
+			if(sdm.disks[i].valid && !strcmp(sdm.disks[i].szDevNode, pszDevNode))
+			{
+				nIndex = i;	// found partition
+				break;
+			}
+		}
+	}
+
+	return nIndex;
+}
+
+static int _AddDisk(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, const GFA_SYSINFO_DISK &disk, bool &bChange)
+{
+	int nIndex = -1;
+	bChange = false;
+
+	if((nIndex = _LookupDisk(sdm, disk.szDevNode)) >= 0)
+		return nIndex; // partition already exists
+
+	for(size_t i = 0; i < _countof(sdm.disks); ++i)
+	{
+		if(!sdm.disks[i].valid)
+		{
+			memcpy(&sdm.disks[i], &disk, sizeof(GFA_SYSINFO_DISK));
+			sdm.disks[i].valid = true;
+			bChange = true;
+			nIndex = i;
+			break;
+		}
+	}
+
+	return nIndex;
+}
+
+static int _RemoveDisk(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, int nIndex, bool &bChange)
+{
+	bChange = false;
+
+	if(nIndex >= 0 && nIndex < (int)_countof(sdm.disks))
+	{
+		bChange = sdm.disks[nIndex].valid;
+		sdm.disks[nIndex].valid = false;
+	}
+
+	return nIndex;
+}
+
+static unsigned int _DiskAddPartition(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, int nDiskIdx, int nPartIdx, bool &bChange)
+{
+	bChange = false;
+
+	if(	(nPartIdx >= 0) && (nPartIdx < (int)_countof(sdm.parts)) &&
+		(nDiskIdx >= 0) && (nDiskIdx < (int)_countof(sdm.disks)))
+	{
+		GFA_SYSINFO_DISK &disk = sdm.disks[nDiskIdx];
+
+		for(unsigned int i = 0; i < disk.nPartCount; ++i)
+		{
+			if(disk.aPartIdx[i] == nPartIdx)
+				return disk.nPartCount;
+		}
+
+		if(disk.nPartCount < _countof(disk.aPartIdx))
+		{
+			disk.aPartIdx[disk.nPartCount++] = nPartIdx;
+			bChange = true;
+			return disk.nPartCount;
+		}
+	}
+
+	return 0;
+}
+
+static unsigned int _DiskRemovePartition(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, int nDiskIdx, int nPartIdx, bool &bChange)
+{
+	bChange = false;
+
+	if(	(nPartIdx >= 0) && (nPartIdx < (int)_countof(sdm.parts)) &&
+		(nDiskIdx >= 0) && (nDiskIdx < (int)_countof(sdm.disks)))
+	{
+		unsigned int i, j;
+		GFA_SYSINFO_DISK &disk = sdm.disks[nDiskIdx];
+
+		for(i = 0; i < disk.nPartCount; ++i)
+		{
+			if(disk.aPartIdx[i] == nPartIdx)
+				break;
+		}
+
+		if(i < disk.nPartCount)
+		{
+			for(j = i + 1; j < disk.nPartCount; ++i, ++j)
+			{
+				disk.aPartIdx[i] = disk.aPartIdx[j];
+			}
+			bChange = true;
+			return --disk.nPartCount;
+		}
+
+		return disk.nPartCount;
+	}
+
+	return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+static bool _LookupMountPoint(MountMap &mm, const char *pszNode, char *pszMntPoint, size_t nCChMntPoint)
+{
+	MountMap::const_iterator it = mm.find(pszNode);
+	if(it == mm.end())
+		return false;
+	const std::string &s = it->second;
+
+	if(s.length() < nCChMntPoint)
+	{
+		strcpy(pszMntPoint, s.c_str());
+		return true;
+	}
+
+	return false;
+}
+
+static bool _UpdatePartitionFsInfo(MountMap &mm, GFA_SYSINFO_PARTITION &part)
+{
+	if(_LookupMountPoint(mm, part.szDevNode, part.szMntPoint, sizeof(part.szMntPoint)))
+	{
+		struct statvfs stvs;
+
+		if(!statvfs(part.szMntPoint, &stvs))
+		{
+			part.nKiBSize = stvs.f_bsize * stvs.f_blocks	/ 1024;
+			part.nKiBFree = stvs.f_bsize * stvs.f_bfree	/ 1024;
+			part.nKiBUsed = part.nKiBSize - part.nKiBFree;
+		}
+		else
+		{
+			TRACE("statvfs failed on \"%s\": %s\n", part.szMntPoint, strerror(errno));
+			memset(part.szMntPoint, 0, sizeof(part.szMntPoint));
+			part.nKiBSize = 0;
+			part.nKiBFree = 0;
+			part.nKiBUsed = 0;
+		}
+
+		return true;
+	}
+	else
+	{
+		memset(part.szMntPoint, 0, sizeof(part.szMntPoint));
+		part.nKiBSize = 0;
+		part.nKiBFree = 0;
+		part.nKiBUsed = 0;
+	}
+
+	return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+static std::string _StrReplace(std::string &s, const char *pszFind, const char *pszRepl)
+{
+	std::string r = s;
+
+	if(pszFind && *pszFind && pszRepl)
+	{
+		size_t nFind, nLen = strlen(pszFind);
+
+		while((nFind = r.find(pszFind)) !=  std::string::npos)
+		{
+			r = r.replace(nFind, nLen, pszRepl);
+		}
+	}
+	
+	return r;
+}
+	
+static std::string _UnescapeMountpointString(std::string &s)
+{
+	std::string r = s;
+	r = _StrReplace(r, "\\040", " ");
+	r = _StrReplace(r, "\\011", "\t");
+	r = _StrReplace(r, "\\012", "\n");
+	r = _StrReplace(r, "\\134", "\\");
+	return r;
+}
+
+static void _UpdateMountMap(MountMap &mm)
+{
+	char szLine[512], szNode[512], szMount[512];
+	std::ifstream mounts(_MOUNTS_FILE);
+	mm.clear();
+
+	while(mounts.getline(szLine, sizeof(szLine)).good())
+	{
+		if((sscanf(szLine, "%s %s", szNode, szMount) == 2))
+		{
+			std::string key(szNode);
+			key = _UnescapeMountpointString(key);
+			std::string val(szMount);
+			val = _UnescapeMountpointString(val);
+			mm.emplace(key, val);
+		}
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+static bool _ProcessMounts(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, MountMap &mm)
+{
+	bool bChange = false;
+	char szMntPoint[_countof(GFA_SYSINFO_PARTITION::szMntPoint)];
+
+	for(size_t i = 0; i < _countof(sdm.parts); ++i)
+	{
+		GFA_SYSINFO_PARTITION &part = sdm.parts[i];
+
+		if(part.valid)
+		{
+			bool bMountedOld = !!*part.szMntPoint;
+			bool bMountedNew = _LookupMountPoint(mm, part.szDevNode, szMntPoint, sizeof(szMntPoint));
+
+			if(!bMountedOld && bMountedNew)
+			{
+				memcpy(part.szMntPoint, szMntPoint, sizeof(part.szMntPoint));
+				_UpdatePartitionFsInfo(mm, part);
+				_SetPartitionChange(sdm, i);
+				bChange = true;
+			}
+			else if(bMountedOld && !bMountedNew)
+			{
+				memset(part.szMntPoint, 0, sizeof(part.szMntPoint));
+				_UpdatePartitionFsInfo(mm, part);
+				_SetPartitionChange(sdm, i);
+				bChange = true;
+			}
+			else if(bMountedOld && bMountedNew)
+			{
+				if(strcmp(part.szMntPoint, szMntPoint))
+				{
+					memcpy(part.szMntPoint, szMntPoint, sizeof(part.szMntPoint));
+					_UpdatePartitionFsInfo(mm, part);
+					_SetPartitionChange(sdm, i);
+					bChange = true;
+				}
+			}
+		}
+	}
+
+	return bChange;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+static const char* _GetFileSpec(const char *pszPathname)
+{
+	if(pszPathname && *pszPathname)
+	{
+		const char *pSlash =  strrchr(pszPathname, '/');
+		if(pSlash)
+			return  ++pSlash;
+		else
+			return pszPathname;
+	}
+
+	return NULL;
+}
+
+static bool _IsInternalEmmc(const char *pszDevNode)
+{
+	if(pszDevNode)
+	{
+		const char *pszDevName = _GetFileSpec(pszDevNode);
+		return _STR_EQUALS(pszDevName, _INTERNAL_EMMC_PART2) || _STR_EQUALS(pszDevName, _INTERNAL_EMMC_PART1);
+	}
+
+	return false;
+}
+
+static long long _NumberFromString(const char *pszString, int base, bool *pbErr)
+{
+	if(!pszString || !*pszString)
+	{
+		if(pbErr)
+			*pbErr = true;
+		return 0;
+	}
+
+	char *endptr;
+	long long nRet = strtoll(pszString, &endptr, base);
+
+	if( (((nRet == LLONG_MAX) || (nRet == LLONG_MIN)) && (errno == ERANGE)) ||
+		((nRet == 0) && (errno == EINVAL)) ||
+		(!!*endptr))
+	{
+		if(pbErr)
+			*pbErr = true;
+		return 0;
+	}
+
+	if(pbErr)
+		*pbErr = false;
+	return nRet;
+}
+
+static long long _ReadDevPropertyValue(struct udev_device* dev, const char *pszKey, int base, bool *pbErr)
+{
+	char szNum[64];
+	return _NumberFromString(_ReadDevPropertyValue(dev, pszKey, szNum, sizeof(szNum), false), base, pbErr);
+}
+
+static const char* _ReadDevPropertyValue(struct udev_device* dev, const char *pszKey, char *pszValue, size_t nCChValue, bool bTruncate)
+{
+	if(!pszValue || !nCChValue)
+	    return NULL;
+   memset(pszValue, 0, nCChValue);
+
+    const char *pszVal = ::udev_device_get_property_value(dev, pszKey);
+
+    if(pszVal)
+    {
+    	size_t nLen = strlen(pszVal);
+
+    	if(nLen < nCChValue)
+    	{
+    		strcpy(pszValue, pszVal);
+    		return pszValue;
+    	}
+    	else if(bTruncate)
+    	{
+    		memcpy(pszValue, pszVal, nCChValue);
+    		pszValue[nCChValue - 1] = '\0';
+    		return pszValue;
+    	}
+    }
+
+    return NULL;
+}
+
+static void _ProcessPartition(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, MountMap &mm, struct udev_device* dev)
+{
+	if(dev)
+	{
+		bool bChange;
+		GFA_SYSINFO_PARTITION part;
+		memset(&part, 0, sizeof(part));
+		part.nDiskIdx = -1;
+		int nPartIdx = -1;
+		int nDiskIdx = -1;
+
+		const char *pszDevNode = ::udev_device_get_devnode(dev);
+
+		if(!pszDevNode)
+			return;
+//		if(_IsInternalEmmc(pszDevNode))
+//			return;	// skip internal emmc
+		bool bInternalEmmc = _IsInternalEmmc(pszDevNode);
+		strncpy(part.szDevNode, pszDevNode, sizeof(part.szDevNode) - 1);
+
+		const char *pszAction = ::udev_device_get_action(dev);
+		bool bAdd = _STR_EQUALS(pszAction, "add");
+		bool bRem = _STR_EQUALS(pszAction, "remove");
+		bool bEnum = !pszAction;
+
+		if(bAdd || bEnum)
+		{
+			if(_LookupPartition(sdm, part.szDevNode) >= 0)
+				return;
+
+			part.internal = bInternalEmmc;
+			_ReadDevPropertyValue(dev, "ID_FS_LABEL", part.szFsLabel, sizeof(part.szFsLabel), true);
+			_ReadDevPropertyValue(dev, "ID_FS_TYPE", part.szFsType, sizeof(part.szFsType), true);
+			_ReadDevPropertyValue(dev, "ID_FS_VERSION", part.szFsVersion, sizeof(part.szFsVersion), true);
+			part.nKiBPartSize = _ReadDevPropertyValue(dev, "ID_PART_ENTRY_SIZE") / 2;
+			_UpdatePartitionFsInfo(mm, part);
+
+			struct udev_device* cur = dev;
+
+			while((cur = ::udev_device_get_parent(cur)))
+			{
+				const char *pszSs = ::udev_device_get_subsystem(cur);
+				const char *pszDt = ::udev_device_get_devtype(cur);
+				const char *pszDn = ::udev_device_get_devnode(cur);
+
+				if(!pszDn)
+					break;
+
+				if(_STR_EQUALS(pszSs, "usb") || _STR_EQUALS(pszSs, "block"))
+				{
+					if(_STR_EQUALS(pszDt, "disk"))
+					{
+						GFA_SYSINFO_DISK disk;
+						memset(&disk, 0, sizeof(disk));
+						strncpy(disk.szDevNode, pszDn, sizeof(disk.szDevNode) - 1);
+
+						disk.internal = bInternalEmmc;
+						if(!_ReadDevPropertyValue(cur, "ID_NAME", disk.szName, sizeof(disk.szName), true))
+							_ReadDevPropertyValue(cur, "ID_MODEL", disk.szName, sizeof(disk.szName), true);
+						_ReadDevPropertyValue(cur, "ID_VENDOR", disk.szVendor, sizeof(disk.szVendor), true);
+						_ReadDevPropertyValue(cur, "ID_BUS", disk.szBus, sizeof(disk.szBus), true);
+
+						disk.nVendorID	= _ReadDevPropertyValue(cur, "ID_VENDOR_ID", 16);
+						disk.nProductID	= _ReadDevPropertyValue(cur, "ID_MODEL_ID", 16);
+
+						if((nDiskIdx = _AddDisk(sdm, disk, bChange)) >= 0)
+						{
+							if((nPartIdx = _AddPartition(sdm, part, bChange)) < 0)
+							{
+								if(sdm.disks[nDiskIdx].nPartCount == 0)
+									_RemoveDisk(sdm, nDiskIdx, bChange);
+								break;
+							}
+							_DiskAddPartition(sdm, nDiskIdx, nPartIdx, bChange);
+							_PartitionSetDisk(sdm, nPartIdx, nDiskIdx, bChange);
+							_SetPartitionChange(sdm, nPartIdx);
+						}
+
+						break;
+					}
+				}
+			}
+		}
+		else if(bRem)
+		{
+			if((nPartIdx = _LookupPartition(sdm, pszDevNode)) >= 0)
+			{
+				_RemovePartition(sdm, nPartIdx, bChange);
+				_SetPartitionChange(sdm, nPartIdx);
+
+				if((nDiskIdx = _PartitionGetDisk(sdm, nPartIdx)) >= 0)
+				{
+					if(!_DiskRemovePartition(sdm, nDiskIdx, nPartIdx, bChange))
+					{
+						_RemoveDisk(sdm, nDiskIdx, bChange);
+					}
+				}
+			}
+		}
+	}
+}
+
+static void _EnumStorageDevices(GFA_SYSINFO_STORAGE_DEVICE_MAP &sdm, MountMap &mm, struct udev *pUdev)
+{
+	struct udev_enumerate *pEnum = ::udev_enumerate_new(pUdev);
+	::udev_enumerate_add_match_subsystem(pEnum, "block");
+	::udev_enumerate_add_match_property(pEnum, "DEVTYPE", "partition");
+	::udev_enumerate_scan_devices(pEnum);
+
+	struct udev_list_entry *devices = ::udev_enumerate_get_list_entry(pEnum);
+	struct udev_list_entry *entry;
+
+	udev_list_entry_foreach(entry, devices)
+	{
+		const char *pszPath = ::udev_list_entry_get_name(entry);
+		struct udev_device* dev = ::udev_device_new_from_syspath(pUdev, pszPath);
+
+		_ProcessPartition(sdm, mm, dev);
+		::udev_device_unref(dev);
+	}
+
+	::udev_enumerate_unref(pEnum);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+CStgDevInfo::CStgDevInfo(void) : m_bPaused(false), m_bStateTransition(false)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+void* CStgDevInfo::ThreadRoutine(void *pParam)
+{
+	LPEXEC_PARAMS pep = (LPEXEC_PARAMS)pParam;
+	TRACE("Enter CStgDevInfo::ThreadRoutine: TID: %ld\n", syscall(SYS_gettid));
+
+	if(pep)
+	{
+		bool bRun = false;
+		int nRet, nSig;
+		struct udev *pUdev = NULL;
+		struct udev_monitor *pUdevMon = NULL;
+		GFA_SYSINFO_STORAGE_DEVICE_MAP sdm;
+		memset(&sdm, 0, sizeof(sdm));
+		MountMap mm;
+		pollfd pfd[2];
+		memset(&pfd, 0, sizeof(pfd));
+		unsigned long long nWaitIntvUs = 0;
+
+		do
+		{
+		    if(!(pUdev = ::udev_new()))
+		    {
+		        TRACE("udev_new failed\n");
+				break;
+		    }
+
+			_UpdateMountMap(mm);
+			_EnumStorageDevices(sdm, mm, pUdev);
+
+			if(!(pUdevMon = ::udev_monitor_new_from_netlink(pUdev, "udev")))
+			{
+		        TRACE("udev_monitor_new_from_netlink failed\n");
+				break;
+			}
+
+			if(::udev_monitor_filter_add_match_subsystem_devtype(pUdevMon, "block", "partition") < 0)
+			{
+		        TRACE("udev_monitor_filter_add_match_subsystem_devtype failed\n");
+				break;
+			}
+
+			if(::udev_monitor_enable_receiving(pUdevMon) < 0)
+			{
+		        TRACE("udev_monitor_enable_receiving failed\n");
+				break;
+			}
+
+			pfd[0].fd = ::udev_monitor_get_fd(pUdevMon);
+			pfd[0].events = POLLIN;
+			pfd[0].revents = 0;
+			pfd[1].fd = open(_MOUNTS_FILE, O_RDONLY, 0);
+			pfd[1].events = POLLPRI;
+			pfd[1].revents = 0;
+
+			bRun = true;
+			::GfaIpcAppCtrlUpdateStorageDeviceMap(pep->hAC, sdm);
+			sleep(1);
+		}
+		while(false);
+
+		while(bRun)
+		{
+			nWaitIntvUs = m_bPaused ? (_POLL_INTERVAL_MS * 1000ULL) : 0;
+
+			if((nRet = WaitSignalTimeout(nWaitIntvUs, &nSig)) == ETIMEDOUT)
+			{
+				_ClearMapChanges(sdm);
+
+				if(!m_bPaused)
+				{
+					while(poll(pfd, 2, _POLL_INTERVAL_MS) > 0)
+					{
+						_UpdateMountMap(mm);
+
+						if(pfd[0].revents & POLLIN)
+						{
+							struct udev_device* dev;
+
+							while((dev = ::udev_monitor_receive_device(pUdevMon)))
+							{
+								_ProcessPartition(sdm, mm, dev);
+							}
+						}
+
+						if(pfd[1].revents & POLLPRI)
+						{
+							_ProcessMounts(sdm, mm);
+						}
+					}
+
+					if(_DeviceMapChanged(sdm) || m_bStateTransition)
+					{
+						if(m_bStateTransition)
+						{
+							_UpdateMountMap(mm);
+							memset(&sdm, 0, sizeof(sdm));
+							_EnumStorageDevices(sdm, mm, pUdev);
+						}
+						::GfaIpcAppCtrlUpdateStorageDeviceMap(pep->hAC, sdm);
+					}
+				}
+				
+				m_bStateTransition = false;
+			}
+			else if(!nRet) // signal received
+			{
+				TRACE("%s signal %d received.\n", "CStgDevInfo::ThreadRoutine", nSig);
+
+				switch(nSig)
+				{
+				case S_Init:
+					break;
+				case S_Update:
+					break;
+				case S_Pause:
+					memset(&sdm, 0, sizeof(sdm));
+					::GfaIpcAppCtrlUpdateStorageDeviceMap(pep->hAC, sdm, true);
+					m_bStateTransition = !m_bPaused;
+					m_bPaused = true;
+					break;
+				case S_Resume:
+					m_bStateTransition = m_bPaused;
+					m_bPaused = false;
+					break;
+				case S_Terminate:
+					bRun = false;
+					break;
+				default:
+					break;
+				}
+			}
+			else
+			{
+				TRACE("%s error %d.\n", "CStgDevInfo::ThreadRoutine", nRet);
+			}
+		}
+
+		if(pfd[0].fd >= 0)
+			close(pfd[0].fd);
+		if(pfd[1].fd >= 0)
+			close(pfd[1].fd);
+		if(pUdevMon)
+			::udev_monitor_unref(pUdevMon);
+		if(pUdev)
+			::udev_unref(pUdev);
+	}
+
+	TRACE("%s exit.\n", "CStgDevInfo::ThreadRoutine");
+	return NULL;
+}

+ 45 - 0
src/stgdevinfo.h

@@ -0,0 +1,45 @@
+// stgdevinfo.h :
+//
+
+#if !defined(AGD_STGDEVINFO_H__42237980_CC43_484B_8AAC_425F05E77063__INCLUDED_)
+#define AGD_STGDEVINFO_H__42237980_CC43_484B_8AAC_425F05E77063__INCLUDED_
+
+#include <gfa/gfaipc.h>
+#include <gfa/ipcpriv.h>
+#include <gfa/thread.h>
+
+/////////////////////////////////////////////////////////////////////////////
+// stgdevinfo.h - Declarations:
+
+class CStgDevInfo : public CThread
+{
+public:
+	typedef enum
+	{
+		S_Init = 1,
+		S_Update,
+		S_Pause,
+		S_Resume,
+		S_Terminate
+	}Signals;
+
+public:
+	typedef struct _EXEC_PARAMS
+	{
+		HAPPCTRL hAC;
+	}EXEC_PARAMS, *LPEXEC_PARAMS;
+	typedef const EXEC_PARAMS *LPCEXEC_PARAMS;
+
+public:
+	CStgDevInfo(void);
+
+protected:
+	virtual void* ThreadRoutine(void *pParam);
+
+private:
+	bool m_bPaused;
+	bool m_bStateTransition;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+#endif	//	!defined(AGD_STGDEVINFO_H__42237980_CC43_484B_8AAC_425F05E77063__INCLUDED_)