/* Copyright (C) 2014 InfiniDB, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include "boost/filesystem/operations.hpp" #include "boost/filesystem/path.hpp" namespace fs = boost::filesystem; using namespace std; #include "unistd.h" #include "stdint.h" #include "sys/time.h" #include "syslog.h" #include "idbregistry.h" #include "WinSysLog.h" // WinSyslog class encapsulates what substitutes for the syslog in the // InfiniDB Windows version. // Singleton instance WinSyslog* WinSyslog::fpSysLog = WinSyslog::instance(); // For archive coordination. // We have two mutexes. Only one will be used. We want to use the inter-process // mutex name fhMutex. If creation succeeds, we set fbGoodIPMutex so the code knows // it's good. If we fail, fMutex (Only this processes threads) will be used - it's // better than nothing. boost::mutex WinSyslog::fMutex; // Failsafe Intra process mutex HANDLE WinSyslog::fhMutex; // Interprocess named mutex bool WinSyslog::fbGoodIPMutex = false; #define WINSYSLOG_MUTEX_NAME "WinSysLogMutex" // For the Windows Named Mutex // Constructor, wherein we initialize everything WinSyslog::WinSyslog() : fLastArchiveTime(0), fLastArchiveDay(0), fLogLineheaderSize(0) { fLogDirName = IDBreadRegistry("") + "\\log"; fLogFileName = IDBreadRegistry("") + "\\log\\InfiniDBLog.txt"; fTimeFileName = IDBreadRegistry("") + "\\log\\InfiniDBLastArchive.dat"; // Set up a buffer with all the line header except the timestamp. // Leave room in the front for the timestamp. // This header will approximate the linux standard syslog log header. memset(fLogLineHeader, 0, LOG_BUF_SIZE); char* pEnd = fLogLineHeader + LOG_BUF_SIZE; char* pHeader = fLogLineHeader + TIMESTAMP_SIZE; char exePath[LOG_BUF_SIZE]; DWORD nameSize = (DWORD)(pEnd - pHeader); GetComputerName(pHeader, &nameSize); pHeader += nameSize; *pHeader++ = ' '; GetModuleFileName(0, exePath, LOG_BUF_SIZE); _splitpath_s(exePath, NULL, 0, NULL, 0, pHeader, pEnd - pHeader, NULL, 0); pHeader += strlen(pHeader); ; *pHeader++ = '['; int pid = GetCurrentProcessId(); snprintf(pHeader, pEnd - pHeader, "%d", pid); pHeader += strlen(pHeader); ; *pHeader++ = ']'; *pHeader++ = ':'; fLogLineheaderSize = (int)(pHeader - fLogLineHeader); // Set up the date we compare against for archiving. // This will either be today (if fTimeFileName doesn't exist) // or the the date stored in fTimeFileName. time_t now = time(0); FILE* lf; lf = fopen(fTimeFileName.c_str(), "r+b"); if (lf == NULL) // First time archiving. { fLastArchiveTime = now; // Persist the timestamp of last archive. lf = fopen(fTimeFileName.c_str(), "w+b"); if (lf != 0) { fwrite(&fLastArchiveTime, sizeof(time_t), 1, lf); fclose(lf); } } else { fread(&fLastArchiveTime, sizeof(time_t), 1, lf); fclose(lf); } struct tm lasttm; localtime_s(&lasttm, &fLastArchiveTime); fLastArchiveDay = lasttm.tm_yday; // Create an interprocess mutex to coordinate the archiving function fhMutex = CreateMutex(NULL, FALSE, WINSYSLOG_MUTEX_NAME); if (fhMutex == NULL) { syslog(LOG_ERR, "WinSyslog CreateMutex error: %d\n", GetLastError()); } else { fbGoodIPMutex = true; } } WinSyslog::~WinSyslog() { CloseHandle(fhMutex); } // Log() is called by ::syslog() to actually write stuff to the file int WinSyslog::Log(int priority, const char* format, va_list& args) { struct tm nowtm; FILE* f; time_t now = time(0); localtime_s(&nowtm, &now); // If now isn't the same day as the last archive date, archve. if (nowtm.tm_yday != fLastArchiveDay) { Archive(nowtm); } // Log the line. strftime(fLogLineHeader, TIMESTAMP_SIZE, "%b %d %H:%M:%S", &nowtm); fLogLineHeader[TIMESTAMP_SIZE - 1] = ' '; f = fopen(fLogFileName.c_str(), "a+"); if (f == 0) return -1; fwrite(fLogLineHeader, 1, fLogLineheaderSize, f); vfprintf(f, format, args); fwrite("\n", 1, 1, f); fclose(f); return 0; } // Archive() is called by Log() when the date changes // Here we rename our log file to an archive name and delete // any old files. void WinSyslog::Archive(const tm& nowtm) { struct tm yesterdaytm; bool bArchive = true; time_t writeTime; char ctimebuf[TIME_BUF_SIZE] = {0}; DWORD dwWaitResult; char* szError = ""; // Lock it so we don't have two threads archiving. if (fbGoodIPMutex) { // We have a good interprocess mutex. Try for one second to // lock it. Abandon trying after a second. Something's stuck. dwWaitResult = WaitForSingleObject(fhMutex, 1000); switch (dwWaitResult) { // The thread got ownership of the mutex. Continue on. case WAIT_OBJECT_0: break; // The thread got ownership of an abandoned mutex, most likely because // some process crashed in the middle of archiving. Continue on. case WAIT_ABANDONED: break; // We timed out. Something's not right. Don't archive. case WAIT_TIMEOUT: szError = "WinSyslog::Archive WAIT_TIMEOUT"; bArchive = false; break; // Horrible failure. Don't archive case WAIT_FAILED: szError = "WinSyslog::Archive WAIT_FAILED"; bArchive = false; break; } } else { boost::mutex::scoped_lock lock(fMutex); } try { // Check to see if any other thread or process already archived. // If the value in fTimeFileName is different than our saved value // Then somebody else beat us to it. Don't do it again. time_t storedLastArchiveTime; FILE* lf; lf = fopen(fTimeFileName.c_str(), "r+b"); if (lf != NULL) { fread(&storedLastArchiveTime, sizeof(time_t), 1, lf); fclose(lf); if (storedLastArchiveTime != fLastArchiveTime) { bArchive = false; } } else { // If we had something go wrong earlier, and then failed to read the Time file, // this should prevent retrying to archive every log line. storedLastArchiveTime = fLastArchiveTime = time(NULL); } if (bArchive == false) { // Something's not right. Try to set the internals so we don't attempt // archiving again. fLastArchiveTime = storedLastArchiveTime; fLastArchiveDay = nowtm.tm_yday; if (szError) { syslog(LOG_ERR, szError); } return; } // Get last archive date (usually yesterday) yesterdaytm = nowtm; yesterdaytm.tm_mday -= 1; // May be 0. mktime() adjusts accordingly. time_t yesterday = mktime(&yesterdaytm); localtime_s(&yesterdaytm, &yesterday); string archiveFileName = fLogFileName; size_t tail = archiveFileName.find(".txt"); archiveFileName.erase(tail, 4); strftime(ctimebuf, TIME_BUF_SIZE, "-%Y-%m-%d.txt", &yesterdaytm); archiveFileName += ctimebuf; rename(fLogFileName.c_str(), archiveFileName.c_str()); // Persist the timestamp of last archive. In case of reboot, we // can still archive properly lf = fopen(fTimeFileName.c_str(), "w+b"); if (lf != 0) { fLastArchiveTime = time(0); fwrite(&fLastArchiveTime, sizeof(time_t), 1, lf); fclose(lf); } fLastArchiveDay = nowtm.tm_yday; // Get the dates of all archive files. fs::path sourceDir(fLogDirName); fs::directory_iterator iter(sourceDir); fs::directory_iterator end_iter; std::multimap fileSet; // Stays sorted while (iter != end_iter) { fs::path archiveFile = *iter; if (fs::is_regular_file(archiveFile)) { if (archiveFile.extension() == ".txt") { writeTime = fs::last_write_time(archiveFile); fileSet.insert(make_pair(writeTime, archiveFile)); } } ++iter; } // Delete anything past the first seven newest files typedef std::multimap::iterator PATH_ITER; PATH_ITER fileIter = fileSet.begin(); PATH_ITER fileIterEnd = fileSet.end(); std::reverse_iterator revEnd(fileIter); std::reverse_iterator revIter(fileIterEnd); int cnt = 0; for (; revIter != revEnd; ++revIter) { if (cnt++ < 7) continue; _wunlink(revIter->second.c_str()); } } catch (exception& e) { syslog(LOG_ERR, "WinSyslog::Archive Exception %s", e.what()); } catch (...) { syslog(LOG_ERR, "WinSyslog::Archive Exception ..."); } if (fbGoodIPMutex) { if (!ReleaseMutex(fhMutex)) syslog(LOG_ERR, "WinSyslog::Archive ReleaseMutex failed %d", GetLastError()); } }