From 4b99f670d7e35ac87340258fadba58b5dc1543d8 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Fri, 26 Jul 2024 16:13:57 +0200 Subject: Add support for redirecting file descriptors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- sshexec.1 | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- sshexec.c | 59 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 143 insertions(+), 6 deletions(-) diff --git a/sshexec.1 b/sshexec.1 index b734c5b..0dfe121 100644 --- a/sshexec.1 +++ b/sshexec.1 @@ -6,8 +6,9 @@ sshexec - run a command through ssh(1) with normal command syntax .SH SYNOPSIS .B sshexec .RB [ { -.RI [\fBssh= ssh-command ] -.RI [\fBdir= directory ] +.RI [\fBssh=\fP ssh-command ] +.RI [\fBdir=\fP directory ] +.RB [[\fIfd\fP]{ > , >> , >| , < , <> }[ & ] = \fIfile\fP] .BR } ] [ssh-option] ...\, .I destination @@ -97,6 +98,91 @@ In the remote, change working directory to .I directory before executing .IR command . +.TP +.IB \fR[\fPfd\fP]\fP >= file +After changing working directory (assuming one is specified), +create or truncate the specified +.I file +and open it for writing, using file descriptor number +.IR fd . +(Default +.I fd +is 1 (standard output).) +.TP +.IB \fR[\fPfd\fP]\fP >>= file +After changing working directory (assuming one is specified), +create the specified +.I file +if it does not exist and open it for writing in +append-mode, using file descriptor number +.IR fd . +(Default +.I fd +is 1 (standard output).) +.TP +.IB \fR[\fPfd\fP]\fP >|= file +After changing working directory (assuming one is specified), +create the specified +.IR file , +but fail if it already exists, and open it for writing, +using file descriptor number +.IR fd . +(Default +.I fd +is 1 (standard output).) +.TP +.IB \fR[\fPfd\fP]\fP <= file +After changing working directory (assuming one is specified), +open the specified +.IR file , +for reading, using file descriptor number +.IR fd . +(Default +.I fd +is 0 (standard input).) +.TP +.IB \fR[\fPfd\fP]\fP <>= file +After changing working directory (assuming one is specified), +open the specified +.IR file , +for reading and writing, creating it if it does not +already exist, using file descriptor number +.IR fd . +(Default +.I fd +is 0 (standard input).) +.TP +.IB \fR[\fPfd\fP]\fP \fR{\fP>\fP,\fP>>\fP,\fP>|\fP}\fP&= file +Duplicate the file descriptor +.I fd +giving the new file descriptor the number +.IR file . +(Default +.I fd +is 1 (standard output).) +.TP +.IB \fR[\fPfd\fP]\fP \fR{\fP>\fP,\fP>>\fP,\fP>|\fP}\fP&=- +Close the file descriptor +.IR fd . +(Default +.I fd +is 1 (standard output).) +.TP +.IB \fR[\fPfd\fP]\fP \fR{\fP<\fP,\fP<>\fP}\fP&= file +Duplicate the file descriptor +.I fd +giving the new file descriptor the number +.IR file . +(Default +.I fd +is 0 (standard input).) +.TP +.IB \fR[\fPfd\fP]\fP \fR{\fP<\fP,\fP<>\fP}\fP&=- +Close the file descriptor +.IR fd . +(Default +.I fd +is 0 (standard input).) .SH OPERANDS The following operands are supported: diff --git a/sshexec.c b/sshexec.c index f8eaed6..1ad2cf8 100644 --- a/sshexec.c +++ b/sshexec.c @@ -19,9 +19,16 @@ static void usage(void) { exitf("usage: %s [{ %s }] [ssh-option] ... destination command [argument] ...\n", - argv0, "[ssh=command] [dir=directory]"); + argv0, "[ssh=command] [dir=directory] [[fd]{>,>>,>|,<>}[&]=file]"); } + +struct redirection { + const char *asis; + const char *escape; +}; + + static const enum optclass { NO_RECOGNISED = 0, NO_ARGUMENT, @@ -54,7 +61,7 @@ static size_t command_len = 0; } while (0) #define build_command_asis(S)\ - build_command(S, sizeof(S) - 1) + build_command(S, strlen(S)) #define finalise_command()\ build_command("", 1) @@ -122,8 +129,10 @@ main(int argc_unused, char *argv[]) { const char *dir = NULL; const char *ssh = NULL; + struct redirection *redirections = NULL; + size_t nredirections = 0; const char *destination; - char **opts; + char **opts, *p; size_t nopts; enum optclass class; const char *arg; @@ -142,17 +151,51 @@ main(int argc_unused, char *argv[]) usage();\ continue;\ } + if (*argv && !strcmp(*argv, "{")) { argv++; for (; *argv && strcmp(*argv, "}"); argv++) { STORE_OPT(&ssh, "ssh") STORE_OPT(&dir, "dir") - usage(); + + p = *argv; + while (isdigit(*p)) + p++; + if (p[0] == '>') + p = &p[1 + (p[1] == '>' || p[1] == '|')]; + else if (p[0] == '<') + p = &p[1 + (p[1] == '>')]; + else + usage(); + if (p[p[0] == '&'] != '=') + usage(); + redirections = realloc(redirections, (nredirections + 1U) * sizeof(*redirections)); + if (!redirections) + exitf("%s: could not allocate enough memory\n", argv0); + if (*p == '&') { + p = &p[1]; + memmove(p, &p[1], strlen(&p[1]) + 1U); + if (isdigit(*p)) { + p++; + while (isdigit(*p)) + p++; + } else if (*p == '-') { + p++; + } + if (*p) + usage(); + redirections[nredirections].escape = NULL; + } else { + *p++ = '\0'; + redirections[nredirections].escape = p; + } + redirections[nredirections++].asis = *argv; } if (!*argv) usage(); argv++; } + #undef STORE_OPT if (!ssh) @@ -202,6 +245,14 @@ main(int argc_unused, char *argv[]) build_command_asis(" "); build_command_escape(*argv); } + for (i = 0; i < nredirections; i++) { + build_command_asis(" "); + build_command_asis(redirections[i].asis); + if (redirections[i].escape) { + build_command_asis(" "); + build_command_escape(redirections[i].escape); + } + } finalise_command(); i = 0; -- cgit v1.2.3-70-g09d2