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# $FreeBSD$ 31b3af24b4SEnji Cooper# 32b3af24b4SEnji Cooper 33b3af24b4SEnji Cooper# 34b3af24b4SEnji Cooper# Possible improvements: 35b3af24b4SEnji Cooper# 36b3af24b4SEnji Cooper# - distinguish stdout and stderr output 37b3af24b4SEnji Cooper# - add environment variable like assignments 38b3af24b4SEnji Cooper# - run up to a specific line 39b3af24b4SEnji Cooper# - resume at a specific line 40b3af24b4SEnji Cooper# 41b3af24b4SEnji Cooper 42b3af24b4SEnji Cooperuse strict; 43b3af24b4SEnji Cooperuse FileHandle; 44b3af24b4SEnji Cooperuse Getopt::Std; 45b3af24b4SEnji Cooperuse POSIX qw(isatty setuid getcwd); 46b3af24b4SEnji Cooperuse vars qw($opt_l $opt_v); 47b3af24b4SEnji Cooper 48b3af24b4SEnji Cooperno warnings qw(taint); 49b3af24b4SEnji Cooper 50b3af24b4SEnji Cooper$opt_l = ~0; # a really huge number 51b3af24b4SEnji Coopergetopts('l:v'); 52b3af24b4SEnji Cooper 53b3af24b4SEnji Coopermy ($OK, $FAILED) = ("ok", "failed"); 54b3af24b4SEnji Cooperif (isatty(fileno(STDOUT))) { 55b3af24b4SEnji Cooper $OK = "\033[32m" . $OK . "\033[m"; 56b3af24b4SEnji Cooper $FAILED = "\033[31m\033[1m" . $FAILED . "\033[m"; 57b3af24b4SEnji Cooper} 58b3af24b4SEnji Cooper 59b3af24b4SEnji Coopersub exec_test($$); 60b3af24b4SEnji Coopersub process_test($$$$); 61b3af24b4SEnji Cooper 62b3af24b4SEnji Coopermy ($prog, $in, $out) = ([], [], []); 63b3af24b4SEnji Coopermy $prog_line = 0; 64b3af24b4SEnji Coopermy ($tests, $failed) = (0,0); 65b3af24b4SEnji Coopermy $lineno; 66b3af24b4SEnji Coopermy $width = ($ENV{COLUMNS} || 80) >> 1; 67b3af24b4SEnji Cooper 68b3af24b4SEnji Cooperfor (;;) { 69b3af24b4SEnji Cooper my $line = <>; $lineno++; 70b3af24b4SEnji Cooper if (defined $line) { 71b3af24b4SEnji Cooper # Substitute %VAR and %{VAR} with environment variables. 72b3af24b4SEnji Cooper $line =~ s[%(\w+)][$ENV{$1}]eg; 73*8adbc737SEitan Adler $line =~ s[%\{(\w+)\}][$ENV{$1}]eg; 74b3af24b4SEnji Cooper } 75b3af24b4SEnji Cooper if (defined $line) { 76b3af24b4SEnji Cooper if ($line =~ s/^\s*< ?//) { 77b3af24b4SEnji Cooper push @$in, $line; 78b3af24b4SEnji Cooper } elsif ($line =~ s/^\s*> ?//) { 79b3af24b4SEnji Cooper push @$out, $line; 80b3af24b4SEnji Cooper } else { 81b3af24b4SEnji Cooper process_test($prog, $prog_line, $in, $out); 82b3af24b4SEnji Cooper last if $prog_line >= $opt_l; 83b3af24b4SEnji Cooper 84b3af24b4SEnji Cooper $prog = []; 85b3af24b4SEnji Cooper $prog_line = 0; 86b3af24b4SEnji Cooper } 87b3af24b4SEnji Cooper if ($line =~ s/^\s*\$ ?//) { 88b3af24b4SEnji Cooper $prog = [ map { s/\\(.)/$1/g; $_ } split /(?<!\\)\s+/, $line ]; 89b3af24b4SEnji Cooper $prog_line = $lineno; 90b3af24b4SEnji Cooper $in = []; 91b3af24b4SEnji Cooper $out = []; 92b3af24b4SEnji Cooper } 93b3af24b4SEnji Cooper } else { 94b3af24b4SEnji Cooper process_test($prog, $prog_line, $in, $out); 95b3af24b4SEnji Cooper last; 96b3af24b4SEnji Cooper } 97b3af24b4SEnji Cooper} 98b3af24b4SEnji Cooper 99b3af24b4SEnji Coopermy $status = sprintf("%d commands (%d passed, %d failed)", 100b3af24b4SEnji Cooper $tests, $tests-$failed, $failed); 101b3af24b4SEnji Cooperif (isatty(fileno(STDOUT))) { 102b3af24b4SEnji Cooper if ($failed) { 103b3af24b4SEnji Cooper $status = "\033[31m\033[1m" . $status . "\033[m"; 104b3af24b4SEnji Cooper } else { 105b3af24b4SEnji Cooper $status = "\033[32m" . $status . "\033[m"; 106b3af24b4SEnji Cooper } 107b3af24b4SEnji Cooper} 108b3af24b4SEnji Cooperprint $status, "\n"; 109b3af24b4SEnji Cooperexit $failed ? 1 : 0; 110b3af24b4SEnji Cooper 111b3af24b4SEnji Cooper 112b3af24b4SEnji Coopersub process_test($$$$) { 113b3af24b4SEnji Cooper my ($prog, $prog_line, $in, $out) = @_; 114b3af24b4SEnji Cooper 115b3af24b4SEnji Cooper return unless @$prog; 116b3af24b4SEnji Cooper 117b3af24b4SEnji Cooper my $p = [ @$prog ]; 118b3af24b4SEnji Cooper print "[$prog_line] \$ ", join(' ', 119b3af24b4SEnji Cooper map { s/\s/\\$&/g; $_ } @$p), " -- "; 120b3af24b4SEnji Cooper my $result = exec_test($prog, $in); 121b3af24b4SEnji Cooper my @good = (); 122b3af24b4SEnji Cooper my $nmax = (@$out > @$result) ? @$out : @$result; 123b3af24b4SEnji Cooper for (my $n=0; $n < $nmax; $n++) { 124b3af24b4SEnji Cooper my $use_re; 125b3af24b4SEnji Cooper if (defined $out->[$n] && $out->[$n] =~ /^~ /) { 126b3af24b4SEnji Cooper $use_re = 1; 127b3af24b4SEnji Cooper $out->[$n] =~ s/^~ //g; 128b3af24b4SEnji Cooper } 129b3af24b4SEnji Cooper 130b3af24b4SEnji Cooper if (!defined($out->[$n]) || !defined($result->[$n]) || 131b3af24b4SEnji Cooper (!$use_re && $result->[$n] ne $out->[$n]) || 132b3af24b4SEnji Cooper ( $use_re && $result->[$n] !~ /^$out->[$n]/)) { 133b3af24b4SEnji Cooper push @good, ($use_re ? '!~' : '!='); 134b3af24b4SEnji Cooper } 135b3af24b4SEnji Cooper else { 136b3af24b4SEnji Cooper push @good, ($use_re ? '=~' : '=='); 137b3af24b4SEnji Cooper } 138b3af24b4SEnji Cooper } 139b3af24b4SEnji Cooper my $good = !(grep /!/, @good); 140b3af24b4SEnji Cooper $tests++; 141b3af24b4SEnji Cooper $failed++ unless $good; 142b3af24b4SEnji Cooper print $good ? $OK : $FAILED, "\n"; 143b3af24b4SEnji Cooper if (!$good || $opt_v) { 144b3af24b4SEnji Cooper for (my $n=0; $n < $nmax; $n++) { 145b3af24b4SEnji Cooper my $l = defined($out->[$n]) ? $out->[$n] : "~"; 146b3af24b4SEnji Cooper chomp $l; 147b3af24b4SEnji Cooper my $r = defined($result->[$n]) ? $result->[$n] : "~"; 148b3af24b4SEnji Cooper chomp $r; 149b3af24b4SEnji Cooper print sprintf("%-" . ($width-3) . "s %s %s\n", 150b3af24b4SEnji Cooper $r, $good[$n], $l); 151b3af24b4SEnji Cooper } 152b3af24b4SEnji Cooper } 153b3af24b4SEnji Cooper} 154b3af24b4SEnji Cooper 155b3af24b4SEnji Cooper 156b3af24b4SEnji Coopersub su($) { 157b3af24b4SEnji Cooper my ($user) = @_; 158b3af24b4SEnji Cooper 159b3af24b4SEnji Cooper $user ||= "root"; 160b3af24b4SEnji Cooper 161b3af24b4SEnji Cooper my ($login, $pass, $uid, $gid) = getpwnam($user) 162b3af24b4SEnji Cooper or return [ "su: user $user does not exist\n" ]; 163b3af24b4SEnji Cooper my @groups = (); 164b3af24b4SEnji Cooper my $fh = new FileHandle("/etc/group") 165b3af24b4SEnji Cooper or return [ "opening /etc/group: $!\n" ]; 166b3af24b4SEnji Cooper while (<$fh>) { 167b3af24b4SEnji Cooper chomp; 168b3af24b4SEnji Cooper my ($group, $passwd, $gid, $users) = split /:/; 169b3af24b4SEnji Cooper foreach my $u (split /,/, $users) { 170b3af24b4SEnji Cooper push @groups, $gid 171b3af24b4SEnji Cooper if ($user eq $u); 172b3af24b4SEnji Cooper } 173b3af24b4SEnji Cooper } 174b3af24b4SEnji Cooper $fh->close; 175b3af24b4SEnji Cooper 176b3af24b4SEnji Cooper my $groups = join(" ", ($gid, $gid, @groups)); 177b3af24b4SEnji Cooper #print STDERR "[[$groups]]\n"; 178b3af24b4SEnji Cooper $! = 0; # reset errno 179b3af24b4SEnji Cooper $> = 0; 180b3af24b4SEnji Cooper $( = $gid; 181b3af24b4SEnji Cooper $) = $groups; 182b3af24b4SEnji Cooper if ($!) { 183b3af24b4SEnji Cooper return [ "su: $!\n" ]; 184b3af24b4SEnji Cooper } 185b3af24b4SEnji Cooper if ($uid != 0) { 186b3af24b4SEnji Cooper $> = $uid; 187b3af24b4SEnji Cooper #$< = $uid; 188b3af24b4SEnji Cooper if ($!) { 189b3af24b4SEnji Cooper return [ "su: $prog->[1]: $!\n" ]; 190b3af24b4SEnji Cooper } 191b3af24b4SEnji Cooper } 192b3af24b4SEnji Cooper #print STDERR "[($>,$<)($(,$))]"; 193b3af24b4SEnji Cooper return []; 194b3af24b4SEnji Cooper} 195b3af24b4SEnji Cooper 196b3af24b4SEnji Cooper 197b3af24b4SEnji Coopersub sg($) { 198b3af24b4SEnji Cooper my ($group) = @_; 199b3af24b4SEnji Cooper 200b3af24b4SEnji Cooper my $gid = getgrnam($group) 201b3af24b4SEnji Cooper or return [ "sg: group $group does not exist\n" ]; 202b3af24b4SEnji Cooper my %groups = map { $_ eq $gid ? () : ($_ => 1) } (split /\s/, $)); 203b3af24b4SEnji Cooper 204b3af24b4SEnji Cooper #print STDERR "<<", join("/", keys %groups), ">>\n"; 205b3af24b4SEnji Cooper my $groups = join(" ", ($gid, $gid, keys %groups)); 206b3af24b4SEnji Cooper #print STDERR "[[$groups]]\n"; 207b3af24b4SEnji Cooper $! = 0; # reset errno 208b3af24b4SEnji Cooper if ($> != 0) { 209b3af24b4SEnji Cooper my $uid = $>; 210b3af24b4SEnji Cooper $> = 0; 211b3af24b4SEnji Cooper $( = $gid; 212b3af24b4SEnji Cooper $) = $groups; 213b3af24b4SEnji Cooper $> = $uid; 214b3af24b4SEnji Cooper } else { 215b3af24b4SEnji Cooper $( = $gid; 216b3af24b4SEnji Cooper $) = $groups; 217b3af24b4SEnji Cooper } 218b3af24b4SEnji Cooper if ($!) { 219b3af24b4SEnji Cooper return [ "sg: $!\n" ]; 220b3af24b4SEnji Cooper } 221b3af24b4SEnji Cooper print STDERR "[($>,$<)($(,$))]"; 222b3af24b4SEnji Cooper return []; 223b3af24b4SEnji Cooper} 224b3af24b4SEnji Cooper 225b3af24b4SEnji Cooper 226b3af24b4SEnji Coopersub exec_test($$) { 227b3af24b4SEnji Cooper my ($prog, $in) = @_; 228b3af24b4SEnji Cooper local (*IN, *IN_DUP, *IN2, *OUT_DUP, *OUT, *OUT2); 229b3af24b4SEnji Cooper my $needs_shell = (join('', @$prog) =~ /[][|<>"'`\$\*\?]/); 230b3af24b4SEnji Cooper 231b3af24b4SEnji Cooper if ($prog->[0] eq "umask") { 232b3af24b4SEnji Cooper umask oct $prog->[1]; 233b3af24b4SEnji Cooper return []; 234b3af24b4SEnji Cooper } elsif ($prog->[0] eq "cd") { 235b3af24b4SEnji Cooper if (!chdir $prog->[1]) { 236b3af24b4SEnji Cooper return [ "chdir: $prog->[1]: $!\n" ]; 237b3af24b4SEnji Cooper } 238b3af24b4SEnji Cooper $ENV{PWD} = getcwd; 239b3af24b4SEnji Cooper return []; 240b3af24b4SEnji Cooper } elsif ($prog->[0] eq "su") { 241b3af24b4SEnji Cooper return su($prog->[1]); 242b3af24b4SEnji Cooper } elsif ($prog->[0] eq "sg") { 243b3af24b4SEnji Cooper return sg($prog->[1]); 244b3af24b4SEnji Cooper } elsif ($prog->[0] eq "export") { 245b3af24b4SEnji Cooper my ($name, $value) = split /=/, $prog->[1]; 246b3af24b4SEnji Cooper # FIXME: need to evaluate $value, so that things like this will work: 247b3af24b4SEnji Cooper # export dir=$PWD/dir 248b3af24b4SEnji Cooper $ENV{$name} = $value; 249b3af24b4SEnji Cooper return []; 250b3af24b4SEnji Cooper } elsif ($prog->[0] eq "unset") { 251b3af24b4SEnji Cooper delete $ENV{$prog->[1]}; 252b3af24b4SEnji Cooper return []; 253b3af24b4SEnji Cooper } 254b3af24b4SEnji Cooper 255b3af24b4SEnji Cooper pipe *IN2, *OUT 256b3af24b4SEnji Cooper or die "Can't create pipe for reading: $!"; 257b3af24b4SEnji Cooper open *IN_DUP, "<&STDIN" 258b3af24b4SEnji Cooper or *IN_DUP = undef; 259b3af24b4SEnji Cooper open *STDIN, "<&IN2" 260b3af24b4SEnji Cooper or die "Can't duplicate pipe for reading: $!"; 261b3af24b4SEnji Cooper close *IN2; 262b3af24b4SEnji Cooper 263b3af24b4SEnji Cooper open *OUT_DUP, ">&STDOUT" 264b3af24b4SEnji Cooper or die "Can't duplicate STDOUT: $!"; 265b3af24b4SEnji Cooper pipe *IN, *OUT2 266b3af24b4SEnji Cooper or die "Can't create pipe for writing: $!"; 267b3af24b4SEnji Cooper open *STDOUT, ">&OUT2" 268b3af24b4SEnji Cooper or die "Can't duplicate pipe for writing: $!"; 269b3af24b4SEnji Cooper close *OUT2; 270b3af24b4SEnji Cooper 271b3af24b4SEnji Cooper *STDOUT->autoflush(); 272b3af24b4SEnji Cooper *OUT->autoflush(); 273b3af24b4SEnji Cooper 274b3af24b4SEnji Cooper $SIG{CHLD} = 'IGNORE'; 275b3af24b4SEnji Cooper 276b3af24b4SEnji Cooper if (fork()) { 277b3af24b4SEnji Cooper # Server 278b3af24b4SEnji Cooper if (*IN_DUP) { 279b3af24b4SEnji Cooper open *STDIN, "<&IN_DUP" 280b3af24b4SEnji Cooper or die "Can't duplicate STDIN: $!"; 281b3af24b4SEnji Cooper close *IN_DUP 282b3af24b4SEnji Cooper or die "Can't close STDIN duplicate: $!"; 283b3af24b4SEnji Cooper } 284b3af24b4SEnji Cooper open *STDOUT, ">&OUT_DUP" 285b3af24b4SEnji Cooper or die "Can't duplicate STDOUT: $!"; 286b3af24b4SEnji Cooper close *OUT_DUP 287b3af24b4SEnji Cooper or die "Can't close STDOUT duplicate: $!"; 288b3af24b4SEnji Cooper 289b3af24b4SEnji Cooper foreach my $line (@$in) { 290b3af24b4SEnji Cooper #print "> $line"; 291b3af24b4SEnji Cooper print OUT $line; 292b3af24b4SEnji Cooper } 293b3af24b4SEnji Cooper close *OUT 294b3af24b4SEnji Cooper or die "Can't close pipe for writing: $!"; 295b3af24b4SEnji Cooper 296b3af24b4SEnji Cooper my $result = []; 297b3af24b4SEnji Cooper while (<IN>) { 298b3af24b4SEnji Cooper #print "< $_"; 299b3af24b4SEnji Cooper if ($needs_shell) { 300b3af24b4SEnji Cooper s#^/bin/sh: line \d+: ##; 301b3af24b4SEnji Cooper } 302b3af24b4SEnji Cooper push @$result, $_; 303b3af24b4SEnji Cooper } 304b3af24b4SEnji Cooper return $result; 305b3af24b4SEnji Cooper } else { 306b3af24b4SEnji Cooper # Client 307b3af24b4SEnji Cooper $< = $>; 308b3af24b4SEnji Cooper close IN 309b3af24b4SEnji Cooper or die "Can't close read end for input pipe: $!"; 310b3af24b4SEnji Cooper close OUT 311b3af24b4SEnji Cooper or die "Can't close write end for output pipe: $!"; 312b3af24b4SEnji Cooper close OUT_DUP 313b3af24b4SEnji Cooper or die "Can't close STDOUT duplicate: $!"; 314b3af24b4SEnji Cooper local *ERR_DUP; 315b3af24b4SEnji Cooper open ERR_DUP, ">&STDERR" 316b3af24b4SEnji Cooper or die "Can't duplicate STDERR: $!"; 317b3af24b4SEnji Cooper open STDERR, ">&STDOUT" 318b3af24b4SEnji Cooper or die "Can't join STDOUT and STDERR: $!"; 319b3af24b4SEnji Cooper 320b3af24b4SEnji Cooper if ($needs_shell) { 321b3af24b4SEnji Cooper exec ('/bin/sh', '-c', join(" ", @$prog)); 322b3af24b4SEnji Cooper } else { 323b3af24b4SEnji Cooper exec @$prog; 324b3af24b4SEnji Cooper } 325b3af24b4SEnji Cooper print STDERR $prog->[0], ": $!\n"; 326b3af24b4SEnji Cooper exit; 327b3af24b4SEnji Cooper } 328b3af24b4SEnji Cooper} 329b3af24b4SEnji Cooper 330