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