Coverage Report

Created: 2025-07-11 06:20

/src/tmux/cmd-source-file.c
Line
Count
Source (jump to first uncovered line)
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 <errno.h>
22
#include <glob.h>
23
#include <stdlib.h>
24
#include <string.h>
25
26
#include "tmux.h"
27
28
/*
29
 * Sources a configuration file.
30
 */
31
32
0
#define CMD_SOURCE_FILE_DEPTH_LIMIT 50
33
static u_int cmd_source_file_depth;
34
35
static enum cmd_retval  cmd_source_file_exec(struct cmd *, struct cmdq_item *);
36
37
const struct cmd_entry cmd_source_file_entry = {
38
  .name = "source-file",
39
  .alias = "source",
40
41
  .args = { "t:Fnqv", 1, -1, NULL },
42
  .usage = "[-Fnqv] " CMD_TARGET_PANE_USAGE " path ...",
43
44
  .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
45
46
  .flags = 0,
47
  .exec = cmd_source_file_exec
48
};
49
50
struct cmd_source_file_data {
51
  struct cmdq_item   *item;
52
  int       flags;
53
54
  struct cmdq_item   *after;
55
  enum cmd_retval     retval;
56
57
  u_int       current;
58
  char      **files;
59
  u_int       nfiles;
60
};
61
62
static enum cmd_retval
63
cmd_source_file_complete_cb(struct cmdq_item *item, __unused void *data)
64
0
{
65
0
  struct client *c = cmdq_get_client(item);
66
67
0
  if (c == NULL) {
68
0
    cmd_source_file_depth--;
69
0
    log_debug("%s: depth now %u", __func__, cmd_source_file_depth);
70
0
  } else {
71
0
    c->source_file_depth--;
72
0
    log_debug("%s: depth now %u", __func__, c->source_file_depth);
73
0
  }
74
75
0
  cfg_print_causes(item);
76
0
  return (CMD_RETURN_NORMAL);
77
0
}
78
79
static void
80
cmd_source_file_complete(struct client *c, struct cmd_source_file_data *cdata)
81
0
{
82
0
  struct cmdq_item  *new_item;
83
0
  u_int      i;
84
85
0
  if (cfg_finished) {
86
0
    if (cdata->retval == CMD_RETURN_ERROR &&
87
0
        c != NULL &&
88
0
        c->session == NULL)
89
0
      c->retval = 1;
90
0
    new_item = cmdq_get_callback(cmd_source_file_complete_cb, NULL);
91
0
    cmdq_insert_after(cdata->after, new_item);
92
0
  }
93
94
0
  for (i = 0; i < cdata->nfiles; i++)
95
0
    free(cdata->files[i]);
96
0
  free(cdata->files);
97
0
  free(cdata);
98
0
}
99
100
static void
101
cmd_source_file_done(struct client *c, const char *path, int error,
102
    int closed, struct evbuffer *buffer, void *data)
