1 /* 2 * Copyright (c) 2015, Juniper Networks, Inc. 3 * All rights reserved. 4 * This SOFTWARE is licensed under the LICENSE provided in the 5 * ../Copyright file. By downloading, installing, copying, or otherwise 6 * using the SOFTWARE, you agree to be bound by the terms of that 7 * LICENSE. 8 * Phil Shafer, June 2015 9 */ 10 11 /* 12 * Portions of this file are: 13 * Copyright (c) 1983, 1988, 1993 14 * The Regents of the University of California. All rights reserved. 15 * 16 * Redistribution and use in source and binary forms, with or without 17 * modification, are permitted provided that the following conditions 18 * are met: 19 * 1. Redistributions of source code must retain the above copyright 20 * notice, this list of conditions and the following disclaimer. 21 * 2. Redistributions in binary form must reproduce the above copyright 22 * notice, this list of conditions and the following disclaimer in the 23 * documentation and/or other materials provided with the distribution. 24 * 3. Neither the name of the University nor the names of its contributors 25 * may be used to endorse or promote products derived from this software 26 * without specific prior written permission. 27 * 28 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 29 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 31 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 32 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 36 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 37 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 38 * SUCH DAMAGE. 39 */ 40 41 #include <sys/cdefs.h> 42 #include <sys/types.h> 43 #include <sys/socket.h> 44 #include <sys/syslog.h> 45 #include <sys/uio.h> 46 #include <sys/un.h> 47 #include <netdb.h> 48 #include <errno.h> 49 #include <fcntl.h> 50 #include <paths.h> 51 #include <pthread.h> 52 #include <stdio.h> 53 #include <stdlib.h> 54 #include <string.h> 55 #include <time.h> 56 #include <unistd.h> 57 #include <stdarg.h> 58 #include <sys/time.h> 59 #include <sys/types.h> 60 #include <sys/sysctl.h> 61 62 #include "xo_config.h" 63 #include "xo.h" 64 #include "xo_encoder.h" /* For xo_realloc */ 65 #include "xo_buf.h" 66 67 /* 68 * SYSLOG (RFC 5424) requires an enterprise identifier. This turns 69 * out to be a fickle little issue. For a single-vendor box, the 70 * system should have a single EID that all software can use. When 71 * VendorX turns FreeBSD into a product, all software (kernel and 72 * utilities) should report VendorX's EID. But when software is 73 * installed on top of an external operating system, the application 74 * should report it's own EID, distinct from the base OS. 75 * 76 * To make this happen, the kernel should support a sysctl to assign a 77 * custom enterprise-id ("kern.syslog.enterprise_id"). libxo then 78 * allows an application to set a custom EID to override that system 79 * wide value, if needed. 80 * 81 * We try to set the stock IANA assigned Enterprise ID value for the 82 * vendors we know about (FreeBSD, macosx), but fallback to the 83 * "example" EID defined by IANA. See: 84 * https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers 85 */ 86 87 #define XO_SYSLOG_ENTERPRISE_ID "kern.syslog.enterprise_id" 88 89 #if defined(__FreeBSD__) 90 #define XO_DEFAULT_EID 2238 91 #elif defined(__macosx__) 92 #define XO_DEFAULT_EID 63 93 #else 94 #define XO_DEFAULT_EID 32473 /* Fallback to the "example" number */ 95 #endif 96 97 #ifdef _SC_HOST_NAME_MAX 98 #define HOST_NAME_MAX _SC_HOST_NAME_MAX 99 #else 100 #define HOST_NAME_MAX 255 101 #endif /* _SC_HOST_NAME_MAX */ 102 103 #ifndef UNUSED 104 #define UNUSED __attribute__ ((__unused__)) 105 #endif /* UNUSED */ 106 107 static int xo_logfile = -1; /* fd for log */ 108 static int xo_status; /* connection xo_status */ 109 static int xo_opened; /* have done openlog() */ 110 static int xo_logstat = 0; /* xo_status bits, set by openlog() */ 111 static const char *xo_logtag = NULL; /* string to tag the entry with */ 112 static int xo_logfacility = LOG_USER; /* default facility code */ 113 static int xo_logmask = 0xff; /* mask of priorities to be logged */ 114 static pthread_mutex_t xo_syslog_mutex UNUSED = PTHREAD_MUTEX_INITIALIZER; 115 static int xo_unit_test; /* Fake data for unit test */ 116 117 #define REAL_VOID(_x) \ 118 do { int really_ignored = _x; if (really_ignored) { }} while (0) 119 120 #if !defined(HAVE_DECL___ISTHREADED) || !HAVE_DECL___ISTHREADED 121 #define __isthreaded 1 122 #endif 123 124 #define THREAD_LOCK() \ 125 do { \ 126 if (__isthreaded) pthread_mutex_lock(&xo_syslog_mutex); \ 127 } while(0) 128 #define THREAD_UNLOCK() \ 129 do { \ 130 if (__isthreaded) pthread_mutex_unlock(&xo_syslog_mutex); \ 131 } while(0) 132 133 static void xo_disconnect_log(void); /* disconnect from syslogd */ 134 static void xo_connect_log(void); /* (re)connect to syslogd */ 135 static void xo_open_log_unlocked(const char *, int, int); 136 137 enum { 138 NOCONN = 0, 139 CONNDEF, 140 CONNPRIV, 141 }; 142 143 static xo_syslog_open_t xo_syslog_open; 144 static xo_syslog_send_t xo_syslog_send; 145 static xo_syslog_close_t xo_syslog_close; 146 147 static char xo_syslog_enterprise_id[12]; 148 149 /* 150 * Record an enterprise ID, which functions as a namespace for syslog 151 * messages. The value is pre-formatted into a string. This allows 152 * applications to customize their syslog message set, when needed. 153 */ 154 void 155 xo_set_syslog_enterprise_id (unsigned short eid) 156 { 157 snprintf(xo_syslog_enterprise_id, sizeof(xo_syslog_enterprise_id), 158 "%u", eid); 159 } 160 161 /* 162 * Handle the work of transmitting the syslog message 163 */ 164 static void 165 xo_send_syslog (char *full_msg, char *v0_hdr, 166 char *text_only) 167 { 168 if (xo_syslog_send) { 169 xo_syslog_send(full_msg, v0_hdr, text_only); 170 return; 171 } 172 173 int fd; 174 int full_len = strlen(full_msg); 175 176 /* Output to stderr if requested. */ 177 if (xo_logstat & LOG_PERROR) { 178 struct iovec iov[3]; 179 struct iovec *v = iov; 180 char newline[] = "\n"; 181 182 v->iov_base = v0_hdr; 183 v->iov_len = strlen(v0_hdr); 184 v += 1; 185 v->iov_base = text_only; 186 v->iov_len = strlen(text_only); 187 v += 1; 188 v->iov_base = newline; 189 v->iov_len = 1; 190 v += 1; 191 REAL_VOID(writev(STDERR_FILENO, iov, 3)); 192 } 193 194 /* Get connected, output the message to the local logger. */ 195 if (!xo_opened) 196 xo_open_log_unlocked(xo_logtag, xo_logstat | LOG_NDELAY, 0); 197 xo_connect_log(); 198 199 /* 200 * If the send() fails, there are two likely scenarios: 201 * 1) syslogd was restarted 202 * 2) /var/run/log is out of socket buffer space, which 203 * in most cases means local DoS. 204 * If the error does not indicate a full buffer, we address 205 * case #1 by attempting to reconnect to /var/run/log[priv] 206 * and resending the message once. 207 * 208 * If we are working with a privileged socket, the retry 209 * attempts end there, because we don't want to freeze a 210 * critical application like su(1) or sshd(8). 211 * 212 * Otherwise, we address case #2 by repeatedly retrying the 213 * send() to give syslogd a chance to empty its socket buffer. 214 */ 215 216 if (send(xo_logfile, full_msg, full_len, 0) < 0) { 217 if (errno != ENOBUFS) { 218 /* 219 * Scenario 1: syslogd was restarted 220 * reconnect and resend once 221 */ 222 xo_disconnect_log(); 223 xo_connect_log(); 224 if (send(xo_logfile, full_msg, full_len, 0) >= 0) { 225 return; 226 } 227 /* 228 * if the resend failed, fall through to 229 * possible scenario 2 230 */ 231 } 232 while (errno == ENOBUFS) { 233 /* 234 * Scenario 2: out of socket buffer space 235 * possible DoS, fail fast on a privileged 236 * socket 237 */ 238 if (xo_status == CONNPRIV) 239 break; 240 usleep(1); 241 if (send(xo_logfile, full_msg, full_len, 0) >= 0) { 242 return; 243 } 244 } 245 } else { 246 return; 247 } 248 249 /* 250 * Output the message to the console; try not to block 251 * as a blocking console should not stop other processes. 252 * Make sure the error reported is the one from the syslogd failure. 253 */ 254 int flags = O_WRONLY | O_NONBLOCK; 255 #ifdef O_CLOEXEC 256 flags |= O_CLOEXEC; 257 #endif /* O_CLOEXEC */ 258 259 if (xo_logstat & LOG_CONS 260 && (fd = open(_PATH_CONSOLE, flags, 0)) >= 0) { 261 struct iovec iov[2]; 262 struct iovec *v = iov; 263 char crnl[] = "\r\n"; 264 char *p; 265 266 p = strchr(full_msg, '>') + 1; 267 v->iov_base = p; 268 v->iov_len = full_len - (p - full_msg); 269 ++v; 270 v->iov_base = crnl; 271 v->iov_len = 2; 272 REAL_VOID(writev(fd, iov, 2)); 273 (void) close(fd); 274 } 275 } 276 277 /* Should be called with mutex acquired */ 278 static void 279 xo_disconnect_log (void) 280 { 281 if (xo_syslog_close) { 282 xo_syslog_close(); 283 return; 284 } 285 286 /* 287 * If the user closed the FD and opened another in the same slot, 288 * that's their problem. They should close it before calling on 289 * system services. 290 */ 291 if (xo_logfile != -1) { 292 close(xo_logfile); 293 xo_logfile = -1; 294 } 295 xo_status = NOCONN; /* retry connect */ 296 } 297 298 /* Should be called with mutex acquired */ 299 static void 300 xo_connect_log (void) 301 { 302 if (xo_syslog_open) { 303 xo_syslog_open(); 304 return; 305 } 306 307 struct sockaddr_un saddr; /* AF_UNIX address of local logger */ 308 309 if (xo_logfile == -1) { 310 int flags = SOCK_DGRAM; 311 #ifdef SOCK_CLOEXEC 312 flags |= SOCK_CLOEXEC; 313 #endif /* SOCK_CLOEXEC */ 314 if ((xo_logfile = socket(AF_UNIX, flags, 0)) == -1) 315 return; 316 } 317 if (xo_logfile != -1 && xo_status == NOCONN) { 318 #ifdef HAVE_SUN_LEN 319 saddr.sun_len = sizeof(saddr); 320 #endif /* HAVE_SUN_LEN */ 321 saddr.sun_family = AF_UNIX; 322 323 /* 324 * First try privileged socket. If no success, 325 * then try default socket. 326 */ 327 328 #ifdef _PATH_LOG_PRIV 329 (void) strncpy(saddr.sun_path, _PATH_LOG_PRIV, 330 sizeof saddr.sun_path); 331 if (connect(xo_logfile, (struct sockaddr *) &saddr, 332 sizeof(saddr)) != -1) 333 xo_status = CONNPRIV; 334 #endif /* _PATH_LOG_PRIV */ 335 336 #ifdef _PATH_LOG 337 if (xo_status == NOCONN) { 338 (void) strncpy(saddr.sun_path, _PATH_LOG, 339 sizeof saddr.sun_path); 340 if (connect(xo_logfile, (struct sockaddr *)&saddr, 341 sizeof(saddr)) != -1) 342 xo_status = CONNDEF; 343 } 344 #endif /* _PATH_LOG */ 345 346 #ifdef _PATH_OLDLOG 347 if (xo_status == NOCONN) { 348 /* 349 * Try the old "/dev/log" path, for backward 350 * compatibility. 351 */ 352 (void) strncpy(saddr.sun_path, _PATH_OLDLOG, 353 sizeof saddr.sun_path); 354 if (connect(xo_logfile, (struct sockaddr *)&saddr, 355 sizeof(saddr)) != -1) 356 xo_status = CONNDEF; 357 } 358 #endif /* _PATH_OLDLOG */ 359 360 if (xo_status == NOCONN) { 361 (void) close(xo_logfile); 362 xo_logfile = -1; 363 } 364 } 365 } 366 367 static void 368 xo_open_log_unlocked (const char *ident, int logstat, int logfac) 369 { 370 if (ident != NULL) 371 xo_logtag = ident; 372 xo_logstat = logstat; 373 if (logfac != 0 && (logfac &~ LOG_FACMASK) == 0) 374 xo_logfacility = logfac; 375 376 if (xo_logstat & LOG_NDELAY) /* open immediately */ 377 xo_connect_log(); 378 379 xo_opened = 1; /* ident and facility has been set */ 380 } 381 382 void 383 xo_open_log (const char *ident, int logstat, int logfac) 384 { 385 THREAD_LOCK(); 386 xo_open_log_unlocked(ident, logstat, logfac); 387 THREAD_UNLOCK(); 388 } 389 390 391 void 392 xo_close_log (void) 393 { 394 THREAD_LOCK(); 395 if (xo_logfile != -1) { 396 (void) close(xo_logfile); 397 xo_logfile = -1; 398 } 399 xo_logtag = NULL; 400 xo_status = NOCONN; 401 THREAD_UNLOCK(); 402 } 403 404 /* xo_set_logmask -- set the log mask level */ 405 int 406 xo_set_logmask (int pmask) 407 { 408 int omask; 409 410 THREAD_LOCK(); 411 omask = xo_logmask; 412 if (pmask != 0) 413 xo_logmask = pmask; 414 THREAD_UNLOCK(); 415 return (omask); 416 } 417 418 void 419 xo_set_syslog_handler (xo_syslog_open_t open_func, 420 xo_syslog_send_t send_func, 421 xo_syslog_close_t close_func) 422 { 423 xo_syslog_open = open_func; 424 xo_syslog_send = send_func; 425 xo_syslog_close = close_func; 426 } 427 428 static size_t 429 xo_snprintf (char *out, size_t outsize, const char *fmt, ...) 430 { 431 int status; 432 size_t retval = 0; 433 va_list ap; 434 if (out && outsize) { 435 va_start(ap, fmt); 436 status = vsnprintf(out, outsize, fmt, ap); 437 if (status < 0) { /* this should never happen, */ 438 *out = 0; /* handle it in the safest way possible if it does */ 439 retval = 0; 440 } else { 441 retval = status; 442 retval = retval > outsize ? outsize : retval; 443 } 444 va_end(ap); 445 } 446 return retval; 447 } 448 449 static int 450 xo_syslog_handle_write (void *opaque, const char *data) 451 { 452 xo_buffer_t *xbp = opaque; 453 int len = strlen(data); 454 int left = xo_buf_left(xbp); 455 456 if (len > left - 1) 457 len = left - 1; 458 459 memcpy(xbp->xb_curp, data, len); 460 xbp->xb_curp += len; 461 *xbp->xb_curp = '\0'; 462 463 return len; 464 } 465 466 static void 467 xo_syslog_handle_close (void *opaque UNUSED) 468 { 469 } 470 471 static int 472 xo_syslog_handle_flush (void *opaque UNUSED) 473 { 474 return 0; 475 } 476 477 void 478 xo_set_unit_test_mode (int value) 479 { 480 xo_unit_test = value; 481 } 482 483 void 484 xo_vsyslog (int pri, const char *name, const char *fmt, va_list vap) 485 { 486 int saved_errno = errno; 487 char tbuf[2048]; 488 char *tp = NULL, *ep = NULL; 489 unsigned start_of_msg = 0; 490 char *v0_hdr = NULL; 491 xo_buffer_t xb; 492 static pid_t my_pid; 493 unsigned log_offset; 494 495 if (my_pid == 0) 496 my_pid = xo_unit_test ? 222 : getpid(); 497 498 /* Check for invalid bits */ 499 if (pri & ~(LOG_PRIMASK|LOG_FACMASK)) { 500 xo_syslog(LOG_ERR | LOG_CONS | LOG_PERROR | LOG_PID, 501 "syslog-unknown-priority", 502 "syslog: unknown facility/priority: %#x", pri); 503 pri &= LOG_PRIMASK|LOG_FACMASK; 504 } 505 506 THREAD_LOCK(); 507 508 /* Check priority against setlogmask values. */ 509 if (!(LOG_MASK(LOG_PRI(pri)) & xo_logmask)) { 510 THREAD_UNLOCK(); 511 return; 512 } 513 514 /* Set default facility if none specified. */ 515 if ((pri & LOG_FACMASK) == 0) 516 pri |= xo_logfacility; 517 518 /* Create the primary stdio hook */ 519 xb.xb_bufp = tbuf; 520 xb.xb_curp = tbuf; 521 xb.xb_size = sizeof(tbuf); 522 523 xo_handle_t *xop = xo_create(XO_STYLE_SDPARAMS, 0); 524 if (xop == NULL) { 525 THREAD_UNLOCK(); 526 return; 527 } 528 529 #ifdef HAVE_GETPROGNAME 530 if (xo_logtag == NULL) 531 xo_logtag = getprogname(); 532 #endif /* HAVE_GETPROGNAME */ 533 534 xo_set_writer(xop, &xb, xo_syslog_handle_write, xo_syslog_handle_close, 535 xo_syslog_handle_flush); 536 537 /* Build the message; start by getting the time */ 538 struct tm tm; 539 struct timeval tv; 540 541 /* Unit test hack: fake a fixed time */ 542 if (xo_unit_test) { 543 tv.tv_sec = 1435085229; 544 tv.tv_usec = 123456; 545 } else 546 gettimeofday(&tv, NULL); 547 548 (void) localtime_r(&tv.tv_sec, &tm); 549 550 if (xo_logstat & LOG_PERROR) { 551 /* 552 * For backwards compatibility, we need to make the old-style 553 * message. This message can be emitted to the console/tty. 554 */ 555 v0_hdr = alloca(2048); 556 tp = v0_hdr; 557 ep = v0_hdr + 2048; 558 559 if (xo_logtag != NULL) 560 tp += xo_snprintf(tp, ep - tp, "%s", xo_logtag); 561 if (xo_logstat & LOG_PID) 562 tp += xo_snprintf(tp, ep - tp, "[%d]", my_pid); 563 if (xo_logtag) 564 tp += xo_snprintf(tp, ep - tp, ": "); 565 } 566 567 log_offset = xb.xb_curp - xb.xb_bufp; 568 569 /* Add PRI, PRIVAL, and VERSION */ 570 xb.xb_curp += xo_snprintf(xb.xb_curp, xo_buf_left(&xb), "<%d>1 ", pri); 571 572 /* Add TIMESTAMP with milliseconds and TZOFFSET */ 573 xb.xb_curp += strftime(xb.xb_curp, xo_buf_left(&xb), "%FT%T", &tm); 574 xb.xb_curp += xo_snprintf(xb.xb_curp, xo_buf_left(&xb), 575 ".%03.3u", tv.tv_usec / 1000); 576 xb.xb_curp += strftime(xb.xb_curp, xo_buf_left(&xb), "%z ", &tm); 577 578 /* 579 * Add HOSTNAME; we rely on gethostname and don't fluff with 580 * ip addresses. Might need to revisit..... 581 */ 582 char hostname[HOST_NAME_MAX]; 583 hostname[0] = '\0'; 584 if (xo_unit_test) 585 strcpy(hostname, "worker-host"); 586 else 587 (void) gethostname(hostname, sizeof(hostname)); 588 589 xb.xb_curp += xo_snprintf(xb.xb_curp, xo_buf_left(&xb), "%s ", 590 hostname[0] ? hostname : "-"); 591 592 /* Add APP-NAME */ 593 xb.xb_curp += xo_snprintf(xb.xb_curp, xo_buf_left(&xb), "%s ", 594 xo_logtag ?: "-"); 595 596 /* Add PROCID */ 597 xb.xb_curp += xo_snprintf(xb.xb_curp, xo_buf_left(&xb), "%d ", my_pid); 598 599 /* 600 * Add MSGID. The user should provide us with a name, which we 601 * prefix with the current enterprise ID, as learned from the kernel. 602 * If the kernel won't tell us, we use the stock/builtin number. 603 */ 604 char *buf UNUSED = NULL; 605 const char *eid = xo_syslog_enterprise_id; 606 const char *at_sign = "@"; 607 608 if (name == NULL) { 609 name = "-"; 610 eid = at_sign = ""; 611 612 } else if (*name == '@') { 613 /* Our convention is to prefix IANA-defined names with an "@" */ 614 name += 1; 615 eid = at_sign = ""; 616 617 } else if (eid[0] == '\0') { 618 #ifdef HAVE_SYSCTLBYNAME 619 /* 620 * See if the kernel knows the sysctl for the enterprise ID 621 */ 622 size_t size = 0; 623 if (sysctlbyname(XO_SYSLOG_ENTERPRISE_ID, NULL, &size, NULL, 0) == 0 624 && size > 0) { 625 buf = alloca(size); 626 if (sysctlbyname(XO_SYSLOG_ENTERPRISE_ID, buf, &size, NULL, 0) == 0 627 && size > 0) 628 eid = buf; 629 } 630 #endif /* HAVE_SYSCTLBYNAME */ 631 632 if (eid[0] == '\0') { 633 /* Fallback to our base default */ 634 xo_set_syslog_enterprise_id(XO_DEFAULT_EID); 635 eid = xo_syslog_enterprise_id; 636 } 637 } 638 639 xb.xb_curp += xo_snprintf(xb.xb_curp, xo_buf_left(&xb), "%s [%s%s%s ", 640 name, name, at_sign, eid); 641 642 /* 643 * Now for the real content. We make two distinct passes thru the 644 * xo_emit engine, first for the SD-PARAMS and then for the text 645 * message. 646 */ 647 va_list ap; 648 va_copy(ap, vap); 649 650 errno = saved_errno; /* Restore saved error value */ 651 xo_emit_hv(xop, fmt, ap); 652 xo_flush_h(xop); 653 654 va_end(ap); 655 656 /* Trim trailing space */ 657 if (xb.xb_curp[-1] == ' ') 658 xb.xb_curp -= 1; 659 660 /* Close the structured data (SD-ELEMENT) */ 661 xb.xb_curp += xo_snprintf(xb.xb_curp, xo_buf_left(&xb), "] "); 662 663 /* 664 * Since our MSG is known to be UTF-8, we MUST prefix it with 665 * that most-annoying-of-all-UTF-8 features, the BOM (0xEF.BB.BF). 666 */ 667 xb.xb_curp += xo_snprintf(xb.xb_curp, xo_buf_left(&xb), 668 "%c%c%c", 0xEF, 0xBB, 0xBF); 669 670 /* Save the start of the message */ 671 if (xo_logstat & LOG_PERROR) 672 start_of_msg = xb.xb_curp - xb.xb_bufp; 673 674 xo_set_style(xop, XO_STYLE_TEXT); 675 xo_set_flags(xop, XOF_UTF8); 676 677 errno = saved_errno; /* Restore saved error value */ 678 xo_emit_hv(xop, fmt, ap); 679 xo_flush_h(xop); 680 681 /* Remove a trailing newline */ 682 if (xb.xb_curp[-1] == '\n') 683 *--xb.xb_curp = '\0'; 684 685 if (xo_get_flags(xop) & XOF_LOG_SYSLOG) 686 fprintf(stderr, "xo: syslog: %s\n", xb.xb_bufp + log_offset); 687 688 xo_send_syslog(xb.xb_bufp, v0_hdr, xb.xb_bufp + start_of_msg); 689 690 xo_destroy(xop); 691 692 THREAD_UNLOCK(); 693 } 694 695 /* 696 * syslog - print message on log file; output is intended for syslogd(8). 697 */ 698 void 699 xo_syslog (int pri, const char *name, const char *fmt, ...) 700 { 701 va_list ap; 702 703 va_start(ap, fmt); 704 xo_vsyslog(pri, name, fmt, ap); 705 va_end(ap); 706 } 707