xref: /openbsd/usr.bin/tmux/cmd-if-shell.c (revision 73471bf0)
1 /* $OpenBSD: cmd-if-shell.c,v 1.84 2021/10/11 10:55:30 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2009 Tiago Cunha <me@tiagocunha.org>
5  * Copyright (c) 2009 Nicholas Marriott <nicm@openbsd.org>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
16  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
17  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/types.h>
21 #include <sys/wait.h>
22 
23 #include <ctype.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include "tmux.h"
28 
29 /*
30  * Executes a tmux command if a shell command returns true or false.
31  */
32 
33 static enum args_parse_type	cmd_if_shell_args_parse(struct args *, u_int,
34 				    char **);
35 static enum cmd_retval		cmd_if_shell_exec(struct cmd *,
36 				    struct cmdq_item *);
37 
38 static void	cmd_if_shell_callback(struct job *);
39 static void	cmd_if_shell_free(void *);
40 
41 const struct cmd_entry cmd_if_shell_entry = {
42 	.name = "if-shell",
43 	.alias = "if",
44 
45 	.args = { "bFt:", 2, 3, cmd_if_shell_args_parse },
46 	.usage = "[-bF] " CMD_TARGET_PANE_USAGE " shell-command command "
47 		 "[command]",
48 
49 	.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
50 
51 	.flags = 0,
52 	.exec = cmd_if_shell_exec
53 };
54 
55 struct cmd_if_shell_data {
56 	struct args_command_state	*cmd_if;
57 	struct args_command_state	*cmd_else;
58 
59 	struct client			*client;
60 	struct cmdq_item		*item;
61 };
62 
63 static enum args_parse_type
64 cmd_if_shell_args_parse(__unused struct args *args, u_int idx,
65     __unused char **cause)
66 {
67 	if (idx == 1 || idx == 2)
68 		return (ARGS_PARSE_COMMANDS_OR_STRING);
69 	return (ARGS_PARSE_STRING);
70 }
71 
72 static enum cmd_retval
73 cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
74 {
75 	struct args			*args = cmd_get_args(self);
76 	struct cmd_find_state		*target = cmdq_get_target(item);
77 	struct cmd_if_shell_data	*cdata;
78 	struct cmdq_item		*new_item;
79 	char				*shellcmd;
80 	struct client			*tc = cmdq_get_target_client(item);
81 	struct session			*s = target->s;
82 	struct cmd_list			*cmdlist;
83 	u_int				 count = args_count(args);
84 	int				 wait = !args_has(args, 'b');
85 
86 	shellcmd = format_single_from_target(item, args_string(args, 0));
87 	if (args_has(args, 'F')) {
88 		if (*shellcmd != '0' && *shellcmd != '\0')
89 			cmdlist = args_make_commands_now(self, item, 1, 0);
90 		else if (count == 3)
91 			cmdlist = args_make_commands_now(self, item, 2, 0);
92 		else {
93 			free(shellcmd);
94 			return (CMD_RETURN_NORMAL);
95 		}
96 		free(shellcmd);
97 		if (cmdlist == NULL)
98 			return (CMD_RETURN_ERROR);
99 		new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
100 		cmdq_insert_after(item, new_item);
101 		return (CMD_RETURN_NORMAL);
102 	}
103 
104 	cdata = xcalloc(1, sizeof *cdata);
105 
106 	cdata->cmd_if = args_make_commands_prepare(self, item, 1, NULL, wait,
107 	    0);
108 	if (count == 3) {
109 		cdata->cmd_else = args_make_commands_prepare(self, item, 2,
110 		    NULL, wait, 0);
111 	}
112 
113 	if (wait) {
114 		cdata->client = cmdq_get_client(item);
115 		cdata->item = item;
116 	} else
117 		cdata->client = tc;
118 	if (cdata->client != NULL)
119 		cdata->client->references++;
120 
121 	if (job_run(shellcmd, 0, NULL, NULL, s,
122 	    server_client_get_cwd(cmdq_get_client(item), s), NULL,
123 	    cmd_if_shell_callback, cmd_if_shell_free, cdata, 0, -1,
124 	    -1) == NULL) {
125 		cmdq_error(item, "failed to run command: %s", shellcmd);
126 		free(shellcmd);
127 		free(cdata);
128 		return (CMD_RETURN_ERROR);
129 	}
130 	free(shellcmd);
131 
132 	if (!wait)
133 		return (CMD_RETURN_NORMAL);
134 	return (CMD_RETURN_WAIT);
135 }
136 
137 static void
138 cmd_if_shell_callback(struct job *job)
139 {
140 	struct cmd_if_shell_data	*cdata = job_get_data(job);
141 	struct client			*c = cdata->client;
142 	struct cmdq_item		*item = cdata->item, *new_item;
143 	struct args_command_state	*state;
144 	struct cmd_list			*cmdlist;
145 	char				*error;
146 	int				 status;
147 
148 	status = job_get_status(job);
149 	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
150 		state = cdata->cmd_else;
151 	else
152 		state = cdata->cmd_if;
153 	if (state == NULL)
154 		goto out;
155 
156 	cmdlist = args_make_commands(state, 0, NULL, &error);
157 	if (cmdlist == NULL) {
158 		if (cdata->item == NULL) {
159 			*error = toupper((u_char)*error);
160 			status_message_set(c, -1, 1, 0, "%s", error);
161 		} else
162 			cmdq_error(cdata->item, "%s", error);
163 		free(error);
164 	} else if (item == NULL) {
165 		new_item = cmdq_get_command(cmdlist, NULL);
166 		cmdq_append(c, new_item);
167 	} else {
168 		new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
169 		cmdq_insert_after(item, new_item);
170 	}
171 
172 out:
173 	if (cdata->item != NULL)
174 		cmdq_continue(cdata->item);
175 }
176 
177 static void
178 cmd_if_shell_free(void *data)
179 {
180 	struct cmd_if_shell_data	*cdata = data;
181 
182 	if (cdata->client != NULL)
183 		server_client_unref(cdata->client);
184 
185 	if (cdata->cmd_else != NULL)
186 		args_make_commands_free(cdata->cmd_else);
187 	args_make_commands_free(cdata->cmd_if);
188 
189 	free(cdata);
190 }
191