103
0
{
104
0
  struct cmd_source_file_data *cdata = data;
105
0
  struct cmdq_item    *item = cdata->item;
106
0
  void        *bdata = EVBUFFER_DATA(buffer);
107
0
  size_t         bsize = EVBUFFER_LENGTH(buffer);
108
0
  u_int        n;
109
0
  struct cmdq_item    *new_item;
110
0
  struct cmd_find_state   *target = cmdq_get_target(item);
111
112
0
  if (!closed)
113
0
    return;
114
115
0
  if (error != 0)
116
0
    cmdq_error(item, "%s: %s", path, strerror(error));
117
0
  else if (bsize != 0) {
118
0
    if (load_cfg_from_buffer(bdata, bsize, path, c, cdata->after,
119
0
        target, cdata->flags, &new_item) < 0)
120
0
      cdata->retval = CMD_RETURN_ERROR;
121
0
    else if (new_item != NULL)
122
0
      cdata->after = new_item;
123
0
  }
124
125
0
  n = ++cdata->current;
126
0
  if (n < cdata->nfiles)
127
0
    file_read(c, cdata->files[n], cmd_source_file_done, cdata);
128
0
  else {
129
0
    cmd_source_file_complete(c, cdata);
130
0
    cmdq_continue(item);
131
0
  }
132
0
}
133
134
static void
135
cmd_source_file_add(struct cmd_source_file_data *cdata, const char *path)
136
0
{
137
0
  char  resolved[PATH_MAX];
138
139
0
  if (realpath(path, resolved) == NULL) {
140
0
    log_debug("%s: realpath(\"%s\") failed: %s", __func__,
141
0
      path, strerror(errno));
142
0
  } else
143
0
    path = resolved;
144
145
0
  log_debug("%s: %s", __func__, path);
146
147
0
  cdata->files = xreallocarray(cdata->files, cdata->nfiles + 1,
148
0
      sizeof *cdata->files);
149
0
  cdata->files[cdata->nfiles++] = xstrdup(path);
150
0
}
151
152
static enum cmd_retval
153
cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
154
0
{
155
0
  struct args     *args = cmd_get_args(self);
156
0
  struct cmd_source_file_data *cdata;
157
0
  struct client     *c = cmdq_get_client(item);
158
0
  enum cmd_retval      retval = CMD_RETURN_NORMAL;
159
0
  char        *pattern, *cwd, *expanded = NULL;
160
0
  const char      *path, *error;
161
0
  glob_t         g;
162
0
  int        result;
163
0
  u_int        i, j;
164
165
0
  if (c == NULL) {
166
0
    if (cmd_source_file_depth >= CMD_SOURCE_FILE_DEPTH_LIMIT) {
167
0
      cmdq_error(item, "too many nested files");
168
0
      return (CMD_RETURN_ERROR);
169
0
    }
170
0
    cmd_source_file_depth++;
171
0
    log_debug("%s: depth now %u", __func__, cmd_source_file_depth);
172
0
  } else {
173
0
    if (c->source_file_depth >= CMD_SOURCE_FILE_DEPTH_LIMIT) {
174
0
      cmdq_error(item, "too many nested files");
175
0
      return (CMD_RETURN_ERROR);
176
0
    }
177
0
    c->source_file_depth++;
178
0
    log_debug("%s: depth now %u", __func__, c->source_file_depth);
179
0
  }
180
181
0
  cdata = xcalloc(1, sizeof *cdata);
182
0
  cdata->item = item;
183
184
0
  if (args_has(args, 'q'))
185
0
    cdata->flags |= CMD_PARSE_QUIET;
186
0
  if (args_has(args, 'n'))
187
0
    cdata->flags |= CMD_PARSE_PARSEONLY;
188
0
  if (args_has(args, 'v'))
189
0
    cdata->flags |= CMD_PARSE_VERBOSE;
190
191
0
  utf8_stravis(&cwd, server_client_get_cwd(c, NULL), VIS_GLOB);
192
193
0
  for (i = 0; i < args_count(args); i++) {
194
0
    path = args_string(args, i);
195
0
    if (args_has(args, 'F')) {
196
0
      free(expanded);
197
0
      expanded = format_single_from_target(item, path);
198
0
      path = expanded;
199
0
    }
200
0
    if (strcmp(path, "-") == 0) {
201
0
      cmd_source_file_add(cdata, "-");
202
0
      continue;
203
0
    }
204
205
0
    if (*path == '/')
206
0
      pattern = xstrdup(path);
207
0
    else
208
0
      xasprintf(&pattern, "%s/%s", cwd, path);
209
0
    log_debug("%s: %s", __func__, pattern);
210
211
0
    if ((result = glob(pattern, 0, NULL, &g)) != 0) {
212
0
      if (result != GLOB_NOMATCH ||
213
0
          (~cdata->flags & CMD_PARSE_QUIET)) {
214
0
        if (result == GLOB_NOMATCH)
215
0
          error = strerror(ENOENT);
216
0
        else if (result == GLOB_NOSPACE)
217
0
          error = strerror(ENOMEM);
218
0
        else
219
0
          error = strerror(EINVAL);
220
0
        cmdq_error(item, "%s: %s", path, error);
221
0
        retval = CMD_RETURN_ERROR;
222
0
      }
223
0
      globfree(&g);
224
0
      free(pattern);
225
0
      continue;
226
0
    }
227
0
    free(pattern);
228
229
0
    for (j = 0; j < g.gl_pathc; j++)
230
0
      cmd_source_file_add(cdata, g.gl_pathv[j]);
231
0
    globfree(&g);
232
0
  }
233
0
  free(expanded);
234
235
0
  cdata->after = item;
236
0
  cdata->retval = retval;
237
238
0
  if (cdata->nfiles != 0) {
239
0
    file_read(c, cdata->files[0], cmd_source_file_done, cdata);
240
0
    retval = CMD_RETURN_WAIT;
241
0
  } else
242
0
    cmd_source_file_complete(c, cdata);
243
244
0
  free(cwd);
245
0
  return (retval);
246
0
}