Line | Count | Source |
1 | | /* $OpenBSD: proc.c,v 1.31 2026/06/08 21:38:19 nicm Exp $ */ |
2 | | |
3 | | /* |
4 | | * Copyright (c) 2015 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/socket.h> |
21 | | #include <sys/uio.h> |
22 | | #include <sys/utsname.h> |
23 | | |
24 | | #include <errno.h> |
25 | | #include <signal.h> |
26 | | #include <stdlib.h> |
27 | | #include <string.h> |
28 | | #include <unistd.h> |
29 | | |
30 | | #if defined(HAVE_NCURSES_H) |
31 | | #include <ncurses.h> |
32 | | #endif |
33 | | |
34 | | #ifdef HAVE_JEMALLOC |
35 | | #include <jemalloc/jemalloc.h> |
36 | | #endif |
37 | | |
38 | | #include "tmux.h" |
39 | | |
40 | | struct tmuxproc { |
41 | | const char *name; |
42 | | int exit; |
43 | | |
44 | | void (*signalcb)(int); |
45 | | |
46 | | struct event ev_sigint; |
47 | | struct event ev_sighup; |
48 | | struct event ev_sigchld; |
49 | | struct event ev_sigcont; |
50 | | struct event ev_sigterm; |
51 | | struct event ev_sigusr1; |
52 | | struct event ev_sigusr2; |
53 | | struct event ev_sigwinch; |
54 | | |
55 | | TAILQ_HEAD(, tmuxpeer) peers; |
56 | | }; |
57 | | |
58 | | struct tmuxpeer { |
59 | | struct tmuxproc *parent; |
60 | | |
61 | | struct imsgbuf ibuf; |
62 | | struct event event; |
63 | | uid_t uid; |
64 | | gid_t gid; |
65 | | |
66 | | int flags; |
67 | 0 | #define PEER_BAD 0x1 |
68 | | |
69 | | void (*dispatchcb)(struct imsg *, void *); |
70 | | void *arg; |
71 | | |
72 | | TAILQ_ENTRY(tmuxpeer) entry; |
73 | | }; |
74 | | |
75 | | static int peer_check_version(struct tmuxpeer *, struct imsg *); |
76 | | static void proc_update_event(struct tmuxpeer *); |
77 | | |
78 | | static void |
79 | | proc_event_cb(__unused int fd, short events, void *arg) |
80 | 0 | { |
81 | 0 | struct tmuxpeer *peer = arg; |
82 | 0 | ssize_t n; |
83 | 0 | struct imsg imsg; |
84 | |
|
85 | 0 | if (!(peer->flags & PEER_BAD) && (events & EV_READ)) { |
86 | 0 | if (imsgbuf_read(&peer->ibuf) != 1) { |
87 | 0 | peer->dispatchcb(NULL, peer->arg); |
88 | 0 | return; |
89 | 0 | } |
90 | 0 | for (;;) { |
91 | 0 | if ((n = imsg_get(&peer->ibuf, &imsg)) == -1) { |
92 | 0 | peer->dispatchcb(NULL, peer->arg); |
93 | 0 | return; |
94 | 0 | } |
95 | 0 | if (n == 0) |
96 | 0 | break; |
97 | 0 | log_debug("peer %p message %d", peer, imsg.hdr.type); |
98 | |
|
99 | 0 | if (peer_check_version(peer, &imsg) != 0) { |
100 | 0 | imsg_free(&imsg); |
101 | 0 | break; |
102 | 0 | } |
103 | | |
104 | 0 | peer->dispatchcb(&imsg, peer->arg); |
105 | 0 | imsg_free(&imsg); |
106 | 0 | } |
107 | 0 | } |
108 | | |
109 | 0 | if (events & EV_WRITE) { |
110 | 0 | if (imsgbuf_write(&peer->ibuf) == -1) { |
111 | 0 | peer->dispatchcb(NULL, peer->arg); |
112 | 0 | return; |
113 | 0 | } |
114 | 0 | } |
115 | | |
116 | 0 | if ((peer->flags & PEER_BAD) && imsgbuf_queuelen(&peer->ibuf) == 0) { |
117 | 0 | peer->dispatchcb(NULL, peer->arg); |
118 | 0 | return; |
119 | 0 | } |
120 | | |
121 | 0 | proc_update_event(peer); |
122 | 0 | } |
123 | | |
124 | | static void |
125 | | proc_signal_cb(int signo, __unused short events, void *arg) |
126 | 0 | { |
127 | 0 | struct tmuxproc *tp = arg; |
128 | |
|
129 | 0 | tp->signalcb(signo); |
130 | 0 | } |
131 | | |
132 | | static int |
133 | | peer_check_version(struct tmuxpeer *peer, struct imsg *imsg) |
134 | 0 | { |
135 | 0 | int version; |
136 | |
|
137 | 0 | version = imsg->hdr.peerid & 0xff; |
138 | 0 | if (imsg->hdr.type != MSG_VERSION && version != PROTOCOL_VERSION) { |
139 | 0 | log_debug("peer %p bad version %d", peer, version); |
140 | |
|
141 | 0 | proc_send(peer, MSG_VERSION, -1, NULL, 0); |
142 | 0 | peer->flags |= PEER_BAD; |
143 | |
|
144 | 0 | return (-1); |
145 | 0 | } |
146 | 0 | return (0); |
147 | 0 | } |
148 | | |
149 | | static void |
150 | | proc_update_event(struct tmuxpeer *peer) |
151 | 0 | { |
152 | 0 | short events; |
153 | |
|
154 | 0 | event_del(&peer->event); |
155 | |
|
156 | 0 | events = EV_READ; |
157 | 0 | if (imsgbuf_queuelen(&peer->ibuf) > 0) |
158 | 0 | events |= EV_WRITE; |
159 | 0 | event_set(&peer->event, peer->ibuf.fd, events, proc_event_cb, peer); |
160 | |
|
161 | 0 | event_add(&peer->event, NULL); |
162 | 0 | } |
163 | | |
164 | | int |
165 | | proc_send(struct tmuxpeer *peer, enum msgtype type, int fd, const void *buf, |
166 | | size_t len) |
167 | 0 | { |
168 | 0 | struct imsgbuf *ibuf = &peer->ibuf; |
169 | 0 | void *vp = (void *)buf; |
170 | 0 | int retval; |
171 | |
|
172 | 0 | if (peer->flags & PEER_BAD) |
173 | 0 | return (-1); |
174 | 0 | log_debug("sending message %d to peer %p (%zu bytes)", type, peer, len); |
175 | |
|
176 | 0 | retval = imsg_compose(ibuf, type, PROTOCOL_VERSION, -1, fd, vp, len); |
177 | 0 | if (retval != 1) |
178 | 0 | return (-1); |
179 | 0 | proc_update_event(peer); |
180 | 0 | return (0); |
181 | 0 | } |
182 | | |
183 | | struct tmuxproc * |
184 | | proc_start(const char *name) |
185 | 0 | { |
186 | 0 | struct tmuxproc *tp; |
187 | 0 | struct utsname u; |
188 | | #ifdef HAVE_JEMALLOC |
189 | | const char *version; |
190 | | size_t size = sizeof version; |
191 | | #endif |
192 | |
|
193 | 0 | log_open(name); |
194 | 0 | setproctitle("%s (%s)", name, socket_path); |
195 | |
|
196 | 0 | if (uname(&u) < 0) |
197 | 0 | memset(&u, 0, sizeof u); |
198 | |
|
199 | 0 | log_debug("%s started (%ld): version %s, socket %s, protocol %d", name, |
200 | 0 | (long)getpid(), getversion(), socket_path, PROTOCOL_VERSION); |
201 | 0 | log_debug("on %s %s %s", u.sysname, u.release, u.version); |
202 | 0 | log_debug("using libevent %s %s", event_get_version(), event_get_method()); |
203 | | #ifdef HAVE_UTF8PROC |
204 | | log_debug("using utf8proc %s", utf8proc_version()); |
205 | | #endif |
206 | | #ifdef HAVE_JEMALLOC |
207 | | if (mallctl("version", &version, &size, NULL, 0) != 0) |
208 | | version = "(unknown version)"; |
209 | | log_debug("using jemalloc %s", version); |
210 | | #endif |
211 | 0 | #ifdef NCURSES_VERSION |
212 | 0 | log_debug("using ncurses %s %06u", NCURSES_VERSION, NCURSES_VERSION_PATCH); |
213 | 0 | #endif |
214 | |
|
215 | 0 | tp = xcalloc(1, sizeof *tp); |
216 | 0 | tp->name = xstrdup(name); |
217 | 0 | TAILQ_INIT(&tp->peers); |
218 | |
|
219 | 0 | return (tp); |
220 | 0 | } |
221 | | |
222 | | void |
223 | | proc_loop(struct tmuxproc *tp, int (*loopcb)(void)) |
224 | 0 | { |
225 | 0 | log_debug("%s loop enter", tp->name); |
226 | 0 | do |
227 | 0 | event_loop(EVLOOP_ONCE); |
228 | 0 | while (!tp->exit && (loopcb == NULL || !loopcb ())); |
229 | 0 | log_debug("%s loop exit", tp->name); |
230 | 0 | } |
231 | | |
232 | | void |
233 | | proc_exit(struct tmuxproc *tp) |
234 | 0 | { |
235 | 0 | struct tmuxpeer *peer; |
236 | |
|
237 | 0 | TAILQ_FOREACH(peer, &tp->peers, entry) |
238 | 0 | imsgbuf_flush(&peer->ibuf); |
239 | 0 | tp->exit = 1; |
240 | 0 | } |
241 | | |
242 | | void |
243 | | proc_set_signals(struct tmuxproc *tp, void (*signalcb)(int)) |
244 | 0 | { |
245 | 0 | struct sigaction sa; |
246 | |
|
247 | 0 | tp->signalcb = signalcb; |
248 | |
|
249 | 0 | memset(&sa, 0, sizeof sa); |
250 | 0 | sigemptyset(&sa.sa_mask); |
251 | 0 | sa.sa_flags = SA_RESTART; |
252 | 0 | sa.sa_handler = SIG_IGN; |
253 | |
|
254 | 0 | sigaction(SIGPIPE, &sa, NULL); |
255 | 0 | sigaction(SIGTSTP, &sa, NULL); |
256 | 0 | sigaction(SIGTTIN, &sa, NULL); |
257 | 0 | sigaction(SIGTTOU, &sa, NULL); |
258 | 0 | sigaction(SIGQUIT, &sa, NULL); |
259 | |
|
260 | 0 | signal_set(&tp->ev_sigint, SIGINT, proc_signal_cb, tp); |
261 | 0 | signal_add(&tp->ev_sigint, NULL); |
262 | 0 | signal_set(&tp->ev_sighup, SIGHUP, proc_signal_cb, tp); |
263 | 0 | signal_add(&tp->ev_sighup, NULL); |
264 | 0 | signal_set(&tp->ev_sigchld, SIGCHLD, proc_signal_cb, tp); |
265 | 0 | signal_add(&tp->ev_sigchld, NULL); |
266 | 0 | signal_set(&tp->ev_sigcont, SIGCONT, proc_signal_cb, tp); |
267 | 0 | signal_add(&tp->ev_sigcont, NULL); |
268 | 0 | signal_set(&tp->ev_sigterm, SIGTERM, proc_signal_cb, tp); |
269 | 0 | signal_add(&tp->ev_sigterm, NULL); |
270 | 0 | signal_set(&tp->ev_sigusr1, SIGUSR1, proc_signal_cb, tp); |
271 | 0 | signal_add(&tp->ev_sigusr1, NULL); |
272 | 0 | signal_set(&tp->ev_sigusr2, SIGUSR2, proc_signal_cb, tp); |
273 | 0 | signal_add(&tp->ev_sigusr2, NULL); |
274 | 0 | signal_set(&tp->ev_sigwinch, SIGWINCH, proc_signal_cb, tp); |
275 | 0 | signal_add(&tp->ev_sigwinch, NULL); |
276 | 0 | } |
277 | | |
278 | | void |
279 | | proc_clear_signals(struct tmuxproc *tp, int defaults) |
280 | 0 | { |
281 | 0 | struct sigaction sa; |
282 | |
|
283 | 0 | memset(&sa, 0, sizeof sa); |
284 | 0 | sigemptyset(&sa.sa_mask); |
285 | 0 | sa.sa_flags = SA_RESTART; |
286 | 0 | sa.sa_handler = SIG_DFL; |
287 | |
|
288 | 0 | sigaction(SIGPIPE, &sa, NULL); |
289 | 0 | sigaction(SIGTSTP, &sa, NULL); |
290 | |
|
291 | 0 | signal_del(&tp->ev_sigint); |
292 | 0 | signal_del(&tp->ev_sighup); |
293 | 0 | signal_del(&tp->ev_sigchld); |
294 | 0 | signal_del(&tp->ev_sigcont); |
295 | 0 | signal_del(&tp->ev_sigterm); |
296 | 0 | signal_del(&tp->ev_sigusr1); |
297 | 0 | signal_del(&tp->ev_sigusr2); |
298 | 0 | signal_del(&tp->ev_sigwinch); |
299 | |
|
300 | 0 | if (defaults) { |
301 | 0 | sigaction(SIGINT, &sa, NULL); |
302 | 0 | sigaction(SIGQUIT, &sa, NULL); |
303 | 0 | sigaction(SIGHUP, &sa, NULL); |
304 | 0 | sigaction(SIGCHLD, &sa, NULL); |
305 | 0 | sigaction(SIGCONT, &sa, NULL); |
306 | 0 | sigaction(SIGTERM, &sa, NULL); |
307 | 0 | sigaction(SIGUSR1, &sa, NULL); |
308 | 0 | sigaction(SIGUSR2, &sa, NULL); |
309 | 0 | sigaction(SIGWINCH, &sa, NULL); |
310 | 0 | } |
311 | 0 | } |
312 | | |
313 | | struct tmuxpeer * |
314 | | proc_add_peer(struct tmuxproc *tp, int fd, |
315 | | void (*dispatchcb)(struct imsg *, void *), void *arg) |
316 | 0 | { |
317 | 0 | struct tmuxpeer *peer; |
318 | |
|
319 | 0 | peer = xcalloc(1, sizeof *peer); |
320 | 0 | peer->parent = tp; |
321 | |
|
322 | 0 | peer->dispatchcb = dispatchcb; |
323 | 0 | peer->arg = arg; |
324 | |
|
325 | 0 | if (imsgbuf_init(&peer->ibuf, fd) == -1) |
326 | 0 | fatal("imsgbuf_init"); |
327 | 0 | imsgbuf_allow_fdpass(&peer->ibuf); |
328 | 0 | event_set(&peer->event, fd, EV_READ, proc_event_cb, peer); |
329 | |
|
330 | 0 | if (getpeereid(fd, &peer->uid, &peer->gid) != 0) { |
331 | 0 | peer->uid = (uid_t)-1; |
332 | 0 | peer->gid = (gid_t)-1; |
333 | 0 | } |
334 | |
|
335 | 0 | log_debug("add peer %p: %d (%p)", peer, fd, arg); |
336 | 0 | TAILQ_INSERT_TAIL(&tp->peers, peer, entry); |
337 | |
|
338 | 0 | proc_update_event(peer); |
339 | 0 | return (peer); |
340 | 0 | } |
341 | | |
342 | | void |
343 | | proc_remove_peer(struct tmuxpeer *peer) |
344 | 0 | { |
345 | 0 | TAILQ_REMOVE(&peer->parent->peers, peer, entry); |
346 | 0 | log_debug("remove peer %p", peer); |
347 | |
|
348 | 0 | event_del(&peer->event); |
349 | 0 | imsgbuf_clear(&peer->ibuf); |
350 | |
|
351 | 0 | close(peer->ibuf.fd); |
352 | 0 | free(peer); |
353 | 0 | } |
354 | | |
355 | | void |
356 | | proc_kill_peer(struct tmuxpeer *peer) |
357 | 0 | { |
358 | 0 | peer->flags |= PEER_BAD; |
359 | 0 | } |
360 | | |
361 | | void |
362 | | proc_flush_peer(struct tmuxpeer *peer) |
363 | 0 | { |
364 | 0 | imsgbuf_flush(&peer->ibuf); |
365 | 0 | } |
366 | | |
367 | | void |
368 | | proc_toggle_log(struct tmuxproc *tp) |
369 | 0 | { |
370 | 0 | log_toggle(tp->name); |
371 | 0 | } |
372 | | |
373 | | pid_t |
374 | | proc_fork_and_daemon(int *fd) |
375 | 0 | { |
376 | 0 | pid_t pid; |
377 | 0 | int pair[2]; |
378 | |
|
379 | 0 | if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) |
380 | 0 | fatal("socketpair failed"); |
381 | 0 | switch (pid = fork()) { |
382 | 0 | case -1: |
383 | 0 | fatal("fork failed"); |
384 | 0 | case 0: |
385 | 0 | close(pair[0]); |
386 | 0 | *fd = pair[1]; |
387 | 0 | if (daemon(1, 0) != 0) |
388 | 0 | fatal("daemon failed"); |
389 | 0 | return (0); |
390 | 0 | default: |
391 | 0 | close(pair[1]); |
392 | 0 | *fd = pair[0]; |
393 | 0 | return (pid); |
394 | 0 | } |
395 | 0 | } |
396 | | |
397 | | uid_t |
398 | | proc_get_peer_uid(struct tmuxpeer *peer) |
399 | 0 | { |
400 | 0 | return (peer->uid); |
401 | 0 | } |
402 | | |
403 | | gid_t |
404 | | proc_get_peer_gid(struct tmuxpeer *peer) |
405 | 0 | { |
406 | 0 | return (peer->gid); |
407 | 0 | } |