Home | History | Annotate | Line # | Download | only in dist
      1 /* $OpenBSD$ */
      2 
      3 /*
      4  * Copyright (c) 2013 Nicholas Marriott <nicholas.marriott (at) gmail.com>
      5  * Copyright (c) 2013 Thiago de Arruda <tpadilha84 (at) 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(__UNCONST(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