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 <string.h>
28
29#include "tmux.h"
30
31int
32systemd_activated(void)
33{
34	return (sd_listen_fds(0) >= 1);
35}
36
37int
38systemd_create_socket(int flags, char **cause)
39{
40	int			fds;
41	int			fd;
42	struct sockaddr_un	sa;
43	socklen_t		addrlen = sizeof sa;
44
45	fds = sd_listen_fds(0);
46	if (fds > 1) { /* too many file descriptors */
47		errno = E2BIG;
48		goto fail;
49	}
50
51	if (fds == 1) { /* socket-activated */
52		fd = SD_LISTEN_FDS_START;
53		if (!sd_is_socket_unix(fd, SOCK_STREAM, 1, NULL, 0)) {
54			errno = EPFNOSUPPORT;
55			goto fail;
56		}
57		if (getsockname(fd, (struct sockaddr *)&sa, &addrlen) == -1)
58			goto fail;
59		socket_path = xstrdup(sa.sun_path);
60		return (fd);
61	}
62
63	return (server_create_socket(flags, cause));
64
65fail:
66	if (cause != NULL)
67		xasprintf(cause, "systemd socket error (%s)", strerror(errno));
68	return (-1);
69}
70
71int
72systemd_move_pid_to_new_cgroup(pid_t pid, char **cause)
73{
74	sd_bus_error	 error = SD_BUS_ERROR_NULL;
75	sd_bus_message	*m = NULL, *reply = NULL;
76	sd_bus 		*bus = NULL;
77	char		*name, *desc, *slice;
78	sd_id128_t	 uuid;
79	int		 r;
80	pid_t		 parent_pid;
81
82	/* Connect to the session bus. */
83	r = sd_bus_default_user(&bus);
84	if (r < 0) {
85		xasprintf(cause, "failed to connect to session bus: %s",
86		    strerror(-r));
87		goto finish;
88	}
89
90	/* Start building the method call. */
91	r = sd_bus_message_new_method_call(bus, &m,
92	    "org.freedesktop.systemd1",
93	    "/org/freedesktop/systemd1",
94	    "org.freedesktop.systemd1.Manager",
95	    "StartTransientUnit");
96	if (r < 0) {
97		xasprintf(cause, "failed to create bus message: %s",
98		    strerror(-r));
99		goto finish;
100	}
101
102	/* Generate a unique name for the new scope, to avoid collisions. */
103	r = sd_id128_randomize(&uuid);
104	if (r < 0) {
105		xasprintf(cause, "failed to generate uuid: %s", strerror(-r));
106		goto finish;
107	}
108	xasprintf(&name, "tmux-spawn-" SD_ID128_UUID_FORMAT_STR ".scope",
109	    SD_ID128_FORMAT_VAL(uuid));
110	r = sd_bus_message_append(m, "s", name);
111	free(name);
112	if (r < 0) {
113		xasprintf(cause, "failed to append to bus message: %s",
114		    strerror(-r));
115		goto finish;
116	}
117
118	/* Mode: fail if there's a queued unit with the same name. */
119	r = sd_bus_message_append(m, "s", "fail");
120	if (r < 0) {
121		xasprintf(cause, "failed to append to bus message: %s",
122		    strerror(-r));
123		goto finish;
124	}
125
126	/* Start properties array. */
127	r = sd_bus_message_open_container(m, 'a', "(sv)");
128	if (r < 0) {
129		xasprintf(cause, "failed to start properties array: %s",
130		    strerror(-r));
131		goto finish;
132	}
133
134	parent_pid = getpid();
135	xasprintf(&desc, "tmux child pane %ld launched by process %ld",
136	    (long)pid, (long)parent_pid);
137	r = sd_bus_message_append(m, "(sv)", "Description", "s", desc);
138	free(desc);
139	if (r < 0) {
140		xasprintf(cause, "failed to append to properties: %s",
141		    strerror(-r));
142		goto finish;
143	}
144
145	/*
146	 * Inherit the slice from the parent process, or default to
147	 * "app-tmux.slice" if that fails.
148	 */
149	r = sd_pid_get_user_slice(parent_pid, &slice);
150	if (r < 0) {
151		slice = xstrdup("app-tmux.slice");
152	}
153	r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice);
154	free(slice);
155	if (r < 0) {
156		xasprintf(cause, "failed to append to properties: %s",
157		    strerror(-r));
158		goto finish;
159	}
160
161	/* PIDs to add to the scope: length - 1 array of uint32_t. */
162	r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, pid);
163	if (r < 0) {
164		xasprintf(cause, "failed to append to properties: %s",
165		    strerror(-r));
166		goto finish;
167	}
168
169	/* Clean up the scope even if it fails. */
170	r = sd_bus_message_append(m, "(sv)", "CollectMode", "s",
171	    "inactive-or-failed");
172	if (r < 0) {
173		xasprintf(cause, "failed to append to properties: %s",
174		    strerror(-r));
175		goto finish;
176	}
177
178	/* End properties array. */
179	r = sd_bus_message_close_container(m);
180	if (r < 0) {
181		xasprintf(cause, "failed to end properties array: %s",
182		    strerror(-r));
183		goto finish;
184	}
185
186	/* aux is currently unused and should be passed an empty array. */
187	r = sd_bus_message_append(m, "a(sa(sv))", 0);
188	if (r < 0) {
189		xasprintf(cause, "failed to append to bus message: %s",
190		    strerror(-r));
191		goto finish;
192	}
193
194	/* Call the method with a timeout of 1 second = 1e6 us. */
195	r = sd_bus_call(bus, m, 1000000, &error, &reply);
196	if (r < 0) {
197		if (error.message != NULL) {
198			/* We have a specific error message from sd-bus. */
199			xasprintf(cause, "StartTransientUnit call failed: %s",
200			    error.message);
201		} else {
202			xasprintf(cause, "StartTransientUnit call failed: %s",
203			    strerror(-r));
204		}
205		goto finish;
206	}
207
208finish:
209	sd_bus_error_free(&error);
210	sd_bus_message_unref(m);
211	sd_bus_message_unref(reply);
212	sd_bus_unref(bus);
213
214	return (r);
215}
216