| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | #!/usr/bin/perl -w | 
| 2 |  |  |  |  |  |  | # -*- perl -*- | 
| 3 |  |  |  |  |  |  |  | 
| 4 |  |  |  |  |  |  | # | 
| 5 |  |  |  |  |  |  | # Author: Slaven Rezic | 
| 6 |  |  |  |  |  |  | # | 
| 7 |  |  |  |  |  |  | # Copyright (C) 2017,2018,2019,2020,2022 Slaven Rezic. All rights reserved. | 
| 8 |  |  |  |  |  |  | # This program is free software; you can redistribute it and/or | 
| 9 |  |  |  |  |  |  | # modify it under the same terms as Perl itself. | 
| 10 |  |  |  |  |  |  | # | 
| 11 |  |  |  |  |  |  | # Mail: slaven@rezic.de | 
| 12 |  |  |  |  |  |  | # WWW:  http://www.rezic.de/eserte/ | 
| 13 |  |  |  |  |  |  | # | 
| 14 |  |  |  |  |  |  |  | 
| 15 | 44 |  |  | 44 |  | 2338614 | use strict; | 
|  | 44 |  |  |  |  | 413 |  | 
|  | 44 |  |  |  |  | 1168 |  | 
| 16 | 44 |  |  | 44 |  | 194 | use warnings; | 
|  | 44 |  |  |  |  | 60 |  | 
|  | 44 |  |  |  |  | 3143 |  | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | { | 
| 19 |  |  |  |  |  |  | package Doit; | 
| 20 |  |  |  |  |  |  | our $VERSION = '0.025_58'; | 
| 21 |  |  |  |  |  |  | $VERSION =~ s{_}{}; | 
| 22 |  |  |  |  |  |  |  | 
| 23 | 44 |  |  | 44 |  | 256 | use constant IS_WIN => $^O eq 'MSWin32'; | 
|  | 44 |  |  |  |  | 73 |  | 
|  | 44 |  |  |  |  | 4768 |  | 
| 24 |  |  |  |  |  |  | } | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | { | 
| 27 |  |  |  |  |  |  | package Doit::Log; | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | sub _use_coloring { | 
| 30 | 44 |  |  | 44 |  | 280 | no warnings 'redefine'; | 
|  | 44 |  |  |  |  | 78 |  | 
|  | 44 |  |  |  |  | 5765 |  | 
| 31 | 97 |  |  | 97 |  | 413 | *colored_error = sub ($) { Term::ANSIColor::colored($_[0], 'red on_black')}; | 
|  | 44 |  |  | 44 |  | 269 |  | 
| 32 | 44 |  |  | 422 |  | 1483 | *colored_info  = sub ($) { Term::ANSIColor::colored($_[0], 'green on_black')}; | 
|  | 422 |  |  |  |  | 3246 |  | 
| 33 |  |  |  |  |  |  | } | 
| 34 |  |  |  |  |  |  | sub _no_coloring { | 
| 35 | 44 |  |  | 44 |  | 287 | no warnings 'redefine'; | 
|  | 44 |  |  |  |  | 87 |  | 
|  | 44 |  |  |  |  | 7556 |  | 
| 36 | 1 |  |  | 1 |  | 28 | *colored_error = *colored_info = sub ($) { $_[0] }; | 
|  | 1 |  |  | 1 |  | 1220 |  | 
| 37 |  |  |  |  |  |  | } | 
| 38 |  |  |  |  |  |  | { | 
| 39 |  |  |  |  |  |  | my $can_coloring; | 
| 40 |  |  |  |  |  |  | sub _can_coloring { | 
| 41 | 46 | 100 |  | 46 |  | 81603 | return $can_coloring if defined $can_coloring; | 
| 42 |  |  |  |  |  |  | # XXX What needs to be done to get coloring on Windows? | 
| 43 |  |  |  |  |  |  | # XXX Probably should also check if the terminal is ANSI-capable at all | 
| 44 |  |  |  |  |  |  | # XXX Probably should not use coloring on non-terminals (but | 
| 45 |  |  |  |  |  |  | #     there could be a --color option like in git to force it) | 
| 46 | 44 | 50 | 33 |  |  | 508 | $can_coloring = !Doit::IS_WIN && ($ENV{TERM}||'') !~ m{^(|dumb)$} && eval { require Term::ANSIColor; 1 } ? 1 : 0; | 
| 47 |  |  |  |  |  |  | } | 
| 48 |  |  |  |  |  |  | } | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | BEGIN { | 
| 51 | 44 | 50 |  | 44 |  | 198 | if (_can_coloring()) { | 
| 52 | 44 |  |  |  |  | 135 | _use_coloring(); | 
| 53 |  |  |  |  |  |  | } else { | 
| 54 | 0 |  |  |  |  | 0 | _no_coloring(); | 
| 55 |  |  |  |  |  |  | } | 
| 56 |  |  |  |  |  |  | } | 
| 57 |  |  |  |  |  |  |  | 
| 58 | 44 |  |  | 44 |  | 676 | use Exporter 'import'; | 
|  | 44 |  |  |  |  | 60 |  | 
|  | 44 |  |  |  |  | 1996 |  | 
| 59 | 44 |  |  | 44 |  | 1073 | our @EXPORT; BEGIN { @EXPORT = qw(info warning error) } | 
| 60 |  |  |  |  |  |  |  | 
| 61 | 44 |  |  | 44 |  | 9787 | BEGIN { $INC{'Doit/Log.pm'} = __FILE__ } # XXX hack | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  | my $current_label = ''; | 
| 64 |  |  |  |  |  |  |  | 
| 65 | 423 |  |  | 423 | 1 | 8059 | sub info ($)    { print STDERR colored_info("INFO$current_label:"), " ", $_[0], "\n" } | 
| 66 | 3 |  |  | 3 | 1 | 1302 | sub warning ($) { print STDERR colored_error("WARN$current_label:"), " ", $_[0], "\n" } | 
| 67 | 94 |  |  | 94 | 1 | 2976 | sub error ($)   { require Carp; Carp::croak(colored_error("ERROR$current_label:"), " ", $_[0]) } | 
|  | 94 |  |  |  |  | 512 |  | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | sub set_label ($) { | 
| 70 | 2 |  |  | 2 | 1 | 1852 | my $label = shift; | 
| 71 | 2 | 100 |  |  |  | 6 | if (defined $label) { | 
| 72 | 1 |  |  |  |  | 3 | $current_label = " $label"; | 
| 73 |  |  |  |  |  |  | } else { | 
| 74 | 1 |  |  |  |  | 3 | $current_label = ''; | 
| 75 |  |  |  |  |  |  | } | 
| 76 |  |  |  |  |  |  | } | 
| 77 |  |  |  |  |  |  | } | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | { | 
| 80 |  |  |  |  |  |  | package Doit::Exception; | 
| 81 | 44 |  |  | 44 |  | 34011 | use overload '""' => 'stringify'; | 
|  | 44 |  |  |  |  | 27952 |  | 
|  | 44 |  |  |  |  | 252 |  | 
| 82 | 44 |  |  | 44 |  | 2830 | use Exporter 'import'; | 
|  | 44 |  |  |  |  | 83 |  | 
|  | 44 |  |  |  |  | 14880 |  | 
| 83 |  |  |  |  |  |  | our @EXPORT_OK = qw(throw); | 
| 84 |  |  |  |  |  |  | $INC{'Doit/Exception.pm'} = __FILE__; # XXX hack | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | sub new { | 
| 87 | 54 |  |  | 54 | 0 | 77007 | my($class, $msg, %opts) = @_; | 
| 88 | 54 |  | 50 |  |  | 659 | my $level = delete $opts{__level} || 'auto'; | 
| 89 | 54 | 50 |  |  |  | 200 | if ($level eq 'auto') { | 
| 90 | 54 |  |  |  |  | 91 | my $_level = 0; | 
| 91 | 54 |  |  |  |  | 99 | while() { | 
| 92 | 310 |  |  |  |  | 3500 | my @stackinfo = caller($_level); | 
| 93 | 310 | 50 |  |  |  | 785 | if (!@stackinfo) { | 
| 94 | 0 |  |  |  |  | 0 | $level = $_level - 1; | 
| 95 | 0 |  |  |  |  | 0 | last; | 
| 96 |  |  |  |  |  |  | } | 
| 97 | 310 | 100 |  |  |  | 1575 | if ($stackinfo[1] !~ m{([/\\]|^)Doit\.pm$}) { | 
| 98 | 54 |  |  |  |  | 113 | $level = $_level; | 
| 99 | 54 |  |  |  |  | 134 | last; | 
| 100 |  |  |  |  |  |  | } | 
| 101 | 256 |  |  |  |  | 470 | $_level++; | 
| 102 |  |  |  |  |  |  | } | 
| 103 |  |  |  |  |  |  | } | 
| 104 | 54 |  |  |  |  | 444 | ($opts{__package}, $opts{__filename}, $opts{__line}) = caller($level); | 
| 105 | 54 |  |  |  |  | 3117 | bless { | 
| 106 |  |  |  |  |  |  | __msg  => $msg, | 
| 107 |  |  |  |  |  |  | %opts, | 
| 108 |  |  |  |  |  |  | }, $class; | 
| 109 |  |  |  |  |  |  | } | 
| 110 |  |  |  |  |  |  | sub stringify { | 
| 111 | 31 |  |  | 31 | 0 | 13981 | my $self = shift; | 
| 112 | 31 |  |  |  |  | 162 | my $msg = $self->{__msg}; | 
| 113 | 31 | 100 |  |  |  | 134 | $msg = 'Died' if !defined $msg; | 
| 114 | 31 | 100 |  |  |  | 208 | if ($msg !~ /\n\z/) { | 
| 115 | 29 |  |  |  |  | 186 | $msg .= ' at ' . $self->{__filename} . ' line ' . $self->{__line} . ".\n"; | 
| 116 |  |  |  |  |  |  | } | 
| 117 | 31 |  |  |  |  | 416 | $msg; | 
| 118 |  |  |  |  |  |  | } | 
| 119 |  |  |  |  |  |  |  | 
| 120 | 50 |  |  | 50 | 0 | 3330 | sub throw { die Doit::Exception->new(@_) } | 
| 121 |  |  |  |  |  |  | } | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | { | 
| 124 |  |  |  |  |  |  | package Doit::ScopeCleanups; | 
| 125 |  |  |  |  |  |  | $INC{'Doit/ScopeCleanups.pm'} = __FILE__; # XXX hack | 
| 126 | 44 |  |  | 44 |  | 307 | use Doit::Log; | 
|  | 44 |  |  |  |  | 85 |  | 
|  | 44 |  |  |  |  | 9520 |  | 
| 127 |  |  |  |  |  |  |  | 
| 128 |  |  |  |  |  |  | sub new { | 
| 129 | 127 |  |  | 127 | 0 | 340 | my($class) = @_; | 
| 130 | 127 |  |  |  |  | 388 | bless [], $class; | 
| 131 |  |  |  |  |  |  | } | 
| 132 |  |  |  |  |  |  |  | 
| 133 |  |  |  |  |  |  | sub add_scope_cleanup { | 
| 134 | 131 |  |  | 131 | 0 | 267 | my($self, $code) = @_; | 
| 135 | 131 |  |  |  |  | 451 | push @$self, { code => $code }; | 
| 136 |  |  |  |  |  |  | } | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | sub DESTROY { | 
| 139 | 127 |  |  | 127 |  | 37327 | my $self = shift; | 
| 140 | 127 |  |  |  |  | 809 | for my $scope_cleanup (@$self) { | 
| 141 | 129 |  |  |  |  | 380 | my($code) = $scope_cleanup->{code}; | 
| 142 | 129 | 50 |  |  |  | 343 | if ($] >= 5.014) { | 
| 143 | 129 |  |  |  |  | 282 | eval { | 
| 144 | 129 |  |  |  |  | 818 | $code->(); | 
| 145 |  |  |  |  |  |  | }; | 
| 146 | 129 | 100 |  |  |  | 5250 | if ($@) { | 
| 147 |  |  |  |  |  |  | # error() will give visual feedback about the problem, | 
| 148 |  |  |  |  |  |  | # die() would be left unnoticed. Note that | 
| 149 |  |  |  |  |  |  | # an exception in a DESTROY block is not fatal, | 
| 150 |  |  |  |  |  |  | # and can be only detected by inspecting $@. | 
| 151 | 4 |  |  |  |  | 12 | error "Scope cleanup failed: $@"; | 
| 152 |  |  |  |  |  |  | } | 
| 153 |  |  |  |  |  |  | } else { | 
| 154 |  |  |  |  |  |  | # And eval {} in older perl versions would | 
| 155 |  |  |  |  |  |  | # clobber an outside $@. See | 
| 156 |  |  |  |  |  |  | # perldoc perl5140delta, "Exception Handling" | 
| 157 | 0 |  |  |  |  | 0 | $code->(); | 
| 158 |  |  |  |  |  |  | } | 
| 159 |  |  |  |  |  |  | } | 
| 160 |  |  |  |  |  |  | } | 
| 161 |  |  |  |  |  |  | } | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | { | 
| 164 |  |  |  |  |  |  | package Doit::Util; | 
| 165 | 44 |  |  | 44 |  | 306 | use Exporter 'import'; | 
|  | 44 |  |  |  |  | 82 |  | 
|  | 44 |  |  |  |  | 1946 |  | 
| 166 | 44 |  |  | 44 |  | 1253 | our @EXPORT; BEGIN { @EXPORT = qw(in_directory new_scope_cleanup copy_stat get_sudo_cmd is_in_path) } | 
| 167 |  |  |  |  |  |  | $INC{'Doit/Util.pm'} = __FILE__; # XXX hack | 
| 168 | 44 |  |  | 44 |  | 242 | use Doit::Log; | 
|  | 44 |  |  |  |  | 75 |  | 
|  | 44 |  |  |  |  | 69508 |  | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  | sub new_scope_cleanup (&) { | 
| 171 | 127 |  |  | 127 | 1 | 9833 | my($code) = @_; | 
| 172 | 127 |  |  |  |  | 1408 | my $sc = Doit::ScopeCleanups->new; | 
| 173 | 127 |  |  |  |  | 355 | $sc->add_scope_cleanup($code); | 
| 174 | 127 |  |  |  |  | 249 | $sc; | 
| 175 |  |  |  |  |  |  | } | 
| 176 |  |  |  |  |  |  |  | 
| 177 |  |  |  |  |  |  | sub in_directory (&$) { | 
| 178 | 302 |  |  | 302 | 1 | 315205 | my($code, $dir) = @_; | 
| 179 | 302 |  |  |  |  | 403 | my $scope_cleanup; | 
| 180 | 302 | 100 |  |  |  | 1195 | if (defined $dir) { | 
| 181 | 102 |  |  |  |  | 1112 | require Cwd; | 
| 182 | 102 |  |  |  |  | 946 | my $pwd = Cwd::getcwd(); | 
| 183 | 102 | 100 | 66 |  |  | 992 | if (!defined $pwd || $pwd eq '') { # XS variant returns undef, PP variant returns '' --- see https://rt.perl.org/Ticket/Display.html?id=132648 | 
| 184 | 2 |  |  |  |  | 8 | warning "No known current working directory"; | 
| 185 |  |  |  |  |  |  | } else { | 
| 186 |  |  |  |  |  |  | $scope_cleanup = new_scope_cleanup | 
| 187 |  |  |  |  |  |  | (sub { | 
| 188 | 100 | 100 |  | 100 |  | 1314 | chdir $pwd or error "Can't chdir to $pwd: $!"; | 
| 189 | 100 |  |  |  |  | 1072 | }); | 
| 190 |  |  |  |  |  |  | } | 
| 191 | 102 | 100 |  |  |  | 1399 | chdir $dir | 
| 192 |  |  |  |  |  |  | or error "Can't chdir to $dir: $!"; | 
| 193 |  |  |  |  |  |  | } | 
| 194 | 299 |  |  |  |  | 822 | $code->(); | 
| 195 |  |  |  |  |  |  | } | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | # $src may be a source file or an arrayref with stat information | 
| 198 |  |  |  |  |  |  | sub copy_stat ($$;@) { | 
| 199 | 65 |  |  | 65 | 1 | 4610 | my($src, $dest, %preserve) = @_; | 
| 200 | 65 | 100 |  |  |  | 886 | my @stat = ref $src eq 'ARRAY' ? @$src : stat($src); | 
| 201 | 65 | 50 |  |  |  | 244 | error "Can't stat $src: $!" if !@stat; | 
| 202 |  |  |  |  |  |  |  | 
| 203 | 65 |  |  |  |  | 174 | my $preserve_default   = !%preserve; | 
| 204 | 65 | 100 |  |  |  | 230 | my $preserve_ownership = exists $preserve{ownership} ? delete $preserve{ownership} : $preserve_default; | 
| 205 | 65 | 100 |  |  |  | 239 | my $preserve_mode      = exists $preserve{mode}      ? delete $preserve{mode}      : $preserve_default; | 
| 206 | 65 | 100 |  |  |  | 200 | my $preserve_time      = exists $preserve{time}      ? delete $preserve{time}      : $preserve_default; | 
| 207 |  |  |  |  |  |  |  | 
| 208 | 65 | 50 |  |  |  | 178 | error "Unhandled preserve values: " . join(" ", %preserve) if %preserve; | 
| 209 |  |  |  |  |  |  |  | 
| 210 | 65 | 100 |  |  |  | 158 | if ($preserve_mode) { | 
| 211 | 62 | 50 |  |  |  | 875 | chmod $stat[2], $dest | 
| 212 |  |  |  |  |  |  | or warning "Can't chmod $dest to " . sprintf("0%o", $stat[2]) . ": $!"; | 
| 213 |  |  |  |  |  |  | } | 
| 214 | 65 | 100 |  |  |  | 210 | if ($preserve_ownership) { | 
| 215 |  |  |  |  |  |  | chown $stat[4], $stat[5], $dest | 
| 216 | 62 | 50 |  |  |  | 928 | or do { | 
| 217 | 0 |  |  |  |  | 0 | my $save_err = $!; # otherwise it's lost in the get... calls | 
| 218 | 0 |  |  |  |  | 0 | warning "Can't chown $dest to " . | 
| 219 |  |  |  |  |  |  | (getpwuid($stat[4]))[0] . "/" . | 
| 220 |  |  |  |  |  |  | (getgrgid($stat[5]))[0] . ": $save_err"; | 
| 221 |  |  |  |  |  |  | }; | 
| 222 |  |  |  |  |  |  | } | 
| 223 | 65 | 100 |  |  |  | 247 | if ($preserve_time) { | 
| 224 | 41 | 50 |  |  |  | 594 | utime $stat[8], $stat[9], $dest | 
| 225 |  |  |  |  |  |  | or warning "Can't utime $dest to " . | 
| 226 |  |  |  |  |  |  | scalar(localtime $stat[8]) . "/" . | 
| 227 |  |  |  |  |  |  | scalar(localtime $stat[9]) . | 
| 228 |  |  |  |  |  |  | ": $!"; | 
| 229 |  |  |  |  |  |  | } | 
| 230 |  |  |  |  |  |  | } | 
| 231 |  |  |  |  |  |  |  | 
| 232 |  |  |  |  |  |  | sub get_sudo_cmd () { | 
| 233 | 0 | 0 |  | 0 | 1 | 0 | return () if $> == 0; | 
| 234 | 0 |  |  |  |  | 0 | return ('sudo'); | 
| 235 |  |  |  |  |  |  | } | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | sub is_in_path { | 
| 238 | 20 |  |  | 20 | 0 | 52 | my($prog) = @_; | 
| 239 |  |  |  |  |  |  |  | 
| 240 | 20 | 100 |  |  |  | 75 | if (!defined &_file_name_is_absolute) { | 
| 241 | 13 | 50 |  |  |  | 27 | if (eval { require File::Spec; defined &File::Spec::file_name_is_absolute }) { | 
|  | 13 |  |  |  |  | 76 |  | 
|  | 13 |  |  |  |  | 55 |  | 
| 242 | 0 |  |  |  |  | 0 | *_file_name_is_absolute = \&File::Spec::file_name_is_absolute; | 
| 243 |  |  |  |  |  |  | } else { | 
| 244 |  |  |  |  |  |  | *_file_name_is_absolute = sub { | 
| 245 | 20 |  |  | 20 |  | 38 | my $file = shift; | 
| 246 | 20 |  |  |  |  | 31 | my $r; | 
| 247 | 20 | 50 |  |  |  | 73 | if ($^O eq 'MSWin32') { | 
| 248 | 0 |  |  |  |  | 0 | $r = ($file =~ m;^([a-z]:(/|\\)|\\\\|//);i); | 
| 249 |  |  |  |  |  |  | } else { | 
| 250 | 20 |  |  |  |  | 74 | $r = ($file =~ m|^/|); | 
| 251 |  |  |  |  |  |  | } | 
| 252 | 20 |  |  |  |  | 60 | $r; | 
| 253 | 13 |  |  |  |  | 97 | }; | 
| 254 |  |  |  |  |  |  | } | 
| 255 |  |  |  |  |  |  | } | 
| 256 |  |  |  |  |  |  |  | 
| 257 | 20 | 50 |  |  |  | 58 | if (_file_name_is_absolute($prog)) { | 
| 258 | 0 | 0 |  |  |  | 0 | if ($^O eq 'MSWin32') { | 
| 259 | 0 | 0 | 0 |  |  | 0 | return $prog       if (-f $prog && -x $prog); | 
| 260 | 0 | 0 | 0 |  |  | 0 | return "$prog.bat" if (-f "$prog.bat" && -x "$prog.bat"); | 
| 261 | 0 | 0 | 0 |  |  | 0 | return "$prog.com" if (-f "$prog.com" && -x "$prog.com"); | 
| 262 | 0 | 0 | 0 |  |  | 0 | return "$prog.exe" if (-f "$prog.exe" && -x "$prog.exe"); | 
| 263 | 0 | 0 | 0 |  |  | 0 | return "$prog.cmd" if (-f "$prog.cmd" && -x "$prog.cmd"); | 
| 264 |  |  |  |  |  |  | } else { | 
| 265 | 0 | 0 | 0 |  |  | 0 | return $prog if -f $prog and -x $prog; | 
| 266 |  |  |  |  |  |  | } | 
| 267 |  |  |  |  |  |  | } | 
| 268 | 20 |  |  |  |  | 104 | require Config; | 
| 269 | 20 |  |  |  |  | 28 | %Config::Config = %Config::Config if 0; # cease -w | 
| 270 | 20 |  | 50 |  |  | 316 | my $sep = $Config::Config{'path_sep'} || ':'; | 
| 271 | 20 |  |  |  |  | 250 | foreach (split(/$sep/o, $ENV{PATH})) { | 
| 272 | 146 | 50 |  |  |  | 402 | if ($^O eq 'MSWin32') { | 
| 273 |  |  |  |  |  |  | # maybe use $ENV{PATHEXT} like maybe_command in ExtUtils/MM_Win32.pm? | 
| 274 | 0 | 0 | 0 |  |  | 0 | return "$_\\$prog"     if (-f "$_\\$prog" && -x "$_\\$prog"); | 
| 275 | 0 | 0 | 0 |  |  | 0 | return "$_\\$prog.bat" if (-f "$_\\$prog.bat" && -x "$_\\$prog.bat"); | 
| 276 | 0 | 0 | 0 |  |  | 0 | return "$_\\$prog.com" if (-f "$_\\$prog.com" && -x "$_\\$prog.com"); | 
| 277 | 0 | 0 | 0 |  |  | 0 | return "$_\\$prog.exe" if (-f "$_\\$prog.exe" && -x "$_\\$prog.exe"); | 
| 278 | 0 | 0 | 0 |  |  | 0 | return "$_\\$prog.cmd" if (-f "$_\\$prog.cmd" && -x "$_\\$prog.cmd"); | 
| 279 |  |  |  |  |  |  | } else { | 
| 280 | 146 | 100 | 66 |  |  | 3005 | return "$_/$prog" if (-x "$_/$prog" && !-d "$_/$prog"); | 
| 281 |  |  |  |  |  |  | } | 
| 282 |  |  |  |  |  |  | } | 
| 283 | 6 |  |  |  |  | 28 | undef; | 
| 284 |  |  |  |  |  |  | } | 
| 285 |  |  |  |  |  |  | } | 
| 286 |  |  |  |  |  |  |  | 
| 287 |  |  |  |  |  |  | { | 
| 288 |  |  |  |  |  |  | package Doit::Win32Util; | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | # Taken from http://blogs.perl.org/users/graham_knop/2011/12/using-system-or-exec-safely-on-windows.html | 
| 291 |  |  |  |  |  |  | sub win32_quote_list { | 
| 292 | 0 |  |  | 0 |  | 0 | my (@args) = @_; | 
| 293 |  |  |  |  |  |  |  | 
| 294 | 0 |  |  |  |  | 0 | my $args = join ' ', map { _quote_literal($_) } @args; | 
|  | 0 |  |  |  |  | 0 |  | 
| 295 |  |  |  |  |  |  |  | 
| 296 | 0 | 0 |  |  |  | 0 | if (_has_shell_metachars($args)) { | 
| 297 |  |  |  |  |  |  | # cmd.exe treats quotes differently from standard | 
| 298 |  |  |  |  |  |  | # argument parsing. just escape everything using ^. | 
| 299 | 0 |  |  |  |  | 0 | $args =~ s/([()%!^"<>&|])/^$1/g; | 
| 300 |  |  |  |  |  |  | } | 
| 301 | 0 |  |  |  |  | 0 | return $args; | 
| 302 |  |  |  |  |  |  | } | 
| 303 |  |  |  |  |  |  |  | 
| 304 |  |  |  |  |  |  | sub _quote_literal { | 
| 305 | 0 |  |  | 0 |  | 0 | my ($text) = @_; | 
| 306 |  |  |  |  |  |  |  | 
| 307 |  |  |  |  |  |  | # basic argument quoting.  uses backslashes and quotes to escape | 
| 308 |  |  |  |  |  |  | # everything. | 
| 309 |  |  |  |  |  |  | # | 
| 310 |  |  |  |  |  |  | # The original code had a \v here, but this is not supported | 
| 311 |  |  |  |  |  |  | # in perl5.8. Also, \v probably matches too many characters here | 
| 312 |  |  |  |  |  |  | # --- restrict to the ones < 0x100 | 
| 313 | 0 | 0 | 0 |  |  | 0 | if ($text ne '' && $text !~ /[ \t\n\x0a\x0b\x0c\x0d\x85"]/) { | 
| 314 |  |  |  |  |  |  | # no quoting needed | 
| 315 |  |  |  |  |  |  | } else { | 
| 316 | 0 |  |  |  |  | 0 | my @text = split '', $text; | 
| 317 | 0 |  |  |  |  | 0 | $text = q{"}; | 
| 318 | 0 |  |  |  |  | 0 | for (my $i = 0; ; $i++) { | 
| 319 | 0 |  |  |  |  | 0 | my $bs_count = 0; | 
| 320 | 0 |  | 0 |  |  | 0 | while ( $i < @text && $text[$i] eq "\\" ) { | 
| 321 | 0 |  |  |  |  | 0 | $i++; | 
| 322 | 0 |  |  |  |  | 0 | $bs_count++; | 
| 323 |  |  |  |  |  |  | } | 
| 324 | 0 | 0 |  |  |  | 0 | if ($i > $#text) { | 
|  |  | 0 |  |  |  |  |  | 
| 325 | 0 |  |  |  |  | 0 | $text .= "\\" x ($bs_count * 2); | 
| 326 | 0 |  |  |  |  | 0 | last; | 
| 327 |  |  |  |  |  |  | } elsif ($text[$i] eq q{"}) { | 
| 328 | 0 |  |  |  |  | 0 | $text .= "\\" x ($bs_count * 2 + 1); | 
| 329 |  |  |  |  |  |  | } else { | 
| 330 | 0 |  |  |  |  | 0 | $text .= "\\" x $bs_count; | 
| 331 |  |  |  |  |  |  | } | 
| 332 | 0 |  |  |  |  | 0 | $text .= $text[$i]; | 
| 333 |  |  |  |  |  |  | } | 
| 334 | 0 |  |  |  |  | 0 | $text .= q{"}; | 
| 335 |  |  |  |  |  |  | } | 
| 336 |  |  |  |  |  |  |  | 
| 337 | 0 |  |  |  |  | 0 | return $text; | 
| 338 |  |  |  |  |  |  | } | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | # direct port of code from win32.c | 
| 341 |  |  |  |  |  |  | sub _has_shell_metachars { | 
| 342 | 0 |  |  | 0 |  | 0 | my $string = shift; | 
| 343 | 0 |  |  |  |  | 0 | my $inquote = 0; | 
| 344 | 0 |  |  |  |  | 0 | my $quote = ''; | 
| 345 |  |  |  |  |  |  |  | 
| 346 | 0 |  |  |  |  | 0 | my @string = split '', $string; | 
| 347 | 0 |  |  |  |  | 0 | for my $char (@string) { | 
| 348 | 0 | 0 | 0 |  |  | 0 | if ($char eq q{%}) { | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
| 349 | 0 |  |  |  |  | 0 | return 1; | 
| 350 |  |  |  |  |  |  | } elsif ($char eq q{'} || $char eq q{"}) { | 
| 351 | 0 | 0 |  |  |  | 0 | if ($inquote) { | 
| 352 | 0 | 0 |  |  |  | 0 | if ($char eq $quote) { | 
| 353 | 0 |  |  |  |  | 0 | $inquote = 0; | 
| 354 | 0 |  |  |  |  | 0 | $quote = ''; | 
| 355 |  |  |  |  |  |  | } | 
| 356 |  |  |  |  |  |  | } else { | 
| 357 | 0 |  |  |  |  | 0 | $quote = $char; | 
| 358 | 0 |  |  |  |  | 0 | $inquote++; | 
| 359 |  |  |  |  |  |  | } | 
| 360 |  |  |  |  |  |  | } elsif ($char eq q{<} || $char eq q{>} || $char eq q{|}) { | 
| 361 | 0 | 0 |  |  |  | 0 | if ( ! $inquote) { | 
| 362 | 0 |  |  |  |  | 0 | return 1; | 
| 363 |  |  |  |  |  |  | } | 
| 364 |  |  |  |  |  |  | } | 
| 365 |  |  |  |  |  |  | } | 
| 366 | 0 |  |  |  |  | 0 | return; | 
| 367 |  |  |  |  |  |  | } | 
| 368 |  |  |  |  |  |  | } | 
| 369 |  |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | { | 
| 371 |  |  |  |  |  |  | package Doit; | 
| 372 |  |  |  |  |  |  |  | 
| 373 |  |  |  |  |  |  | sub import { | 
| 374 | 49 |  |  | 49 |  | 5278 | warnings->import; | 
| 375 | 49 |  |  |  |  | 30200 | strict->import; | 
| 376 |  |  |  |  |  |  | } | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | sub unimport { | 
| 379 | 2 |  |  | 2 |  | 48 | warnings->unimport; | 
| 380 | 2 |  |  |  |  | 51 | strict->unimport; | 
| 381 |  |  |  |  |  |  | } | 
| 382 |  |  |  |  |  |  |  | 
| 383 | 44 |  |  | 44 |  | 339 | use Doit::Log; | 
|  | 44 |  |  |  |  | 69 |  | 
|  | 44 |  |  |  |  | 18404 |  | 
| 384 |  |  |  |  |  |  |  | 
| 385 |  |  |  |  |  |  | my $diff_error_shown; | 
| 386 |  |  |  |  |  |  | our @diff_cmd; | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | sub _new { | 
| 389 | 41 |  |  | 41 |  | 108 | my $class = shift; | 
| 390 | 41 |  |  |  |  | 137 | my $self = bless { }, $class; | 
| 391 | 41 |  |  |  |  | 96 | $self; | 
| 392 |  |  |  |  |  |  | } | 
| 393 |  |  |  |  |  |  | sub runner { | 
| 394 | 34 |  |  | 34 | 0 | 82 | my($self) = @_; | 
| 395 |  |  |  |  |  |  | # XXX hmmm, creating now self-refential data structures ... | 
| 396 | 34 |  | 33 |  |  | 495 | $self->{runner} ||= Doit::Runner->new($self); | 
| 397 |  |  |  |  |  |  | } | 
| 398 |  |  |  |  |  |  |  | 
| 399 |  |  |  |  |  |  | sub dryrunner { | 
| 400 | 7 |  |  | 7 | 0 | 31 | my($self) = @_; | 
| 401 |  |  |  |  |  |  | # XXX hmmm, creating now self-refential data structures ... | 
| 402 | 7 |  | 33 |  |  | 171 | $self->{dryrunner} ||= Doit::Runner->new($self, dryrun => 1); | 
| 403 |  |  |  |  |  |  | } | 
| 404 |  |  |  |  |  |  |  | 
| 405 |  |  |  |  |  |  | sub init { | 
| 406 | 41 |  |  | 41 | 0 | 4296180 | my($class) = @_; | 
| 407 | 41 |  |  |  |  | 20615 | require Getopt::Long; | 
| 408 | 41 |  |  |  |  | 286860 | my $getopt = Getopt::Long::Parser->new; | 
| 409 | 41 |  |  |  |  | 1061 | $getopt->configure(qw(pass_through noauto_abbrev)); | 
| 410 | 41 |  |  |  |  | 2716 | $getopt->getoptions( | 
| 411 |  |  |  |  |  |  | 'dry-run|n' => \my $dry_run, | 
| 412 |  |  |  |  |  |  | ); | 
| 413 | 41 |  |  |  |  | 8380 | my $doit = $class->_new; | 
| 414 | 41 | 100 |  |  |  | 162 | if ($dry_run) { | 
| 415 | 7 |  |  |  |  | 56 | $doit->dryrunner; | 
| 416 |  |  |  |  |  |  | } else { | 
| 417 | 34 |  |  |  |  | 147 | $doit->runner; | 
| 418 |  |  |  |  |  |  | } | 
| 419 |  |  |  |  |  |  | } | 
| 420 |  |  |  |  |  |  |  | 
| 421 |  |  |  |  |  |  | sub install_generic_cmd { | 
| 422 | 2 |  |  | 2 | 0 | 11 | my($self, $name, $check, $code, $msg) = @_; | 
| 423 | 2 | 50 |  |  |  | 9 | if (!$msg) { | 
| 424 | 2 | 50 |  | 2 |  | 23 | $msg = sub { my($self, $args) = @_; $name . ($args ? " @$args" : '') }; | 
|  | 2 |  |  |  |  | 10 |  | 
|  | 2 |  |  |  |  | 14 |  | 
| 425 |  |  |  |  |  |  | } | 
| 426 |  |  |  |  |  |  | my $cmd = sub { | 
| 427 | 3 |  |  | 3 |  | 8 | my($self, @args) = @_; | 
| 428 | 3 |  |  |  |  | 8 | my @commands; | 
| 429 | 3 |  |  |  |  | 10 | my $addinfo = {}; | 
| 430 | 3 | 100 |  |  |  | 12 | if ($check->($self, \@args, $addinfo)) { | 
| 431 |  |  |  |  |  |  | push @commands, { | 
| 432 | 2 |  |  | 2 |  | 7 | code => sub { $code->($self, \@args, $addinfo) }, | 
| 433 | 2 |  |  |  |  | 30 | msg  => $msg->($self, \@args, $addinfo), | 
| 434 |  |  |  |  |  |  | }; | 
| 435 |  |  |  |  |  |  | } | 
| 436 | 3 |  |  |  |  | 18 | Doit::Commands->new(@commands); | 
| 437 | 2 |  |  |  |  | 14 | }; | 
| 438 | 44 |  |  | 44 |  | 293 | no strict 'refs'; | 
|  | 44 |  |  |  |  | 72 |  | 
|  | 44 |  |  |  |  | 345298 |  | 
| 439 | 2 |  |  |  |  | 10 | *{"cmd_$name"} = $cmd; | 
|  | 2 |  |  |  |  | 31 |  | 
| 440 |  |  |  |  |  |  | } | 
| 441 |  |  |  |  |  |  |  | 
| 442 |  |  |  |  |  |  | sub cmd_chmod { | 
| 443 | 34 |  |  | 34 | 0 | 83 | my($self, @args) = @_; | 
| 444 | 34 | 100 | 66 |  |  | 52 | my %options; if (@args && ref $args[0] eq 'HASH') { %options = %{ shift @args } } | 
|  | 34 |  |  |  |  | 214 |  | 
|  | 17 |  |  |  |  | 29 |  | 
|  | 17 |  |  |  |  | 62 |  | 
| 445 | 34 |  |  |  |  | 74 | my $quiet = delete $options{quiet}; | 
| 446 | 34 | 50 |  |  |  | 77 | error "Unhandled options: " . join(" ", %options) if %options; | 
| 447 | 34 |  |  |  |  | 69 | my($mode, @files) = @args; | 
| 448 | 34 |  |  |  |  | 47 | my @files_to_change; | 
| 449 | 34 |  |  |  |  | 73 | for my $file (@files) { | 
| 450 | 37 |  |  |  |  | 436 | my @s = stat($file); | 
| 451 | 37 | 100 |  |  |  | 137 | if (@s) { | 
| 452 | 33 | 100 |  |  |  | 104 | if (($s[2] & 07777) != $mode) { | 
| 453 | 30 |  |  |  |  | 106 | push @files_to_change, $file; | 
| 454 |  |  |  |  |  |  | } | 
| 455 |  |  |  |  |  |  | } else { | 
| 456 | 4 |  |  |  |  | 12 | push @files_to_change, $file; | 
| 457 |  |  |  |  |  |  | } | 
| 458 |  |  |  |  |  |  | } | 
| 459 | 34 | 100 |  |  |  | 73 | if (@files_to_change) { | 
| 460 |  |  |  |  |  |  | my @commands =  { | 
| 461 |  |  |  |  |  |  | code => sub { | 
| 462 | 28 |  |  | 28 |  | 444 | my $changed_files = chmod $mode, @files_to_change; | 
| 463 | 28 | 100 |  |  |  | 162 | if ($changed_files != @files_to_change) { | 
| 464 | 3 | 100 |  |  |  | 13 | if (@files_to_change == 1) { | 
|  |  | 100 |  |  |  |  |  | 
| 465 | 1 |  |  |  |  | 13 | error "chmod failed: $!"; | 
| 466 |  |  |  |  |  |  | } elsif ($changed_files == 0) { | 
| 467 | 1 |  |  |  |  | 7 | error "chmod failed on all files: $!"; | 
| 468 |  |  |  |  |  |  | } else { | 
| 469 | 1 |  |  |  |  | 11 | error "chmod failed on some files (" . (@files_to_change-$changed_files) . "/" . scalar(@files_to_change) . "): $!"; | 
| 470 |  |  |  |  |  |  | } | 
| 471 |  |  |  |  |  |  | } | 
| 472 |  |  |  |  |  |  | }, | 
| 473 | 31 | 100 |  |  |  | 321 | ($quiet ? () : (msg => sprintf("chmod 0%o %s", $mode, join(" ", @files_to_change)))), # shellquote? | 
| 474 |  |  |  |  |  |  | rv   => scalar @files_to_change, | 
| 475 |  |  |  |  |  |  | }; | 
| 476 | 31 |  |  |  |  | 153 | Doit::Commands->new(@commands); | 
| 477 |  |  |  |  |  |  | } else { | 
| 478 | 3 |  |  |  |  | 15 | Doit::Commands->return_zero; | 
| 479 |  |  |  |  |  |  | } | 
| 480 |  |  |  |  |  |  | } | 
| 481 |  |  |  |  |  |  |  | 
| 482 |  |  |  |  |  |  | sub cmd_chown { | 
| 483 | 12 |  |  | 12 | 0 | 24 | my($self, @args) = @_; | 
| 484 | 12 | 100 | 66 |  |  | 16 | my %options; if (@args && ref $args[0] eq 'HASH') { %options = %{ shift @args } } | 
|  | 12 |  |  |  |  | 55 |  | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 8 |  | 
| 485 | 12 |  |  |  |  | 20 | my $quiet = delete $options{quiet}; | 
| 486 | 12 | 50 |  |  |  | 25 | error "Unhandled options: " . join(" ", %options) if %options; | 
| 487 | 12 |  |  |  |  | 20 | my($uid, $gid, @files) = @args; | 
| 488 |  |  |  |  |  |  |  | 
| 489 | 12 | 100 |  |  |  | 63 | if (!defined $uid) { | 
|  |  | 100 |  |  |  |  |  | 
| 490 | 3 |  |  |  |  | 5 | $uid = -1; | 
| 491 |  |  |  |  |  |  | } elsif ($uid !~ /^-?\d+$/) { | 
| 492 | 2 |  |  |  |  | 735 | my $_uid = (getpwnam $uid)[2]; | 
| 493 | 2 | 100 |  |  |  | 12 | if (!defined $_uid) { | 
| 494 |  |  |  |  |  |  | # XXX problem: in dry-run mode the user/group could be | 
| 495 |  |  |  |  |  |  | # created in _this_ pass, so this error would happen | 
| 496 |  |  |  |  |  |  | # while in wet-run everything would be fine. Good solution? | 
| 497 |  |  |  |  |  |  | # * do uid/gid resolution _again_ in the command if it failed here? | 
| 498 |  |  |  |  |  |  | # * maintain a virtual list of created users/groups while this run, and | 
| 499 |  |  |  |  |  |  | #   use this list as a fallback? | 
| 500 | 1 |  |  |  |  | 6 | error "User '$uid' does not exist"; | 
| 501 |  |  |  |  |  |  | } | 
| 502 | 1 |  |  |  |  | 4 | $uid = $_uid; | 
| 503 |  |  |  |  |  |  | } | 
| 504 | 11 | 100 |  |  |  | 37 | if (!defined $gid) { | 
|  |  | 100 |  |  |  |  |  | 
| 505 | 6 |  |  |  |  | 7 | $gid = -1; | 
| 506 |  |  |  |  |  |  | } elsif ($gid !~ /^-?\d+$/) { | 
| 507 | 1 |  |  |  |  | 110 | my $_gid = (getgrnam $gid)[2]; | 
| 508 | 1 | 50 |  |  |  | 7 | if (!defined $_gid) { | 
| 509 | 1 |  |  |  |  | 7 | error "Group '$gid' does not exist"; | 
| 510 |  |  |  |  |  |  | } | 
| 511 | 0 |  |  |  |  | 0 | $gid = $_gid; | 
| 512 |  |  |  |  |  |  | } | 
| 513 |  |  |  |  |  |  |  | 
| 514 | 10 |  |  |  |  | 15 | my @files_to_change; | 
| 515 | 10 | 100 | 100 |  |  | 27 | if ($uid != -1 || $gid != -1) { | 
| 516 | 9 |  |  |  |  | 16 | for my $file (@files) { | 
| 517 | 10 |  |  |  |  | 112 | my @s = stat($file); | 
| 518 | 10 | 100 |  |  |  | 29 | if (@s) { | 
| 519 | 7 | 50 | 66 |  |  | 47 | if ($uid != -1 && $s[4] != $uid) { | 
|  |  | 50 | 66 |  |  |  |  | 
| 520 | 0 |  |  |  |  | 0 | push @files_to_change, $file; | 
| 521 |  |  |  |  |  |  | } elsif ($gid != -1 && $s[5] != $gid) { | 
| 522 | 0 |  |  |  |  | 0 | push @files_to_change, $file; | 
| 523 |  |  |  |  |  |  | } | 
| 524 |  |  |  |  |  |  | } else { | 
| 525 | 3 |  |  |  |  | 10 | push @files_to_change, $file; | 
| 526 |  |  |  |  |  |  | } | 
| 527 |  |  |  |  |  |  | } | 
| 528 |  |  |  |  |  |  | } | 
| 529 |  |  |  |  |  |  |  | 
| 530 | 10 | 100 |  |  |  | 17 | if (@files_to_change) { | 
| 531 |  |  |  |  |  |  | my @commands =  { | 
| 532 |  |  |  |  |  |  | code => sub { | 
| 533 | 2 |  |  | 2 |  | 24 | my $changed_files = chown $uid, $gid, @files_to_change; | 
| 534 | 2 | 50 |  |  |  | 11 | if ($changed_files != @files_to_change) { | 
| 535 | 2 | 100 |  |  |  | 8 | if (@files_to_change == 1) { | 
|  |  | 50 |  |  |  |  |  | 
| 536 | 1 |  |  |  |  | 8 | error "chown failed: $!"; | 
| 537 |  |  |  |  |  |  | } elsif ($changed_files == 0) { | 
| 538 | 1 |  |  |  |  | 8 | error "chown failed on all files: $!"; | 
| 539 |  |  |  |  |  |  | } else { | 
| 540 | 0 |  |  |  |  | 0 | error "chown failed on some files (" . (@files_to_change-$changed_files) . "/" . scalar(@files_to_change) . "): $!"; | 
| 541 |  |  |  |  |  |  | } | 
| 542 |  |  |  |  |  |  | } | 
| 543 |  |  |  |  |  |  | }, | 
| 544 | 2 | 50 |  |  |  | 21 | ($quiet ? () : (msg => "chown $uid, $gid, @files_to_change")), # shellquote? | 
| 545 |  |  |  |  |  |  | rv   => scalar @files_to_change, | 
| 546 |  |  |  |  |  |  | }; | 
| 547 | 2 |  |  |  |  | 8 | Doit::Commands->new(@commands); | 
| 548 |  |  |  |  |  |  | } else { | 
| 549 | 8 |  |  |  |  | 23 | Doit::Commands->return_zero; | 
| 550 |  |  |  |  |  |  | } | 
| 551 |  |  |  |  |  |  | } | 
| 552 |  |  |  |  |  |  |  | 
| 553 |  |  |  |  |  |  | sub cmd_cond_run { | 
| 554 | 12 |  |  | 12 | 0 | 94 | my($self, %opts) = @_; | 
| 555 | 12 |  |  |  |  | 32 | my $if      = delete $opts{if}; | 
| 556 | 12 |  |  |  |  | 19 | my $unless  = delete $opts{unless}; | 
| 557 | 12 |  |  |  |  | 30 | my $creates = delete $opts{creates}; | 
| 558 | 12 |  |  |  |  | 22 | my $cmd     = delete $opts{cmd}; | 
| 559 | 12 | 100 |  |  |  | 55 | error "Unhandled options: " . join(" ", %opts) if %opts; | 
| 560 |  |  |  |  |  |  |  | 
| 561 | 11 | 100 |  |  |  | 28 | if (!$cmd) { | 
| 562 | 1 |  |  |  |  | 25 | error "cmd is a mandatory option for cond_run"; | 
| 563 |  |  |  |  |  |  | } | 
| 564 | 10 | 100 |  |  |  | 24 | if (ref $cmd ne 'ARRAY') { | 
| 565 | 1 |  |  |  |  | 11 | error "cmd must be an array reference"; | 
| 566 |  |  |  |  |  |  | } | 
| 567 |  |  |  |  |  |  |  | 
| 568 | 9 |  |  |  |  | 16 | my $doit = 1; | 
| 569 | 9 | 100 | 100 |  |  | 102 | if ($if && !$if->()) { | 
| 570 | 2 |  |  |  |  | 19 | $doit = 0; | 
| 571 |  |  |  |  |  |  | } | 
| 572 | 9 | 100 | 100 |  |  | 56 | if ($doit && $unless && $unless->()) { | 
|  |  |  | 100 |  |  |  |  | 
| 573 | 1 |  |  |  |  | 9 | $doit = 0; | 
| 574 |  |  |  |  |  |  | } | 
| 575 | 9 | 100 | 100 |  |  | 104 | if ($doit && $creates && -e $creates) { | 
|  |  |  | 100 |  |  |  |  | 
| 576 | 1 |  |  |  |  | 8 | $doit = 0; | 
| 577 |  |  |  |  |  |  | } | 
| 578 |  |  |  |  |  |  |  | 
| 579 | 9 | 100 |  |  |  | 23 | if ($doit) { | 
| 580 | 5 |  |  |  |  | 6 | my $doit_commands; | 
| 581 | 5 | 50 |  |  |  | 15 | if (ref $cmd->[0] eq 'ARRAY') { | 
| 582 | 0 |  |  |  |  | 0 | $doit_commands = $self->cmd_run(@$cmd); | 
| 583 |  |  |  |  |  |  | } else { | 
| 584 | 5 |  |  |  |  | 43 | $doit_commands = $self->cmd_system(@$cmd); | 
| 585 |  |  |  |  |  |  | } | 
| 586 | 5 |  |  |  |  | 38 | $doit_commands->set_last_rv(1); | 
| 587 | 5 |  |  |  |  | 15 | $doit_commands; | 
| 588 |  |  |  |  |  |  | } else { | 
| 589 | 4 |  |  |  |  | 25 | Doit::Commands->return_zero; | 
| 590 |  |  |  |  |  |  | } | 
| 591 |  |  |  |  |  |  | } | 
| 592 |  |  |  |  |  |  |  | 
| 593 |  |  |  |  |  |  | sub cmd_ln_nsf { | 
| 594 | 6 |  |  | 6 | 0 | 14 | my($self, $oldfile, $newfile) = @_; | 
| 595 |  |  |  |  |  |  |  | 
| 596 | 6 |  |  |  |  | 8 | my $doit = 1; | 
| 597 | 6 | 100 |  |  |  | 82 | if (!defined $oldfile) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 598 | 1 |  |  |  |  | 3 | error "oldfile was not specified for ln_nsf"; | 
| 599 |  |  |  |  |  |  | } elsif (!defined $newfile) { | 
| 600 | 1 |  |  |  |  | 4 | error "newfile was not specified for ln_nsf"; | 
| 601 |  |  |  |  |  |  | } elsif (-l $newfile) { | 
| 602 | 2 | 50 |  |  |  | 28 | my $points_to = readlink $newfile | 
| 603 |  |  |  |  |  |  | or error "Unexpected: readlink $newfile failed (race condition?)"; | 
| 604 | 2 | 100 |  |  |  | 10 | if ($points_to eq $oldfile) { | 
| 605 | 1 |  |  |  |  | 8 | $doit = 0; | 
| 606 |  |  |  |  |  |  | } | 
| 607 |  |  |  |  |  |  | } elsif (-d $newfile) { | 
| 608 |  |  |  |  |  |  | # Theoretically "ln -nsf destination directory" works (not always, | 
| 609 |  |  |  |  |  |  | # e.g. fails with destination=/), but results are not very useful, | 
| 610 |  |  |  |  |  |  | # so fail here. | 
| 611 | 1 |  |  |  |  | 11 | error qq{"$newfile" already exists as a directory}; | 
| 612 |  |  |  |  |  |  | } else { | 
| 613 |  |  |  |  |  |  | # probably a file, keep $doit=1 | 
| 614 |  |  |  |  |  |  | } | 
| 615 |  |  |  |  |  |  |  | 
| 616 | 3 | 100 |  |  |  | 8 | if ($doit) { | 
| 617 |  |  |  |  |  |  | my @commands =  { | 
| 618 |  |  |  |  |  |  | code => sub { | 
| 619 | 2 |  |  | 2 |  | 4606 | system 'ln', '-nsf', $oldfile, $newfile; | 
| 620 | 2 | 50 |  |  |  | 112 | error "ln -nsf $oldfile $newfile failed" if $? != 0; | 
| 621 |  |  |  |  |  |  | }, | 
| 622 | 2 |  |  |  |  | 22 | msg => "ln -nsf $oldfile $newfile", | 
| 623 |  |  |  |  |  |  | rv  => 1, | 
| 624 |  |  |  |  |  |  | }; | 
| 625 | 2 |  |  |  |  | 8 | Doit::Commands->new(@commands); | 
| 626 |  |  |  |  |  |  | } else { | 
| 627 | 1 |  |  |  |  | 9 | Doit::Commands->return_zero; | 
| 628 |  |  |  |  |  |  | } | 
| 629 |  |  |  |  |  |  | } | 
| 630 |  |  |  |  |  |  |  | 
| 631 |  |  |  |  |  |  | sub cmd_make_path { | 
| 632 | 3 |  |  | 3 | 0 | 10 | my($self, @directories) = @_; | 
| 633 | 3 | 100 |  |  |  | 7 | my $options = {}; if (ref $directories[-1] eq 'HASH') { $options = pop @directories } | 
|  | 3 |  |  |  |  | 17 |  | 
|  | 1 |  |  |  |  | 2 |  | 
| 634 | 3 |  |  |  |  | 6 | my @directories_to_create = grep { !-d $_ } @directories; | 
|  | 5 |  |  |  |  | 107 |  | 
| 635 | 3 | 100 |  |  |  | 11 | if (@directories_to_create) { | 
| 636 |  |  |  |  |  |  | my @commands =  { | 
| 637 |  |  |  |  |  |  | code => sub { | 
| 638 | 2 |  |  | 2 |  | 12 | require File::Path; | 
| 639 | 2 | 50 |  |  |  | 635 | File::Path::make_path(@directories_to_create, $options) | 
| 640 |  |  |  |  |  |  | or error $!; | 
| 641 |  |  |  |  |  |  | }, | 
| 642 | 2 |  |  |  |  | 25 | msg => "make_path @directories", | 
| 643 |  |  |  |  |  |  | rv  => scalar @directories_to_create, | 
| 644 |  |  |  |  |  |  | }; | 
| 645 | 2 |  |  |  |  | 11 | Doit::Commands->new(@commands); | 
| 646 |  |  |  |  |  |  | } else { | 
| 647 | 1 |  |  |  |  | 3 | Doit::Commands->return_zero; | 
| 648 |  |  |  |  |  |  | } | 
| 649 |  |  |  |  |  |  | } | 
| 650 |  |  |  |  |  |  |  | 
| 651 |  |  |  |  |  |  | sub cmd_mkdir { | 
| 652 | 20 |  |  | 20 | 0 | 71 | my($self, $directory, $mode) = @_; | 
| 653 | 20 | 100 |  |  |  | 367 | if (!-d $directory) { | 
| 654 | 18 |  |  |  |  | 58 | my @commands; | 
| 655 | 18 | 100 |  |  |  | 58 | if (defined $mode) { | 
| 656 |  |  |  |  |  |  | push @commands, { | 
| 657 | 2 | 100 |  | 2 |  | 74 | code => sub { mkdir $directory, $mode or error "$!" }, | 
| 658 | 2 |  |  |  |  | 18 | msg  => "mkdir $directory with mask $mode", | 
| 659 |  |  |  |  |  |  | rv   => 1, | 
| 660 |  |  |  |  |  |  | }; | 
| 661 |  |  |  |  |  |  | } else { | 
| 662 |  |  |  |  |  |  | push @commands, { | 
| 663 | 16 | 100 |  | 16 |  | 1129 | code => sub { mkdir $directory or error "$!" }, | 
| 664 | 16 |  |  |  |  | 205 | msg  => "mkdir $directory", | 
| 665 |  |  |  |  |  |  | rv   => 1, | 
| 666 |  |  |  |  |  |  | }; | 
| 667 |  |  |  |  |  |  | } | 
| 668 | 18 |  |  |  |  | 157 | Doit::Commands->new(@commands); | 
| 669 |  |  |  |  |  |  | } else { | 
| 670 | 2 |  |  |  |  | 17 | Doit::Commands->return_zero; | 
| 671 |  |  |  |  |  |  | } | 
| 672 |  |  |  |  |  |  | } | 
| 673 |  |  |  |  |  |  |  | 
| 674 |  |  |  |  |  |  | sub cmd_remove_tree { | 
| 675 | 4 |  |  | 4 | 0 | 8 | my($self, @directories) = @_; | 
| 676 | 4 | 100 |  |  |  | 6 | my $options = {}; if (ref $directories[-1] eq 'HASH') { $options = pop @directories } | 
|  | 4 |  |  |  |  | 11 |  | 
|  | 1 |  |  |  |  | 3 |  | 
| 677 | 4 |  |  |  |  | 8 | my @directories_to_remove = grep { -d $_ } @directories; | 
|  | 6 |  |  |  |  | 97 |  | 
| 678 | 4 | 100 |  |  |  | 14 | if (@directories_to_remove) { | 
| 679 |  |  |  |  |  |  | my @commands =  { | 
| 680 |  |  |  |  |  |  | code => sub { | 
| 681 | 3 |  |  | 3 |  | 16 | require File::Path; | 
| 682 | 3 | 50 |  |  |  | 1206 | File::Path::remove_tree(@directories_to_remove, $options) | 
| 683 |  |  |  |  |  |  | or error "$!"; | 
| 684 |  |  |  |  |  |  | }, | 
| 685 | 3 |  |  |  |  | 34 | msg => "remove_tree @directories_to_remove", | 
| 686 |  |  |  |  |  |  | rv  => scalar @directories_to_remove, | 
| 687 |  |  |  |  |  |  | }; | 
| 688 | 3 |  |  |  |  | 13 | Doit::Commands->new(@commands); | 
| 689 |  |  |  |  |  |  | } else { | 
| 690 | 1 |  |  |  |  | 5 | Doit::Commands->return_zero; | 
| 691 |  |  |  |  |  |  | } | 
| 692 |  |  |  |  |  |  | } | 
| 693 |  |  |  |  |  |  |  | 
| 694 |  |  |  |  |  |  | sub cmd_rename { | 
| 695 | 15 |  |  | 15 | 0 | 25 | my($self, $from, $to) = @_; | 
| 696 | 15 |  |  |  |  | 20 | my @commands; | 
| 697 |  |  |  |  |  |  | push @commands, { | 
| 698 | 13 | 100 |  | 13 |  | 709 | code => sub { rename $from, $to or error "$!" }, | 
| 699 | 15 |  |  |  |  | 117 | msg  => "rename $from, $to", | 
| 700 |  |  |  |  |  |  | rv   => 1, | 
| 701 |  |  |  |  |  |  | }; | 
| 702 | 15 |  |  |  |  | 59 | Doit::Commands->new(@commands); | 
| 703 |  |  |  |  |  |  | } | 
| 704 |  |  |  |  |  |  |  | 
| 705 |  |  |  |  |  |  | sub cmd_copy { | 
| 706 | 17 |  |  | 17 | 0 | 66 | my($self, @args) = @_; | 
| 707 | 17 | 100 | 100 |  |  | 44 | my %options; if (@args && ref $args[0] eq 'HASH') { %options = %{ shift @args } } | 
|  | 17 |  |  |  |  | 150 |  | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 16 |  | 
| 708 | 17 |  |  |  |  | 37 | my $quiet = delete $options{quiet}; | 
| 709 | 17 | 100 |  |  |  | 59 | error "Unhandled options: " . join(" ", %options) if %options; | 
| 710 | 16 | 100 |  |  |  | 54 | if (@args != 2) { | 
| 711 | 1 |  |  |  |  | 5 | error "Expecting two arguments: from and to filenames"; | 
| 712 |  |  |  |  |  |  | } | 
| 713 | 15 |  |  |  |  | 38 | my($from, $to) = @args; | 
| 714 |  |  |  |  |  |  |  | 
| 715 | 15 |  |  |  |  | 20 | my $real_to; | 
| 716 | 15 | 100 |  |  |  | 228 | if (-d $to) { | 
| 717 | 4 |  |  |  |  | 25 | require File::Basename; | 
| 718 | 4 |  |  |  |  | 195 | $real_to = "$to/" . File::Basename::basename($from); | 
| 719 |  |  |  |  |  |  | } else { | 
| 720 | 11 |  |  |  |  | 33 | $real_to = $to; | 
| 721 |  |  |  |  |  |  | } | 
| 722 | 15 | 100 | 100 |  |  | 195 | if (!-e $real_to || do { require File::Compare; File::Compare::compare($from, $real_to) != 0 }) { | 
|  | 8 |  |  |  |  | 1419 |  | 
|  | 8 |  |  |  |  | 2713 |  | 
| 723 |  |  |  |  |  |  | my @commands =  { | 
| 724 |  |  |  |  |  |  | code => sub { | 
| 725 | 11 |  |  | 11 |  | 1132 | require File::Copy; | 
| 726 | 11 | 100 |  |  |  | 4584 | File::Copy::copy($from, $to) | 
| 727 |  |  |  |  |  |  | or error "Copy failed: $!"; | 
| 728 |  |  |  |  |  |  | }, | 
| 729 | 11 |  |  |  |  | 975 | msg => do { | 
| 730 | 11 | 100 |  |  |  | 101 | if (!-e $real_to) { | 
| 731 | 7 |  |  |  |  | 53 | "copy $from $real_to (destination does not exist)"; | 
| 732 |  |  |  |  |  |  | } else { | 
| 733 | 4 | 100 |  |  |  | 33 | if ($quiet) { | 
| 734 | 1 |  |  |  |  | 12 | "copy $from $real_to"; | 
| 735 |  |  |  |  |  |  | } else { | 
| 736 | 3 |  |  |  |  | 20 | "copy $from $real_to\ndiff:\n" . _diff_files($real_to, $from); | 
| 737 |  |  |  |  |  |  | } | 
| 738 |  |  |  |  |  |  | } | 
| 739 |  |  |  |  |  |  | }, | 
| 740 |  |  |  |  |  |  | rv => 1, | 
| 741 |  |  |  |  |  |  | }; | 
| 742 | 11 |  |  |  |  | 118 | Doit::Commands->new(@commands); | 
| 743 |  |  |  |  |  |  | } else { | 
| 744 | 4 |  |  |  |  | 676 | Doit::Commands->return_zero; | 
| 745 |  |  |  |  |  |  | } | 
| 746 |  |  |  |  |  |  | } | 
| 747 |  |  |  |  |  |  |  | 
| 748 |  |  |  |  |  |  | sub cmd_move { | 
| 749 | 3 |  |  | 3 | 0 | 6 | my($self, $from, $to) = @_; | 
| 750 |  |  |  |  |  |  | my @commands = { | 
| 751 |  |  |  |  |  |  | code => sub { | 
| 752 | 2 |  |  | 2 |  | 484 | require File::Copy; | 
| 753 | 2 | 100 |  |  |  | 2099 | File::Copy::move($from, $to) | 
| 754 |  |  |  |  |  |  | or error "Move failed: $!"; | 
| 755 |  |  |  |  |  |  | }, | 
| 756 | 3 |  |  |  |  | 31 | msg => "move $from $to", | 
| 757 |  |  |  |  |  |  | rv  => 1, | 
| 758 |  |  |  |  |  |  | }; | 
| 759 | 3 |  |  |  |  | 17 | Doit::Commands->new(@commands); | 
| 760 |  |  |  |  |  |  | } | 
| 761 |  |  |  |  |  |  |  | 
| 762 |  |  |  |  |  |  | sub _analyze_dollar_questionmark () { | 
| 763 | 50 | 100 |  | 50 |  | 390 | if ($? == -1) { | 
|  |  | 100 |  |  |  |  |  | 
| 764 |  |  |  |  |  |  | ( | 
| 765 | 1 |  |  |  |  | 98 | msg       => sprintf("Could not execute command: %s", $!), | 
| 766 |  |  |  |  |  |  | errno     => $!, | 
| 767 |  |  |  |  |  |  | exitcode  => $?, | 
| 768 |  |  |  |  |  |  | ); | 
| 769 |  |  |  |  |  |  | } elsif ($? & 127) { | 
| 770 | 8 |  |  |  |  | 73 | my $signalnum = $? & 127; | 
| 771 | 8 | 50 |  |  |  | 88 | my $coredump = ($? & 128) ? 'with' : 'without'; | 
| 772 |  |  |  |  |  |  | ( | 
| 773 | 8 |  |  |  |  | 249 | msg       => sprintf("Command died with signal %d, %s coredump", $signalnum, $coredump), | 
| 774 |  |  |  |  |  |  | signalnum => $signalnum, | 
| 775 |  |  |  |  |  |  | coredump  => $coredump, | 
| 776 |  |  |  |  |  |  | ); | 
| 777 |  |  |  |  |  |  | } else { | 
| 778 | 41 |  |  |  |  | 246 | my $exitcode = $?>>8; | 
| 779 |  |  |  |  |  |  | ( | 
| 780 | 41 |  |  |  |  | 971 | msg      => "Command exited with exit code " . $exitcode, | 
| 781 |  |  |  |  |  |  | exitcode => $exitcode, | 
| 782 |  |  |  |  |  |  | ); | 
| 783 |  |  |  |  |  |  | } | 
| 784 |  |  |  |  |  |  | } | 
| 785 |  |  |  |  |  |  |  | 
| 786 |  |  |  |  |  |  | sub _handle_dollar_questionmark (@) { | 
| 787 | 46 |  |  | 46 |  | 459 | my(%opts) = @_; | 
| 788 | 46 |  |  |  |  | 242 | my $prefix_msg = delete $opts{prefix_msg}; | 
| 789 | 46 | 50 |  |  |  | 204 | error "Unhandled options: " . join(" ", %opts) if %opts; | 
| 790 |  |  |  |  |  |  |  | 
| 791 | 46 |  |  |  |  | 261 | my %res = _analyze_dollar_questionmark; | 
| 792 | 46 |  |  |  |  | 298 | my $msg = delete $res{msg}; | 
| 793 | 46 | 100 |  |  |  | 218 | if (defined $prefix_msg) { | 
| 794 | 22 |  |  |  |  | 106 | $msg = $prefix_msg.$msg; | 
| 795 |  |  |  |  |  |  | } | 
| 796 | 46 |  |  |  |  | 594 | Doit::Exception::throw($msg, %res); | 
| 797 |  |  |  |  |  |  | } | 
| 798 |  |  |  |  |  |  |  | 
| 799 |  |  |  |  |  |  | sub _show_cwd ($) { | 
| 800 | 136 |  |  | 136 |  | 355 | my $flag = shift; | 
| 801 | 136 | 100 |  |  |  | 378 | if ($flag) { | 
| 802 | 42 |  |  |  |  | 606 | require Cwd; | 
| 803 | 42 |  |  |  |  | 1277 | " (in " . Cwd::getcwd() . ")"; | 
| 804 |  |  |  |  |  |  | } else { | 
| 805 | 94 |  |  |  |  | 1099 | ""; | 
| 806 |  |  |  |  |  |  | } | 
| 807 |  |  |  |  |  |  | } | 
| 808 |  |  |  |  |  |  |  | 
| 809 |  |  |  |  |  |  | sub _open2 { | 
| 810 | 10 |  |  | 10 |  | 38 | my($instr, @args) = @_; | 
| 811 | 10 |  |  |  |  | 11 | @args = Doit::Win32Util::win32_quote_list(@args) if Doit::IS_WIN; | 
| 812 |  |  |  |  |  |  |  | 
| 813 | 10 |  |  |  |  | 631 | require IPC::Open2; | 
| 814 |  |  |  |  |  |  |  | 
| 815 | 10 |  |  |  |  | 2941 | my($chld_out, $chld_in); | 
| 816 | 10 |  |  |  |  | 74 | my $pid = IPC::Open2::open2($chld_out, $chld_in, @args); | 
| 817 | 10 |  |  |  |  | 31877 | print $chld_in $instr; | 
| 818 | 10 |  |  |  |  | 78 | close $chld_in; | 
| 819 | 10 |  |  |  |  | 101 | local $/; | 
| 820 | 10 |  |  |  |  | 13867 | my $buf = <$chld_out>; | 
| 821 | 10 |  |  |  |  | 161 | close $chld_out; | 
| 822 | 10 |  |  |  |  | 209 | waitpid $pid, 0; | 
| 823 |  |  |  |  |  |  |  | 
| 824 | 10 |  |  |  |  | 372 | $buf; | 
| 825 |  |  |  |  |  |  | } | 
| 826 |  |  |  |  |  |  |  | 
| 827 |  |  |  |  |  |  | sub cmd_open2 { | 
| 828 | 11 |  |  | 11 | 0 | 60 | my($self, @args) = @_; | 
| 829 | 11 | 100 | 66 |  |  | 47 | my %options; if (@args && ref $args[0] eq 'HASH') { %options = %{ shift @args } } | 
|  | 11 |  |  |  |  | 145 |  | 
|  | 8 |  |  |  |  | 17 |  | 
|  | 8 |  |  |  |  | 63 |  | 
| 830 | 11 |  |  |  |  | 26 | my $quiet = delete $options{quiet}; | 
| 831 | 11 |  |  |  |  | 18 | my $info = delete $options{info}; | 
| 832 | 11 | 100 |  |  |  | 14 | my $instr = delete $options{instr}; $instr = '' if !defined $instr; | 
|  | 11 |  |  |  |  | 30 |  | 
| 833 | 11 | 50 |  |  |  | 20 | error "Unhandled options: " . join(" ", %options) if %options; | 
| 834 |  |  |  |  |  |  |  | 
| 835 |  |  |  |  |  |  | my $code = sub { | 
| 836 | 10 |  |  | 10 |  | 51 | my $buf = _open2($instr, @args); | 
| 837 | 10 | 100 | 66 |  |  | 180 | $? == 0 | 
|  |  | 100 |  |  |  |  |  | 
| 838 |  |  |  |  |  |  | or _handle_dollar_questionmark($quiet||$info ? (prefix_msg => "open2 command '@args' failed: ") : ()); | 
| 839 | 7 |  |  |  |  | 202 | $buf; | 
| 840 | 11 |  |  |  |  | 76 | }; | 
| 841 |  |  |  |  |  |  |  | 
| 842 | 11 |  |  |  |  | 39 | my @commands; | 
| 843 |  |  |  |  |  |  | push @commands, { | 
| 844 | 11 | 100 |  | 1 |  | 96 | ($info ? (rv => $code->(), code => sub {}) : (code => $code)), | 
|  |  | 100 |  |  |  |  |  | 
| 845 |  |  |  |  |  |  | ($quiet ? () : (msg => "@args")), | 
| 846 |  |  |  |  |  |  | }; | 
| 847 | 10 |  |  |  |  | 167 | Doit::Commands->new(@commands); | 
| 848 |  |  |  |  |  |  | } | 
| 849 |  |  |  |  |  |  |  | 
| 850 |  |  |  |  |  |  | sub cmd_info_open2 { | 
| 851 | 3 |  |  | 3 | 0 | 16 | my($self, @args) = @_; | 
| 852 | 3 | 100 | 66 |  |  | 11 | my %options; if (@args && ref $args[0] eq 'HASH') { %options = %{ shift @args } } | 
|  | 3 |  |  |  |  | 41 |  | 
|  | 1 |  |  |  |  | 5 |  | 
|  | 1 |  |  |  |  | 9 |  | 
| 853 | 3 |  |  |  |  | 17 | $options{info} = 1; | 
| 854 | 3 |  |  |  |  | 20 | $self->cmd_open2(\%options, @args); | 
| 855 |  |  |  |  |  |  | } | 
| 856 |  |  |  |  |  |  |  | 
| 857 |  |  |  |  |  |  | sub _open3 { | 
| 858 | 73 |  |  | 73 |  | 255 | my($instr, @args) = @_; | 
| 859 | 73 |  |  |  |  | 183 | @args = Doit::Win32Util::win32_quote_list(@args) if Doit::IS_WIN; | 
| 860 |  |  |  |  |  |  |  | 
| 861 | 73 |  |  |  |  | 3884 | require IO::Select; | 
| 862 | 73 |  |  |  |  | 4343 | require IPC::Open3; | 
| 863 | 73 |  |  |  |  | 22672 | require Symbol; | 
| 864 |  |  |  |  |  |  |  | 
| 865 | 73 |  |  |  |  | 169 | my($chld_out, $chld_in, $chld_err); | 
| 866 | 73 |  |  |  |  | 756 | $chld_err = Symbol::gensym(); | 
| 867 | 73 | 100 |  |  |  | 2666 | my $pid = IPC::Open3::open3((defined $instr ? $chld_in : undef), $chld_out, $chld_err, @args); | 
| 868 | 72 | 100 |  |  |  | 241629 | if (defined $instr) { | 
| 869 | 34 |  |  |  |  | 374 | print $chld_in $instr; | 
| 870 | 34 |  |  |  |  | 308 | close $chld_in; | 
| 871 |  |  |  |  |  |  | } | 
| 872 |  |  |  |  |  |  |  | 
| 873 | 72 |  |  |  |  | 1909 | my $sel = IO::Select->new; | 
| 874 | 72 |  |  |  |  | 1700 | $sel->add($chld_out); | 
| 875 | 72 |  |  |  |  | 6017 | $sel->add($chld_err); | 
| 876 |  |  |  |  |  |  |  | 
| 877 | 72 |  |  |  |  | 3123 | my %buf = ($chld_out => '', $chld_err => ''); | 
| 878 | 72 |  |  |  |  | 485 | while(my @ready_fhs = $sel->can_read()) { | 
| 879 | 141 |  |  |  |  | 1094054 | for my $ready_fh (@ready_fhs) { | 
| 880 | 213 |  |  |  |  | 978 | my $buf = ''; | 
| 881 | 213 |  |  |  |  | 21825 | while (sysread $ready_fh, $buf, 1024, length $buf) { } | 
| 882 | 213 | 100 |  |  |  | 746 | if ($buf eq '') { # eof | 
| 883 | 144 |  |  |  |  | 730 | $sel->remove($ready_fh); | 
| 884 | 144 |  |  |  |  | 6842 | $ready_fh->close; | 
| 885 | 144 | 100 |  |  |  | 3037 | last if $sel->count == 0; | 
| 886 |  |  |  |  |  |  | } else { | 
| 887 | 69 |  |  |  |  | 858 | $buf{$ready_fh} .= $buf; | 
| 888 |  |  |  |  |  |  | } | 
| 889 |  |  |  |  |  |  | } | 
| 890 |  |  |  |  |  |  | } | 
| 891 |  |  |  |  |  |  |  | 
| 892 | 72 |  |  |  |  | 2490 | waitpid $pid, 0; | 
| 893 |  |  |  |  |  |  |  | 
| 894 | 72 |  |  |  |  | 2924 | ($buf{$chld_out}, $buf{$chld_err}); | 
| 895 |  |  |  |  |  |  | } | 
| 896 |  |  |  |  |  |  |  | 
| 897 |  |  |  |  |  |  | sub cmd_open3 { | 
| 898 | 38 |  |  | 38 | 0 | 199 | my($self, @args) = @_; | 
| 899 | 38 | 100 | 66 |  |  | 114 | my %options; if (@args && ref $args[0] eq 'HASH') { %options = %{ shift @args } } | 
|  | 38 |  |  |  |  | 508 |  | 
|  | 36 |  |  |  |  | 86 |  | 
|  | 36 |  |  |  |  | 314 |  | 
| 900 | 38 |  |  |  |  | 222 | my $quiet = delete $options{quiet}; | 
| 901 | 38 |  |  |  |  | 151 | my $info = delete $options{info}; | 
| 902 | 38 |  |  |  |  | 80 | my $instr = delete $options{instr}; | 
| 903 | 38 |  |  |  |  | 78 | my $errref = delete $options{errref}; | 
| 904 | 38 |  |  |  |  | 63 | my $statusref = delete $options{statusref}; | 
| 905 | 38 | 50 |  |  |  | 107 | error "Unhandled options: " . join(" ", %options) if %options; | 
| 906 |  |  |  |  |  |  |  | 
| 907 |  |  |  |  |  |  | my $code = sub { | 
| 908 | 37 |  |  | 37 |  | 145 | my($stdout, $stderr) = _open3($instr, @args); | 
| 909 |  |  |  |  |  |  |  | 
| 910 | 36 | 100 |  |  |  | 352 | if ($errref) { | 
| 911 | 29 |  |  |  |  | 225 | $$errref = $stderr; | 
| 912 |  |  |  |  |  |  | } | 
| 913 |  |  |  |  |  |  |  | 
| 914 | 36 | 100 |  |  |  | 139 | if ($statusref) { | 
| 915 | 2 |  |  |  |  | 22 | %$statusref = ( _analyze_dollar_questionmark ); | 
| 916 |  |  |  |  |  |  | } else { | 
| 917 | 34 | 100 |  |  |  | 327 | if ($? != 0) { | 
| 918 | 4 | 100 | 66 |  |  | 89 | _handle_dollar_questionmark($quiet||$info ? (prefix_msg => "open3 command '@args' failed: ") : ()); | 
| 919 |  |  |  |  |  |  | } | 
| 920 |  |  |  |  |  |  | } | 
| 921 |  |  |  |  |  |  |  | 
| 922 | 32 |  |  |  |  | 321 | $stdout; | 
| 923 | 38 |  |  |  |  | 395 | }; | 
| 924 |  |  |  |  |  |  |  | 
| 925 | 38 |  |  |  |  | 186 | my @commands; | 
| 926 |  |  |  |  |  |  | push @commands, { | 
| 927 | 38 | 100 |  | 1 |  | 323 | ($info ? (rv => $code->(), code => sub {}) : (code => $code)), | 
|  |  | 100 |  |  |  |  |  | 
| 928 |  |  |  |  |  |  | ($quiet ? () : (msg => "@args")), | 
| 929 |  |  |  |  |  |  | }; | 
| 930 | 37 |  |  |  |  | 520 | Doit::Commands->new(@commands); | 
| 931 |  |  |  |  |  |  | } | 
| 932 |  |  |  |  |  |  |  | 
| 933 |  |  |  |  |  |  | sub cmd_info_open3 { | 
| 934 | 3 |  |  | 3 | 0 | 13 | my($self, @args) = @_; | 
| 935 | 3 | 100 | 66 |  |  | 7 | my %options; if (@args && ref $args[0] eq 'HASH') { %options = %{ shift @args } } | 
|  | 3 |  |  |  |  | 60 |  | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 4 |  | 
| 936 | 3 |  |  |  |  | 12 | $options{info} = 1; | 
| 937 | 3 |  |  |  |  | 13 | $self->cmd_open3(\%options, @args); | 
| 938 |  |  |  |  |  |  | } | 
| 939 |  |  |  |  |  |  |  | 
| 940 |  |  |  |  |  |  | sub _qx { | 
| 941 | 143 |  |  | 143 |  | 525 | my(@args) = @_; | 
| 942 | 143 |  |  |  |  | 170 | @args = Doit::Win32Util::win32_quote_list(@args) if Doit::IS_WIN; | 
| 943 |  |  |  |  |  |  |  | 
| 944 | 143 | 50 |  |  |  | 308142 | open my $fh, '-|', @args | 
| 945 |  |  |  |  |  |  | or error "Error running '@args': $!"; | 
| 946 | 143 |  |  |  |  | 4227 | local $/; | 
| 947 | 143 |  |  |  |  | 224683 | my $buf = <$fh>; | 
| 948 | 143 |  |  |  |  | 5036 | close $fh; | 
| 949 | 143 |  |  |  |  | 7122 | \$buf; | 
| 950 |  |  |  |  |  |  | } | 
| 951 |  |  |  |  |  |  |  | 
| 952 |  |  |  |  |  |  | sub cmd_qx { | 
| 953 | 144 |  |  | 144 | 0 | 447 | my($self, @args) = @_; | 
| 954 | 144 | 100 | 66 |  |  | 236 | my %options; if (@args && ref $args[0] eq 'HASH') { %options = %{ shift @args } } | 
|  | 144 |  |  |  |  | 1381 |  | 
|  | 138 |  |  |  |  | 260 |  | 
|  | 138 |  |  |  |  | 1181 |  | 
| 955 | 144 |  |  |  |  | 439 | my $quiet = delete $options{quiet}; | 
| 956 | 144 |  |  |  |  | 221 | my $info = delete $options{info}; | 
| 957 | 144 |  |  |  |  | 230 | my $statusref = delete $options{statusref}; | 
| 958 | 144 | 50 |  |  |  | 402 | error "Unhandled options: " . join(" ", %options) if %options; | 
| 959 |  |  |  |  |  |  |  | 
| 960 |  |  |  |  |  |  | my $code = sub { | 
| 961 | 143 |  |  | 143 |  | 718 | my $bufref = _qx(@args); | 
| 962 | 143 | 100 |  |  |  | 929 | if ($statusref) { | 
| 963 | 2 |  |  |  |  | 29 | %$statusref = ( _analyze_dollar_questionmark ); | 
| 964 |  |  |  |  |  |  | } else { | 
| 965 | 141 | 100 |  |  |  | 1858 | if ($? != 0) { | 
| 966 | 22 | 100 | 100 |  |  | 511 | _handle_dollar_questionmark($quiet||$info ? (prefix_msg => "qx command '@args' failed: ") : ()); | 
| 967 |  |  |  |  |  |  | } | 
| 968 |  |  |  |  |  |  | } | 
| 969 | 121 |  |  |  |  | 4768 | $$bufref; | 
| 970 | 144 |  |  |  |  | 1388 | }; | 
| 971 |  |  |  |  |  |  |  | 
| 972 | 144 |  |  |  |  | 277 | my @commands; | 
| 973 |  |  |  |  |  |  | push @commands, { | 
| 974 | 144 | 100 |  | 113 |  | 450 | ($info ? (rv => $code->(), code => sub {}) : (code => $code)), | 
|  |  | 100 |  |  |  |  |  | 
| 975 |  |  |  |  |  |  | ($quiet ? () : (msg => "@args")), | 
| 976 |  |  |  |  |  |  | }; | 
| 977 | 124 |  |  |  |  | 3370 | Doit::Commands->new(@commands); | 
| 978 |  |  |  |  |  |  | } | 
| 979 |  |  |  |  |  |  |  | 
| 980 |  |  |  |  |  |  | sub cmd_info_qx { | 
| 981 | 134 |  |  | 134 | 0 | 672 | my($self, @args) = @_; | 
| 982 | 134 | 100 | 66 |  |  | 208 | my %options; if (@args && ref $args[0] eq 'HASH') { %options = %{ shift @args } } | 
|  | 134 |  |  |  |  | 1485 |  | 
|  | 131 |  |  |  |  | 266 |  | 
|  | 131 |  |  |  |  | 591 |  | 
| 983 | 134 |  |  |  |  | 539 | $options{info} = 1; | 
| 984 | 134 |  |  |  |  | 700 | $self->cmd_qx(\%options, @args); | 
| 985 |  |  |  |  |  |  | } | 
| 986 |  |  |  |  |  |  |  | 
| 987 |  |  |  |  |  |  | sub cmd_rmdir { | 
| 988 | 6 |  |  | 6 | 0 | 18 | my($self, $directory) = @_; | 
| 989 | 6 | 100 |  |  |  | 84 | if (-d $directory) { | 
| 990 |  |  |  |  |  |  | my @commands =  { | 
| 991 | 5 | 100 |  | 5 |  | 275 | code => sub { rmdir $directory or error "$!" }, | 
| 992 | 5 |  |  |  |  | 48 | msg  => "rmdir $directory", | 
| 993 |  |  |  |  |  |  | }; | 
| 994 | 5 |  |  |  |  | 31 | Doit::Commands->new(@commands); | 
| 995 |  |  |  |  |  |  | } else { | 
| 996 | 1 |  |  |  |  | 5 | Doit::Commands->return_zero; | 
| 997 |  |  |  |  |  |  | } | 
| 998 |  |  |  |  |  |  | } | 
| 999 |  |  |  |  |  |  |  | 
| 1000 |  |  |  |  |  |  | sub cmd_run { | 
| 1001 | 0 |  |  | 0 | 0 | 0 | my($self, @args) = @_; | 
| 1002 | 0 |  |  |  |  | 0 | my @commands; | 
| 1003 |  |  |  |  |  |  | push @commands, { | 
| 1004 |  |  |  |  |  |  | code => sub { | 
| 1005 | 0 |  |  | 0 |  | 0 | require IPC::Run; | 
| 1006 | 0 |  |  |  |  | 0 | my $success = IPC::Run::run(@args); | 
| 1007 | 0 | 0 |  |  |  | 0 | if (!$success) { | 
| 1008 | 0 |  |  |  |  | 0 | _handle_dollar_questionmark; | 
| 1009 |  |  |  |  |  |  | } | 
| 1010 |  |  |  |  |  |  | }, | 
| 1011 | 0 |  |  |  |  | 0 | msg  => do { | 
| 1012 | 0 |  |  |  |  | 0 | my @print_cmd; | 
| 1013 | 0 |  |  |  |  | 0 | for my $arg (@args) { | 
| 1014 | 0 | 0 |  |  |  | 0 | if (ref $arg eq 'ARRAY') { | 
| 1015 | 0 |  |  |  |  | 0 | push @print_cmd, @$arg; | 
| 1016 |  |  |  |  |  |  | } else { | 
| 1017 | 0 |  |  |  |  | 0 | push @print_cmd, $arg; | 
| 1018 |  |  |  |  |  |  | } | 
| 1019 |  |  |  |  |  |  | } | 
| 1020 | 0 |  |  |  |  | 0 | join " ", @print_cmd; | 
| 1021 |  |  |  |  |  |  | }, | 
| 1022 |  |  |  |  |  |  | rv  => 1, | 
| 1023 |  |  |  |  |  |  | }; | 
| 1024 | 0 |  |  |  |  | 0 | Doit::Commands->new(@commands); | 
| 1025 |  |  |  |  |  |  | } | 
| 1026 |  |  |  |  |  |  |  | 
| 1027 |  |  |  |  |  |  | sub cmd_setenv { | 
| 1028 | 3 |  |  | 3 | 0 | 6 | my($self, $key, $val) = @_; | 
| 1029 | 3 | 100 | 100 |  |  | 15 | if (!defined $ENV{$key} || $ENV{$key} ne $val) { | 
| 1030 |  |  |  |  |  |  | my @commands =  { | 
| 1031 | 2 |  |  | 2 |  | 12 | code => sub { $ENV{$key} = $val }, | 
| 1032 | 2 | 100 |  |  |  | 19 | msg  => qq{set \$ENV{$key} to "$val", previous value was } . (defined $ENV{$key} ? qq{"$ENV{$key}"} : qq{unset}), | 
| 1033 |  |  |  |  |  |  | rv   => 1, | 
| 1034 |  |  |  |  |  |  | }; | 
| 1035 | 2 |  |  |  |  | 10 | Doit::Commands->new(@commands); | 
| 1036 |  |  |  |  |  |  | } else { | 
| 1037 | 1 |  |  |  |  | 5 | Doit::Commands->return_zero; | 
| 1038 |  |  |  |  |  |  | } | 
| 1039 |  |  |  |  |  |  | } | 
| 1040 |  |  |  |  |  |  |  | 
| 1041 |  |  |  |  |  |  | sub cmd_symlink { | 
| 1042 | 2 |  |  | 2 | 0 | 5 | my($self, $oldfile, $newfile) = @_; | 
| 1043 | 2 |  |  |  |  | 3 | my $doit; | 
| 1044 | 2 | 100 |  |  |  | 34 | if (-l $newfile) { | 
|  |  | 50 |  |  |  |  |  | 
| 1045 | 1 | 50 |  |  |  | 13 | my $points_to = readlink $newfile | 
| 1046 |  |  |  |  |  |  | or error "Unexpected: readlink $newfile failed (race condition?)"; | 
| 1047 | 1 | 50 |  |  |  | 4 | if ($points_to ne $oldfile) { | 
| 1048 | 0 |  |  |  |  | 0 | $doit = 1; | 
| 1049 |  |  |  |  |  |  | } | 
| 1050 |  |  |  |  |  |  | } elsif (!-e $newfile) { | 
| 1051 | 1 |  |  |  |  | 3 | $doit = 1; | 
| 1052 |  |  |  |  |  |  | } else { | 
| 1053 | 0 |  |  |  |  | 0 | warning "$newfile exists but is not a symlink, will fail later..."; | 
| 1054 |  |  |  |  |  |  | } | 
| 1055 | 2 | 100 |  |  |  | 5 | if ($doit) { | 
| 1056 |  |  |  |  |  |  | my @commands =  { | 
| 1057 | 1 | 50 |  | 1 |  | 35 | code => sub { symlink $oldfile, $newfile or error "$!" }, | 
| 1058 | 1 |  |  |  |  | 10 | msg  => "symlink $oldfile $newfile", | 
| 1059 |  |  |  |  |  |  | rv   => 1, | 
| 1060 |  |  |  |  |  |  | }; | 
| 1061 | 1 |  |  |  |  | 4 | Doit::Commands->new(@commands); | 
| 1062 |  |  |  |  |  |  | } else { | 
| 1063 | 1 |  |  |  |  | 4 | Doit::Commands->return_zero; | 
| 1064 |  |  |  |  |  |  | } | 
| 1065 |  |  |  |  |  |  | } | 
| 1066 |  |  |  |  |  |  |  | 
| 1067 |  |  |  |  |  |  | sub cmd_system { | 
| 1068 | 138 |  |  | 138 | 0 | 924 | my($self, @args) = @_; | 
| 1069 | 138 | 100 | 66 |  |  | 310 | my %options; if (@args && ref $args[0] eq 'HASH') { %options = %{ shift @args } } | 
|  | 138 |  |  |  |  | 2622 |  | 
|  | 46 |  |  |  |  | 135 |  | 
|  | 46 |  |  |  |  | 487 |  | 
| 1070 | 138 |  |  |  |  | 424 | my $quiet = delete $options{quiet}; | 
| 1071 | 138 |  |  |  |  | 240 | my $info = delete $options{info}; | 
| 1072 | 138 |  |  |  |  | 203 | my $show_cwd = delete $options{show_cwd}; | 
| 1073 | 138 | 50 |  |  |  | 528 | error "Unhandled options: " . join(" ", %options) if %options; | 
| 1074 |  |  |  |  |  |  |  | 
| 1075 | 138 |  |  |  |  | 234 | @args = Doit::Win32Util::win32_quote_list(@args) if Doit::IS_WIN; | 
| 1076 |  |  |  |  |  |  |  | 
| 1077 |  |  |  |  |  |  | my $code = sub { | 
| 1078 | 137 |  |  | 137 |  | 1669415 | system @args; | 
| 1079 | 137 | 100 |  |  |  | 7620 | if ($? != 0) { | 
| 1080 | 17 |  |  |  |  | 399 | _handle_dollar_questionmark; | 
| 1081 |  |  |  |  |  |  | } | 
| 1082 | 138 |  |  |  |  | 1655 | }; | 
| 1083 |  |  |  |  |  |  |  | 
| 1084 | 138 |  |  |  |  | 294 | my @commands; | 
| 1085 |  |  |  |  |  |  | push @commands, { | 
| 1086 |  |  |  |  |  |  | ($info | 
| 1087 |  |  |  |  |  |  | ? ( | 
| 1088 | 2 |  |  |  |  | 5 | rv   => do { $code->(); 1 }, | 
|  | 2 |  |  |  |  | 135 |  | 
| 1089 |  |  |  | 0 |  |  | code => sub {}, | 
| 1090 |  |  |  |  |  |  | ) | 
| 1091 | 138 | 100 |  |  |  | 1736 | : ( | 
|  |  | 100 |  |  |  |  |  | 
| 1092 |  |  |  |  |  |  | rv   => 1, | 
| 1093 |  |  |  |  |  |  | code => $code, | 
| 1094 |  |  |  |  |  |  | ) | 
| 1095 |  |  |  |  |  |  | ), | 
| 1096 |  |  |  |  |  |  | ($quiet ? () : (msg  => "@args" . _show_cwd($show_cwd))), | 
| 1097 |  |  |  |  |  |  | }; | 
| 1098 | 138 |  |  |  |  | 1500 | Doit::Commands->new(@commands); | 
| 1099 |  |  |  |  |  |  | } | 
| 1100 |  |  |  |  |  |  |  | 
| 1101 |  |  |  |  |  |  | sub cmd_info_system { | 
| 1102 | 1 |  |  | 1 | 0 | 4 | my($self, @args) = @_; | 
| 1103 | 1 | 50 | 33 |  |  | 2 | my %options; if (@args && ref $args[0] eq 'HASH') { %options = %{ shift @args } } | 
|  | 1 |  |  |  |  | 13 |  | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 1104 | 1 |  |  |  |  | 8 | $options{info} = 1; | 
| 1105 | 1 |  |  |  |  | 7 | $self->cmd_system(\%options, @args); | 
| 1106 |  |  |  |  |  |  | } | 
| 1107 |  |  |  |  |  |  |  | 
| 1108 |  |  |  |  |  |  | sub cmd_touch { | 
| 1109 | 23 |  |  | 23 | 0 | 140 | my($self, @files) = @_; | 
| 1110 | 23 |  |  |  |  | 59 | my @commands; | 
| 1111 | 23 |  |  |  |  | 97 | for my $file (@files) { | 
| 1112 | 29 | 100 |  |  |  | 566 | if (!-e $file) { | 
| 1113 |  |  |  |  |  |  | push @commands, { | 
| 1114 | 28 | 50 |  | 28 |  | 2447 | code => sub { open my $fh, '>>', $file or error "$!" }, | 
| 1115 | 28 |  |  |  |  | 447 | msg  => "touch non-existent file $file", | 
| 1116 |  |  |  |  |  |  | } | 
| 1117 |  |  |  |  |  |  | } else { | 
| 1118 |  |  |  |  |  |  | push @commands, { | 
| 1119 | 1 | 50 |  | 1 |  | 25 | code => sub { utime time, time, $file or error "$!" }, | 
| 1120 | 1 |  |  |  |  | 9 | msg  => "touch existent file $file", | 
| 1121 |  |  |  |  |  |  | }; | 
| 1122 |  |  |  |  |  |  | } | 
| 1123 |  |  |  |  |  |  | } | 
| 1124 | 23 |  |  |  |  | 265 | my $doit_commands = Doit::Commands->new(@commands); | 
| 1125 | 23 |  |  |  |  | 138 | $doit_commands->set_last_rv(scalar @files); | 
| 1126 | 23 |  |  |  |  | 109 | $doit_commands; | 
| 1127 |  |  |  |  |  |  | } | 
| 1128 |  |  |  |  |  |  |  | 
| 1129 |  |  |  |  |  |  | sub cmd_create_file_if_nonexisting { | 
| 1130 | 16 |  |  | 16 | 0 | 67 | my($self, @files) = @_; | 
| 1131 | 16 |  |  |  |  | 40 | my @commands; | 
| 1132 | 16 |  |  |  |  | 63 | for my $file (@files) { | 
| 1133 | 18 | 100 |  |  |  | 308 | if (!-e $file) { | 
| 1134 |  |  |  |  |  |  | push @commands, { | 
| 1135 | 16 | 50 |  | 16 |  | 1256 | code => sub { open my $fh, '>>', $file or error "$!" }, | 
| 1136 | 16 |  |  |  |  | 218 | msg  => "create empty file $file", | 
| 1137 |  |  |  |  |  |  | }; | 
| 1138 |  |  |  |  |  |  | } | 
| 1139 |  |  |  |  |  |  | } | 
| 1140 | 16 | 100 |  |  |  | 65 | if (@commands) { | 
| 1141 | 15 |  |  |  |  | 96 | my $doit_commands = Doit::Commands->new(@commands); | 
| 1142 | 15 |  |  |  |  | 97 | $doit_commands->set_last_rv(scalar @commands); | 
| 1143 | 15 |  |  |  |  | 49 | $doit_commands; | 
| 1144 |  |  |  |  |  |  | } else { | 
| 1145 | 1 |  |  |  |  | 5 | Doit::Commands->return_zero; | 
| 1146 |  |  |  |  |  |  | } | 
| 1147 |  |  |  |  |  |  | } | 
| 1148 |  |  |  |  |  |  |  | 
| 1149 |  |  |  |  |  |  | sub cmd_unlink { | 
| 1150 | 20 |  |  | 20 | 0 | 112 | my($self, @files) = @_; | 
| 1151 | 20 |  |  |  |  | 54 | my @files_to_remove; | 
| 1152 | 20 |  |  |  |  | 82 | for my $file (@files) { | 
| 1153 | 28 | 100 | 100 |  |  | 439 | if (-e $file || -l $file) { | 
| 1154 | 25 |  |  |  |  | 110 | push @files_to_remove, $file; | 
| 1155 |  |  |  |  |  |  | } | 
| 1156 |  |  |  |  |  |  | } | 
| 1157 | 20 | 100 |  |  |  | 71 | if (@files_to_remove) { | 
| 1158 |  |  |  |  |  |  | my @commands =  { | 
| 1159 | 18 | 50 |  | 18 |  | 1023 | code => sub { unlink @files_to_remove or error "$!" }, | 
| 1160 | 18 |  |  |  |  | 274 | msg  => "unlink @files_to_remove", # shellquote? | 
| 1161 |  |  |  |  |  |  | }; | 
| 1162 | 18 |  |  |  |  | 135 | Doit::Commands->new(@commands); | 
| 1163 |  |  |  |  |  |  | } else { | 
| 1164 | 2 |  |  |  |  | 7 | Doit::Commands->return_zero; | 
| 1165 |  |  |  |  |  |  | } | 
| 1166 |  |  |  |  |  |  | } | 
| 1167 |  |  |  |  |  |  |  | 
| 1168 |  |  |  |  |  |  | sub cmd_unsetenv { | 
| 1169 | 2 |  |  | 2 | 0 | 4 | my($self, $key) = @_; | 
| 1170 | 2 | 100 |  |  |  | 7 | if (defined $ENV{$key}) { | 
| 1171 |  |  |  |  |  |  | my @commands =  { | 
| 1172 | 1 |  |  | 1 |  | 7 | code => sub { delete $ENV{$key} }, | 
| 1173 | 1 |  |  |  |  | 9 | msg  => qq{unset \$ENV{$key}, previous value was "$ENV{$key}"}, | 
| 1174 |  |  |  |  |  |  | rv   => 1, | 
| 1175 |  |  |  |  |  |  | }; | 
| 1176 | 1 |  |  |  |  | 3 | Doit::Commands->new(@commands); | 
| 1177 |  |  |  |  |  |  | } else { | 
| 1178 | 1 |  |  |  |  | 4 | Doit::Commands->return_zero; | 
| 1179 |  |  |  |  |  |  | } | 
| 1180 |  |  |  |  |  |  | } | 
| 1181 |  |  |  |  |  |  |  | 
| 1182 |  |  |  |  |  |  | sub cmd_utime { | 
| 1183 | 10 |  |  | 10 | 0 | 35 | my($self, $atime, $mtime, @files) = @_; | 
| 1184 |  |  |  |  |  |  |  | 
| 1185 | 10 |  |  |  |  | 18 | my $now; | 
| 1186 | 10 | 100 |  |  |  | 33 | if (!defined $atime) { | 
| 1187 | 1 |  | 33 |  |  | 6 | $atime = ($now ||= time); | 
| 1188 |  |  |  |  |  |  | } | 
| 1189 | 10 | 100 |  |  |  | 25 | if (!defined $mtime) { | 
| 1190 | 1 |  | 33 |  |  | 3 | $mtime = ($now ||= time); | 
| 1191 |  |  |  |  |  |  | } | 
| 1192 |  |  |  |  |  |  |  | 
| 1193 | 10 |  |  |  |  | 14 | my @files_to_change; | 
| 1194 | 10 |  |  |  |  | 28 | for my $file (@files) { | 
| 1195 | 14 |  |  |  |  | 167 | my @s = stat $file; | 
| 1196 | 14 | 100 |  |  |  | 41 | if (@s) { | 
| 1197 | 10 | 100 | 100 |  |  | 34 | if ($s[8] != $atime || $s[9] != $mtime) { | 
| 1198 | 9 |  |  |  |  | 30 | push @files_to_change, $file; | 
| 1199 |  |  |  |  |  |  | } | 
| 1200 |  |  |  |  |  |  | } else { | 
| 1201 | 4 |  |  |  |  | 13 | push @files_to_change, $file; # will fail later | 
| 1202 |  |  |  |  |  |  | } | 
| 1203 |  |  |  |  |  |  | } | 
| 1204 |  |  |  |  |  |  |  | 
| 1205 | 10 | 100 |  |  |  | 22 | if (@files_to_change) { | 
| 1206 |  |  |  |  |  |  | my @commands =  { | 
| 1207 |  |  |  |  |  |  | code => sub { | 
| 1208 | 9 |  |  | 9 |  | 167 | my $changed_files = utime $atime, $mtime, @files; | 
| 1209 | 9 | 100 |  |  |  | 57 | if ($changed_files != @files_to_change) { | 
| 1210 | 3 | 100 |  |  |  | 12 | if (@files_to_change == 1) { | 
|  |  | 100 |  |  |  |  |  | 
| 1211 | 1 |  |  |  |  | 8 | error "utime failed: $!"; | 
| 1212 |  |  |  |  |  |  | } elsif ($changed_files == 0) { | 
| 1213 | 1 |  |  |  |  | 8 | error "utime failed on all files: $!"; | 
| 1214 |  |  |  |  |  |  | } else { | 
| 1215 | 1 |  |  |  |  | 11 | error "utime failed on some files (" . (@files_to_change-$changed_files) . "/" . scalar(@files_to_change) . "): $!"; | 
| 1216 |  |  |  |  |  |  | } | 
| 1217 |  |  |  |  |  |  | } | 
| 1218 |  |  |  |  |  |  | }, | 
| 1219 | 9 |  |  |  |  | 111 | msg  => "utime $atime, $mtime, @files", | 
| 1220 |  |  |  |  |  |  | rv   => scalar @files_to_change, | 
| 1221 |  |  |  |  |  |  | }; | 
| 1222 | 9 |  |  |  |  | 45 | Doit::Commands->new(@commands); | 
| 1223 |  |  |  |  |  |  | } else { | 
| 1224 | 1 |  |  |  |  | 4 | Doit::Commands->return_zero; | 
| 1225 |  |  |  |  |  |  | } | 
| 1226 |  |  |  |  |  |  | } | 
| 1227 |  |  |  |  |  |  |  | 
| 1228 |  |  |  |  |  |  | sub cmd_which { | 
| 1229 | 15 |  |  | 15 | 0 | 37 | my($self, @args) = @_; | 
| 1230 | 15 | 100 |  |  |  | 61 | if (@args != 1) { | 
| 1231 | 2 |  |  |  |  | 6 | error "Expecting exactly one argument: command"; | 
| 1232 |  |  |  |  |  |  | } | 
| 1233 | 13 |  |  |  |  | 46 | my $path = Doit::Util::is_in_path($args[0]); | 
| 1234 | 13 |  |  | 13 |  | 142 | Doit::Commands->new({ rv => $path, code => sub {} }); | 
| 1235 |  |  |  |  |  |  | } | 
| 1236 |  |  |  |  |  |  |  | 
| 1237 |  |  |  |  |  |  | sub cmd_write_binary { | 
| 1238 | 32 |  |  | 32 | 0 | 138 | my($self, @args) = @_; | 
| 1239 | 32 | 100 | 100 |  |  | 81 | my %options; if (@args && ref $args[0] eq 'HASH') { %options = %{ shift @args } } | 
|  | 32 |  |  |  |  | 289 |  | 
|  | 12 |  |  |  |  | 25 |  | 
|  | 12 |  |  |  |  | 53 |  | 
| 1240 | 32 |  | 100 |  |  | 159 | my $quiet  = delete $options{quiet} || 0; | 
| 1241 | 32 | 100 |  |  |  | 151 | my $atomic = exists $options{atomic} ? delete $options{atomic} : 1; | 
| 1242 | 32 | 100 |  |  |  | 106 | error "Unhandled options: " . join(" ", %options) if %options; | 
| 1243 | 31 | 100 |  |  |  | 132 | if (@args != 2) { | 
| 1244 | 1 |  |  |  |  | 4 | error "Expecting two arguments: filename and contents"; | 
| 1245 |  |  |  |  |  |  | } | 
| 1246 | 30 |  |  |  |  | 68 | my($filename, $content) = @args; | 
| 1247 |  |  |  |  |  |  |  | 
| 1248 | 30 |  |  |  |  | 57 | my $doit; | 
| 1249 |  |  |  |  |  |  | my $need_diff; | 
| 1250 | 30 | 100 |  |  |  | 686 | if (!-e $filename) { | 
|  |  | 100 |  |  |  |  |  | 
| 1251 | 13 |  |  |  |  | 57 | $doit = 1; | 
| 1252 |  |  |  |  |  |  | } elsif (-s $filename != length($content)) { | 
| 1253 | 12 |  |  |  |  | 45 | $doit = 1; | 
| 1254 | 12 |  |  |  |  | 28 | $need_diff = 1; | 
| 1255 |  |  |  |  |  |  | } else { | 
| 1256 | 5 | 50 |  |  |  | 147 | open my $fh, '<', $filename | 
| 1257 |  |  |  |  |  |  | or error "Can't open $filename: $!"; | 
| 1258 | 5 |  |  |  |  | 15 | binmode $fh; | 
| 1259 | 5 |  |  |  |  | 28 | local $/; | 
| 1260 | 5 |  |  |  |  | 102 | my $file_content = <$fh>; | 
| 1261 | 5 | 100 |  |  |  | 69 | if ($file_content ne $content) { | 
| 1262 | 1 |  |  |  |  | 3 | $doit = 1; | 
| 1263 | 1 |  |  |  |  | 13 | $need_diff = 1; | 
| 1264 |  |  |  |  |  |  | } | 
| 1265 |  |  |  |  |  |  | } | 
| 1266 |  |  |  |  |  |  |  | 
| 1267 | 30 | 100 |  |  |  | 79 | if ($doit) { | 
| 1268 |  |  |  |  |  |  | my @commands =  { | 
| 1269 |  |  |  |  |  |  | code => sub { | 
| 1270 |  |  |  |  |  |  | # XXX consider to reuse code for atomic writes: | 
| 1271 |  |  |  |  |  |  | # either from Doit::File::file_atomic_write (problematic, different component) | 
| 1272 |  |  |  |  |  |  | # or share code with change_file | 
| 1273 | 26 | 100 |  | 26 |  | 324 | my $outfile = $atomic ? "$filename.$$.".time.".tmp" : $filename; | 
| 1274 | 26 | 100 |  |  |  | 2066 | open my $ofh, '>', $outfile | 
| 1275 |  |  |  |  |  |  | or error "Can't write to $outfile: $!"; | 
| 1276 | 25 | 100 |  |  |  | 342 | if (-e $filename) { | 
| 1277 | 13 |  |  |  |  | 142 | Doit::Util::copy_stat($filename, $outfile, ownership => 1, mode => 1); | 
| 1278 |  |  |  |  |  |  | } | 
| 1279 | 25 |  |  |  |  | 127 | binmode $ofh; | 
| 1280 | 25 |  |  |  |  | 251 | print $ofh $content; | 
| 1281 | 25 | 50 |  |  |  | 989 | close $ofh | 
| 1282 |  |  |  |  |  |  | or error "While closing $outfile: $!"; | 
| 1283 | 25 | 100 |  |  |  | 190 | if ($atomic) { | 
| 1284 | 24 | 50 |  |  |  | 1753 | rename $outfile, $filename | 
| 1285 |  |  |  |  |  |  | or error "Error while renaming $outfile to $filename: $!"; | 
| 1286 |  |  |  |  |  |  | } | 
| 1287 |  |  |  |  |  |  | }, | 
| 1288 |  |  |  |  |  |  | rv => 1, | 
| 1289 |  |  |  |  |  |  | ($quiet >= 2 | 
| 1290 |  |  |  |  |  |  | ? () | 
| 1291 | 26 | 100 |  |  |  | 329 | : (msg => do { | 
| 1292 | 22 | 100 |  |  |  | 55 | if ($quiet) { | 
| 1293 | 5 | 100 |  |  |  | 28 | if ($need_diff) { | 
| 1294 | 2 |  |  |  |  | 18 | "Replace existing file $filename"; | 
| 1295 |  |  |  |  |  |  | } else { | 
| 1296 | 3 |  |  |  |  | 22 | "Create new file $filename"; | 
| 1297 |  |  |  |  |  |  | } | 
| 1298 |  |  |  |  |  |  | } else { | 
| 1299 | 17 | 100 |  |  |  | 45 | if ($need_diff) { | 
| 1300 | 8 |  |  |  |  | 89 | "Replace existing file $filename with diff:\n" . _diff_files($filename, \$content); | 
| 1301 |  |  |  |  |  |  | } else { | 
| 1302 | 9 |  |  |  |  | 73 | "Create new file $filename with content:\n$content"; | 
| 1303 |  |  |  |  |  |  | } | 
| 1304 |  |  |  |  |  |  | } | 
| 1305 |  |  |  |  |  |  | } | 
| 1306 |  |  |  |  |  |  | )), | 
| 1307 |  |  |  |  |  |  | }; | 
| 1308 | 26 |  |  |  |  | 270 | Doit::Commands->new(@commands); | 
| 1309 |  |  |  |  |  |  | } else { | 
| 1310 | 4 |  |  |  |  | 34 | Doit::Commands->return_zero; | 
| 1311 |  |  |  |  |  |  | } | 
| 1312 |  |  |  |  |  |  | } | 
| 1313 |  |  |  |  |  |  |  | 
| 1314 |  |  |  |  |  |  | sub cmd_change_file { | 
| 1315 | 47 |  |  | 47 | 0 | 160 | my($self, @args) = @_; | 
| 1316 | 47 | 100 | 100 |  |  | 135 | my %options; if (@args && ref $args[0] eq 'HASH') { %options = %{ shift @args } } | 
|  | 47 |  |  |  |  | 572 |  | 
|  | 3 |  |  |  |  | 8 |  | 
|  | 3 |  |  |  |  | 14 |  | 
| 1317 | 47 |  |  |  |  | 125 | my $check = delete $options{check}; | 
| 1318 | 47 |  |  |  |  | 79 | my $debug = delete $options{debug}; | 
| 1319 | 47 | 50 | 66 |  |  | 264 | if ($check && ref $check ne 'CODE') { error "check parameter should be a CODE reference" } | 
|  | 0 |  |  |  |  | 0 |  | 
| 1320 | 47 | 100 |  |  |  | 123 | error "Unhandled options: " . join(" ", %options) if %options; | 
| 1321 |  |  |  |  |  |  |  | 
| 1322 | 46 | 100 |  |  |  | 145 | if (@args < 1) { | 
| 1323 | 1 |  |  |  |  | 4 | error "Expecting at least a filename and one or more changes"; | 
| 1324 |  |  |  |  |  |  | } | 
| 1325 |  |  |  |  |  |  |  | 
| 1326 | 45 |  |  |  |  | 187 | my($file, @changes) = @args; | 
| 1327 | 45 | 100 |  |  |  | 747 | if (!-e $file) { | 
| 1328 | 2 |  |  |  |  | 14 | error "$file does not exist"; | 
| 1329 |  |  |  |  |  |  | } | 
| 1330 | 43 | 100 |  |  |  | 411 | if (!-f $file) { | 
| 1331 | 2 |  |  |  |  | 12 | error "$file is not a file"; | 
| 1332 |  |  |  |  |  |  | } | 
| 1333 |  |  |  |  |  |  |  | 
| 1334 | 41 |  |  |  |  | 102 | my @commands; | 
| 1335 |  |  |  |  |  |  |  | 
| 1336 | 41 |  |  |  |  | 103 | for (@changes) { | 
| 1337 | 46 | 100 |  |  |  | 173 | if ($_->{add_if_missing}) { | 
| 1338 | 26 |  |  |  |  | 61 | my $line = delete $_->{add_if_missing}; | 
| 1339 | 26 |  |  |  |  | 61 | $_->{unless_match} = $line; | 
| 1340 | 26 | 100 | 100 |  |  | 354 | if (defined $_->{add_after}       || | 
|  |  |  | 100 |  |  |  |  | 
|  |  |  | 100 |  |  |  |  | 
| 1341 |  |  |  |  |  |  | defined $_->{add_after_first} || | 
| 1342 |  |  |  |  |  |  | defined $_->{add_before}      || | 
| 1343 |  |  |  |  |  |  | defined $_->{add_before_last} | 
| 1344 |  |  |  |  |  |  | ) { | 
| 1345 |  |  |  |  |  |  | my $defines = | 
| 1346 |  |  |  |  |  |  | (defined $_->{add_after}       || 0) + | 
| 1347 |  |  |  |  |  |  | (defined $_->{add_after_first} || 0) + | 
| 1348 |  |  |  |  |  |  | (defined $_->{add_before}      || 0) + | 
| 1349 | 10 |  | 100 |  |  | 204 | (defined $_->{add_before_last} || 0) | 
|  |  |  | 100 |  |  |  |  | 
|  |  |  | 100 |  |  |  |  | 
|  |  |  | 100 |  |  |  |  | 
| 1350 |  |  |  |  |  |  | ; | 
| 1351 | 10 | 50 |  |  |  | 40 | if ($defines != 1) { | 
| 1352 | 0 |  |  |  |  | 0 | error "Can specify only one of the following: 'add_after', 'add_after_first', 'add_before', 'add_before_last' (change for $file)\n"; | 
| 1353 |  |  |  |  |  |  | } | 
| 1354 | 10 |  |  |  |  | 42 | my $add; | 
| 1355 |  |  |  |  |  |  | my $do_after; | 
| 1356 | 10 |  |  |  |  | 0 | my $reverse; | 
| 1357 | 10 | 100 |  |  |  | 70 | if (defined $_->{add_after}) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 1358 | 4 |  |  |  |  | 9 | $add = delete $_->{add_after}; | 
| 1359 | 4 |  |  |  |  | 12 | $reverse = 1; | 
| 1360 | 4 |  |  |  |  | 11 | $do_after = 1; | 
| 1361 |  |  |  |  |  |  | } elsif (defined $_->{add_after_first}) { | 
| 1362 | 2 |  |  |  |  | 12 | $add = delete $_->{add_after_first}; | 
| 1363 | 2 |  |  |  |  | 5 | $reverse = 0; | 
| 1364 | 2 |  |  |  |  | 7 | $do_after = 1; | 
| 1365 |  |  |  |  |  |  | } elsif (defined $_->{add_before}) { | 
| 1366 | 2 |  |  |  |  | 5 | $add = delete $_->{add_before}; | 
| 1367 | 2 |  |  |  |  | 12 | $reverse = 0; | 
| 1368 | 2 |  |  |  |  | 7 | $do_after = 0; | 
| 1369 |  |  |  |  |  |  | } elsif (defined $_->{add_before_last}) { | 
| 1370 | 2 |  |  |  |  | 8 | $add = delete $_->{add_before_last}; | 
| 1371 | 2 |  |  |  |  | 8 | $reverse = 1; | 
| 1372 | 2 |  |  |  |  | 3 | $do_after = 0; | 
| 1373 |  |  |  |  |  |  | } else { | 
| 1374 | 0 |  |  |  |  | 0 | error "Can never happen"; | 
| 1375 |  |  |  |  |  |  | } | 
| 1376 | 10 |  |  |  |  | 81 | qr{$add}; # must be a regexp | 
| 1377 |  |  |  |  |  |  | $_->{action} = sub { | 
| 1378 | 6 |  |  | 6 |  | 14 | my $arrayref = $_[0]; | 
| 1379 | 6 |  |  |  |  | 10 | my $found = 0; | 
| 1380 | 6 | 100 |  |  |  | 22 | my $from = $reverse ? $#$arrayref : 0; | 
| 1381 | 6 | 100 |  |  |  | 57 | my $to   = $reverse ? 0 : $#$arrayref; | 
| 1382 | 6 | 100 |  |  |  | 90 | my $inc  = $reverse ? -1 : +1; | 
| 1383 | 6 | 100 |  |  |  | 27 | for(my $i=$from; ($reverse ? $i>=$to : $i<=$to); $i+=$inc) { | 
| 1384 | 12 | 100 |  |  |  | 348 | if ($arrayref->[$i] =~ $add) { | 
| 1385 | 5 | 100 |  |  |  | 396 | if ($do_after) { | 
| 1386 | 3 |  |  |  |  | 33 | splice @$arrayref, $i+1, 0, $line; | 
| 1387 |  |  |  |  |  |  | } else { | 
| 1388 | 2 |  |  |  |  | 22 | splice @$arrayref, $i, 0, $line; | 
| 1389 |  |  |  |  |  |  | } | 
| 1390 | 5 |  |  |  |  | 2151 | $found = 1; | 
| 1391 | 5 |  |  |  |  | 9 | last; | 
| 1392 |  |  |  |  |  |  | } | 
| 1393 |  |  |  |  |  |  | } | 
| 1394 | 6 | 100 |  |  |  | 83 | if (!$found) { | 
| 1395 | 1 |  |  |  |  | 23 | error "Cannot find '$add' in file"; | 
| 1396 |  |  |  |  |  |  | } | 
| 1397 | 10 |  |  |  |  | 132 | }; | 
| 1398 |  |  |  |  |  |  | } else { | 
| 1399 | 16 |  |  | 12 |  | 142 | $_->{action} = sub { my $arrayref = $_[0]; push @$arrayref, $line }; | 
|  | 12 |  |  |  |  | 21 |  | 
|  | 12 |  |  |  |  | 64 |  | 
| 1400 |  |  |  |  |  |  | } | 
| 1401 |  |  |  |  |  |  | } | 
| 1402 |  |  |  |  |  |  | } | 
| 1403 |  |  |  |  |  |  |  | 
| 1404 | 41 |  |  |  |  | 154 | my @match_actions; | 
| 1405 |  |  |  |  |  |  | my @unless_match_actions; | 
| 1406 | 41 |  |  |  |  | 3728 | for (@changes) { | 
| 1407 | 46 | 100 |  |  |  | 182 | if ($_->{unless_match}) { | 
|  |  | 100 |  |  |  |  |  | 
| 1408 | 29 | 100 |  |  |  | 98 | if (ref $_->{unless_match} ne 'Regexp') { | 
| 1409 | 26 |  |  |  |  | 142 | my $rx = '^' . quotemeta($_->{unless_match}) . '$'; | 
| 1410 | 26 |  |  |  |  | 553 | $_->{unless_match} = qr{$rx}; | 
| 1411 |  |  |  |  |  |  | } | 
| 1412 | 29 | 100 |  |  |  | 111 | if (!$_->{action}) { | 
| 1413 | 1 |  |  |  |  | 3 | error "action is missing"; | 
| 1414 |  |  |  |  |  |  | } | 
| 1415 | 28 | 100 |  |  |  | 120 | if (ref $_->{action} ne 'CODE') { | 
| 1416 | 1 |  |  |  |  | 4 | error "action must be a sub reference"; | 
| 1417 |  |  |  |  |  |  | } | 
| 1418 | 27 |  |  |  |  | 79 | push @unless_match_actions, $_; | 
| 1419 |  |  |  |  |  |  | } elsif ($_->{match}) { | 
| 1420 | 16 | 100 |  |  |  | 76 | if (ref $_->{match} ne 'Regexp') { | 
| 1421 | 3 |  |  |  |  | 29 | my $rx = '^' . quotemeta($_->{match}) . '$'; | 
| 1422 | 3 |  |  |  |  | 114 | $_->{match} = qr{$rx}; | 
| 1423 |  |  |  |  |  |  | } | 
| 1424 | 16 | 100 |  |  |  | 122 | my $consequences = ($_->{action}?1:0) + (defined $_->{replace}?1:0) + (defined $_->{delete}?1:0); | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 1425 | 16 | 100 |  |  |  | 56 | if ($consequences != 1) { | 
| 1426 | 1 |  |  |  |  | 141 | error "Exactly one of the following is missing: action, replace, or delete"; | 
| 1427 |  |  |  |  |  |  | } | 
| 1428 | 15 | 100 |  |  |  | 68 | if ($_->{action}) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 1429 | 3 | 100 |  |  |  | 19 | if (ref $_->{action} ne 'CODE') { | 
| 1430 | 1 |  |  |  |  | 11 | error "action must be a sub reference"; | 
| 1431 |  |  |  |  |  |  | } | 
| 1432 |  |  |  |  |  |  | } elsif (defined $_->{replace}) { | 
| 1433 |  |  |  |  |  |  | # accept | 
| 1434 |  |  |  |  |  |  | } elsif (defined $_->{delete}) { | 
| 1435 |  |  |  |  |  |  | # accept | 
| 1436 |  |  |  |  |  |  | } else { | 
| 1437 | 0 |  |  |  |  | 0 | error "FATAL: should never happen"; | 
| 1438 |  |  |  |  |  |  | } | 
| 1439 | 14 |  |  |  |  | 43 | push @match_actions, $_; | 
| 1440 |  |  |  |  |  |  | } else { | 
| 1441 | 1 |  |  |  |  | 66 | error "match or unless_match is missing"; | 
| 1442 |  |  |  |  |  |  | } | 
| 1443 |  |  |  |  |  |  | } | 
| 1444 |  |  |  |  |  |  |  | 
| 1445 | 36 |  |  |  |  | 352 | require File::Temp; | 
| 1446 | 36 |  |  |  |  | 161 | require File::Basename; | 
| 1447 | 36 |  |  |  |  | 1725 | require File::Copy; | 
| 1448 | 36 |  |  |  |  | 9416 | my($tmpfh,$tmpfile) = File::Temp::tempfile('doittemp_XXXXXXXX', UNLINK => 1, DIR => File::Basename::dirname($file)); | 
| 1449 | 36 | 50 |  |  |  | 18600 | File::Copy::copy($file, $tmpfile) | 
| 1450 |  |  |  |  |  |  | or error "failed to copy $file to temporary file $tmpfile: $!"; | 
| 1451 | 36 |  |  |  |  | 13386 | Doit::Util::copy_stat($file, $tmpfile); | 
| 1452 |  |  |  |  |  |  |  | 
| 1453 | 36 |  |  |  |  | 4459 | require Tie::File; | 
| 1454 | 36 | 50 |  |  |  | 72875 | tie my @lines, 'Tie::File', $tmpfile | 
| 1455 |  |  |  |  |  |  | or error "cannot tie file $file: $!"; | 
| 1456 |  |  |  |  |  |  |  | 
| 1457 | 36 |  |  |  |  | 7792 | my $no_of_changes = 0; | 
| 1458 | 36 |  |  |  |  | 121 | for my $match_action (@match_actions) { | 
| 1459 | 14 |  |  |  |  | 216 | my $match  = $match_action->{match}; | 
| 1460 | 14 |  |  |  |  | 103 | for(my $line_i=0; $line_i<=$#lines; $line_i++) { | 
| 1461 | 44 | 50 |  |  |  | 4315 | if ($debug) { info "change_file check '$lines[$line_i]' =~ '$match'" } | 
|  | 0 |  |  |  |  | 0 |  | 
| 1462 | 44 | 100 |  |  |  | 162 | if ($lines[$line_i] =~ $match) { | 
| 1463 | 18 | 100 |  |  |  | 1994 | if (exists $match_action->{replace}) { | 
|  |  | 100 |  |  |  |  |  | 
| 1464 | 9 |  |  |  |  | 15 | my $replace = $match_action->{replace}; | 
| 1465 | 9 | 50 |  |  |  | 29 | if ($lines[$line_i] ne $replace) { | 
| 1466 | 9 |  |  |  |  | 419 | push @commands, { msg => "replace '$lines[$line_i]' with '$replace' in '$file'" }; | 
| 1467 | 9 |  |  |  |  | 388 | $lines[$line_i] = $replace; | 
| 1468 | 9 |  |  |  |  | 2622 | $no_of_changes++; | 
| 1469 |  |  |  |  |  |  | } | 
| 1470 |  |  |  |  |  |  | } elsif (exists $match_action->{delete}) { | 
| 1471 | 6 | 100 |  |  |  | 23 | if ($match_action->{delete}) { | 
| 1472 | 5 |  |  |  |  | 62 | push @commands, { msg => "delete '$lines[$line_i]' in '$file'" }; | 
| 1473 | 5 |  |  |  |  | 332 | splice @lines, $line_i, 1; | 
| 1474 | 5 |  |  |  |  | 2368 | $line_i--; | 
| 1475 | 5 |  |  |  |  | 27 | $no_of_changes++; | 
| 1476 |  |  |  |  |  |  | } | 
| 1477 |  |  |  |  |  |  | } else { | 
| 1478 | 3 |  |  |  |  | 17 | push @commands, { msg => "matched '$match' on line '$lines[$line_i]' in '$file' -> execute action" }; | 
| 1479 | 3 |  |  |  |  | 128 | my $action = $match_action->{action}; | 
| 1480 | 3 |  |  |  |  | 18 | $action->($lines[$line_i]); | 
| 1481 | 3 |  |  |  |  | 936 | $no_of_changes++; | 
| 1482 |  |  |  |  |  |  | } | 
| 1483 |  |  |  |  |  |  | } | 
| 1484 |  |  |  |  |  |  | } | 
| 1485 |  |  |  |  |  |  | } | 
| 1486 | 36 |  |  |  |  | 849 | ITER: for my $unless_match_action (@unless_match_actions) { | 
| 1487 | 27 |  |  |  |  | 64 | my $match  = $unless_match_action->{unless_match}; | 
| 1488 | 27 |  |  |  |  | 180 | for my $line (@lines) { | 
| 1489 | 70 | 100 |  |  |  | 8846 | if ($line =~ $match) { | 
| 1490 | 8 |  |  |  |  | 1087 | next ITER; | 
| 1491 |  |  |  |  |  |  | } | 
| 1492 |  |  |  |  |  |  | } | 
| 1493 | 19 |  |  |  |  | 2415 | push @commands, { msg => "did not find '$match' in '$file' -> execute action" }; | 
| 1494 | 19 |  |  |  |  | 44 | my $action = $unless_match_action->{action}; | 
| 1495 | 19 |  |  |  |  | 64 | $action->(\@lines); | 
| 1496 | 18 |  |  |  |  | 4658 | $no_of_changes++; | 
| 1497 |  |  |  |  |  |  | } | 
| 1498 |  |  |  |  |  |  |  | 
| 1499 | 35 |  |  |  |  | 253 | untie @lines; | 
| 1500 | 35 |  |  |  |  | 1655 | close $tmpfh; | 
| 1501 |  |  |  |  |  |  |  | 
| 1502 | 35 | 100 |  |  |  | 151 | if ($no_of_changes) { | 
| 1503 |  |  |  |  |  |  | push @commands, { | 
| 1504 |  |  |  |  |  |  | code => sub { | 
| 1505 | 24 | 100 |  | 24 |  | 111 | if ($check) { | 
| 1506 |  |  |  |  |  |  | # XXX maybe it would be good to pass the Doit::Runner object, | 
| 1507 |  |  |  |  |  |  | #     but unfortunately it's not available at this point --- | 
| 1508 |  |  |  |  |  |  | #     maybe the code sub should generally get it as first argument? | 
| 1509 | 2 | 50 |  |  |  | 28 | $check->($tmpfile) | 
| 1510 |  |  |  |  |  |  | or error "Check on file $file failed"; | 
| 1511 |  |  |  |  |  |  | } | 
| 1512 | 23 | 50 |  |  |  | 2730 | rename $tmpfile, $file | 
| 1513 |  |  |  |  |  |  | or error "Can't rename $tmpfile to $file: $!"; | 
| 1514 |  |  |  |  |  |  | }, | 
| 1515 | 25 |  |  |  |  | 233 | msg => do { | 
| 1516 | 25 |  |  |  |  | 112 | "Final changes as diff:\n" . _diff_files($file, $tmpfile); | 
| 1517 |  |  |  |  |  |  | }, | 
| 1518 |  |  |  |  |  |  | rv => $no_of_changes, | 
| 1519 |  |  |  |  |  |  | }; | 
| 1520 |  |  |  |  |  |  | } | 
| 1521 |  |  |  |  |  |  |  | 
| 1522 | 35 | 100 |  |  |  | 291 | if ($no_of_changes) { | 
| 1523 | 25 |  |  |  |  | 525 | Doit::Commands->new(@commands); | 
| 1524 |  |  |  |  |  |  | } else { | 
| 1525 | 10 |  |  |  |  | 79 | Doit::Commands->return_zero; | 
| 1526 |  |  |  |  |  |  | } | 
| 1527 |  |  |  |  |  |  | } | 
| 1528 |  |  |  |  |  |  |  | 
| 1529 |  |  |  |  |  |  | sub _diff_files { | 
| 1530 | 36 |  |  | 36 |  | 128 | my($file1, $file2) = @_; | 
| 1531 |  |  |  |  |  |  |  | 
| 1532 | 36 |  |  |  |  | 103 | my $stdin; | 
| 1533 | 36 | 100 |  |  |  | 182 | if (ref $file2) { | 
| 1534 | 8 |  |  |  |  | 22 | $stdin = $$file2; | 
| 1535 | 8 |  |  |  |  | 31 | $file2 = '-'; | 
| 1536 |  |  |  |  |  |  | } | 
| 1537 |  |  |  |  |  |  |  | 
| 1538 | 36 | 100 |  |  |  | 209 | if (!@diff_cmd) { | 
| 1539 | 7 |  |  |  |  | 28 | my @diff_candidates = (['diff', '-u']); | 
| 1540 | 7 | 50 |  |  |  | 34 | if ($^O eq 'MSWin32') { | 
| 1541 | 0 |  |  |  |  | 0 | push @diff_candidates, ['fc']; | 
| 1542 |  |  |  |  |  |  | } | 
| 1543 | 7 |  |  |  |  | 18 | for my $diff_candidate (@diff_candidates) { | 
| 1544 | 7 | 50 |  |  |  | 67 | if (Doit::Util::is_in_path($diff_candidate->[0])) { | 
| 1545 | 7 |  |  |  |  | 30 | @diff_cmd = @$diff_candidate; | 
| 1546 | 7 |  |  |  |  | 14 | last; | 
| 1547 |  |  |  |  |  |  | } | 
| 1548 |  |  |  |  |  |  | } | 
| 1549 | 7 | 0 |  |  |  | 34 | return "(diff not available" . (!$diff_error_shown++ ? ", error: none of the candidates (" . join(", ", map { $_->[0] } @diff_candidates) . ") exist" : "") . ")" | 
|  | 0 | 50 |  |  |  | 0 |  | 
| 1550 |  |  |  |  |  |  | if !@diff_cmd; | 
| 1551 |  |  |  |  |  |  | } | 
| 1552 |  |  |  |  |  |  |  | 
| 1553 | 36 |  |  |  |  | 81 | my $cannot_use_dash; | 
| 1554 | 36 | 50 | 33 |  |  | 254 | if ($^O eq 'MSWin32' && $diff_cmd[0] eq 'fc') { # FC cannot handle forward slashes | 
| 1555 | 0 |  |  |  |  | 0 | s{/}{\\}g for ($file1, $file2); | 
| 1556 | 0 | 0 |  |  |  | 0 | if ($file2 eq '-') { | 
| 1557 | 0 |  |  |  |  | 0 | $cannot_use_dash = 1; | 
| 1558 |  |  |  |  |  |  | } | 
| 1559 |  |  |  |  |  |  | } | 
| 1560 |  |  |  |  |  |  |  | 
| 1561 | 36 |  |  |  |  | 83 | my($diff, $diff_stderr); | 
| 1562 | 36 | 50 | 33 |  |  | 229 | if (!$cannot_use_dash && eval { require IPC::Run; 1 }) { | 
|  | 36 |  |  |  |  | 4945 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 1563 | 0 | 0 |  |  |  | 0 | if (!eval { | 
| 1564 | 0 | 0 |  |  |  | 0 | IPC::Run::run([@diff_cmd, $file1, $file2], (defined $stdin ? ('<', \$stdin) : ()), '>', \$diff, '2>', \$diff_stderr); 1; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1565 |  |  |  |  |  |  | }) { | 
| 1566 | 0 | 0 |  |  |  | 0 | $diff = "(diff not available" . (!$diff_error_shown++ ? ", error: $@" : "") . ")"; | 
| 1567 | 0 |  |  |  |  | 0 | $diff_stderr = ''; | 
| 1568 |  |  |  |  |  |  | } | 
| 1569 |  |  |  |  |  |  | } else { | 
| 1570 | 36 | 50 | 33 |  |  | 269 | if ($^O eq 'MSWin32' || $cannot_use_dash) { # list systems with unreliable IPC::Open3 here | 
| 1571 | 0 |  |  |  |  | 0 | my $tmp; | 
| 1572 | 0 | 0 |  |  |  | 0 | if ($file2 eq '-') { | 
| 1573 | 0 |  |  |  |  | 0 | require File::Temp; | 
| 1574 | 0 |  |  |  |  | 0 | $tmp = File::Temp->new; | 
| 1575 | 0 |  |  |  |  | 0 | binmode($tmp); # XXX yes or no? | 
| 1576 | 0 |  |  |  |  | 0 | $tmp->print($stdin); | 
| 1577 | 0 |  |  |  |  | 0 | $tmp->close; | 
| 1578 | 0 |  |  |  |  | 0 | $file2 = "$tmp"; | 
| 1579 |  |  |  |  |  |  | } | 
| 1580 | 0 |  |  |  |  | 0 | my $diffref = _qx(@diff_cmd, $file1, $file2); | 
| 1581 | 0 |  |  |  |  | 0 | $diff = $$diffref; | 
| 1582 | 0 |  |  |  |  | 0 | $diff_stderr = ''; | 
| 1583 |  |  |  |  |  |  | } else { | 
| 1584 | 36 |  |  |  |  | 74 | ($diff, $diff_stderr) = eval { _open3($stdin, @diff_cmd, $file1, $file2) }; | 
|  | 36 |  |  |  |  | 181 |  | 
| 1585 | 36 | 50 |  |  |  | 256 | if ($@) { | 
| 1586 | 0 | 0 |  |  |  | 0 | $diff = "(diff not available" . (!$diff_error_shown++ ? ", error: $@" : "") . ")"; | 
| 1587 |  |  |  |  |  |  | } | 
| 1588 |  |  |  |  |  |  | } | 
| 1589 |  |  |  |  |  |  | } | 
| 1590 | 36 |  |  |  |  | 1480 | "$diff$diff_stderr"; | 
| 1591 |  |  |  |  |  |  | } | 
| 1592 |  |  |  |  |  |  |  | 
| 1593 |  |  |  |  |  |  | } | 
| 1594 |  |  |  |  |  |  |  | 
| 1595 |  |  |  |  |  |  | { | 
| 1596 |  |  |  |  |  |  | package Doit::Commands; | 
| 1597 |  |  |  |  |  |  | sub new { | 
| 1598 | 583 |  |  | 583 |  | 3601 | my($class, @commands) = @_; | 
| 1599 | 583 |  |  |  |  | 1536 | my $self = bless \@commands, $class; | 
| 1600 | 583 |  |  |  |  | 7886 | $self; | 
| 1601 |  |  |  |  |  |  | } | 
| 1602 |  |  |  |  |  |  | sub return_zero { | 
| 1603 | 46 |  |  | 46 |  | 112 | my $class = shift; | 
| 1604 | 46 |  |  | 45 |  | 408 | $class->new({ code => sub {}, rv => 0 }); | 
| 1605 |  |  |  |  |  |  | } | 
| 1606 | 626 |  |  | 626 |  | 1038 | sub commands { @{$_[0]} } | 
|  | 626 |  |  |  |  | 2480 |  | 
| 1607 |  |  |  |  |  |  | sub set_last_rv { | 
| 1608 | 43 |  |  | 43 |  | 120 | my($self, $rv) = @_; | 
| 1609 | 43 |  |  |  |  | 182 | my @commands = $self->commands; | 
| 1610 | 43 | 50 |  |  |  | 166 | if (@commands) { | 
| 1611 | 43 |  |  |  |  | 147 | $commands[-1]->{rv} = $rv; | 
| 1612 |  |  |  |  |  |  | } | 
| 1613 |  |  |  |  |  |  | } | 
| 1614 |  |  |  |  |  |  | sub doit { | 
| 1615 | 563 |  |  | 563 |  | 1818 | my($self) = @_; | 
| 1616 | 563 |  |  |  |  | 771 | my $rv; | 
| 1617 | 563 |  |  |  |  | 1914 | for my $command ($self->commands) { | 
| 1618 | 603 | 100 |  |  |  | 1903 | if (exists $command->{msg}) { | 
| 1619 | 389 |  |  |  |  | 2408 | Doit::Log::info($command->{msg}); | 
| 1620 |  |  |  |  |  |  | } | 
| 1621 | 603 | 100 |  |  |  | 71129 | if (exists $command->{code}) { | 
| 1622 | 569 |  |  |  |  | 1583 | my $this_rv = $command->{code}->(); | 
| 1623 | 527 | 100 |  |  |  | 6344 | if (exists $command->{rv}) { | 
| 1624 | 457 |  |  |  |  | 2351 | $rv = $command->{rv}; | 
| 1625 |  |  |  |  |  |  | } else { | 
| 1626 | 70 |  |  |  |  | 432 | $rv = $this_rv; | 
| 1627 |  |  |  |  |  |  | } | 
| 1628 |  |  |  |  |  |  | } | 
| 1629 |  |  |  |  |  |  | } | 
| 1630 | 521 |  |  |  |  | 16113 | $rv; | 
| 1631 |  |  |  |  |  |  | } | 
| 1632 |  |  |  |  |  |  | sub show { | 
| 1633 | 20 |  |  | 20 |  | 78 | my($self) = @_; | 
| 1634 | 20 |  |  |  |  | 35 | my $rv; | 
| 1635 | 20 |  |  |  |  | 73 | for my $command ($self->commands) { | 
| 1636 | 21 | 100 |  |  |  | 93 | if (exists $command->{msg}) { | 
| 1637 | 17 |  |  |  |  | 184 | Doit::Log::info($command->{msg} . " (dry-run)"); | 
| 1638 |  |  |  |  |  |  | } | 
| 1639 | 21 | 100 |  |  |  | 3747 | if (exists $command->{code}) { | 
| 1640 | 20 | 100 |  |  |  | 63 | if (exists $command->{rv}) { | 
| 1641 | 17 |  |  |  |  | 146 | $rv = $command->{rv}; | 
| 1642 |  |  |  |  |  |  | } else { | 
| 1643 |  |  |  |  |  |  | # Well, in dry-run mode we have no real return value... | 
| 1644 |  |  |  |  |  |  | } | 
| 1645 |  |  |  |  |  |  | } | 
| 1646 |  |  |  |  |  |  | } | 
| 1647 | 20 |  |  |  |  | 431 | $rv; | 
| 1648 |  |  |  |  |  |  | } | 
| 1649 |  |  |  |  |  |  | } | 
| 1650 |  |  |  |  |  |  |  | 
| 1651 |  |  |  |  |  |  | { | 
| 1652 |  |  |  |  |  |  | package Doit::Runner; | 
| 1653 |  |  |  |  |  |  | sub new { | 
| 1654 | 41 |  |  | 41 |  | 167 | my($class, $Doit, %options) = @_; | 
| 1655 | 41 |  |  |  |  | 118 | my $dryrun = delete $options{dryrun}; | 
| 1656 | 41 | 50 |  |  |  | 146 | die "Unhandled options: " . join(" ", %options) if %options; | 
| 1657 | 41 |  |  |  |  | 695 | bless { Doit => $Doit, dryrun => $dryrun, components => [] }, $class; | 
| 1658 |  |  |  |  |  |  | } | 
| 1659 | 15 |  |  | 15 |  | 104 | sub is_dry_run { shift->{dryrun} } | 
| 1660 |  |  |  |  |  |  |  | 
| 1661 | 1 |  |  | 1 |  | 4 | sub can_ipc_run { eval { require IPC::Run; 1 } } | 
|  | 1 |  |  |  |  | 119 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 1662 |  |  |  |  |  |  |  | 
| 1663 |  |  |  |  |  |  | sub install_generic_cmd { | 
| 1664 | 2 |  |  | 2 |  | 410 | my($self, $name, @args) = @_; | 
| 1665 | 2 |  |  |  |  | 25 | $self->{Doit}->install_generic_cmd($name, @args); | 
| 1666 | 2 |  |  |  |  | 25 | __PACKAGE__->install_cmd($name); | 
| 1667 |  |  |  |  |  |  | } | 
| 1668 |  |  |  |  |  |  |  | 
| 1669 |  |  |  |  |  |  | sub install_cmd { | 
| 1670 | 1322 |  |  | 1322 |  | 1326 | shift; # $class unused | 
| 1671 | 1322 |  |  |  |  | 1314 | my $cmd = shift; | 
| 1672 | 1322 |  |  |  |  | 1809 | my $meth = 'cmd_' . $cmd; | 
| 1673 |  |  |  |  |  |  | my $code = sub { | 
| 1674 | 631 |  |  | 631 |  | 319649 | my($self, @args) = @_; | 
| 1675 | 631 | 100 |  |  |  | 4613 | if ($self->{dryrun}) { | 
| 1676 | 22 |  |  |  |  | 129 | $self->{Doit}->$meth(@args)->show; | 
| 1677 |  |  |  |  |  |  | } else { | 
| 1678 | 609 |  |  |  |  | 6302 | $self->{Doit}->$meth(@args)->doit; | 
| 1679 |  |  |  |  |  |  | } | 
| 1680 | 1322 |  |  |  |  | 3387 | }; | 
| 1681 | 44 |  |  | 44 |  | 460 | no strict 'refs'; | 
|  | 44 |  |  |  |  | 85 |  | 
|  | 44 |  |  |  |  | 10009 |  | 
| 1682 | 1322 |  |  |  |  | 1502 | *{$cmd} = $code; | 
|  | 1322 |  |  |  |  | 4184 |  | 
| 1683 |  |  |  |  |  |  | } | 
| 1684 |  |  |  |  |  |  |  | 
| 1685 |  |  |  |  |  |  | sub add_component { | 
| 1686 | 14 |  |  | 14 |  | 2563 | my($self, $component_or_module) = @_; | 
| 1687 | 14 |  |  |  |  | 27 | my $module; | 
| 1688 | 14 | 100 |  |  |  | 81 | if ($component_or_module =~ /::/) { | 
| 1689 | 3 |  |  |  |  | 8 | $module = $component_or_module; | 
| 1690 |  |  |  |  |  |  | } else { | 
| 1691 | 11 |  |  |  |  | 45 | $module = 'Doit::' . ucfirst($component_or_module); | 
| 1692 |  |  |  |  |  |  | } | 
| 1693 |  |  |  |  |  |  |  | 
| 1694 | 14 |  |  |  |  | 24 | for (@{ $self->{components} }) { | 
|  | 14 |  |  |  |  | 82 |  | 
| 1695 | 6 | 100 |  |  |  | 18 | return if $_->{module} eq $module; | 
| 1696 |  |  |  |  |  |  | } | 
| 1697 |  |  |  |  |  |  |  | 
| 1698 | 12 | 100 |  |  |  | 765 | if (!eval qq{ require $module; 1 }) { | 
| 1699 | 1 |  |  |  |  | 10 | Doit::Log::error("Cannot load $module: $@"); | 
| 1700 |  |  |  |  |  |  | } | 
| 1701 | 11 | 50 |  |  |  | 89 | my $o = $module->new | 
| 1702 |  |  |  |  |  |  | or Doit::Log::error("Error while calling $module->new"); | 
| 1703 | 11 |  |  |  |  | 49 | for my $function ($o->functions) { | 
| 1704 | 35 |  |  |  |  | 82 | my $fullqual = $module.'::'.$function; | 
| 1705 |  |  |  |  |  |  | my $code = sub { | 
| 1706 | 322 |  |  | 322 |  | 58814 | my($self, @args) = @_; | 
| 1707 | 322 |  |  |  |  | 4281 | $self->$fullqual(@args); | 
| 1708 | 35 |  |  |  |  | 120 | }; | 
| 1709 | 44 |  |  | 44 |  | 346 | no strict 'refs'; | 
|  | 44 |  |  |  |  | 100 |  | 
|  | 44 |  |  |  |  | 13109 |  | 
| 1710 | 35 |  |  |  |  | 47 | *{$function} = $code; | 
|  | 35 |  |  |  |  | 134 |  | 
| 1711 |  |  |  |  |  |  | } | 
| 1712 | 11 |  |  |  |  | 23 | my $mod_file = do { | 
| 1713 | 11 |  |  |  |  | 49 | (my $relpath = $module) =~ s{::}{/}; | 
| 1714 | 11 |  |  |  |  | 30 | $relpath .= '.pm'; | 
| 1715 |  |  |  |  |  |  | }; | 
| 1716 | 11 |  |  |  |  | 68 | push @{ $self->{components} }, { module => $module, path => $INC{$mod_file}, relpath => $mod_file }; | 
|  | 11 |  |  |  |  | 90 |  | 
| 1717 |  |  |  |  |  |  |  | 
| 1718 | 11 | 50 |  |  |  | 136 | if ($o->can('add_components')) { | 
| 1719 | 0 |  |  |  |  | 0 | for my $sub_component ($o->add_components) { | 
| 1720 | 0 |  |  |  |  | 0 | $self->add_component($sub_component); | 
| 1721 |  |  |  |  |  |  | } | 
| 1722 |  |  |  |  |  |  | } | 
| 1723 |  |  |  |  |  |  | } | 
| 1724 |  |  |  |  |  |  |  | 
| 1725 |  |  |  |  |  |  | for my $cmd ( | 
| 1726 |  |  |  |  |  |  | qw(chmod chown mkdir rename rmdir symlink unlink utime), | 
| 1727 |  |  |  |  |  |  | qw(make_path remove_tree), # File::Path | 
| 1728 |  |  |  |  |  |  | qw(copy move), # File::Copy | 
| 1729 |  |  |  |  |  |  | qw(run), # IPC::Run | 
| 1730 |  |  |  |  |  |  | qw(qx info_qx), # qx// and variant which even runs in dry-run mode, both using list syntax | 
| 1731 |  |  |  |  |  |  | qw(open2 info_open2), # IPC::Open2 | 
| 1732 |  |  |  |  |  |  | qw(open3 info_open3), # IPC::Open3 | 
| 1733 |  |  |  |  |  |  | qw(system info_system), # builtin system with variant | 
| 1734 |  |  |  |  |  |  | qw(cond_run), # conditional run | 
| 1735 |  |  |  |  |  |  | qw(touch), # like unix touch | 
| 1736 |  |  |  |  |  |  | qw(ln_nsf), # like unix ln -nsf | 
| 1737 |  |  |  |  |  |  | qw(which), # like unix which | 
| 1738 |  |  |  |  |  |  | qw(create_file_if_nonexisting), # does the half of touch | 
| 1739 |  |  |  |  |  |  | qw(write_binary), # like File::Slurper | 
| 1740 |  |  |  |  |  |  | qw(change_file), # own invention | 
| 1741 |  |  |  |  |  |  | qw(setenv unsetenv), # $ENV manipulation | 
| 1742 |  |  |  |  |  |  | ) { | 
| 1743 |  |  |  |  |  |  | __PACKAGE__->install_cmd($cmd); | 
| 1744 |  |  |  |  |  |  | } | 
| 1745 |  |  |  |  |  |  |  | 
| 1746 |  |  |  |  |  |  | sub call_wrapped_method { | 
| 1747 | 6 |  |  | 6 |  | 18 | my($self, $context, $method, @args) = @_; | 
| 1748 | 6 |  |  |  |  | 9 | my @ret; | 
| 1749 | 6 | 100 |  |  |  | 15 | if ($context eq 'a') { | 
| 1750 | 2 |  |  |  |  | 6 | @ret    = eval { $self->$method(@args) }; | 
|  | 2 |  |  |  |  | 7 |  | 
| 1751 |  |  |  |  |  |  | } else { | 
| 1752 | 4 |  |  |  |  | 5 | $ret[0] = eval { $self->$method(@args) }; | 
|  | 4 |  |  |  |  | 10 |  | 
| 1753 |  |  |  |  |  |  | } | 
| 1754 | 6 | 100 |  |  |  | 155 | if ($@) { | 
| 1755 | 2 |  |  |  |  | 6 | ('e', $@); | 
| 1756 |  |  |  |  |  |  | } else { | 
| 1757 | 4 |  |  |  |  | 14 | ('r', @ret); | 
| 1758 |  |  |  |  |  |  | } | 
| 1759 |  |  |  |  |  |  | } | 
| 1760 |  |  |  |  |  |  |  | 
| 1761 |  |  |  |  |  |  | # XXX call vs. call_with_runner ??? | 
| 1762 |  |  |  |  |  |  | sub call { | 
| 1763 | 6 |  |  | 6 |  | 12 | my($self, $sub, @args) = @_; | 
| 1764 | 6 | 50 |  |  |  | 23 | $sub = 'main::' . $sub if $sub !~ /::/; | 
| 1765 | 44 |  |  | 44 |  | 322 | no strict 'refs'; | 
|  | 44 |  |  |  |  | 109 |  | 
|  | 44 |  |  |  |  | 3938 |  | 
| 1766 | 6 |  |  |  |  | 71 | &$sub(@args); | 
| 1767 |  |  |  |  |  |  | } | 
| 1768 |  |  |  |  |  |  |  | 
| 1769 |  |  |  |  |  |  | sub call_with_runner { | 
| 1770 | 2 |  |  | 2 |  | 1143 | my($self, $sub, @args) = @_; | 
| 1771 | 2 | 50 |  |  |  | 15 | $sub = 'main::' . $sub if $sub !~ /::/; | 
| 1772 | 44 |  |  | 44 |  | 321 | no strict 'refs'; | 
|  | 44 |  |  |  |  | 89 |  | 
|  | 44 |  |  |  |  | 7312 |  | 
| 1773 | 2 |  |  |  |  | 14 | &$sub($self, @args); | 
| 1774 |  |  |  |  |  |  | } | 
| 1775 |  |  |  |  |  |  |  | 
| 1776 |  |  |  |  |  |  | # XXX does this belong here? | 
| 1777 |  |  |  |  |  |  | sub do_ssh_connect { | 
| 1778 | 5 |  |  | 5 |  | 2787 | my($self, $host, %opts) = @_; | 
| 1779 | 5 |  |  |  |  | 16 | my $remote = Doit::SSH->do_connect($host, dry_run => $self->is_dry_run, components => $self->{components}, %opts); | 
| 1780 | 0 |  |  |  |  | 0 | $remote; | 
| 1781 |  |  |  |  |  |  | } | 
| 1782 |  |  |  |  |  |  |  | 
| 1783 |  |  |  |  |  |  | # XXX does this belong here? | 
| 1784 |  |  |  |  |  |  | sub do_sudo { | 
| 1785 | 0 |  |  | 0 |  | 0 | my($self, %opts) = @_; | 
| 1786 | 0 |  |  |  |  | 0 | my $sudo = Doit::Sudo->do_connect(dry_run => $self->is_dry_run, components => $self->{components}, %opts); | 
| 1787 | 0 |  |  |  |  | 0 | $sudo; | 
| 1788 |  |  |  |  |  |  | } | 
| 1789 |  |  |  |  |  |  | } | 
| 1790 |  |  |  |  |  |  |  | 
| 1791 |  |  |  |  |  |  | { | 
| 1792 |  |  |  |  |  |  | package Doit::RPC; | 
| 1793 |  |  |  |  |  |  |  | 
| 1794 |  |  |  |  |  |  | require Storable; | 
| 1795 |  |  |  |  |  |  | require IO::Handle; | 
| 1796 |  |  |  |  |  |  |  | 
| 1797 | 44 |  |  | 44 |  | 280 | use Doit::Log; | 
|  | 44 |  |  |  |  | 83 |  | 
|  | 44 |  |  |  |  | 49639 |  | 
| 1798 |  |  |  |  |  |  |  | 
| 1799 |  |  |  |  |  |  | sub new { | 
| 1800 | 0 |  |  | 0 |  | 0 | die "Please use either Doit::RPC::Client, Doit::RPC::Server or Doit::RPC::SimpleServer"; | 
| 1801 |  |  |  |  |  |  | } | 
| 1802 |  |  |  |  |  |  |  | 
| 1803 | 6 |  |  | 6 |  | 22 | sub runner { shift->{runner} } | 
| 1804 |  |  |  |  |  |  |  | 
| 1805 |  |  |  |  |  |  | sub receive_data { | 
| 1806 | 14 |  |  | 14 |  | 39 | my($self) = @_; | 
| 1807 | 14 |  |  |  |  | 27 | my $fh = $self->{infh}; | 
| 1808 | 14 |  |  |  |  | 21 | my $buf; | 
| 1809 | 14 |  |  |  |  | 16744 | my $ret = read $fh, $buf, 4; | 
| 1810 | 14 | 50 |  |  |  | 87 | if (!defined $ret) { | 
|  |  | 50 |  |  |  |  |  | 
| 1811 | 0 |  |  |  |  | 0 | die "receive_data failed (getting length): $!"; | 
| 1812 |  |  |  |  |  |  | } elsif (!$ret) { | 
| 1813 | 0 |  |  |  |  | 0 | return; # eof | 
| 1814 |  |  |  |  |  |  | } | 
| 1815 | 14 |  |  |  |  | 51 | my $length = unpack("N", $buf); | 
| 1816 | 14 | 50 |  |  |  | 61 | read $fh, $buf, $length or die "receive_data failed (getting data): $!"; | 
| 1817 | 14 |  |  |  |  | 24 | @{ Storable::thaw($buf) }; | 
|  | 14 |  |  |  |  | 63 |  | 
| 1818 |  |  |  |  |  |  | } | 
| 1819 |  |  |  |  |  |  |  | 
| 1820 |  |  |  |  |  |  | sub send_data { | 
| 1821 | 14 |  |  | 14 |  | 44 | my($self, @cmd) = @_; | 
| 1822 | 14 |  |  |  |  | 26 | my $fh = $self->{outfh}; | 
| 1823 | 14 |  |  |  |  | 60 | my $data = Storable::nfreeze(\@cmd); | 
| 1824 | 14 |  |  |  |  | 1007 | print $fh pack("N", length($data)) . $data; | 
| 1825 |  |  |  |  |  |  | } | 
| 1826 |  |  |  |  |  |  |  | 
| 1827 |  |  |  |  |  |  | { | 
| 1828 |  |  |  |  |  |  | my $done_POSIX_warning; | 
| 1829 |  |  |  |  |  |  | sub _reap_process { | 
| 1830 | 0 |  |  | 0 |  | 0 | my($self, $pid) = @_; | 
| 1831 | 0 | 0 |  |  |  | 0 | return if !defined $pid; | 
| 1832 | 0 | 0 |  |  |  | 0 | if (eval { require POSIX; defined &POSIX::WNOHANG }) { | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 1833 | 0 | 0 |  |  |  | 0 | if ($self->{debug}) { | 
| 1834 | 0 |  |  |  |  | 0 | info "Reaping process $pid..."; | 
| 1835 |  |  |  |  |  |  | } | 
| 1836 | 0 |  |  |  |  | 0 | my $start_time = time; | 
| 1837 |  |  |  |  |  |  | my $got_pid = Doit::RPC::gentle_retry( | 
| 1838 |  |  |  |  |  |  | code => sub { | 
| 1839 | 0 |  |  | 0 |  | 0 | waitpid $pid, &POSIX::WNOHANG; | 
| 1840 |  |  |  |  |  |  | }, | 
| 1841 |  |  |  |  |  |  | retry_msg_code => sub { | 
| 1842 | 0 |  |  | 0 |  | 0 | my($seconds) = @_; | 
| 1843 | 0 | 0 |  |  |  | 0 | if (time - $start_time >= 2) { | 
| 1844 | 0 |  |  |  |  | 0 | info "can't reap process $pid, sleep for $seconds seconds"; | 
| 1845 |  |  |  |  |  |  | } | 
| 1846 |  |  |  |  |  |  | }, | 
| 1847 | 0 |  |  |  |  | 0 | fast_sleep => 0.01, | 
| 1848 |  |  |  |  |  |  | ); | 
| 1849 | 0 | 0 |  |  |  | 0 | if (!$got_pid) { | 
| 1850 | 0 |  |  |  |  | 0 | warning "Could not reap process $pid..."; | 
| 1851 |  |  |  |  |  |  | } | 
| 1852 |  |  |  |  |  |  | } else { | 
| 1853 | 0 | 0 |  |  |  | 0 | if (!$done_POSIX_warning++) { | 
| 1854 | 0 |  |  |  |  | 0 | warning "Can't require POSIX, cannot reap zombies..." | 
| 1855 |  |  |  |  |  |  | } | 
| 1856 |  |  |  |  |  |  | } | 
| 1857 |  |  |  |  |  |  | } | 
| 1858 |  |  |  |  |  |  | } | 
| 1859 |  |  |  |  |  |  |  | 
| 1860 |  |  |  |  |  |  | sub gentle_retry { | 
| 1861 | 0 |  |  | 0 |  | 0 | my(%opts) = @_; | 
| 1862 | 0 |  | 0 |  |  | 0 | my $code           = delete $opts{code} || die "code is mandatory"; | 
| 1863 | 0 |  | 0 |  |  | 0 | my $tries          = delete $opts{tries} || 20; | 
| 1864 | 0 |  | 0 |  |  | 0 | my $fast_tries     = delete $opts{fast_tries} || int($tries/2); | 
| 1865 | 0 |  | 0 |  |  | 0 | my $slow_sleep     = delete $opts{slow_sleep} || 1; | 
| 1866 | 0 |  | 0 |  |  | 0 | my $fast_sleep     = delete $opts{fast_sleep} || 0.1; | 
| 1867 | 0 |  |  |  |  | 0 | my $retry_msg_code = delete $opts{retry_msg_code}; | 
| 1868 | 0 |  |  |  |  | 0 | my $fail_info_ref  = delete $opts{fail_info_ref}; | 
| 1869 | 0 | 0 |  |  |  | 0 | die "Unhandled options: " . join(" ", %opts) if %opts; | 
| 1870 |  |  |  |  |  |  |  | 
| 1871 | 0 |  |  |  |  | 0 | for my $try (1..$tries) { | 
| 1872 | 0 |  |  |  |  | 0 | my $ret = $code->(fail_info_ref => $fail_info_ref, try => $try); | 
| 1873 | 0 | 0 |  |  |  | 0 | return $ret if $ret; | 
| 1874 | 0 |  |  |  |  | 0 | my $sleep_sub; | 
| 1875 | 0 | 0 | 0 |  |  | 0 | if ($fast_tries && eval { require Time::HiRes; 1 }) { | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 1876 | 0 |  |  |  |  | 0 | $sleep_sub = \&Time::HiRes::sleep; | 
| 1877 |  |  |  |  |  |  | } else { | 
| 1878 | 0 |  |  | 0 |  | 0 | $sleep_sub = sub { sleep $_[0] }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1879 |  |  |  |  |  |  | } | 
| 1880 | 0 | 0 | 0 |  |  | 0 | my $seconds = $try <= $fast_tries && defined &Time::HiRes::sleep ? $fast_sleep : $slow_sleep; | 
| 1881 | 0 | 0 |  |  |  | 0 | $retry_msg_code->($seconds) if $retry_msg_code; | 
| 1882 | 0 |  |  |  |  | 0 | $sleep_sub->($seconds); | 
| 1883 |  |  |  |  |  |  | } | 
| 1884 |  |  |  |  |  |  |  | 
| 1885 | 0 |  |  |  |  | 0 | undef; | 
| 1886 |  |  |  |  |  |  | } | 
| 1887 |  |  |  |  |  |  |  | 
| 1888 |  |  |  |  |  |  | } | 
| 1889 |  |  |  |  |  |  |  | 
| 1890 |  |  |  |  |  |  | { | 
| 1891 |  |  |  |  |  |  | package Doit::RPC::Client; | 
| 1892 |  |  |  |  |  |  | our @ISA = ('Doit::RPC'); | 
| 1893 |  |  |  |  |  |  |  | 
| 1894 |  |  |  |  |  |  | sub new { | 
| 1895 | 1 |  |  | 1 |  | 78666 | my($class, $infh, $outfh, %options) = @_; | 
| 1896 |  |  |  |  |  |  |  | 
| 1897 | 1 |  |  |  |  | 15 | my $debug = delete $options{debug}; | 
| 1898 | 1 |  |  |  |  | 16 | my $label = delete $options{label}; | 
| 1899 | 1 | 50 |  |  |  | 36 | die "Unhandled options: " . join(" ", %options) if %options; | 
| 1900 |  |  |  |  |  |  |  | 
| 1901 | 1 |  |  |  |  | 47 | $outfh->autoflush(1); | 
| 1902 | 1 |  |  |  |  | 214 | bless { | 
| 1903 |  |  |  |  |  |  | infh  => $infh, | 
| 1904 |  |  |  |  |  |  | outfh => $outfh, | 
| 1905 |  |  |  |  |  |  | label => $label, | 
| 1906 |  |  |  |  |  |  | debug => $debug, | 
| 1907 |  |  |  |  |  |  | }, $class; | 
| 1908 |  |  |  |  |  |  | } | 
| 1909 |  |  |  |  |  |  |  | 
| 1910 |  |  |  |  |  |  | # Call for every command on client | 
| 1911 |  |  |  |  |  |  | sub call_remote { | 
| 1912 | 7 |  |  | 7 |  | 5685 | my($self, @args) = @_; | 
| 1913 | 7 | 100 |  |  |  | 47 | my $context = wantarray ? 'a' : 's'; # XXX more possible context (void...)? | 
| 1914 | 7 |  |  |  |  | 50 | $self->send_data($context, @args); | 
| 1915 | 7 |  |  |  |  | 40 | my($rettype, @ret) = $self->receive_data(@args); | 
| 1916 | 7 | 100 | 66 |  |  | 223 | if (defined $rettype && $rettype eq 'e') { | 
|  |  | 50 | 33 |  |  |  |  | 
| 1917 | 2 |  |  |  |  | 11 | die $ret[0]; | 
| 1918 |  |  |  |  |  |  | } elsif (defined $rettype && $rettype eq 'r') { | 
| 1919 | 5 | 100 |  |  |  | 13 | if ($context eq 'a') { | 
| 1920 | 2 |  |  |  |  | 9 | return @ret; | 
| 1921 |  |  |  |  |  |  | } else { | 
| 1922 | 3 |  |  |  |  | 12 | return $ret[0]; | 
| 1923 |  |  |  |  |  |  | } | 
| 1924 |  |  |  |  |  |  | } else { | 
| 1925 | 0 | 0 |  |  |  | 0 | die "Unexpected return type " . (defined $self->{label} ? "in connection '$self->{label}' " : "") . (defined $rettype ? "'$rettype'" : "") . " (should be 'e' or 'r')"; | 
|  |  | 0 |  |  |  |  |  | 
| 1926 |  |  |  |  |  |  | } | 
| 1927 |  |  |  |  |  |  | } | 
| 1928 |  |  |  |  |  |  | } | 
| 1929 |  |  |  |  |  |  |  | 
| 1930 |  |  |  |  |  |  | { | 
| 1931 |  |  |  |  |  |  | package Doit::RPC::Server; | 
| 1932 |  |  |  |  |  |  | our @ISA = ('Doit::RPC'); | 
| 1933 |  |  |  |  |  |  |  | 
| 1934 |  |  |  |  |  |  | sub new { | 
| 1935 | 0 |  |  | 0 |  | 0 | my($class, $runner, $sockpath, %options) = @_; | 
| 1936 |  |  |  |  |  |  |  | 
| 1937 | 0 |  |  |  |  | 0 | my $debug = delete $options{debug}; | 
| 1938 | 0 |  |  |  |  | 0 | my $excl  = delete $options{excl}; | 
| 1939 | 0 | 0 |  |  |  | 0 | die "Unhandled options: " . join(" ", %options) if %options; | 
| 1940 |  |  |  |  |  |  |  | 
| 1941 | 0 |  |  |  |  | 0 | bless { | 
| 1942 |  |  |  |  |  |  | runner   => $runner, | 
| 1943 |  |  |  |  |  |  | sockpath => $sockpath, | 
| 1944 |  |  |  |  |  |  | debug    => $debug, | 
| 1945 |  |  |  |  |  |  | excl     => $excl, | 
| 1946 |  |  |  |  |  |  | }, $class; | 
| 1947 |  |  |  |  |  |  | } | 
| 1948 |  |  |  |  |  |  |  | 
| 1949 |  |  |  |  |  |  | sub run { | 
| 1950 | 0 |  |  | 0 |  | 0 | my($self) = @_; | 
| 1951 |  |  |  |  |  |  |  | 
| 1952 | 0 |  |  |  |  | 0 | require IO::Socket::UNIX; | 
| 1953 | 0 |  |  |  |  | 0 | IO::Socket::UNIX->VERSION('1.18'); # autoflush | 
| 1954 | 0 |  |  |  |  | 0 | IO::Socket::UNIX->import(qw(SOCK_STREAM)); | 
| 1955 | 44 |  |  | 44 |  | 21037 | use IO::Select; | 
|  | 44 |  |  |  |  | 62268 |  | 
|  | 44 |  |  |  |  | 42806 |  | 
| 1956 |  |  |  |  |  |  |  | 
| 1957 | 0 |  |  |  |  | 0 | my $d; | 
| 1958 | 0 | 0 |  |  |  | 0 | if ($self->{debug}) { | 
| 1959 |  |  |  |  |  |  | $d = sub ($) { | 
| 1960 | 0 |  |  | 0 |  | 0 | Doit::Log::info("WORKER: $_[0]"); | 
| 1961 | 0 |  |  |  |  | 0 | }; | 
| 1962 |  |  |  |  |  |  | } else { | 
| 1963 | 0 |  |  | 0 |  | 0 | $d = sub ($) { }; | 
| 1964 |  |  |  |  |  |  | } | 
| 1965 |  |  |  |  |  |  |  | 
| 1966 | 0 |  |  |  |  | 0 | $d->("Start worker ($$)..."); | 
| 1967 | 0 |  |  |  |  | 0 | my $sockpath = $self->{sockpath}; | 
| 1968 | 0 | 0 | 0 |  |  | 0 | if (!$self->{excl} && -e $sockpath) { | 
| 1969 | 0 |  |  |  |  | 0 | $d->("unlink socket $sockpath"); | 
| 1970 | 0 |  |  |  |  | 0 | unlink $sockpath; | 
| 1971 |  |  |  |  |  |  | } | 
| 1972 | 0 | 0 |  |  |  | 0 | my $sock = IO::Socket::UNIX->new( | 
| 1973 |  |  |  |  |  |  | Type  => SOCK_STREAM(), | 
| 1974 |  |  |  |  |  |  | Local => $sockpath, | 
| 1975 |  |  |  |  |  |  | Listen => 1, | 
| 1976 |  |  |  |  |  |  | ) or die "WORKER: Can't create socket: $!"; | 
| 1977 | 0 |  |  |  |  | 0 | $d->("socket was created"); | 
| 1978 |  |  |  |  |  |  |  | 
| 1979 | 0 |  |  |  |  | 0 | my $sel = IO::Select->new($sock); | 
| 1980 | 0 |  |  |  |  | 0 | $d->("waiting for client"); | 
| 1981 | 0 |  |  |  |  | 0 | my @ready = $sel->can_read(); | 
| 1982 | 0 | 0 |  |  |  | 0 | die "WORKER: unexpected filehandle @ready" if $ready[0] != $sock; | 
| 1983 | 0 |  |  |  |  | 0 | $d->("accept socket"); | 
| 1984 | 0 |  |  |  |  | 0 | my $fh = $sock->accept; | 
| 1985 | 0 |  |  |  |  | 0 | $self->{infh} = $self->{outfh} = $fh; | 
| 1986 | 0 |  |  |  |  | 0 | while () { | 
| 1987 | 0 |  |  |  |  | 0 | $d->(" waiting for line from comm"); | 
| 1988 | 0 |  |  |  |  | 0 | my($context, @data) = $self->receive_data; | 
| 1989 | 0 | 0 |  |  |  | 0 | if (!defined $context) { | 
|  |  | 0 |  |  |  |  |  | 
| 1990 | 0 |  |  |  |  | 0 | $d->(" got eof"); | 
| 1991 | 0 |  |  |  |  | 0 | $fh->close; | 
| 1992 | 0 |  |  |  |  | 0 | return; | 
| 1993 |  |  |  |  |  |  | } elsif ($data[0] =~ m{^exit$}) { | 
| 1994 | 0 |  |  |  |  | 0 | $d->(" got exit command"); | 
| 1995 | 0 |  |  |  |  | 0 | $self->send_data('r', 'bye-bye'); | 
| 1996 | 0 |  |  |  |  | 0 | $fh->close; | 
| 1997 | 0 |  |  |  |  | 0 | return; | 
| 1998 |  |  |  |  |  |  | } | 
| 1999 | 0 |  |  |  |  | 0 | $d->(" calling method $data[0]"); | 
| 2000 | 0 |  |  |  |  | 0 | my($rettype, @ret) = $self->runner->call_wrapped_method($context, @data); | 
| 2001 | 0 |  |  |  |  | 0 | $d->(" sending result back"); | 
| 2002 | 0 |  |  |  |  | 0 | $self->send_data($rettype, @ret); | 
| 2003 |  |  |  |  |  |  | } | 
| 2004 |  |  |  |  |  |  | } | 
| 2005 |  |  |  |  |  |  |  | 
| 2006 |  |  |  |  |  |  | } | 
| 2007 |  |  |  |  |  |  |  | 
| 2008 |  |  |  |  |  |  | { | 
| 2009 |  |  |  |  |  |  | package Doit::RPC::SimpleServer; | 
| 2010 |  |  |  |  |  |  | our @ISA = ('Doit::RPC'); | 
| 2011 |  |  |  |  |  |  |  | 
| 2012 |  |  |  |  |  |  | sub new { | 
| 2013 | 1 |  |  | 1 |  | 6 | my($class, $runner, $infh, $outfh, %options) = @_; | 
| 2014 | 1 |  |  |  |  | 3 | my $debug = delete $options{debug}; | 
| 2015 | 1 | 50 |  |  |  | 4 | die "Unhandled options: " . join(" ", %options) if %options; | 
| 2016 |  |  |  |  |  |  |  | 
| 2017 | 1 | 50 |  |  |  | 11 | $infh  = \*STDIN if !$infh; | 
| 2018 | 1 | 50 |  |  |  | 11 | $outfh = \*STDOUT if !$outfh; | 
| 2019 | 1 |  |  |  |  | 20 | $outfh->autoflush(1); | 
| 2020 | 1 |  |  |  |  | 99 | bless { | 
| 2021 |  |  |  |  |  |  | runner => $runner, | 
| 2022 |  |  |  |  |  |  | infh   => $infh, | 
| 2023 |  |  |  |  |  |  | outfh  => $outfh, | 
| 2024 |  |  |  |  |  |  | debug  => $debug, | 
| 2025 |  |  |  |  |  |  | }, $class; | 
| 2026 |  |  |  |  |  |  | } | 
| 2027 |  |  |  |  |  |  |  | 
| 2028 |  |  |  |  |  |  | sub run { | 
| 2029 | 1 |  |  | 1 |  | 2 | my $self = shift; | 
| 2030 | 1 |  |  |  |  | 3 | while() { | 
| 2031 | 7 |  |  |  |  | 46 | my($context, @data) = $self->receive_data; | 
| 2032 | 7 | 50 |  |  |  | 196 | if (!defined $context) { | 
|  |  | 100 |  |  |  |  |  | 
| 2033 | 0 |  |  |  |  | 0 | return; | 
| 2034 |  |  |  |  |  |  | } elsif ($data[0] =~ m{^exit$}) { | 
| 2035 | 1 |  |  |  |  | 12 | $self->send_data('r', 'bye-bye'); | 
| 2036 | 1 |  |  |  |  | 50 | return; | 
| 2037 |  |  |  |  |  |  | } | 
| 2038 | 6 | 50 |  |  |  | 145 | open my $oldout, ">&STDOUT" or die $!; | 
| 2039 | 6 |  |  |  |  | 14 | if (Doit::IS_WIN) { | 
| 2040 |  |  |  |  |  |  | open STDOUT, '>', 'CON:' or die $!; # XXX???? | 
| 2041 |  |  |  |  |  |  | } else { | 
| 2042 | 6 | 50 |  |  |  | 285 | open STDOUT, '>', "/dev/stderr" or die $!; # XXX???? | 
| 2043 |  |  |  |  |  |  | } | 
| 2044 | 6 |  |  |  |  | 39 | my($rettype, @ret) = $self->runner->call_wrapped_method($context, @data); | 
| 2045 | 6 | 50 |  |  |  | 198 | open STDOUT, ">&", $oldout or die $!; | 
| 2046 | 6 |  |  |  |  | 34 | $self->send_data($rettype, @ret); | 
| 2047 |  |  |  |  |  |  | } | 
| 2048 |  |  |  |  |  |  | } | 
| 2049 |  |  |  |  |  |  | } | 
| 2050 |  |  |  |  |  |  |  | 
| 2051 |  |  |  |  |  |  | { | 
| 2052 |  |  |  |  |  |  | package Doit::_AnyRPCImpl; | 
| 2053 |  |  |  |  |  |  | sub call_remote { | 
| 2054 | 0 |  |  | 0 |  | 0 | my($self, @args) = @_; | 
| 2055 | 0 |  |  |  |  | 0 | $self->{rpc}->call_remote(@args); | 
| 2056 |  |  |  |  |  |  | } | 
| 2057 |  |  |  |  |  |  |  | 
| 2058 |  |  |  |  |  |  | our $AUTOLOAD; | 
| 2059 |  |  |  |  |  |  | sub AUTOLOAD { | 
| 2060 | 0 |  |  | 0 |  | 0 | (my $method = $AUTOLOAD) =~ s{.*::}{}; | 
| 2061 | 0 |  |  |  |  | 0 | my $self = shift; | 
| 2062 | 0 |  |  |  |  | 0 | $self->call_remote($method, @_); # XXX or use goto? | 
| 2063 |  |  |  |  |  |  | } | 
| 2064 |  |  |  |  |  |  |  | 
| 2065 |  |  |  |  |  |  | sub _can_LANS { | 
| 2066 | 0 |  |  | 0 |  | 0 | require POSIX; | 
| 2067 | 0 | 0 |  |  |  | 0 | $^O eq 'linux' && (POSIX::uname())[2] !~ m{^([01]\.|2\.[01]\.)} # osvers >= 2.2, earlier versions did not have LANS | 
| 2068 |  |  |  |  |  |  | } | 
| 2069 |  |  |  |  |  |  |  | 
| 2070 |  |  |  |  |  |  | } | 
| 2071 |  |  |  |  |  |  |  | 
| 2072 |  |  |  |  |  |  | { | 
| 2073 |  |  |  |  |  |  | package Doit::_ScriptTools; | 
| 2074 |  |  |  |  |  |  |  | 
| 2075 |  |  |  |  |  |  | sub add_components { | 
| 2076 | 0 |  |  | 0 |  | 0 | my(@components) = @_; | 
| 2077 | 0 |  |  |  |  | 0 | q|for my $component_module (qw(| . join(" ", map { qq{$_->{module}} } @components) . q|)) { $d->add_component($component_module) } |; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2078 |  |  |  |  |  |  | } | 
| 2079 |  |  |  |  |  |  |  | 
| 2080 |  |  |  |  |  |  | sub self_require (;$) { | 
| 2081 | 0 |  |  | 0 |  | 0 | my $realscript = shift; | 
| 2082 | 0 | 0 |  |  |  | 0 | if (!defined $realscript) { $realscript = $0 } | 
|  | 0 |  |  |  |  | 0 |  | 
| 2083 | 0 | 0 |  |  |  | 0 | if ($realscript ne '-e') { # not a oneliner | 
| 2084 | 0 |  |  |  |  | 0 | q{$ENV{DOIT_IN_REMOTE} = 1; } . | 
| 2085 |  |  |  |  |  |  | q{require "} . File::Basename::basename($realscript) . q{"; }; | 
| 2086 |  |  |  |  |  |  | } else { | 
| 2087 | 0 |  |  |  |  | 0 | q{use Doit; }; | 
| 2088 |  |  |  |  |  |  | } | 
| 2089 |  |  |  |  |  |  | } | 
| 2090 |  |  |  |  |  |  | } | 
| 2091 |  |  |  |  |  |  |  | 
| 2092 |  |  |  |  |  |  | { | 
| 2093 |  |  |  |  |  |  | package Doit::Sudo; | 
| 2094 |  |  |  |  |  |  |  | 
| 2095 |  |  |  |  |  |  | our @ISA = ('Doit::_AnyRPCImpl'); | 
| 2096 |  |  |  |  |  |  |  | 
| 2097 | 44 |  |  | 44 |  | 366 | use Doit::Log; | 
|  | 44 |  |  |  |  | 90 |  | 
|  | 44 |  |  |  |  | 23139 |  | 
| 2098 |  |  |  |  |  |  |  | 
| 2099 |  |  |  |  |  |  | my $socket_count = 0; | 
| 2100 |  |  |  |  |  |  |  | 
| 2101 |  |  |  |  |  |  | sub do_connect { | 
| 2102 | 0 |  |  | 0 |  | 0 | my($class, %opts) = @_; | 
| 2103 | 0 | 0 |  |  |  | 0 | my @sudo_opts = @{ delete $opts{sudo_opts} || [] }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2104 | 0 |  |  |  |  | 0 | my $dry_run = delete $opts{dry_run}; | 
| 2105 | 0 |  |  |  |  | 0 | my $debug = delete $opts{debug}; | 
| 2106 | 0 | 0 |  |  |  | 0 | my @components = @{ delete $opts{components} || [] }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 2107 | 0 |  | 0 |  |  | 0 | my $perl = delete $opts{perl} || $^X; | 
| 2108 | 0 | 0 |  |  |  | 0 | die "Unhandled options: " . join(" ", %opts) if %opts; | 
| 2109 |  |  |  |  |  |  |  | 
| 2110 | 0 |  |  |  |  | 0 | my $self = bless { }, $class; | 
| 2111 |  |  |  |  |  |  |  | 
| 2112 | 0 |  |  |  |  | 0 | require File::Basename; | 
| 2113 | 0 |  |  |  |  | 0 | require IPC::Open2; | 
| 2114 | 0 |  |  |  |  | 0 | require POSIX; | 
| 2115 | 0 |  |  |  |  | 0 | require Symbol; | 
| 2116 |  |  |  |  |  |  |  | 
| 2117 |  |  |  |  |  |  | # Socket pathname, make it possible to find out | 
| 2118 |  |  |  |  |  |  | # old outdated sockets easily by including a | 
| 2119 |  |  |  |  |  |  | # timestamp. Also need to maintain a $socket_count, | 
| 2120 |  |  |  |  |  |  | # if the same script opens multiple sockets quickly. | 
| 2121 | 0 |  |  |  |  | 0 | my $sock_path = "/tmp/." . join(".", "doit", "sudo", POSIX::strftime("%Y%m%d_%H%M%S", gmtime), $<, $$, (++$socket_count)) . ".sock"; | 
| 2122 |  |  |  |  |  |  |  | 
| 2123 |  |  |  |  |  |  | # Make sure password has to be entered only once (if at all) | 
| 2124 |  |  |  |  |  |  | # Using 'sudo --validate' would be more correct, however, | 
| 2125 |  |  |  |  |  |  | # mysterious "sudo: ignoring time stamp from the future" | 
| 2126 |  |  |  |  |  |  | # errors may happen every now and then. Seen on a | 
| 2127 |  |  |  |  |  |  | # debian/jessie system, possibly related to | 
| 2128 |  |  |  |  |  |  | # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=762465 | 
| 2129 |  |  |  |  |  |  | { | 
| 2130 | 0 |  |  |  |  | 0 | my @cmd = ('sudo', @sudo_opts, 'true'); | 
|  | 0 |  |  |  |  | 0 |  | 
| 2131 | 0 |  |  |  |  | 0 | system @cmd; | 
| 2132 | 0 | 0 |  |  |  | 0 | if ($? != 0) { | 
| 2133 |  |  |  |  |  |  | # Possible cases: | 
| 2134 |  |  |  |  |  |  | # - sudo is not installed | 
| 2135 |  |  |  |  |  |  | # - sudo authentication is not possible or user entered wrong password | 
| 2136 |  |  |  |  |  |  | # - true is not installed (hopefully this never happens on Unix systems) | 
| 2137 | 0 |  |  |  |  | 0 | error "Command '@cmd' failed"; | 
| 2138 |  |  |  |  |  |  | } | 
| 2139 |  |  |  |  |  |  | } | 
| 2140 |  |  |  |  |  |  |  | 
| 2141 |  |  |  |  |  |  | # On linux use Linux Abstract Namespace Sockets --- | 
| 2142 |  |  |  |  |  |  | # invisible and automatically cleaned up. See man 7 unix. | 
| 2143 | 0 | 0 |  |  |  | 0 | my $LANS_PREFIX = $class->_can_LANS ? '\0' : ''; | 
| 2144 |  |  |  |  |  |  |  | 
| 2145 |  |  |  |  |  |  | # Run the server | 
| 2146 | 0 | 0 |  |  |  | 0 | my @cmd_worker = | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 2147 |  |  |  |  |  |  | ( | 
| 2148 |  |  |  |  |  |  | 'sudo', @sudo_opts, $perl, "-I".File::Basename::dirname(__FILE__), "-I".File::Basename::dirname($0), "-e", | 
| 2149 |  |  |  |  |  |  | Doit::_ScriptTools::self_require() . | 
| 2150 |  |  |  |  |  |  | q{my $d = Doit->init; } . | 
| 2151 |  |  |  |  |  |  | Doit::_ScriptTools::add_components(@components) . | 
| 2152 |  |  |  |  |  |  | q{Doit::RPC::Server->new($d, "} . $LANS_PREFIX . $sock_path . q{", excl => 1, debug => } . ($debug?1:0) . q{)->run();} . | 
| 2153 |  |  |  |  |  |  | ($LANS_PREFIX ? '' : q . $sock_path . q<" }>), # cleanup socket file, except if Linux Abstract Namespace Sockets are used | 
| 2154 |  |  |  |  |  |  | "--", ($dry_run? "--dry-run" : ()) | 
| 2155 |  |  |  |  |  |  | ); | 
| 2156 | 0 |  |  |  |  | 0 | my $worker_pid = fork; | 
| 2157 | 0 | 0 |  |  |  | 0 | if (!defined $worker_pid) { | 
|  |  | 0 |  |  |  |  |  | 
| 2158 | 0 |  |  |  |  | 0 | die "fork failed: $!"; | 
| 2159 |  |  |  |  |  |  | } elsif ($worker_pid == 0) { | 
| 2160 | 0 | 0 |  |  |  | 0 | warn "worker perl cmd: @cmd_worker\n" if $debug; | 
| 2161 | 0 |  |  |  |  | 0 | exec @cmd_worker; | 
| 2162 | 0 |  |  |  |  | 0 | die "Failed to run '@cmd_worker': $!"; | 
| 2163 |  |  |  |  |  |  | } | 
| 2164 |  |  |  |  |  |  |  | 
| 2165 |  |  |  |  |  |  | # Run the client --- must also run under root for socket | 
| 2166 |  |  |  |  |  |  | # access. | 
| 2167 | 0 |  |  |  |  | 0 | my($in, $out); | 
| 2168 | 0 |  |  |  |  | 0 | my @cmd_comm = ('sudo', @sudo_opts, $perl, "-I".File::Basename::dirname(__FILE__), "-MDoit", "-e", | 
| 2169 |  |  |  |  |  |  | q{Doit::Comm->comm_to_sock("} . $LANS_PREFIX . $sock_path . q{", debug => shift)}, !!$debug); | 
| 2170 | 0 | 0 |  |  |  | 0 | warn "comm perl cmd: @cmd_comm\n" if $debug; | 
| 2171 | 0 |  |  |  |  | 0 | my $comm_pid = IPC::Open2::open2($out, $in, @cmd_comm); | 
| 2172 | 0 |  |  |  |  | 0 | $self->{rpc} = Doit::RPC::Client->new($out, $in, label => "sudo:", debug => $debug); | 
| 2173 |  |  |  |  |  |  |  | 
| 2174 | 0 |  |  |  |  | 0 | $self; | 
| 2175 |  |  |  |  |  |  | } | 
| 2176 |  |  |  |  |  |  |  | 
| 2177 |  |  |  | 0 |  |  | sub DESTROY { } | 
| 2178 |  |  |  |  |  |  |  | 
| 2179 |  |  |  |  |  |  | } | 
| 2180 |  |  |  |  |  |  |  | 
| 2181 |  |  |  |  |  |  | { | 
| 2182 |  |  |  |  |  |  | package Doit::SSH; | 
| 2183 |  |  |  |  |  |  |  | 
| 2184 |  |  |  |  |  |  | our @ISA = ('Doit::_AnyRPCImpl'); | 
| 2185 |  |  |  |  |  |  |  | 
| 2186 | 44 |  |  | 44 |  | 314 | use Doit::Log; | 
|  | 44 |  |  |  |  | 102 |  | 
|  | 44 |  |  |  |  | 21426 |  | 
| 2187 |  |  |  |  |  |  |  | 
| 2188 |  |  |  |  |  |  | sub do_connect { | 
| 2189 | 5 |  |  | 5 |  | 32 | require File::Basename; | 
| 2190 | 5 |  |  |  |  | 696 | require Net::OpenSSH; | 
| 2191 | 0 |  |  |  |  |  | require FindBin; | 
| 2192 | 0 |  |  |  |  |  | my($class, $ssh_or_host, %opts) = @_; | 
| 2193 | 0 |  |  |  |  |  | my $dry_run = delete $opts{dry_run}; | 
| 2194 | 0 | 0 |  |  |  |  | my @components = @{ delete $opts{components} || [] }; | 
|  | 0 |  |  |  |  |  |  | 
| 2195 | 0 |  |  |  |  |  | my $debug = delete $opts{debug}; | 
| 2196 | 0 |  |  |  |  |  | my $as = delete $opts{as}; | 
| 2197 | 0 |  |  |  |  |  | my $forward_agent = delete $opts{forward_agent}; | 
| 2198 | 0 |  |  |  |  |  | my $tty = delete $opts{tty}; | 
| 2199 | 0 |  |  |  |  |  | my $port = delete $opts{port}; | 
| 2200 | 0 |  |  |  |  |  | my $master_opts = delete $opts{master_opts}; | 
| 2201 | 0 |  |  |  |  |  | my $dest_os = delete $opts{dest_os}; | 
| 2202 | 0 | 0 |  |  |  |  | $dest_os = 'unix' if !defined $dest_os; | 
| 2203 | 0 |  | 0 |  |  |  | my $put_to_remote = delete $opts{put_to_remote} || 'rsync_put'; # XXX ideally this should be determined automatically | 
| 2204 | 0 | 0 |  |  |  |  | $put_to_remote =~ m{^(rsync_put|scp_put)$} | 
| 2205 |  |  |  |  |  |  | or error "Valid values for put_to_remote: rsync_put or scp_put"; | 
| 2206 | 0 |  | 0 |  |  |  | my $perl = delete $opts{perl} || 'perl'; | 
| 2207 | 0 |  |  |  |  |  | my $umask = delete $opts{umask}; | 
| 2208 | 0 | 0 | 0 |  |  |  | if (defined $umask && $umask !~ m{^\d+$}) { | 
| 2209 | 0 |  |  |  |  |  | error "The umask '$umask' does not look correct, it should be a (possibly octal) number"; | 
| 2210 |  |  |  |  |  |  | } | 
| 2211 | 0 |  |  |  |  |  | my $bootstrap = delete $opts{bootstrap}; | 
| 2212 | 0 | 0 |  |  |  |  | error "Unhandled options: " . join(" ", %opts) if %opts; | 
| 2213 |  |  |  |  |  |  |  | 
| 2214 | 0 |  |  |  |  |  | my $self = bless { debug => $debug }, $class; | 
| 2215 | 0 | 0 |  |  |  |  | my %ssh_run_opts = ( | 
|  |  | 0 |  |  |  |  |  | 
| 2216 |  |  |  |  |  |  | ($forward_agent ? (forward_agent => $forward_agent) : ()), | 
| 2217 |  |  |  |  |  |  | ($tty           ? (tty           => $tty)           : ()), | 
| 2218 |  |  |  |  |  |  | ); | 
| 2219 | 0 | 0 |  |  |  |  | my %ssh_new_opts = ( | 
|  |  | 0 |  |  |  |  |  | 
| 2220 |  |  |  |  |  |  | ($forward_agent ? (forward_agent => $forward_agent) : ()), | 
| 2221 |  |  |  |  |  |  | ($master_opts   ? (master_opts   => $master_opts)   : ()), | 
| 2222 |  |  |  |  |  |  | ); | 
| 2223 |  |  |  |  |  |  |  | 
| 2224 | 0 |  |  |  |  |  | my($host, $ssh); | 
| 2225 | 0 | 0 |  |  |  |  | if (UNIVERSAL::isa($ssh_or_host, 'Net::OpenSSH')) { | 
| 2226 | 0 |  |  |  |  |  | $ssh = $ssh_or_host; | 
| 2227 | 0 |  |  |  |  |  | $host = $ssh->get_host; # XXX what about username/port/...? | 
| 2228 |  |  |  |  |  |  | } else { | 
| 2229 | 0 |  |  |  |  |  | $host = $ssh_or_host; | 
| 2230 | 0 |  |  |  |  |  | $ssh = Net::OpenSSH->new($host, %ssh_new_opts); | 
| 2231 | 0 | 0 |  |  |  |  | $ssh->error | 
| 2232 |  |  |  |  |  |  | and error "Connection error to $host: " . $ssh->error; | 
| 2233 |  |  |  |  |  |  | } | 
| 2234 | 0 |  |  |  |  |  | $self->{ssh} = $ssh; | 
| 2235 |  |  |  |  |  |  |  | 
| 2236 | 0 | 0 | 0 |  |  |  | if (($bootstrap||'') eq 'perl') { | 
| 2237 | 0 |  |  |  |  |  | require Doit::Bootstrap; | 
| 2238 | 0 |  |  |  |  |  | Doit::Bootstrap::_bootstrap_perl($self, dry_run => $dry_run); | 
| 2239 |  |  |  |  |  |  | } | 
| 2240 |  |  |  |  |  |  |  | 
| 2241 |  |  |  |  |  |  | { | 
| 2242 | 0 |  |  |  |  |  | my $remote_cmd; | 
|  | 0 |  |  |  |  |  |  | 
| 2243 | 0 | 0 |  |  |  |  | if ($dest_os eq 'MSWin32') { | 
| 2244 | 0 |  |  |  |  |  | $remote_cmd = 'if not exist .doit\lib\ mkdir .doit\lib'; | 
| 2245 |  |  |  |  |  |  | } else { | 
| 2246 | 0 |  |  |  |  |  | $remote_cmd = "[ ! -d .doit/lib ] && mkdir -p .doit/lib"; | 
| 2247 |  |  |  |  |  |  | } | 
| 2248 | 0 | 0 |  |  |  |  | if ($debug) { | 
| 2249 | 0 |  |  |  |  |  | info "Running '$remote_cmd' on remote"; | 
| 2250 |  |  |  |  |  |  | } | 
| 2251 | 0 |  |  |  |  |  | $ssh->system(\%ssh_run_opts, $remote_cmd); | 
| 2252 |  |  |  |  |  |  | } | 
| 2253 | 0 | 0 |  |  |  |  | if ($FindBin::RealScript ne '-e') { | 
| 2254 | 44 |  |  | 44 |  | 314 | no warnings 'once'; | 
|  | 44 |  |  |  |  | 87 |  | 
|  | 44 |  |  |  |  | 61097 |  | 
| 2255 | 0 |  |  |  |  |  | $ssh->$put_to_remote({verbose => $debug}, "$FindBin::RealBin/$FindBin::RealScript", ".doit/"); # XXX verbose? | 
| 2256 |  |  |  |  |  |  | } | 
| 2257 | 0 |  |  |  |  |  | $ssh->$put_to_remote({verbose => $debug}, __FILE__, ".doit/lib/"); | 
| 2258 |  |  |  |  |  |  | { | 
| 2259 | 0 |  |  |  |  |  | my %seen_dir; | 
|  | 0 |  |  |  |  |  |  | 
| 2260 | 0 | 0 |  |  |  |  | for my $component ( | 
| 2261 |  |  |  |  |  |  | @components, | 
| 2262 |  |  |  |  |  |  | ( # add additional RPC components | 
| 2263 |  |  |  |  |  |  | $dest_os ne 'MSWin32' ? () : | 
| 2264 |  |  |  |  |  |  | do { | 
| 2265 | 0 |  |  |  |  |  | (my $srcpath = __FILE__) =~ s{\.pm}{/WinRPC.pm}; | 
| 2266 | 0 |  |  |  |  |  | {relpath => "Doit/WinRPC.pm", path => $srcpath}, | 
| 2267 |  |  |  |  |  |  | } | 
| 2268 |  |  |  |  |  |  | ) | 
| 2269 |  |  |  |  |  |  | ) { | 
| 2270 | 0 |  |  |  |  |  | my $from = $component->{path}; | 
| 2271 | 0 |  |  |  |  |  | my $to = $component->{relpath}; | 
| 2272 | 0 |  |  |  |  |  | my $full_target = ".doit/lib/$to"; | 
| 2273 | 0 |  |  |  |  |  | my $target_dir = File::Basename::dirname($full_target); | 
| 2274 | 0 | 0 |  |  |  |  | if (!$seen_dir{$target_dir}) { | 
| 2275 | 0 |  |  |  |  |  | my $remote_cmd; | 
| 2276 | 0 | 0 |  |  |  |  | if ($dest_os eq 'MSWin32') { | 
| 2277 | 0 |  |  |  |  |  | (my $win_target_dir = $target_dir) =~ s{/}{\\}g; | 
| 2278 | 0 |  |  |  |  |  | $remote_cmd = "if not exist $win_target_dir mkdir $win_target_dir"; # XXX is this equivalent to mkdir -p? | 
| 2279 |  |  |  |  |  |  | } else { | 
| 2280 | 0 |  |  |  |  |  | $remote_cmd = "[ ! -d $target_dir ] && mkdir -p $target_dir"; | 
| 2281 |  |  |  |  |  |  | } | 
| 2282 | 0 |  |  |  |  |  | $ssh->system(\%ssh_run_opts, $remote_cmd); | 
| 2283 | 0 |  |  |  |  |  | $seen_dir{$target_dir} = 1; | 
| 2284 |  |  |  |  |  |  | } | 
| 2285 | 0 |  |  |  |  |  | $ssh->$put_to_remote({verbose => $debug}, $from, $full_target); | 
| 2286 |  |  |  |  |  |  | } | 
| 2287 |  |  |  |  |  |  | } | 
| 2288 |  |  |  |  |  |  |  | 
| 2289 |  |  |  |  |  |  | my $sock_path = ( | 
| 2290 |  |  |  |  |  |  | $dest_os eq 'MSWin32' | 
| 2291 |  |  |  |  |  |  | ? join("-", "doit", "ssh", POSIX::strftime("%Y%m%d_%H%M%S", gmtime), int(rand(99999999))) | 
| 2292 | 0 | 0 |  |  |  |  | : do { | 
| 2293 | 0 |  |  |  |  |  | require POSIX; | 
| 2294 | 0 |  |  |  |  |  | "/tmp/." . join(".", "doit", "ssh", POSIX::strftime("%Y%m%d_%H%M%S", gmtime), $<, $$, int(rand(99999999))) . ".sock"; | 
| 2295 |  |  |  |  |  |  | } | 
| 2296 |  |  |  |  |  |  | ); | 
| 2297 |  |  |  |  |  |  |  | 
| 2298 | 0 |  |  |  |  |  | my @cmd; | 
| 2299 | 0 | 0 |  |  |  |  | if (defined $as) { | 
| 2300 | 0 | 0 |  |  |  |  | if ($as eq 'root') { | 
| 2301 | 0 |  |  |  |  |  | @cmd = ('sudo'); | 
| 2302 |  |  |  |  |  |  | } else { | 
| 2303 | 0 |  |  |  |  |  | @cmd = ('sudo', '-u', $as); | 
| 2304 |  |  |  |  |  |  | } | 
| 2305 |  |  |  |  |  |  | } # XXX add ssh option -t? for password input? | 
| 2306 |  |  |  |  |  |  |  | 
| 2307 | 0 |  |  |  |  |  | my @cmd_worker; | 
| 2308 | 0 | 0 |  |  |  |  | if ($dest_os eq 'MSWin32') { | 
| 2309 | 0 | 0 |  |  |  |  | @cmd_worker = | 
|  |  | 0 |  |  |  |  |  | 
| 2310 |  |  |  |  |  |  | ( | 
| 2311 |  |  |  |  |  |  | # @cmd not used here (no sudo) | 
| 2312 |  |  |  |  |  |  | $perl, "-I.doit", "-I.doit\\lib", "-e", | 
| 2313 |  |  |  |  |  |  | Doit::_ScriptTools::self_require($FindBin::RealScript) . | 
| 2314 |  |  |  |  |  |  | q{use Doit::WinRPC; } . | 
| 2315 |  |  |  |  |  |  | q{my $d = Doit->init; } . | 
| 2316 |  |  |  |  |  |  | Doit::_ScriptTools::add_components(@components) . | 
| 2317 |  |  |  |  |  |  | # XXX server cleanup? on signals? on END? | 
| 2318 |  |  |  |  |  |  | q{Doit::WinRPC::Server->new($d, "} . $sock_path . q{", debug => } . ($debug?1:0).q{)->run();}, | 
| 2319 |  |  |  |  |  |  | "--", ($dry_run? "--dry-run" : ()) | 
| 2320 |  |  |  |  |  |  | ); | 
| 2321 | 0 |  |  |  |  |  | @cmd_worker = Doit::Win32Util::win32_quote_list(@cmd_worker); | 
| 2322 |  |  |  |  |  |  | } else { | 
| 2323 | 0 | 0 |  |  |  |  | @cmd_worker = | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 2324 |  |  |  |  |  |  | ( | 
| 2325 |  |  |  |  |  |  | @cmd, $perl, "-I.doit", "-I.doit/lib", "-e", | 
| 2326 |  |  |  |  |  |  | (defined $umask ? qq{umask $umask; } : q{}) . | 
| 2327 |  |  |  |  |  |  | Doit::_ScriptTools::self_require($FindBin::RealScript) . | 
| 2328 |  |  |  |  |  |  | q{my $d = Doit->init; } . | 
| 2329 |  |  |  |  |  |  | Doit::_ScriptTools::add_components(@components) . | 
| 2330 |  |  |  |  |  |  | q . $sock_path . q<" }> . | 
| 2331 |  |  |  |  |  |  | q<$SIG{PIPE} = \&_server_cleanup; > . | 
| 2332 |  |  |  |  |  |  | q . | 
| 2333 |  |  |  |  |  |  | q{Doit::RPC::Server->new($d, "} . $sock_path . q{", excl => 1, debug => } . ($debug?1:0).q{)->run();}, | 
| 2334 |  |  |  |  |  |  | "--", ($dry_run? "--dry-run" : ()) | 
| 2335 |  |  |  |  |  |  | ); | 
| 2336 |  |  |  |  |  |  | } | 
| 2337 | 0 | 0 |  |  |  |  | warn "remote perl cmd: @cmd_worker\n" if $debug; | 
| 2338 | 0 |  |  |  |  |  | my $worker_pid = $ssh->spawn(\%ssh_run_opts, @cmd_worker); # XXX what to do with worker pid? | 
| 2339 | 0 |  |  |  |  |  | $self->{worker_pid} = $worker_pid; | 
| 2340 |  |  |  |  |  |  |  | 
| 2341 | 0 |  |  |  |  |  | my @cmd_comm; | 
| 2342 | 0 | 0 |  |  |  |  | if ($dest_os eq 'MSWin32') { | 
| 2343 | 0 |  |  |  |  |  | @cmd_comm = | 
| 2344 |  |  |  |  |  |  | ($perl, "-I.doit\\lib", "-MDoit", "-MDoit::WinRPC", "-e", | 
| 2345 |  |  |  |  |  |  | q{Doit::WinRPC::Comm->new("} . $sock_path . q{", debug => shift)->run}, | 
| 2346 |  |  |  |  |  |  | !!$debug, | 
| 2347 |  |  |  |  |  |  | ); | 
| 2348 | 0 |  |  |  |  |  | @cmd_comm = Doit::Win32Util::win32_quote_list(@cmd_comm); | 
| 2349 |  |  |  |  |  |  | } else { | 
| 2350 | 0 |  |  |  |  |  | @cmd_comm = | 
| 2351 |  |  |  |  |  |  | ( | 
| 2352 |  |  |  |  |  |  | @cmd, $perl, "-I.doit/lib", "-MDoit", "-e", | 
| 2353 |  |  |  |  |  |  | q{Doit::Comm->comm_to_sock("} . $sock_path . q{", debug => shift);}, | 
| 2354 |  |  |  |  |  |  | !!$debug, | 
| 2355 |  |  |  |  |  |  | ); | 
| 2356 |  |  |  |  |  |  | } | 
| 2357 | 0 | 0 |  |  |  |  | warn "comm perl cmd: @cmd_comm\n" if $debug; | 
| 2358 | 0 |  |  |  |  |  | my($out, $in, $comm_pid) = $ssh->open2(@cmd_comm); | 
| 2359 | 0 |  |  |  |  |  | $self->{comm_pid} = $comm_pid; | 
| 2360 | 0 |  |  |  |  |  | $self->{rpc} = Doit::RPC::Client->new($in, $out, label => "ssh:$host", debug => $debug); | 
| 2361 |  |  |  |  |  |  |  | 
| 2362 | 0 |  |  |  |  |  | $self; | 
| 2363 |  |  |  |  |  |  | } | 
| 2364 |  |  |  |  |  |  |  | 
| 2365 | 0 |  |  | 0 |  |  | sub ssh { $_[0]->{ssh} } | 
| 2366 |  |  |  |  |  |  |  | 
| 2367 |  |  |  |  |  |  | sub DESTROY { | 
| 2368 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 2369 | 0 |  |  |  |  |  | local $?; # XXX Net::OpenSSH::_waitpid sets $?=0 | 
| 2370 | 0 | 0 |  |  |  |  | if ($self->{ssh}) { | 
| 2371 | 0 | 0 |  |  |  |  | $self->{ssh}->disconnect if $self->{ssh}->can('disconnect'); | 
| 2372 | 0 |  |  |  |  |  | delete $self->{ssh}; | 
| 2373 |  |  |  |  |  |  | } | 
| 2374 | 0 | 0 |  |  |  |  | if ($self->{rpc}) { | 
| 2375 | 0 |  |  |  |  |  | $self->{rpc}->_reap_process($self->{comm_pid}); | 
| 2376 | 0 |  |  |  |  |  | $self->{rpc}->_reap_process($self->{worker_pid}); | 
| 2377 |  |  |  |  |  |  | } | 
| 2378 |  |  |  |  |  |  | } | 
| 2379 |  |  |  |  |  |  |  | 
| 2380 |  |  |  |  |  |  | } | 
| 2381 |  |  |  |  |  |  |  | 
| 2382 |  |  |  |  |  |  | { | 
| 2383 |  |  |  |  |  |  | package Doit::Comm; | 
| 2384 |  |  |  |  |  |  |  | 
| 2385 |  |  |  |  |  |  | sub comm_to_sock { | 
| 2386 | 0 |  |  | 0 |  |  | my(undef, $peer, %options) = @_; | 
| 2387 | 0 | 0 |  |  |  |  | die "Please specify path to unix domain socket" if !defined $peer; | 
| 2388 | 0 |  |  |  |  |  | my $debug = delete $options{debug}; | 
| 2389 | 0 | 0 |  |  |  |  | die "Unhandled options: " . join(" ", %options) if %options; | 
| 2390 |  |  |  |  |  |  |  | 
| 2391 | 0 |  |  |  |  |  | my $infh = \*STDIN; | 
| 2392 | 0 |  |  |  |  |  | my $outfh = \*STDOUT; | 
| 2393 |  |  |  |  |  |  |  | 
| 2394 | 0 |  |  |  |  |  | require IO::Socket::UNIX; | 
| 2395 | 0 |  |  |  |  |  | IO::Socket::UNIX->VERSION('1.18'); # autoflush | 
| 2396 | 0 |  |  |  |  |  | IO::Socket::UNIX->import(qw(SOCK_STREAM)); | 
| 2397 |  |  |  |  |  |  |  | 
| 2398 | 0 |  |  |  |  |  | my $d; | 
| 2399 | 0 | 0 |  |  |  |  | if ($debug) { | 
| 2400 |  |  |  |  |  |  | $d = sub ($) { | 
| 2401 | 0 |  |  | 0 |  |  | Doit::Log::info("COMM: $_[0]"); | 
| 2402 | 0 |  |  |  |  |  | }; | 
| 2403 |  |  |  |  |  |  | } else { | 
| 2404 | 0 |  |  | 0 |  |  | $d = sub ($) { }; | 
| 2405 |  |  |  |  |  |  | } | 
| 2406 |  |  |  |  |  |  |  | 
| 2407 | 0 |  |  |  |  |  | $d->("Start communication process (pid $$)..."); | 
| 2408 |  |  |  |  |  |  |  | 
| 2409 | 0 |  |  |  |  |  | my $tries = 20; | 
| 2410 | 0 |  |  |  |  |  | my $sock_err; | 
| 2411 |  |  |  |  |  |  | my $sock = Doit::RPC::gentle_retry( | 
| 2412 |  |  |  |  |  |  | code => sub { | 
| 2413 | 0 |  |  | 0 |  |  | my(%opts) = @_; | 
| 2414 | 0 |  |  |  |  |  | my $sock = IO::Socket::UNIX->new( | 
| 2415 |  |  |  |  |  |  | Type => SOCK_STREAM(), | 
| 2416 |  |  |  |  |  |  | Peer => $peer, | 
| 2417 |  |  |  |  |  |  | ); | 
| 2418 | 0 | 0 |  |  |  |  | return $sock if $sock; | 
| 2419 | 0 |  |  |  |  |  | ${$opts{fail_info_ref}} = "(peer=$peer, errno=$!)"; | 
|  | 0 |  |  |  |  |  |  | 
| 2420 | 0 |  |  |  |  |  | undef; | 
| 2421 |  |  |  |  |  |  | }, | 
| 2422 |  |  |  |  |  |  | retry_msg_code => sub { | 
| 2423 | 0 |  |  | 0 |  |  | my($seconds) = @_; | 
| 2424 | 0 |  |  |  |  |  | $d->("can't connect, sleep for $seconds seconds"); | 
| 2425 |  |  |  |  |  |  | }, | 
| 2426 | 0 |  |  |  |  |  | fail_info_ref => \$sock_err, | 
| 2427 |  |  |  |  |  |  | ); | 
| 2428 | 0 | 0 |  |  |  |  | if (!$sock) { | 
| 2429 | 0 |  |  |  |  |  | die "COMM: Can't connect to socket (after $tries retries) $sock_err"; | 
| 2430 |  |  |  |  |  |  | } | 
| 2431 | 0 |  |  |  |  |  | $d->("socket to worker was created"); | 
| 2432 |  |  |  |  |  |  |  | 
| 2433 |  |  |  |  |  |  | my $get_and_send = sub ($$$$) { | 
| 2434 | 0 |  |  | 0 |  |  | my($infh, $outfh, $inname, $outname) = @_; | 
| 2435 |  |  |  |  |  |  |  | 
| 2436 | 0 |  |  |  |  |  | my $length_buf; | 
| 2437 | 0 | 0 |  |  |  |  | read $infh, $length_buf, 4 or die "COMM: reading data from $inname failed (getting length): $!"; | 
| 2438 | 0 |  |  |  |  |  | my $length = unpack("N", $length_buf); | 
| 2439 | 0 |  |  |  |  |  | $d->("starting getting data from $inname, length is $length"); | 
| 2440 | 0 |  |  |  |  |  | my $buf = ''; | 
| 2441 | 0 |  |  |  |  |  | while (1) { | 
| 2442 | 0 |  |  |  |  |  | my $got = read($infh, $buf, $length, length($buf)); | 
| 2443 | 0 | 0 |  |  |  |  | last if $got == $length; | 
| 2444 | 0 | 0 |  |  |  |  | die "COMM: Unexpected error $got > $length" if $got > $length; | 
| 2445 | 0 |  |  |  |  |  | $length -= $got; | 
| 2446 |  |  |  |  |  |  | } | 
| 2447 | 0 |  |  |  |  |  | $d->("finished reading data from $inname"); | 
| 2448 |  |  |  |  |  |  |  | 
| 2449 | 0 |  |  |  |  |  | print $outfh $length_buf; | 
| 2450 | 0 |  |  |  |  |  | print $outfh $buf; | 
| 2451 | 0 |  |  |  |  |  | $d->("finished sending data to $outname"); | 
| 2452 | 0 |  |  |  |  |  | }; | 
| 2453 |  |  |  |  |  |  |  | 
| 2454 | 0 |  |  |  |  |  | $outfh->autoflush(1); | 
| 2455 | 0 |  |  |  |  |  | $d->("about to enter loop"); | 
| 2456 | 0 |  |  |  |  |  | while () { | 
| 2457 | 0 | 0 |  |  |  |  | $d->("seen eof from local"), last if eof($infh); | 
| 2458 | 0 |  |  |  |  |  | $get_and_send->($infh, $sock, "local", "worker"); | 
| 2459 | 0 |  |  |  |  |  | $get_and_send->($sock, $outfh, "worker", "local"); | 
| 2460 |  |  |  |  |  |  | } | 
| 2461 | 0 |  |  |  |  |  | $d->("exited loop"); | 
| 2462 |  |  |  |  |  |  | } | 
| 2463 |  |  |  |  |  |  |  | 
| 2464 |  |  |  |  |  |  | } | 
| 2465 |  |  |  |  |  |  |  | 
| 2466 |  |  |  |  |  |  | 1; | 
| 2467 |  |  |  |  |  |  |  | 
| 2468 |  |  |  |  |  |  | __END__ |