Skip to content

Commit

Permalink
Support wrapping with arbitrary I/O channels
Browse files Browse the repository at this point in the history
Issue: #78
  • Loading branch information
dspinellis committed Aug 19, 2017
1 parent 0a014e7 commit 92ab854
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 56 deletions.
53 changes: 40 additions & 13 deletions core-tools/src/dgsh-wrap.1
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,17 @@
dgsh-wrap \- allow any program to participate in an dgsh pipeline
.SH SYNOPSIS
\fBdgsh-wrap\fP
[\fB-S\fP] [\fB-deImO\fP] \fIprogram\fP [\fIprogram-arguments\fP ...]
[\fB-S\fP]
[\fB-i\fP \fB0\fP|\fBa\fP]
[\fB-o\fP \fB0\fP|\fBa\fP]
[\fB-eIO\fP]
\fIprogram\fP [\fIprogram-arguments\fP ...]

#!/usr/libexec/dgsh/\fBdgsh-wrap\fP
\fB-s\fP [\fB-deImO\fP] [\fIprogram-arguments\fP ...]
\fB-s\fP
[\fB-i\fP \fB0\fP|\fBa\fP]
[\fB-o\fP \fB0\fP|\fBa\fP]
[\fB-eIO\fP] [\fIprogram-arguments\fP ...]

.SH DESCRIPTION
\fIdgsh-wrap\fP takes as arguments an absolute path or the name
Expand Down Expand Up @@ -49,32 +56,42 @@ wrapped with \fIdgsh-wrap\fP in order to communicate their particular
I/O requirements.

.SH OPTIONS
.IP "\fB\-d\fP
The wrapped program is deaf, it will not read any input.

.IP "\fB\-e\fP
Replace \fI<|\fP and \fI>|\fP strings embedded within arguments,
with names of input file descriptor paths.
By default, only standalone arguments are thus replaced.

.IP "\fB\-i\fP \fB0\fP|\fBa\fP
Specify the wrapped program's number of input channels.
The \fB0\fP character specifies that the program does not read any input.
The \fBa\fP character specifies that the program can read an arbitrary
number of input streams;
these will be automatically supplied by \fIdgsh-wrap\fP as file descriptor
command line arguments.

.IP "\fB\-I\fP
Do not include the standard input to the command line arguments,
when replacing \fI<|\fP arguments with names of input file descriptor paths.
This will result in the standard input becoming available to the
command as its standard input, rather than as a command line argument.
Without this option, the first path with be \fI/proc/self/fd/0\fP.
Without this option, the first path with be \fI/dev/fd/0\fP.
When this option is given, the program will require one input channel
more than those specified by the \fI<|\fP arguments.

.IP "\fB\-m\fP
The wrapped program is mute, it will not produce any output.
.IP "\fB\-o\fP \fB0\fP|\fBa\fP
Specify the wrapped program's number of output channels.
The \fB0\fP character specifies that the program does not produce any output.
The \fBa\fP character specifies that the program can write to an arbitrary
number of output streams;
these will be automatically supplied by \fIdgsh-wrap\fP as file descriptor
command line arguments.

.IP "\fB\-O\fP
Do not include the standard output to the command line arguments,
when replacing \fI>|\fP arguments with names of output file descriptor paths.
This will result in the standard output becoming available to the
command as its standard output, rather than as a command line argument.
Without this option the first path with be \fI/proc/self/fd/1\fP.
Without this option the first path with be \fI/dev/fd/1\fP.
When this option is given, the program will require one output channel
more than those specified by the \fI>|\fP arguments.

Expand Down Expand Up @@ -117,7 +134,7 @@ Wrap the \fIecho\fP command, specifying that it accepts no input.
.ft C
.ps -1
.nf
dgsh-wrap -d echo hi
dgsh-wrap -i 0 echo hi
.fi
.ps +1
.ft P
Expand All @@ -126,7 +143,7 @@ Wrap the \fIcp\fP command, specifying that it does not perform any I/O.
.ft C
.ps -1
.nf
dgsh-wrap -dm cp src-file dest-file
dgsh-wrap -i 0 -o 0 cp src-file dest-file
.fi
.ps +1
.ft P
Expand All @@ -135,7 +152,7 @@ Wrap the \fIpaste\fP command, supplying its two arguments.
.ft C
.ps -1
.nf
dgsh-wrap -I /usr/bin/paste "<|" "<|"
dgsh-wrap /usr/bin/paste "<|" "<|"
.fi
.ps +1
.ft P
Expand All @@ -145,7 +162,17 @@ input and uses an additional input argument.
.ft C
.ps -1
.nf
dgsh-wrap /usr/bin/paste - "<|"
dgsh-wrap -I /usr/bin/paste - "<|"
.fi
.ps +1
.ft P
.PP
Wrap the \fIpaste\fP command, so that it will process all input channels
provided to it.
.ft C
.ps -1
.nf
dgsh-wrap -i a /usr/bin/paste
.fi
.ps +1
.ft P
Expand Down
120 changes: 94 additions & 26 deletions core-tools/src/dgsh-wrap.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@

