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_write_callback(struct bufferevent *, void *); 39 static void cmd_pipe_pane_error_callback(struct bufferevent *, short, void *); 40 41 const struct cmd_entry cmd_pipe_pane_entry = { 42 .name = "pipe-pane", 43 .alias = "pipep", 44 45 .args = { "ot:", 0, 1 }, 46 .usage = "[-o] " CMD_TARGET_PANE_USAGE " [command]", 47 48 .target = { 't', CMD_FIND_PANE, 0 }, 49 50 .flags = CMD_AFTERHOOK, 51 .exec = cmd_pipe_pane_exec 52 }; 53 54 static enum cmd_retval 55 cmd_pipe_pane_exec(struct cmd *self, struct cmdq_item *item) 56 { 57 struct args *args = self->args; 58 struct client *c = cmd_find_client(item, NULL, 1); 59 struct window_pane *wp = item->target.wp; 60 struct session *s = item->target.s; 61 struct winlink *wl = item->target.wl; 62 char *cmd; 63 int old_fd, pipe_fd[2], null_fd; 64 struct format_tree *ft; 65 sigset_t set, oldset; 66 67 /* Destroy the old pipe. */ 68 old_fd = wp->pipe_fd; 69 if (wp->pipe_fd != -1) { 70 bufferevent_free(wp->pipe_event); 71 close(wp->pipe_fd); 72 wp->pipe_fd = -1; 73 74 if (window_pane_destroy_ready(wp)) { 75 server_destroy_pane(wp, 1); 76 return (CMD_RETURN_NORMAL); 77 } 78 } 79 80 /* If no pipe command, that is enough. */ 81 if (args->argc == 0 || *args->argv[0] == '\0') 82 return (CMD_RETURN_NORMAL); 83 84 /* 85 * With -o, only open the new pipe if there was no previous one. This 86 * allows a pipe to be toggled with a single key, for example: 87 * 88 * bind ^p pipep -o 'cat >>~/output' 89 */ 90 if (args_has(self->args, 'o') && old_fd != -1) 91 return (CMD_RETURN_NORMAL); 92 93 /* Open the new pipe. */ 94 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_fd) != 0) { 95 cmdq_error(item, "socketpair error: %s", strerror(errno)); 96 return (CMD_RETURN_ERROR); 97 } 98 99 /* Expand the command. */ 100 ft = format_create(item->client, item, FORMAT_NONE, 0); 101 format_defaults(ft, c, s, wl, wp); 102 cmd = format_expand_time(ft, args->argv[0], time(NULL)); 103 format_free(ft); 104 105 /* Fork the child. */ 106 sigfillset(&set); 107 sigprocmask(SIG_BLOCK, &set, &oldset); 108 switch (fork()) { 109 case -1: 110 sigprocmask(SIG_SETMASK, &oldset, NULL); 111 cmdq_error(item, "fork error: %s", strerror(errno)); 112 113 free(cmd); 114 return (CMD_RETURN_ERROR); 115 case 0: 116 /* Child process. */ 117 proc_clear_signals(server_proc, 1); 118 sigprocmask(SIG_SETMASK, &oldset, NULL); 119 close(pipe_fd[0]); 120 121 if (dup2(pipe_fd[1], STDIN_FILENO) == -1) 122 _exit(1); 123 if (pipe_fd[1] != STDIN_FILENO) 124 close(pipe_fd[1]); 125 126 null_fd = open(_PATH_DEVNULL, O_WRONLY, 0); 127 if (dup2(null_fd, STDOUT_FILENO) == -1) 128 _exit(1); 129 if (dup2(null_fd, STDERR_FILENO) == -1) 130 _exit(1); 131 if (null_fd != STDOUT_FILENO && null_fd != STDERR_FILENO) 132 close(null_fd); 133 134 closefrom(STDERR_FILENO + 1); 135 136 execl(_PATH_BSHELL, "sh", "-c", cmd, (char *) NULL); 137 _exit(1); 138 default: 139 /* Parent process. */ 140 sigprocmask(SIG_SETMASK, &oldset, NULL); 141 close(pipe_fd[1]); 142 143 wp->pipe_fd = pipe_fd[0]; 144 wp->pipe_off = EVBUFFER_LENGTH(wp->event->input); 145 146 wp->pipe_event = bufferevent_new(wp->pipe_fd, NULL, 147 cmd_pipe_pane_write_callback, cmd_pipe_pane_error_callback, 148 wp); 149 bufferevent_enable(wp->pipe_event, EV_WRITE); 150 151 setblocking(wp->pipe_fd, 0); 152 153 free(cmd); 154 return (CMD_RETURN_NORMAL); 155 } 156 } 157 158 static void 159 cmd_pipe_pane_write_callback(__unused struct bufferevent *bufev, void *data) 160 { 161 struct window_pane *wp = data; 162 163 log_debug("%%%u pipe empty", wp->id); 164 if (window_pane_destroy_ready(wp)) 165 server_destroy_pane(wp, 1); 166 } 167 168 static void 169 cmd_pipe_pane_error_callback(__unused struct bufferevent *bufev, 170 __unused short what, void *data) 171 { 172 struct window_pane *wp = data; 173 174 log_debug("%%%u pipe error", wp->id); 175 176 bufferevent_free(wp->pipe_event); 177 close(wp->pipe_fd); 178 wp->pipe_fd = -1; 179 180 if (window_pane_destroy_ready(wp)) 181 server_destroy_pane(wp, 1); 182 } 183