/*
Mysql Reviver
Autor: Łukasz 'Szift' Hejnak
Aktualizacja: 22.06.07

Prosty program, monitorujący syslog w celu szybkiego wykrycia zawieszonego mysqld.
Wykonuje parę różnych testów:
 1. sprawdza logi i szuka komunikatów o błędach (jeżeli > 5)
 2. sprawdza czas ostatniego komunikatu (jeżeli < 10 minut)
 3. sprawdza czas poprzedniego restartu mysqld (jeżeli > 30 sekund)
 4. pobiera wybraną stronę www, korzystającą z danego serwera i szuka komunikatu o błędzie
w przypadku wykrycia zawieszenia wykonuje restart mysqld. 
*/


#include <iostream>
#include <fstream>
#include <string>
#include <time.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <resolv.h>
using namespace std;
  
// Definicje
#define DEBUG 			0						// dodatkowe informacje
#define SYSLOGPATH	"/var/log/sys.log"	// ścieżka do pliku syslog'a z komunikatami z mysqld
#define STAMPPATH		"/root/mysql.reviver.stamp"	// ścieżka do pliku z informacją o poprzednim restarcie 
#define WWWTEST		"/"	// ścieżka do serwisu korzystającego z bazy  
#define HOSTNAME		""						// można podać, lub wykorzystywany będzie ten z syslog'a 
#define MYSQL_RESTART	"/etc/init.d/mysql restart >&/dev/null"	// ścieżka polecenia restartującego mysql
#define LOOP_DELAY	15						// czas czekania po każdej pętli w sekundach 
#define ALIVE_NOTICE	60						// co ile pętli ma być komunikat że proces żyje? ( <= 0 - wyłącza komunikaty)


void podaj_czas_jak_syslog(char * timebuffer)
{
	struct tm * timeinfo;
	time_t rawtime;
	time (&rawtime);
	timeinfo = localtime (&rawtime);
	if (timeinfo->tm_mday < 10)
		strftime (timebuffer,100,"%b  %-1d %X",timeinfo);
	else
		strftime (timebuffer,100,"%b %d %X",timeinfo);
}


