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