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