1/*
2 * Copyright 2003-2010, Axel D��rfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "syslog_output.h"
8
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <sys/stat.h>
13#include <syslog.h>
14#include <unistd.h>
15
16#include <FindDirectory.h>
17#include <Path.h>
18#include <driver_settings.h>
19
20
21static const char *kFacilities[] = {
22	"KERN", "USER", "MAIL", "DAEMON",
23	"AUTH", "SYSLOGD", "LPR", "NEWS",
24	"UUCP", "CRON", "AUTHPRIV", "FTP",
25	"", "", "", "",
26	"LOCAL0", "LOCAL1", "LOCAL2", "LOCAL3",
27	"LOCAL4", "LOCAL5", "LOCAL6", "LOCAL7",
28	NULL
29};
30static const int32 kNumFacilities = 24;
31
32static int sLog = -1;
33static char sLastMessage[1024];
34static thread_id sLastThread;
35static int32 sRepeatCount;
36static size_t sLogMaxSize = 524288;	// 512kB
37static bool sLogTimeStamps = false;
38
39
40/*!	Creates the log file if not yet existing, or renames the old
41	log file, if it's too big already.
42*/
43static status_t
44prepare_output()
45{
46	bool needNew = true;
47	bool tooLarge = false;
48
49	if (sLog >= 0) {
50		// check file size
51		struct stat stat;
52		if (fstat(sLog, &stat) == 0) {
53			if (stat.st_size < (off_t)sLogMaxSize)
54				needNew = false;
55			else
56				tooLarge = true;
57		}
58	}
59
60	if (needNew) {
61		// close old file; it'll be (re)moved soon
62		if (sLog >= 0)
63			close(sLog);
64
65		// get path (and create it if necessary)
66		BPath base;
67		find_directory(B_SYSTEM_LOG_DIRECTORY, &base, true);
68
69		BPath syslog(base);
70		syslog.Append("syslog");
71
72		// move old file if it already exists
73		if (tooLarge) {
74			BPath oldlog(base);
75			oldlog.Append("syslog.old");
76
77			remove(oldlog.Path());
78			rename(syslog.Path(), oldlog.Path());
79
80			// ToDo: just remove old file if space on device is tight?
81		}
82
83		bool haveSyslog = sLog >= 0;
84
85		// open file
86		sLog = open(syslog.Path(), O_APPEND | O_CREAT | O_WRONLY, 0644);
87		if (!haveSyslog && sLog >= 0) {
88			// first time open, check file size again
89			prepare_output();
90		}
91	}
92
93	return sLog >= 0 ? B_OK : B_ERROR;
94}
95
96
97static status_t
98write_to_log(const char *buffer, int32 length)
99{
100	if (sRepeatCount > 0) {
101		char repeat[64];
102		ssize_t size = snprintf(repeat, sizeof(repeat),
103			"Last message repeated %" B_PRId32 " time%s\n", sRepeatCount,
104			sRepeatCount > 1 ? "s" : "");
105		sRepeatCount = 0;
106		if (write(sLog, repeat, strlen(repeat)) < size)
107			return B_ERROR;
108	}
109
110	if (write(sLog, buffer, length) < length)
111		return B_ERROR;
112
113	return B_OK;
114}
115
116
117static void
118syslog_output(syslog_message &message)
119{
120	char header[128];
121	int32 headerLength;
122	int32 pos = 0;
123
124	if (sLogTimeStamps) {
125		// parse & nicely print the time stamp from the message
126		struct tm when;
127		localtime_r(&message.when, &when);
128		pos = strftime(header, sizeof(header), "%Y-%m-%d %H:%M:%S ", &when);
129	}
130
131	// add facility
132	int facility = SYSLOG_FACILITY_INDEX(message.priority);
133	if (facility >= kNumFacilities)
134		facility = SYSLOG_FACILITY_INDEX(LOG_USER);
135	pos += snprintf(header + pos, sizeof(header) - pos, "%s",
136		kFacilities[facility]);
137
138	// add ident/thread ID
139	if (message.ident[0] == '\0') {
140		// ToDo: find out team name?
141	} else {
142		pos += snprintf(header + pos, sizeof(header) - pos, " '%s'",
143			message.ident);
144	}
145
146	if ((message.options & LOG_PID) != 0) {
147		pos += snprintf(header + pos, sizeof(header) - pos, "[%" B_PRId32 "]",
148			message.from);
149	}
150
151	headerLength = pos + strlcpy(header + pos, ": ", sizeof(header) - pos);
152	if (headerLength >= (int32)sizeof(header))
153		headerLength = sizeof(header) - 1;
154
155	// add header to every line of the message and write it to the syslog
156
157	char buffer[SYSLOG_MESSAGE_BUFFER_SIZE];
158	pos = 0;
159
160	while (true) {
161		strcpy(buffer, header);
162		int32 length;
163
164		const char *newLine = strchr(message.message + pos, '\n');
165		if (newLine != NULL) {
166			length = newLine - message.message + 1 - pos;
167			strlcpy(buffer + headerLength, message.message + pos, length + 1);
168			pos += length;
169		} else {
170			length = strlcpy(buffer + headerLength, message.message + pos,
171				sizeof(buffer) - headerLength);
172			if (length == 0)
173				break;
174		}
175
176		length += headerLength;
177
178		if (length >= (int32)sizeof(buffer))
179			length = sizeof(buffer) - 1;
180
181		if (message.from == sLastThread
182			&& !strncmp(buffer + headerLength, sLastMessage,
183					sizeof(sLastMessage))) {
184			// we got this message already
185			sRepeatCount++;
186		} else {
187			// dump message line
188
189			if (prepare_output() < B_OK
190				|| write_to_log(buffer, length) < B_OK) {
191				// cannot write to syslog!
192				break;
193			}
194
195			// save this message to suppress repeated messages
196			strlcpy(sLastMessage, buffer + headerLength, sizeof(sLastMessage));
197			sLastThread = message.from;
198		}
199
200		if (newLine == NULL || !newLine[1]) {
201			// wrote last line of output
202			break;
203		}
204	}
205}
206
207
208void
209init_syslog_output(SyslogDaemon *daemon)
210{
211	void *handle;
212
213	// get kernel syslog settings
214	handle = load_driver_settings("kernel");
215	if (handle != NULL) {
216		sLogTimeStamps = get_driver_boolean_parameter(handle,
217			"syslog_time_stamps", false, false);
218		const char *param = get_driver_parameter(handle,
219			"syslog_max_size", "0", "0");
220		int maxSize = strtol(param, NULL, 0);
221		if (strchr(param, 'k') || strchr(param, 'K'))
222			maxSize *= 1024;
223		else if (strchr(param, 'm') || strchr(param, 'M'))
224			maxSize *= 1048576;
225		if (maxSize > 0)
226			sLogMaxSize = maxSize;
227
228		unload_driver_settings(handle);
229	}
230
231	daemon->AddHandler(syslog_output);
232}
233