1 | /* $OpenBSD$ */ |
2 | |
3 | /* |
4 | * Copyright (c) 2009 Nicholas Marriott <[email protected]> |
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 | #include <sys/socket.h> |
21 | |
22 | #include <errno.h> |
23 | #include <fcntl.h> |
24 | #include <signal.h> |
25 | #include <stdlib.h> |
26 | #include <string.h> |
27 | #include <time.h> |
28 | #include <unistd.h> |
29 | |
30 | #include "tmux.h" |
31 | |
32 | /* |
33 | * Open pipe to redirect pane output. If already open, close first. |
34 | */ |
35 | |
36 | static enum cmd_retval cmd_pipe_pane_exec(struct cmd *, struct cmdq_item *); |
37 | |
38 | static void cmd_pipe_pane_read_callback(struct bufferevent *, void *); |
39 | static void cmd_pipe_pane_write_callback(struct bufferevent *, void *); |
40 | static void cmd_pipe_pane_error_callback(struct bufferevent *, short, void *); |
41 | |
42 | const struct cmd_entry cmd_pipe_pane_entry = { |
43 | .name = "pipe-pane" , |
44 | .alias = "pipep" , |
45 | |
46 | .args = { "IOot:" , 0, 1 }, |
47 | .usage = "[-IOo] " CMD_TARGET_PANE_USAGE " [command]" , |
48 | |
49 | .target = { 't', CMD_FIND_PANE, 0 }, |
50 | |
51 | .flags = CMD_AFTERHOOK, |
52 | .exec = cmd_pipe_pane_exec |
53 | }; |
54 | |
55 | static enum cmd_retval |
56 | cmd_pipe_pane_exec(struct cmd *self, struct cmdq_item *item) |
57 | { |
58 | struct args *args = self->args; |
59 | struct client *c = cmd_find_client(item, NULL, 1); |
60 | struct window_pane *wp = item->target.wp; |
61 | struct session *s = item->target.s; |
62 | struct winlink *wl = item->target.wl; |
63 | char *cmd; |
64 | int old_fd, pipe_fd[2], null_fd, in, out; |
65 | struct format_tree *ft; |
66 | sigset_t set, oldset; |
67 | |
68 | /* Destroy the old pipe. */ |
69 | old_fd = wp->pipe_fd; |
70 | if (wp->pipe_fd != -1) { |
71 | bufferevent_free(wp->pipe_event); |
72 | close(wp->pipe_fd); |
73 | wp->pipe_fd = -1; |
74 | |
75 | if (window_pane_destroy_ready(wp)) { |
76 | server_destroy_pane(wp, 1); |
77 | return (CMD_RETURN_NORMAL); |
78 | } |
79 | } |
80 | |
81 | /* If no pipe command, that is enough. */ |
82 | if (args->argc == 0 || *args->argv[0] == '\0') |
83 | return (CMD_RETURN_NORMAL); |
84 | |
85 | /* |
86 | * With -o, only open the new pipe if there was no previous one. This |
87 | * allows a pipe to be toggled with a single key, for example: |
88 | * |
89 | * bind ^p pipep -o 'cat >>~/output' |
90 | */ |
91 | if (args_has(self->args, 'o') && old_fd != -1) |
92 | return (CMD_RETURN_NORMAL); |
93 | |
94 | /* What do we want to do? Neither -I or -O is -O. */ |
95 | if (args_has(self->args, 'I')) { |
96 | in = 1; |
97 | out = args_has(self->args, 'O'); |
98 | } else { |
99 | in = 0; |
100 | out = 1; |
101 | } |
102 | |
103 | /* Open the new pipe. */ |
104 | if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_fd) != 0) { |
105 | cmdq_error(item, "socketpair error: %s" , strerror(errno)); |
106 | return (CMD_RETURN_ERROR); |
107 | } |
108 | |
109 | /* Expand the command. */ |
110 | ft = format_create(item->client, item, FORMAT_NONE, 0); |
111 | format_defaults(ft, c, s, wl, wp); |
112 | cmd = format_expand_time(ft, args->argv[0]); |
113 | format_free(ft); |
114 | |
115 | /* Fork the child. */ |
116 | sigfillset(&set); |
117 | sigprocmask(SIG_BLOCK, &set, &oldset); |
118 | switch (fork()) { |
119 | case -1: |
120 | sigprocmask(SIG_SETMASK, &oldset, NULL); |
121 | cmdq_error(item, "fork error: %s" , strerror(errno)); |
122 | |
123 | free(cmd); |
124 | return (CMD_RETURN_ERROR); |
125 | case 0: |
126 | /* Child process. */ |
127 | proc_clear_signals(server_proc, 1); |
128 | sigprocmask(SIG_SETMASK, &oldset, NULL); |
129 | close(pipe_fd[0]); |
130 | |
131 | null_fd = open(_PATH_DEVNULL, O_WRONLY, 0); |
132 | if (out) { |
133 | if (dup2(pipe_fd[1], STDIN_FILENO) == -1) |
134 | _exit(1); |
135 | } else { |
136 | if (dup2(null_fd, STDIN_FILENO) == -1) |
137 | _exit(1); |
138 | } |
139 | if (in) { |
140 | if (dup2(pipe_fd[1], STDOUT_FILENO) == -1) |
141 | _exit(1); |
142 | if (pipe_fd[1] != STDOUT_FILENO) |
143 | close(pipe_fd[1]); |
144 | } else { |
145 | if (dup2(null_fd, STDOUT_FILENO) == -1) |
146 | _exit(1); |
147 | } |
148 | if (dup2(null_fd, STDERR_FILENO) == -1) |
149 | _exit(1); |
150 | closefrom(STDERR_FILENO + 1); |
151 | |
152 | execl(_PATH_BSHELL, "sh" , "-c" , cmd, (char *) NULL); |
153 | _exit(1); |
154 | default: |
155 | /* Parent process. */ |
156 | sigprocmask(SIG_SETMASK, &oldset, NULL); |
157 | close(pipe_fd[1]); |
158 | |
159 | wp->pipe_fd = pipe_fd[0]; |
160 | if (wp->fd != -1) |
161 | wp->pipe_off = EVBUFFER_LENGTH(wp->event->input); |
162 | else |
163 | wp->pipe_off = 0; |
164 | |
165 | setblocking(wp->pipe_fd, 0); |
166 | wp->pipe_event = bufferevent_new(wp->pipe_fd, |
167 | cmd_pipe_pane_read_callback, |
168 | cmd_pipe_pane_write_callback, |
169 | cmd_pipe_pane_error_callback, |
170 | wp); |
171 | if (wp->pipe_event == NULL) |
172 | fatalx("out of memory" ); |
173 | if (out) |
174 | bufferevent_enable(wp->pipe_event, EV_WRITE); |
175 | if (in) |
176 | bufferevent_enable(wp->pipe_event, EV_READ); |
177 | |
178 | free(cmd); |
179 | return (CMD_RETURN_NORMAL); |
180 | } |
181 | } |
182 | |
183 | static void |
184 | cmd_pipe_pane_read_callback(__unused struct bufferevent *bufev, void *data) |
185 | { |
186 | struct window_pane *wp = data; |
187 | struct evbuffer *evb = wp->pipe_event->input; |
188 | size_t available; |
189 | |
190 | available = EVBUFFER_LENGTH(evb); |
191 | log_debug("%%%u pipe read %zu" , wp->id, available); |
192 | |
193 | bufferevent_write(wp->event, EVBUFFER_DATA(evb), available); |
194 | evbuffer_drain(evb, available); |
195 | |
196 | if (window_pane_destroy_ready(wp)) |
197 | server_destroy_pane(wp, 1); |
198 | } |
199 | |
200 | static void |
201 | cmd_pipe_pane_write_callback(__unused struct bufferevent *bufev, void *data) |
202 | { |
203 | struct window_pane *wp = data; |
204 | |
205 | log_debug("%%%u pipe empty" , wp->id); |
206 | |
207 | if (window_pane_destroy_ready(wp)) |
208 | server_destroy_pane(wp, 1); |
209 | } |
210 | |
211 | static void |
212 | cmd_pipe_pane_error_callback(__unused struct bufferevent *bufev, |
213 | __unused short what, void *data) |
214 | { |
215 | struct window_pane *wp = data; |
216 | |
217 | log_debug("%%%u pipe error" , wp->id); |
218 | |
219 | bufferevent_free(wp->pipe_event); |
220 | close(wp->pipe_fd); |
221 | wp->pipe_fd = -1; |
222 | |
223 | if (window_pane_destroy_ready(wp)) |
224 | server_destroy_pane(wp, 1); |
225 | } |
226 | |