Coverage Report

Created: 2025-10-10 06:33

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