1 /* $OpenBSD: lib_trace.c,v 1.9 2023/10/17 09:52:09 nicm Exp $ */ 2 3 /**************************************************************************** 4 * Copyright 2018-2022,2023 Thomas E. Dickey * 5 * Copyright 1998-2016,2017 Free Software Foundation, Inc. * 6 * * 7 * Permission is hereby granted, free of charge, to any person obtaining a * 8 * copy of this software and associated documentation files (the * 9 * "Software"), to deal in the Software without restriction, including * 10 * without limitation the rights to use, copy, modify, merge, publish, * 11 * distribute, distribute with modifications, sublicense, and/or sell * 12 * copies of the Software, and to permit persons to whom the Software is * 13 * furnished to do so, subject to the following conditions: * 14 * * 15 * The above copyright notice and this permission notice shall be included * 16 * in all copies or substantial portions of the Software. * 17 * * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 21 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 22 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 23 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 24 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 25 * * 26 * Except as contained in this notice, the name(s) of the above copyright * 27 * holders shall not be used in advertising or otherwise to promote the * 28 * sale, use or other dealings in this Software without prior written * 29 * authorization. * 30 ****************************************************************************/ 31 32 /**************************************************************************** 33 * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 * 34 * and: Eric S. Raymond <esr@snark.thyrsus.com> * 35 * and: Thomas E. Dickey 1996-on * 36 * and: Juergen Pfeifer * 37 ****************************************************************************/ 38 39 /* 40 * lib_trace.c - Tracing/Debugging routines 41 * 42 * The _tracef() function is originally from pcurses (by Pavel Curtis) in 1982. 43 * pcurses allowed one to enable/disable tracing using traceon() and traceoff() 44 * functions. ncurses provides a trace() function which allows one to 45 * selectively enable or disable several tracing features. 46 */ 47 48 #include <curses.priv.h> 49 #include <tic.h> 50 51 #include <ctype.h> 52 53 MODULE_ID("$Id: lib_trace.c,v 1.9 2023/10/17 09:52:09 nicm Exp $") 54 55 NCURSES_EXPORT_VAR(unsigned) _nc_tracing = 0; /* always define this */ 56 57 #ifdef TRACE 58 59 #if USE_REENTRANT 60 NCURSES_EXPORT(const char *) 61 NCURSES_PUBLIC_VAR(_nc_tputs_trace) (void) 62 { 63 return CURRENT_SCREEN ? CURRENT_SCREEN->_tputs_trace : _nc_prescreen._tputs_trace; 64 } 65 NCURSES_EXPORT(long) 66 NCURSES_PUBLIC_VAR(_nc_outchars) (void) 67 { 68 return CURRENT_SCREEN ? CURRENT_SCREEN->_outchars : _nc_prescreen._outchars; 69 } 70 NCURSES_EXPORT(void) 71 _nc_set_tputs_trace(const char *s) 72 { 73 if (CURRENT_SCREEN) 74 CURRENT_SCREEN->_tputs_trace = s; 75 else 76 _nc_prescreen._tputs_trace = s; 77 } 78 NCURSES_EXPORT(void) 79 _nc_count_outchars(long increment) 80 { 81 if (CURRENT_SCREEN) 82 CURRENT_SCREEN->_outchars += increment; 83 else 84 _nc_prescreen._outchars += increment; 85 } 86 #else 87 NCURSES_EXPORT_VAR(const char *) _nc_tputs_trace = ""; 88 NCURSES_EXPORT_VAR(long) _nc_outchars = 0; 89 #endif 90 91 #define MyFP _nc_globals.trace_fp 92 #define MyFD _nc_globals.trace_fd 93 #define MyInit _nc_globals.trace_opened 94 #define MyPath _nc_globals.trace_fname 95 #define MyLevel _nc_globals.trace_level 96 #define MyNested _nc_globals.nested_tracef 97 #endif /* TRACE */ 98 99 #if USE_REENTRANT 100 #define Locked(statement) \ 101 do { \ 102 _nc_lock_global(tst_tracef); \ 103 statement; \ 104 _nc_unlock_global(tst_tracef); \ 105 } while (0) 106 #else 107 #define Locked(statement) statement 108 #endif 109 110 NCURSES_EXPORT(unsigned) 111 curses_trace(unsigned tracelevel) 112 { 113 unsigned result; 114 115 #if defined(TRACE) 116 int bit; 117 118 #define DATA(name) { name, #name } 119 static struct { 120 unsigned mask; 121 const char *name; 122 } trace_names[] = { 123 DATA(TRACE_TIMES), 124 DATA(TRACE_TPUTS), 125 DATA(TRACE_UPDATE), 126 DATA(TRACE_MOVE), 127 DATA(TRACE_CHARPUT), 128 DATA(TRACE_CALLS), 129 DATA(TRACE_VIRTPUT), 130 DATA(TRACE_IEVENT), 131 DATA(TRACE_BITS), 132 DATA(TRACE_ICALLS), 133 DATA(TRACE_CCALLS), 134 DATA(TRACE_DATABASE), 135 DATA(TRACE_ATTRS) 136 }; 137 #undef DATA 138 139 Locked(result = _nc_tracing); 140 141 if ((MyFP == 0) && tracelevel) { 142 MyInit = TRUE; 143 if (MyFD >= 0) { 144 MyFP = fdopen(MyFD, BIN_W); 145 } else { 146 if (MyPath[0] == '\0') { 147 size_t size = sizeof(MyPath) - 12; 148 if (getcwd(MyPath, size) == 0) { 149 perror("curses: Can't get working directory"); 150 exit(EXIT_FAILURE); 151 } 152 MyPath[size] = '\0'; 153 assert(strlen(MyPath) <= size); 154 _nc_STRCAT(MyPath, "/trace", sizeof(MyPath)); 155 if (_nc_is_dir_path(MyPath)) { 156 _nc_STRCAT(MyPath, ".log", sizeof(MyPath)); 157 } 158 } 159 #define SAFE_MODE (O_CREAT | O_EXCL | O_RDWR) 160 if (_nc_access(MyPath, W_OK) < 0 161 || (MyFD = safe_open3(MyPath, SAFE_MODE, 0600)) < 0 162 || (MyFP = fdopen(MyFD, BIN_W)) == 0) { 163 ; /* EMPTY */ 164 } 165 } 166 Locked(_nc_tracing = tracelevel); 167 /* Try to set line-buffered mode, or (failing that) unbuffered, 168 * so that the trace-output gets flushed automatically at the 169 * end of each line. This is useful in case the program dies. 170 */ 171 if (MyFP != 0) { 172 #if HAVE_SETVBUF /* ANSI */ 173 (void) setvbuf(MyFP, (char *) 0, _IOLBF, (size_t) 0); 174 #elif HAVE_SETBUF /* POSIX */ 175 (void) setbuffer(MyFP, (char *) 0); 176 #endif 177 } 178 _tracef("TRACING NCURSES version %s.%d (tracelevel=%#x)", 179 NCURSES_VERSION, 180 NCURSES_VERSION_PATCH, 181 tracelevel); 182 183 #define SPECIAL_MASK(mask) \ 184 if ((tracelevel & mask) == mask) \ 185 _tracef("- %s (%u)", #mask, mask) 186 187 for (bit = 0; bit < TRACE_SHIFT; ++bit) { 188 unsigned mask = (1U << bit) & tracelevel; 189 if ((mask & trace_names[bit].mask) != 0) { 190 _tracef("- %s (%u)", trace_names[bit].name, mask); 191 } 192 } 193 SPECIAL_MASK(TRACE_MAXIMUM); 194 else 195 SPECIAL_MASK(TRACE_ORDINARY); 196 197 if (tracelevel > TRACE_MAXIMUM) { 198 _tracef("- DEBUG_LEVEL(%u)", tracelevel >> TRACE_SHIFT); 199 } 200 } else if (tracelevel == 0) { 201 if (MyFP != 0) { 202 MyFD = dup(MyFD); /* allow reopen of same file */ 203 fclose(MyFP); 204 MyFP = 0; 205 } 206 Locked(_nc_tracing = tracelevel); 207 } else if (_nc_tracing != tracelevel) { 208 Locked(_nc_tracing = tracelevel); 209 _tracef("tracelevel=%#x", tracelevel); 210 } 211 #else 212 (void) tracelevel; 213 result = 0; 214 #endif 215 return result; 216 } 217 218 #if defined(TRACE) 219 NCURSES_EXPORT(void) 220 trace(const unsigned int tracelevel) 221 { 222 curses_trace(tracelevel); 223 } 224 225 static void 226 _nc_va_tracef(const char *fmt, va_list ap) 227 { 228 static const char Called[] = T_CALLED(""); 229 static const char Return[] = T_RETURN(""); 230 231 bool before = FALSE; 232 bool after = FALSE; 233 unsigned doit = _nc_tracing; 234 int save_err = errno; 235 FILE *fp = MyFP; 236 237 #ifdef TRACE 238 /* verbose-trace in the command-line utilities relies on this */ 239 if (fp == 0 && !MyInit && _nc_tracing >= DEBUG_LEVEL(1)) 240 fp = stderr; 241 #endif 242 243 if (strlen(fmt) >= sizeof(Called) - 1) { 244 if (!strncmp(fmt, Called, sizeof(Called) - 1)) { 245 before = TRUE; 246 MyLevel++; 247 } else if (!strncmp(fmt, Return, sizeof(Return) - 1)) { 248 after = TRUE; 249 } 250 if (before || after) { 251 if ((MyLevel <= 1) 252 || (doit & TRACE_ICALLS) != 0) 253 doit &= (TRACE_CALLS | TRACE_CCALLS); 254 else 255 doit = 0; 256 } 257 } 258 259 if (doit != 0 && fp != 0) { 260 #ifdef USE_PTHREADS 261 /* 262 * TRACE_ICALLS is "really" needed to show normal use with threaded 263 * applications, since anything can be running during a napms(), 264 * making it appear in the hierarchical trace as it other functions 265 * are being called. 266 * 267 * Rather than add the complication of a per-thread stack, just 268 * show the thread-id in each line of the trace. 269 */ 270 # if USE_WEAK_SYMBOLS 271 if ((pthread_self)) 272 # endif 273 fprintf(fp, "%#" PRIxPTR ":", 274 #ifdef _NC_WINDOWS 275 CASTxPTR(pthread_self().p) 276 #else 277 CASTxPTR(pthread_self()) 278 #endif 279 ); 280 #endif 281 if (before || after) { 282 int n; 283 for (n = 1; n < MyLevel; n++) 284 fputs("+ ", fp); 285 } 286 vfprintf(fp, fmt, ap); 287 fputc('\n', fp); 288 fflush(fp); 289 } 290 291 if (after && MyLevel) 292 MyLevel--; 293 294 errno = save_err; 295 } 296 297 NCURSES_EXPORT(void) 298 _tracef(const char *fmt, ...) 299 { 300 va_list ap; 301 302 va_start(ap, fmt); 303 _nc_va_tracef(fmt, ap); 304 va_end(ap); 305 } 306 307 /* Trace 'bool' return-values */ 308 NCURSES_EXPORT(NCURSES_BOOL) 309 _nc_retrace_bool(int code) 310 { 311 T((T_RETURN("%s"), code ? "TRUE" : "FALSE")); 312 return code; 313 } 314 315 /* Trace 'char' return-values */ 316 NCURSES_EXPORT(char) 317 _nc_retrace_char(int code) 318 { 319 T((T_RETURN("%c"), code)); 320 return (char) code; 321 } 322 323 /* Trace 'int' return-values */ 324 NCURSES_EXPORT(int) 325 _nc_retrace_int(int code) 326 { 327 T((T_RETURN("%d"), code)); 328 return code; 329 } 330 331 /* Trace 'unsigned' return-values */ 332 NCURSES_EXPORT(unsigned) 333 _nc_retrace_unsigned(unsigned code) 334 { 335 T((T_RETURN("%#x"), code)); 336 return code; 337 } 338 339 /* Trace 'char*' return-values */ 340 NCURSES_EXPORT(char *) 341 _nc_retrace_ptr(char *code) 342 { 343 T((T_RETURN("%s"), _nc_visbuf(code))); 344 return code; 345 } 346 347 /* Trace 'const char*' return-values */ 348 NCURSES_EXPORT(const char *) 349 _nc_retrace_cptr(const char *code) 350 { 351 T((T_RETURN("%s"), _nc_visbuf(code))); 352 return code; 353 } 354 355 /* Trace 'NCURSES_CONST void*' return-values */ 356 NCURSES_EXPORT(NCURSES_CONST void *) 357 _nc_retrace_cvoid_ptr(NCURSES_CONST void *code) 358 { 359 T((T_RETURN("%p"), code)); 360 return code; 361 } 362 363 /* Trace 'void*' return-values */ 364 NCURSES_EXPORT(void *) 365 _nc_retrace_void_ptr(void *code) 366 { 367 T((T_RETURN("%p"), code)); 368 return code; 369 } 370 371 /* Trace 'SCREEN *' return-values */ 372 NCURSES_EXPORT(SCREEN *) 373 _nc_retrace_sp(SCREEN *code) 374 { 375 T((T_RETURN("%p"), (void *) code)); 376 return code; 377 } 378 379 /* Trace 'WINDOW *' return-values */ 380 NCURSES_EXPORT(WINDOW *) 381 _nc_retrace_win(WINDOW *code) 382 { 383 T((T_RETURN("%p"), (void *) code)); 384 return code; 385 } 386 387 NCURSES_EXPORT(char *) 388 _nc_fmt_funcptr(char *target, const char *source, size_t size) 389 { 390 size_t n; 391 char *dst = target; 392 bool leading = TRUE; 393 394 union { 395 int value; 396 char bytes[sizeof(int)]; 397 } byteorder; 398 399 byteorder.value = 0x1234; 400 401 *dst++ = '0'; 402 *dst++ = 'x'; 403 404 for (n = 0; n < size; ++n) { 405 unsigned ch = ((byteorder.bytes[0] == 0x34) 406 ? UChar(source[size - n - 1]) 407 : UChar(source[n])); 408 if (ch != 0 || (n + 1) >= size) 409 leading = FALSE; 410 if (!leading) { 411 _nc_SPRINTF(dst, _nc_SLIMIT(TR_FUNC_LEN - (size_t) (dst - target)) 412 "%02x", ch & 0xff); 413 dst += 2; 414 } 415 } 416 *dst = '\0'; 417 return target; 418 } 419 420 #if USE_REENTRANT 421 /* 422 * Check if the given trace-mask is enabled. 423 * 424 * This function may be called from within one of the functions that fills 425 * in parameters for _tracef(), but in that case we do not want to lock the 426 * mutex, since it is already locked. 427 */ 428 NCURSES_EXPORT(int) 429 _nc_use_tracef(unsigned mask) 430 { 431 bool result = FALSE; 432 433 _nc_lock_global(tst_tracef); 434 if (!MyNested++) { 435 if ((result = (_nc_tracing & (mask))) != 0 436 && _nc_try_global(tracef) == 0) { 437 /* we will call _nc_locked_tracef(), no nesting so far */ 438 } else { 439 /* we will not call _nc_locked_tracef() */ 440 MyNested = 0; 441 } 442 } else { 443 /* we may call _nc_locked_tracef(), but with nested_tracef > 0 */ 444 result = (_nc_tracing & (mask)); 445 } 446 _nc_unlock_global(tst_tracef); 447 return result; 448 } 449 450 /* 451 * We call this if _nc_use_tracef() returns true, which means we must unlock 452 * the tracef mutex. 453 */ 454 NCURSES_EXPORT(void) 455 _nc_locked_tracef(const char *fmt, ...) 456 { 457 va_list ap; 458 459 va_start(ap, fmt); 460 _nc_va_tracef(fmt, ap); 461 va_end(ap); 462 463 if (--(MyNested) == 0) { 464 _nc_unlock_global(tracef); 465 } 466 } 467 #endif /* USE_REENTRANT */ 468 469 #endif /* TRACE */ 470