Coverage Report

Created: 2026-09-01 06:27

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.63 2026/08/18 08:05:05 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
  .alias = "source",
41
42
  .args = { "t:Fnqv", 1, -1, NULL },
43
  .usage = "[-Fnqv] " CMD_TARGET_PANE_USAGE " path ...",
44
45
  .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
46
47
  .flags = 0,
48
  .exec = cmd_source_file_exec
49
};
50
51
struct cmd_source_file_data {
52
  struct cmdq_item   *item;
53
  struct client    *client;
54
  int       flags;
55
56
  struct cmdq_item   *after;
57
  enum cmd_retval     retval;
58
59
  u_int       current;
60
  char      **files;
61
  u_int       nfiles;
62
};
63
64
static void
65
cmd_source_file_free_data(struct cmd_source_file_data *cdata)
66
0
{
67
0
  u_int i;
68
69
0
  for (i = 0; i < cdata->nfiles; i++)
70
0
    free(cdata->files[i]);
71
0
  free(cdata->files);
72
0
  if (cdata->client != NULL)
73
0
    server_client_unref(cdata->client);
74
0
  free(cdata);
75
0
}
76
77
static enum cmd_retval
78
cmd_source_file_complete_cb(struct cmdq_item *item, void *data)
79
0
{
80
0
  struct cmd_source_file_data *cdata = data;
81
0
  struct client     *c = cdata->client;
82
83
0
  if (c == NULL) {
84
0
    cmd_source_file_depth--;
85
0
    log_debug("%s: depth now %u", __func__, cmd_source_file_depth);
86
0
  } else {
87
0
    c->source_file_depth--;
88
0
    log_debug("%s: depth now %u", __func__, c->source_file_depth);
89
0
  }
90
91
0
  cfg_print_causes(item);
92
0
  cmd_source_file_free_data(cdata);
93
0
  return (CMD_RETURN_NORMAL);
94
0
}
95
96
static void
97
cmd_source_file_complete(struct cmd_source_file_data *cdata)
98
0
{
99
0
  struct client   *c = cdata->client;
100
0
  struct cmdq_item  *new_item;
101
102
0
  if (!cfg_finished) {
103
0
    cmd_source_file_free_data(cdata);
104
0
    return;
105
0
  }
106
107
0
  if (cdata->retval == CMD_RETURN_ERROR &&
108
0
      c != NULL &&
109
0
      c->session == NULL)
110
0
    c->retval = 1;
111
0
  new_item = cmdq_get_callback(cmd_source_file_complete_cb, cdata);
112
0
  cmdq_insert_after(cdata->after, new_item);
113
0
}
114
115
static void
116
cmd_source_file_done(__unused struct client *oc, const char *path,
117
    int error, int closed, struct evbuffer *buffer, void *data)
