| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "systemd-journal-helpers.h" |
| 4 | #include "../libnetdata.h" |
| 5 | |
| 6 | bool is_path_unix_socket(const char *path) { |
| 7 | // Check if the path is valid |
| 8 | if(!path || !*path) |
| 9 | return false; |
| 10 | |
| 11 | struct stat statbuf; |
| 12 | |
| 13 | // Use stat to check if the file exists and is a socket |
| 14 | if (stat(path, &statbuf) == -1) |
| 15 | // The file does not exist or cannot be accessed |
| 16 | return false; |
| 17 | |
| 18 | // Check if the file is a socket |
| 19 | if (S_ISSOCK(statbuf.st_mode)) |
| 20 | return true; |
| 21 | |
| 22 | return false; |
| 23 | } |
| 24 | |
| 25 | bool is_stderr_connected_to_journal(void) { |
| 26 | const char *journal_stream = getenv("JOURNAL_STREAM"); |
| 27 | if (!journal_stream) |
| 28 | return false; // JOURNAL_STREAM is not set |
| 29 | |
| 30 | struct stat stderr_stat; |
| 31 | if (fstat(STDERR_FILENO, &stderr_stat) < 0) |
| 32 | return false; // Error in getting stderr info |
| 33 | |
| 34 | // Parse device and inode from JOURNAL_STREAM |
| 35 | char *endptr; |
| 36 | long journal_dev = strtol(journal_stream, &endptr, 10); |
| 37 | if (*endptr != ':') |
| 38 | return false; // Format error in JOURNAL_STREAM |
| 39 | |
| 40 | long journal_ino = strtol(endptr + 1, NULL, 10); |
| 41 | |
| 42 | return (stderr_stat.st_dev == (dev_t)journal_dev) && (stderr_stat.st_ino == (ino_t)journal_ino); |
| 43 | } |
| 44 | |
| 45 | int journal_direct_fd(const char *path) { |
| 46 | if(!path || !*path) |
| 47 | path = JOURNAL_DIRECT_SOCKET; |
| 48 | |
| 49 | if(!is_path_unix_socket(path)) |
| 50 | return -1; |
| 51 | |
| 52 | int fd = socket(AF_UNIX, SOCK_DGRAM| DEFAULT_SOCKET_FLAGS, 0); |
| 53 | if (fd < 0) return -1; |
| 54 | |
| 55 | sock_setcloexec(fd, true); |
| 56 | |
| 57 | struct sockaddr_un addr; |
| 58 | memset(&addr, 0, sizeof(struct sockaddr_un)); |
| 59 | addr.sun_family = AF_UNIX; |
| 60 | strncpyz(addr.sun_path, path, sizeof(addr.sun_path) - 1); |
| 61 | |
| 62 | // Connect the socket (optional, but can simplify send operations) |
| 63 | if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { |
| 64 | close(fd); |
| 65 | return -1; |
| 66 | } |
| 67 | |
| 68 | return fd; |
| 69 | } |
| 70 | |
| 71 | static inline bool journal_send_with_memfd(int fd __maybe_unused, const char *msg __maybe_unused, size_t msg_len __maybe_unused) { |
| 72 | #if defined(__NR_memfd_create) && defined(MFD_ALLOW_SEALING) && defined(F_ADD_SEALS) && defined(F_SEAL_SHRINK) && defined(F_SEAL_GROW) && defined(F_SEAL_WRITE) |
| 73 | // Create a memory file descriptor |
| 74 | int memfd = (int)syscall(__NR_memfd_create, "journald", MFD_ALLOW_SEALING); |
| 75 | if (memfd < 0) return false; |
| 76 | |
| 77 | // Write data to the memfd |
| 78 | if (write(memfd, msg, msg_len) != (ssize_t)msg_len) { |
| 79 | close(memfd); |
| 80 | return false; |
| 81 | } |
| 82 | |
| 83 | // Seal the memfd to make it immutable |
| 84 | if (fcntl(memfd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE) < 0) { |
| 85 | close(memfd); |
| 86 | return false; |
| 87 | } |
| 88 | |
| 89 | struct iovec iov = {0}; |
| 90 | struct msghdr msghdr = {0}; |
| 91 | struct cmsghdr *cmsghdr; |
| 92 | char cmsgbuf[CMSG_SPACE(sizeof(int))]; |
| 93 | |
| 94 | msghdr.msg_iov = &iov; |
| 95 | msghdr.msg_iovlen = 1; |
| 96 | msghdr.msg_control = cmsgbuf; |
| 97 | msghdr.msg_controllen = sizeof(cmsgbuf); |
| 98 | |
| 99 | cmsghdr = CMSG_FIRSTHDR(&msghdr); |
| 100 | if(!cmsghdr) { |
| 101 | close(memfd); |
| 102 | return false; |
| 103 | } |
| 104 | |
| 105 | cmsghdr->cmsg_level = SOL_SOCKET; |
| 106 | cmsghdr->cmsg_type = SCM_RIGHTS; |
| 107 | cmsghdr->cmsg_len = CMSG_LEN(sizeof(int)); |
| 108 | memcpy(CMSG_DATA(cmsghdr), &memfd, sizeof(int)); |
| 109 | |
| 110 | ssize_t r = sendmsg(fd, &msghdr, 0); |
| 111 | |
| 112 | close(memfd); |
| 113 | return r >= 0; |
| 114 | #else |
| 115 | return false; |
| 116 | #endif |
| 117 | } |
| 118 | |
| 119 | bool journal_direct_send(int fd, const char *msg, size_t msg_len) { |
| 120 | // Send the datagram |
| 121 | if (send(fd, msg, msg_len, 0) < 0) { |
| 122 | if(errno != EMSGSIZE) |
| 123 | return false; |
| 124 | |
| 125 | // datagram is too large, fallback to memfd |
| 126 | if(!journal_send_with_memfd(fd, msg, msg_len)) |
| 127 | return false; |
| 128 | } |
| 129 | |
| 130 | return true; |
| 131 | } |
| 132 | |
| 133 | void journal_construct_path(char *dst, size_t dst_len, const char *host_prefix, const char *namespace_str) { |
| 134 | if(!host_prefix) |
| 135 | host_prefix = ""; |
| 136 | |
| 137 | if(namespace_str) |
| 138 | snprintfz(dst, dst_len, "%s/run/systemd/journal.%s/socket", |
| 139 | host_prefix, namespace_str); |
| 140 | else |
| 141 | snprintfz(dst, dst_len, "%s" JOURNAL_DIRECT_SOCKET, |
| 142 | host_prefix); |
| 143 | } |