1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 #if defined(_MSC_VER)
19 	#pragma warning ( disable: 4231 4251 4275 4786 )
20 #endif
21 
22 #ifdef LOG4CXX_MULTI_PROCESS
23 	#include <apr_portable.h>
24 	#include <libgen.h>
25 	#include <apr_file_io.h>
26 	#include <apr_atomic.h>
27 	#include <apr_mmap.h>
28 	#ifndef MAX_FILE_LEN
29 		#define MAX_FILE_LEN 2048
30 	#endif
31 	#include <log4cxx/pattern/filedatepatternconverter.h>
32 	#include <log4cxx/helpers/date.h>
33 #endif
34 
35 #include <log4cxx/rolling/rollingfileappender.h>
36 #include <log4cxx/helpers/loglog.h>
37 #include <log4cxx/helpers/synchronized.h>
38 #include <log4cxx/rolling/rolloverdescription.h>
39 #include <log4cxx/helpers/fileoutputstream.h>
40 #include <log4cxx/helpers/bytebuffer.h>
41 #include <log4cxx/rolling/fixedwindowrollingpolicy.h>
42 #include <log4cxx/rolling/manualtriggeringpolicy.h>
43 
44 using namespace log4cxx;
45 using namespace log4cxx::rolling;
46 using namespace log4cxx::helpers;
47 using namespace log4cxx::spi;
48 
49 
50 IMPLEMENT_LOG4CXX_OBJECT(RollingFileAppenderSkeleton)
IMPLEMENT_LOG4CXX_OBJECT(RollingFileAppender)51 IMPLEMENT_LOG4CXX_OBJECT(RollingFileAppender)
52 
53 
54 /**
55  * Construct a new instance.
56  */
57 RollingFileAppenderSkeleton::RollingFileAppenderSkeleton() : _event(NULL)
58 {
59 }
60 
RollingFileAppender()61 RollingFileAppender::RollingFileAppender()
62 {
63 }
64 
65 /**
66  * Prepare instance of use.
67  */
activateOptions(Pool & p)68 void RollingFileAppenderSkeleton::activateOptions(Pool& p)
69 {
70 	if (rollingPolicy == NULL)
71 	{
72 		FixedWindowRollingPolicy* fwrp = new FixedWindowRollingPolicy();
73 		fwrp->setFileNamePattern(getFile() + LOG4CXX_STR(".%i"));
74 		rollingPolicy = fwrp;
75 	}
76 
77 	//
78 	//  if no explicit triggering policy and rolling policy is both.
79 	//
80 	if (triggeringPolicy == NULL)
81 	{
82 		TriggeringPolicyPtr trig(rollingPolicy);
83 
84 		if (trig != NULL)
85 		{
86 			triggeringPolicy = trig;
87 		}
88 	}
89 
90 	if (triggeringPolicy == NULL)
91 	{
92 		triggeringPolicy = new ManualTriggeringPolicy();
93 	}
94 
95 	{
96 		LOCK_W sync(mutex);
97 		triggeringPolicy->activateOptions(p);
98 		rollingPolicy->activateOptions(p);
99 
100 		try
101 		{
102 			RolloverDescriptionPtr rollover1 =
103 				rollingPolicy->initialize(getFile(), getAppend(), p);
104 
105 			if (rollover1 != NULL)
106 			{
107 				ActionPtr syncAction(rollover1->getSynchronous());
108 
109 				if (syncAction != NULL)
110 				{
111 					syncAction->execute(p);
112 				}
113 
114 				setFile(rollover1->getActiveFileName());
115 				setAppend(rollover1->getAppend());
116 
117 				//
118 				//  async action not yet implemented
119 				//
120 				ActionPtr asyncAction(rollover1->getAsynchronous());
121 
122 				if (asyncAction != NULL)
123 				{
124 					asyncAction->execute(p);
125 				}
126 			}
127 
128 			File activeFile;
129 			activeFile.setPath(getFile());
130 
131 			if (getAppend())
132 			{
133 				fileLength = activeFile.length(p);
134 			}
135 			else
136 			{
137 				fileLength = 0;
138 			}
139 
140 			FileAppender::activateOptions(p);
141 		}
142 		catch (std::exception&)
143 		{
144 			LogLog::warn(
145 				LogString(LOG4CXX_STR("Exception will initializing RollingFileAppender named "))
146 				+ getName());
147 		}
148 	}
149 }
150 
151 #ifdef LOG4CXX_MULTI_PROCESS
releaseFileLock(apr_file_t * lock_file)152 void RollingFileAppenderSkeleton::releaseFileLock(apr_file_t* lock_file)
153 {
154 	if (lock_file)
155 	{
156 		apr_status_t stat = apr_file_unlock(lock_file);
157 
158 		if (stat != APR_SUCCESS)
159 		{
160 			LogLog::warn(LOG4CXX_STR("flock: unlock failed"));
161 		}
162 
163 		apr_file_close(lock_file);
164 		lock_file = NULL;
165 	}
166 }
167 #endif
168 /**
169    Implements the usual roll over behaviour.
170 
171    <p>If <code>MaxBackupIndex</code> is positive, then files
172    {<code>File.1</code>, ..., <code>File.MaxBackupIndex -1</code>}
173    are renamed to {<code>File.2</code>, ...,
174    <code>File.MaxBackupIndex</code>}. Moreover, <code>File</code> is
175    renamed <code>File.1</code> and closed. A new <code>File</code> is
176    created to receive further log output.
177 
178    <p>If <code>MaxBackupIndex</code> is equal to zero, then the
179    <code>File</code> is truncated with no backup files created.
180 
181  * @return true if rollover performed.
182  */
rollover(Pool & p)183 bool RollingFileAppenderSkeleton::rollover(Pool& p)
184 {
185 	//
186 	//   can't roll without a policy
187 	//
188 	if (rollingPolicy != NULL)
189 	{
190 
191 		{
192 			LOCK_W sync(mutex);
193 
194 #ifdef LOG4CXX_MULTI_PROCESS
195 			std::string fileName(getFile());
196 			RollingPolicyBase* basePolicy = dynamic_cast<RollingPolicyBase* >(&(*rollingPolicy));
197 			apr_time_t n = apr_time_now();
198 			ObjectPtr obj(new Date(n));
199 			LogString fileNamePattern;
200 
201 			if (basePolicy)
202 			{
203 				if (basePolicy->getPatternConverterList().size())
204 				{
205 					(*(basePolicy->getPatternConverterList().begin()))->format(obj, fileNamePattern, p);
206 					fileName = std::string(fileNamePattern);
207 				}
208 			}
209 
210 			bool bAlreadyRolled = true;
211 			char szDirName[MAX_FILE_LEN] = {'\0'};
212 			char szBaseName[MAX_FILE_LEN] = {'\0'};
213 			char szUid[MAX_FILE_LEN] = {'\0'};
214 			memcpy(szDirName, fileName.c_str(), fileName.size() > MAX_FILE_LEN ? MAX_FILE_LEN : fileName.size());
215 			memcpy(szBaseName, fileName.c_str(), fileName.size() > MAX_FILE_LEN ? MAX_FILE_LEN : fileName.size());
216 			apr_uid_t uid;
217 			apr_gid_t groupid;
218 			apr_status_t stat = apr_uid_current(&uid, &groupid, pool.getAPRPool());
219 
220 			if (stat == APR_SUCCESS)
221 			{
222 				snprintf(szUid, MAX_FILE_LEN, "%u", uid);
223 			}
224 
225 			const std::string lockname = std::string(::dirname(szDirName)) + "/." + ::basename(szBaseName) + szUid + ".lock";
226 			apr_file_t* lock_file;
227 			stat = apr_file_open(&lock_file, lockname.c_str(), APR_CREATE | APR_READ | APR_WRITE, APR_OS_DEFAULT, p.getAPRPool());
228 
229 			if (stat != APR_SUCCESS)
230 			{
231 				std::string err = "lockfile return error: open lockfile failed. ";
232 				err += (strerror(errno));
233 				LogLog::warn(LOG4CXX_STR(err.c_str()));
234 				bAlreadyRolled = false;
235 				lock_file = NULL;
236 			}
237 			else
238 			{
239 				stat = apr_file_lock(lock_file, APR_FLOCK_EXCLUSIVE);
240 
241 				if (stat != APR_SUCCESS)
242 				{
243 					std::string err = "apr_file_lock: lock failed. ";
244 					err += (strerror(errno));
245 					LogLog::warn(LOG4CXX_STR(err.c_str()));
246 					bAlreadyRolled = false;
247 				}
248 				else
249 				{
250 					if (_event)
251 					{
252 						triggeringPolicy->isTriggeringEvent(this, *_event, getFile(), getFileLength());
253 					}
254 				}
255 			}
256 
257 			if (bAlreadyRolled)
258 			{
259 				apr_finfo_t finfo1, finfo2;
260 				apr_status_t st1, st2;
261 				apr_file_t* _fd = getWriter()->getOutPutStreamPtr()->getFileOutPutStreamPtr().getFilePtr();
262 				st1 = apr_file_info_get(&finfo1, APR_FINFO_IDENT, _fd);
263 
264 				if (st1 != APR_SUCCESS)
265 				{
266 					LogLog::warn(LOG4CXX_STR("apr_file_info_get failed"));
267 				}
268 
269 				st2 = apr_stat(&finfo2, std::string(getFile()).c_str(), APR_FINFO_IDENT, p.getAPRPool());
270 
271 				if (st2 != APR_SUCCESS)
272 				{
273 					LogLog::warn(LOG4CXX_STR("apr_stat failed."));
274 				}
275 
276 				bAlreadyRolled = ((st1 == APR_SUCCESS) && (st2 == APR_SUCCESS)
277 						&& ((finfo1.device != finfo2.device) || (finfo1.inode != finfo2.inode)));
278 			}
279 
280 			if (!bAlreadyRolled)
281 			{
282 #endif
283 
284 				try
285 				{
286 					RolloverDescriptionPtr rollover1(rollingPolicy->rollover(this->getFile(), this->getAppend(), p));
287 
288 					if (rollover1 != NULL)
289 					{
290 						if (rollover1->getActiveFileName() == getFile())
291 						{
292 							closeWriter();
293 
294 							bool success = true;
295 
296 							if (rollover1->getSynchronous() != NULL)
297 							{
298 								success = false;
299 
300 								try
301 								{
302 									success = rollover1->getSynchronous()->execute(p);
303 								}
304 								catch (std::exception&)
305 								{
306 									LogLog::warn(LOG4CXX_STR("Exception on rollover"));
307 								}
308 							}
309 
310 							if (success)
311 							{
312 								if (rollover1->getAppend())
313 								{
314 									fileLength = File().setPath(rollover1->getActiveFileName()).length(p);
315 								}
316 								else
317 								{
318 									fileLength = 0;
319 								}
320 
321 								//
322 								//  async action not yet implemented
323 								//
324 								ActionPtr asyncAction(rollover1->getAsynchronous());
325 
326 								if (asyncAction != NULL)
327 								{
328 									asyncAction->execute(p);
329 								}
330 
331 								setFile(
332 									rollover1->getActiveFileName(), rollover1->getAppend(),
333 									bufferedIO, bufferSize, p);
334 							}
335 							else
336 							{
337 								setFile(
338 									rollover1->getActiveFileName(), true, bufferedIO, bufferSize, p);
339 							}
340 						}
341 						else
342 						{
343 							OutputStreamPtr os(new FileOutputStream(
344 									rollover1->getActiveFileName(), rollover1->getAppend()));
345 							WriterPtr newWriter(createWriter(os));
346 							closeWriter();
347 							setFile(rollover1->getActiveFileName());
348 							setWriter(newWriter);
349 
350 							bool success = true;
351 
352 							if (rollover1->getSynchronous() != NULL)
353 							{
354 								success = false;
355 
356 								try
357 								{
358 									success = rollover1->getSynchronous()->execute(p);
359 								}
360 								catch (std::exception&)
361 								{
362 									LogLog::warn(LOG4CXX_STR("Exception during rollover"));
363 								}
364 							}
365 
366 							if (success)
367 							{
368 								if (rollover1->getAppend())
369 								{
370 									fileLength = File().setPath(rollover1->getActiveFileName()).length(p);
371 								}
372 								else
373 								{
374 									fileLength = 0;
375 								}
376 
377 								//
378 								//   async action not yet implemented
379 								//
380 								ActionPtr asyncAction(rollover1->getAsynchronous());
381 
382 								if (asyncAction != NULL)
383 								{
384 									asyncAction->execute(p);
385 								}
386 							}
387 
388 							writeHeader(p);
389 						}
390 
391 #ifdef LOG4CXX_MULTI_PROCESS
392 						releaseFileLock(lock_file);
393 #endif
394 						return true;
395 					}
396 				}
397 				catch (std::exception&)
398 				{
399 					LogLog::warn(LOG4CXX_STR("Exception during rollover"));
400 				}
401 
402 #ifdef LOG4CXX_MULTI_PROCESS
403 			}
404 			else
405 			{
406 				reopenLatestFile(p);
407 			}
408 
409 			releaseFileLock(lock_file);
410 #endif
411 		}
412 	}
413 
414 	return false;
415 }
416 
417 #ifdef LOG4CXX_MULTI_PROCESS
418 /**
419  * re-open current file when its own handler has been renamed
420  */
reopenLatestFile(Pool & p)421 void RollingFileAppenderSkeleton::reopenLatestFile(Pool& p)
422 {
423 	closeWriter();
424 	OutputStreamPtr os(new FileOutputStream(getFile(), true));
425 	WriterPtr newWriter(createWriter(os));
426 	setFile(getFile());
427 	setWriter(newWriter);
428 	fileLength = File().setPath(getFile()).length(p);
429 	writeHeader(p);
430 }
431 
432 #endif
433 
434 /**
435  * {@inheritDoc}
436 */
subAppend(const LoggingEventPtr & event,Pool & p)437 void RollingFileAppenderSkeleton::subAppend(const LoggingEventPtr& event, Pool& p)
438 {
439 	// The rollover check must precede actual writing. This is the
440 	// only correct behavior for time driven triggers.
441 	if (
442 		triggeringPolicy->isTriggeringEvent(
443 			this, event, getFile(), getFileLength()))
444 	{
445 		//
446 		//   wrap rollover request in try block since
447 		//    rollover may fail in case read access to directory
448 		//    is not provided.  However appender should still be in good
449 		//     condition and the append should still happen.
450 		try
451 		{
452 			_event = &(const_cast<LoggingEventPtr&>(event));
453 			rollover(p);
454 		}
455 		catch (std::exception&)
456 		{
457 			LogLog::warn(LOG4CXX_STR("Exception during rollover attempt."));
458 		}
459 	}
460 
461 #ifdef LOG4CXX_MULTI_PROCESS
462 	//do re-check before every write
463 	//
464 	apr_finfo_t finfo1, finfo2;
465 	apr_status_t st1, st2;
466 	apr_file_t* _fd = getWriter()->getOutPutStreamPtr()->getFileOutPutStreamPtr().getFilePtr();
467 	st1 = apr_file_info_get(&finfo1, APR_FINFO_IDENT, _fd);
468 
469 	if (st1 != APR_SUCCESS)
470 	{
471 		LogLog::warn(LOG4CXX_STR("apr_file_info_get failed"));
472 	}
473 
474 	st2 = apr_stat(&finfo2, std::string(getFile()).c_str(), APR_FINFO_IDENT, p.getAPRPool());
475 
476 	if (st2 != APR_SUCCESS)
477 	{
478 		std::string err = "apr_stat failed. file:" + std::string(getFile());
479 		LogLog::warn(LOG4CXX_STR(err.c_str()));
480 	}
481 
482 	bool bAlreadyRolled = ((st1 == APR_SUCCESS) && (st2 == APR_SUCCESS)
483 			&& ((finfo1.device != finfo2.device) || (finfo1.inode != finfo2.inode)));
484 
485 	if (bAlreadyRolled)
486 	{
487 		reopenLatestFile(p);
488 	}
489 
490 #endif
491 
492 	FileAppender::subAppend(event, p);
493 }
494 
495 /**
496  * Get rolling policy.
497  * @return rolling policy.
498  */
getRollingPolicy() const499 RollingPolicyPtr RollingFileAppenderSkeleton::getRollingPolicy() const
500 {
501 	return rollingPolicy;
502 }
503 
504 /**
505  * Get triggering policy.
506  * @return triggering policy.
507  */
getTriggeringPolicy() const508 TriggeringPolicyPtr RollingFileAppenderSkeleton::getTriggeringPolicy() const
509 {
510 	return triggeringPolicy;
511 }
512 
513 /**
514  * Sets the rolling policy.
515  * @param policy rolling policy.
516  */
setRollingPolicy(const RollingPolicyPtr & policy)517 void RollingFileAppenderSkeleton::setRollingPolicy(const RollingPolicyPtr& policy)
518 {
519 	rollingPolicy = policy;
520 }
521 
522 /**
523  * Set triggering policy.
524  * @param policy triggering policy.
525  */
setTriggeringPolicy(const TriggeringPolicyPtr & policy)526 void RollingFileAppenderSkeleton::setTriggeringPolicy(const TriggeringPolicyPtr& policy)
527 {
528 	triggeringPolicy = policy;
529 }
530 
531 /**
532  * Close appender.  Waits for any asynchronous file compression actions to be completed.
533  */
close()534 void RollingFileAppenderSkeleton::close()
535 {
536 	FileAppender::close();
537 }
538 
539 namespace log4cxx
540 {
541 namespace rolling
542 {
543 /**
544  * Wrapper for OutputStream that will report all write
545  * operations back to this class for file length calculations.
546  */
547 class CountingOutputStream : public OutputStream
548 {
549 		/**
550 		 * Wrapped output stream.
551 		 */
552 	private:
553 		OutputStreamPtr os;
554 
555 		/**
556 		 * Rolling file appender to inform of stream writes.
557 		 */
558 		RollingFileAppenderSkeleton* rfa;
559 
560 	public:
561 		/**
562 		 * Constructor.
563 		 * @param os output stream to wrap.
564 		 * @param rfa rolling file appender to inform.
565 		 */
CountingOutputStream(OutputStreamPtr & os1,RollingFileAppenderSkeleton * rfa1)566 		CountingOutputStream(
567 			OutputStreamPtr& os1, RollingFileAppenderSkeleton* rfa1) :
568 			os(os1), rfa(rfa1)
569 		{
570 		}
571 
572 		/**
573 		 * {@inheritDoc}
574 		 */
close(Pool & p)575 		void close(Pool& p)
576 		{
577 			os->close(p);
578 			rfa = 0;
579 		}
580 
581 		/**
582 		 * {@inheritDoc}
583 		 */
flush(Pool & p)584 		void flush(Pool& p)
585 		{
586 			os->flush(p);
587 		}
588 
589 		/**
590 		 * {@inheritDoc}
591 		 */
write(ByteBuffer & buf,Pool & p)592 		void write(ByteBuffer& buf, Pool& p)
593 		{
594 			os->write(buf, p);
595 
596 			if (rfa != 0)
597 			{
598 #ifndef LOG4CXX_MULTI_PROCESS
599 				rfa->incrementFileLength(buf.limit());
600 #else
601 				rfa->setFileLength(File().setPath(rfa->getFile()).length(p));
602 #endif
603 			}
604 		}
605 
606 #ifdef LOG4CXX_MULTI_PROCESS
getFileOutPutStreamPtr()607 		OutputStream& getFileOutPutStreamPtr()
608 		{
609 			return *os;
610 		}
611 #endif
612 };
613 }
614 }
615 
616 /**
617    Returns an OutputStreamWriter when passed an OutputStream.  The
618    encoding used will depend on the value of the
619    <code>encoding</code> property.  If the encoding value is
620    specified incorrectly the writer will be opened using the default
621    system encoding (an error message will be printed to the loglog.
622  @param os output stream, may not be null.
623  @return new writer.
624  */
createWriter(OutputStreamPtr & os)625 WriterPtr RollingFileAppenderSkeleton::createWriter(OutputStreamPtr& os)
626 {
627 	OutputStreamPtr cos(new CountingOutputStream(os, this));
628 	return FileAppender::createWriter(cos);
629 }
630 
631 /**
632  * Get byte length of current active log file.
633  * @return byte length of current active log file.
634  */
getFileLength() const635 size_t RollingFileAppenderSkeleton::getFileLength() const
636 {
637 	return fileLength;
638 }
639 
640 #ifdef LOG4CXX_MULTI_PROCESS
setFileLength(size_t length)641 void RollingFileAppenderSkeleton::setFileLength(size_t length)
642 {
643 	fileLength = length;
644 }
645 #endif
646 
647 /**
648  * Increments estimated byte length of current active log file.
649  * @param increment additional bytes written to log file.
650  */
incrementFileLength(size_t increment)651 void RollingFileAppenderSkeleton::incrementFileLength(size_t increment)
652 {
653 	fileLength += increment;
654 }
655