1 /* $OpenBSD: cmd-wait-for.c,v 1.22 2021/08/21 10:22: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 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 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 * 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 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((void *)wc->name); 117 free(wc); 118 } 119 120 static enum cmd_retval 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 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 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 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 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 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