1 /* $OpenBSD$ */
2 
3 /*
4  * Copyright (c) 2013 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 <stdlib.h>
23 #include <string.h>
24 #include <time.h>
25 
26 #include "tmux.h"
27 #include "tmate.h"
28 
29 static enum cmd_retval	cmdq_continue_one(struct cmd_q *);
30 
31 /* Create new command queue. */
32 struct cmd_q *
cmdq_new(struct client * c)33 cmdq_new(struct client *c)
34 {
35 	struct cmd_q	*cmdq;
36 
37 	cmdq = xcalloc(1, sizeof *cmdq);
38 	cmdq->references = 1;
39 	cmdq->flags = 0;
40 
41 	cmdq->client = c;
42 	cmdq->client_exit = -1;
43 
44 	TAILQ_INIT(&cmdq->queue);
45 	cmdq->item = NULL;
46 	cmdq->cmd = NULL;
47 
48 	cmd_find_clear_state(&cmdq->current, NULL, 0);
49 	cmdq->parent = NULL;
50 
51 	return (cmdq);
52 }
53 
54 /* Free command queue */
55 int
cmdq_free(struct cmd_q * cmdq)56 cmdq_free(struct cmd_q *cmdq)
57 {
58 	if (--cmdq->references != 0) {
59 		if (cmdq->flags & CMD_Q_DEAD)
60 			return (1);
61 		return (0);
62 	}
63 
64 	cmdq_flush(cmdq);
65 	free(cmdq);
66 	return (1);
67 }
68 
69 /* Show message from command. */
70 void
cmdq_print(struct cmd_q * cmdq,const char * fmt,...)71 cmdq_print(struct cmd_q *cmdq, const char *fmt, ...)
72 {
73 	struct client	*c = cmdq->client;
74 	struct window	*w;
75 	va_list		 ap;
76 	char		*tmp, *msg;
77 
78 	va_start(ap, fmt);
79 
80 	if (c == NULL)
81 		/* nothing */;
82 	else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
83 		if (~c->flags & CLIENT_UTF8) {
84 			vasprintf(&tmp, fmt, ap);
85 			msg = utf8_sanitize(tmp);
86 			free(tmp);
87 			evbuffer_add(c->stdout_data, msg, strlen(msg));
88 			free(msg);
89 		} else
90 			evbuffer_add_vprintf(c->stdout_data, fmt, ap);
91 		evbuffer_add(c->stdout_data, "\n", 1);
92 		server_client_push_stdout(c);
93 	} else {
94 		w = c->session->curw->window;
95 		if (w->active->mode != &window_copy_mode) {
96 			window_pane_reset_mode(w->active);
97 			window_pane_set_mode(w->active, &window_copy_mode);
98 			window_copy_init_for_output(w->active);
99 #ifdef TMATE
100 			tmate_sync_copy_mode(w->active);
101 #endif
102 		}
103 		window_copy_vadd(w->active, fmt, ap);
104 	}
105 
106 	va_end(ap);
107 }
108 
109 /* Show error from command. */
110 void
cmdq_error(struct cmd_q * cmdq,const char * fmt,...)111 cmdq_error(struct cmd_q *cmdq, const char *fmt, ...)
112 {
113 	struct client	*c = cmdq->client;
114 	struct cmd	*cmd = cmdq->cmd;
115 	va_list		 ap;
116 	char		*msg;
117 	size_t		 msglen;
118 	char		*tmp;
119 
120 	va_start(ap, fmt);
121 	msglen = xvasprintf(&msg, fmt, ap);
122 	va_end(ap);
123 
124 	if (c == NULL)
125 #ifdef TMATE
126 		if (cmd->file && cmd->line)
127 			cfg_add_cause("%s:%u: %s", cmd->file, cmd->line, msg);
128 		else
129 			cfg_add_cause("%s", msg);
130 #else
131 		cfg_add_cause("%s:%u: %s", cmd->file, cmd->line, msg);
132 #endif
133 	else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) {
134 		if (~c->flags & CLIENT_UTF8) {
135 			tmp = msg;
136 			msg = utf8_sanitize(tmp);
137 			free(tmp);
138 			msglen = strlen(msg);
139 		}
140 		evbuffer_add(c->stderr_data, msg, msglen);
141 		evbuffer_add(c->stderr_data, "\n", 1);
142 		server_client_push_stderr(c);
143 		c->retval = 1;
144 	} else {
145 		*msg = toupper((u_char) *msg);
146 		status_message_set(c, "%s", msg);
147 	}
148 
149 	free(msg);
150 }
151 
152 /* Print a guard line. */
153 void
cmdq_guard(struct cmd_q * cmdq,const char * guard,int flags)154 cmdq_guard(struct cmd_q *cmdq, const char *guard, int flags)
155 {
156 	struct client	*c = cmdq->client;
157 
158 	if (c == NULL || !(c->flags & CLIENT_CONTROL))
159 		return;
160 
161 	evbuffer_add_printf(c->stdout_data, "%%%s %ld %u %d\n", guard,
162 	    (long) cmdq->time, cmdq->number, flags);
163 	server_client_push_stdout(c);
164 }
165 
166 /* Add command list to queue and begin processing if needed. */
167 void
cmdq_run(struct cmd_q * cmdq,struct cmd_list * cmdlist,struct mouse_event * m)168 cmdq_run(struct cmd_q *cmdq, struct cmd_list *cmdlist, struct mouse_event *m)
169 {
170 	cmdq_append(cmdq, cmdlist, m);
171 
172 	if (cmdq->item == NULL) {
173 		cmdq->cmd = NULL;
174 		cmdq_continue(cmdq);
175 	}
176 }
177 
178 /* Add command list to queue. */
179 void
cmdq_append(struct cmd_q * cmdq,struct cmd_list * cmdlist,struct mouse_event * m)180 cmdq_append(struct cmd_q *cmdq, struct cmd_list *cmdlist, struct mouse_event *m)
181 {
182 	struct cmd_q_item	*item;
183 
184 	item = xcalloc(1, sizeof *item);
185 	item->cmdlist = cmdlist;
186 	TAILQ_INSERT_TAIL(&cmdq->queue, item, qentry);
187 	cmdlist->references++;
188 
189 	if (m != NULL)
190 		memcpy(&item->mouse, m, sizeof item->mouse);
191 	else
192 		item->mouse.valid = 0;
193 }
194 
195 /* Process one command. */
196 static enum cmd_retval
cmdq_continue_one(struct cmd_q * cmdq)197 cmdq_continue_one(struct cmd_q *cmdq)
198 {
199 	struct cmd	*cmd = cmdq->cmd;
200 	enum cmd_retval	 retval;
201 	char		*tmp;
202 	int		 flags = !!(cmd->flags & CMD_CONTROL);
203 
204 #ifdef TMATE
205 	if (tmate_should_replicate_cmd(cmd->entry))
206 		tmate_exec_cmd(cmd);
207 #endif
208 
209 	tmp = cmd_print(cmd);
210 	log_debug("cmdq %p: %s", cmdq, tmp);
211 	free(tmp);
212 
213 	cmdq->time = time(NULL);
214 	cmdq->number++;
215 
216 	cmdq_guard(cmdq, "begin", flags);
217 
218 	if (cmd_prepare_state(cmd, cmdq, NULL) != 0)
219 		goto error;
220 	retval = cmd->entry->exec(cmd, cmdq);
221 	if (retval == CMD_RETURN_ERROR)
222 		goto error;
223 
224 	cmdq_guard(cmdq, "end", flags);
225 	return (retval);
226 
227 error:
228 	cmdq_guard(cmdq, "error", flags);
229 	return (CMD_RETURN_ERROR);
230 }
231 
232 /* Continue processing command queue. Returns 1 if finishes empty. */
233 int
cmdq_continue(struct cmd_q * cmdq)234 cmdq_continue(struct cmd_q *cmdq)
235 {
236 	struct client		*c = cmdq->client;
237 	struct cmd_q_item	*next;
238 	enum cmd_retval		 retval;
239 	int			 empty;
240 
241 	cmdq->references++;
242 	notify_disable();
243 
244 	log_debug("continuing cmdq %p: flags %#x, client %p", cmdq, cmdq->flags,
245 	    c);
246 
247 	empty = TAILQ_EMPTY(&cmdq->queue);
248 	if (empty)
249 		goto empty;
250 
251 	if (cmdq->item == NULL) {
252 		cmdq->item = TAILQ_FIRST(&cmdq->queue);
253 		cmdq->cmd = TAILQ_FIRST(&cmdq->item->cmdlist->list);
254 	} else
255 		cmdq->cmd = TAILQ_NEXT(cmdq->cmd, qentry);
256 
257 	do {
258 		while (cmdq->cmd != NULL) {
259 			retval = cmdq_continue_one(cmdq);
260 			if (retval == CMD_RETURN_ERROR)
261 				break;
262 			if (retval == CMD_RETURN_WAIT)
263 				goto out;
264 			if (retval == CMD_RETURN_STOP) {
265 				cmdq_flush(cmdq);
266 				goto empty;
267 			}
268 			cmdq->cmd = TAILQ_NEXT(cmdq->cmd, qentry);
269 		}
270 		next = TAILQ_NEXT(cmdq->item, qentry);
271 
272 		TAILQ_REMOVE(&cmdq->queue, cmdq->item, qentry);
273 		cmd_list_free(cmdq->item->cmdlist);
274 		free(cmdq->item);
275 
276 		cmdq->item = next;
277 		if (cmdq->item != NULL)
278 			cmdq->cmd = TAILQ_FIRST(&cmdq->item->cmdlist->list);
279 	} while (cmdq->item != NULL);
280 
281 empty:
282 	if (cmdq->client_exit > 0)
283 		cmdq->client->flags |= CLIENT_EXIT;
284 	if (cmdq->emptyfn != NULL)
285 		cmdq->emptyfn(cmdq);
286 	empty = 1;
287 
288 out:
289 	notify_enable();
290 	cmdq_free(cmdq);
291 
292 	return (empty);
293 }
294 
295 /* Flush command queue. */
296 void
cmdq_flush(struct cmd_q * cmdq)297 cmdq_flush(struct cmd_q *cmdq)
298 {
299 	struct cmd_q_item	*item, *item1;
300 
301 	TAILQ_FOREACH_SAFE(item, &cmdq->queue, qentry, item1) {
302 		TAILQ_REMOVE(&cmdq->queue, item, qentry);
303 		cmd_list_free(item->cmdlist);
304 		free(item);
305 	}
306 	cmdq->item = NULL;
307 }
308 
309