550 lines
17 KiB
C++
550 lines
17 KiB
C++
/*
|
|
*
|
|
* @APPLE_LICENSE_HEADER_START@
|
|
*
|
|
* Copyright (c) 1999-2008 Apple Inc. All Rights Reserved.
|
|
*
|
|
* This file contains Original Code and/or Modifications of Original Code
|
|
* as defined in and that are subject to the Apple Public Source License
|
|
* Version 2.0 (the 'License'). You may not use this file except in
|
|
* compliance with the License. Please obtain a copy of the License at
|
|
* http://www.opensource.apple.com/apsl/ and read it before using this
|
|
* file.
|
|
*
|
|
* The Original Code and all software distributed under the License are
|
|
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
|
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
|
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
|
* Please see the License for the specific language governing rights and
|
|
* limitations under the License.
|
|
*
|
|
* @APPLE_LICENSE_HEADER_END@
|
|
*
|
|
*/
|
|
/*
|
|
File: QTSSRollingLog.cpp
|
|
|
|
Contains: Implements object defined in .h file
|
|
|
|
|
|
*/
|
|
|
|
#include <time.h>
|
|
#include <stdlib.h>
|
|
#include "SafeStdLib.h"
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
#ifndef __Win32__
|
|
#include <sys/time.h>
|
|
#endif
|
|
#include "QTSSRollingLog.h"
|
|
#include "OS.h"
|
|
#include "OSMemory.h"
|
|
#include "OSArrayObjectDeleter.h"
|
|
#include "ResizeableStringFormatter.h"
|
|
|
|
static Bool16 sCloseOnWrite = true;
|
|
|
|
QTSSRollingLog::QTSSRollingLog() :
|
|
fLog(NULL),
|
|
fLogCreateTime(-1),
|
|
fLogFullPath(NULL),
|
|
fAppendDotLog(true),
|
|
fLogging(true)
|
|
{
|
|
this->SetTaskName("QTSSRollingLog");
|
|
}
|
|
|
|
QTSSRollingLog::~QTSSRollingLog()
|
|
{
|
|
//
|
|
// Log should already be closed, but just in case...
|
|
this->CloseLog();
|
|
delete [] fLogFullPath;
|
|
}
|
|
|
|
// Set this to true to get the log to close the file between writes.
|
|
void QTSSRollingLog::SetCloseOnWrite(Bool16 closeOnWrite)
|
|
{
|
|
sCloseOnWrite = closeOnWrite;
|
|
}
|
|
|
|
Bool16 QTSSRollingLog::IsLogEnabled()
|
|
{
|
|
return sCloseOnWrite || (fLog != NULL);
|
|
}
|
|
|
|
void QTSSRollingLog::WriteToLog(char* inLogData, Bool16 allowLogToRoll)
|
|
{
|
|
OSMutexLocker locker(&fMutex);
|
|
|
|
if (fLogging == false)
|
|
return;
|
|
|
|
if (sCloseOnWrite && fLog == NULL)
|
|
this->EnableLog(fAppendDotLog ); //re-open log file before we write
|
|
|
|
if (allowLogToRoll)
|
|
(void)this->CheckRollLog();
|
|
|
|
if (fLog != NULL)
|
|
{
|
|
qtss_fprintf(fLog, "%s", inLogData);
|
|
::fflush(fLog);
|
|
}
|
|
|
|
if (sCloseOnWrite)
|
|
this->CloseLog( false );
|
|
}
|
|
|
|
Bool16 QTSSRollingLog::RollLog()
|
|
{
|
|
OSMutexLocker locker(&fMutex);
|
|
|
|
//returns false if an error occurred, true otherwise
|
|
|
|
//close the old file.
|
|
if (fLog != NULL)
|
|
this->CloseLog();
|
|
|
|
if (fLogging == false)
|
|
return false;
|
|
|
|
//rename the old file
|
|
Bool16 result = this->RenameLogFile(fLogFullPath);
|
|
if (result)
|
|
this->EnableLog(fAppendDotLog);//re-opens log file
|
|
|
|
return result;
|
|
}
|
|
|
|
char* QTSSRollingLog::GetLogPath(char *extension)
|
|
{
|
|
char *thePath = NULL;
|
|
|
|
OSCharArrayDeleter logDir(this->GetLogDir()); //The string passed into this function is a copy
|
|
OSCharArrayDeleter logName(this->GetLogName()); //The string passed into this function is a copy
|
|
|
|
ResizeableStringFormatter formatPath(NULL,0); //allocate the buffer
|
|
formatPath.PutFilePath(logDir, logName);
|
|
|
|
if ( extension != NULL)
|
|
formatPath.Put(extension);
|
|
|
|
formatPath.PutTerminator();
|
|
thePath = formatPath.GetBufPtr();
|
|
|
|
formatPath.Set(NULL,0); //don't delete buffer we are returning the path as a result
|
|
|
|
return thePath;
|
|
}
|
|
|
|
void QTSSRollingLog::EnableLog( Bool16 appendDotLog )
|
|
{
|
|
//
|
|
// Start this object running!
|
|
this->Signal(Task::kStartEvent);
|
|
|
|
OSMutexLocker locker(&fMutex);
|
|
|
|
fAppendDotLog = appendDotLog;
|
|
|
|
if (fLogging == false)
|
|
return;
|
|
|
|
char *extension = ".log";
|
|
if (!appendDotLog)
|
|
extension = NULL;
|
|
|
|
delete[] fLogFullPath;
|
|
fLogFullPath = this->GetLogPath(extension);
|
|
|
|
//we need to make sure that when we create a new log file, we write the
|
|
//log header at the top
|
|
Bool16 logExists = this->DoesFileExist(fLogFullPath);
|
|
|
|
//create the log directory if it doesn't already exist
|
|
if (!logExists)
|
|
{
|
|
OSCharArrayDeleter tempDir(this->GetLogDir());
|
|
OS::RecursiveMakeDir(tempDir.GetObject());
|
|
}
|
|
|
|
fLog = ::fopen(fLogFullPath, "a+");//open for "append"
|
|
if (NULL != fLog)
|
|
{
|
|
if (!logExists) //the file is new, write a log header with the create time of the file.
|
|
{ fLogCreateTime = this->WriteLogHeader(fLog);
|
|
#if __MacOSX__
|
|
(void) ::chown(fLogFullPath, 76, (gid_t)-1);//set owner to user qtss.
|
|
#endif
|
|
}
|
|
else //the file is old, read the log header to find the create time of the file.
|
|
fLogCreateTime = this->ReadLogHeader(fLog);
|
|
}
|
|
}
|
|
|
|
void QTSSRollingLog::CloseLog( Bool16 leaveEnabled )
|
|
{
|
|
OSMutexLocker locker(&fMutex);
|
|
|
|
if (leaveEnabled)
|
|
sCloseOnWrite = true;
|
|
|
|
if (fLog != NULL)
|
|
{
|
|
::fclose(fLog);
|
|
fLog = NULL;
|
|
}
|
|
}
|
|
|
|
//returns false if some error has occurred
|
|
Bool16 QTSSRollingLog::FormatDate(char *ioDateBuffer, Bool16 logTimeInGMT)
|
|
{
|
|
Assert(NULL != ioDateBuffer);
|
|
|
|
//use ansi routines for getting the date.
|
|
time_t calendarTime = ::time(NULL);
|
|
Assert(-1 != calendarTime);
|
|
if (-1 == calendarTime)
|
|
return false;
|
|
|
|
struct tm* theTime = NULL;
|
|
struct tm timeResult;
|
|
|
|
if (logTimeInGMT)
|
|
theTime = ::qtss_gmtime(&calendarTime, &timeResult);
|
|
else
|
|
theTime = qtss_localtime(&calendarTime, &timeResult);
|
|
|
|
Assert(NULL != theTime);
|
|
|
|
if (NULL == theTime)
|
|
return false;
|
|
|
|
// date time needs to look like this for extended log file format: 2001-03-16 23:34:54
|
|
// this wonderful ANSI routine just does it for you.
|
|
// the format is YYYY-MM-DD HH:MM:SS
|
|
// the date time is in GMT, unless logTimeInGMT is false, in which case
|
|
// the time logged is local time
|
|
//qtss_strftime(ioDateBuffer, kMaxDateBufferSize, "%d/%b/%Y:%H:%M:%S", theLocalTime);
|
|
qtss_strftime(ioDateBuffer, kMaxDateBufferSizeInBytes, "%Y-%m-%d %H:%M:%S", theTime);
|
|
return true;
|
|
}
|
|
|
|
Bool16 QTSSRollingLog::CheckRollLog()
|
|
{
|
|
//returns false if an error occurred, true otherwise
|
|
if (fLog == NULL)
|
|
return true;
|
|
|
|
//first check to see if log rolling should happen because of a date interval.
|
|
//This takes precedence over size based log rolling
|
|
// this is only if a client connects just between 00:00:00 and 00:01:00
|
|
// since the task object runs every minute
|
|
|
|
// when an entry is written to the log file, only the file size must be checked
|
|
// to see if it exceeded the limits
|
|
|
|
// the roll interval should be monitored in a task object
|
|
// and rolled at midnight if the creation time has exceeded.
|
|
if ((-1 != fLogCreateTime) && (0 != this->GetRollIntervalInDays()))
|
|
{
|
|
time_t logCreateTimeMidnight = -1;
|
|
QTSSRollingLog::ResetToMidnight(&fLogCreateTime, &logCreateTimeMidnight);
|
|
Assert(logCreateTimeMidnight != -1);
|
|
|
|
time_t calendarTime = ::time(NULL);
|
|
|
|
Assert(-1 != calendarTime);
|
|
if (-1 != calendarTime)
|
|
{
|
|
double theExactInterval = ::difftime(calendarTime, logCreateTimeMidnight);
|
|
SInt32 theCurInterval = (SInt32)::floor(theExactInterval);
|
|
|
|
//transfer_roll_interval is in days, theCurInterval is in seconds
|
|
SInt32 theRollInterval = this->GetRollIntervalInDays() * 60 * 60 * 24;
|
|
if (theCurInterval > theRollInterval)
|
|
return this->RollLog();
|
|
}
|
|
}
|
|
|
|
|
|
//now check size based log rolling
|
|
UInt32 theCurrentPos = ::ftell(fLog);
|
|
//max_transfer_log_size being 0 is a signal to ignore the setting.
|
|
if ((this->GetMaxLogBytes() != 0) &&
|
|
(theCurrentPos > this->GetMaxLogBytes()))
|
|
return this->RollLog();
|
|
return true;
|
|
}
|
|
|
|
Bool16 QTSSRollingLog::RenameLogFile(const char* inFileName)
|
|
{
|
|
//returns false if an error occurred, true otherwise
|
|
|
|
//this function takes care of renaming a log file from "myLogFile.log" to
|
|
//"myLogFile.981217000.log" or if that is already taken, myLogFile.981217001.log", etc
|
|
|
|
//fix 2287086. Rolled log name can be different than original log name
|
|
//GetLogDir returns a copy of the log dir
|
|
OSCharArrayDeleter logDirectory(this->GetLogDir());
|
|
|
|
//create the log directory if it doesn't already exist
|
|
OS::RecursiveMakeDir(logDirectory.GetObject());
|
|
|
|
//GetLogName returns a copy of the log name
|
|
OSCharArrayDeleter logBaseName(this->GetLogName());
|
|
|
|
//QTStreamingServer.981217003.log
|
|
//format the new file name
|
|
OSCharArrayDeleter theNewNameBuffer(NEW char[::strlen(logDirectory) + kMaxFilenameLengthInBytes + 3]);
|
|
|
|
//copy over the directory - append a '/' if it's missing
|
|
::strcpy(theNewNameBuffer, logDirectory);
|
|
if (theNewNameBuffer[::strlen(theNewNameBuffer)-1] != kPathDelimiterChar)
|
|
{
|
|
::strcat(theNewNameBuffer, kPathDelimiterString);
|
|
}
|
|
|
|
//copy over the base filename
|
|
::strcat(theNewNameBuffer, logBaseName.GetObject());
|
|
|
|
//append the date the file was created
|
|
struct tm timeResult;
|
|
struct tm* theLocalTime = qtss_localtime(&fLogCreateTime, &timeResult);
|
|
char timeString[10];
|
|
qtss_strftime(timeString, 10, ".%y%m%d", theLocalTime);
|
|
::strcat(theNewNameBuffer, timeString);
|
|
|
|
SInt32 theBaseNameLength = ::strlen(theNewNameBuffer);
|
|
|
|
|
|
//loop until we find a unique name to rename this file
|
|
//and append the log number and suffix
|
|
SInt32 theErr = 0;
|
|
for (SInt32 x = 0; (theErr == 0) && (x<=1000); x++)
|
|
{
|
|
if (x == 1000) //we don't have any digits left, so just reuse the "---" until tomorrow...
|
|
{
|
|
//add a bogus log number and exit the loop
|
|
qtss_sprintf(theNewNameBuffer + theBaseNameLength, "---.log");
|
|
break;
|
|
}
|
|
|
|
//add the log number & suffix
|
|
qtss_sprintf(theNewNameBuffer + theBaseNameLength, "%03"_S32BITARG_".log", x);
|
|
|
|
//assume that when ::stat returns an error, it is becase
|
|
//the file doesnt exist. Once that happens, we have a unique name
|
|
// csl - shouldn't you watch for a ENOENT result?
|
|
struct stat theIdontCare;
|
|
theErr = ::stat(theNewNameBuffer, &theIdontCare);
|
|
WarnV((theErr == 0 || OSThread::GetErrno() == ENOENT), "unexpected stat error in RenameLogFile");
|
|
|
|
}
|
|
|
|
//rename the file. Use posix rename function
|
|
int result = ::rename(inFileName, theNewNameBuffer);
|
|
if (result == -1)
|
|
theErr = (SInt32)OSThread::GetErrno();
|
|
else
|
|
theErr = 0;
|
|
|
|
WarnV(theErr == 0 , "unexpected rename error in RenameLogFile");
|
|
|
|
|
|
if (theErr != 0)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
Bool16 QTSSRollingLog::DoesFileExist(const char *inPath)
|
|
{
|
|
struct stat theStat;
|
|
int theErr = ::stat(inPath, &theStat);
|
|
if (theErr != 0)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
time_t QTSSRollingLog::WriteLogHeader(FILE* inFile)
|
|
{
|
|
OSMutexLocker locker(&fMutex);
|
|
|
|
//The point of this header is to record the exact time the log file was created,
|
|
//in a format that is easy to parse through whenever we open the file again.
|
|
//This is necessary to support log rolling based on a time interval, and POSIX doesn't
|
|
//support a create date in files.
|
|
time_t calendarTime = ::time(NULL);
|
|
Assert(-1 != calendarTime);
|
|
if (-1 == calendarTime)
|
|
return -1;
|
|
|
|
struct tm timeResult;
|
|
struct tm* theLocalTime = qtss_localtime(&calendarTime, &timeResult);
|
|
Assert(NULL != theLocalTime);
|
|
if (NULL == theLocalTime)
|
|
return -1;
|
|
|
|
//
|
|
// Files are always created at hour 0 (we don't care about the time, we always
|
|
// want them to roll at midnight.
|
|
//theLocalTime->tm_hour = 0;
|
|
//theLocalTime->tm_min = 0;
|
|
//theLocalTime->tm_sec = 0;
|
|
|
|
char tempbuf[1024];
|
|
qtss_strftime(tempbuf, sizeof(tempbuf), "#Log File Created On: %m/%d/%Y %H:%M:%S\n", theLocalTime);
|
|
//qtss_sprintf(tempbuf, "#Log File Created On: %d/%d/%d %d:%d:%d %d:%d:%d GMT\n",
|
|
// theLocalTime->tm_mon, theLocalTime->tm_mday, theLocalTime->tm_year,
|
|
// theLocalTime->tm_hour, theLocalTime->tm_min, theLocalTime->tm_sec,
|
|
// theLocalTime->tm_yday, theLocalTime->tm_wday, theLocalTime->tm_isdst);
|
|
this->WriteToLog(tempbuf, !kAllowLogToRoll);
|
|
|
|
return this->ReadLogHeader(inFile);
|
|
}
|
|
|
|
time_t QTSSRollingLog::ReadLogHeader(FILE* inFile)
|
|
{
|
|
OSMutexLocker locker(&fMutex);
|
|
|
|
//This function reads the header in a log file, returning the time stored
|
|
//at the beginning of this file. This value is used to determine when to
|
|
//roll the log.
|
|
//Returns -1 if the header is bogus. In that case, just ignore time based log rolling
|
|
|
|
//first seek to the beginning of the file
|
|
SInt32 theCurrentPos = ::ftell(inFile);
|
|
if (theCurrentPos == -1)
|
|
return -1;
|
|
(void)::rewind(inFile);
|
|
|
|
const UInt32 kMaxHeaderLength = 500;
|
|
char theFirstLine[kMaxHeaderLength];
|
|
|
|
if (NULL == ::fgets(theFirstLine, kMaxHeaderLength, inFile))
|
|
{
|
|
::fseek(inFile, 0, SEEK_END);
|
|
return -1;
|
|
}
|
|
::fseek(inFile, 0, SEEK_END);
|
|
|
|
struct tm theFileCreateTime;
|
|
|
|
// Zero out fields we will not be using
|
|
theFileCreateTime.tm_isdst = -1;
|
|
theFileCreateTime.tm_wday = 0;
|
|
theFileCreateTime.tm_yday = 0;
|
|
|
|
//if (EOF == ::sscanf(theFirstLine, "#Log File Created On: %d/%d/%d %d:%d:%d\n",
|
|
// &theFileCreateTime.tm_mon, &theFileCreateTime.tm_mday, &theFileCreateTime.tm_year,
|
|
// &theFileCreateTime.tm_hour, &theFileCreateTime.tm_min, &theFileCreateTime.tm_sec))
|
|
// return -1;
|
|
|
|
//
|
|
// We always want to roll at hour 0, so ignore the time of creation
|
|
|
|
if (EOF == ::sscanf(theFirstLine, "#Log File Created On: %d/%d/%d %d:%d:%d\n",
|
|
&theFileCreateTime.tm_mon, &theFileCreateTime.tm_mday, &theFileCreateTime.tm_year,
|
|
&theFileCreateTime.tm_hour, &theFileCreateTime.tm_min, &theFileCreateTime.tm_sec))
|
|
return -1;
|
|
|
|
//
|
|
// It should be like this anyway, but if the log file is legacy, then...
|
|
// No! The log file will have the actual time in it but we shall return the exact time
|
|
//theFileCreateTime.tm_hour = 0;
|
|
//theFileCreateTime.tm_min = 0;
|
|
//theFileCreateTime.tm_sec = 0;
|
|
|
|
// Actually, it seems like all platforms need this.
|
|
//#ifdef __Win32__
|
|
// Win32 has slightly different atime basis than UNIX.
|
|
theFileCreateTime.tm_yday--;
|
|
theFileCreateTime.tm_mon--;
|
|
theFileCreateTime.tm_year -= 1900;
|
|
//#endif
|
|
|
|
#if 0
|
|
//use ansi routines for getting the date.
|
|
time_t calendarTime = ::time(NULL);
|
|
Assert(-1 != calendarTime);
|
|
if (-1 == calendarTime)
|
|
return false;
|
|
|
|
struct tm timeResult;
|
|
struct tm* theLocalTime = qtss_localtime(&calendarTime, &timeResult);
|
|
Assert(NULL != theLocalTime);
|
|
if (NULL == theLocalTime)
|
|
return false;
|
|
#endif
|
|
|
|
//ok, we should have a filled in tm struct. Convert it to a time_t.
|
|
//time_t thePoopTime = ::mktime(theLocalTime);
|
|
time_t theTime = ::mktime(&theFileCreateTime);
|
|
return theTime;
|
|
}
|
|
|
|
SInt64 QTSSRollingLog::Run()
|
|
{
|
|
//
|
|
// If we are going away, just return
|
|
EventFlags events = this->GetEvents();
|
|
if (events & Task::kKillEvent)
|
|
return -1;
|
|
|
|
OSMutexLocker locker(&fMutex);
|
|
|
|
UInt32 theRollInterval = (this->GetRollIntervalInDays()) * 60 * 60 * 24;
|
|
|
|
if((fLogCreateTime != -1) && (fLog != NULL))
|
|
{
|
|
time_t logRollTimeMidnight = -1;
|
|
this->ResetToMidnight(&fLogCreateTime, &logRollTimeMidnight);
|
|
Assert(logRollTimeMidnight != -1);
|
|
|
|
if(theRollInterval != 0)
|
|
{
|
|
time_t calendarTime = ::time(NULL);
|
|
Assert(-1 != calendarTime);
|
|
double theExactInterval = ::difftime(calendarTime, logRollTimeMidnight);
|
|
if(theExactInterval > 0) {
|
|
UInt32 theCurInterval = (UInt32)::floor(theExactInterval);
|
|
if (theCurInterval >= theRollInterval)
|
|
this->RollLog();
|
|
}
|
|
}
|
|
}
|
|
return 60 * 1000;
|
|
}
|
|
|
|
void QTSSRollingLog::ResetToMidnight(time_t* inTimePtr, time_t* outTimePtr)
|
|
{
|
|
if(*inTimePtr == -1)
|
|
{
|
|
*outTimePtr = -1;
|
|
return;
|
|
}
|
|
|
|
struct tm timeResult;
|
|
struct tm* theLocalTime = qtss_localtime(inTimePtr, &timeResult);
|
|
Assert(theLocalTime != NULL);
|
|
|
|
theLocalTime->tm_hour = 0;
|
|
theLocalTime->tm_min = 0;
|
|
theLocalTime->tm_sec = 0;
|
|
|
|
// some weird stuff
|
|
//theLocalTime->tm_yday--;
|
|
//theLocalTime->tm_mon--;
|
|
//theLocalTime->tm_year -= 1900;
|
|
|
|
*outTimePtr = ::mktime(theLocalTime);
|
|
|
|
}
|