GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/tmux/cfg.c Lines: 0 147 0.0 %
Date: 2017-11-13 Branches: 0 90 0.0 %

Line Branch Exec Source
1
/* $OpenBSD: cfg.c,v 1.61 2017/10/06 18:02:30 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
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 <stdio.h>
24
#include <stdlib.h>
25
#include <string.h>
26
#include <util.h>
27
28
#include "tmux.h"
29
30
/* Condition for %if, %elif, %else and %endif. */
31
struct cfg_cond {
32
	size_t			line;		/* line number of %if */
33
	int			met;		/* condition was met */
34
	int			skip;		/* skip later %elif/%else */
35
	int			saw_else;	/* saw a %else */
36
37
	TAILQ_ENTRY(cfg_cond)	entry;
38
};
39
TAILQ_HEAD(cfg_conds, cfg_cond);
40
41
static char		 *cfg_file;
42
int			  cfg_finished;
43
static char		**cfg_causes;
44
static u_int		  cfg_ncauses;
45
static struct cmdq_item	 *cfg_item;
46
47
static enum cmd_retval
48
cfg_client_done(__unused struct cmdq_item *item, __unused void *data)
49
{
50
	if (!cfg_finished)
51
		return (CMD_RETURN_WAIT);
52
	return (CMD_RETURN_NORMAL);
53
}
54
55
static enum cmd_retval
56
cfg_done(__unused struct cmdq_item *item, __unused void *data)
57
{
58
	if (cfg_finished)
59
		return (CMD_RETURN_NORMAL);
60
	cfg_finished = 1;
61
62
	if (!RB_EMPTY(&sessions))
63
		cfg_show_causes(RB_MIN(sessions, &sessions));
64
65
	if (cfg_item != NULL)
66
		cfg_item->flags &= ~CMDQ_WAITING;
67
68
	status_prompt_load_history();
69
70
	return (CMD_RETURN_NORMAL);
71
}
72
73
void
74
set_cfg_file(const char *path)
75
{
76
	free(cfg_file);
77
	cfg_file = xstrdup(path);
78
}
79
80
void
81
start_cfg(void)
82
{
83
	const char	*home;
84
	int		 quiet = 0;
85
	struct client	*c;
86
87
	/*
88
	 * Configuration files are loaded without a client, so NULL is passed
89
	 * into load_cfg() and commands run in the global queue with
90
	 * item->client NULL.
91
	 *
92
	 * However, we must block the initial client (but just the initial
93
	 * client) so that its command runs after the configuration is loaded.
94
	 * Because start_cfg() is called so early, we can be sure the client's
95
	 * command queue is currently empty and our callback will be at the
96
	 * front - we need to get in before MSG_COMMAND.
97
	 */
98
	c = TAILQ_FIRST(&clients);
99
	if (c != NULL) {
100
		cfg_item = cmdq_get_callback(cfg_client_done, NULL);
101
		cmdq_append(c, cfg_item);
102
	}
103
104
	load_cfg(TMUX_CONF, NULL, NULL, 1);
105
106
	if (cfg_file == NULL && (home = find_home()) != NULL) {
107
		xasprintf(&cfg_file, "%s/.tmux.conf", home);
108
		quiet = 1;
109
	}
110
	if (cfg_file != NULL)
111
		load_cfg(cfg_file, NULL, NULL, quiet);
112
113
	cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL));
114
}
115
116
static int
117
cfg_check_condition(const char *path, size_t line, const char *p, int *skip)
118
{
119
	struct format_tree	*ft;
120
	char			*s;
121
	int			 result;
122
123
	while (isspace((u_char)*p))
124
		p++;
125
	if (p[0] == '\0') {
126
		cfg_add_cause("%s:%zu: invalid condition", path, line);
127
		*skip = 1;
128
		return (0);
129
	}
130
131
	ft = format_create(NULL, NULL, FORMAT_NONE, FORMAT_NOJOBS);
132
	s = format_expand(ft, p);
133
	result = format_true(s);
134
	free(s);
135
	format_free(ft);
136
137
	*skip = result;
138
	return (result);
139
}
140
141
static void
142
cfg_handle_if(const char *path, size_t line, struct cfg_conds *conds,
143
    const char *p)