int main () {
	char buffer[500], timebuffer[100], wwwbuffer[1024];
	string analise, lasterr, source, hostname;
	long int pos;
	int warn=0, i, sd, error = 0, count = 0;
	fstream filestr;
	time_t rawtime, stamp1, stamp2;
	struct tm * timeinfo, * tmptime, extrtime;

	// przygotowanie struktur do połączeń www
	struct hostent* host;
	struct sockaddr_in addr;
	string request = "GET ";
	request += WWWTEST;
	request += " HTTP/1.1\r\n";
	request += "Host: MySQL.Reviver\r\n";
	request += "Connection: close\r\n\r\n";
	size_t request_size = request.size() + 1;
	char crequest[request_size];
	strncpy (crequest, request.c_str(), request_size);

	podaj_czas_jak_syslog(timebuffer);
	// jeżeli nie ustawiona, pobieramy nazwę hosta używaną przez syslog
	if (HOSTNAME=="")
	{
		filestr.open (SYSLOGPATH, fstream::in);
		if (!filestr.is_open())
		{
			cout << timebuffer << " localhost Mysql.Reviver: Blad! Nie powiodlo sie otwarcie pliku " << SYSLOGPATH << endl;
			return 2;
		}
		filestr.getline (buffer, 500);
		filestr.close();
		analise = buffer;
		hostname = analise.substr(16,analise.length()-17);
		if (hostname.find (" ") != string::npos)
			i = hostname.find (" ");
		else
			i = hostname.length();
		hostname = hostname.substr(0, i);
	} else
			hostname = HOSTNAME;

	cout << timebuffer << " " << hostname << " Mysql.Reviver: startuje (deamon mode)" << endl;
	while (true)
	{
		error = 0;
		time (&rawtime);
		timeinfo = localtime (&rawtime);
		stamp1 = mktime (timeinfo);
		// sprawdzamy zapis czasu ostatniego restartu (odstęp minimum 30 sekund)
		filestr.open (STAMPPATH, fstream::in);
		if (filestr.is_open())
		{
			filestr.getline (timebuffer, 15);
			filestr.close();
			if (DEBUG)
				cout << "timebuffer: " << timebuffer << endl;
			if (strptime (timebuffer, "%b %d %X", &extrtime) != NULL)
			{
				tmptime = localtime(&rawtime);
				tmptime->tm_min = extrtime.tm_min;
				tmptime->tm_hour = extrtime.tm_hour;
				tmptime->tm_mday = extrtime.tm_mday;
				tmptime->tm_mon = extrtime.tm_mon;
				stamp2 = mktime (tmptime);
				if (DEBUG)
					cout << stamp1 << ":" << stamp2 << endl;
				if (stamp2+30 > stamp1) // czy stamp2 jest starszy niż 30 sekund?
					error = 1;
			}
		} else
				error = 1; // w razie kłopotów z plikiem, zakładamy że jest ok

		if (!error)
		{
			filestr.open (SYSLOGPATH, fstream::in);
			if (filestr.is_open())
			{
				error = 0;
				filestr.seekp (0,ios_base::end);
				pos=filestr.tellp();
				if (DEBUG)
					cout << "Wielkosc pliku: " << (pos/1024) << "kb\n";
				if (pos>4000)
				{
					filestr.seekp (-4000,ios_base::end);
					// omijamy pierwszą linię (pewnie jest obcięta)
					filestr.getline (buffer,500);
				}
				else
					filestr.seekp (0,ios_base::beg);
			
				while (!filestr.eof())
				{
					filestr.getline (buffer,500);
					// szukamy PHP Warningów
					analise = buffer;
					if (analise.find ("php: PHP Warning:") != string::npos)
					{
						warn++;
						lasterr = analise;
					}
				}
				filestr.close();
			} else
				error = 1;
			if(!error)
			{
			//	cout << warn << " warnings found" << endl;
			// przynajmniej 5 php Warningów
				if (warn >= 5)
				{
					warn = 0;
					if (DEBUG)
						cout << "Cos sie dzieje... sprawdzam dalej" << endl;
					// lasterr przechowuję ostatnią linijkę z błędem
					// sprawdzamy kiedy to było
					lasterr = lasterr.substr (0, 15);
					for(i=0;i<lasterr.length(); i++)
						timebuffer[i] = lasterr[i];
					// używamy ponownie raz już pobranego czasu
					stamp1 = mktime (timeinfo);
					if (strptime (timebuffer, "%b %d %X", &extrtime)!=NULL)
					{
						// rok i inne detale muszą się zgadzać!
						tmptime = localtime(&rawtime);
						tmptime->tm_min = extrtime.tm_min;
						tmptime->tm_hour = extrtime.tm_hour;
						tmptime->tm_mday = extrtime.tm_mday;
						tmptime->tm_mon = extrtime.tm_mon;
						stamp2 = mktime (tmptime);
						if (stamp2+(600) >= stamp1) // czy stamp2 jest starszy o nie więcej niz 10 minut?
						{
							if (DEBUG)
								cout << "Mamy cie!" << endl;
							// no więc ostatnia linijka z błędem jest dość świeża, pozostaje ostateczny test
							// sprawdzamy co jest na stronie WWWTEST
							sd = socket(PF_INET, SOCK_STREAM, 0); /* create socket */
							memset(&addr, 0, sizeof(addr));    /* create & zero struct */
							addr.sin_family = PF_INET;    /* select internet protocol */
							addr.sin_port = htons(80);         /* set the port # */
							host = gethostbyname("localhost");
							addr.sin_addr.s_addr = *(long*)host->h_addr_list[0]; /* set the addr */
							connect(sd, (sockaddr *)&addr, sizeof(addr));         /* connect! */
							send(sd, crequest, strlen(crequest), 0);
							do
							{
								i = recv(sd, wwwbuffer, sizeof(wwwbuffer), 0);
								source = wwwbuffer;
								if (DEBUG)
									cout << source << endl;
								if (source.find ("Can't connect to local MySQL server") != string::npos)
								{
									// znaczy że jest faktycznie kłopot -> restart
									error = 1;
									break;
								}
							} while (i != 0);
							close(sd);
							if (error)
							{
								podaj_czas_jak_syslog(timebuffer);
								cout << timebuffer << " " << hostname << " Mysql.Reviver: restartuje serwer MySql!" << endl;
								// o tu
								system(MYSQL_RESTART);
								filestr.open (STAMPPATH, fstream::out | fstream::trunc);
								filestr << timebuffer << endl;
								filestr.close();
							} else
									if (DEBUG)
										cout << "hoax" << endl;
						} else
								if (DEBUG)
									cout << "log: bylo.pl" << endl;
					}
					// else
					//	cout << "strptime error!" << endl;
				} // warn > 5 
			} // chwilowy blad odczytu pliku (logrotate?) ignorujemy
		} // swiezy stamp
		else
			if (DEBUG)
				cout << "stamp: bylo.pl" << endl;
		
		// spac i komunikat co ALIVE_NOTICE * LOOP_DELAY * 1 sekunda (domyslnie 15 minut)
		if ((count > ALIVE_NOTICE) && (ALIVE_NOTICE > 0))
		{
			podaj_czas_jak_syslog(timebuffer);
			cout << timebuffer << " " << hostname << " Mysql.Reviver nie spie.." << endl;
			count = 0;
		} else if (ALIVE_NOTICE > 0)
			count++;
		usleep(LOOP_DELAY * 1000000);
	}
	return 0;
}

