xref: /openbsd/usr.bin/tmux/cmd-wait-for.c (revision 898184e3)
1 /* $OpenBSD: cmd-wait-for.c,v 1.2 2013/03/25 10:09:35 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 	"[-LSU] channel",
37 	0,
38 	NULL,
39 	NULL,
40 	cmd_wait_for_exec
41 };
42 
43 struct wait_channel {
44 	const char	       *name;
45 	int			locked;
46 
47 	TAILQ_HEAD(, cmd_q)	waiters;
48 	TAILQ_HEAD(, cmd_q)	lockers;
49 
50 	RB_ENTRY(wait_channel)	entry;
51 };
52 RB_HEAD(wait_channels, wait_channel);
53 struct wait_channels wait_channels = RB_INITIALIZER(wait_channels);
54 
55 int	wait_channel_cmp(struct wait_channel *, struct wait_channel *);
56 RB_PROTOTYPE(wait_channels, wait_channel, entry, wait_channel_cmp);
57 RB_GENERATE(wait_channels, wait_channel, entry, wait_channel_cmp);
58 
59 int
60 wait_channel_cmp(struct wait_channel *wc1, struct wait_channel *wc2)
61 {
62 	return (strcmp(wc1->name, wc2->name));
63 }
64 
65 enum cmd_retval	cmd_wait_for_signal(struct cmd_q *, const char *,
66 		    struct wait_channel *);
67 enum cmd_retval	cmd_wait_for_wait(struct cmd_q *, const char *,
68 		    struct wait_channel *);
69 enum cmd_retval	cmd_wait_for_lock(struct cmd_q *, const char *,
70 		    struct wait_channel *);
71 enum cmd_retval	cmd_wait_for_unlock(struct cmd_q *, const char *,
72 		    struct wait_channel *);
73 
74 enum cmd_retval
75 cmd_wait_for_exec(struct cmd *self, struct cmd_q *cmdq)
76 {
77 	struct args     	*args = self->args;
78 	const char		*name = args->argv[0];
79 	struct wait_channel	*wc, wc0;
80 
81 	wc0.name = name;
82 	wc = RB_FIND(wait_channels, &wait_channels, &wc0);
83 
84 	if (args_has(args, 'S'))
85 		return (cmd_wait_for_signal(cmdq, name, wc));
86 	if (args_has(args, 'L'))
87 		return (cmd_wait_for_lock(cmdq, name, wc));
88 	if (args_has(args, 'U'))
89 		return (cmd_wait_for_unlock(cmdq, name, wc));
90 	return (cmd_wait_for_wait(cmdq, name, wc));
91 }
92 
93 enum cmd_retval
94 cmd_wait_for_signal(struct cmd_q *cmdq, const char *name,
95     struct wait_channel *wc)
96 {
97 	struct cmd_q	*wq, *wq1;
98 
99 	if (wc == NULL || TAILQ_EMPTY(&wc->waiters)) {
100 		cmdq_error(cmdq, "no waiting clients on %s", name);
101 		return (CMD_RETURN_ERROR);
102 	}
103 
104 	TAILQ_FOREACH_SAFE(wq, &wc->waiters, waitentry, wq1) {
105 		TAILQ_REMOVE(&wc->waiters, wq, waitentry);
106 		if (!cmdq_free(wq))
107 			cmdq_continue(wq);
108 	}
109 
110 	if (!wc->locked) {
111 		RB_REMOVE(wait_channels, &wait_channels, wc);
112 		free((void*) wc->name);
113 		free(wc);
114 	}
115 
116 	return (CMD_RETURN_NORMAL);
117 }
118 
119 enum cmd_retval
120 cmd_wait_for_wait(struct cmd_q *cmdq, const char *name,
121     struct wait_channel *wc)
122 {
123 	if (cmdq->client == NULL || cmdq->client->session != NULL) {
124 		cmdq_error(cmdq, "not able to wait");
125 		return (CMD_RETURN_ERROR);
126 	}
127 
128 	if (wc == NULL) {
129 		wc = xmalloc(sizeof *wc);
130 		wc->name = xstrdup(name);
131 		wc->locked = 0;
132 		TAILQ_INIT(&wc->waiters);
133 		TAILQ_INIT(&wc->lockers);
134 		RB_INSERT(wait_channels, &wait_channels, wc);
135 	}
136 
137 	TAILQ_INSERT_TAIL(&wc->waiters, cmdq, waitentry);
138 	cmdq->references++;
139 
140 	return (CMD_RETURN_WAIT);
141 }
142 
143 enum cmd_retval
144 cmd_wait_for_lock(struct cmd_q *cmdq, const char *name,
145     struct wait_channel *wc)
146 {
147 	if (cmdq->client == NULL || cmdq->client->session != NULL) {
148 		cmdq_error(cmdq, "not able to lock");
149 		return (CMD_RETURN_ERROR);
150 	}
151 
152 	if (wc == NULL) {
153 		wc = xmalloc(sizeof *wc);
154 		wc->name = xstrdup(name);
155 		wc->locked = 0;
156 		TAILQ_INIT(&wc->waiters);
157 		TAILQ_INIT(&wc->lockers);
158 		RB_INSERT(wait_channels, &wait_channels, wc);
159 	}
160 
161 	if (wc->locked) {
162 		TAILQ_INSERT_TAIL(&wc->lockers, cmdq, waitentry);
163 		cmdq->references++;
164 		return (CMD_RETURN_WAIT);
165 	}
166 	wc->locked = 1;
167 
168 	return (CMD_RETURN_NORMAL);
169 }
170 
171 enum cmd_retval
172 cmd_wait_for_unlock(struct cmd_q *cmdq, const char *name,
173     struct wait_channel *wc)
174 {
175 	struct cmd_q	*wq;
176 
177 	if (wc == NULL || !wc->locked) {
178 		cmdq_error(cmdq, "channel %s not locked", name);
179 		return (CMD_RETURN_ERROR);
180 	}
181 
182 	if ((wq = TAILQ_FIRST(&wc->lockers)) != NULL) {
183 		TAILQ_REMOVE(&wc->lockers, wq, waitentry);
184 		if (!cmdq_free(wq))
185 			cmdq_continue(wq);
186 	} else {
187 		wc->locked = 0;
188 		if (TAILQ_EMPTY(&wc->waiters)) {
189 			RB_REMOVE(wait_channels, &wait_channels, wc);
190 			free((void*) wc->name);
191 			free(wc);
192 		}
193 	}
194 
195 	return (CMD_RETURN_NORMAL);
196 }
197 
198