144
{
145
	struct cfg_cond	*cond;
146
	struct cfg_cond	*parent = TAILQ_FIRST(conds);
147
148
	/*
149
	 * Add a new condition. If a previous condition exists and isn't
150
	 * currently met, this new one also can't be met.
151
	 */
152
	cond = xcalloc(1, sizeof *cond);
153
	cond->line = line;
154
	if (parent == NULL || parent->met)
155
		cond->met = cfg_check_condition(path, line, p, &cond->skip);
156
	else
157
		cond->skip = 1;
158
	cond->saw_else = 0;
159
	TAILQ_INSERT_HEAD(conds, cond, entry);
160
}
161
162
static void
163
cfg_handle_elif(const char *path, size_t line, struct cfg_conds *conds,
164
    const char *p)
165
{
166
	struct cfg_cond	*cond = TAILQ_FIRST(conds);
167
168
	/*
169
	 * If a previous condition exists and wasn't met, check this
170
	 * one instead and change the state.
171
	 */
172
	if (cond == NULL || cond->saw_else)
173
		cfg_add_cause("%s:%zu: unexpected %%elif", path, line);
174
	else if (!cond->skip)
175
		cond->met = cfg_check_condition(path, line, p, &cond->skip);
176
	else
177
		cond->met = 0;
178
}
179
180
static void
181
cfg_handle_else(const char *path, size_t line, struct cfg_conds *conds)
182
{
183
	struct cfg_cond	*cond = TAILQ_FIRST(conds);
184
185
	/*
186
	 * If a previous condition exists and wasn't met and wasn't already
187
	 * %else, use this one instead.
188
	 */
189
	if (cond == NULL || cond->saw_else) {
190
		cfg_add_cause("%s:%zu: unexpected %%else", path, line);
191
		return;
192
	}
193
	cond->saw_else = 1;
194
	cond->met = !cond->skip;
195
	cond->skip = 1;
196
}
197
198
static void
199
cfg_handle_endif(const char *path, size_t line, struct cfg_conds *conds)
200
{
201
	struct cfg_cond	*cond = TAILQ_FIRST(conds);
202
203
	/*
204
	 * Remove previous condition if one exists.
205
	 */
206
	if (cond == NULL) {
207
		cfg_add_cause("%s:%zu: unexpected %%endif", path, line);
208
		return;
209
	}
210
	TAILQ_REMOVE(conds, cond, entry);
211
	free(cond);
212
}
213
214
static void
215
cfg_handle_directive(const char *p, const char *path, size_t line,
216
    struct cfg_conds *conds)
