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