1 /* $OpenBSD$ */ 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 <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 = cmd_get_args(self); 59 struct cmd_find_state *target = cmdq_get_target(item); 60 struct client *tc = cmdq_get_target_client(item); 61 struct window_pane *wp = target->wp; 62 struct session *s = target->s; 63 struct winlink *wl = target->wl; 64 struct window_pane_offset *wpo = &wp->pipe_offset; 65 char *cmd; 66 int old_fd, pipe_fd[2], null_fd, in, out; 67 struct format_tree *ft; 68 sigset_t set, oldset; 69 70 /* Destroy the old pipe. */ 71 old_fd = wp->pipe_fd; 72 if (wp->pipe_fd != -1) { 73 bufferevent_free(wp->pipe_event); 74 close(wp->pipe_fd); 75 wp->pipe_fd = -1; 76 77 if (window_pane_destroy_ready(wp)) { 78 server_destroy_pane(wp, 1); 79 return (CMD_RETURN_NORMAL); 80 } 81 } 82 83 /* If no pipe command, that is enough. */ 84 if (args->argc == 0 || *args->argv[0] == '\0') 85 return (CMD_RETURN_NORMAL); 86 87 /* 88 * With -o, only open the new pipe if there was no previous one. This 89 * allows a pipe to be toggled with a single key, for example: 90 * 91 * bind ^p pipep -o 'cat >>~/output' 92 */ 93 if (args_has(args, 'o') && old_fd != -1) 94 return (CMD_RETURN_NORMAL); 95 96 /* What do we want to do? Neither -I or -O is -O. */ 97 if (args_has(args, 'I')) { 98 in = 1; 99 out = args_has(args, 'O'); 100 } else { 101 in = 0; 102 out = 1; 103 } 104 105 /* Open the new pipe. */ 106 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_fd) != 0) { 107 cmdq_error(item, "socketpair error: %s", strerror(errno)); 108 return (CMD_RETURN_ERROR); 109 } 110 111 /* Expand the command. */ 112 ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0); 113 format_defaults(ft, tc, s, wl, wp); 114 cmd = format_expand_time(ft, args->argv[0]); 115 format_free(ft); 116 117 /* Fork the child. */ 118 sigfillset(&set); 119 sigprocmask(SIG_BLOCK, &set, &oldset); 120 switch (fork()) { 121 case -1: 122 sigprocmask(SIG_SETMASK, &oldset, NULL); 123 cmdq_error(item, "fork error: %s", strerror(errno)); 124 125 free(cmd); 126 return (CMD_RETURN_ERROR); 127 case 0: 128 /* Child process. */ 129 proc_clear_signals(server_proc, 1); 130 sigprocmask(SIG_SETMASK, &oldset, NULL); 131 close(pipe_fd[0]); 132 133 null_fd = open(_PATH_DEVNULL, O_WRONLY, 0); 134 if (out) { 135 if (dup2(pipe_fd[1], STDIN_FILENO) == -1) 136 _exit(1); 137 } else { 138 if (dup2(null_fd, STDIN_FILENO) == -1) 139 _exit(1); 140 } 141 if (in) { 142 if (dup2(pipe_fd[1], STDOUT_FILENO) == -1) 143 _exit(1); 144 if (pipe_fd[1] != STDOUT_FILENO) 145 close(pipe_fd[1]); 146 } else { 147 if (dup2(null_fd, STDOUT_FILENO) == -1) 148 _exit(1); 149 } 150 if (dup2(null_fd, STDERR_FILENO) == -1) 151 _exit(1); 152 closefrom(STDERR_FILENO + 1); 153 154 execl(_PATH_BSHELL, "sh", "-c", cmd, (char *) NULL); 155 _exit(1); 156 default: 157 /* Parent process. */ 158 sigprocmask(SIG_SETMASK, &oldset, NULL); 159 close(pipe_fd[1]); 160 161 wp->pipe_fd = pipe_fd[0]; 162 memcpy(wpo, &wp->offset, sizeof *wpo); 163 164 setblocking(wp->pipe_fd, 0); 165 wp->pipe_event = bufferevent_new(wp->pipe_fd, 166 cmd_pipe_pane_read_callback, 167 cmd_pipe_pane_write_callback, 168 cmd_pipe_pane_error_callback, 169 wp); 170 if (wp->pipe_event == NULL) 171 fatalx("out of memory"); 172 if (out) 173 bufferevent_enable(wp->pipe_event, EV_WRITE); 174 if (in) 175 bufferevent_enable(wp->pipe_event, EV_READ); 176 177 free(cmd); 178 return (CMD_RETURN_NORMAL); 179 } 180 } 181 182 static void 183 cmd_pipe_pane_read_callback(__unused struct bufferevent *bufev, void *data) 184 { 185 struct window_pane *wp = data; 186 struct evbuffer *evb = wp->pipe_event->input; 187 size_t available; 188 189 available = EVBUFFER_LENGTH(evb); 190 log_debug("%%%u pipe read %zu", wp->id, available); 191 192 bufferevent_write(wp->event, EVBUFFER_DATA(evb), available); 193 evbuffer_drain(evb, available); 194 195 if (window_pane_destroy_ready(wp)) 196 server_destroy_pane(wp, 1); 197 } 198 199 static void 200 cmd_pipe_pane_write_callback(__unused struct bufferevent *bufev, void *data) 201 { 202 struct window_pane *wp = data; 203 204 log_debug("%%%u pipe empty", wp->id); 205 206 if (window_pane_destroy_ready(wp)) 207 server_destroy_pane(wp, 1); 208 } 209 210 static void 211 cmd_pipe_pane_error_callback(__unused struct bufferevent *bufev, 212 __unused short what, void *data) 213 { 214 struct window_pane *wp = data; 215 216 log_debug("%%%u pipe error", wp->id); 217 218 bufferevent_free(wp->pipe_event); 219 close(wp->pipe_fd); 220 wp->pipe_fd = -1; 221 222 if (window_pane_destroy_ready(wp)) 223 server_destroy_pane(wp, 1); 224 } 225