217
{
218
	int	n = 0;
219
220
	while (p[n] != '\0' && !isspace((u_char)p[n]))
221
		n++;
222
	if (strncmp(p, "%if", n) == 0)
223
		cfg_handle_if(path, line, conds, p + n);
224
	else if (strncmp(p, "%elif", n) == 0)
225
		cfg_handle_elif(path, line, conds, p + n);
226
	else if (strcmp(p, "%else") == 0)
227
		cfg_handle_else(path, line, conds);
228
	else if (strcmp(p, "%endif") == 0)
229
		cfg_handle_endif(path, line, conds);
230
	else
231
		cfg_add_cause("%s:%zu: invalid directive: %s", path, line, p);
232
}
233
234
int
235
load_cfg(const char *path, struct client *c, struct cmdq_item *item, int quiet)
236
{
237
	FILE			*f;
238
	const char		 delim[3] = { '\\', '\\', '\0' };
239
	u_int			 found = 0;
240
	size_t			 line = 0;
241
	char			*buf, *cause1, *p, *q;
242
	struct cmd_list		*cmdlist;
243
	struct cmdq_item	*new_item;
244
	struct cfg_cond		*cond, *cond1;
245
	struct cfg_conds	 conds;
246
247
	TAILQ_INIT(&conds);
248
249
	log_debug("loading %s", path);
250
	if ((f = fopen(path, "rb")) == NULL) {
251
		if (errno == ENOENT && quiet)
252
			return (0);
253
		cfg_add_cause("%s: %s", path, strerror(errno));
254
		return (-1);
255
	}
256
257
	while ((buf = fparseln(f, NULL, &line, delim, 0)) != NULL) {
258
		log_debug("%s: %s", path, buf);
259
260
		p = buf;
261
		while (isspace((u_char)*p))
262
			p++;
263
		if (*p == '\0') {
264
			free(buf);
265
			continue;
266
		}
267
		q = p + strlen(p) - 1;
268
		while (q != p && isspace((u_char)*q))
269
			*q-- = '\0';
270
271
		if (*p == '%') {
272
			cfg_handle_directive(p, path, line, &conds);
273
			continue;
274
		}
275
		cond = TAILQ_FIRST(&conds);
276
		if (cond != NULL && !cond->met)
277
			continue;
278
279
		cmdlist = cmd_string_parse(p, path, line, &cause1);
280
		if (cmdlist == NULL) {
281
			free(buf);
282
			if (cause1 == NULL)
283
				continue;
284
			cfg_add_cause("%s:%zu: %s", path, line, cause1);
285
			free(cause1);
286
			continue;
287
		}
288
		free(buf);
289
290
		if (cmdlist == NULL)
291
			continue;
292
		new_item = cmdq_get_command(cmdlist, NULL, NULL, 0);
293
		if (item != NULL)
294
			cmdq_insert_after(item, new_item);
295
		else
296
			cmdq_append(c, new_item);
297
		cmd_list_free(cmdlist);
298
299
		found++;
300
	}
301
	fclose(f);
302
303
	TAILQ_FOREACH_REVERSE_SAFE(cond, &conds, cfg_conds, entry, cond1) {
304
		cfg_add_cause("%s:%zu: unterminated %%if", path, cond->line);
305
		TAILQ_REMOVE(&conds, cond, entry);
306
		free(cond);
307
	}
308
309
	return (found);
310
}
311
312
void
313
cfg_add_cause(const char *fmt, ...)
314
{
315
	va_list	 ap;
316
	char	*msg;
317
318
	va_start(ap, fmt);
319
	xvasprintf(&msg, fmt, ap);
320
	va_end(ap);
321
322
	cfg_ncauses++;
323
	cfg_causes = xreallocarray(cfg_causes, cfg_ncauses, sizeof *cfg_causes);
324
	cfg_causes[cfg_ncauses - 1] = msg;
325
}
326
327
void
328
cfg_print_causes(struct cmdq_item *item)
329
{
330
	u_int	 i;
331
332
	for (i = 0; i < cfg_ncauses; i++) {
333
		cmdq_print(item, "%s", cfg_causes[i]);
334
		free(cfg_causes[i]);
335
	}
336
337
	free(cfg_causes);
338
	cfg_causes = NULL;
339
	cfg_ncauses = 0;
340
}
341
342
void
343
cfg_show_causes(struct session *s)
344
{
345
	struct window_pane	*wp;
346
	u_int			 i;
347
348
	if (s == NULL || cfg_ncauses == 0)
349
		return;
350
	wp = s->curw->window->active;
351
352
	window_pane_set_mode(wp, &window_copy_mode, NULL, NULL);
353
	window_copy_init_for_output(wp);
354
	for (i = 0; i < cfg_ncauses; i++) {
355
		window_copy_add(wp, "%s", cfg_causes[i]);
356
		free(cfg_causes[i]);
357
	}
358
359
	free(cfg_causes);
360
	cfg_causes = NULL;
361
	cfg_ncauses = 0;
362
}