xref: /netbsd-src/external/bsd/tmux/dist/compat/systemd.c (revision f8cf1a9151c7af1cb0bd8b09c13c66bca599c027)
1 /* $OpenBSD$ */
2 
3 /*
4  * Copyright (c) 2022 Nicholas Marriott <nicholas.marriott@gmail.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/un.h>
21 
22 #include <systemd/sd-bus.h>
23 #include <systemd/sd-daemon.h>
24 #include <systemd/sd-login.h>
25 #include <systemd/sd-id128.h>
26 
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 
31 #include "tmux.h"
32 
33 int
34 systemd_activated(void)
35 {
36 	return (sd_listen_fds(0) >= 1);
37 }
38 
39 int
40 systemd_create_socket(int flags, char **cause)
41 {
42 	int			fds;
43 	int			fd;
44 	struct sockaddr_un	sa;
45 	socklen_t		addrlen = sizeof sa;
46 
47 	fds = sd_listen_fds(0);
48 	if (fds > 1) { /* too many file descriptors */
49 		errno = E2BIG;
50 		goto fail;
51 	}
52 
53 	if (fds == 1) { /* socket-activated */
54 		fd = SD_LISTEN_FDS_START;
55 		if (!sd_is_socket_unix(fd, SOCK_STREAM, 1, NULL, 0)) {
56 			errno = EPFNOSUPPORT;
57 			goto fail;
58 		}
59 		if (getsockname(fd, (struct sockaddr *)&sa, &addrlen) == -1)
60 			goto fail;
61 		socket_path = xstrdup(sa.sun_path);
62 		return (fd);
63 	}
64 
65 	return (server_create_socket(flags, cause));
66 
67 fail:
68 	if (cause != NULL)
69 		xasprintf(cause, "systemd socket error (%s)", strerror(errno));
70 	return (-1);
71 }
72 
73 int
74 systemd_move_pid_to_new_cgroup(pid_t pid, char **cause)
75 {
76 	sd_bus_error	 error = SD_BUS_ERROR_NULL;
77 	sd_bus_message	*m = NULL, *reply = NULL;
78 	sd_bus 		*bus = NULL;
79 	char		*name, *desc, *slice;
80 	sd_id128_t	 uuid;
81 	int		 r;
82 	pid_t		 parent_pid;
83 
84 	/* Connect to the session bus. */
85 	r = sd_bus_default_user(&bus);
86 	if (r < 0) {
87 		xasprintf(cause, "failed to connect to session bus: %s",
88 		    strerror(-r));
89 		goto finish;
90 	}
91 
92 	/* Start building the method call. */
93 	r = sd_bus_message_new_method_call(bus, &m,
94 	    "org.freedesktop.systemd1",
95 	    "/org/freedesktop/systemd1",
96 	    "org.freedesktop.systemd1.Manager",
97 	    "StartTransientUnit");
98 	if (r < 0) {
99 		xasprintf(cause, "failed to create bus message: %s",
100 		    strerror(-r));
101 		goto finish;
102 	}
103 
104 	/* Generate a unique name for the new scope, to avoid collisions. */
105 	r = sd_id128_randomize(&uuid);
106 	if (r < 0) {
107 		xasprintf(cause, "failed to generate uuid: %s", strerror(-r));
108 		goto finish;
109 	}
110 	xasprintf(&name, "tmux-spawn-" SD_ID128_UUID_FORMAT_STR ".scope",
111 	    SD_ID128_FORMAT_VAL(uuid));
112 	r = sd_bus_message_append(m, "s", name);
113 	free(name);
114 	if (r < 0) {
115 		xasprintf(cause, "failed to append to bus message: %s",
116 		    strerror(-r));
117 		goto finish;
118 	}
119 
120 	/* Mode: fail if there's a queued unit with the same name. */
121 	r = sd_bus_message_append(m, "s", "fail");
122 	if (r < 0) {
123 		xasprintf(cause, "failed to append to bus message: %s",
124 		    strerror(-r));
125 		goto finish;
126 	}
127 
128 	/* Start properties array. */
129 	r = sd_bus_message_open_container(m, 'a', "(sv)");
130 	if (r < 0) {
131 		xasprintf(cause, "failed to start properties array: %s",
132 		    strerror(-r));
133 		goto finish;
134 	}
135 
136 	parent_pid = getpid();
137 	xasprintf(&desc, "tmux child pane %ld launched by process %ld",
138 	    (long)pid, (long)parent_pid);
139 	r = sd_bus_message_append(m, "(sv)", "Description", "s", desc);
140 	free(desc);
141 	if (r < 0) {
142 		xasprintf(cause, "failed to append to properties: %s",
143 		    strerror(-r));
144 		goto finish;
145 	}
146 
147 	/*
148 	 * Make sure that the session shells are terminated with SIGHUP since
149 	 * bash and friends tend to ignore SIGTERM.
150 	 */
151 	r = sd_bus_message_append(m, "(sv)", "SendSIGHUP", "b", 1);
152 	if (r < 0) {
153 		xasprintf(cause, "failed to append to properties: %s",
154 		    strerror(-r));
155 		goto finish;
156 	}
157 
158 	/*
159 	 * Inherit the slice from the parent process, or default to
160 	 * "app-tmux.slice" if that fails.
161 	 */
162 	r = sd_pid_get_user_slice(parent_pid, &slice);
163 	if (r < 0) {
164 		slice = xstrdup("app-tmux.slice");
165 	}
166 	r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice);
167 	free(slice);
168 	if (r < 0) {
169 		xasprintf(cause, "failed to append to properties: %s",
170 		    strerror(-r));
171 		goto finish;
172 	}
173 
174 	/* PIDs to add to the scope: length - 1 array of uint32_t. */
175 	r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, pid);
176 	if (r < 0) {
177 		xasprintf(cause, "failed to append to properties: %s",
178 		    strerror(-r));
179 		goto finish;
180 	}
181 
182 	/* Clean up the scope even if it fails. */
183 	r = sd_bus_message_append(m, "(sv)", "CollectMode", "s",
184 	    "inactive-or-failed");
185 	if (r < 0) {
186 		xasprintf(cause, "failed to append to properties: %s",
187 		    strerror(-r));
188 		goto finish;
189 	}
190 
191 	/* End properties array. */
192 	r = sd_bus_message_close_container(m);
193 	if (r < 0) {
194 		xasprintf(cause, "failed to end properties array: %s",
195 		    strerror(-r));
196 		goto finish;
197 	}
198 
199 	/* aux is currently unused and should be passed an empty array. */
200 	r = sd_bus_message_append(m, "a(sa(sv))", 0);
201 	if (r < 0) {
202 		xasprintf(cause, "failed to append to bus message: %s",
203 		    strerror(-r));
204 		goto finish;
205 	}
206 
207 	/* Call the method with a timeout of 1 second = 1e6 us. */
208 	r = sd_bus_call(bus, m, 1000000, &error, &reply);
209 	if (r < 0) {
210 		if (error.message != NULL) {
211 			/* We have a specific error message from sd-bus. */
212 			xasprintf(cause, "StartTransientUnit call failed: %s",
213 			    error.message);
214 		} else {
215 			xasprintf(cause, "StartTransientUnit call failed: %s",
216 			    strerror(-r));
217 		}
218 		goto finish;
219 	}
220 
221 finish:
222 	sd_bus_error_free(&error);
223 	sd_bus_message_unref(m);
224 	sd_bus_message_unref(reply);
225 	sd_bus_unref(bus);
226 
227 	return (r);
228 }
229