Coverage Report

Created: 2026-10-28 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/cmd-source-file.c
Line
Count
Source
1
/* $OpenBSD: cmd-source-file.c,v 1.64 2026/10/09 13:12:14 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2008 Tiago Cunha <me@tiagocunha.org>
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include <sys/types.h>
20
21
#include <ctype.h>
22
#include <errno.h>
23
#include <glob.h>
24
#include <stdlib.h>
25
#include <string.h>
26
27
#include "tmux.h"
28
29
/*
30
 * Sources a configuration file.
31
 */
32
33
0
#define CMD_SOURCE_FILE_DEPTH_LIMIT 50
34
static u_int cmd_source_file_depth;
35
36
static enum cmd_retval  cmd_source_file_exec(struct cmd *, struct cmdq_item *);
37
38
const struct cmd_entry cmd_source_file_entry = {
39
  .name = "source-file",
40
  .description = "Load commands from a configuration file.",
41
  .alias = "source",
42
43
  .args = { "t:Fnqv", 1, -1, NULL },
44
  .usage = "[-Fnqv] " CMD_TARGET_PANE_USAGE " path ...",
45
46
  .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
47
48
  .flags = 0,
49
  .exec = cmd_source_file_exec
50
};
51
52
struct cmd_source_file_data {
53
  struct cmdq_item   *item;
54
  struct client    *client;
55
  int       flags;
56
57
  struct cmdq_item   *after;
58
  enum cmd_retval     retval;
59
60
  u_int       current;
61
  char      **files;
62
  u_int       nfiles;
63
};
64
65
static void
66
cmd_source_file_free_data(struct cmd_source_file_data *cdata)
67
0
{
68
0
  u_int i;
69
70
0
  for (i = 0; i < cdata->nfiles; i++)
71
0
    free(cdata->files[i]);
72
0
  free(cdata->files);
73
0
  if (cdata->client != NULL)
74
0
    server_client_unref(cdata->client);
75
0
  free(cdata);
76
0
}
77
78
static enum cmd_retval
79
cmd_source_file_complete_cb(struct cmdq_item *item, void *data)
80
0
{
81
0
  struct cmd_source_file_data *cdata = data;
82
0
  struct client     *c = cdata->client;
83
84
0
  if (c == NULL) {
85
0
    cmd_source_file_depth--;
86
0
    log_debug("%s: depth now %u", __func__, cmd_source_file_depth);
87
0
  } else {
88
0
    c->source_file_depth--;
89
0
    log_debug("%s: depth now %u", __func__, c->source_file_depth);
90
0
  }
91
92
0
  cfg_print_causes(item);
93
0
  cmd_source_file_free_data(cdata);
94
0
  return (CMD_RETURN_NORMAL);
95
0
}
96
97
static void
98
cmd_source_file_complete(struct cmd_source_file_data *cdata)
99
0
{
100
0
  struct client   *c = cdata->client;
101
0
  struct cmdq_item  *new_item;
102
103
0
  if (!cfg_finished) {
104
0
    cmd_source_file_free_data(cdata);
105
0
    return;
106
0
  }
107
108
0
  if (cdata->retval == CMD_RETURN_ERROR &&
109
0
      c != NULL &&
110
0
      c->session == NULL)
111
0
    c->retval = 1;
112
0
  new_item = cmdq_get_callback(cmd_source_file_complete_cb, cdata);
113
0
  cmdq_insert_after(cdata->after, new_item);
114
0
}
115
116
static void
117
cmd_source_file_done(__unused struct client *oc, const char *path,
118
    int error, int closed, struct evbuffer *buffer, void *data)
