[Tizen] Unify dnetmemoryenumlib terms to match the codebase (#291)
[platform/upstream/coreclr.git] / src / utilcode / circularlog.cpp
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 //
6 // Circular file log 
7 //
8 #include "stdafx.h"
9
10 #include "utilcode.h"
11 #include "circularlog.h"
12
13 CircularLog::CircularLog()
14 {
15     m_bInit = false;
16 }
17
18 CircularLog::~CircularLog()
19 {
20     Shutdown();
21 }
22
23 bool    CircularLog::Init(const WCHAR* logname, const WCHAR* logHeader, DWORD maxSize)
24 {
25     Shutdown();
26
27     m_LogFilename = logname;        
28     m_LogFilename.Append(W(".log"));
29
30     m_LockFilename = logname;
31     m_LockFilename.Append(W(".lock"));
32     
33     m_OldLogFilename = logname;
34     m_OldLogFilename .Append(W(".old.log"));
35
36     if (logHeader)
37         m_LogHeader = logHeader;
38             
39     m_MaxSize = maxSize;
40     m_uLogCount = 0;
41
42     m_bInit = true;
43
44     if (CheckLogHeader())
45     {
46         CheckForLogReset(FALSE);
47     }
48     return true;
49 }
50
51 void CircularLog::Shutdown()
52 {
53     m_bInit = false;
54 }
55
56 void CircularLog::Log(const WCHAR* string)
57 {
58     if (!m_bInit) 
59     {
60         return;        
61     }
62     
63     HANDLE hLogFile = OpenFile();
64     if (hLogFile == INVALID_HANDLE_VALUE)
65     {
66         return;
67     }
68
69     // Check for file limit only once in a while
70     if ((m_uLogCount % 16) == 0)
71     {
72         // First do a quick check without acquiring lock, optimizing for the common case where file is not overflow.
73         LARGE_INTEGER fileSize;
74         if (GetFileSizeEx(hLogFile, &fileSize) && fileSize.QuadPart > m_MaxSize)
75         {
76             // Must close existing handle before calling CheckForOverflow, and re-open it afterwards.
77             CloseHandle(hLogFile);
78             CheckForLogReset(TRUE);
79             hLogFile = OpenFile();
80             if (hLogFile == INVALID_HANDLE_VALUE)
81             {
82                 return;
83             }
84         }
85     }
86     m_uLogCount++;
87
88     // Replace \n with \r\n (we're writing to a binary file)
89     NewArrayHolder<WCHAR> pwszConvertedHolder = new WCHAR[wcslen(string)*2 + 1];
90     WCHAR* pD = pwszConvertedHolder;
91     WCHAR previous = W('\0');
92     for (const WCHAR* pS = string ; *pS != W('\0') ; pS++)
93     {
94         // We get mixed strings ('\n' and '\r\n'). So attempt to filter
95         // the ones that don't need to have a '\r' added.
96         if (*pS == W('\n') && previous != W('\r'))
97         {
98             *pD = W('\r');
99             pD ++;
100         }
101
102         *pD = *pS;
103         pD++;
104         
105         previous = *pS;
106     }   
107
108     *pD = W('\0');
109
110     // Convert to Utf8 to reduce typical log file size
111     SString logString(pwszConvertedHolder);
112     StackScratchBuffer bufUtf8;
113     COUNT_T cBytesUtf8 = 0;
114     const UTF8 * pszUtf8Log = logString.GetUTF8(bufUtf8, &cBytesUtf8);
115     // Remove null terminator from log entry buffer
116     cBytesUtf8--;
117
118     DWORD dwWritten;
119     WriteFile(hLogFile, pszUtf8Log, (DWORD)cBytesUtf8, &dwWritten, NULL);
120     CloseHandle(hLogFile);
121 }
122     
123
124 BOOL CircularLog::CheckLogHeader()
125 {
126     BOOL fNeedsPushToBackupLog = FALSE;
127
128     // Check to make sure the header on the log is utf8, if it is not, push the current file to the .bak file and try again.
129     HANDLE hLogFile = WszCreateFile(
130         m_LogFilename.GetUnicode(),
131         FILE_READ_DATA,
132         FILE_SHARE_READ | FILE_SHARE_WRITE,
133         NULL,
134         OPEN_EXISTING,
135         FILE_ATTRIBUTE_NORMAL,
136         NULL);
137
138     if (hLogFile != INVALID_HANDLE_VALUE)
139     {
140         CHAR unicodeHeader []= {(char)0xef, (char)0xbb, (char)0xbf};
141         CHAR unicodeHeaderCheckBuf[_countof(unicodeHeader)];
142
143         DWORD dwRead = sizeof(unicodeHeaderCheckBuf);
144         fNeedsPushToBackupLog = !ReadFile(hLogFile, &unicodeHeaderCheckBuf, dwRead, &dwRead, NULL);
145
146         if (!fNeedsPushToBackupLog)
147         {
148             // Successfully read from file. Now check to ensure we read the right amount, and that we read the right data
149             if ((dwRead != sizeof(unicodeHeader)) || (0 != memcmp(unicodeHeader, unicodeHeaderCheckBuf, dwRead)))
150             {
151                 fNeedsPushToBackupLog = TRUE;
152             }
153         }
154         CloseHandle(hLogFile);
155     }
156
157     return fNeedsPushToBackupLog;
158 }
159
160 #define MOVE_FILE_RETRY_TIME 100
161 #define MOVE_FILE_RETRY_COUNT 10
162 void CircularLog::CheckForLogReset(BOOL fOverflow)
163 {
164     if (!m_MaxSize)
165     {
166         return;
167     }
168     
169     for (int i = 0; i < MOVE_FILE_RETRY_COUNT; i++)
170     {
171         FileLockHolder lock;
172         if (FAILED(lock.AcquireNoThrow(m_LockFilename.GetUnicode())))
173         {
174             // FileLockHolder::Acquire already has a retry loop, so don't retry if it fails.
175             return;
176         }
177
178         BOOL fLogNeedsReset = FALSE;
179
180         if (fOverflow)
181         {
182             WIN32_FILE_ATTRIBUTE_DATA fileData;
183             if (WszGetFileAttributesEx(
184                     m_LogFilename, 
185                     GetFileExInfoStandard, 
186                     &fileData) == FALSE)
187             {
188                 return;
189             }
190
191             unsigned __int64 fileSize = 
192                  (((unsigned __int64) fileData.nFileSizeHigh) << 32) |
193                   ((unsigned __int64) fileData.nFileSizeLow);
194
195                 
196             if (fileSize > (unsigned __int64) m_MaxSize)
197             {
198                 fLogNeedsReset = TRUE;
199             }
200         }
201         else
202         {
203             fLogNeedsReset = CheckLogHeader();
204         }
205
206         if (fLogNeedsReset)
207         {
208             // Push current log out to .old file
209             BOOL success = WszMoveFileEx(
210                 m_LogFilename.GetUnicode(),
211                 m_OldLogFilename.GetUnicode(),
212                 MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED);
213
214             if (success || GetLastError() != ERROR_SHARING_VIOLATION)
215             {
216                 return;
217             }
218         }
219         else
220         {
221             // Someone else moved the file before we can.
222             return;
223         }
224
225         // Don't want to hold the lock while sleeping.
226         lock.Release();
227         ClrSleepEx(MOVE_FILE_RETRY_TIME, FALSE);
228     }
229 }
230
231 #define OPEN_FILE_RETRY_TIME 100
232 #define OPEN_FILE_RETRY_COUNT 10
233 // Normally we open file with FILE_SHARE_WRITE, to avoid sharing violations between multiple threads or
234 // processes.  However, when we create a new file, the Unicode header must be written at the beginning of the
235 // file.  This can't be guaranteed with multiple writers, so we require exclusive access while creating a new
236 // file.  Our algorithm is first try to open with OPEN_EXISTING and FILE_SHARE_WRITE, and if that fails, try
237 // again with OPEN_ALWAYS and no write sharing.
238 HANDLE CircularLog::OpenFile()
239 {
240     for (int i = 0; i < OPEN_FILE_RETRY_COUNT; i++)
241     {
242         // First try to open an existing file allowing shared write.
243         HANDLE hLogFile = WszCreateFile(
244             m_LogFilename.GetUnicode(),
245             FILE_APPEND_DATA,
246             FILE_SHARE_READ | FILE_SHARE_WRITE,
247             NULL,
248             OPEN_EXISTING,
249             FILE_ATTRIBUTE_NORMAL,
250             NULL);
251
252         if (hLogFile != INVALID_HANDLE_VALUE)
253         {
254             return hLogFile;
255         }
256
257         if (GetLastError() == ERROR_FILE_NOT_FOUND)
258         {
259             // Try to create an new file with exclusive access.
260             HANDLE hLogFile = WszCreateFile(
261                 m_LogFilename.GetUnicode(),
262                 FILE_APPEND_DATA,
263                 FILE_SHARE_READ,
264                 NULL,
265                 OPEN_ALWAYS,
266                 FILE_ATTRIBUTE_NORMAL,
267                 NULL);
268
269             if (hLogFile != INVALID_HANDLE_VALUE)
270             {
271                 LARGE_INTEGER fileSize;
272                 if (! GetFileSizeEx(hLogFile, &fileSize))
273                 {
274                     CloseHandle(hLogFile);
275                     return INVALID_HANDLE_VALUE;
276                 }
277
278                 // If the file size is 0, need to write the Unicode header (utf8 bom).
279                 if (fileSize.QuadPart == 0)
280                 {
281                     CHAR unicodeHeader []= {(char)0xef, (char)0xbb, (char)0xbf};
282                     DWORD dwWritten;
283                     WriteFile(hLogFile, &unicodeHeader, sizeof(unicodeHeader), &dwWritten, NULL);
284
285                     // Write out header
286
287                     // Convert to Utf8 to reduce typical log file size
288                     StackScratchBuffer bufUtf8;
289                     COUNT_T cBytesUtf8 = 0;
290                     const UTF8 * pszUtf8Log = m_LogHeader.GetUTF8(bufUtf8, &cBytesUtf8);
291                     // Remove null terminator from log entry buffer
292                     cBytesUtf8--;
293
294                     WriteFile(hLogFile, pszUtf8Log, (DWORD)cBytesUtf8, &dwWritten, NULL);
295                 }
296
297                 return hLogFile;
298             }
299         }
300
301         if (GetLastError() != ERROR_SHARING_VIOLATION)
302         {
303             break;
304         }
305
306         ClrSleepEx(OPEN_FILE_RETRY_TIME, FALSE);
307     }
308
309     return INVALID_HANDLE_VALUE;
310
311 }