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