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