1 /*
2  *
3   ***** BEGIN LICENSE BLOCK *****
4 
5   Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
6 
7   This file is part of CLIXON.
8 
9   Licensed under the Apache License, Version 2.0 (the "License");
10   you may not use this file except in compliance with the License.
11   You may obtain a copy of the License at
12 
13     http://www.apache.org/licenses/LICENSE-2.0
14 
15   Unless required by applicable law or agreed to in writing, software
16   distributed under the License is distributed on an "AS IS" BASIS,
17   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   See the License for the specific language governing permissions and
19   limitations under the License.
20 
21   Alternatively, the contents of this file may be used under the terms of
22   the GNU General Public License Version 3 or later (the "GPL"),
23   in which case the provisions of the GPL are applicable instead
24   of those above. If you wish to allow use of your version of this file only
25   under the terms of the GPL, and not to allow others to
26   use your version of this file under the terms of Apache License version 2,
27   indicate your decision by deleting the provisions above and replace them with
28   the  notice and other provisions required by the GPL. If you do not delete
29   the provisions above, a recipient may use your version of this file under
30   the terms of any one of the Apache License version 2 or the GPL.
31 
32   ***** END LICENSE BLOCK *****
33 
34  *
35  * Regular logging and debugging. Syslog using levels.
36  */
37 
38 #ifdef HAVE_CONFIG_H
39 #include "clixon_config.h" /* generated by config & autoconf */
40 #endif
41 
42 #include <stdio.h>
43 #include <stdarg.h>
44 #include <stdlib.h>
45 #include <fcntl.h>
46 #include <unistd.h>
47 #include <syslog.h>
48 #include <string.h>
49 #include <time.h>
50 #include <errno.h>
51 #include <sys/time.h>
52 #include <sys/types.h>
53 
54 /* cligen */
55 #include <cligen/cligen.h>
56 
57 /* clicon */
58 #include "clixon_err.h"
59 #include "clixon_log.h"
60 
61 /* The global debug level. 0 means no debug
62  * @note There are pros and cons in having the debug state as a global variable. The
63  * alternative to bind it to the clicon handle (h) was considered but it limits its
64  * usefulness, since not all functions have h
65  */
66 static int _clixon_debug = 0;
67 
68 /* Bitmask whether to log to syslog or stderr: CLICON_LOG_STDERR | CLICON_LOG_SYSLOG */
69 static int _logflags = 0x0;
70 
71 /* Set to open file to write debug messages directly to file */
72 static FILE *_logfile = NULL;
73 
74 
75 /*! Initialize system logger.
76  *
77  * Make syslog(3) calls with specified ident and gates calls of level upto specified level (upto).
78  * May also print to stderr, if err is set.
79  * Applies to clicon_err() and clicon_debug too
80  *
81  * @param[in]  ident   prefix that appears on syslog (eg 'cli')
82  * @param[in]  upto    log priority, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG (see syslog(3)).
83  * @param[in]  flags   bitmask: if CLICON_LOG_STDERR, then print logs to stderr
84  *                              if CLICON_LOG_SYSLOG, then print logs to syslog
85  *				You can do a combination of both
86  * @code
87  *  clicon_log_init(h, __PROGRAM__, LOG_INFO, CLICON_LOG_STDERR);
88  * @endcode
89  */
90 int
clicon_log_init(char * ident,int upto,int flags)91 clicon_log_init(char         *ident,
92 		int           upto,
93 		int           flags)
94 {
95     _logflags = flags;
96     if (flags & CLICON_LOG_SYSLOG){
97 	if (setlogmask(LOG_UPTO(upto)) < 0)
98 	    /* Cant syslog here */
99 	    fprintf(stderr, "%s: setlogmask: %s\n", __FUNCTION__, strerror(errno));
100 	openlog(ident, LOG_PID, LOG_USER); /* LOG_PUSER is achieved by direct stderr logs in clicon_log */
101     }
102     return 0;
103 }
104 
105 int
clicon_log_exit(void)106 clicon_log_exit(void)
107 {
108     if (_logfile)
109 	fclose(_logfile);
110     closelog(); /* optional */
111     return 0;
112 }
113 
114 /*! Utility function to set log destination/flag using command-line option
115  * @param[in]  c  Log option,one of s,f,e,o
116  * @retval    -1  No match
117  * @retval     0  One of CLICON_LOG_SYSLOG|STDERR|STDOUT|FILE
118  */
119 int
clicon_log_opt(char c)120 clicon_log_opt(char c)
121 {
122     int logdst = -1;
123 
124     switch (c){
125     case 's':
126 	logdst = CLICON_LOG_SYSLOG;
127 	break;
128     case 'e':
129 	logdst = CLICON_LOG_STDERR;
130 	break;
131     case 'o':
132 	logdst = CLICON_LOG_STDOUT;
133 	break;
134     case 'f':
135 	logdst = CLICON_LOG_FILE;
136 	break;
137     default:
138 	break;
139     }
140     return logdst;
141 }
142 
143 /*! If log flags include CLICON_LOG_FILE, set the file
144  * @param[in]   filename   File to log to
145  * @retval      0          OK
146  * @retval     -1          Error
147  */
148 int
clicon_log_file(char * filename)149 clicon_log_file(char *filename)
150 {
151     if (_logfile)
152 	fclose(_logfile);
153     if ((_logfile = fopen(filename, "a")) == NULL){
154 	fprintf(stderr, "fopen: %s\n", strerror(errno)); /* dont use clicon_err here due to recursion */
155 	return -1;
156     }
157     return 0;
158 }
159 
160 int
clicon_get_logflags(void)161 clicon_get_logflags(void)
162 {
163     return _logflags;
164 }
165 
166 /*! Mimic syslog and print a time on file f
167  */
168 static int
flogtime(FILE * f)169 flogtime(FILE *f)
170 {
171     struct timeval tv;
172     struct tm *tm;
173 
174     gettimeofday(&tv, NULL);
175     tm = localtime((time_t*)&tv.tv_sec);
176     fprintf(f, "%s %2d %02d:%02d:%02d: ",
177 	    mon2name(tm->tm_mon), tm->tm_mday,
178 	    tm->tm_hour, tm->tm_min, tm->tm_sec);
179     return 0;
180 }
181 
182 #ifdef NOTUSED
183 /*
184  * Mimic syslog and print a time on string s
185  * String returned needs to be freed.
186  */
187 static char *
slogtime(void)188 slogtime(void)
189 {
190     struct timeval tv;
191     struct tm     *tm;
192     char           *str;
193 
194     /* Example: "Apr 14 11:30:52: " len=17+1 */
195     if ((str = malloc(18)) == NULL){
196 	fprintf(stderr, "%s: malloc: %s\n", __FUNCTION__, strerror(errno));
197 	return NULL;
198     }
199     gettimeofday(&tv, NULL);
200     tm = localtime((time_t*)&tv.tv_sec);
201     snprintf(str, 18, "%s %2d %02d:%02d:%02d: ",
202 	     mon2name(tm->tm_mon), tm->tm_mday,
203 	     tm->tm_hour, tm->tm_min, tm->tm_sec);
204     return str;
205 }
206 #endif
207 
208 /*! Make a logging call to syslog (or stderr).
209  *
210  * @param[in]   level log level, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG. Thisis OR:d with facility == LOG_USER
211  * @param[in]   msg   Message to print as argv.
212  * This is the _only_ place the actual syslog (or stderr) logging is made in clicon,..
213  * @note syslog makes its own filtering, but if log to stderr we do it here
214  * @see  clicon_debug
215  */
216 static int
clicon_log_str(int level,char * msg)217 clicon_log_str(int           level,
218 	       char         *msg)
219 {
220     if (_logflags & CLICON_LOG_SYSLOG)
221 	syslog(LOG_MAKEPRI(LOG_USER, level), "%s", msg);
222    /* syslog makes own filtering, we do it here:
223     * if normal (not debug) then filter loglevels >= debug
224     */
225     if (_clixon_debug == 0 && level >= LOG_DEBUG)
226 	goto done;
227     if (_logflags & CLICON_LOG_STDERR){
228 	flogtime(stderr);
229 	fprintf(stderr, "%s\n", msg);
230     }
231     if (_logflags & CLICON_LOG_STDOUT){
232 	flogtime(stdout);
233 	fprintf(stdout, "%s\n", msg);
234     }
235     if ((_logflags & CLICON_LOG_FILE) && _logfile){
236 	flogtime(_logfile);
237 	fprintf(_logfile, "%s\n", msg);
238 	fflush(_logfile);
239 
240     }
241 
242     /* Enable this if you want syslog in a stream. But there are problems with
243      * recursion
244      */
245  done:
246     return 0;
247 }
248 
249 /*! Make a logging call to syslog using variable arg syntax.
250  *
251  * @param[in]   level    log level, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG. This
252  *                       is OR:d with facility == LOG_USER
253  * @param[in]   format   Message to print as argv.
254  * @code
255 	clicon_log(LOG_NOTICE, "%s: dump to dtd not supported", __PROGRAM__);
256  * @endcode
257  * @see clicon_log_init and clicon_log_str
258  */
259 int
clicon_log(int level,const char * format,...)260 clicon_log(int         level,
261 	   const char *format, ...)
262 {
263     va_list args;
264     int     len;
265     char   *msg    = NULL;
266     int     retval = -1;
267 
268     /* first round: compute length of debug message */
269     va_start(args, format);
270     len = vsnprintf(NULL, 0, format, args);
271     va_end(args);
272 
273     /* allocate a message string exactly fitting the message length */
274     if ((msg = malloc(len+1)) == NULL){
275 	fprintf(stderr, "malloc: %s\n", strerror(errno)); /* dont use clicon_err here due to recursion */
276 	goto done;
277     }
278 
279     /* second round: compute write message from format and args */
280     va_start(args, format);
281     if (vsnprintf(msg, len+1, format, args) < 0){
282 	va_end(args);
283 	fprintf(stderr, "vsnprintf: %s\n", strerror(errno)); /* dont use clicon_err here due to recursion */
284 	goto done;
285     }
286     va_end(args);
287     /* Actually log it */
288     clicon_log_str(level, msg);
289 
290     retval = 0;
291   done:
292     if (msg)
293 	free(msg);
294     return retval;
295 }
296 
297 /*! Initialize debug messages. Set debug level.
298  *
299  * Initialize debug module. The level is used together with clicon_debug(dbglevel) calls as follows:
300  * print message if level >= dbglevel.
301  * Example: clicon_debug_init(1) -> debug(1) is printed, but not debug(2).
302  * Normally, debug messages are sent to clicon_log() which in turn can be sent to syslog and/or stderr.
303  * But you can also override this with a specific debug file so that debug messages are written on the file
304  * independently of log or errors. This is to ensure that a syslog of normal logs is unpolluted by extensive
305  * debugging.
306  *
307  * @param[in] dbglevel  0 is show no debug messages, 1 is normal, 2.. is high debug.
308                         Note this is _not_ level from syslog(3)
309  * @param[in] f         Debug-file. Open file where debug messages are directed.
310                         This overrides the clicon_log settings which is otherwise where
311 			debug messages are directed.
312  */
313 int
clicon_debug_init(int dbglevel,FILE * f)314 clicon_debug_init(int   dbglevel,
315 		  FILE *f)
316 {
317     _clixon_debug = dbglevel; /* Global variable */
318     return 0;
319 }
320 
321 int
clicon_debug_get(void)322 clicon_debug_get(void)
323 {
324     return _clixon_debug;
325 }
326 
327 /*! Print a debug message with debug-level. Settings determine where msg appears.
328  *
329  * If the dbglevel passed in the function is equal to or lower than the one set by
330  * clicon_debug_init(level).  That is, only print debug messages <= than what you want:
331  *      print message if level >= dbglevel.
332  * The message is sent to clicon_log. EIther to syslog, stderr or both, depending on
333  * clicon_log_init() setting
334  *
335  * @param[in] dbglevel   0 always called (dont do this: not really a dbg message)
336  *                       1 default level if passed -D
337  *                       2.. Higher debug levels
338  * @param[in] format     Message to print as argv.
339  */
340 int
clicon_debug(int dbglevel,const char * format,...)341 clicon_debug(int         dbglevel,
342 	     const char *format, ...)
343 {
344     va_list args;
345     int     len;
346     char   *msg    = NULL;
347     int     retval = -1;
348 
349     if (dbglevel > _clixon_debug) /* compare debug mask with global variable */
350 	return 0;
351     /* first round: compute length of debug message */
352     va_start(args, format);
353     len = vsnprintf(NULL, 0, format, args);
354     va_end(args);
355 
356     /* allocate a message string exactly fitting the messgae length */
357     if ((msg = malloc(len+1)) == NULL){
358 	clicon_err(OE_UNIX, errno, "malloc");
359 	goto done;
360     }
361     /* second round: compute write message from format and args */
362     va_start(args, format);
363     if (vsnprintf(msg, len+1, format, args) < 0){
364 	va_end(args);
365 	clicon_err(OE_UNIX, errno, "vsnprintf");
366 	goto done;
367     }
368     va_end(args);
369     clicon_log_str(LOG_DEBUG, msg);
370     retval = 0;
371   done:
372     if (msg)
373 	free(msg);
374     return retval;
375 }
376 
377 /*! Translate month number (0..11) to a three letter month name
378  * @param[in] md  month number, where 0 is january
379  */
380 char *
mon2name(int md)381 mon2name(int md)
382 {
383     switch(md){
384     case 0: return "Jan";
385     case 1: return "Feb";
386     case 2: return "Mar";
387     case 3: return "Apr";
388     case 4: return "May";
389     case 5: return "Jun";
390     case 6: return "Jul";
391     case 7: return "Aug";
392     case 8: return "Sep";
393     case 9: return "Oct";
394     case 10: return "Nov";
395     case 11: return "Dec";
396     default:
397 	break;
398     }
399     return NULL;
400 }
401 
402