aboutsummaryrefslogtreecommitdiff
path: root/sbin/mdconfig
diff options
context:
space:
mode:
authorSimon J. Gerraty <sjg@FreeBSD.org>2014-04-28 07:50:45 +0000
committerSimon J. Gerraty <sjg@FreeBSD.org>2014-04-28 07:50:45 +0000
commit3b8f08459569bf0faa21473e5cec2491e95c9349 (patch)
tree80f45dd81ca716bcd7ca9674581e1fc40b93cd34 /sbin/mdconfig
parent9d2ab4a62d6733c45958627ac113bdbd818d1e2a (diff)
parentb2ba55951383498f252746f618d513139da06e8e (diff)
downloadsrc-3b8f08459569bf0faa21473e5cec2491e95c9349.tar.gz
src-3b8f08459569bf0faa21473e5cec2491e95c9349.zip
Merge head
Notes
Notes: svn path=/projects/bmake/; revision=265044
Diffstat (limited to 'sbin/mdconfig')
-rw-r--r--sbin/mdconfig/Makefile6
-rw-r--r--sbin/mdconfig/mdconfig.828
-rw-r--r--sbin/mdconfig/mdconfig.c28
-rw-r--r--sbin/mdconfig/tests/Makefile13
-rw-r--r--sbin/mdconfig/tests/legacy_test.sh47
-rw-r--r--sbin/mdconfig/tests/mdconfig.test231
-rw-r--r--sbin/mdconfig/tests/run.pl329
7 files changed, 661 insertions, 21 deletions
diff --git a/sbin/mdconfig/Makefile b/sbin/mdconfig/Makefile
index be8b1b556dde..4b9b9405474d 100644
--- a/sbin/mdconfig/Makefile
+++ b/sbin/mdconfig/Makefile
@@ -1,9 +1,15 @@
# $FreeBSD$
+.include <bsd.own.mk>
+
PROG= mdconfig
MAN= mdconfig.8
DPADD= ${LIBUTIL} ${LIBGEOM} ${LIBBSDXML} ${LIBSBUF}
LDADD= -lutil -lgeom -lbsdxml -lsbuf
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
.include <bsd.prog.mk>
diff --git a/sbin/mdconfig/mdconfig.8 b/sbin/mdconfig/mdconfig.8
index 9e3089655ca6..6b52bec7830a 100644
--- a/sbin/mdconfig/mdconfig.8
+++ b/sbin/mdconfig/mdconfig.8
@@ -16,11 +16,7 @@
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
-.\" 3. All advertising materials mentioning features or use of this software
-.\" must display the following acknowledgement:
-.\" This product includes software developed by the University of
-.\" California, Berkeley and its contributors.
-.\" 4. Neither the name of the University nor the names of its contributors
+.\" 3. Neither the name of the University nor the names of its contributors
.\" may be used to endorse or promote products derived from this software
.\" without specific prior written permission.
.\"
@@ -41,7 +37,7 @@
.\"
.\" $FreeBSD$
.\"
-.Dd June 20, 2013
+.Dd November 30, 2013
.Dt MDCONFIG 8
.Os
.Sh NAME
@@ -89,6 +85,9 @@ Options indicate an action to be performed:
Attach a memory disk.
This will configure and attach a memory disk with the
parameters specified and attach it to the system.
+If the
+.Fl u Ar unit
+option is not provided, the newly created device name will be printed on stdout.
.It Fl d
Detach a memory disk from the system and release all resources.
.It Fl r
@@ -119,6 +118,8 @@ Using
backing is generally preferred instead of using
.Cm malloc
backing.
+.It Cm null
+Bitsink; all writes do nothing, all reads return zeroes.
.El
.It Fl f Ar file
Filename to use for the vnode type memory disk.
@@ -209,9 +210,12 @@ that might adversely affect the system.
Enable/disable readonly mode.
.El
.It Fl u Ar unit
-Request a specific unit number for the
+Request a specific unit number or device name for the
.Xr md 4
device instead of automatic allocation.
+If a device name is specified, it must be start with
+.Dq md
+followed by the unit number.
.El
.Pp
The last form,
@@ -237,19 +241,19 @@ with
.Pa /tmp/boot.flp
as backing storage:
.Pp
-.Dl mdconfig -a -t vnode -f /tmp/boot.flp -u 4
+.Dl mdconfig -a -t vnode -f /tmp/boot.flp -u md4
.Pp
Detach and free all resources used by
.Pa /dev/md4 :
.Pp
-.Dl mdconfig -d -u 4
+.Dl mdconfig -d -u md4
.Pp
Create a 128MByte swap backed disk, initialize an
.Xr ffs 7
file system on it, and mount it on
.Pa /tmp :
.Bd -literal -offset indent
-mdconfig -a -t swap -s 128M -u 10
+mdconfig -a -t swap -s 128M -u md10
newfs -U /dev/md10
mount /dev/md10 /tmp
chmod 1777 /tmp
@@ -263,7 +267,7 @@ are implied
.Pc :
.Bd -literal -offset indent
dd if=/dev/zero of=somebackingfile bs=1k count=5k
-mdconfig -f somebackingfile -u 0
+mdconfig -f somebackingfile -u md0
bsdlabel -w md0 auto
newfs md0c
mount /dev/md0c /mnt
@@ -290,7 +294,7 @@ is used to skip over the header information, positioning
.Pa md1.nop
to the start of the filesystem in the image.
.Bd -literal -offset indent
-mdconfig -f diskimage.img -u 1
+mdconfig -f diskimage.img -u md1
gnop create -o 512K md1
mount /dev/md1.nop /mnt
.Ed
diff --git a/sbin/mdconfig/mdconfig.c b/sbin/mdconfig/mdconfig.c
index 233058d5d661..d741c7770cfc 100644
--- a/sbin/mdconfig/mdconfig.c
+++ b/sbin/mdconfig/mdconfig.c
@@ -155,6 +155,9 @@ main(int argc, char **argv)
} else if (!strcmp(optarg, "swap")) {
mdio.md_type = MD_SWAP;
mdio.md_options |= MD_CLUSTER | MD_AUTOUNIT | MD_COMPRESS;
+ } else if (!strcmp(optarg, "null")) {
+ mdio.md_type = MD_NULL;
+ mdio.md_options |= MD_CLUSTER | MD_AUTOUNIT | MD_COMPRESS;
} else
errx(1, "unknown type: %s", optarg);
break;
@@ -287,9 +290,10 @@ main(int argc, char **argv)
}
}
- if ((mdio.md_type == MD_MALLOC || mdio.md_type == MD_SWAP) &&
- sflag == NULL)
- errx(1, "must specify -s for -t malloc or -t swap");
+ if ((mdio.md_type == MD_MALLOC || mdio.md_type == MD_SWAP ||
+ mdio.md_type == MD_NULL) && sflag == NULL)
+ errx(1, "must specify -s for -t malloc, -t swap, "
+ "or -t null");
if (mdio.md_type == MD_VNODE && mdio.md_file[0] == '\0')
errx(1, "must specify -f for -t vnode");
} else {
@@ -481,12 +485,18 @@ md_list(const char *units, int opt, const char *fflag)
printf("\n");
/* XXX: Check if it's enough to clean everything. */
geom_stats_snapshot_free(sq);
- if (((opt & OPT_UNIT) && (fflag == NULL) && ufound) ||
- ((opt & OPT_UNIT) == 0 && (fflag != NULL) && ffound) ||
- ((opt & OPT_UNIT) && (fflag != NULL) && ufound && ffound))
- return (0);
- else
- return (-1);
+ if (opt & OPT_UNIT) {
+ if (((fflag == NULL) && ufound) ||
+ ((fflag == NULL) && (units != NULL) && ufound) ||
+ ((fflag != NULL) && ffound) ||
+ ((fflag != NULL) && (units != NULL) && ufound && ffound))
+ return (0);
+ } else if (opt & OPT_LIST) {
+ if ((fflag == NULL) ||
+ ((fflag != NULL) && ffound))
+ return (0);
+ }
+ return (-1);
}
/*
diff --git a/sbin/mdconfig/tests/Makefile b/sbin/mdconfig/tests/Makefile
new file mode 100644
index 000000000000..17284bb6ee22
--- /dev/null
+++ b/sbin/mdconfig/tests/Makefile
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+TESTSDIR= ${TESTSBASE}/sbin/mdconfig
+
+TAP_TESTS_SH= legacy_test
+TAP_TESTS_SH_SED_legacy_test= 's,__PERL__,${TAP_PERL_INTERPRETER},g'
+TEST_METADATA.legacy_test+= required_programs="${TAP_PERL_INTERPRETER}"
+
+FILESDIR= ${TESTSDIR}
+FILES= mdconfig.test
+FILES+= run.pl
+
+.include <bsd.test.mk>
diff --git a/sbin/mdconfig/tests/legacy_test.sh b/sbin/mdconfig/tests/legacy_test.sh
new file mode 100644
index 000000000000..728224dbd975
--- /dev/null
+++ b/sbin/mdconfig/tests/legacy_test.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Edward Tomasz Napierała <trasz@FreeBSD.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD$
+#
+
+# This is a wrapper script to run mdconfig.test.
+
+echo "1..1"
+
+if [ `whoami` != "root" ]; then
+ echo "ok 1 # skip You need to be root to run this test."
+ exit 0
+fi
+
+TESTDIR=$(dirname $(realpath $0))
+
+__PERL__ -w -U $TESTDIR/run.pl $TESTDIR/mdconfig.test > /dev/null
+
+if [ $? -eq 0 ]; then
+ echo "ok 1"
+else
+ echo "not ok 1"
+fi
diff --git a/sbin/mdconfig/tests/mdconfig.test b/sbin/mdconfig/tests/mdconfig.test
new file mode 100644
index 000000000000..65d3670b57c3
--- /dev/null
+++ b/sbin/mdconfig/tests/mdconfig.test
@@ -0,0 +1,231 @@
+# Copyright (c) 2012 Edward Tomasz Napierała <trasz@FreeBSD.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD$
+#
+
+# This is a test for mdconfig(8) functionality. Run it as root:
+#
+# /usr/src/tools/regression/mdconfig/run /usr/src/tools/regression/mdconfig/mdconfig.test
+#
+# WARNING: Creates files in unsafe way.
+
+$ whoami
+> root
+$ umask 022
+$ truncate -s 1gb xxx
+
+$ mdconfig -l
+
+$ mdconfig -af xxx
+> md0
+
+# This awk thing is to strip the file path.
+$ mdconfig -lv | awk '{ print $1, $2, $3 }'
+> md0 vnode 1024M
+
+$ diskinfo -v /dev/md0 | expand
+> /dev/md0
+> 512 # sectorsize
+> 1073741824 # mediasize in bytes (1.0G)
+> 2097152 # mediasize in sectors
+> 0 # stripesize
+> 0 # stripeoffset
+>
+
+$ mdconfig -du 0
+
+# Check different valid syntax variations: implicit -a.
+
+$ mdconfig xxx
+> md0
+
+$ mdconfig -lv | awk '{ print $1, $2, $3 }'
+> md0 vnode 1024M
+
+$ diskinfo -v /dev/md0 | expand
+> /dev/md0
+> 512 # sectorsize
+> 1073741824 # mediasize in bytes (1.0G)
+> 2097152 # mediasize in sectors
+> 0 # stripesize
+> 0 # stripeoffset
+>
+
+$ mdconfig -du 0
+
+# Explicit -t vnode.
+
+$ mdconfig -a -t vnode -f xxx
+> md0
+
+$ mdconfig -lv | awk '{ print $1, $2, $3 }'
+> md0 vnode 1024M
+
+$ diskinfo -v /dev/md0 | expand
+> /dev/md0
+> 512 # sectorsize
+> 1073741824 # mediasize in bytes (1.0G)
+> 2097152 # mediasize in sectors
+> 0 # stripesize
+> 0 # stripeoffset
+>
+
+$ mdconfig -du 0
+
+# Size for vnodes - smaller than the actual file.
+
+$ mdconfig -a -t vnode -f xxx -s 128m
+> md0
+
+$ mdconfig -lv | awk '{ print $1, $2, $3 }'
+> md0 vnode 128M
+
+$ diskinfo -v /dev/md0 | expand
+> /dev/md0
+> 512 # sectorsize
+> 134217728 # mediasize in bytes (128M)
+> 262144 # mediasize in sectors
+> 0 # stripesize
+> 0 # stripeoffset
+>
+
+$ mdconfig -du 0
+
+# Size for vnodes - larger than the actual file.
+
+$ mdconfig -a -t vnode -f xxx -s 128g
+> md0
+
+$ mdconfig -lv | awk '{ print $1, $2, $3 }'
+> md0 vnode 128G
+
+$ diskinfo -v /dev/md0 | expand
+> /dev/md0
+> 512 # sectorsize
+> 137438953472 # mediasize in bytes (128G)
+> 268435456 # mediasize in sectors
+> 0 # stripesize
+> 0 # stripeoffset
+>
+
+$ mdconfig -du 0
+
+# Sector size for vnodes.
+
+$ mdconfig -a -t vnode -f xxx -S 2048
+> md0
+
+$ mdconfig -lv | awk '{ print $1, $2, $3 }'
+> md0 vnode 1024M
+
+$ diskinfo -v /dev/md0 | expand
+> /dev/md0
+> 2048 # sectorsize
+> 1073741824 # mediasize in bytes (1.0G)
+> 524288 # mediasize in sectors
+> 0 # stripesize
+> 0 # stripeoffset
+>
+
+$ mdconfig -du 0
+
+# Malloc type.
+
+$ mdconfig -a -t malloc -s 1g
+> md0
+
+$ mdconfig -lv | awk '{ print $1, $2, $3 }'
+> md0 malloc 1024M
+
+$ diskinfo -v /dev/md0 | expand
+> /dev/md0
+> 512 # sectorsize
+> 1073741824 # mediasize in bytes (1.0G)
+> 2097152 # mediasize in sectors
+> 0 # stripesize
+> 0 # stripeoffset
+>
+
+$ mdconfig -du 0
+
+# Swap type.
+
+$ mdconfig -a -t swap -s 1g
+> md0
+
+$ mdconfig -lv | awk '{ print $1, $2, $3 }'
+> md0 swap 1024M
+
+$ diskinfo -v /dev/md0 | expand
+> /dev/md0
+> 512 # sectorsize
+> 1073741824 # mediasize in bytes (1.0G)
+> 2097152 # mediasize in sectors
+> 0 # stripesize
+> 0 # stripeoffset
+>
+
+$ mdconfig -du 0
+
+# Attaching with a specific unit number.
+
+$ mdconfig -as 1g -u 42
+
+$ mdconfig -lv | awk '{ print $1, $2, $3 }'
+> md42 swap 1024M
+
+$ diskinfo -v /dev/md42 | expand
+> /dev/md42
+> 512 # sectorsize
+> 1073741824 # mediasize in bytes (1.0G)
+> 2097152 # mediasize in sectors
+> 0 # stripesize
+> 0 # stripeoffset
+>
+
+$ mdconfig -du 42
+
+# Querying.
+
+$ mdconfig -as 1g
+> md0
+$ mdconfig -as 2g -u 42
+
+$ mdconfig -lv | awk '{ print $1, $2, $3 }'
+> md0 swap 1024M
+> md42 swap 2048M
+
+$ mdconfig -lvu 0 | awk '{ print $1, $2, $3 }'
+> md0 swap 1024M
+
+$ mdconfig -lvu 42 | awk '{ print $1, $2, $3 }'
+> md42 swap 2048M
+
+$ mdconfig -lvu 24 | awk '{ print $1, $2, $3 }'
+
+$ mdconfig -du 42
+$ mdconfig -du 0
+
+$ rm xxx
diff --git a/sbin/mdconfig/tests/run.pl b/sbin/mdconfig/tests/run.pl
new file mode 100644
index 000000000000..383f47e62397
--- /dev/null
+++ b/sbin/mdconfig/tests/run.pl
@@ -0,0 +1,329 @@
+#!/usr/bin/perl -w -U
+
+# Copyright (c) 2007, 2008 Andreas Gruenbacher.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions, and the following disclaimer,
+# without modification, immediately at the beginning of the file.
+# 2. The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# Alternatively, this software may be distributed under the terms of the
+# GNU Public License ("GPL").
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD$
+#
+
+#
+# Possible improvements:
+#
+# - distinguish stdout and stderr output
+# - add environment variable like assignments
+# - run up to a specific line
+# - resume at a specific line
+#
+
+use strict;
+use FileHandle;
+use Getopt::Std;
+use POSIX qw(isatty setuid getcwd);
+use vars qw($opt_l $opt_v);
+
+no warnings qw(taint);
+
+$opt_l = ~0; # a really huge number
+getopts('l:v');
+
+my ($OK, $FAILED) = ("ok", "failed");
+if (isatty(fileno(STDOUT))) {
+ $OK = "\033[32m" . $OK . "\033[m";
+ $FAILED = "\033[31m\033[1m" . $FAILED . "\033[m";
+}
+
+sub exec_test($$);
+sub process_test($$$$);
+
+my ($prog, $in, $out) = ([], [], []);
+my $prog_line = 0;
+my ($tests, $failed) = (0,0);
+my $lineno;
+my $width = ($ENV{COLUMNS} || 80) >> 1;
+
+for (;;) {
+ my $line = <>; $lineno++;
+ if (defined $line) {
+ # Substitute %VAR and %{VAR} with environment variables.
+ $line =~ s[%(\w+)][$ENV{$1}]eg;
+ $line =~ s[%{(\w+)}][$ENV{$1}]eg;
+ }
+ if (defined $line) {
+ if ($line =~ s/^\s*< ?//) {
+ push @$in, $line;
+ } elsif ($line =~ s/^\s*> ?//) {
+ push @$out, $line;
+ } else {
+ process_test($prog, $prog_line, $in, $out);
+ last if $prog_line >= $opt_l;
+
+ $prog = [];
+ $prog_line = 0;
+ }
+ if ($line =~ s/^\s*\$ ?//) {
+ $prog = [ map { s/\\(.)/$1/g; $_ } split /(?<!\\)\s+/, $line ];
+ $prog_line = $lineno;
+ $in = [];
+ $out = [];
+ }
+ } else {
+ process_test($prog, $prog_line, $in, $out);
+ last;
+ }
+}
+
+my $status = sprintf("%d commands (%d passed, %d failed)",
+ $tests, $tests-$failed, $failed);
+if (isatty(fileno(STDOUT))) {
+ if ($failed) {
+ $status = "\033[31m\033[1m" . $status . "\033[m";
+ } else {
+ $status = "\033[32m" . $status . "\033[m";
+ }
+}
+print $status, "\n";
+exit $failed ? 1 : 0;
+
+
+sub process_test($$$$) {
+ my ($prog, $prog_line, $in, $out) = @_;
+
+ return unless @$prog;
+
+ my $p = [ @$prog ];
+ print "[$prog_line] \$ ", join(' ',
+ map { s/\s/\\$&/g; $_ } @$p), " -- ";
+ my $result = exec_test($prog, $in);
+ my @good = ();
+ my $nmax = (@$out > @$result) ? @$out : @$result;
+ for (my $n=0; $n < $nmax; $n++) {
+ my $use_re;
+ if (defined $out->[$n] && $out->[$n] =~ /^~ /) {
+ $use_re = 1;
+ $out->[$n] =~ s/^~ //g;
+ }
+
+ if (!defined($out->[$n]) || !defined($result->[$n]) ||
+ (!$use_re && $result->[$n] ne $out->[$n]) ||
+ ( $use_re && $result->[$n] !~ /^$out->[$n]/)) {
+ push @good, ($use_re ? '!~' : '!=');
+ }
+ else {
+ push @good, ($use_re ? '=~' : '==');
+ }
+ }
+ my $good = !(grep /!/, @good);
+ $tests++;
+ $failed++ unless $good;
+ print $good ? $OK : $FAILED, "\n";
+ if (!$good || $opt_v) {
+ for (my $n=0; $n < $nmax; $n++) {
+ my $l = defined($out->[$n]) ? $out->[$n] : "~";
+ chomp $l;
+ my $r = defined($result->[$n]) ? $result->[$n] : "~";
+ chomp $r;
+ print sprintf("%-" . ($width-3) . "s %s %s\n",
+ $r, $good[$n], $l);
+ }
+ }
+}
+
+
+sub su($) {
+ my ($user) = @_;
+
+ $user ||= "root";
+
+ my ($login, $pass, $uid, $gid) = getpwnam($user)
+ or return [ "su: user $user does not exist\n" ];
+ my @groups = ();
+ my $fh = new FileHandle("/etc/group")
+ or return [ "opening /etc/group: $!\n" ];
+ while (<$fh>) {
+ chomp;
+ my ($group, $passwd, $gid, $users) = split /:/;
+ foreach my $u (split /,/, $users) {
+ push @groups, $gid
+ if ($user eq $u);
+ }
+ }
+ $fh->close;
+
+ my $groups = join(" ", ($gid, $gid, @groups));
+ #print STDERR "[[$groups]]\n";
+ $! = 0; # reset errno
+ $> = 0;
+ $( = $gid;
+ $) = $groups;
+ if ($!) {
+ return [ "su: $!\n" ];
+ }
+ if ($uid != 0) {
+ $> = $uid;
+ #$< = $uid;
+ if ($!) {
+ return [ "su: $prog->[1]: $!\n" ];
+ }
+ }
+ #print STDERR "[($>,$<)($(,$))]";
+ return [];
+}
+
+
+sub sg($) {
+ my ($group) = @_;
+
+ my $gid = getgrnam($group)
+ or return [ "sg: group $group does not exist\n" ];
+ my %groups = map { $_ eq $gid ? () : ($_ => 1) } (split /\s/, $));
+
+ #print STDERR "<<", join("/", keys %groups), ">>\n";
+ my $groups = join(" ", ($gid, $gid, keys %groups));
+ #print STDERR "[[$groups]]\n";
+ $! = 0; # reset errno
+ if ($> != 0) {
+ my $uid = $>;
+ $> = 0;
+ $( = $gid;
+ $) = $groups;
+ $> = $uid;
+ } else {
+ $( = $gid;
+ $) = $groups;
+ }
+ if ($!) {
+ return [ "sg: $!\n" ];
+ }
+ print STDERR "[($>,$<)($(,$))]";
+ return [];
+}
+
+
+sub exec_test($$) {
+ my ($prog, $in) = @_;
+ local (*IN, *IN_DUP, *IN2, *OUT_DUP, *OUT, *OUT2);
+ my $needs_shell = (join('', @$prog) =~ /[][|<>"'`\$\*\?]/);
+
+ if ($prog->[0] eq "umask") {
+ umask oct $prog->[1];
+ return [];
+ } elsif ($prog->[0] eq "cd") {
+ if (!chdir $prog->[1]) {
+ return [ "chdir: $prog->[1]: $!\n" ];
+ }
+ $ENV{PWD} = getcwd;
+ return [];
+ } elsif ($prog->[0] eq "su") {
+ return su($prog->[1]);
+ } elsif ($prog->[0] eq "sg") {
+ return sg($prog->[1]);
+ } elsif ($prog->[0] eq "export") {
+ my ($name, $value) = split /=/, $prog->[1];
+ # FIXME: need to evaluate $value, so that things like this will work:
+ # export dir=$PWD/dir
+ $ENV{$name} = $value;
+ return [];
+ } elsif ($prog->[0] eq "unset") {
+ delete $ENV{$prog->[1]};
+ return [];
+ }
+
+ pipe *IN2, *OUT
+ or die "Can't create pipe for reading: $!";
+ open *IN_DUP, "<&STDIN"
+ or *IN_DUP = undef;
+ open *STDIN, "<&IN2"
+ or die "Can't duplicate pipe for reading: $!";
+ close *IN2;
+
+ open *OUT_DUP, ">&STDOUT"
+ or die "Can't duplicate STDOUT: $!";
+ pipe *IN, *OUT2
+ or die "Can't create pipe for writing: $!";
+ open *STDOUT, ">&OUT2"
+ or die "Can't duplicate pipe for writing: $!";
+ close *OUT2;
+
+ *STDOUT->autoflush();
+ *OUT->autoflush();
+
+ $SIG{CHLD} = 'IGNORE';
+
+ if (fork()) {
+ # Server
+ if (*IN_DUP) {
+ open *STDIN, "<&IN_DUP"
+ or die "Can't duplicate STDIN: $!";
+ close *IN_DUP
+ or die "Can't close STDIN duplicate: $!";
+ }
+ open *STDOUT, ">&OUT_DUP"
+ or die "Can't duplicate STDOUT: $!";
+ close *OUT_DUP
+ or die "Can't close STDOUT duplicate: $!";
+
+ foreach my $line (@$in) {
+ #print "> $line";
+ print OUT $line;
+ }
+ close *OUT
+ or die "Can't close pipe for writing: $!";
+
+ my $result = [];
+ while (<IN>) {
+ #print "< $_";
+ if ($needs_shell) {
+ s#^/bin/sh: line \d+: ##;
+ }
+ push @$result, $_;
+ }
+ return $result;
+ } else {
+ # Client
+ $< = $>;
+ close IN
+ or die "Can't close read end for input pipe: $!";
+ close OUT
+ or die "Can't close write end for output pipe: $!";
+ close OUT_DUP
+ or die "Can't close STDOUT duplicate: $!";
+ local *ERR_DUP;
+ open ERR_DUP, ">&STDERR"
+ or die "Can't duplicate STDERR: $!";
+ open STDERR, ">&STDOUT"
+ or die "Can't join STDOUT and STDERR: $!";
+
+ if ($needs_shell) {
+ exec ('/bin/sh', '-c', join(" ", @$prog));
+ } else {
+ exec @$prog;
+ }
+ print STDERR $prog->[0], ": $!\n";
+ exit;
+ }
+}
+