xref: /openbsd-src/usr.bin/tmux/cmd-wait-for.c (revision 5b859c19fe53bbea08f5c342e0a4470e99f883e1)
1 /* $OpenBSD: cmd-wait-for.c,v 1.7 2014/10/20 22:29:25 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2013 Nicholas Marriott <nicm@users.sourceforge.net>
5  * Copyright (c) 2013 Thiago de Arruda <tpadilha84@gmail.com>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
16  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
17  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/types.h>
21 
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include "tmux.h"
26 
27 /*
28  * Block or wake a client on a named wait channel.
29  */
30 
31 enum cmd_retval cmd_wait_for_exec(struct cmd *, struct cmd_q *);
32 
33 const struct cmd_entry cmd_wait_for_entry = {
34 	"wait-for", "wait",
35 	"LSU", 1, 1,
36 	"[-L|-S|-U] channel",
37 	0,
38 	cmd_wait_for_exec
39 };
40 
41 struct wait_channel {
42 	const char	       *name;
43 	int			locked;
44 
45 	TAILQ_HEAD(, cmd_q)	waiters;
46 	TAILQ_HEAD(, cmd_q)	lockers;
47 
48 	RB_ENTRY(wait_channel)	entry;
49 };
50 RB_HEAD(wait_channels, wait_channel);
51 struct wait_channels wait_channels = RB_INITIALIZER(wait_channels);
52 
53 int	wait_channel_cmp(struct wait_channel *, struct wait_channel *);
54 RB_PROTOTYPE(wait_channels, wait_channel, entry, wait_channel_cmp);
55 RB_GENERATE(wait_channels, wait_channel, entry, wait_channel_cmp);
56 
57 int
58 wait_channel_cmp(struct wait_channel *wc1, struct wait_channel *wc2)
59 {
60 	return (strcmp(wc1->name, wc2->name));
61 }
62 
63 enum cmd_retval	cmd_wait_for_signal(struct cmd_q *, const char *,
64 		    struct wait_channel *);
65 enum cmd_retval	cmd_wait_for_wait(struct cmd_q *, const char *,
66 		    struct wait_channel *);
67 enum cmd_retval	cmd_wait_for_lock(struct cmd_q *, const char *,
68 		    struct wait_channel *);
69 enum cmd_retval	cmd_wait_for_unlock(struct cmd_q *, const char *,
70 		    struct wait_channel *);
71 
72 enum cmd_retval
73 cmd_wait_for_exec(struct cmd *self, struct cmd_q *cmdq)
74 {
75 	struct args     	*args = self->args;
76 	const char		*name = args->argv[0];
77 	struct wait_channel	*wc, wc0;
78 
79 	wc0.name = name;
80 	wc = RB_FIND(wait_channels, &wait_channels, &wc0);
81 
82 	if (args_has(args, 'S'))
83 		return (cmd_wait_for_signal(cmdq, name, wc));
84 	if (args_has(args, 'L'))
85 		return (cmd_wait_for_lock(cmdq, name, wc));
86 	if (args_has(args, 'U'))
87 		return (cmd_wait_for_unlock(cmdq, name, wc));
88 	return (cmd_wait_for_wait(cmdq, name, wc));
89 }
90 
91 enum cmd_retval
92 cmd_wait_for_signal(struct cmd_q *cmdq, const char *name,
93     struct wait_channel *wc)
94 {
95 	struct cmd_q	*wq, *wq1;
96 
97 	if (wc == NULL || TAILQ_EMPTY(&wc->waiters)) {
98 		cmdq_error(cmdq, "no waiting clients on %s", name);
99 		return (CMD_RETURN_ERROR);
100 	}
101 
102 	TAILQ_FOREACH_SAFE(wq, &wc->waiters, waitentry, wq1) {
103 		TAILQ_REMOVE(&wc->waiters, wq, waitentry);
104 		if (!cmdq_free(wq))
105 			cmdq_continue(wq);
106 	}
107 
108 	if (!wc->locked) {
109 		RB_REMOVE(wait_channels, &wait_channels, wc);
110 		free((void *)wc->name);
111 		free(wc);
112 	}
113 
114 	return (CMD_RETURN_NORMAL);
115 }
116 
117 enum cmd_retval
118 cmd_wait_for_wait(struct cmd_q *cmdq, const char *name,
119     struct wait_channel *wc)
120 {
121 	if (cmdq->client == NULL || cmdq->client->session != NULL) {
122 		cmdq_error(cmdq, "not able to wait");
123 		return (CMD_RETURN_ERROR);
124 	}
125 
126 	if (wc == NULL) {
127 		wc = xmalloc(sizeof *wc);
128 		wc->name = xstrdup(name);
129 		wc->locked = 0;
130 		TAILQ_INIT(&wc->waiters);
131 		TAILQ_INIT(&wc->lockers);
132 		RB_INSERT(wait_channels, &wait_channels, wc);
133 	}
134 
135 	TAILQ_INSERT_TAIL(&wc->waiters, cmdq, waitentry);
136 	cmdq->references++;
137 
138 	return (CMD_RETURN_WAIT);
139 }
140 
141 enum cmd_retval
142 cmd_wait_for_lock(struct cmd_q *cmdq, const char *name,
143     struct wait_channel *wc)
144 {
145 	if (cmdq->client == NULL || cmdq->client->session != NULL) {
146 		cmdq_error(cmdq, "not able to lock");
147 		return (CMD_RETURN_ERROR);
148 	}
149 
150 	if (wc == NULL) {
151 		wc = xmalloc(sizeof *wc);
152 		wc->name = xstrdup(name);
153 		wc->locked = 0;
154 		TAILQ_INIT(&wc->waiters);
155 		TAILQ_INIT(&wc->lockers);
156 		RB_INSERT(wait_channels, &wait_channels, wc);
157 	}
158 
159 	if (wc->locked) {
160 		TAILQ_INSERT_TAIL(&wc->lockers, cmdq, waitentry);
161 		cmdq->references++;
162 		return (CMD_RETURN_WAIT);
163 	}
164 	wc->locked = 1;
165 
166 	return (CMD_RETURN_NORMAL);
167 }
168 
169 enum cmd_retval
170 cmd_wait_for_unlock(struct cmd_q *cmdq, const char *name,
171     struct wait_channel *wc)
172 {
173 	struct cmd_q	*wq;
174 
175 	if (wc == NULL || !wc->locked) {
176 		cmdq_error(cmdq, "channel %s not locked", name);
177 		return (CMD_RETURN_ERROR);
178 	}
179 
180 	if ((wq = TAILQ_FIRST(&wc->lockers)) != NULL) {
181 		TAILQ_REMOVE(&wc->lockers, wq, waitentry);
182 		if (!cmdq_free(wq))
183 			cmdq_continue(wq);
184 	} else {
185 		wc->locked = 0;
186 		if (TAILQ_EMPTY(&wc->waiters)) {
187 			RB_REMOVE(wait_channels, &wait_channels, wc);
188 			free((void *)wc->name);
189 			free(wc);
190 		}
191 	}
192 
193 	return (CMD_RETURN_NORMAL);
194 }
195 
196 void
197 cmd_wait_for_flush(void)
198 {
199 	struct wait_channel	*wc, *wc1;
200 	struct cmd_q		*wq, *wq1;
201 
202 	RB_FOREACH_SAFE(wc, wait_channels, &wait_channels, wc1) {
203 		TAILQ_FOREACH_SAFE(wq, &wc->waiters, waitentry, wq1) {
204 			TAILQ_REMOVE(&wc->waiters, wq, waitentry);
205 			if (!cmdq_free(wq))
206 				cmdq_continue(wq);
207 		}
208 		while ((wq = TAILQ_FIRST(&wc->lockers)) != NULL) {
209 			TAILQ_REMOVE(&wc->lockers, wq, waitentry);
210 			if (!cmdq_free(wq))
211 				cmdq_continue(wq);
212 		}
213 		RB_REMOVE(wait_channels, &wait_channels, wc);
214 		free((void *)wc->name);
215 		free(wc);
216 	}
217 }
218