1 /* $OpenBSD: cmd-wait-for.c,v 1.8 2015/09/04 12:02:44 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 int woken; 45 46 TAILQ_HEAD(, cmd_q) waiters; 47 TAILQ_HEAD(, cmd_q) lockers; 48 49 RB_ENTRY(wait_channel) entry; 50 }; 51 RB_HEAD(wait_channels, wait_channel); 52 struct wait_channels wait_channels = RB_INITIALIZER(wait_channels); 53 54 int wait_channel_cmp(struct wait_channel *, struct wait_channel *); 55 RB_PROTOTYPE(wait_channels, wait_channel, entry, wait_channel_cmp); 56 RB_GENERATE(wait_channels, wait_channel, entry, wait_channel_cmp); 57 58 int 59 wait_channel_cmp(struct wait_channel *wc1, struct wait_channel *wc2) 60 { 61 return (strcmp(wc1->name, wc2->name)); 62 } 63 64 enum cmd_retval cmd_wait_for_signal(struct cmd_q *, const char *, 65 struct wait_channel *); 66 enum cmd_retval cmd_wait_for_wait(struct cmd_q *, const char *, 67 struct wait_channel *); 68 enum cmd_retval cmd_wait_for_lock(struct cmd_q *, const char *, 69 struct wait_channel *); 70 enum cmd_retval cmd_wait_for_unlock(struct cmd_q *, const char *, 71 struct wait_channel *); 72 73 struct wait_channel *cmd_wait_for_add(const char *); 74 void cmd_wait_for_remove(struct wait_channel *wc); 75 76 struct wait_channel * 77 cmd_wait_for_add(const char *name) 78 { 79 struct wait_channel *wc; 80 81 wc = xmalloc(sizeof *wc); 82 wc->name = xstrdup(name); 83 84 wc->locked = 0; 85 wc->woken = 0; 86 87 TAILQ_INIT(&wc->waiters); 88 TAILQ_INIT(&wc->lockers); 89 90 RB_INSERT(wait_channels, &wait_channels, wc); 91 92 log_debug("add wait channel %s", wc->name); 93 94 return (wc); 95 } 96 97 void 98 cmd_wait_for_remove(struct wait_channel *wc) 99 { 100 101 if (wc->locked) 102 return; 103 if (!TAILQ_EMPTY(&wc->waiters) || !wc->woken) 104 return; 105 106 log_debug("remove wait channel %s", wc->name); 107 108 RB_REMOVE(wait_channels, &wait_channels, wc); 109 110 free((void *)wc->name); 111 free(wc); 112 } 113 114 enum cmd_retval 115 cmd_wait_for_exec(struct cmd *self, unused struct cmd_q *cmdq) 116 { 117 struct args *args = self->args; 118 const char *name = args->argv[0]; 119 struct wait_channel *wc, wc0; 120 121 wc0.name = name; 122 wc = RB_FIND(wait_channels, &wait_channels, &wc0); 123 124 if (args_has(args, 'S')) 125 return (cmd_wait_for_signal(cmdq, name, wc)); 126 if (args_has(args, 'L')) 127 return (cmd_wait_for_lock(cmdq, name, wc)); 128 if (args_has(args, 'U')) 129 return (cmd_wait_for_unlock(cmdq, name, wc)); 130 return (cmd_wait_for_wait(cmdq, name, wc)); 131 } 132 133 enum cmd_retval 134 cmd_wait_for_signal(unused struct cmd_q *cmdq, const char *name, 135 struct wait_channel *wc) 136 { 137 struct cmd_q *wq, *wq1; 138 139 if (wc == NULL) 140 wc = cmd_wait_for_add(name); 141 142 if (TAILQ_EMPTY(&wc->waiters) && !wc->woken) { 143 log_debug("signal wait channel %s, no waiters", wc->name); 144 wc->woken = 1; 145 return (CMD_RETURN_NORMAL); 146 } 147 log_debug("signal wait channel %s, with waiters", wc->name); 148 149 TAILQ_FOREACH_SAFE(wq, &wc->waiters, waitentry, wq1) { 150 TAILQ_REMOVE(&wc->waiters, wq, waitentry); 151 if (!cmdq_free(wq)) 152 cmdq_continue(wq); 153 } 154 155 cmd_wait_for_remove(wc); 156 return (CMD_RETURN_NORMAL); 157 } 158 159 enum cmd_retval 160 cmd_wait_for_wait(struct cmd_q *cmdq, const char *name, 161 struct wait_channel *wc) 162 { 163 struct client *c = cmdq->client; 164 165 if (c == NULL || c->session != NULL) { 166 cmdq_error(cmdq, "not able to wait"); 167 return (CMD_RETURN_ERROR); 168 } 169 170 if (wc == NULL) 171 wc = cmd_wait_for_add(name); 172 173 if (wc->woken) { 174 log_debug("wait channel %s already woken (client %d)", wc->name, 175 c->tty.fd); 176 cmd_wait_for_remove(wc); 177 return (CMD_RETURN_NORMAL); 178 } 179 log_debug("wait channel %s not woken (client %d)", wc->name, c->tty.fd); 180 181 TAILQ_INSERT_TAIL(&wc->waiters, cmdq, waitentry); 182 cmdq->references++; 183 184 return (CMD_RETURN_WAIT); 185 } 186 187 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 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 while ((wq = TAILQ_FIRST(&wc->lockers)) != NULL) { 245 TAILQ_REMOVE(&wc->lockers, wq, waitentry); 246 if (!cmdq_free(wq)) 247 cmdq_continue(wq); 248 } 249 wc->locked = 0; 250 cmd_wait_for_remove(wc); 251 } 252 } 253