118
0
{
119
0
  struct cmd_source_file_data *cdata = data;
120
0
  struct cmdq_item    *item = cdata->item;
121
0
  struct client     *c = cdata->client;
122
0
  void        *bdata = EVBUFFER_DATA(buffer);
123
0
  size_t         bsize = EVBUFFER_LENGTH(buffer);
124
0
  u_int        n;
125
0
  struct cmdq_item    *new_item;
126
0
  struct cmd_find_state   *target = cmdq_get_target(item);
127
128
0
  if (!closed)
129
0
    return;
130
131
0
  if (error != 0)
132
0
    cmdq_error(item, "%s: %s", strerror(error), path);
133
0
  else if (bsize != 0) {
134
0
    if (load_cfg_from_buffer(bdata, bsize, path, c, cdata->after,
135
0
        target, cdata->flags, &new_item) < 0)
136
0
      cdata->retval = CMD_RETURN_ERROR;
137
0
    else if (new_item != NULL)
138
0
      cdata->after = new_item;
139
0
  }
140
141
0
  n = ++cdata->current;
142
0
  if (n < cdata->nfiles)
143
0
    file_read(c, cdata->files[n], cmd_source_file_done, cdata);
144
0
  else {
145
0
    cmd_source_file_complete(cdata);
146
0
    cmdq_continue(item);
147
0
  }
148
0
}
149
150
static void
151
cmd_source_file_add(struct cmd_source_file_data *cdata, const char *path)
152
0
{
153
0
  log_debug("%s: %s", __func__, path);
154
0
  cdata->files = xreallocarray(cdata->files, cdata->nfiles + 1,
155
0
      sizeof *cdata->files);
156
0
  cdata->files[cdata->nfiles++] = xstrdup(path);
157
0
}
158
159
static char *
160
cmd_source_file_quote_for_glob(const char *path)
161
0
{
162
0
  char    *quoted = xmalloc(2 * strlen(path) + 1), *q = quoted;
163
0
  const char  *p = path;
164
165
0
  while (*p != '\0') {
166
0
    if ((u_char)*p < 128 && !isalnum((u_char)*p) && *p != '/')
167
0
      *q++ = '\\';
168
0
    *q++ = *p++;
169
0
  }
170
0
  *q = '\0';
171
0
  return (quoted);
172
0
}
173
174
static enum cmd_retval
175
cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
176
0
{
177
0
  struct args     *args = cmd_get_args(self);
178
0
  struct cmd_source_file_data *cdata;
179
0
  struct client     *c = cmdq_get_client(item);
180
0
  enum cmd_retval      retval = CMD_RETURN_NORMAL;
181
0
  char        *pattern, *cwd, *expanded = NULL;
182
0
  const char      *path, *error;
183
0
  glob_t         g;
184
0
  int        result, parse_flags;
185
0
  u_int        i, j;
186
187
0
  if (c == NULL) {
188
0
    if (cmd_source_file_depth >= CMD_SOURCE_FILE_DEPTH_LIMIT) {
189
0
      cmdq_error(item, "too many nested files");
190
0
      return (CMD_RETURN_ERROR);
191
0
    }
192
0
    cmd_source_file_depth++;
193
0
    log_debug("%s: depth now %u", __func__, cmd_source_file_depth);
194
0
  } else {
195
0
    if (c->source_file_depth >= CMD_SOURCE_FILE_DEPTH_LIMIT) {
196
0
      cmdq_error(item, "too many nested files");
197
0
      return (CMD_RETURN_ERROR);
198
0
    }
199
0
    c->source_file_depth++;
200
0
    log_debug("%s: depth now %u", __func__, c->source_file_depth);
201
0
  }
202
203
0
  cdata = xcalloc(1, sizeof *cdata);
204
0
  cdata->item = item;
205
0
  cdata->client = c;
206
0
  if (c != NULL)
207
0
    c->references++;
208
209
0
  if (args_has(args, 'q'))
210
0
    cdata->flags |= CMD_PARSE_QUIET;
211
0
  if (args_has(args, 'n'))
212
0
    cdata->flags |= CMD_PARSE_PARSEONLY;
213
0
  if (c == NULL || ~c->flags & CLIENT_CONTROL) {
214
0
    parse_flags = cmd_get_parse_flags(self);
215
0
    if (args_has(args, 'v') || (parse_flags & CMD_PARSE_VERBOSE))
216
0
      cdata->flags |= CMD_PARSE_VERBOSE;
217
0
  }
218
219
0
  cwd = cmd_source_file_quote_for_glob(server_client_get_cwd(c, NULL));
220
221
0
  for (i = 0; i < args_count(args); i++) {
222
0
    path = args_string(args, i);
223
0
    if (args_has(args, 'F')) {
224
0
      free(expanded);
225
0
      expanded = format_single_from_target(item, path);
226
0
      path = expanded;
227
0
    }
228
0
    if (strcmp(path, "-") == 0) {
229
0
      cmd_source_file_add(cdata, "-");
230
0
      continue;
231
0
    }
232
233
0
    if (*path == '/')
234
0
      pattern = xstrdup(path);
235
0
    else
236
0
      xasprintf(&pattern, "%s/%s", cwd, path);
237
0
    log_debug("%s: %s", __func__, pattern);
238
239
0
    if ((result = glob(pattern, 0, NULL, &g)) != 0) {
240
0
      if (result != GLOB_NOMATCH ||
241
0
          (~cdata->flags & CMD_PARSE_QUIET)) {
242
0
        if (result == GLOB_NOMATCH)
243
0
          error = strerror(ENOENT);
244
0
        else if (result == GLOB_NOSPACE)
245
0
          error = strerror(ENOMEM);
246
0
        else
247
0
          error = strerror(EINVAL);
248
0
        cmdq_error(item, "%s: %s", error, path);
249
0
        retval = CMD_RETURN_ERROR;
250
0
      }
251
0
      globfree(&g);
252
0
      free(pattern);
253
0
      continue;
254
0
    }
255
0
    free(pattern);
256
257
0
    for (j = 0; j < g.gl_pathc; j++)
258
0
      cmd_source_file_add(cdata, g.gl_pathv[j]);
259
0
    globfree(&g);
260
0
  }
261
0
  free(expanded);
262
263
0
  cdata->after = item;
264
0
  cdata->retval = retval;
265
266
0
  if (cdata->nfiles != 0) {
267
0
    file_read(c, cdata->files[0], cmd_source_file_done, cdata);
268
0
    retval = CMD_RETURN_WAIT;
269
0
  } else
270
0
    cmd_source_file_complete(cdata);
271
272
0
  free(cwd);
273
0
  return (retval);
274
0
}