1*3d40d63aSnicm /* $OpenBSD: server.c,v 1.208 2025/01/01 15:17:36 nicm Exp $ */
2311827fbSnicm
3311827fbSnicm /*
498ca8272Snicm * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5311827fbSnicm *
6311827fbSnicm * Permission to use, copy, modify, and distribute this software for any
7311827fbSnicm * purpose with or without fee is hereby granted, provided that the above
8311827fbSnicm * copyright notice and this permission notice appear in all copies.
9311827fbSnicm *
10311827fbSnicm * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11311827fbSnicm * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12311827fbSnicm * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13311827fbSnicm * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14311827fbSnicm * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15311827fbSnicm * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16311827fbSnicm * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17311827fbSnicm */
18311827fbSnicm
19311827fbSnicm #include <sys/types.h>
20311827fbSnicm #include <sys/ioctl.h>
21311827fbSnicm #include <sys/socket.h>
22311827fbSnicm #include <sys/stat.h>
23311827fbSnicm #include <sys/un.h>
24311827fbSnicm #include <sys/wait.h>
25311827fbSnicm
26311827fbSnicm #include <errno.h>
277724d0b0Snicm #include <event.h>
28311827fbSnicm #include <fcntl.h>
29a575a2dfSnicm #include <paths.h>
30311827fbSnicm #include <signal.h>
31311827fbSnicm #include <stdio.h>
32311827fbSnicm #include <stdlib.h>
33311827fbSnicm #include <string.h>
34311827fbSnicm #include <termios.h>
35311827fbSnicm #include <time.h>
36311827fbSnicm #include <unistd.h>
37311827fbSnicm
38311827fbSnicm #include "tmux.h"
39311827fbSnicm
40311827fbSnicm /*
41311827fbSnicm * Main server functions.
42311827fbSnicm */
43311827fbSnicm
44311827fbSnicm struct clients clients;
45311827fbSnicm
465b8ac713Snicm struct tmuxproc *server_proc;
47432eaf59Snicm static int server_fd = -1;
48b3e4eeb8Snicm static uint64_t server_client_flags;
499883b791Snicm static int server_exit;
509883b791Snicm static struct event server_ev_accept;
5197887b4cSnicm static struct event server_ev_tidy;
52b7fa064bSnicm
537519eda3Snicm struct cmd_find_state marked_pane;
542986c025Snicm
550687354cSnicm static u_int message_next;
560687354cSnicm struct message_list message_log;
570687354cSnicm
58a711f92aSnicm time_t current_time;
59a711f92aSnicm
609883b791Snicm static int server_loop(void);
619883b791Snicm static void server_send_exit(void);
629883b791Snicm static void server_accept(int, short, void *);
639883b791Snicm static void server_signal(int);
649883b791Snicm static void server_child_signal(void);
659883b791Snicm static void server_child_exited(pid_t, int);
669883b791Snicm static void server_child_stopped(pid_t, int);
67b7fa064bSnicm
682986c025Snicm /* Set marked pane. */
692986c025Snicm void
server_set_marked(struct session * s,struct winlink * wl,struct window_pane * wp)702986c025Snicm server_set_marked(struct session *s, struct winlink *wl, struct window_pane *wp)
712986c025Snicm {
7293e732aaSnicm cmd_find_clear_state(&marked_pane, 0);
737519eda3Snicm marked_pane.s = s;
747519eda3Snicm marked_pane.wl = wl;
757519eda3Snicm marked_pane.w = wl->window;
767519eda3Snicm marked_pane.wp = wp;
772986c025Snicm }
782986c025Snicm
792986c025Snicm /* Clear marked pane. */
802986c025Snicm void
server_clear_marked(void)812986c025Snicm server_clear_marked(void)
822986c025Snicm {
8393e732aaSnicm cmd_find_clear_state(&marked_pane, 0);
842986c025Snicm }
852986c025Snicm
862986c025Snicm /* Is this the marked pane? */
872986c025Snicm int
server_is_marked(struct session * s,struct winlink * wl,struct window_pane * wp)882986c025Snicm server_is_marked(struct session *s, struct winlink *wl, struct window_pane *wp)
892986c025Snicm {
902986c025Snicm if (s == NULL || wl == NULL || wp == NULL)
912986c025Snicm return (0);
927519eda3Snicm if (marked_pane.s != s || marked_pane.wl != wl)
932986c025Snicm return (0);
947519eda3Snicm if (marked_pane.wp != wp)
952986c025Snicm return (0);
962986c025Snicm return (server_check_marked());
972986c025Snicm }
982986c025Snicm
992986c025Snicm /* Check if the marked pane is still valid. */
1002986c025Snicm int
server_check_marked(void)1012986c025Snicm server_check_marked(void)
1022986c025Snicm {
1037519eda3Snicm return (cmd_find_valid_state(&marked_pane));
1042986c025Snicm }
1052986c025Snicm
10689654e7cSnicm /* Create server socket. */
107ab0a70a7Snicm int
server_create_socket(uint64_t flags,char ** cause)108ab0a70a7Snicm server_create_socket(uint64_t flags, char **cause)
109311827fbSnicm {
11089654e7cSnicm struct sockaddr_un sa;
11189654e7cSnicm size_t size;
11289654e7cSnicm mode_t mask;
113039206c6Snicm int fd, saved_errno;
11489654e7cSnicm
11589654e7cSnicm memset(&sa, 0, sizeof sa);
11689654e7cSnicm sa.sun_family = AF_UNIX;
11789654e7cSnicm size = strlcpy(sa.sun_path, socket_path, sizeof sa.sun_path);
11889654e7cSnicm if (size >= sizeof sa.sun_path) {
11989654e7cSnicm errno = ENAMETOOLONG;
120039206c6Snicm goto fail;
12189654e7cSnicm }
12289654e7cSnicm unlink(sa.sun_path);
12389654e7cSnicm
12489654e7cSnicm if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
125039206c6Snicm goto fail;
12689654e7cSnicm
127b6d7e0c7Snicm if (flags & CLIENT_DEFAULTSOCKET)
1287394227cSnicm mask = umask(S_IXUSR|S_IXGRP|S_IRWXO);
129b6d7e0c7Snicm else
130b6d7e0c7Snicm mask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
131039206c6Snicm if (bind(fd, (struct sockaddr *)&sa, sizeof sa) == -1) {
132039206c6Snicm saved_errno = errno;
133fb807b18Snicm close(fd);
134039206c6Snicm errno = saved_errno;
135039206c6Snicm goto fail;
136fb807b18Snicm }
13789654e7cSnicm umask(mask);
13889654e7cSnicm
139fb807b18Snicm if (listen(fd, 128) == -1) {
140039206c6Snicm saved_errno = errno;
141fb807b18Snicm close(fd);
142039206c6Snicm errno = saved_errno;
143039206c6Snicm goto fail;
144fb807b18Snicm }
145a0ee4254Snicm setblocking(fd, 0);
146311827fbSnicm
14789654e7cSnicm return (fd);
148039206c6Snicm
149039206c6Snicm fail:
150039206c6Snicm if (cause != NULL) {
151039206c6Snicm xasprintf(cause, "error creating %s (%s)", socket_path,
152039206c6Snicm strerror(errno));
153039206c6Snicm }
154039206c6Snicm return (-1);
155039206c6Snicm }
156039206c6Snicm
15797887b4cSnicm /* Tidy up every hour. */
15897887b4cSnicm static void
server_tidy_event(__unused int fd,__unused short events,__unused void * data)15997887b4cSnicm server_tidy_event(__unused int fd, __unused short events, __unused void *data)
16097887b4cSnicm {
16197887b4cSnicm struct timeval tv = { .tv_sec = 3600 };
16297887b4cSnicm uint64_t t = get_timer();
16397887b4cSnicm
16497887b4cSnicm format_tidy_jobs();
16597887b4cSnicm
1662bd728e0Snicm log_debug("%s: took %llu milliseconds", __func__,
1672bd728e0Snicm (unsigned long long)(get_timer() - t));
16897887b4cSnicm evtimer_add(&server_ev_tidy, &tv);
16997887b4cSnicm }
17097887b4cSnicm
171311827fbSnicm /* Fork new server. */
172311827fbSnicm int
server_start(struct tmuxproc * client,uint64_t flags,struct event_base * base,int lockfd,char * lockfile)173ab0a70a7Snicm server_start(struct tmuxproc *client, uint64_t flags, struct event_base *base,
174b6d7e0c7Snicm int lockfd, char *lockfile)
175311827fbSnicm {
176ab26eabfSnicm int fd;
177189d1393Snicm sigset_t set, oldset;
17810e1651aSnicm struct client *c = NULL;
179039206c6Snicm char *cause = NULL;
18097887b4cSnicm struct timeval tv = { .tv_sec = 3600 };
181311827fbSnicm
182189d1393Snicm sigfillset(&set);
183189d1393Snicm sigprocmask(SIG_BLOCK, &set, &oldset);
1849e0637eaSnicm
1859e0637eaSnicm if (~flags & CLIENT_NOFORK) {
186ab26eabfSnicm if (proc_fork_and_daemon(&fd) != 0) {
187189d1393Snicm sigprocmask(SIG_SETMASK, &oldset, NULL);
188ab26eabfSnicm return (fd);
189311827fbSnicm }
1909e0637eaSnicm }
19189b52a5bSnicm proc_clear_signals(client, 0);
192ab26eabfSnicm server_client_flags = flags;
1939e0637eaSnicm
194c37a9299Snicm if (event_reinit(base) != 0)
195c37a9299Snicm fatalx("event_reinit failed");
196c37a9299Snicm server_proc = proc_start("server");
1979e0637eaSnicm
198c37a9299Snicm proc_set_signals(server_proc, server_signal);
199189d1393Snicm sigprocmask(SIG_SETMASK, &oldset, NULL);
200c37a9299Snicm
20179c9b201Snicm if (log_get_level() > 1)
2026d2e9b46Snicm tty_create_log();
203cb19d99cSnicm if (pledge("stdio rpath wpath cpath fattr unix getpw recvfd proc exec "
204cb19d99cSnicm "tty ps", NULL) != 0)
2054e7a7adeSnicm fatal("pledge failed");
2064e7a7adeSnicm
2079265d1acSnicm input_key_build();
208*3d40d63aSnicm utf8_update_width_cache();
209ae69181dSnicm RB_INIT(&windows);
2103e0f2533Snicm RB_INIT(&all_window_panes);
21182873134Snicm TAILQ_INIT(&clients);
21259996dc3Snicm RB_INIT(&sessions);
213311827fbSnicm key_bindings_init();
2140687354cSnicm TAILQ_INIT(&message_log);
2158c1ade38Snicm gettimeofday(&start_time, NULL);
216311827fbSnicm
217b6d7e0c7Snicm server_fd = server_create_socket(flags, &cause);
218039206c6Snicm if (server_fd != -1)
219d53cf33dSnicm server_update_socket();
2209e0637eaSnicm if (~flags & CLIENT_NOFORK)
221ab26eabfSnicm c = server_client_create(fd);
2229e0637eaSnicm else
2239e0637eaSnicm options_set_number(global_options, "exit-empty", 0);
224311827fbSnicm
2255c705c36Snicm if (lockfd >= 0) {
226549634dfSnicm unlink(lockfile);
2277d053cf9Snicm free(lockfile);
228549634dfSnicm close(lockfd);
2295c705c36Snicm }
230549634dfSnicm
231039206c6Snicm if (cause != NULL) {
23210e1651aSnicm if (c != NULL) {
23387d557e0Snicm c->exit_message = cause;
234039206c6Snicm c->flags |= CLIENT_EXIT;
235b43857e5Snicm } else {
236b43857e5Snicm fprintf(stderr, "%s\n", cause);
237b43857e5Snicm exit(1);
238b43857e5Snicm }
23910e1651aSnicm }
2407724d0b0Snicm
24197887b4cSnicm evtimer_set(&server_ev_tidy, server_tidy_event, NULL);
24297887b4cSnicm evtimer_add(&server_ev_tidy, &tv);
24397887b4cSnicm
2448ab000fcSnicm server_acl_init();
2458ab000fcSnicm
246432eaf59Snicm server_add_accept(0);
2475b8ac713Snicm proc_loop(server_proc, server_loop);
2481a432fefSnicm
2499430fe9eSnicm job_kill_all();
250179ef399Snicm status_prompt_save_history();
2519430fe9eSnicm
2527724d0b0Snicm exit(0);
253311827fbSnicm }
254311827fbSnicm
2555b8ac713Snicm /* Server loop callback. */
2569883b791Snicm static int
server_loop(void)2577724d0b0Snicm server_loop(void)
258311827fbSnicm {
2595b8ac713Snicm struct client *c;
260765b9a58Snicm u_int items;
261765b9a58Snicm
262a711f92aSnicm current_time = time(NULL);
263a711f92aSnicm
264765b9a58Snicm do {
265765b9a58Snicm items = cmdq_next(NULL);
2666a2dc005Snicm TAILQ_FOREACH(c, &clients, entry) {
2676a2dc005Snicm if (c->flags & CLIENT_IDENTIFIED)
268765b9a58Snicm items += cmdq_next(c);
2696a2dc005Snicm }
270765b9a58Snicm } while (items != 0);
271311827fbSnicm
27289654e7cSnicm server_client_loop();
27330da262fSnicm
2740d4a1e1fSnicm if (!options_get_number(global_options, "exit-empty") && !server_exit)
2750d4a1e1fSnicm return (0);
2760d4a1e1fSnicm
277d89252e5Snicm if (!options_get_number(global_options, "exit-unattached")) {
27859996dc3Snicm if (!RB_EMPTY(&sessions))
27930da262fSnicm return (0);
28030da262fSnicm }
2817d5939e2Snicm
28282873134Snicm TAILQ_FOREACH(c, &clients, entry) {
28382873134Snicm if (c->session != NULL)
2847d5939e2Snicm return (0);
2857d5939e2Snicm }
2867d5939e2Snicm
2877d5939e2Snicm /*
2887d5939e2Snicm * No attached clients therefore want to exit - flush any waiting
2897d5939e2Snicm * clients but don't actually exit until they've gone.
2907d5939e2Snicm */
2917d5939e2Snicm cmd_wait_for_flush();
29282873134Snicm if (!TAILQ_EMPTY(&clients))
29330da262fSnicm return (0);
2947d5939e2Snicm
2959430fe9eSnicm if (job_still_running())
29649505a58Snicm return (0);
29749505a58Snicm
29830da262fSnicm return (1);
299311827fbSnicm }
300311827fbSnicm
3014d9325ceSnicm /* Exit the server by killing all clients and windows. */
3029883b791Snicm static void
server_send_exit(void)3034d9325ceSnicm server_send_exit(void)
304311827fbSnicm {
30582873134Snicm struct client *c, *c1;
30682873134Snicm struct session *s, *s1;
307311827fbSnicm
3087d5939e2Snicm cmd_wait_for_flush();
3097d5939e2Snicm
31082873134Snicm TAILQ_FOREACH_SAFE(c, &clients, entry, c1) {
3115b8ac713Snicm if (c->flags & CLIENT_SUSPENDED)
3127724d0b0Snicm server_client_lost(c);
31349505a58Snicm else {
314a34cf9c8Snicm c->flags |= CLIENT_EXIT;
315a34cf9c8Snicm c->exit_type = CLIENT_EXIT_SHUTDOWN;
31649505a58Snicm }
3177724d0b0Snicm c->session = NULL;
3188148c7feSnicm }
319311827fbSnicm
32082873134Snicm RB_FOREACH_SAFE(s, sessions, &sessions, s1)
321c26c4f79Snicm session_destroy(s, 1, __func__);
322311827fbSnicm }
323311827fbSnicm
3247724d0b0Snicm /* Update socket execute permissions based on whether sessions are attached. */
32537dfd443Snicm void
server_update_socket(void)3267724d0b0Snicm server_update_socket(void)
3277724d0b0Snicm {
3287724d0b0Snicm struct session *s;
3297724d0b0Snicm static int last = -1;
3306d9987f5Snicm int n, mode;
3316d9987f5Snicm struct stat sb;
3327724d0b0Snicm
3337724d0b0Snicm n = 0;
33459996dc3Snicm RB_FOREACH(s, sessions, &sessions) {
335647c5c18Snicm if (s->attached != 0) {
3367724d0b0Snicm n++;
3377724d0b0Snicm break;
3387724d0b0Snicm }
3397724d0b0Snicm }
3407724d0b0Snicm
3417724d0b0Snicm if (n != last) {
3427724d0b0Snicm last = n;
3436d9987f5Snicm
3446d9987f5Snicm if (stat(socket_path, &sb) != 0)
3456d9987f5Snicm return;
346d61e0664Ssemarie mode = sb.st_mode & ACCESSPERMS;
3476d9987f5Snicm if (n != 0) {
3486d9987f5Snicm if (mode & S_IRUSR)
3496d9987f5Snicm mode |= S_IXUSR;
3506d9987f5Snicm if (mode & S_IRGRP)
3516d9987f5Snicm mode |= S_IXGRP;
3526d9987f5Snicm if (mode & S_IROTH)
3536d9987f5Snicm mode |= S_IXOTH;
3546d9987f5Snicm } else
3556d9987f5Snicm mode &= ~(S_IXUSR|S_IXGRP|S_IXOTH);
3566d9987f5Snicm chmod(socket_path, mode);
3577724d0b0Snicm }
3587724d0b0Snicm }
3597724d0b0Snicm
3607724d0b0Snicm /* Callback for server socket. */
3619883b791Snicm static void
server_accept(int fd,short events,__unused void * data)362d0e2e7f1Snicm server_accept(int fd, short events, __unused void *data)
3637724d0b0Snicm {
3647724d0b0Snicm struct sockaddr_storage sa;
3657724d0b0Snicm socklen_t slen = sizeof sa;
3667724d0b0Snicm int newfd;
3678ab000fcSnicm struct client *c;
3687724d0b0Snicm
369a06e463cSnicm server_add_accept(0);
3707724d0b0Snicm if (!(events & EV_READ))
3717724d0b0Snicm return;
3727724d0b0Snicm
3737724d0b0Snicm newfd = accept(fd, (struct sockaddr *) &sa, &slen);
3747724d0b0Snicm if (newfd == -1) {
3757724d0b0Snicm if (errno == EAGAIN || errno == EINTR || errno == ECONNABORTED)
3767724d0b0Snicm return;
377a06e463cSnicm if (errno == ENFILE || errno == EMFILE) {
378a06e463cSnicm /* Delete and don't try again for 1 second. */
379a06e463cSnicm server_add_accept(1);
380a06e463cSnicm return;
381a06e463cSnicm }
3827724d0b0Snicm fatal("accept failed");
3837724d0b0Snicm }
3848ab000fcSnicm
3854d9325ceSnicm if (server_exit) {
3867724d0b0Snicm close(newfd);
3877724d0b0Snicm return;
3887724d0b0Snicm }
3898ab000fcSnicm c = server_client_create(newfd);
3908ab000fcSnicm if (!server_acl_join(c)) {
3918ab000fcSnicm c->exit_message = xstrdup("access not allowed");
3928ab000fcSnicm c->flags |= CLIENT_EXIT;
3938ab000fcSnicm }
3947724d0b0Snicm }
3957724d0b0Snicm
396a06e463cSnicm /*
397a06e463cSnicm * Add accept event. If timeout is nonzero, add as a timeout instead of a read
398a06e463cSnicm * event - used to backoff when running out of file descriptors.
399a06e463cSnicm */
400a06e463cSnicm void
server_add_accept(int timeout)401a06e463cSnicm server_add_accept(int timeout)
402a06e463cSnicm {
403a06e463cSnicm struct timeval tv = { timeout, 0 };
404a06e463cSnicm
405432eaf59Snicm if (server_fd == -1)
406432eaf59Snicm return;
407432eaf59Snicm
408a06e463cSnicm if (event_initialized(&server_ev_accept))
409a06e463cSnicm event_del(&server_ev_accept);
410a06e463cSnicm
411a06e463cSnicm if (timeout == 0) {
4125b8ac713Snicm event_set(&server_ev_accept, server_fd, EV_READ, server_accept,
4135b8ac713Snicm NULL);
414a06e463cSnicm event_add(&server_ev_accept, NULL);
415a06e463cSnicm } else {
4165b8ac713Snicm event_set(&server_ev_accept, server_fd, EV_TIMEOUT,
4175b8ac713Snicm server_accept, NULL);
418a06e463cSnicm event_add(&server_ev_accept, &tv);
419a06e463cSnicm }
420a06e463cSnicm }
421a06e463cSnicm
4227724d0b0Snicm /* Signal handler. */
4239883b791Snicm static void
server_signal(int sig)4245b8ac713Snicm server_signal(int sig)
4257724d0b0Snicm {
426d53cf33dSnicm int fd;
4275235cd7fSnicm
428bc4816c6Snicm log_debug("%s: %s", __func__, strsignal(sig));
4297724d0b0Snicm switch (sig) {
4309e0637eaSnicm case SIGINT:
4317724d0b0Snicm case SIGTERM:
4324d9325ceSnicm server_exit = 1;
4334d9325ceSnicm server_send_exit();
4347724d0b0Snicm break;
4357724d0b0Snicm case SIGCHLD:
4367724d0b0Snicm server_child_signal();
4377724d0b0Snicm break;
4387724d0b0Snicm case SIGUSR1:
4397724d0b0Snicm event_del(&server_ev_accept);
440b6d7e0c7Snicm fd = server_create_socket(server_client_flags, NULL);
441d53cf33dSnicm if (fd != -1) {
4427724d0b0Snicm close(server_fd);
443d53cf33dSnicm server_fd = fd;
444d53cf33dSnicm server_update_socket();
445d53cf33dSnicm }
446a06e463cSnicm server_add_accept(0);
4477724d0b0Snicm break;
44879c9b201Snicm case SIGUSR2:
44979c9b201Snicm proc_toggle_log(server_proc);
45079c9b201Snicm break;
4517724d0b0Snicm }
4527724d0b0Snicm }
4537724d0b0Snicm
4547724d0b0Snicm /* Handle SIGCHLD. */
4559883b791Snicm static void
server_child_signal(void)4567724d0b0Snicm server_child_signal(void)
4577724d0b0Snicm {
4587724d0b0Snicm int status;
4597724d0b0Snicm pid_t pid;
4607724d0b0Snicm
4617724d0b0Snicm for (;;) {
4627724d0b0Snicm switch (pid = waitpid(WAIT_ANY, &status, WNOHANG|WUNTRACED)) {
4637724d0b0Snicm case -1:
4647724d0b0Snicm if (errno == ECHILD)
4657724d0b0Snicm return;
4667724d0b0Snicm fatal("waitpid failed");
4677724d0b0Snicm case 0:
4687724d0b0Snicm return;
4697724d0b0Snicm }
4707724d0b0Snicm if (WIFSTOPPED(status))
4717724d0b0Snicm server_child_stopped(pid, status);
472a236b623Snicm else if (WIFEXITED(status) || WIFSIGNALED(status))
4737724d0b0Snicm server_child_exited(pid, status);
4747724d0b0Snicm }
4757724d0b0Snicm }
4767724d0b0Snicm
4777724d0b0Snicm /* Handle exited children. */
4789883b791Snicm static void
server_child_exited(pid_t pid,int status)4797724d0b0Snicm server_child_exited(pid_t pid, int status)
480311827fbSnicm {
481ae69181dSnicm struct window *w, *w1;
48289654e7cSnicm struct window_pane *wp;
4837724d0b0Snicm
484ae69181dSnicm RB_FOREACH_SAFE(w, windows, &windows, w1) {
4857724d0b0Snicm TAILQ_FOREACH(wp, &w->panes, entry) {
4867724d0b0Snicm if (wp->pid == pid) {
4873e459475Snicm wp->status = status;
488514fd64dSnicm wp->flags |= PANE_STATUSREADY;
489f35e5f52Snicm
490f35e5f52Snicm log_debug("%%%u exited", wp->id);
491f35e5f52Snicm wp->flags |= PANE_EXITED;
492f35e5f52Snicm
493f35e5f52Snicm if (window_pane_destroy_ready(wp))
4944fc586aaSnicm server_destroy_pane(wp, 1);
4957c6e570cSnicm break;
4967724d0b0Snicm }
4977724d0b0Snicm }
4987724d0b0Snicm }
4999430fe9eSnicm job_check_died(pid, status);
5007724d0b0Snicm }
5017724d0b0Snicm
5027724d0b0Snicm /* Handle stopped children. */
5039883b791Snicm static void
server_child_stopped(pid_t pid,int status)5047724d0b0Snicm server_child_stopped(pid_t pid, int status)
5057724d0b0Snicm {
5067724d0b0Snicm struct window *w;
5077724d0b0Snicm struct window_pane *wp;
5087724d0b0Snicm
5097724d0b0Snicm if (WSTOPSIG(status) == SIGTTIN || WSTOPSIG(status) == SIGTTOU)
5107724d0b0Snicm return;
5117724d0b0Snicm
512ae69181dSnicm RB_FOREACH(w, windows, &windows) {
5137724d0b0Snicm TAILQ_FOREACH(wp, &w->panes, entry) {
5147724d0b0Snicm if (wp->pid == pid) {
5157724d0b0Snicm if (killpg(pid, SIGCONT) != 0)
5167724d0b0Snicm kill(pid, SIGCONT);
5177724d0b0Snicm }
5187724d0b0Snicm }
5197724d0b0Snicm }
520a6c9106fSnicm job_check_died(pid, status);
5217724d0b0Snicm }
5220687354cSnicm
5230687354cSnicm /* Add to message log. */
5240687354cSnicm void
server_add_message(const char * fmt,...)5250687354cSnicm server_add_message(const char *fmt, ...)
5260687354cSnicm {
5270687354cSnicm struct message_entry *msg, *msg1;
5280687354cSnicm char *s;
5290687354cSnicm va_list ap;
5300687354cSnicm u_int limit;
5310687354cSnicm
5320687354cSnicm va_start(ap, fmt);
5330687354cSnicm xvasprintf(&s, fmt, ap);
5340687354cSnicm va_end(ap);
5350687354cSnicm
5360687354cSnicm log_debug("message: %s", s);
5370687354cSnicm
5380687354cSnicm msg = xcalloc(1, sizeof *msg);
5390687354cSnicm gettimeofday(&msg->msg_time, NULL);
5400687354cSnicm msg->msg_num = message_next++;
5410687354cSnicm msg->msg = s;
5420687354cSnicm TAILQ_INSERT_TAIL(&message_log, msg, entry);
5430687354cSnicm
5440687354cSnicm limit = options_get_number(global_options, "message-limit");
5450687354cSnicm TAILQ_FOREACH_SAFE(msg, &message_log, entry, msg1) {
5460687354cSnicm if (msg->msg_num + limit >= message_next)
5470687354cSnicm break;
5480687354cSnicm free(msg->msg);
5490687354cSnicm TAILQ_REMOVE(&message_log, msg, entry);
5500687354cSnicm free(msg);
5510687354cSnicm }
5520687354cSnicm }
553