1 /* $OpenBSD$ */ 2 3 /* 4 * Copyright (c) 2022 Nicholas Marriott <nicholas.marriott (at) 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 #ifndef SD_ID128_UUID_FORMAT_STR 34 #define SD_ID128_UUID_FORMAT_STR \ 35 "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x" 36 #endif 37 38 int 39 systemd_activated(void) 40 { 41 return (sd_listen_fds(0) >= 1); 42 } 43 44 int 45 systemd_create_socket(int flags, char **cause) 46 { 47 int fds; 48 int fd; 49 struct sockaddr_un sa; 50 socklen_t addrlen = sizeof sa; 51 52 fds = sd_listen_fds(0); 53 if (fds > 1) { /* too many file descriptors */ 54 errno = E2BIG; 55 goto fail; 56 } 57 58 if (fds == 1) { /* socket-activated */ 59 fd = SD_LISTEN_FDS_START; 60 if (!sd_is_socket_unix(fd, SOCK_STREAM, 1, NULL, 0)) { 61 errno = EPFNOSUPPORT; 62 goto fail; 63 } 64 if (getsockname(fd, (struct sockaddr *)&sa, &addrlen) == -1) 65 goto fail; 66 socket_path = xstrdup(sa.sun_path); 67 return (fd); 68 } 69 70 return (server_create_socket(flags, cause)); 71 72 fail: 73 if (cause != NULL) 74 xasprintf(cause, "systemd socket error (%s)", strerror(errno)); 75 return (-1); 76 } 77 78 struct systemd_job_watch { 79 const char *path; 80 int done; 81 }; 82 83 static int 84 job_removed_handler(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) 85 { 86 struct systemd_job_watch *watch = userdata; 87 const char *path = NULL; 88 uint32_t id; 89 int r; 90 91 /* This handler could be called during the sd_bus_call. */ 92 if (watch->path == NULL) 93 return 0; 94 95 r = sd_bus_message_read(m, "uo", &id, &path); 96 if (r < 0) 97 return (r); 98 99 if (strcmp(path, watch->path) == 0) 100 watch->done = 1; 101 102 return (0); 103 } 104 105 int 106 systemd_move_to_new_cgroup(char **cause) 107 { 108 sd_bus_error error = SD_BUS_ERROR_NULL; 109 sd_bus_message *m = NULL, *reply = NULL; 110 sd_bus *bus = NULL; 111 sd_bus_slot *slot = NULL; 112 char *name, *desc, *slice; 113 sd_id128_t uuid; 114 int r; 115 uint64_t elapsed_usec; 116 pid_t pid, parent_pid; 117 struct timeval start, now; 118 struct systemd_job_watch watch = {}; 119 120 gettimeofday(&start, NULL); 121 122 /* Connect to the session bus. */ 123 r = sd_bus_default_user(&bus); 124 if (r < 0) { 125 xasprintf(cause, "failed to connect to session bus: %s", 126 strerror(-r)); 127 goto finish; 128 } 129 130 /* Start watching for JobRemoved events */ 131 r = sd_bus_match_signal(bus, &slot, 132 "org.freedesktop.systemd1", 133 "/org/freedesktop/systemd1", 134 "org.freedesktop.systemd1.Manager", 135 "JobRemoved", 136 job_removed_handler, 137 &watch); 138 if (r < 0) { 139 xasprintf(cause, "failed to create match signal: %s", 140 strerror(-r)); 141 goto finish; 142 } 143 144 /* Start building the method call. */ 145 r = sd_bus_message_new_method_call(bus, &m, 146 "org.freedesktop.systemd1", 147 "/org/freedesktop/systemd1", 148 "org.freedesktop.systemd1.Manager", 149 "StartTransientUnit"); 150 if (r < 0) { 151 xasprintf(cause, "failed to create bus message: %s", 152 strerror(-r)); 153 goto finish; 154 } 155 156 /* Generate a unique name for the new scope, to avoid collisions. */ 157 r = sd_id128_randomize(&uuid); 158 if (r < 0) { 159 xasprintf(cause, "failed to generate uuid: %s", strerror(-r)); 160 goto finish; 161 } 162 xasprintf(&name, "tmux-spawn-" SD_ID128_UUID_FORMAT_STR ".scope", 163 SD_ID128_FORMAT_VAL(uuid)); 164 r = sd_bus_message_append(m, "s", name); 165 free(name); 166 if (r < 0) { 167 xasprintf(cause, "failed to append to bus message: %s", 168 strerror(-r)); 169 goto finish; 170 } 171 172 /* Mode: fail if there's a queued unit with the same name. */ 173 r = sd_bus_message_append(m, "s", "fail"); 174 if (r < 0) { 175 xasprintf(cause, "failed to append to bus message: %s", 176 strerror(-r)); 177 goto finish; 178 } 179 180 /* Start properties array. */ 181 r = sd_bus_message_open_container(m, 'a', "(sv)"); 182 if (r < 0) { 183 xasprintf(cause, "failed to start properties array: %s", 184 strerror(-r)); 185 goto finish; 186 } 187 188 pid = getpid(); 189 parent_pid = getppid(); 190 xasprintf(&desc, "tmux child pane %ld launched by process %ld", 191 (long)pid, (long)parent_pid); 192 r = sd_bus_message_append(m, "(sv)", "Description", "s", desc); 193 free(desc); 194 if (r < 0) { 195 xasprintf(cause, "failed to append to properties: %s", 196 strerror(-r)); 197 goto finish; 198 } 199 200 /* 201 * Make sure that the session shells are terminated with SIGHUP since 202 * bash and friends tend to ignore SIGTERM. 203 */ 204 r = sd_bus_message_append(m, "(sv)", "SendSIGHUP", "b", 1); 205 if (r < 0) { 206 xasprintf(cause, "failed to append to properties: %s", 207 strerror(-r)); 208 goto finish; 209 } 210 211 /* 212 * Inherit the slice from the parent process, or default to 213 * "app-tmux.slice" if that fails. 214 */ 215 r = sd_pid_get_user_slice(parent_pid, &slice); 216 if (r < 0) { 217 slice = xstrdup("app-tmux.slice"); 218 } 219 r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); 220 free(slice); 221 if (r < 0) { 222 xasprintf(cause, "failed to append to properties: %s", 223 strerror(-r)); 224 goto finish; 225 } 226 227 /* PIDs to add to the scope: length - 1 array of uint32_t. */ 228 r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, pid); 229 if (r < 0) { 230 xasprintf(cause, "failed to append to properties: %s", 231 strerror(-r)); 232 goto finish; 233 } 234 235 /* Clean up the scope even if it fails. */ 236 r = sd_bus_message_append(m, "(sv)", "CollectMode", "s", 237 "inactive-or-failed"); 238 if (r < 0) { 239 xasprintf(cause, "failed to append to properties: %s", 240 strerror(-r)); 241 goto finish; 242 } 243 244 /* End properties array. */ 245 r = sd_bus_message_close_container(m); 246 if (r < 0) { 247 xasprintf(cause, "failed to end properties array: %s", 248 strerror(-r)); 249 goto finish; 250 } 251 252 /* aux is currently unused and should be passed an empty array. */ 253 r = sd_bus_message_append(m, "a(sa(sv))", 0); 254 if (r < 0) { 255 xasprintf(cause, "failed to append to bus message: %s", 256 strerror(-r)); 257 goto finish; 258 } 259 260 /* Call the method with a timeout of 1 second = 1e6 us. */ 261 r = sd_bus_call(bus, m, 1000000, &error, &reply); 262 if (r < 0) { 263 if (error.message != NULL) { 264 /* We have a specific error message from sd-bus. */ 265 xasprintf(cause, "StartTransientUnit call failed: %s", 266 error.message); 267 } else { 268 xasprintf(cause, "StartTransientUnit call failed: %s", 269 strerror(-r)); 270 } 271 goto finish; 272 } 273 274 /* Get the job (object path) from the reply */ 275 r = sd_bus_message_read(reply, "o", &watch.path); 276 if (r < 0) { 277 xasprintf(cause, "failed to parse method reply: %s", 278 strerror(-r)); 279 goto finish; 280 } 281 282 while (!watch.done) { 283 /* Process events including callbacks. */ 284 r = sd_bus_process(bus, NULL); 285 if (r < 0) { 286 xasprintf(cause, 287 "failed waiting for cgroup allocation: %s", 288 strerror(-r)); 289 goto finish; 290 } 291 292 /* 293 * A positive return means we handled an event and should keep 294 * processing; zero indicates no events available, so wait. 295 */ 296 if (r > 0) 297 continue; 298 299 gettimeofday(&now, NULL); 300 elapsed_usec = (now.tv_sec - start.tv_sec) * 1000000 + 301 now.tv_usec - start.tv_usec; 302 303 if (elapsed_usec >= 1000000) { 304 xasprintf(cause, 305 "timeout waiting for cgroup allocation"); 306 goto finish; 307 } 308 309 r = sd_bus_wait(bus, 1000000 - elapsed_usec); 310 if (r < 0) { 311 xasprintf(cause, 312 "failed waiting for cgroup allocation: %s", 313 strerror(-r)); 314 goto finish; 315 } 316 } 317 318 finish: 319 sd_bus_error_free(&error); 320 sd_bus_message_unref(m); 321 sd_bus_message_unref(reply); 322 sd_bus_slot_unref(slot); 323 sd_bus_unref(bus); 324 325 return (r); 326 } 327