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