/src/tpm2-tss/src/util/log.c
Line | Count | Source |
1 | | /* SPDX-FileCopyrightText: 2018 - 2022, Intel */ |
2 | | /* SPDX-FileCopyrightText: 2018 - 2020, Fraunhofer SIT sponsored by Infineon */ |
3 | | /* SPDX-FileCopyrightText: 2019, Fabrice Funtaine */ |
4 | | /* SPDX-FileCopyrightText: 2019, Alon Bar-Lev */ |
5 | | /* SPDX-FileCopyrightText: 2019 - 2022, Infineon Technologies AG */ |
6 | | /* SPDX-FileCopyrightText: 2022, Juergen Repp */ |
7 | | /* SPDX-License-Identifier: BSD-2-Clause */ |
8 | | #ifdef HAVE_CONFIG_H |
9 | | #include "config.h" // IWYU pragma: keep |
10 | | #endif |
11 | | |
12 | | #include <ctype.h> |
13 | | #include <errno.h> |
14 | | #include <stdarg.h> |
15 | | #include <stdio.h> |
16 | | #include <stdlib.h> |
17 | | #include <string.h> |
18 | | |
19 | | #define LOGMODULE log |
20 | | #include "log.h" |
21 | | |
22 | | #if !defined(_MSC_VER) || defined(__INTEL_COMPILER) |
23 | | #define likely(x) __builtin_expect(!!(x), 1) |
24 | 438 | #define unlikely(x) __builtin_expect(!!(x), 0) |
25 | | #else |
26 | | /* Microsoft Visual Studio gives internal error C1001 with _builtin_expect */ |
27 | | #define likely(x) (x) |
28 | | #define unlikely(x) (x) |
29 | | #endif |
30 | | |
31 | | #if MAXLOGLEVEL != LOGL_NONE |
32 | | |
33 | | static const char *log_strings[] |
34 | | = { "none", "(unused)", "ERROR", "WARNING", "info", "debug", "trace" }; |
35 | | |
36 | | /** |
37 | | * Compares two strings byte by byte and ignores the |
38 | | * character's case. Stops at the n-th byte of both |
39 | | * strings. |
40 | | * |
41 | | * This is basically a replacement of the POSIX-function |
42 | | * _strncasecmp_. Since tpm2-tss is supposed to be compatible |
43 | | * with ISO C99 and not with POSIX, _strncasecmp_ had to be |
44 | | * replaced. This function creates lowercase representations |
45 | | * of the strings and compares them bytewise. |
46 | | * |
47 | | * @param string1 The first of the two strings to compare |
48 | | * @param string2 The second of the two strings to compare |
49 | | * @param n The maximum number of bytes to compare |
50 | | * @return 0 if both strings are equal (case insensitive), |
51 | | * an integer greater than zero if string1 is greater than |
52 | | * string 2 and an integer smaller than zero if string1 is |
53 | | * smaller than string2 |
54 | | * |
55 | | */ |
56 | | static int |
57 | 0 | case_insensitive_strncmp(const char *string1, const char *string2, size_t n) { |
58 | 0 | if ((string1 == NULL) && (string2 == NULL)) { |
59 | 0 | return 0; |
60 | 0 | } |
61 | 0 | if ((string1 == NULL) && (string2 != NULL)) { |
62 | 0 | return -1; |
63 | 0 | } |
64 | 0 | if ((string1 != NULL) && (string2 == NULL)) { |
65 | 0 | return 1; |
66 | 0 | } |
67 | 0 | if (n == 0) { // Zero bytes are always equal |
68 | 0 | return 0; |
69 | 0 | } |
70 | 0 | if (string1 == string2) { // return equal if they point to same location |
71 | 0 | return 0; |
72 | 0 | } |
73 | | |
74 | 0 | int result; |
75 | 0 | do { |
76 | 0 | result = tolower((unsigned char)*string1) - tolower((unsigned char)*string2); |
77 | 0 | if (result != 0) { |
78 | 0 | break; |
79 | 0 | } |
80 | 0 | } while (*string1++ != '\0' && *string2++ != '\0' && --n); |
81 | 0 | return result; |
82 | 0 | } |
83 | | |
84 | | static log_level getLogLevel(const char *module, log_level logdefault); |
85 | | |
86 | | static FILE * |
87 | 438 | getLogFile(void) { |
88 | 438 | #ifdef LOG_FILE_ENABLED |
89 | 438 | const char *envpath; |
90 | 438 | static FILE *file = NULL; |
91 | | |
92 | 438 | if (file) { |
93 | 219 | return file; |
94 | 219 | } |
95 | | |
96 | 219 | envpath = getenv("TSS2_LOGFILE"); |
97 | 219 | if (envpath == NULL || !case_insensitive_strncmp(envpath, "stderr", 7)) { |
98 | 219 | file = stderr; |
99 | 219 | } else if (!strcmp(envpath, "-") || !case_insensitive_strncmp(envpath, "stdout", 7)) { |
100 | 0 | file = stdout; |
101 | 0 | } else { |
102 | 0 | file = fopen(envpath, "a+"); |
103 | 0 | if (file == NULL) { |
104 | 0 | file = stderr; |
105 | 0 | fprintf(file, "Failed to open logging file %s: %s\n", envpath, strerror(errno)); |
106 | 0 | fflush(file); |
107 | 0 | } |
108 | 0 | } |
109 | | |
110 | 219 | return file; |
111 | | #else |
112 | | return stderr; |
113 | | #endif |
114 | 438 | } |
115 | | |
116 | | void |
117 | | doLogBlob(log_level loglevel, |
118 | | const char *module, |
119 | | log_level logdefault, |
120 | | log_level *status, |
121 | | const char *file, |
122 | | const char *func, |
123 | | int line, |
124 | | const uint8_t *blob, |
125 | | size_t size, |
126 | | const char *fmt, |
127 | 0 | ...) { |
128 | 0 | FILE *logfile; |
129 | 0 | if (unlikely(*status == LOGLEVEL_UNDEFINED)) |
130 | 0 | *status = getLogLevel(module, logdefault); |
131 | 0 | if (loglevel > *status) |
132 | 0 | return; |
133 | | |
134 | 0 | va_list vaargs; |
135 | 0 | va_start(vaargs, fmt); |
136 | | /* TODO: Unfortunately, vsnprintf(NULL, 0, ...) do not behave the same as |
137 | | snprintf(NULL, 0, ...). Until there is an alternative, messages on |
138 | | logblob are restricted to 255 characters |
139 | | int msg_len = vsnprintf(NULL, 0, fmt, vaargs); */ |
140 | 0 | int msg_len = 255; |
141 | 0 | char msg[msg_len + 1]; |
142 | 0 | vsnprintf(msg, sizeof(msg), fmt, vaargs); |
143 | 0 | va_end(vaargs); |
144 | |
|
145 | 0 | if (!blob) { |
146 | 0 | doLog(loglevel, module, logdefault, status, file, func, line, "%s (size=%zi): (null)", msg, |
147 | 0 | size); |
148 | 0 | return; |
149 | 0 | } |
150 | | |
151 | 0 | doLog(loglevel, module, logdefault, status, file, func, line, "%s (size=%zi):", msg, size); |
152 | |
|
153 | 0 | unsigned int i, y, x, off, off2; |
154 | 0 | unsigned int width = 16; |
155 | 0 | #define LINE_LEN 64 |
156 | 0 | char buffer[LINE_LEN]; |
157 | |
|
158 | 0 | for (i = 1, off = 0, off2 = 0; i <= size; i++) { |
159 | 0 | if (i == 1) { |
160 | 0 | sprintf(&buffer[off], "%04x: ", i - 1); |
161 | 0 | off += 6; |
162 | 0 | } |
163 | | |
164 | | /* data output */ |
165 | 0 | sprintf(&buffer[off], "%02x", blob[i - 1]); |
166 | 0 | off += 2; |
167 | | |
168 | | /* ASCII output */ |
169 | 0 | if ((i % width == 0 && i > 1) || i == size) { |
170 | 0 | sprintf(&buffer[off], " "); |
171 | 0 | off += 2; |
172 | | /* Align to the right */ |
173 | 0 | for (x = off; x < width * 2 + 8; x++) { |
174 | 0 | sprintf(&buffer[off], " "); |
175 | 0 | off++; |
176 | 0 | } |
177 | | |
178 | | /* Account for a line that is not 'full' */ |
179 | 0 | unsigned int less = width - (i % width); |
180 | 0 | if (less == width) |
181 | 0 | less = 0; |
182 | |
|
183 | 0 | for (y = 0; y < width - less; y++) { |
184 | 0 | if (isgraph(blob[off2 + y])) { |
185 | 0 | sprintf(&buffer[y + off], "%c", blob[off2 + y]); |
186 | 0 | } else { |
187 | 0 | sprintf(&buffer[y + off], "%c", '.'); |
188 | 0 | } |
189 | 0 | } |
190 | | /* print the line and restart */ |
191 | 0 | logfile = getLogFile(); |
192 | 0 | fprintf(logfile, "%s\n", buffer); |
193 | 0 | fflush(logfile); |
194 | 0 | off2 = i; |
195 | 0 | off = 0; |
196 | 0 | memset(buffer, '\0', LINE_LEN); |
197 | 0 | sprintf(&buffer[off], "%04x: ", i); |
198 | 0 | off += 6; |
199 | 0 | } |
200 | 0 | } |
201 | 0 | } |
202 | | |
203 | | void |
204 | | doLog(log_level loglevel, |
205 | | const char *module, |
206 | | log_level logdefault, |
207 | | log_level *status, |
208 | | const char *file, |
209 | | const char *func, |
210 | | int line, |
211 | | const char *msg, |
212 | 438 | ...) { |
213 | 438 | FILE *logfile; |
214 | 438 | if (unlikely(*status == LOGLEVEL_UNDEFINED)) |
215 | 219 | *status = getLogLevel(module, logdefault); |
216 | | |
217 | 438 | if (loglevel > *status) |
218 | 0 | return; |
219 | | |
220 | 438 | int size = snprintf(NULL, 0, "%s:%s:%s:%d:%s() %s \n", log_strings[loglevel], module, file, |
221 | 438 | line, func, msg); |
222 | 438 | char fmt[size + 1]; |
223 | 438 | snprintf(fmt, sizeof(fmt), "%s:%s:%s:%d:%s() %s \n", log_strings[loglevel], module, file, line, |
224 | 438 | func, msg); |
225 | | |
226 | 438 | va_list vaargs; |
227 | 438 | va_start(vaargs, msg); |
228 | 438 | logfile = getLogFile(); |
229 | 438 | vfprintf(logfile, fmt, |
230 | | /* log_strings[loglevel], module, file, func, line, */ |
231 | 438 | vaargs); |
232 | 438 | fflush(logfile); |
233 | 438 | va_end(vaargs); |
234 | 438 | } |
235 | | |
236 | | static log_level |
237 | 0 | log_stringlevel(const char *n) { |
238 | 0 | log_level i; |
239 | 0 | for (i = 0; i < sizeof(log_strings) / sizeof(log_strings[0]); i++) { |
240 | 0 | if (case_insensitive_strncmp(log_strings[i], n, strlen(log_strings[i])) == 0) { |
241 | 0 | return i; |
242 | 0 | } |
243 | 0 | } |
244 | 0 | return LOGLEVEL_UNDEFINED; |
245 | 0 | } |
246 | | |
247 | | static log_level |
248 | 219 | getLogLevel(const char *module, log_level logdefault) { |
249 | 219 | log_level loglevel = logdefault; |
250 | 219 | const char *envlevel = getenv("TSS2_LOG"); |
251 | 219 | const char *i = envlevel; |
252 | 219 | if (envlevel == NULL) |
253 | 219 | return loglevel; |
254 | 0 | while ((i = strchr(i, '+')) != NULL) { |
255 | 0 | if ((envlevel <= i - strlen("all") && case_insensitive_strncmp(i - 3, "all", 3) == 0) |
256 | 0 | || (envlevel <= i - strlen(module) |
257 | 0 | && case_insensitive_strncmp(i - strlen(module), module, strlen(module)) == 0)) { |
258 | 0 | log_level tmp = log_stringlevel(i + 1); |
259 | 0 | if (tmp != LOGLEVEL_UNDEFINED) |
260 | 0 | loglevel = tmp; |
261 | 0 | } |
262 | 0 | i = i + 1; |
263 | 0 | } |
264 | 0 | return loglevel; |
265 | 219 | } |
266 | | #endif |