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