xref: /dpdk/lib/log/log_journal.c (revision 8484d74bd656bc0e951a3ed4e0816ee0fea5e593)
1 /* SPDX-License-Identifier: BSD-3-Clause */
2 
3 #include <limits.h>
4 #include <stdbool.h>
5 #include <stdint.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <sys/socket.h>
10 #include <sys/stat.h>
11 #include <sys/uio.h>
12 #include <sys/un.h>
13 #include <unistd.h>
14 
15 #include <rte_log.h>
16 
17 #include "log_private.h"
18 
19 /*
20  * Send structured message using journal protocol
21  * See: https://systemd.io/JOURNAL_NATIVE_PROTOCOL/
22  *
23  * Uses writev() to ensure that whole log message is in one datagram
24  */
25 static int
26 journal_send(int fd, const char *buf, size_t len)
27 {
28 	struct iovec iov[4];
29 	unsigned int n = 0;
30 	int priority = rte_log_cur_msg_loglevel() - 1;
31 	char msg[] = "MESSAGE=";
32 	char newline = '\n';
33 	char pbuf[32];	/* "PRIORITY=N\n" */
34 
35 	iov[n].iov_base = msg;
36 	iov[n++].iov_len = strlen(msg);
37 
38 	iov[n].iov_base = (char *)(uintptr_t)buf;
39 	iov[n++].iov_len = len;
40 
41 	/* if message doesn't end with newline, one will be applied. */
42 	if (buf[len - 1] != '\n') {
43 		iov[n].iov_base = &newline;
44 		iov[n++].iov_len = 1;
45 	}
46 
47 	/* priority value between 0 ("emerg") and 7 ("debug") */
48 	iov[n].iov_base = pbuf;
49 	iov[n++].iov_len = snprintf(pbuf, sizeof(pbuf),
50 				    "PRIORITY=%d\n", priority);
51 	return writev(fd, iov, n);
52 }
53 
54 
55 /* wrapper for log stream to put messages into journal */
56 static ssize_t
57 journal_log_write(void *c, const char *buf, size_t size)
58 {
59 	int fd = (uintptr_t)c;
60 
61 	return journal_send(fd, buf, size);
62 }
63 
64 static int
65 journal_log_close(void *c)
66 {
67 	int fd = (uintptr_t)c;
68 
69 	close(fd);
70 	return 0;
71 }
72 
73 static cookie_io_functions_t journal_log_func = {
74 	.write = journal_log_write,
75 	.close = journal_log_close,
76 };
77 
78 /*
79  * Check if stderr is going to system journal.
80  * This is the documented way to handle systemd journal
81  *
82  * See: https://systemd.io/JOURNAL_NATIVE_PROTOCOL/
83  */
84 bool
85 log_journal_enabled(void)
86 {
87 	char *jenv, *endp = NULL;
88 	struct stat st;
89 	unsigned long dev, ino;
90 
91 	jenv = getenv("JOURNAL_STREAM");
92 	if (jenv == NULL)
93 		return false;
94 
95 	if (fstat(STDERR_FILENO, &st) < 0)
96 		return false;
97 
98 	/* systemd sets colon-separated list of device and inode number */
99 	dev = strtoul(jenv, &endp, 10);
100 	if (endp == NULL || *endp != ':')
101 		return false;	/* missing colon */
102 
103 	ino = strtoul(endp + 1, NULL, 10);
104 
105 	return dev == st.st_dev && ino == st.st_ino;
106 }
107 
108 /* Connect to systemd's journal service */
109 FILE *
110 log_journal_open(const char *id)
111 {
112 	char syslog_id[PATH_MAX];
113 	FILE *log_stream;
114 	int len;
115 	struct sockaddr_un sun = {
116 		.sun_family = AF_UNIX,
117 		.sun_path = "/run/systemd/journal/socket",
118 	};
119 	int jfd = -1;
120 
121 	len = snprintf(syslog_id, sizeof(syslog_id),
122 		       "SYSLOG_IDENTIFIER=%s\nSYSLOG_PID=%u", id, getpid());
123 
124 	/* Detect truncation of message and fallback to no journal */
125 	if (len >= (int)sizeof(syslog_id))
126 		return NULL;
127 
128 	jfd = socket(AF_UNIX, SOCK_DGRAM, 0);
129 	if (jfd < 0) {
130 		perror("socket");
131 		goto error;
132 	}
133 
134 	if (connect(jfd, (struct sockaddr *)&sun, sizeof(sun)) < 0) {
135 		perror("connect");
136 		goto error;
137 	}
138 
139 	/* Send identifier as first message */
140 	if (write(jfd, syslog_id, len) != len) {
141 		perror("write");
142 		goto error;
143 	}
144 
145 	/* redirect other log messages to journal */
146 	log_stream = fopencookie((void *)(uintptr_t)jfd, "w", journal_log_func);
147 	if (log_stream != NULL)
148 		return log_stream;
149 
150 error:
151 	close(jfd);
152 	return NULL;
153 }
154