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