119
0
{
120
0
  struct cmd_source_file_data *cdata = data;
121
0
  struct cmdq_item    *item = cdata->item;
122
0
  struct client     *c = cdata->client;
123
0
  void        *bdata = EVBUFFER_DATA(buffer);
124
0
  size_t         bsize = EVBUFFER_LENGTH(buffer);
125
0
  u_int        n;
126
0
  struct cmdq_item    *new_item;
127
0
  struct cmd_find_state   *target = cmdq_get_target(item);
128
129
0
  if (!closed)
130
0
    return;
131
132
0
  if (error != 0)
133
0
    cmdq_error(item, "%s: %s", strerror(error), path);
134
0
  else if (bsize != 0) {
135
0
    if (load_cfg_from_buffer(bdata, bsize, path, c, cdata->after,
136
0
        target, cdata->flags, &new_item) < 0)
137
0
      cdata->retval = CMD_RETURN_ERROR;
138
0
    else if (new_item != NULL)
139
0
      cdata->after = new_item;
140
0
  }
141
142
0
  n = ++cdata->current;
143
0
  if (n < cdata->nfiles)
144
0
    file_read(c, cdata->files[n], cmd_source_file_done, cdata);
145
0
  else {
146
0
    cmd_source_file_complete(cdata);
147
0
    cmdq_continue(item);
148
0
  }
149
0
}
150
151
static void
152
cmd_source_file_add(struct cmd_source_file_data *cdata, const char *path)
153
0
{
154
0
  log_debug("%s: %s", __func__, path);
155
0
  cdata->files = xreallocarray(cdata->files, cdata->nfiles + 1,
156
0
      sizeof *cdata->files);
157
0
  cdata->files[cdata->nfiles++] = xstrdup(path);
158
0
}
159
160
static char *
161
cmd_source_file_quote_for_glob(const char *path)
162
0
{
163
0
  char    *quoted = xmalloc(2 * strlen(path) + 1), *q = quoted;
164
0
  const char  *p = path;
165
166
0
  while (*p != '\0') {
167
0
    if ((u_char)*p < 128 && !isalnum((u_char)*p) && *p != '/')
168
0
      *q++ = '\\';
169
0
    *q++ = *p++;
170
0
  }
171
0
  *q = '\0';
172
0
  return (quoted);
173
0
}
174
175
static enum cmd_retval
176
cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
177
0
{
178
0
  struct args     *args = cmd_get_args(self);
179
0
  struct cmd_source_file_data *cdata;
180
0
  struct client     *c = cmdq_get_client(item);
181
0
  enum cmd_retval      retval = CMD_RETURN_NORMAL;
182
0
  char        *pattern, *cwd, *expanded = NULL;
183
0
  const char      *path, *error;
184
0
  glob_t         g;
185
0
  int        result, parse_flags;
186
0
  u_int        i, j;
187
188
0
  if (c == NULL) {
189
0
    if (cmd_source_file_depth >= CMD_SOURCE_FILE_DEPTH_LIMIT) {
190
0
      cmdq_error(item, "too many nested files");
191
0
      return (CMD_RETURN_ERROR);
192
0
    }
193
0
    cmd_source_file_depth++;
194
0
    log_debug("%s: depth now %u", __func__, cmd_source_file_depth);
195
0
  } else {
196
0
    if (c->source_file_depth >= CMD_SOURCE_FILE_DEPTH_LIMIT) {
197
0
      cmdq_error(item, "too many nested files");
198
0
      return (CMD_RETURN_ERROR);
199
0
    }
200
0
    c->source_file_depth++;
201
0
    log_debug("%s: depth now %u", __func__, c->source_file_depth);
202
0
  }
203
204
0
  cdata = xcalloc(1, sizeof *cdata);
205
0
  cdata->item = item;
206
0
  cdata->client = c;
207
0
  if (c != NULL)
208
0
    c->references++;
209
210
0
  if (args_has(args, 'q'))
211
0
    cdata->flags |= CMD_PARSE_QUIET;
212
0
  if (args_has(args, 'n'))
213
0
    cdata->flags |= CMD_PARSE_PARSEONLY;
214
0
  if (c == NULL || ~c->flags & CLIENT_CONTROL) {
215
0
    parse_flags = cmd_get_parse_flags(self);
216
0
    if (args_has(args, 'v') || (parse_flags & CMD_PARSE_VERBOSE))
217
0
      cdata->flags |= CMD_PARSE_VERBOSE;
218
0
  }
219
220
0
  cwd = cmd_source_file_quote_for_glob(server_client_get_cwd(c, NULL));
221
222
0
  for (i = 0; i < args_count(args); i++) {
223
0
    path = args_string(args, i);
224
0
    if (args_has(args, 'F')) {
225
0
      free(expanded);
226
0
      expanded = format_single_from_target(item, path);
227
0
      path = expanded;
228
0
    }
229
0
    if (strcmp(path, "-") == 0) {
230
0
      cmd_source_file_add(cdata, "-");
231
0
      continue;
232
0
    }
233
234
0
    if (*path == '/')
235
0
      pattern = xstrdup(path);
236
0
    else
237
0
      xasprintf(&pattern, "%s/%s", cwd, path);
238
0
    log_debug("%s: %s", __func__, pattern);
239
240
0
    if ((result = glob(pattern, 0, NULL, &g)) != 0) {
241
0
      if (result != GLOB_NOMATCH ||
242
0
          (~cdata->flags & CMD_PARSE_QUIET)) {
243
0
        if (result == GLOB_NOMATCH)
244
0
          error = strerror(ENOENT);
245
0
        else if (result == GLOB_NOSPACE)
246
0
          error = strerror(ENOMEM);
247
0
        else
248
0
          error = strerror(EINVAL);
249
0
        cmdq_error(item, "%s: %s", error, path);
250
0
        retval = CMD_RETURN_ERROR;
251
0
      }
252
0
      globfree(&g);
253
0
      free(pattern);
254
0
      continue;
255
0
    }
256
0
    free(pattern);
257
258
0
    for (j = 0; j < g.gl_pathc; j++)
259
0
      cmd_source_file_add(cdata, g.gl_pathv[j]);
260
0
    globfree(&g);
261
0
  }
262
0
  free(expanded);
263
264
0
  cdata->after = item;
265
0
  cdata->retval = retval;
266
267
0
  if (cdata->nfiles != 0) {
268
0
    file_read(c, cdata->files[0], cmd_source_file_done, cdata);
269
0
    retval = CMD_RETURN_WAIT;
270
0
  } else
271
0
    cmd_source_file_complete(cdata);
272
273
0
  free(cwd);
274
0
  return (retval);
275
0
}