1 /* $OpenBSD: cfg.c,v 1.78 2019/12/19 09:22:33 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 struct client *cfg_client; 31 static char *cfg_file; 32 int cfg_finished; 33 static char **cfg_causes; 34 static u_int cfg_ncauses; 35 static struct cmdq_item *cfg_item; 36 37 static enum cmd_retval 38 cfg_client_done(__unused struct cmdq_item *item, __unused void *data) 39 { 40 if (!cfg_finished) 41 return (CMD_RETURN_WAIT); 42 return (CMD_RETURN_NORMAL); 43 } 44 45 static enum cmd_retval 46 cfg_done(__unused struct cmdq_item *item, __unused void *data) 47 { 48 if (cfg_finished) 49 return (CMD_RETURN_NORMAL); 50 cfg_finished = 1; 51 52 if (!RB_EMPTY(&sessions)) 53 cfg_show_causes(RB_MIN(sessions, &sessions)); 54 55 if (cfg_item != NULL) 56 cmdq_continue(cfg_item); 57 58 status_prompt_load_history(); 59 60 return (CMD_RETURN_NORMAL); 61 } 62 63 void 64 set_cfg_file(const char *path) 65 { 66 free(cfg_file); 67 cfg_file = xstrdup(path); 68 } 69 70 static char * 71 expand_cfg_file(const char *path, const char *home) 72 { 73 char *expanded, *name; 74 const char *end; 75 struct environ_entry *value; 76 77 if (strncmp(path, "~/", 2) == 0) { 78 if (home == NULL) 79 return (NULL); 80 xasprintf(&expanded, "%s%s", home, path + 1); 81 return (expanded); 82 } 83 84 if (*path == '$') { 85 end = strchr(path, '/'); 86 if (end == NULL) 87 name = xstrdup(path + 1); 88 else 89 name = xstrndup(path + 1, end - path - 1); 90 value = environ_find(global_environ, name); 91 free(name); 92 if (value == NULL) 93 return (NULL); 94 if (end == NULL) 95 end = ""; 96 xasprintf(&expanded, "%s%s", value->value, end); 97 return (expanded); 98 } 99 100 return (xstrdup(path)); 101 } 102 103 void 104 start_cfg(void) 105 { 106 const char *home = find_home(); 107 struct client *c; 108 char *path, *copy, *next, *expanded; 109 110 /* 111 * Configuration files are loaded without a client, so commands are run 112 * in the global queue with item->client NULL. 113 * 114 * However, we must block the initial client (but just the initial 115 * client) so that its command runs after the configuration is loaded. 116 * Because start_cfg() is called so early, we can be sure the client's 117 * command queue is currently empty and our callback will be at the 118 * front - we need to get in before MSG_COMMAND. 119 */ 120 cfg_client = c = TAILQ_FIRST(&clients); 121 if (c != NULL) { 122 cfg_item = cmdq_get_callback(cfg_client_done, NULL); 123 cmdq_append(c, cfg_item); 124 } 125 126 if (cfg_file == NULL) { 127 path = copy = xstrdup(TMUX_CONF); 128 while ((next = strsep(&path, ":")) != NULL) { 129 expanded = expand_cfg_file(next, home); 130 if (expanded == NULL) { 131 log_debug("couldn't expand %s", next); 132 continue; 133 } 134 log_debug("expanded %s to %s", next, expanded); 135 load_cfg(expanded, c, NULL, CMD_PARSE_QUIET, NULL); 136 free(expanded); 137 } 138 free(copy); 139 } else 140 load_cfg(cfg_file, c, NULL, 0, NULL); 141 142 cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL)); 143 } 144 145 int 146 load_cfg(const char *path, struct client *c, struct cmdq_item *item, int flags, 147 struct cmdq_item **new_item) 148 { 149 FILE *f; 150 struct cmd_parse_input pi; 151 struct cmd_parse_result *pr; 152 struct cmdq_item *new_item0; 153 154 if (new_item != NULL) 155 *new_item = NULL; 156 157 log_debug("loading %s", path); 158 if ((f = fopen(path, "rb")) == NULL) { 159 if (errno == ENOENT && (flags & CMD_PARSE_QUIET)) 160 return (0); 161 cfg_add_cause("%s: %s", path, strerror(errno)); 162 return (-1); 163 } 164 165 memset(&pi, 0, sizeof pi); 166 pi.flags = flags; 167 pi.file = path; 168 pi.line = 1; 169 pi.item = item; 170 pi.c = c; 171 172 pr = cmd_parse_from_file(f, &pi); 173 fclose(f); 174 if (pr->status == CMD_PARSE_EMPTY) 175 return (0); 176 if (pr->status == CMD_PARSE_ERROR) { 177 cfg_add_cause("%s", pr->error); 178 free(pr->error); 179 return (-1); 180 } 181 if (flags & CMD_PARSE_PARSEONLY) { 182 cmd_list_free(pr->cmdlist); 183 return (0); 184 } 185 186 new_item0 = cmdq_get_command(pr->cmdlist, NULL, NULL, 0); 187 if (item != NULL) 188 new_item0 = cmdq_insert_after(item, new_item0); 189 else 190 new_item0 = cmdq_append(NULL, new_item0); 191 cmd_list_free(pr->cmdlist); 192 193 if (new_item != NULL) 194 *new_item = new_item0; 195 return (0); 196 } 197 198 int 199 load_cfg_from_buffer(const void *buf, size_t len, const char *path, 200 struct client *c, struct cmdq_item *item, int flags, 201 struct cmdq_item **new_item) 202 { 203 struct cmd_parse_input pi; 204 struct cmd_parse_result *pr; 205 struct cmdq_item *new_item0; 206 207 if (new_item != NULL) 208 *new_item = NULL; 209 210 log_debug("loading %s", path); 211 212 memset(&pi, 0, sizeof pi); 213 pi.flags = flags; 214 pi.file = path; 215 pi.line = 1; 216 pi.item = item; 217 pi.c = c; 218 219 pr = cmd_parse_from_buffer(buf, len, &pi); 220 if (pr->status == CMD_PARSE_EMPTY) 221 return (0); 222 if (pr->status == CMD_PARSE_ERROR) { 223 cfg_add_cause("%s", pr->error); 224 free(pr->error); 225 return (-1); 226 } 227 if (flags & CMD_PARSE_PARSEONLY) { 228 cmd_list_free(pr->cmdlist); 229 return (0); 230 } 231 232 new_item0 = cmdq_get_command(pr->cmdlist, NULL, NULL, 0); 233 if (item != NULL) 234 new_item0 = cmdq_insert_after(item, new_item0); 235 else 236 new_item0 = cmdq_append(NULL, new_item0); 237 cmd_list_free(pr->cmdlist); 238 239 if (new_item != NULL) 240 *new_item = new_item0; 241 return (0); 242 } 243 244 void 245 cfg_add_cause(const char *fmt, ...) 246 { 247 va_list ap; 248 char *msg; 249 250 va_start(ap, fmt); 251 xvasprintf(&msg, fmt, ap); 252 va_end(ap); 253 254 cfg_ncauses++; 255 cfg_causes = xreallocarray(cfg_causes, cfg_ncauses, sizeof *cfg_causes); 256 cfg_causes[cfg_ncauses - 1] = msg; 257 } 258 259 void 260 cfg_print_causes(struct cmdq_item *item) 261 { 262 u_int i; 263 264 for (i = 0; i < cfg_ncauses; i++) { 265 cmdq_print(item, "%s", cfg_causes[i]); 266 free(cfg_causes[i]); 267 } 268 269 free(cfg_causes); 270 cfg_causes = NULL; 271 cfg_ncauses = 0; 272 } 273 274 void 275 cfg_show_causes(struct session *s) 276 { 277 struct window_pane *wp; 278 struct window_mode_entry *wme; 279 u_int i; 280 281 if (s == NULL || cfg_ncauses == 0) 282 return; 283 wp = s->curw->window->active; 284 285 wme = TAILQ_FIRST(&wp->modes); 286 if (wme == NULL || wme->mode != &window_view_mode) 287 window_pane_set_mode(wp, &window_view_mode, NULL, NULL); 288 for (i = 0; i < cfg_ncauses; i++) { 289 window_copy_add(wp, "%s", cfg_causes[i]); 290 free(cfg_causes[i]); 291 } 292 293 free(cfg_causes); 294 cfg_causes = NULL; 295 cfg_ncauses = 0; 296 } 297