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