Coverage Report

Created: 2026-07-16 06:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/cmd-wait-for.c
Line
Count
Source
1
/* $OpenBSD: cmd-wait-for.c,v 1.23 2026/07/10 13:38:45 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 = { "EF:LSUlvw:", 1, 1, NULL },
38
  .usage = "[-ELSUlv] [-F format] [-w waiter] name",
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_event_item {
50
  struct cmdq_item    *item;
51
  struct events_sink    *sink;
52
  char        *name;
53
  char        *filter;
54
  int        verbose;
55
  TAILQ_ENTRY(wait_event_item)   entry;
56
};
57
static TAILQ_HEAD(, wait_event_item) wait_event_items =
58
    TAILQ_HEAD_INITIALIZER(wait_event_items);
59
60
struct wait_channel {
61
  const char         *name;
62
  int     locked;
63
  int     woken;
64
65
  TAILQ_HEAD(, wait_item) waiters;
66
  TAILQ_HEAD(, wait_item) lockers;
67
68
  RB_ENTRY(wait_channel)  entry;
69
};
70
RB_HEAD(wait_channels, wait_channel);
71
static struct wait_channels wait_channels = RB_INITIALIZER(wait_channels);
72
73
static int wait_channel_cmp(struct wait_channel *, struct wait_channel *);
74
0
RB_GENERATE_STATIC(wait_channels, wait_channel, entry, wait_channel_cmp);
Unexecuted instantiation: cmd-wait-for.c:wait_channels_RB_FIND
Unexecuted instantiation: cmd-wait-for.c:wait_channels_RB_REMOVE
Unexecuted instantiation: cmd-wait-for.c:wait_channels_RB_REMOVE_COLOR
Unexecuted instantiation: cmd-wait-for.c:wait_channels_RB_INSERT
Unexecuted instantiation: cmd-wait-for.c:wait_channels_RB_MINMAX
75
0
76
0
static int
77
0
wait_channel_cmp(struct wait_channel *wc1, struct wait_channel *wc2)
78
0
{
79
0
  return (strcmp(wc1->name, wc2->name));
80
0
}
81
82
static enum cmd_retval  cmd_wait_for_signal(struct cmdq_item *, const char *,
83
          struct wait_channel *);
84
static enum cmd_retval  cmd_wait_for_wait(struct cmdq_item *, const char *,
85
          struct wait_channel *);
86
static enum cmd_retval  cmd_wait_for_lock(struct cmdq_item *, const char *,
87
          struct wait_channel *);
88
static enum cmd_retval  cmd_wait_for_unlock(struct cmdq_item *, const char *,
89
          struct wait_channel *);
90
static enum cmd_retval  cmd_wait_for_event(struct cmdq_item *, const char *,
91
          struct args *);
92
static void   cmd_wait_for_event_cb(const char *,
93
          struct event_payload *, void *);
94
static void   cmd_wait_for_event_free(struct wait_event_item *);
95
static enum cmd_retval  cmd_wait_for_event_list(struct cmdq_item *,
96
          const char *);
97
static enum cmd_retval  cmd_wait_for_event_wake(struct cmdq_item *,
98
          const char *, struct args *);
99
static enum cmd_retval  cmd_wait_for_list(struct cmdq_item *,
100
          struct wait_channel *);
101
static enum cmd_retval  cmd_wait_for_wake(struct cmdq_item *, const char *,
102
          struct args *, struct wait_channel *);
103
104
static struct wait_channel  *cmd_wait_for_add(const char *);
105
static void      cmd_wait_for_remove(struct wait_channel *);
106
static void      cmd_wait_for_remove_empty(
107
            struct wait_channel *);
108
static const char   *cmd_wait_for_item_client_name(
109
            struct cmdq_item *);
110
static const char   *cmd_wait_for_client_name(
111
            struct wait_event_item *);
112
113
static struct wait_channel *
114
cmd_wait_for_add(const char *name)
115
0
{
116
0
  struct wait_channel *wc;
117
118
0
  wc = xmalloc(sizeof *wc);
119
0
  wc->name = xstrdup(name);
120
121
0
  wc->locked = 0;
122
0
  wc->woken = 0;
123
124
0
  TAILQ_INIT(&wc->waiters);
125
0
  TAILQ_INIT(&wc->lockers);
126
127
0
  RB_INSERT(wait_channels, &wait_channels, wc);
128
129
0
  log_debug("add wait channel %s", wc->name);
130
131
0
  return (wc);
132
0
}
133
134
static void
135
cmd_wait_for_remove(struct wait_channel *wc)
136
0
{
137
0
  if (wc->locked)
138
0
    return;
139
0
  if (!TAILQ_EMPTY(&wc->waiters) || !wc->woken)
140
0
    return;
141
142
0
  log_debug("remove wait channel %s", wc->name);
143
144
0
  RB_REMOVE(wait_channels, &wait_channels, wc);
145
146
0
  free((void *)wc->name);
147
0
  free(wc);
148
0
}
149
150
static void
151
cmd_wait_for_remove_empty(struct wait_channel *wc)
152
0
{
153
0
  if (wc->locked || wc->woken)
154
0
    return;
155
0
  if (!TAILQ_EMPTY(&wc->waiters) || !TAILQ_EMPTY(&wc->lockers))
156
0
    return;
157
158
0
  log_debug("remove empty wait channel %s", wc->name);
159
160
0
  RB_REMOVE(wait_channels, &wait_channels, wc);
161
162
0
  free((void *)wc->name);
163
0
  free(wc);
164
0
}
165
166
static const char *
167
cmd_wait_for_item_client_name(struct cmdq_item *item)
168
0
{
169
0
  struct client *c = cmdq_get_client(item);
170
171
0
  if (c == NULL || c->name == NULL)
172
0
    return ("");
173
0
  return (c->name);
174
0
}
175
176
static const char *
177
cmd_wait_for_client_name(struct wait_event_item *wei)
178
0
{
179
0
  return (cmd_wait_for_item_client_name(wei->item));
180
0
}
181
182
static enum cmd_retval
183
cmd_wait_for_exec(struct cmd *self, struct cmdq_item *item)
184
0
{
185
0
  struct args   *args = cmd_get_args(self);
186
0
  const char    *name = args_string(args, 0);
187
0
  struct wait_channel *wc, find = { .name = name };
188
189
0
  if (args_has(args, 'E'))
190
0
    return (cmd_wait_for_event(item, name, args));
191
192
0
  wc = RB_FIND(wait_channels, &wait_channels, &find);
193
0
  if (args_has(args, 'l'))
194
0
    return (cmd_wait_for_list(item, wc));
195
0
  if (args_has(args, 'w'))
196
0
    return (cmd_wait_for_wake(item, name, args, wc));
197
0
  if (args_has(args, 'S'))
198
0
    return (cmd_wait_for_signal(item, name, wc));
199
0
  if (args_has(args, 'L'))
200
0
    return (cmd_wait_for_lock(item, name, wc));
201
0
  if (args_has(args, 'U'))
202
0
    return (cmd_wait_for_unlock(item, name, wc));
203
0
  return (cmd_wait_for_wait(item, name, wc));
204
0
}
205
206
static void
207
cmd_wait_for_event_print(struct wait_event_item *wei, struct event_payload *ep)
208
0
{
209
0
  struct event_payload_item *epi;
210
0
  const char      *key;
211
0
  char        *value;
212
213
0
  epi = event_payload_first(ep);
214
0
  while (epi != NULL) {
215
0
    key = event_payload_item_name(epi);
216
0
    if (*key != '_') {
217
0
      value = event_payload_item_print(epi);
218
0
      cmdq_print(wei->item, "%s=%s", key, value);
219
0
      free(value);
220
0
    }
221
0
    epi = event_payload_next(epi);
222
0
  }
223
0
}
224
225
static void
226
cmd_wait_for_event_cb(__unused const char *name, struct event_payload *ep,
227
    void *item_data)
228
0
{
229
0
  struct wait_event_item  *wei = item_data;
230
0
  struct format_tree  *ft;
231
0
  char      *expanded;
232
0
  int      flag;
233
234
0
  if (wei->verbose)
235
0
    cmd_wait_for_event_print(wei, ep);
236
237
0
  if (wei->filter != NULL) {
238
0
    ft = format_create(cmdq_get_client(wei->item), wei->item,
239
0
        FORMAT_NONE, FORMAT_NOJOBS);
240
0
    event_payload_add_formats(ep, ft, NULL);
241
0
    expanded = format_expand(ft, wei->filter);
242
0
    flag = format_true(expanded);
243
0
    free(expanded);
244
0
    format_free(ft);
245
246
0
    if (!flag)
247
0
      return;
248
0
  }
249
250
0
  TAILQ_REMOVE(&wait_event_items, wei, entry);
251
0
  cmdq_continue(wei->item);
252
0
  cmd_wait_for_event_free(wei);
253
0
}
254
255
static void
256
cmd_wait_for_event_free(struct wait_event_item *wei)
257
0
{
258
0
  events_remove_sink(wei->sink);
259
0
  free(wei->name);
260
0
  free(wei->filter);
261
0
  free(wei);
262
0
}
263
264
static enum cmd_retval
265
cmd_wait_for_event(struct cmdq_item *item, const char *name, struct args *args)
266
0
{
267
0
  struct wait_event_item  *wei;
268
0
  const char    *filter = args_get(args, 'F');
269
270
0
  if (!hooks_valid_event_name(name)) {
271
0
    cmdq_error(item, "invalid event: %s", name);
272
0
    return (CMD_RETURN_ERROR);
273
0
  }
274
0
  if (args_has(args, 'l'))
275
0
    return (cmd_wait_for_event_list(item, name));
276
0
  if (args_has(args, 'w'))
277
0
    return (cmd_wait_for_event_wake(item, name, args));
278
279
0
  if (cmdq_get_client(item) == NULL) {
280
0
    cmdq_error(item, "not able to wait");
281
0
    return (CMD_RETURN_ERROR);
282
0
  }
283
284
0
  wei = xcalloc(1, sizeof *wei);
285
0
  wei->item = item;
286
0
  wei->name = xstrdup(name);
287
0
  wei->filter = (filter != NULL ? xstrdup(filter) : NULL);
288
0
  wei->verbose = args_has(args, 'v');
289
0
  wei->sink = events_add_sink(name, cmd_wait_for_event_cb, wei);
290
0
  TAILQ_INSERT_TAIL(&wait_event_items, wei, entry);
291
292
0
  return (CMD_RETURN_WAIT);
293
0
}
294
295
static enum cmd_retval
296
cmd_wait_for_event_list(struct cmdq_item *item, const char *name)
297
0
{
298
0
  struct wait_event_item  *wei;
299
300
0
  TAILQ_FOREACH(wei, &wait_event_items, entry) {
301
0
    if (strcmp(wei->name, name) == 0)
302
0
      cmdq_print(item, "%s", cmd_wait_for_client_name(wei));
303
0
  }
304
305
0
  return (CMD_RETURN_NORMAL);
306
0
}
307
308
static enum cmd_retval
309
cmd_wait_for_event_wake(struct cmdq_item *item, const char *name,
310
    struct args *args)
311
0
{
312
0
  struct wait_event_item  *wei, *wei1;
313
0
  const char    *client_name = args_get(args, 'w');
314
315
0
  TAILQ_FOREACH_SAFE(wei, &wait_event_items, entry, wei1) {
316
0
    if (strcmp(wei->name, name) != 0)
317
0
      continue;
318
0
    if (strcmp(cmd_wait_for_client_name(wei), client_name) != 0)
319
0
      continue;
320
321
0
    TAILQ_REMOVE(&wait_event_items, wei, entry);
322
0
    cmdq_continue(wei->item);
323
0
    cmd_wait_for_event_free(wei);
324
0
    return (CMD_RETURN_NORMAL);
325
0
  }
326
327
0
  cmdq_error(item, "waiter %s not found", client_name);
328
0
  return (CMD_RETURN_ERROR);
329
0
}
330
331
static enum cmd_retval
332
cmd_wait_for_list(struct cmdq_item *item, struct wait_channel *wc)
333
0
{
334
0
  struct wait_item  *wi;
335
336
0
  if (wc == NULL)
337
0
    return (CMD_RETURN_NORMAL);
338
339
0
  TAILQ_FOREACH(wi, &wc->waiters, entry)
340
0
    cmdq_print(item, "%s", cmd_wait_for_item_client_name(wi->item));
341
0
  TAILQ_FOREACH(wi, &wc->lockers, entry)
342
0
    cmdq_print(item, "%s", cmd_wait_for_item_client_name(wi->item));
343
344
0
  return (CMD_RETURN_NORMAL);
345
0
}
346
347
static enum cmd_retval
348
cmd_wait_for_wake(__unused struct cmdq_item *item, const char *name,
349
    struct args *args, struct wait_channel *wc)
350
0
{
351
0
  struct wait_item  *wi, *wi1;
352
0
  const char    *client_name = args_get(args, 'w');
353
354
0
  if (wc != NULL) {
355
0
    TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {
356
0
      name = cmd_wait_for_item_client_name(wi->item);
357
0
      if (strcmp(name, client_name) != 0)
358
0
        continue;
359
0
      cmdq_continue(wi->item);
360
0
      TAILQ_REMOVE(&wc->waiters, wi, entry);
361
0
      free(wi);
362
0
      cmd_wait_for_remove_empty(wc);
363
0
      return (CMD_RETURN_NORMAL);
364
0
    }
365
0
    TAILQ_FOREACH_SAFE(wi, &wc->lockers, entry, wi1) {
366
0
      name = cmd_wait_for_item_client_name(wi->item);
367
0
      if (strcmp(name, client_name) != 0)
368
0
        continue;
369
0
      cmdq_continue(wi->item);
370
0
      TAILQ_REMOVE(&wc->lockers, wi, entry);
371
0
      free(wi);
372
0
      cmd_wait_for_remove_empty(wc);
373
0
      return (CMD_RETURN_NORMAL);
374
0
    }
375
0
  }
376
0
  return (CMD_RETURN_NORMAL);
377
0
}
378
379
static enum cmd_retval
380
cmd_wait_for_signal(__unused struct cmdq_item *item, const char *name,
381
    struct wait_channel *wc)
382
0
{
383
0
  struct wait_item  *wi, *wi1;
384
385
0
  if (wc == NULL)
386
0
    wc = cmd_wait_for_add(name);
387
388
0
  if (TAILQ_EMPTY(&wc->waiters) && !wc->woken) {
389
0
    log_debug("signal wait channel %s, no waiters", wc->name);
390
0
    wc->woken = 1;
391
0
    return (CMD_RETURN_NORMAL);
392
0
  }
393
0
  log_debug("signal wait channel %s, with waiters", wc->name);
394
395
0
  TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {
396
0
    cmdq_continue(wi->item);
397
398
0
    TAILQ_REMOVE(&wc->waiters, wi, entry);
399
0
    free(wi);
400
0
  }
401
402
0
  cmd_wait_for_remove(wc);
403
0
  return (CMD_RETURN_NORMAL);
404
0
}
405
406
static enum cmd_retval
407
cmd_wait_for_wait(struct cmdq_item *item, const char *name,
408
    struct wait_channel *wc)
409
0
{
410
0
  struct client   *c = cmdq_get_client(item);
411
0
  struct wait_item  *wi;
412
413
0
  if (c == NULL) {
414
0
    cmdq_error(item, "not able to wait");
415
0
    return (CMD_RETURN_ERROR);
416
0
  }
417
418
0
  if (wc == NULL)
419
0
    wc = cmd_wait_for_add(name);
420
421
0
  if (wc->woken) {
422
0
    log_debug("wait channel %s already woken (%p)", wc->name, c);
423
0
    cmd_wait_for_remove(wc);
424
0
    return (CMD_RETURN_NORMAL);
425
0
  }
426
0
  log_debug("wait channel %s not woken (%p)", wc->name, c);
427
428
0
  wi = xcalloc(1, sizeof *wi);
429
0
  wi->item = item;
430
0
  TAILQ_INSERT_TAIL(&wc->waiters, wi, entry);
431
432
0
  return (CMD_RETURN_WAIT);
433
0
}
434
435
static enum cmd_retval
436
cmd_wait_for_lock(struct cmdq_item *item, const char *name,
437
    struct wait_channel *wc)
438
0
{
439
0
  struct wait_item  *wi;
440
441
0
  if (cmdq_get_client(item) == NULL) {
442
0
    cmdq_error(item, "not able to lock");
443
0
    return (CMD_RETURN_ERROR);
444
0
  }
445
446
0
  if (wc == NULL)
447
0
    wc = cmd_wait_for_add(name);
448
449
0
  if (wc->locked) {
450
0
    wi = xcalloc(1, sizeof *wi);
451
0
    wi->item = item;
452
0
    TAILQ_INSERT_TAIL(&wc->lockers, wi, entry);
453
0
    return (CMD_RETURN_WAIT);
454
0
  }
455
0
  wc->locked = 1;
456
457
0
  return (CMD_RETURN_NORMAL);
458
0
}
459
460
static enum cmd_retval
461
cmd_wait_for_unlock(struct cmdq_item *item, const char *name,
462
    struct wait_channel *wc)
463
0
{
464
0
  struct wait_item  *wi;
465
466
0
  if (wc == NULL || !wc->locked) {
467
0
    cmdq_error(item, "channel %s not locked", name);
468
0
    return (CMD_RETURN_ERROR);
469
0
  }
470
471
0
  if ((wi = TAILQ_FIRST(&wc->lockers)) != NULL) {
472
0
    cmdq_continue(wi->item);
473
0
    TAILQ_REMOVE(&wc->lockers, wi, entry);
474
0
    free(wi);
475
0
  } else {
476
0
    wc->locked = 0;
477
0
    cmd_wait_for_remove(wc);
478
0
  }
479
480
0
  return (CMD_RETURN_NORMAL);
481
0
}
482
483
void
484
cmd_wait_for_flush(void)
485
0
{
486
0
  struct wait_channel *wc, *wc1;
487
0
  struct wait_item  *wi, *wi1;
488
0
  struct wait_event_item  *wei, *wei1;
489
490
0
  TAILQ_FOREACH_SAFE(wei, &wait_event_items, entry, wei1) {
491
0
    TAILQ_REMOVE(&wait_event_items, wei, entry);
492
0
    cmdq_continue(wei->item);
493
0
    cmd_wait_for_event_free(wei);
494
0
  }
495
496
0
  RB_FOREACH_SAFE(wc, wait_channels, &wait_channels, wc1) {
497
0
    TAILQ_FOREACH_SAFE(wi, &wc->waiters, entry, wi1) {
498
0
      cmdq_continue(wi->item);
499
0
      TAILQ_REMOVE(&wc->waiters, wi, entry);
500
0
      free(wi);
501
0
    }
502
0
    wc->woken = 1;
503
0
    TAILQ_FOREACH_SAFE(wi, &wc->lockers, entry, wi1) {
504
0
      cmdq_continue(wi->item);
505
      TAILQ_REMOVE(&wc->lockers, wi, entry);
506
0
      free(wi);
507
0
    }
508
0
    wc->locked = 0;
509
0
    cmd_wait_for_remove(wc);
510
0
  }
511
0
}