1/*
2 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
3 * Use is subject to license terms.
4 */
5
6/*
7 * BSD 3 Clause License
8 *
9 * Copyright (c) 2007, The Storage Networking Industry Association.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 *	- Redistributions of source code must retain the above copyright
15 *	  notice, this list of conditions and the following disclaimer.
16 *
17 *	- Redistributions in binary form must reproduce the above copyright
18 *	  notice, this list of conditions and the following disclaimer in
19 *	  the documentation and/or other materials provided with the
20 *	  distribution.
21 *
22 *	- Neither the name of The Storage Networking Industry Association (SNIA)
23 *	  nor the names of its contributors may be used to endorse or promote
24 *	  products derived from this software without specific prior written
25 *	  permission.
26 *
27 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
28 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
31 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 * POSSIBILITY OF SUCH DAMAGE.
38 */
39/* Copyright (c) 2007, The Storage Networking Industry Association. */
40/* Copyright (c) 1996, 1997 PDC, Network Appliance. All Rights Reserved */
41/*
42 * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
43 */
44
45#include <errno.h>
46#include <limits.h>
47#include <stdarg.h>
48#include <stdio.h>
49#include <stdlib.h>
50#include <syslog.h>
51#include <time.h>
52#include <string.h>
53#include <sys/stat.h>
54#include <unistd.h>
55#include <libgen.h>
56#include <pthread.h>
57#include <errno.h>
58#include "ndmpd_log.h"
59#include "ndmpd.h"
60#include "ndmpd_common.h"
61
62#define	LOG_PATH	"/var/log/ndmp"
63#define	LOG_FNAME	"ndmplog.%d"
64#define	LOG_FILE_CNT	5
65#define	LOG_FILE_SIZE	4 * 1024 * 1024
66#define	LOG_SIZE_INT	256
67
68static boolean_t debug = B_FALSE;
69static boolean_t log_to_stderr = B_FALSE;
70static FILE *logfp;
71static int ndmp_synclog = 1;
72
73
74/*
75 * Since we use buffered file I/O for log file, the thread may lose CPU.
76 * At this time, another thread can destroy the contents of the buffer
77 * that must be written to the log file.  The following mutex is used
78 * to allow only one thread to write into the log file.
79 */
80static mutex_t log_lock;
81
82static char *priority_str[] = {
83	"EMERGENCY",
84	"ALERT",
85	"CRITICAL",
86	"ERROR",
87	"WARNING",
88	"NOTICE",
89	"INFO",
90	"DEBUG",
91};
92
93
94/*
95 * mk_pathname
96 *
97 * Append the NDMP working directory path to the specified file
98 */
99static char *
100mk_pathname(char *fname, char *path, int idx)
101{
102	static char buf[PATH_MAX];
103	static char name[NAME_MAX];
104	char *fmt;
105	int len;
106
107	len = strnlen(path, PATH_MAX);
108	fmt = (path[len - 1] == '/') ? "%s%s" : "%s/%s";
109
110	/* LINTED variable format specifier */
111	(void) snprintf(name, NAME_MAX, fname, idx);
112
113	/* LINTED variable format specifier */
114	(void) snprintf(buf, PATH_MAX, fmt, path, name);
115	return (buf);
116}
117
118
119/*
120 * openlogfile
121 *
122 * Open the NDMP log file
123 */
124static int
125openlogfile(char *fname, char *mode)
126{
127	assert(fname != NULL && *fname != '\0' &&
128	    mode != NULL && *mode != '\0');
129
130	if ((logfp = fopen(fname, mode)) == NULL) {
131		perror("Error opening logfile");
132		return (-1);
133	}
134	(void) mutex_init(&log_lock, 0, NULL);
135
136	return (0);
137}
138
139
140/*
141 * log_write_cur_time
142 *
143 * Add the current time for each log entry
144 */
145static void
146log_write_cur_time(void)
147{
148	struct tm tm;
149	time_t secs;
150
151	secs = time(NULL);
152	(void) localtime_r(&secs, &tm);
153	(void) fprintf(logfp, "%2d/%02d %2d:%02d:%02d ",
154	    tm.tm_mon + 1, tm.tm_mday,
155	    tm.tm_hour, tm.tm_min, tm.tm_sec);
156}
157
158
159/*
160 * add_newline
161 *
162 * The new line at the end of each log
163 */
164static void
165add_newline(char *fmt)
166{
167	if (fmt[strlen(fmt) - 1] != '\n')
168		(void) fputc('\n', logfp);
169}
170
171
172/*
173 * log_append
174 *
175 * Append the message to the end of the log
176 */
177static void
178log_append(char *msg)
179{
180	log_write_cur_time();
181	(void) fwrite(msg, 1, strlen(msg), logfp);
182	add_newline(msg);
183	if (ndmp_synclog)
184		(void) fflush(logfp);
185}
186
187
188/*
189 * ndmp_log_openfile
190 *
191 * Open the log file either for append or write mode. This function should
192 * be called while ndmpd is still running single-threaded and in foreground.
193 */
194int
195ndmp_log_open_file(boolean_t to_stderr, boolean_t override_debug)
196{
197	char *fname, *mode, *lpath;
198	char oldfname[PATH_MAX];
199	struct stat64 st;
200	int i;
201
202	log_to_stderr = to_stderr;
203
204	/* read debug property if it isn't overriden by cmd line option */
205	if (override_debug)
206		debug = B_TRUE;
207	else
208		debug = ndmpd_get_prop_yorn(NDMP_DEBUG_MODE) ? B_TRUE : B_FALSE;
209
210	/* Create the debug path if it doesn't exist */
211	lpath = ndmpd_get_prop(NDMP_DEBUG_PATH);
212	if ((lpath == NULL) || (*lpath == '\0'))
213		lpath = LOG_PATH;
214
215	if (stat64(lpath, &st) < 0) {
216		if (mkdirp(lpath, 0755) < 0) {
217			(void) fprintf(stderr,
218			    "Could not create log path %s: %s\n",
219			    lpath, strerror(errno));
220			lpath = "/var";
221		}
222	}
223
224	/*
225	 * NDMP log file name will be {logfilename}.0 to {logfilename}.5, where
226	 * {logfilename}.0 will always be the latest and the {logfilename}.5
227	 * will be the oldest available file on the system. We keep maximum of 5
228	 * log files. With the new session the files are shifted to next number
229	 * and if the last file {logfilename}.5 exist, it will be overwritten
230	 * with {logfilename}.4.
231	 */
232	if (debug) {
233		i = LOG_FILE_CNT - 1;
234		while (i >= 0) {
235			fname = mk_pathname(LOG_FNAME, lpath, i);
236			(void) strncpy(oldfname, fname, PATH_MAX);
237			if (stat64(oldfname, &st) == -1) {
238				i--;
239				continue;
240			}
241
242			fname = mk_pathname(LOG_FNAME, lpath, i + 1);
243			if (rename(oldfname, fname))
244				(void) fprintf(stderr,
245				    "Could not rename %s to %s: %s\n",
246				    oldfname, fname, strerror(errno));
247			i--;
248		}
249	}
250
251	fname = mk_pathname(LOG_FNAME, lpath, 0);
252
253	/*
254	 * Append only if debug is not enable.
255	 */
256	if (debug)
257		mode = "w";
258	else
259		mode = "a";
260
261	return (openlogfile(fname, mode));
262}
263
264/*
265 * ndmp_log_close_file
266 *
267 * Close the log file
268 */
269void
270ndmp_log_close_file(void)
271{
272	if (logfp != NULL) {
273		(void) fclose(logfp);
274		logfp = NULL;
275	}
276	(void) mutex_destroy(&log_lock);
277}
278
279void
280ndmp_log(ulong_t priority, char *ndmp_log_info, char *fmt, ...)
281{
282	int c;
283	va_list args;
284	char *f, *b;
285	char ndmp_log_buf[PATH_MAX+KILOBYTE];
286	char ndmp_syslog_buf[PATH_MAX+KILOBYTE];
287	char buf[PATH_MAX+KILOBYTE];
288	char *errstr;
289
290	if ((priority == LOG_DEBUG) && !debug)
291		return;
292
293	(void) mutex_lock(&log_lock);
294
295	if (priority > 7)
296		priority = LOG_ERR;
297
298	va_start(args, fmt);
299	/* Replace text error messages if fmt contains %m */
300	b = buf;
301	f = fmt;
302	while (((c = *f++) != '\0') && (c != '\n') &&
303	    (b < &buf[PATH_MAX+KILOBYTE])) {
304		if (c != '%') {
305			*b++ = c;
306			continue;
307		}
308		if ((c = *f++) != 'm') {
309			*b++ = '%';
310			*b++ = c;
311			continue;
312		}
313
314		if ((errstr = strerror(errno)) == NULL) {
315			(void) snprintf(b, &buf[PATH_MAX+KILOBYTE] - b,
316			    "error %d", errno);
317		} else {
318			while ((*errstr != '\0') &&
319			    (b < &buf[PATH_MAX+KILOBYTE])) {
320				if (*errstr == '%') {
321					(void) strncpy(b, "%%", 2);
322					b += 2;
323				} else {
324					*b++ = *errstr;
325				}
326				errstr++;
327			}
328			*b = '\0';
329		}
330		b += strlen(b);
331	}
332	*b = '\0';
333
334	/* LINTED variable format specifier */
335	(void) vsnprintf(ndmp_syslog_buf, sizeof (ndmp_syslog_buf), buf, args);
336	va_end(args);
337
338	/* Send all logs other than debug, to syslog log file. */
339	if (priority != LOG_DEBUG)
340		syslog(priority, "%s", ndmp_syslog_buf);
341
342	/* ndmp_log_buf will have priority string and log info also */
343	(void) snprintf(ndmp_log_buf, sizeof (ndmp_log_buf), "%s: %s:%s",
344	    priority_str[priority], ndmp_log_info, ndmp_syslog_buf);
345
346	if (logfp != NULL)
347		log_append(ndmp_log_buf);
348
349	/* if ndmpd is running in foreground print log message to stderr */
350	if (log_to_stderr)
351		(void) fprintf(stderr, "%s\n", ndmp_log_buf);
352
353	(void) mutex_unlock(&log_lock);
354}
355