1*b3af24b4SEnji Cooper#!/usr/bin/perl -w -U 2*b3af24b4SEnji Cooper 3*b3af24b4SEnji Cooper# Copyright (c) 2007, 2008 Andreas Gruenbacher. 4*b3af24b4SEnji Cooper# All rights reserved. 5*b3af24b4SEnji Cooper# 6*b3af24b4SEnji Cooper# Redistribution and use in source and binary forms, with or without 7*b3af24b4SEnji Cooper# modification, are permitted provided that the following conditions 8*b3af24b4SEnji Cooper# are met: 9*b3af24b4SEnji Cooper# 1. Redistributions of source code must retain the above copyright 10*b3af24b4SEnji Cooper# notice, this list of conditions, and the following disclaimer, 11*b3af24b4SEnji Cooper# without modification, immediately at the beginning of the file. 12*b3af24b4SEnji Cooper# 2. The name of the author may not be used to endorse or promote products 13*b3af24b4SEnji Cooper# derived from this software without specific prior written permission. 14*b3af24b4SEnji Cooper# 15*b3af24b4SEnji Cooper# Alternatively, this software may be distributed under the terms of the 16*b3af24b4SEnji Cooper# GNU Public License ("GPL"). 17*b3af24b4SEnji Cooper# 18*b3af24b4SEnji Cooper# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19*b3af24b4SEnji Cooper# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20*b3af24b4SEnji Cooper# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21*b3af24b4SEnji Cooper# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR 22*b3af24b4SEnji Cooper# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23*b3af24b4SEnji Cooper# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24*b3af24b4SEnji Cooper# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25*b3af24b4SEnji Cooper# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26*b3af24b4SEnji Cooper# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27*b3af24b4SEnji Cooper# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28*b3af24b4SEnji Cooper# SUCH DAMAGE. 29*b3af24b4SEnji Cooper# 30*b3af24b4SEnji Cooper# $FreeBSD$ 31*b3af24b4SEnji Cooper# 32*b3af24b4SEnji Cooper 33*b3af24b4SEnji Cooper# 34*b3af24b4SEnji Cooper# Possible improvements: 35*b3af24b4SEnji Cooper# 36*b3af24b4SEnji Cooper# - distinguish stdout and stderr output 37*b3af24b4SEnji Cooper# - add environment variable like assignments 38*b3af24b4SEnji Cooper# - run up to a specific line 39*b3af24b4SEnji Cooper# - resume at a specific line 40*b3af24b4SEnji Cooper# 41*b3af24b4SEnji Cooper 42*b3af24b4SEnji Cooperuse strict; 43*b3af24b4SEnji Cooperuse FileHandle; 44*b3af24b4SEnji Cooperuse Getopt::Std; 45*b3af24b4SEnji Cooperuse POSIX qw(isatty setuid getcwd); 46*b3af24b4SEnji Cooperuse vars qw($opt_l $opt_v); 47*b3af24b4SEnji Cooper 48*b3af24b4SEnji Cooperno warnings qw(taint); 49*b3af24b4SEnji Cooper 50*b3af24b4SEnji Cooper$opt_l = ~0; # a really huge number 51*b3af24b4SEnji Coopergetopts('l:v'); 52*b3af24b4SEnji Cooper 53*b3af24b4SEnji Coopermy ($OK, $FAILED) = ("ok", "failed"); 54*b3af24b4SEnji Cooperif (isatty(fileno(STDOUT))) { 55*b3af24b4SEnji Cooper $OK = "\033[32m" . $OK . "\033[m"; 56*b3af24b4SEnji Cooper $FAILED = "\033[31m\033[1m" . $FAILED . "\033[m"; 57*b3af24b4SEnji Cooper} 58*b3af24b4SEnji Cooper 59*b3af24b4SEnji Coopersub exec_test($$); 60*b3af24b4SEnji Coopersub process_test($$$$); 61*b3af24b4SEnji Cooper 62*b3af24b4SEnji Coopermy ($prog, $in, $out) = ([], [], []); 63*b3af24b4SEnji Coopermy $prog_line = 0; 64*b3af24b4SEnji Coopermy ($tests, $failed) = (0,0); 65*b3af24b4SEnji Coopermy $lineno; 66*b3af24b4SEnji Coopermy $width = ($ENV{COLUMNS} || 80) >> 1; 67*b3af24b4SEnji Cooper 68*b3af24b4SEnji Cooperfor (;;) { 69*b3af24b4SEnji Cooper my $line = <>; $lineno++; 70*b3af24b4SEnji Cooper if (defined $line) { 71*b3af24b4SEnji Cooper # Substitute %VAR and %{VAR} with environment variables. 72*b3af24b4SEnji Cooper $line =~ s[%(\w+)][$ENV{$1}]eg; 73*b3af24b4SEnji Cooper $line =~ s[%{(\w+)}][$ENV{$1}]eg; 74*b3af24b4SEnji Cooper } 75*b3af24b4SEnji Cooper if (defined $line) { 76*b3af24b4SEnji Cooper if ($line =~ s/^\s*< ?//) { 77*b3af24b4SEnji Cooper push @$in, $line; 78*b3af24b4SEnji Cooper } elsif ($line =~ s/^\s*> ?//) { 79*b3af24b4SEnji Cooper push @$out, $line; 80*b3af24b4SEnji Cooper } else { 81*b3af24b4SEnji Cooper process_test($prog, $prog_line, $in, $out); 82*b3af24b4SEnji Cooper last if $prog_line >= $opt_l; 83*b3af24b4SEnji Cooper 84*b3af24b4SEnji Cooper $prog = []; 85*b3af24b4SEnji Cooper $prog_line = 0; 86*b3af24b4SEnji Cooper } 87*b3af24b4SEnji Cooper if ($line =~ s/^\s*\$ ?//) { 88*b3af24b4SEnji Cooper $prog = [ map { s/\\(.)/$1/g; $_ } split /(?<!\\)\s+/, $line ]; 89*b3af24b4SEnji Cooper $prog_line = $lineno; 90*b3af24b4SEnji Cooper $in = []; 91*b3af24b4SEnji Cooper $out = []; 92*b3af24b4SEnji Cooper } 93*b3af24b4SEnji Cooper } else { 94*b3af24b4SEnji Cooper process_test($prog, $prog_line, $in, $out); 95*b3af24b4SEnji Cooper last; 96*b3af24b4SEnji Cooper } 97*b3af24b4SEnji Cooper} 98*b3af24b4SEnji Cooper 99*b3af24b4SEnji Coopermy $status = sprintf("%d commands (%d passed, %d failed)", 100*b3af24b4SEnji Cooper $tests, $tests-$failed, $failed); 101*b3af24b4SEnji Cooperif (isatty(fileno(STDOUT))) { 102*b3af24b4SEnji Cooper if ($failed) { 103*b3af24b4SEnji Cooper $status = "\033[31m\033[1m" . $status . "\033[m"; 104*b3af24b4SEnji Cooper } else { 105*b3af24b4SEnji Cooper $status = "\033[32m" . $status . "\033[m"; 106*b3af24b4SEnji Cooper } 107*b3af24b4SEnji Cooper} 108*b3af24b4SEnji Cooperprint $status, "\n"; 109*b3af24b4SEnji Cooperexit $failed ? 1 : 0; 110*b3af24b4SEnji Cooper 111*b3af24b4SEnji Cooper 112*b3af24b4SEnji Coopersub process_test($$$$) { 113*b3af24b4SEnji Cooper my ($prog, $prog_line, $in, $out) = @_; 114*b3af24b4SEnji Cooper 115*b3af24b4SEnji Cooper return unless @$prog; 116*b3af24b4SEnji Cooper 117*b3af24b4SEnji Cooper my $p = [ @$prog ]; 118*b3af24b4SEnji Cooper print "[$prog_line] \$ ", join(' ', 119*b3af24b4SEnji Cooper map { s/\s/\\$&/g; $_ } @$p), " -- "; 120*b3af24b4SEnji Cooper my $result = exec_test($prog, $in); 121*b3af24b4SEnji Cooper my @good = (); 122*b3af24b4SEnji Cooper my $nmax = (@$out > @$result) ? @$out : @$result; 123*b3af24b4SEnji Cooper for (my $n=0; $n < $nmax; $n++) { 124*b3af24b4SEnji Cooper my $use_re; 125*b3af24b4SEnji Cooper if (defined $out->[$n] && $out->[$n] =~ /^~ /) { 126*b3af24b4SEnji Cooper $use_re = 1; 127*b3af24b4SEnji Cooper $out->[$n] =~ s/^~ //g; 128*b3af24b4SEnji Cooper } 129*b3af24b4SEnji Cooper 130*b3af24b4SEnji Cooper if (!defined($out->[$n]) || !defined($result->[$n]) || 131*b3af24b4SEnji Cooper (!$use_re && $result->[$n] ne $out->[$n]) || 132*b3af24b4SEnji Cooper ( $use_re && $result->[$n] !~ /^$out->[$n]/)) { 133*b3af24b4SEnji Cooper push @good, ($use_re ? '!~' : '!='); 134*b3af24b4SEnji Cooper } 135*b3af24b4SEnji Cooper else { 136*b3af24b4SEnji Cooper push @good, ($use_re ? '=~' : '=='); 137*b3af24b4SEnji Cooper } 138*b3af24b4SEnji Cooper } 139*b3af24b4SEnji Cooper my $good = !(grep /!/, @good); 140*b3af24b4SEnji Cooper $tests++; 141*b3af24b4SEnji Cooper $failed++ unless $good; 142*b3af24b4SEnji Cooper print $good ? $OK : $FAILED, "\n"; 143*b3af24b4SEnji Cooper if (!$good || $opt_v) { 144*b3af24b4SEnji Cooper for (my $n=0; $n < $nmax; $n++) { 145*b3af24b4SEnji Cooper my $l = defined($out->[$n]) ? $out->[$n] : "~"; 146*b3af24b4SEnji Cooper chomp $l; 147*b3af24b4SEnji Cooper my $r = defined($result->[$n]) ? $result->[$n] : "~"; 148*b3af24b4SEnji Cooper chomp $r; 149*b3af24b4SEnji Cooper print sprintf("%-" . ($width-3) . "s %s %s\n", 150*b3af24b4SEnji Cooper $r, $good[$n], $l); 151*b3af24b4SEnji Cooper } 152*b3af24b4SEnji Cooper } 153*b3af24b4SEnji Cooper} 154*b3af24b4SEnji Cooper 155*b3af24b4SEnji Cooper 156*b3af24b4SEnji Coopersub su($) { 157*b3af24b4SEnji Cooper my ($user) = @_; 158*b3af24b4SEnji Cooper 159*b3af24b4SEnji Cooper $user ||= "root"; 160*b3af24b4SEnji Cooper 161*b3af24b4SEnji Cooper my ($login, $pass, $uid, $gid) = getpwnam($user) 162*b3af24b4SEnji Cooper or return [ "su: user $user does not exist\n" ]; 163*b3af24b4SEnji Cooper my @groups = (); 164*b3af24b4SEnji Cooper my $fh = new FileHandle("/etc/group") 165*b3af24b4SEnji Cooper or return [ "opening /etc/group: $!\n" ]; 166*b3af24b4SEnji Cooper while (<$fh>) { 167*b3af24b4SEnji Cooper chomp; 168*b3af24b4SEnji Cooper my ($group, $passwd, $gid, $users) = split /:/; 169*b3af24b4SEnji Cooper foreach my $u (split /,/, $users) { 170*b3af24b4SEnji Cooper push @groups, $gid 171*b3af24b4SEnji Cooper if ($user eq $u); 172*b3af24b4SEnji Cooper } 173*b3af24b4SEnji Cooper } 174*b3af24b4SEnji Cooper $fh->close; 175*b3af24b4SEnji Cooper 176*b3af24b4SEnji Cooper my $groups = join(" ", ($gid, $gid, @groups)); 177*b3af24b4SEnji Cooper #print STDERR "[[$groups]]\n"; 178*b3af24b4SEnji Cooper $! = 0; # reset errno 179*b3af24b4SEnji Cooper $> = 0; 180*b3af24b4SEnji Cooper $( = $gid; 181*b3af24b4SEnji Cooper $) = $groups; 182*b3af24b4SEnji Cooper if ($!) { 183*b3af24b4SEnji Cooper return [ "su: $!\n" ]; 184*b3af24b4SEnji Cooper } 185*b3af24b4SEnji Cooper if ($uid != 0) { 186*b3af24b4SEnji Cooper $> = $uid; 187*b3af24b4SEnji Cooper #$< = $uid; 188*b3af24b4SEnji Cooper if ($!) { 189*b3af24b4SEnji Cooper return [ "su: $prog->[1]: $!\n" ]; 190*b3af24b4SEnji Cooper } 191*b3af24b4SEnji Cooper } 192*b3af24b4SEnji Cooper #print STDERR "[($>,$<)($(,$))]"; 193*b3af24b4SEnji Cooper return []; 194*b3af24b4SEnji Cooper} 195*b3af24b4SEnji Cooper 196*b3af24b4SEnji Cooper 197*b3af24b4SEnji Coopersub sg($) { 198*b3af24b4SEnji Cooper my ($group) = @_; 199*b3af24b4SEnji Cooper 200*b3af24b4SEnji Cooper my $gid = getgrnam($group) 201*b3af24b4SEnji Cooper or return [ "sg: group $group does not exist\n" ]; 202*b3af24b4SEnji Cooper my %groups = map { $_ eq $gid ? () : ($_ => 1) } (split /\s/, $)); 203*b3af24b4SEnji Cooper 204*b3af24b4SEnji Cooper #print STDERR "<<", join("/", keys %groups), ">>\n"; 205*b3af24b4SEnji Cooper my $groups = join(" ", ($gid, $gid, keys %groups)); 206*b3af24b4SEnji Cooper #print STDERR "[[$groups]]\n"; 207*b3af24b4SEnji Cooper $! = 0; # reset errno 208*b3af24b4SEnji Cooper if ($> != 0) { 209*b3af24b4SEnji Cooper my $uid = $>; 210*b3af24b4SEnji Cooper $> = 0; 211*b3af24b4SEnji Cooper $( = $gid; 212*b3af24b4SEnji Cooper $) = $groups; 213*b3af24b4SEnji Cooper $> = $uid; 214*b3af24b4SEnji Cooper } else { 215*b3af24b4SEnji Cooper $( = $gid; 216*b3af24b4SEnji Cooper $) = $groups; 217*b3af24b4SEnji Cooper } 218*b3af24b4SEnji Cooper if ($!) { 219*b3af24b4SEnji Cooper return [ "sg: $!\n" ]; 220*b3af24b4SEnji Cooper } 221*b3af24b4SEnji Cooper print STDERR "[($>,$<)($(,$))]"; 222*b3af24b4SEnji Cooper return []; 223*b3af24b4SEnji Cooper} 224*b3af24b4SEnji Cooper 225*b3af24b4SEnji Cooper 226*b3af24b4SEnji Coopersub exec_test($$) { 227*b3af24b4SEnji Cooper my ($prog, $in) = @_; 228*b3af24b4SEnji Cooper local (*IN, *IN_DUP, *IN2, *OUT_DUP, *OUT, *OUT2); 229*b3af24b4SEnji Cooper my $needs_shell = (join('', @$prog) =~ /[][|<>"'`\$\*\?]/); 230*b3af24b4SEnji Cooper 231*b3af24b4SEnji Cooper if ($prog->[0] eq "umask") { 232*b3af24b4SEnji Cooper umask oct $prog->[1]; 233*b3af24b4SEnji Cooper return []; 234*b3af24b4SEnji Cooper } elsif ($prog->[0] eq "cd") { 235*b3af24b4SEnji Cooper if (!chdir $prog->[1]) { 236*b3af24b4SEnji Cooper return [ "chdir: $prog->[1]: $!\n" ]; 237*b3af24b4SEnji Cooper } 238*b3af24b4SEnji Cooper $ENV{PWD} = getcwd; 239*b3af24b4SEnji Cooper return []; 240*b3af24b4SEnji Cooper } elsif ($prog->[0] eq "su") { 241*b3af24b4SEnji Cooper return su($prog->[1]); 242*b3af24b4SEnji Cooper } elsif ($prog->[0] eq "sg") { 243*b3af24b4SEnji Cooper return sg($prog->[1]); 244*b3af24b4SEnji Cooper } elsif ($prog->[0] eq "export") { 245*b3af24b4SEnji Cooper my ($name, $value) = split /=/, $prog->[1]; 246*b3af24b4SEnji Cooper # FIXME: need to evaluate $value, so that things like this will work: 247*b3af24b4SEnji Cooper # export dir=$PWD/dir 248*b3af24b4SEnji Cooper $ENV{$name} = $value; 249*b3af24b4SEnji Cooper return []; 250*b3af24b4SEnji Cooper } elsif ($prog->[0] eq "unset") { 251*b3af24b4SEnji Cooper delete $ENV{$prog->[1]}; 252*b3af24b4SEnji Cooper return []; 253*b3af24b4SEnji Cooper } 254*b3af24b4SEnji Cooper 255*b3af24b4SEnji Cooper pipe *IN2, *OUT 256*b3af24b4SEnji Cooper or die "Can't create pipe for reading: $!"; 257*b3af24b4SEnji Cooper open *IN_DUP, "<&STDIN" 258*b3af24b4SEnji Cooper or *IN_DUP = undef; 259*b3af24b4SEnji Cooper open *STDIN, "<&IN2" 260*b3af24b4SEnji Cooper or die "Can't duplicate pipe for reading: $!"; 261*b3af24b4SEnji Cooper close *IN2; 262*b3af24b4SEnji Cooper 263*b3af24b4SEnji Cooper open *OUT_DUP, ">&STDOUT" 264*b3af24b4SEnji Cooper or die "Can't duplicate STDOUT: $!"; 265*b3af24b4SEnji Cooper pipe *IN, *OUT2 266*b3af24b4SEnji Cooper or die "Can't create pipe for writing: $!"; 267*b3af24b4SEnji Cooper open *STDOUT, ">&OUT2" 268*b3af24b4SEnji Cooper or die "Can't duplicate pipe for writing: $!"; 269*b3af24b4SEnji Cooper close *OUT2; 270*b3af24b4SEnji Cooper 271*b3af24b4SEnji Cooper *STDOUT->autoflush(); 272*b3af24b4SEnji Cooper *OUT->autoflush(); 273*b3af24b4SEnji Cooper 274*b3af24b4SEnji Cooper $SIG{CHLD} = 'IGNORE'; 275*b3af24b4SEnji Cooper 276*b3af24b4SEnji Cooper if (fork()) { 277*b3af24b4SEnji Cooper # Server 278*b3af24b4SEnji Cooper if (*IN_DUP) { 279*b3af24b4SEnji Cooper open *STDIN, "<&IN_DUP" 280*b3af24b4SEnji Cooper or die "Can't duplicate STDIN: $!"; 281*b3af24b4SEnji Cooper close *IN_DUP 282*b3af24b4SEnji Cooper or die "Can't close STDIN duplicate: $!"; 283*b3af24b4SEnji Cooper } 284*b3af24b4SEnji Cooper open *STDOUT, ">&OUT_DUP" 285*b3af24b4SEnji Cooper or die "Can't duplicate STDOUT: $!"; 286*b3af24b4SEnji Cooper close *OUT_DUP 287*b3af24b4SEnji Cooper or die "Can't close STDOUT duplicate: $!"; 288*b3af24b4SEnji Cooper 289*b3af24b4SEnji Cooper foreach my $line (@$in) { 290*b3af24b4SEnji Cooper #print "> $line"; 291*b3af24b4SEnji Cooper print OUT $line; 292*b3af24b4SEnji Cooper } 293*b3af24b4SEnji Cooper close *OUT 294*b3af24b4SEnji Cooper or die "Can't close pipe for writing: $!"; 295*b3af24b4SEnji Cooper 296*b3af24b4SEnji Cooper my $result = []; 297*b3af24b4SEnji Cooper while (<IN>) { 298*b3af24b4SEnji Cooper #print "< $_"; 299*b3af24b4SEnji Cooper if ($needs_shell) { 300*b3af24b4SEnji Cooper s#^/bin/sh: line \d+: ##; 301*b3af24b4SEnji Cooper } 302*b3af24b4SEnji Cooper push @$result, $_; 303*b3af24b4SEnji Cooper } 304*b3af24b4SEnji Cooper return $result; 305*b3af24b4SEnji Cooper } else { 306*b3af24b4SEnji Cooper # Client 307*b3af24b4SEnji Cooper $< = $>; 308*b3af24b4SEnji Cooper close IN 309*b3af24b4SEnji Cooper or die "Can't close read end for input pipe: $!"; 310*b3af24b4SEnji Cooper close OUT 311*b3af24b4SEnji Cooper or die "Can't close write end for output pipe: $!"; 312*b3af24b4SEnji Cooper close OUT_DUP 313*b3af24b4SEnji Cooper or die "Can't close STDOUT duplicate: $!"; 314*b3af24b4SEnji Cooper local *ERR_DUP; 315*b3af24b4SEnji Cooper open ERR_DUP, ">&STDERR" 316*b3af24b4SEnji Cooper or die "Can't duplicate STDERR: $!"; 317*b3af24b4SEnji Cooper open STDERR, ">&STDOUT" 318*b3af24b4SEnji Cooper or die "Can't join STDOUT and STDERR: $!"; 319*b3af24b4SEnji Cooper 320*b3af24b4SEnji Cooper if ($needs_shell) { 321*b3af24b4SEnji Cooper exec ('/bin/sh', '-c', join(" ", @$prog)); 322*b3af24b4SEnji Cooper } else { 323*b3af24b4SEnji Cooper exec @$prog; 324*b3af24b4SEnji Cooper } 325*b3af24b4SEnji Cooper print STDERR $prog->[0], ": $!\n"; 326*b3af24b4SEnji Cooper exit; 327*b3af24b4SEnji Cooper } 328*b3af24b4SEnji Cooper} 329*b3af24b4SEnji Cooper 330