xref: /haiku/src/servers/syslog_daemon/syslog_output.cpp (revision 0ce4c23d22fae64d10e5575687490fbdf8ee52b8)
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 <FindDirectory.h>
10 #include <Path.h>
11 #include <driver_settings.h>
12 
13 #include <syslog.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <unistd.h>
18 #include <sys/stat.h>
19 
20 
21 static 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 };
30 static const int32 kNumFacilities = 24;
31 
32 static int sLog = -1;
33 static char sLastMessage[1024];
34 static thread_id sLastThread;
35 static int32 sRepeatCount;
36 static size_t sLogMaxSize = 524288;	// 512kB
37 static 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 */
43 static status_t
44 prepare_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_COMMON_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 
97 static status_t
98 write_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 
117 static void
118 syslog_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 
208 void
209 init_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