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