154359Sroberto/* 254359Sroberto * msyslog - either send a message to the terminal or print it on 354359Sroberto * the standard output. 454359Sroberto * 554359Sroberto * Converted to use varargs, much better ... jks 654359Sroberto */ 754359Sroberto 854359Sroberto#ifdef HAVE_CONFIG_H 954359Sroberto# include <config.h> 1054359Sroberto#endif 1154359Sroberto 12280849Scy#include <sys/types.h> 1354359Sroberto#ifdef HAVE_UNISTD_H 1454359Sroberto# include <unistd.h> 1554359Sroberto#endif 1654359Sroberto#include <stdio.h> 1754359Sroberto 1854359Sroberto#include "ntp_string.h" 19280849Scy#include "ntp.h" 20280849Scy#include "ntp_debug.h" 2182498Sroberto#include "ntp_syslog.h" 2254359Sroberto 2354359Sroberto#ifdef SYS_WINNT 24132451Sroberto# include <stdarg.h> 2554359Sroberto# include "..\ports\winnt\libntp\messages.h" 2654359Sroberto#endif 2754359Sroberto 2854359Sroberto 29280849Scyint syslogit = TRUE; 30280849Scyint msyslog_term = FALSE; /* duplicate to stdout/err */ 31280849Scyint msyslog_term_pid = TRUE; 32280849Scyint msyslog_include_timestamp = TRUE; 33280849ScyFILE * syslog_file; 34280849Scychar * syslog_fname; 35280849Scychar * syslog_abs_fname; 3654359Sroberto 37280849Scy/* libntp default ntp_syslogmask is all bits lit */ 38280849Scy#define INIT_NTP_SYSLOGMASK ~(u_int32)0 39280849Scyu_int32 ntp_syslogmask = INIT_NTP_SYSLOGMASK; 4054359Sroberto 41289764Sglebiusextern char const * progname; 4254359Sroberto 43132451Sroberto/* Declare the local functions */ 44280849Scyvoid addto_syslog (int, const char *); 45280849Scy#ifndef VSNPRINTF_PERCENT_M 46280849Scyvoid format_errmsg (char *, size_t, const char *, int); 47132451Sroberto 48280849Scy/* format_errmsg() is under #ifndef VSNPRINTF_PERCENT_M above */ 49132451Srobertovoid 50280849Scyformat_errmsg( 51280849Scy char * nfmt, 52280849Scy size_t lennfmt, 53280849Scy const char * fmt, 54280849Scy int errval 55280849Scy ) 5654359Sroberto{ 57280849Scy char errmsg[256]; 58280849Scy char c; 59280849Scy char *n; 60280849Scy const char *f; 61280849Scy size_t len; 62132451Sroberto 6354359Sroberto n = nfmt; 6454359Sroberto f = fmt; 65280849Scy while ((c = *f++) != '\0' && n < (nfmt + lennfmt - 1)) { 6654359Sroberto if (c != '%') { 6754359Sroberto *n++ = c; 6854359Sroberto continue; 6954359Sroberto } 7054359Sroberto if ((c = *f++) != 'm') { 7154359Sroberto *n++ = '%'; 72280849Scy if ('\0' == c) 73280849Scy break; 7454359Sroberto *n++ = c; 7554359Sroberto continue; 7654359Sroberto } 77280849Scy errno_to_str(errval, errmsg, sizeof(errmsg)); 78280849Scy len = strlen(errmsg); 79280849Scy 80132451Sroberto /* Make sure we have enough space for the error message */ 81280849Scy if ((n + len) < (nfmt + lennfmt - 1)) { 82280849Scy memcpy(n, errmsg, len); 83280849Scy n += len; 8454359Sroberto } 8554359Sroberto } 8654359Sroberto *n = '\0'; 87132451Sroberto} 88280849Scy#endif /* VSNPRINTF_PERCENT_M */ 8954359Sroberto 90280849Scy 91132451Sroberto/* 92280849Scy * errno_to_str() - a thread-safe strerror() replacement. 93280849Scy * Hides the varied signatures of strerror_r(). 94280849Scy * For Windows, we have: 95280849Scy * #define errno_to_str isc_strerror 96132451Sroberto */ 97280849Scy#ifndef errno_to_str 98280849Scyvoid 99280849Scyerrno_to_str( 100280849Scy int err, 101280849Scy char * buf, 102280849Scy size_t bufsiz 103280849Scy ) 104280849Scy{ 105280849Scy# if defined(STRERROR_R_CHAR_P) || !HAVE_DECL_STRERROR_R 106280849Scy char * pstatic; 107132451Sroberto 108280849Scy buf[0] = '\0'; 109280849Scy# ifdef STRERROR_R_CHAR_P 110280849Scy pstatic = strerror_r(err, buf, bufsiz); 111280849Scy# else 112280849Scy pstatic = strerror(err); 113280849Scy# endif 114280849Scy if (NULL == pstatic && '\0' == buf[0]) 115280849Scy snprintf(buf, bufsiz, "%s(%d): errno %d", 116280849Scy# ifdef STRERROR_R_CHAR_P 117280849Scy "strerror_r", 118280849Scy# else 119280849Scy "strerror", 120280849Scy# endif 121280849Scy err, errno); 122280849Scy /* protect against believing an int return is a pointer */ 123280849Scy else if (pstatic != buf && pstatic > (char *)bufsiz) 124280849Scy strlcpy(buf, pstatic, bufsiz); 125280849Scy# else 126280849Scy int rc; 127280849Scy 128280849Scy rc = strerror_r(err, buf, bufsiz); 129280849Scy if (rc < 0) 130280849Scy snprintf(buf, bufsiz, "strerror_r(%d): errno %d", 131280849Scy err, errno); 132280849Scy# endif 133280849Scy} 134280849Scy#endif /* errno_to_str */ 135280849Scy 136280849Scy 137280849Scy/* 138280849Scy * addto_syslog() 139280849Scy * This routine adds the contents of a buffer to the syslog or an 140280849Scy * application-specific logfile. 141280849Scy */ 142280849Scyvoid 143280849Scyaddto_syslog( 144280849Scy int level, 145280849Scy const char * msg 146280849Scy ) 147132451Sroberto{ 148289764Sglebius static char const * prevcall_progname; 149289764Sglebius static char const * prog; 150280849Scy const char nl[] = "\n"; 151280849Scy const char empty[] = ""; 152280849Scy FILE * term_file; 153280849Scy int log_to_term; 154280849Scy int log_to_file; 155280849Scy int pid; 156280849Scy const char * nl_or_empty; 157280849Scy const char * human_time; 158280849Scy 159280849Scy /* setup program basename static var prog if needed */ 160280849Scy if (progname != prevcall_progname) { 161280849Scy prevcall_progname = progname; 162280849Scy prog = strrchr(progname, DIR_SEP); 163280849Scy if (prog != NULL) 164280849Scy prog++; 165280849Scy else 166280849Scy prog = progname; 167280849Scy } 168280849Scy 169280849Scy log_to_term = msyslog_term; 170280849Scy log_to_file = FALSE; 171280849Scy#if !defined(VMS) && !defined(SYS_VXWORKS) 172280849Scy if (syslogit) 173280849Scy syslog(level, "%s", msg); 174280849Scy else 175280849Scy#endif 176280849Scy if (syslog_file != NULL) 177280849Scy log_to_file = TRUE; 178280849Scy else 179280849Scy log_to_term = TRUE; 180280849Scy#if DEBUG 181280849Scy if (debug > 0) 182280849Scy log_to_term = TRUE; 183280849Scy#endif 184280849Scy if (!(log_to_file || log_to_term)) 185280849Scy return; 186280849Scy 187280849Scy /* syslog() adds the timestamp, name, and pid */ 188280849Scy if (msyslog_include_timestamp) 189280849Scy human_time = humanlogtime(); 190280849Scy else /* suppress gcc pot. uninit. warning */ 191280849Scy human_time = NULL; 192280849Scy if (msyslog_term_pid || log_to_file) 193280849Scy pid = getpid(); 194280849Scy else /* suppress gcc pot. uninit. warning */ 195280849Scy pid = -1; 196280849Scy 197280849Scy /* syslog() adds trailing \n if not present */ 198280849Scy if ('\n' != msg[strlen(msg) - 1]) 199280849Scy nl_or_empty = nl; 200280849Scy else 201280849Scy nl_or_empty = empty; 202280849Scy 203280849Scy if (log_to_term) { 204280849Scy term_file = (level <= LOG_ERR) 205280849Scy ? stderr 206280849Scy : stdout; 207280849Scy if (msyslog_include_timestamp) 208280849Scy fprintf(term_file, "%s ", human_time); 209280849Scy if (msyslog_term_pid) 210280849Scy fprintf(term_file, "%s[%d]: ", prog, pid); 211280849Scy fprintf(term_file, "%s%s", msg, nl_or_empty); 212280849Scy fflush(term_file); 213280849Scy } 214280849Scy 215280849Scy if (log_to_file) { 216280849Scy if (msyslog_include_timestamp) 217280849Scy fprintf(syslog_file, "%s ", human_time); 218280849Scy fprintf(syslog_file, "%s[%d]: %s%s", prog, pid, msg, 219280849Scy nl_or_empty); 220280849Scy fflush(syslog_file); 221280849Scy } 222280849Scy} 223280849Scy 224280849Scy 225280849Scyint 226280849Scymvsnprintf( 227280849Scy char * buf, 228280849Scy size_t bufsiz, 229280849Scy const char * fmt, 230280849Scy va_list ap 231280849Scy ) 232280849Scy{ 233280849Scy#ifndef VSNPRINTF_PERCENT_M 234280849Scy char nfmt[256]; 235132451Sroberto#else 236280849Scy const char * nfmt = fmt; 237132451Sroberto#endif 238280849Scy int errval; 239132451Sroberto 240132451Sroberto /* 241132451Sroberto * Save the error value as soon as possible 242132451Sroberto */ 243132451Sroberto#ifdef SYS_WINNT 244280849Scy errval = GetLastError(); 245280849Scy if (NO_ERROR == errval) 246280849Scy#endif /* SYS_WINNT */ 247280849Scy errval = errno; 248280849Scy 249280849Scy#ifndef VSNPRINTF_PERCENT_M 250280849Scy format_errmsg(nfmt, sizeof(nfmt), fmt, errval); 251132451Sroberto#else 252280849Scy errno = errval; 253132451Sroberto#endif 254280849Scy return vsnprintf(buf, bufsiz, nfmt, ap); 255280849Scy} 256132451Sroberto 257132451Sroberto 258280849Scyint 259280849Scymvfprintf( 260280849Scy FILE * fp, 261280849Scy const char * fmt, 262280849Scy va_list ap 263280849Scy ) 264132451Sroberto{ 265280849Scy#ifndef VSNPRINTF_PERCENT_M 266280849Scy char nfmt[256]; 26754359Sroberto#else 268280849Scy const char * nfmt = fmt; 269132451Sroberto#endif 270280849Scy int errval; 27154359Sroberto 272132451Sroberto /* 273132451Sroberto * Save the error value as soon as possible 274132451Sroberto */ 275132451Sroberto#ifdef SYS_WINNT 276280849Scy errval = GetLastError(); 277280849Scy if (NO_ERROR == errval) 278280849Scy#endif /* SYS_WINNT */ 279280849Scy errval = errno; 280280849Scy 281280849Scy#ifndef VSNPRINTF_PERCENT_M 282280849Scy format_errmsg(nfmt, sizeof(nfmt), fmt, errval); 283132451Sroberto#else 284280849Scy errno = errval; 285132451Sroberto#endif 286280849Scy return vfprintf(fp, nfmt, ap); 287280849Scy} 288132451Sroberto 289280849Scy 290280849Scyint 291280849Scymfprintf( 292280849Scy FILE * fp, 293280849Scy const char * fmt, 294280849Scy ... 295280849Scy ) 296280849Scy{ 297280849Scy va_list ap; 298280849Scy int rc; 299280849Scy 300132451Sroberto va_start(ap, fmt); 301280849Scy rc = mvfprintf(fp, fmt, ap); 302280849Scy va_end(ap); 303132451Sroberto 304280849Scy return rc; 305280849Scy} 306132451Sroberto 307280849Scy 308280849Scyint 309280849Scymprintf( 310280849Scy const char * fmt, 311280849Scy ... 312280849Scy ) 313280849Scy{ 314280849Scy va_list ap; 315280849Scy int rc; 316280849Scy 317280849Scy va_start(ap, fmt); 318280849Scy rc = mvfprintf(stdout, fmt, ap); 31954359Sroberto va_end(ap); 320280849Scy 321280849Scy return rc; 32254359Sroberto} 323280849Scy 324280849Scy 325280849Scyint 326280849Scymsnprintf( 327280849Scy char * buf, 328280849Scy size_t bufsiz, 329280849Scy const char * fmt, 330280849Scy ... 331280849Scy ) 332280849Scy{ 333280849Scy va_list ap; 334293423Sdelphij int rc; 335280849Scy 336280849Scy va_start(ap, fmt); 337280849Scy rc = mvsnprintf(buf, bufsiz, fmt, ap); 338280849Scy va_end(ap); 339280849Scy 340280849Scy return rc; 341280849Scy} 342280849Scy 343280849Scy 344280849Scyvoid 345280849Scymsyslog( 346280849Scy int level, 347280849Scy const char * fmt, 348280849Scy ... 349280849Scy ) 350280849Scy{ 351280849Scy char buf[1024]; 352280849Scy va_list ap; 353280849Scy 354280849Scy va_start(ap, fmt); 355280849Scy mvsnprintf(buf, sizeof(buf), fmt, ap); 356280849Scy va_end(ap); 357280849Scy addto_syslog(level, buf); 358280849Scy} 359280849Scy 360289764Sglebiusvoid 361289764Sglebiusmvsyslog( 362289764Sglebius int level, 363289764Sglebius const char * fmt, 364289764Sglebius va_list ap 365289764Sglebius ) 366289764Sglebius{ 367289764Sglebius char buf[1024]; 368289764Sglebius mvsnprintf(buf, sizeof(buf), fmt, ap); 369289764Sglebius addto_syslog(level, buf); 370289764Sglebius} 371280849Scy 372289764Sglebius 373280849Scy/* 374280849Scy * Initialize the logging 375280849Scy * 376280849Scy * Called once per process, including forked children. 377280849Scy */ 378280849Scyvoid 379280849Scyinit_logging( 380280849Scy const char * name, 381280849Scy u_int32 def_syslogmask, 382280849Scy int is_daemon 383280849Scy ) 384280849Scy{ 385280849Scy static int was_daemon; 386289764Sglebius char * cp; 387280849Scy const char * pname; 388280849Scy 389280849Scy /* 390280849Scy * ntpd defaults to only logging sync-category events, when 391280849Scy * NLOG() is used to conditionalize. Other libntp clients 392280849Scy * leave it alone so that all NLOG() conditionals will fire. 393280849Scy * This presumes all bits lit in ntp_syslogmask can't be 394280849Scy * configured via logconfig and all lit is thereby a sentinel 395280849Scy * that ntp_syslogmask is still at its default from libntp, 396280849Scy * keeping in mind this function is called in forked children 397280849Scy * where it has already been called in the parent earlier. 398280849Scy * Forked children pass 0 for def_syslogmask. 399280849Scy */ 400280849Scy if (INIT_NTP_SYSLOGMASK == ntp_syslogmask && 401280849Scy 0 != def_syslogmask) 402280849Scy ntp_syslogmask = def_syslogmask; /* set more via logconfig */ 403280849Scy 404280849Scy /* 405280849Scy * Logging. This may actually work on the gizmo board. Find a name 406280849Scy * to log with by using the basename 407280849Scy */ 408280849Scy cp = strrchr(name, DIR_SEP); 409280849Scy if (NULL == cp) 410280849Scy pname = name; 411280849Scy else 412280849Scy pname = 1 + cp; /* skip DIR_SEP */ 413280849Scy progname = estrdup(pname); 414280849Scy#ifdef SYS_WINNT /* strip ".exe" */ 415280849Scy cp = strrchr(progname, '.'); 416280849Scy if (NULL != cp && !strcasecmp(cp, ".exe")) 417289764Sglebius *cp = '\0'; 418280849Scy#endif 419280849Scy 420280849Scy#if !defined(VMS) 421280849Scy 422280849Scy if (is_daemon) 423280849Scy was_daemon = TRUE; 424280849Scy# ifndef LOG_DAEMON 425280849Scy openlog(progname, LOG_PID); 426280849Scy# else /* LOG_DAEMON */ 427280849Scy 428280849Scy# ifndef LOG_NTP 429280849Scy# define LOG_NTP LOG_DAEMON 430280849Scy# endif 431280849Scy openlog(progname, LOG_PID | LOG_NDELAY, (was_daemon) 432280849Scy ? LOG_NTP 433280849Scy : 0); 434280849Scy# ifdef DEBUG 435280849Scy if (debug) 436280849Scy setlogmask(LOG_UPTO(LOG_DEBUG)); 437280849Scy else 438280849Scy# endif /* DEBUG */ 439280849Scy setlogmask(LOG_UPTO(LOG_DEBUG)); /* @@@ was INFO */ 440280849Scy# endif /* LOG_DAEMON */ 441280849Scy#endif /* !VMS */ 442280849Scy} 443280849Scy 444280849Scy 445280849Scy/* 446280849Scy * change_logfile() 447280849Scy * 448280849Scy * Used to change from syslog to a logfile, or from one logfile to 449280849Scy * another, and to reopen logfiles after forking. On systems where 450280849Scy * ntpd forks, deals with converting relative logfile paths to 451280849Scy * absolute (root-based) because we reopen logfiles after the current 452280849Scy * directory has changed. 453280849Scy */ 454280849Scyint 455280849Scychange_logfile( 456280849Scy const char * fname, 457280849Scy int leave_crumbs 458280849Scy ) 459280849Scy{ 460280849Scy FILE * new_file; 461280849Scy const char * log_fname; 462280849Scy char * abs_fname; 463280849Scy#if !defined(SYS_WINNT) && !defined(SYS_VXWORKS) && !defined(VMS) 464280849Scy char curdir[512]; 465280849Scy size_t cd_octets; 466280849Scy size_t octets; 467280849Scy#endif /* POSIX */ 468280849Scy 469289764Sglebius REQUIRE(fname != NULL); 470280849Scy log_fname = fname; 471280849Scy 472280849Scy /* 473280849Scy * In a forked child of a parent which is logging to a file 474280849Scy * instead of syslog, syslog_file will be NULL and both 475280849Scy * syslog_fname and syslog_abs_fname will be non-NULL. 476280849Scy * If we are given the same filename previously opened 477280849Scy * and it's still open, there's nothing to do here. 478280849Scy */ 479280849Scy if (syslog_file != NULL && syslog_fname != NULL && 480280849Scy 0 == strcmp(syslog_fname, log_fname)) 481280849Scy return 0; 482280849Scy 483280849Scy if (0 == strcmp(log_fname, "stderr")) { 484280849Scy new_file = stderr; 485280849Scy abs_fname = estrdup(log_fname); 486280849Scy } else if (0 == strcmp(log_fname, "stdout")) { 487280849Scy new_file = stdout; 488280849Scy abs_fname = estrdup(log_fname); 489280849Scy } else { 490280849Scy if (syslog_fname != NULL && 491280849Scy 0 == strcmp(log_fname, syslog_fname)) 492280849Scy log_fname = syslog_abs_fname; 493280849Scy#if !defined(SYS_WINNT) && !defined(SYS_VXWORKS) && !defined(VMS) 494280849Scy if (log_fname != syslog_abs_fname && 495280849Scy DIR_SEP != log_fname[0] && 496280849Scy 0 != strcmp(log_fname, "stderr") && 497280849Scy 0 != strcmp(log_fname, "stdout") && 498280849Scy NULL != getcwd(curdir, sizeof(curdir))) { 499280849Scy cd_octets = strlen(curdir); 500280849Scy /* trim any trailing '/' */ 501280849Scy if (cd_octets > 1 && 502280849Scy DIR_SEP == curdir[cd_octets - 1]) 503280849Scy cd_octets--; 504280849Scy octets = cd_octets; 505280849Scy octets += 1; /* separator '/' */ 506280849Scy octets += strlen(log_fname); 507280849Scy octets += 1; /* NUL terminator */ 508280849Scy abs_fname = emalloc(octets); 509280849Scy snprintf(abs_fname, octets, "%.*s%c%s", 510280849Scy (int)cd_octets, curdir, DIR_SEP, 511280849Scy log_fname); 512280849Scy } else 513280849Scy#endif 514280849Scy abs_fname = estrdup(log_fname); 515280849Scy TRACE(1, ("attempting to open log %s\n", abs_fname)); 516280849Scy new_file = fopen(abs_fname, "a"); 517280849Scy } 518280849Scy 519280849Scy if (NULL == new_file) { 520280849Scy free(abs_fname); 521280849Scy return -1; 522280849Scy } 523280849Scy 524280849Scy /* leave a pointer in the old log */ 525280849Scy if (leave_crumbs && (syslogit || log_fname != syslog_abs_fname)) 526280849Scy msyslog(LOG_NOTICE, "switching logging to file %s", 527280849Scy abs_fname); 528280849Scy 529280849Scy if (syslog_file != NULL && 530280849Scy syslog_file != stderr && syslog_file != stdout && 531280849Scy fileno(syslog_file) != fileno(new_file)) 532280849Scy fclose(syslog_file); 533280849Scy syslog_file = new_file; 534280849Scy if (log_fname == syslog_abs_fname) { 535280849Scy free(abs_fname); 536280849Scy } else { 537280849Scy if (syslog_abs_fname != NULL && 538280849Scy syslog_abs_fname != syslog_fname) 539280849Scy free(syslog_abs_fname); 540280849Scy if (syslog_fname != NULL) 541280849Scy free(syslog_fname); 542280849Scy syslog_fname = estrdup(log_fname); 543280849Scy syslog_abs_fname = abs_fname; 544280849Scy } 545280849Scy syslogit = FALSE; 546280849Scy 547280849Scy return 0; 548280849Scy} 549280849Scy 550280849Scy 551280849Scy/* 552280849Scy * setup_logfile() 553280849Scy * 554280849Scy * Redirect logging to a file if requested with -l/--logfile or via 555280849Scy * ntp.conf logfile directive. 556280849Scy * 557280849Scy * This routine is invoked three different times in the sequence of a 558280849Scy * typical daemon ntpd with DNS lookups to do. First it is invoked in 559280849Scy * the original ntpd process, then again in the daemon after closing 560280849Scy * all descriptors. In both of those cases, ntp.conf has not been 561280849Scy * processed, so only -l/--logfile will trigger logfile redirection in 562280849Scy * those invocations. Finally, if DNS names are resolved, the worker 563280849Scy * child invokes this routine after its fork and close of all 564280849Scy * descriptors. In this case, ntp.conf has been processed and any 565280849Scy * "logfile" directive needs to be honored in the child as well. 566280849Scy */ 567280849Scyvoid 568280849Scysetup_logfile( 569280849Scy const char * name 570280849Scy ) 571280849Scy{ 572280849Scy if (NULL == syslog_fname && NULL != name) { 573280849Scy if (-1 == change_logfile(name, TRUE)) 574280849Scy msyslog(LOG_ERR, "Cannot open log file %s, %m", 575280849Scy name); 576280849Scy return ; 577280849Scy } 578280849Scy if (NULL == syslog_fname) 579280849Scy return; 580280849Scy 581280849Scy if (-1 == change_logfile(syslog_fname, FALSE)) 582280849Scy msyslog(LOG_ERR, "Cannot reopen log file %s, %m", 583280849Scy syslog_fname); 584280849Scy} 585358659Scy 586358659Scy/* Helper for unit tests, where stdout + stderr are piped to the same 587358659Scy * stream. This works moderately reliable only if both streams are 588358659Scy * unbuffered or line buffered. Unfortunately stdout can be fully 589358659Scy * buffered on pipes or files... 590358659Scy */ 591358659Scyint 592358659Scychange_iobufs( 593358659Scy int how 594358659Scy ) 595358659Scy{ 596358659Scy int retv = 0; 597358659Scy 598358659Scy# ifdef HAVE_SETVBUF 599358659Scy 600358659Scy int mode; 601358659Scy 602358659Scy switch (how) { 603358659Scy case 0 : mode = _IONBF; break; /* no buffering */ 604358659Scy case 1 : mode = _IOLBF; break; /* line buffering */ 605358659Scy case 2 : mode = _IOFBF; break; /* full buffering */ 606358659Scy default: mode = _IOLBF; break; /* line buffering */ 607358659Scy } 608358659Scy 609358659Scy retv = 1; 610358659Scy if (setvbuf(stdout, NULL, mode, BUFSIZ) != 0) 611358659Scy retv = -1; 612358659Scy if (setvbuf(stderr, NULL, mode, BUFSIZ) != 0) 613358659Scy retv = -1; 614358659Scy 615358659Scy# else 616358659Scy 617358659Scy UNUSED_ARG(how); 618358659Scy 619358659Scy# endif 620358659Scy 621358659Scy return retv; 622358659Scy} 623