/*
* Examples:
* dgsh-wrap -d yes | fsck
* tar cf - / | dgsh-wrap -m dd of=/dev/st0
* dgsh-wrap -i 0 yes | fsck
* tar cf - / | dgsh-wrap -o 0 dd of=/dev/st0
* ls | dgsh-wrap /usr/bin/sort -k5n | more
*/

Expand All @@ -46,11 +46,12 @@
static void
usage(void)
{
fputs("Usage:\tdgsh-wrap [-S] [-dImO] program [program-arguments ...]\n"
"\tdgsh-wrap -s [-diImoO] [program-arguments ...]\n"
"-d\t" "Requires no input (deaf)\n"
fputs("Usage:\tdgsh-wrap [-S] [-i 0|a] [-o 0|a] [-eIO] program [program-arguments ...]\n"
"\tdgsh-wrap -s [-i 0|a] [-o 0|a] [-eIO] [program-arguments ...]\n"
"-e\t" "Process <| and >| embedded in arguments\n"
"-i 0|a\t" "Process no (0) or arbitrary (a) input channels\n"
"-I\t" "Do not provide standard input as a <| arg\n"
"-m\t" "Provides no output; (mute)\n"
"-o 0|a\t" "Process no (0) or arbitrary (a) output channels\n"
"-O\t" "Do not provide standard output as a >| arg\n"
"-S\t" "Process flags and program as a #! interpreter\n"
"-s\t" "Process flags as a #! interpreter\n"
Expand Down Expand Up @@ -274,17 +275,34 @@ process_embedded_io_arg(char **arg, const char *special, int **fdptr)
* Replace an instance of the string special (e.g. "<|") matching an arg
* with /dev/fd/N, where N is the integer pointed
* by fdptr, and increase fdptr to point to the next integer.
* If special is null, then the replacement is always made.
*/
static void
process_standalone_io_arg(char **arg, const char *special, int **fdptr)
{
if (strcmp(*arg, special) != 0)
if (special != NULL && strcmp(*arg, special) != 0)
return;
if (asprintf(arg, "/dev/fd/%d", **fdptr) == -1)
err(1, "asprintf out of memory");
(*fdptr)++;
}

/*
* Increment the channels specified by the given variable.
* Ensure that the corresponding variable is not
* already set to an arbitrary number of channels.
*/
static void
increment_channels(int *var)
{
if (*var == -1) {
fputs("I/O channel arguments cannot be combined with an arbitrary I/O file specification\n",
stderr);
exit(1);
}
(*var)++;
}

int
main(int argc, char *argv[])
{
Expand All @@ -299,6 +317,7 @@ main(int argc, char *argv[])
bool embedded_args = false;
/* Pass stdin/stdout as a command-line argument */
bool stdin_as_arg = true, stdout_as_arg = true;
bool supply_input_args = false, supply_output_args = false;


debug_level = getenv("DGSH_DEBUG_LEVEL");
Expand All @@ -324,12 +343,18 @@ main(int argc, char *argv[])
* first non-flag argument.
* Therefore, adjust argc, argv on entry and optind on exit.
*/
while ((ch = getopt(argc, argv, "+deImOSs")) != -1) {
while ((ch = getopt(argc, argv, "+ei:Io:OSs")) != -1) {
DPRINTF(4, "getopt switch=%c", ch);
switch (ch) {
case 'd':
case 'i':
nflags++;
ninputs = 0;
if (strcmp(optarg, "0") == 0)
ninputs = 0;
else if (strcmp(optarg, "a") == 0) {
ninputs = -1;
supply_input_args = true;
} else
usage();
break;
case 'e':
embedded_args = true;
Expand All @@ -339,9 +364,15 @@ main(int argc, char *argv[])
stdin_as_arg = false;
nflags++;
break;
case 'm':
case 'o':
nflags++;
noutputs = 0;
if (strcmp(optarg, "0") == 0)
noutputs = 0;
else if (strcmp(optarg, "a") == 0) {
noutputs = -1;
supply_output_args = true;
} else
usage();
break;
case 'O':
stdout_as_arg = false;
Expand Down Expand Up @@ -406,14 +437,14 @@ main(int argc, char *argv[])
for (i = optind + 1; i < argc; i++) {
if (embedded_args) {
for (p = argv[i]; p = strstr(p, "<|"); p += 2)
ninputs++;
increment_channels(&ninputs);
for (p = argv[i]; p = strstr(p, ">|"); p += 2)
noutputs++;
increment_channels(&noutputs);
} else {
if (strcmp(argv[i], "<|") == 0)
ninputs++;
increment_channels(&ninputs);
if (strcmp(argv[i], ">|") == 0)
noutputs++;
increment_channels(&noutputs);
}
}
/*
Expand All @@ -434,20 +465,57 @@ main(int argc, char *argv[])
&input_fds, &output_fds);

/*
* Substitute special arguments "<|" and ">|" with file descriptor
* Substitute special arguments "<|" and ">|" with or add file descriptor
* paths /dev/fd/N using the fds received from negotiation.
*/
int *inptr = stdin_as_arg ? input_fds : input_fds + 1;
if (supply_input_args) {
if (!stdin_as_arg)
ninputs--;

/* Create space for arguments to add */
char **nargv = xmalloc((argc + ninputs + 1) * sizeof(char *));
memcpy(nargv, argv, argc * sizeof(char *));
memset(argv + argc, 0, (ninputs + 1) * sizeof(char *));
argv = nargv;

/* Add arguments */
for (i = argc; i < argc + ninputs; i++)
process_standalone_io_arg(&argv[i], NULL, &inptr);
argc += ninputs;
argv[argc] = NULL;
} else {
for (i = optind + 1; i < argc; i++) {
if (embedded_args)
while (process_embedded_io_arg(&argv[i], "<|", &inptr))
;
else
process_standalone_io_arg(&argv[i], "<|", &inptr);
}
}
int *outptr = stdout_as_arg ? output_fds : output_fds + 1;
for (i = optind + 1; i < argc; i++) {
if (embedded_args) {
while (process_embedded_io_arg(&argv[i], "<|", &inptr))
;
while (process_embedded_io_arg(&argv[i], ">|", &outptr))
;
} else {
process_standalone_io_arg(&argv[i], "<|", &inptr);
process_standalone_io_arg(&argv[i], ">|", &outptr);
if (supply_output_args) {
if (!stdout_as_arg)
noutputs--;

/* Create space for arguments to add */
char **nargv = xmalloc((argc + noutputs + 1) * sizeof(char *));
memcpy(nargv, argv, argc * sizeof(char *));
memset(argv + argc, 0, (noutputs + 1) * sizeof(char *));
argv = nargv;

/* Add arguments */
for (i = argc; i < argc + noutputs; i++)
process_standalone_io_arg(&argv[i], NULL, &outptr);
argc += noutputs;
argv[argc] = NULL;
} else {
for (i = optind + 1; i < argc; i++) {
if (embedded_args)
while (process_embedded_io_arg(&argv[i], ">|", &outptr))
;
else
process_standalone_io_arg(&argv[i], ">|", &outptr);
}
}
DPRINTF(4, "Arguments to execvp after substitung <| and >|");
Expand Down
1 change: 1 addition & 0 deletions core-tools/tests-regression/dgsh-wrap/paste3.ok
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0 1
1 change: 1 addition & 0 deletions core-tools/tests-regression/dgsh-wrap/paste4.ok
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0 1
2 changes: 2 additions & 0 deletions core-tools/tests-regression/dgsh-wrap/tee1.ok
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ahi
bhi
2 changes: 2 additions & 0 deletions core-tools/tests-regression/dgsh-wrap/tee2.ok
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ahi
bhi
46 changes: 31 additions & 15 deletions core-tools/tests-regression/test-wrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,17 @@ ensure_same()
export PATH="$PATH:bin"

# Test that echo is wrapped as deaf
$DGSH -c 'dgsh-enumerate 1 | {{ dgsh-wrap -d echo hi ; dgsh-wrap dd 2>/dev/null ; }} | cat' >dgsh-wrap/echo-deaf.test
$DGSH -c 'dgsh-enumerate 1 | {{ dgsh-wrap -i 0 echo hi ; dgsh-wrap dd 2>/dev/null ; }} | cat' >dgsh-wrap/echo-deaf.test
ensure_same echo-deaf

# Test that echo is wrapped as deaf when wrapped as script with supplied exec
echo "#!$TOP/build/libexec/dgsh/dgsh-wrap -S -d `which echo`" >dgsh-wrap/echo-S
echo "#!$TOP/build/libexec/dgsh/dgsh-wrap -S -i 0 `which echo`" >dgsh-wrap/echo-S
chmod +x dgsh-wrap/echo-S
$DGSH -c 'dgsh-enumerate 1 | {{ dgsh-wrap/echo-S hi ; dgsh-wrap dd 2>/dev/null ; }} | cat' >dgsh-wrap/echo-S.test
ensure_same echo-S

# Test that echo is wrapped as deaf when wrapped as script with implied exec
echo "#!$TOP/build/libexec/dgsh/dgsh-wrap -s -d" >dgsh-wrap/echo
echo "#!$TOP/build/libexec/dgsh/dgsh-wrap -s -i 0" >dgsh-wrap/echo
chmod +x dgsh-wrap/echo
$DGSH -c 'dgsh-enumerate 1 | {{ dgsh-wrap/echo hi ; dgsh-wrap dd 2>/dev/null ; }} | cat' >dgsh-wrap/echo-s.test
ensure_same echo-s
Expand All @@ -59,18 +59,34 @@ the first three file descriptors under /dev/fd. In FreeBSD systems
consider mounting fdescfs(5) on /dev/fd to avoid this problem.
EOF
exit 1
else
# Test stand-alone path substitution (with stdin)
$DGSH -c 'dgsh-enumerate 2 | dgsh-wrap paste "<|" "<|" ' >dgsh-wrap/paste2.test
ensure_same paste2

# Test stand-alone path substitution (without stdin)
$DGSH -c 'dgsh-enumerate 2 | dgsh-wrap -I /usr/bin/paste - "<|" ' >dgsh-wrap/paste1.test
ensure_same paste1

# Test substitution of embedded arguments
$DGSH -c 'dgsh-enumerate 1 | {{ dgsh-wrap -e dd "if=<|" "of=>|" 2>/dev/null ; }} | cat' >dgsh-wrap/dd-args.test
ensure_same dd-args
fi

# Test stand-alone input path substitution (with stdin)
$DGSH -c 'dgsh-enumerate 2 | dgsh-wrap paste "<|" "<|" ' >dgsh-wrap/paste1.test
ensure_same paste1

# Test stand-alone input path substitution (without stdin)
$DGSH -c 'dgsh-enumerate 2 | dgsh-wrap -I /usr/bin/paste - "<|" ' >dgsh-wrap/paste2.test
ensure_same paste2

# Test arbitrary input argument provision (with stdin)
$DGSH -c 'dgsh-enumerate 2 | dgsh-wrap -i a paste' >dgsh-wrap/paste3.test
ensure_same paste3

# Test arbitrary input argument provision (without stdin)
$DGSH -c 'dgsh-enumerate 2 | dgsh-wrap -i a -I /usr/bin/paste -' >dgsh-wrap/paste4.test
ensure_same paste4

# Test stand-alone output path substitution (without stdout)
$DGSH -c 'echo hi | dgsh-wrap -O /usr/bin/tee ">|" | {{ sed "s/^/a/" ; sed "s/^/b/" ; }} | cat' >dgsh-wrap/tee1.test
ensure_same tee1

# Test arbitrary input argument provision (without stdout)
$DGSH -c 'echo hi | dgsh-wrap -o a -O /usr/bin/tee | {{ sed "s/^/a/" ; sed "s/^/b/" ; }} | cat' >dgsh-wrap/tee2.test
ensure_same tee2

# Test substitution of embedded arguments
$DGSH -c 'dgsh-enumerate 1 | {{ dgsh-wrap -e dd "if=<|" "of=>|" 2>/dev/null ; }} | cat' >dgsh-wrap/dd-args.test
ensure_same dd-args

exit 0
Loading

0 comments on commit 92ab854

Please sign in to comment.