1 /* $OpenBSD$ */
2 
3 /*
4  * Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
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 static enum cmd_retval cmd_wait_for_exec(struct cmd *, struct cmdq_item *);
32 
33 const struct cmd_entry cmd_wait_for_entry = {
34           .name = "wait-for",
35           .alias = "wait",
36 
37           .args = { "LSU", 1, 1, NULL },
38           .usage = "[-L|-S|-U] channel",
39 
40           .flags = 0,
41           .exec = cmd_wait_for_exec
42 };
43 
44 struct wait_item {
45           struct cmdq_item    *item;
46           TAILQ_ENTRY(wait_item)         entry;
47 };
48 
49 struct wait_channel {
50           const char                 *name;
51           int                           locked;
52           int                           woken;
53 
54           TAILQ_HEAD(, wait_item)       waiters;
55           TAILQ_HEAD(, wait_item)       lockers;
56 
57           RB_ENTRY(wait_channel)        entry;
58 };
59 RB_HEAD(wait_channels, wait_channel);
60 static struct wait_channels wait_channels = RB_INITIALIZER(wait_channels);
61 
62 static int wait_channel_cmp(struct wait_channel *, struct wait_channel *);
63 RB_GENERATE_STATIC(wait_channels, wait_channel, entry, wait_channel_cmp);
64 
65 static int
wait_channel_cmp(struct wait_channel * wc1,struct wait_channel * wc2)66 wait_channel_cmp(struct wait_channel *wc1, struct wait_channel *wc2)
67 {
68           return (strcmp(wc1->name, wc2->name));
69 }
70 
71 static enum cmd_retval        cmd_wait_for_signal(struct cmdq_item *, const char *,
72                                   struct wait_channel *);
73 static enum cmd_retval        cmd_wait_for_wait(struct cmdq_item *, const char *,
74                                   struct wait_channel *);
75 static enum cmd_retval        cmd_wait_for_lock(struct cmdq_item *, const char *,
76                                   struct wait_channel *);
77 static enum cmd_retval        cmd_wait_for_unlock(struct cmdq_item *, const char *,
78                                   struct wait_channel *);
79 
80 static struct wait_channel    *cmd_wait_for_add(const char *);
81 static void                              cmd_wait_for_remove(struct wait_channel *);
82 
83 static struct wait_channel *
cmd_wait_for_add(const char * name)84 cmd_wait_for_add(const char *name)
85 {
86           struct wait_channel *wc;
87 
88           wc = xmalloc(sizeof *wc);
89           wc->name = xstrdup(name);
90 
91           wc->locked = 0;
92           wc->woken = 0;
93 
94           TAILQ_INIT(&wc->waiters);
95           TAILQ_INIT(&wc->lockers);
96 
97           RB_INSERT(wait_channels, &wait_channels, wc);
98 
99           log_debug("add wait channel %s", wc->name);
100 
101           return (wc);
102 }
103 
104 static void
cmd_wait_for_remove(struct wait_channel * wc)105 cmd_wait_for_remove(struct wait_channel *wc)
106 {
107           if (wc->locked)
108                     return;
109           if (!TAILQ_EMPTY(&wc->waiters) || !wc->woken)
110                     return;
111 
112           log_debug("remove wait channel %s", wc->name);
113 
114           RB_REMOVE(wait_channels, &wait_channels, wc);
115 
116           free(__UNCONST(wc->name));
117           free(wc);
118 }
119 
120 static enum cmd_retval
cmd_wait_for_exec(struct cmd * self,struct cmdq_item * item)121 cmd_wait_for_exec(struct cmd *self, struct cmdq_item *item)
122 {
123           struct args         *args = cmd_get_args(self);
124           const char                    *name = args_string(args, 0);
125           struct wait_channel *wc, find;
126 
127           find.name = name;
128           wc = RB_FIND(wait_channels, &wait_channels, &find);
129 
130           if (args_has(args, 'S'))
131                     return (cmd_wait_for_signal(item, name, wc));
132           if (args_has(args, 'L'))
133                     return (cmd_wait_for_lock(item, name, wc));
134           if (args_has(args, 'U'))
135                     return (cmd_wait_for_unlock(item, name, wc));
136           return (cmd_wait_for_wait(item, name, wc));
137 }
138 
139 static enum cmd_retval
cmd_wait_for_signal(__unused struct cmdq_item * item,const char * name,struct wait_channel * wc)140 cmd_wait_for_signal(__unused struct cmdq_item *item, const char *name,
141     struct wait_channel *wc)
142 {
143           struct wait_item    *wi, *wi1;
144 
145           if (wc == NULL)
146                     wc = cmd_wait_for_add(name);
147 
148           if (TAILQ_EMPTY(&wc->waiters) && !wc->woken) {
149                     log_debug("signal wait channel %s, no waiters", wc->name);
150                     wc->woken = 1;
151                     return (CMD_RETURN_NORMAL);
152           }
153           log_debug("signal wait channel %s, with waiters", wc->name);
154 
155           TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {
156                     cmdq_continue(wi->item);
157 
158                     TAILQ_REMOVE(&wc->waiters, wi, entry);
159                     free(wi);
160           }
161 
162           cmd_wait_for_remove(wc);
163           return (CMD_RETURN_NORMAL);
164 }
165 
166 static enum cmd_retval
cmd_wait_for_wait(struct cmdq_item * item,const char * name,struct wait_channel * wc)167 cmd_wait_for_wait(struct cmdq_item *item, const char *name,
168     struct wait_channel *wc)
169 {
170           struct client                 *c = cmdq_get_client(item);
171           struct wait_item    *wi;
172 
173           if (c == NULL) {
174                     cmdq_error(item, "not able to wait");
175                     return (CMD_RETURN_ERROR);
176           }
177 
178           if (wc == NULL)
179                     wc = cmd_wait_for_add(name);
180 
181           if (wc->woken) {
182                     log_debug("wait channel %s already woken (%p)", wc->name, c);
183                     cmd_wait_for_remove(wc);
184                     return (CMD_RETURN_NORMAL);
185           }
186           log_debug("wait channel %s not woken (%p)", wc->name, c);
187 
188           wi = xcalloc(1, sizeof *wi);
189           wi->item = item;
190           TAILQ_INSERT_TAIL(&wc->waiters, wi, entry);
191 
192           return (CMD_RETURN_WAIT);
193 }
194 
195 static enum cmd_retval
cmd_wait_for_lock(struct cmdq_item * item,const char * name,struct wait_channel * wc)196 cmd_wait_for_lock(struct cmdq_item *item, const char *name,
197     struct wait_channel *wc)
198 {
199           struct wait_item    *wi;
200 
201           if (cmdq_get_client(item) == NULL) {
202                     cmdq_error(item, "not able to lock");
203                     return (CMD_RETURN_ERROR);
204           }
205 
206           if (wc == NULL)
207                     wc = cmd_wait_for_add(name);
208 
209           if (wc->locked) {
210                     wi = xcalloc(1, sizeof *wi);
211                     wi->item = item;
212                     TAILQ_INSERT_TAIL(&wc->lockers, wi, entry);
213                     return (CMD_RETURN_WAIT);
214           }
215           wc->locked = 1;
216 
217           return (CMD_RETURN_NORMAL);
218 }
219 
220 static enum cmd_retval
cmd_wait_for_unlock(struct cmdq_item * item,const char * name,struct wait_channel * wc)221 cmd_wait_for_unlock(struct cmdq_item *item, const char *name,
222     struct wait_channel *wc)
223 {
224           struct wait_item    *wi;
225 
226           if (wc == NULL || !wc->locked) {
227                     cmdq_error(item, "channel %s not locked", name);
228                     return (CMD_RETURN_ERROR);
229           }
230 
231           if ((wi = TAILQ_FIRST(&wc->lockers)) != NULL) {
232                     cmdq_continue(wi->item);
233                     TAILQ_REMOVE(&wc->lockers, wi, entry);
234                     free(wi);
235           } else {
236                     wc->locked = 0;
237                     cmd_wait_for_remove(wc);
238           }
239 
240           return (CMD_RETURN_NORMAL);
241 }
242 
243 void
cmd_wait_for_flush(void)244 cmd_wait_for_flush(void)
245 {
246           struct wait_channel *wc, *wc1;
247           struct wait_item    *wi, *wi1;
248 
249           RB_FOREACH_SAFE(wc, wait_channels, &wait_channels, wc1) {
250                     TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {
251                               cmdq_continue(wi->item);
252                               TAILQ_REMOVE(&wc->waiters, wi, entry);
253                               free(wi);
254                     }
255                     wc->woken = 1;
256                     TAILQ_FOREACH_SAFE(wi, &wc->lockers, entry, wi1) {
257                               cmdq_continue(wi->item);
258                               TAILQ_REMOVE(&wc->lockers, wi, entry);
259                               free(wi);
260                     }
261                     wc->locked = 0;
262                     cmd_wait_for_remove(wc);
263           }
264 }
265