xref: /openbsd-src/usr.bin/tmux/cfg.c (revision c7e8ea31cd41a963f06f0a8ba93948b06aa6b4a4)
1 /* $OpenBSD: cfg.c,v 1.60 2017/05/30 21:44:59 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 static char		 *cfg_file;
31 int			  cfg_finished;
32 static char		**cfg_causes;
33 static u_int		  cfg_ncauses;
34 static struct cmdq_item	 *cfg_item;
35 
36 static enum cmd_retval
37 cfg_client_done(__unused struct cmdq_item *item, __unused void *data)
38 {
39 	if (!cfg_finished)
40 		return (CMD_RETURN_WAIT);
41 	return (CMD_RETURN_NORMAL);
42 }
43 
44 static enum cmd_retval
45 cfg_done(__unused struct cmdq_item *item, __unused void *data)
46 {
47 	if (cfg_finished)
48 		return (CMD_RETURN_NORMAL);
49 	cfg_finished = 1;
50 
51 	if (!RB_EMPTY(&sessions))
52 		cfg_show_causes(RB_MIN(sessions, &sessions));
53 
54 	if (cfg_item != NULL)
55 		cfg_item->flags &= ~CMDQ_WAITING;
56 
57 	status_prompt_load_history();
58 
59 	return (CMD_RETURN_NORMAL);
60 }
61 
62 void
63 set_cfg_file(const char *path)
64 {
65 	free(cfg_file);
66 	cfg_file = xstrdup(path);
67 }
68 
69 void
70 start_cfg(void)
71 {
72 	const char	*home;
73 	int		 quiet = 0;
74 	struct client	*c;
75 
76 	/*
77 	 * Configuration files are loaded without a client, so NULL is passed
78 	 * into load_cfg() and commands run in the global queue with
79 	 * item->client NULL.
80 	 *
81 	 * However, we must block the initial client (but just the initial
82 	 * client) so that its command runs after the configuration is loaded.
83 	 * Because start_cfg() is called so early, we can be sure the client's
84 	 * command queue is currently empty and our callback will be at the
85 	 * front - we need to get in before MSG_COMMAND.
86 	 */
87 	c = TAILQ_FIRST(&clients);
88 	if (c != NULL) {
89 		cfg_item = cmdq_get_callback(cfg_client_done, NULL);
90 		cmdq_append(c, cfg_item);
91 	}
92 
93 	load_cfg(TMUX_CONF, NULL, NULL, 1);
94 
95 	if (cfg_file == NULL && (home = find_home()) != NULL) {
96 		xasprintf(&cfg_file, "%s/.tmux.conf", home);
97 		quiet = 1;
98 	}
99 	if (cfg_file != NULL)
100 		load_cfg(cfg_file, NULL, NULL, quiet);
101 
102 	cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL));
103 }
104 
105 int
106 load_cfg(const char *path, struct client *c, struct cmdq_item *item, int quiet)
107 {
108 	FILE			*f;
109 	const char		 delim[3] = { '\\', '\\', '\0' };
110 	u_int			 found = 0;
111 	size_t			 line = 0;
112 	char			*buf, *cause1, *p, *q, *s;
113 	struct cmd_list		*cmdlist;
114 	struct cmdq_item	*new_item;
115 	int			 condition = 0;
116 	struct format_tree	*ft;
117 
118 	log_debug("loading %s", path);
119 	if ((f = fopen(path, "rb")) == NULL) {
120 		if (errno == ENOENT && quiet)
121 			return (0);
122 		cfg_add_cause("%s: %s", path, strerror(errno));
123 		return (-1);
124 	}
125 
126 	while ((buf = fparseln(f, NULL, &line, delim, 0)) != NULL) {
127 		log_debug("%s: %s", path, buf);
128 
129 		p = buf;
130 		while (isspace((u_char)*p))
131 			p++;
132 		if (*p == '\0') {
133 			free(buf);
134 			continue;
135 		}
136 		q = p + strlen(p) - 1;
137 		while (q != p && isspace((u_char)*q))
138 			*q-- = '\0';
139 
140 		if (condition != 0 && strcmp(p, "%endif") == 0) {
141 			condition = 0;
142 			continue;
143 		}
144 		if (strncmp(p, "%if ", 4) == 0) {
145 			if (condition != 0) {
146 				cfg_add_cause("%s:%zu: nested %%if", path,
147 				    line);
148 				continue;
149 			}
150 			ft = format_create(NULL, NULL, FORMAT_NONE,
151 			    FORMAT_NOJOBS);
152 
153 			s = p + 3;
154 			while (isspace((u_char)*s))
155 				s++;
156 			s = format_expand(ft, s);
157 			if (*s != '\0' && (s[0] != '0' || s[1] != '\0'))
158 				condition = 1;
159 			else
160 				condition = -1;
161 			free(s);
162 
163 			format_free(ft);
164 			continue;
165 		}
166 		if (condition == -1)
167 			continue;
168 
169 		cmdlist = cmd_string_parse(p, path, line, &cause1);
170 		if (cmdlist == NULL) {
171 			free(buf);
172 			if (cause1 == NULL)
173 				continue;
174 			cfg_add_cause("%s:%zu: %s", path, line, cause1);
175 			free(cause1);
176 			continue;
177 		}
178 		free(buf);
179 
180 		if (cmdlist == NULL)
181 			continue;
182 		new_item = cmdq_get_command(cmdlist, NULL, NULL, 0);
183 		if (item != NULL)
184 			cmdq_insert_after(item, new_item);
185 		else
186 			cmdq_append(c, new_item);
187 		cmd_list_free(cmdlist);
188 
189 		found++;
190 	}
191 	fclose(f);
192 
193 	return (found);
194 }
195 
196 void
197 cfg_add_cause(const char *fmt, ...)
198 {
199 	va_list	 ap;
200 	char	*msg;
201 
202 	va_start(ap, fmt);
203 	xvasprintf(&msg, fmt, ap);
204 	va_end(ap);
205 
206 	cfg_ncauses++;
207 	cfg_causes = xreallocarray(cfg_causes, cfg_ncauses, sizeof *cfg_causes);
208 	cfg_causes[cfg_ncauses - 1] = msg;
209 }
210 
211 void
212 cfg_print_causes(struct cmdq_item *item)
213 {
214 	u_int	 i;
215 
216 	for (i = 0; i < cfg_ncauses; i++) {
217 		cmdq_print(item, "%s", cfg_causes[i]);
218 		free(cfg_causes[i]);
219 	}
220 
221 	free(cfg_causes);
222 	cfg_causes = NULL;
223 	cfg_ncauses = 0;
224 }
225 
226 void
227 cfg_show_causes(struct session *s)
228 {
229 	struct window_pane	*wp;
230 	u_int			 i;
231 
232 	if (s == NULL || cfg_ncauses == 0)
233 		return;
234 	wp = s->curw->window->active;
235 
236 	window_pane_set_mode(wp, &window_copy_mode, NULL, NULL);
237 	window_copy_init_for_output(wp);
238 	for (i = 0; i < cfg_ncauses; i++) {
239 		window_copy_add(wp, "%s", cfg_causes[i]);
240 		free(cfg_causes[i]);
241 	}
242 
243 	free(cfg_causes);
244 	cfg_causes = NULL;
245 	cfg_ncauses = 0;
246 }
247