xref: /openbsd-src/usr.bin/tmux/cfg.c (revision aa997e528a848ca5596493c2a801bdd6fb26ae61)
1 /* $OpenBSD: cfg.c,v 1.62 2018/01/15 15:27:03 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 		new_item = cmdq_get_command(cmdlist, NULL, NULL, 0);
291 		if (item != NULL)
292 			cmdq_insert_after(item, new_item);
293 		else
294 			cmdq_append(c, new_item);
295 		cmd_list_free(cmdlist);
296 
297 		found++;
298 	}
299 	fclose(f);
300 
301 	TAILQ_FOREACH_REVERSE_SAFE(cond, &conds, cfg_conds, entry, cond1) {
302 		cfg_add_cause("%s:%zu: unterminated %%if", path, cond->line);
303 		TAILQ_REMOVE(&conds, cond, entry);
304 		free(cond);
305 	}
306 
307 	return (found);
308 }
309 
310 void
311 cfg_add_cause(const char *fmt, ...)
312 {
313 	va_list	 ap;
314 	char	*msg;
315 
316 	va_start(ap, fmt);
317 	xvasprintf(&msg, fmt, ap);
318 	va_end(ap);
319 
320 	cfg_ncauses++;
321 	cfg_causes = xreallocarray(cfg_causes, cfg_ncauses, sizeof *cfg_causes);
322 	cfg_causes[cfg_ncauses - 1] = msg;
323 }
324 
325 void
326 cfg_print_causes(struct cmdq_item *item)
327 {
328 	u_int	 i;
329 
330 	for (i = 0; i < cfg_ncauses; i++) {
331 		cmdq_print(item, "%s", cfg_causes[i]);
332 		free(cfg_causes[i]);
333 	}
334 
335 	free(cfg_causes);
336 	cfg_causes = NULL;
337 	cfg_ncauses = 0;
338 }
339 
340 void
341 cfg_show_causes(struct session *s)
342 {
343 	struct window_pane	*wp;
344 	u_int			 i;
345 
346 	if (s == NULL || cfg_ncauses == 0)
347 		return;
348 	wp = s->curw->window->active;
349 
350 	window_pane_set_mode(wp, &window_copy_mode, NULL, NULL);
351 	window_copy_init_for_output(wp);
352 	for (i = 0; i < cfg_ncauses; i++) {
353 		window_copy_add(wp, "%s", cfg_causes[i]);
354 		free(cfg_causes[i]);
355 	}
356 
357 	free(cfg_causes);
358 	cfg_causes = NULL;
359 	cfg_ncauses = 0;
360 }
361