| 1 |
cjwatson |
1901 |
#!/usr/bin/perl -w |
| 2 |
|
|
|
| 3 |
|
|
=head1 NAME |
| 4 |
|
|
|
| 5 |
|
|
debconf-apt-progress - install packages using debconf to display a progress bar |
| 6 |
|
|
|
| 7 |
|
|
=head1 SYNOPSIS |
| 8 |
|
|
|
| 9 |
|
|
debconf-apt-progress [--] command [args ...] |
| 10 |
|
|
debconf-apt-progress --config |
| 11 |
|
|
debconf-apt-progress --start |
| 12 |
|
|
debconf-apt-progress --from waypoint --to waypoint [--] command [args ...] |
| 13 |
|
|
debconf-apt-progress --stop |
| 14 |
|
|
|
| 15 |
|
|
=head1 DESCRIPTION |
| 16 |
|
|
|
| 17 |
|
|
B<debconf-apt-progress> installs packages using debconf to display a |
| 18 |
|
|
progress bar. The given I<command> should be any command-line apt frontend; |
| 19 |
|
|
specifically, it must send progress information to the file descriptor |
| 20 |
|
|
selected by the C<APT::Status-Fd> configuration option, and must keep the |
| 21 |
|
|
file descriptors nominated by the C<APT::Keep-Fds> configuration option open |
| 22 |
|
|
when invoking debconf (directly or indirectly), as those file descriptors |
| 23 |
|
|
will be used for the debconf passthrough protocol. |
| 24 |
|
|
|
| 25 |
|
|
The arguments to the command you supply should generally include B<-y> (for |
| 26 |
|
|
B<apt-get> or B<aptitude>) or similar to avoid the apt frontend prompting |
| 27 |
|
|
for input. B<debconf-apt-progress> cannot do this itself because the |
| 28 |
|
|
appropriate argument may differ between apt frontends. |
| 29 |
|
|
|
| 30 |
|
|
The B<--start>, B<--stop>, B<--from>, and B<--to> options may be used to |
| 31 |
|
|
create a progress bar with multiple segments for different stages of |
| 32 |
|
|
installation, provided that the caller is a debconf confmodule. The caller |
| 33 |
|
|
may also interact with the progress bar itself using the debconf protocol if |
| 34 |
|
|
it so desires. |
| 35 |
|
|
|
| 36 |
|
|
debconf locks its config database when it starts up, which makes it |
| 37 |
|
|
unfortunately inconvenient to have one instance of debconf displaying the |
| 38 |
|
|
progress bar and another passing through questions from packages being |
| 39 |
cjwatson |
1908 |
installed. If you're using a multiple-segment progress bar, you'll need to |
| 40 |
|
|
eval the output of the B<--config> option before starting the debconf |
| 41 |
|
|
frontend to work around this. See L<the EXAMPLES section/EXAMPLES> below. |
| 42 |
cjwatson |
1901 |
|
| 43 |
|
|
=head1 OPTIONS |
| 44 |
|
|
|
| 45 |
|
|
=over 4 |
| 46 |
|
|
|
| 47 |
|
|
=item B<--config> |
| 48 |
|
|
|
| 49 |
cjwatson |
1908 |
Print environment variables necessary to start up a progress bar frontend. |
| 50 |
cjwatson |
1901 |
|
| 51 |
|
|
=item B<--start> |
| 52 |
|
|
|
| 53 |
cjwatson |
1912 |
Start up a progress bar, running from 0 to 100 by default. Use B<--from> and |
| 54 |
|
|
B<--to> to use other endpoints. |
| 55 |
cjwatson |
1901 |
|
| 56 |
|
|
=item B<--from> I<waypoint> |
| 57 |
|
|
|
| 58 |
cjwatson |
1912 |
If used with B<--start>, make the progress bar begin at I<waypoint> rather |
| 59 |
|
|
than 0. |
| 60 |
cjwatson |
1901 |
|
| 61 |
cjwatson |
1912 |
Otherwise, install packages with their progress bar beginning at this |
| 62 |
|
|
"waypoint". Must be used with B<--to>. |
| 63 |
|
|
|
| 64 |
cjwatson |
1901 |
=item B<--to> I<waypoint> |
| 65 |
|
|
|
| 66 |
cjwatson |
1912 |
If used with B<--start>, make the progress bar end at I<waypoint> rather |
| 67 |
|
|
than 100. |
| 68 |
cjwatson |
1901 |
|
| 69 |
cjwatson |
1912 |
Otherwise, install packages with their progress bar ending at this |
| 70 |
|
|
"waypoint". Must be used with B<--from>. |
| 71 |
|
|
|
| 72 |
cjwatson |
1901 |
=item B<--stop> |
| 73 |
|
|
|
| 74 |
|
|
Stop a running progress bar. |
| 75 |
|
|
|
| 76 |
joeyh |
2233 |
=item B<--no-progress> |
| 77 |
|
|
|
| 78 |
|
|
Avoid starting, stopping, or stepping the progress bar. Progress |
| 79 |
|
|
messages from apt, media change events, and debconf questions will still |
| 80 |
|
|
be passed through to debconf. |
| 81 |
|
|
|
| 82 |
joeyh |
2244 |
=item B<--dlwaypoint> I<percentage> |
| 83 |
|
|
|
| 84 |
|
|
Specify what percent of the progress bar to use for downloading packages. |
| 85 |
|
|
The remainder will be used for installing packages. The default is to use |
| 86 |
|
|
15% for downloading and the remaining 85% for installing. |
| 87 |
|
|
|
| 88 |
cjwatson |
1901 |
=item B<--logfile> I<file> |
| 89 |
|
|
|
| 90 |
|
|
Send the normal output from apt to the given file. |
| 91 |
|
|
|
| 92 |
cjwatson |
1904 |
=item B<--logstderr> |
| 93 |
|
|
|
| 94 |
|
|
Send the normal output from apt to stderr. If you supply neither |
| 95 |
|
|
B<--logfile> nor B<--logstderr>, the normal output from apt will be |
| 96 |
|
|
discarded. |
| 97 |
|
|
|
| 98 |
cjwatson |
1901 |
=item B<--> |
| 99 |
|
|
|
| 100 |
|
|
Terminate options. Since you will normally need to give at least the B<-y> |
| 101 |
|
|
argument to the command being run, you will usually need to use B<--> to |
| 102 |
|
|
prevent that being interpreted as an option to B<debconf-apt-progress> |
| 103 |
|
|
itself. |
| 104 |
|
|
|
| 105 |
cjwatson |
2307 |
=back |
| 106 |
|
|
|
| 107 |
cjwatson |
1901 |
=head1 EXAMPLES |
| 108 |
|
|
|
| 109 |
|
|
Install the GNOME desktop and an X window system development environment |
| 110 |
|
|
within a progress bar: |
| 111 |
|
|
|
| 112 |
|
|
debconf-apt-progress -- aptitude -y install gnome x-window-system-dev |
| 113 |
|
|
|
| 114 |
|
|
Install the GNOME, KDE, and XFCE desktops within a single progress bar, |
| 115 |
|
|
allocating 45% of the progress bar for each of GNOME and KDE and the |
| 116 |
|
|
remaining 10% for XFCE: |
| 117 |
|
|
|
| 118 |
|
|
#! /bin/sh |
| 119 |
|
|
set -e |
| 120 |
|
|
case $1 in |
| 121 |
|
|
'') |
| 122 |
cjwatson |
1908 |
eval "$(debconf-apt-progress --config)" |
| 123 |
|
|
"$0" debconf |
| 124 |
cjwatson |
1901 |
;; |
| 125 |
|
|
debconf) |
| 126 |
|
|
. /usr/share/debconf/confmodule |
| 127 |
|
|
debconf-apt-progress --start |
| 128 |
|
|
debconf-apt-progress --from 0 --to 45 -- apt-get -y install gnome |
| 129 |
|
|
debconf-apt-progress --from 45 --to 90 -- apt-get -y install kde |
| 130 |
|
|
debconf-apt-progress --from 90 --to 100 -- apt-get -y install xfce4 |
| 131 |
|
|
debconf-apt-progress --stop |
| 132 |
|
|
;; |
| 133 |
|
|
esac |
| 134 |
|
|
|
| 135 |
joeyh |
2238 |
=head1 RETURN CODE |
| 136 |
|
|
|
| 137 |
|
|
The exit code of the specified command is returned, unless the user hit the |
| 138 |
|
|
cancel button on the progress bar. If the cancel button was hit, a value of |
| 139 |
joeyh |
2239 |
30 is returned. To avoid ambiguity, if the command returned 30, a value of |
| 140 |
|
|
3 will be returned. |
| 141 |
joeyh |
2238 |
|
| 142 |
cjwatson |
1901 |
=cut |
| 143 |
|
|
|
| 144 |
|
|
use strict; |
| 145 |
|
|
use POSIX; |
| 146 |
|
|
use Fcntl; |
| 147 |
|
|
use Getopt::Long; |
| 148 |
|
|
# Avoid starting the debconf frontend just yet. |
| 149 |
|
|
use Debconf::Client::ConfModule (); |
| 150 |
|
|
|
| 151 |
|
|
my ($config, $start, $from, $to, $stop); |
| 152 |
joeyh |
2233 |
my $progress=1; |
| 153 |
joeyh |
2244 |
my $dlwaypoint=15; |
| 154 |
cjwatson |
1904 |
my ($logfile, $logstderr); |
| 155 |
cjwatson |
2343 |
my $had_frontend; |
| 156 |
cjwatson |
1901 |
|
| 157 |
|
|
sub checkopen (@) { |
| 158 |
|
|
my $file = $_[0]; |
| 159 |
|
|
my $fd = POSIX::open($file, &POSIX::O_RDONLY); |
| 160 |
|
|
defined $fd or die "$0: can't open $_[0]: $!\n"; |
| 161 |
|
|
return $fd; |
| 162 |
|
|
} |
| 163 |
|
|
|
| 164 |
|
|
sub checkclose ($) { |
| 165 |
|
|
my $fd = $_[0]; |
| 166 |
|
|
unless (POSIX::close($fd)) { |
| 167 |
|
|
return if $! == &POSIX::EBADF; |
| 168 |
|
|
die "$0: can't close fd $fd: $!\n"; |
| 169 |
|
|
} |
| 170 |
|
|
} |
| 171 |
|
|
|
| 172 |
|
|
sub checkdup2 ($$) { |
| 173 |
|
|
my ($oldfd, $newfd) = @_; |
| 174 |
|
|
checkclose($newfd); |
| 175 |
|
|
POSIX::dup2($oldfd, $newfd) |
| 176 |
|
|
or die "$0: can't dup fd $oldfd to $newfd: $!\n"; |
| 177 |
|
|
} |
| 178 |
|
|
|
| 179 |
|
|
sub nocloexec (*) { |
| 180 |
|
|
my $fh = shift; |
| 181 |
|
|
my $flags = fcntl($fh, F_GETFD, 0); |
| 182 |
|
|
fcntl($fh, F_SETFD, $flags & ~FD_CLOEXEC); |
| 183 |
|
|
} |
| 184 |
|
|
|
| 185 |
cjwatson |
2214 |
sub nonblock (*) { |
| 186 |
|
|
my $fh = shift; |
| 187 |
|
|
my $flags = fcntl($fh, F_GETFL, 0); |
| 188 |
|
|
fcntl($fh, F_SETFL, $flags | O_NONBLOCK); |
| 189 |
|
|
} |
| 190 |
|
|
|
| 191 |
cjwatson |
1901 |
# Open the given file descriptors to make sure they won't accidentally be |
| 192 |
|
|
# used by Perl, leading to confusion. |
| 193 |
|
|
sub reservefds (@) { |
| 194 |
|
|
my $null = checkopen('/dev/null'); |
| 195 |
|
|
my $close = 1; |
| 196 |
|
|
for my $fd (@_) { |
| 197 |
|
|
if ($null == $fd) { |
| 198 |
|
|
$close = 0; |
| 199 |
|
|
} else { |
| 200 |
|
|
checkclose($fd); |
| 201 |
|
|
checkdup2($null, $fd); |
| 202 |
|
|
} |
| 203 |
|
|
} |
| 204 |
|
|
if ($close) { |
| 205 |
|
|
checkclose($null); |
| 206 |
|
|
} |
| 207 |
|
|
} |
| 208 |
|
|
|
| 209 |
cjwatson |
1908 |
# Does this environment variable exist, and is it non-empty? |
| 210 |
|
|
sub envnonempty ($) { |
| 211 |
|
|
my $name = shift; |
| 212 |
|
|
return (exists $ENV{$name} and $ENV{$name} ne ''); |
| 213 |
|
|
} |
| 214 |
|
|
|
| 215 |
cjwatson |
1928 |
sub start_debconf (@) { |
| 216 |
|
|
if (! $ENV{DEBIAN_HAS_FRONTEND}) { |
| 217 |
|
|
# Save existing environment variables. |
| 218 |
|
|
if (envnonempty('DEBCONF_DB_REPLACE')) { |
| 219 |
|
|
$ENV{DEBCONF_APT_PROGRESS_DB_REPLACE} = |
| 220 |
|
|
$ENV{DEBCONF_DB_REPLACE}; |
| 221 |
|
|
} |
| 222 |
|
|
if (envnonempty('DEBCONF_DB_OVERRIDE')) { |
| 223 |
|
|
$ENV{DEBCONF_APT_PROGRESS_DB_OVERRIDE} = |
| 224 |
|
|
$ENV{DEBCONF_DB_OVERRIDE}; |
| 225 |
|
|
} |
| 226 |
|
|
|
| 227 |
|
|
# Make sure the main configdb is opened read-only ... |
| 228 |
|
|
$ENV{DEBCONF_DB_REPLACE} = 'configdb'; |
| 229 |
|
|
# ... and stack a writable db on top of it, since the |
| 230 |
|
|
# passthrough instance is going to be sending us db updates. |
| 231 |
|
|
$ENV{DEBCONF_DB_OVERRIDE} = 'Pipe{infd:none outfd:none}'; |
| 232 |
|
|
|
| 233 |
cjwatson |
2343 |
# Leave a note for ourselves. We need to do it this way |
| 234 |
|
|
# round since DEBIAN_HAS_FRONTEND will be set the second |
| 235 |
|
|
# time round even if it isn't set initially. |
| 236 |
|
|
$ENV{DEBCONF_APT_PROGRESS_NO_FRONTEND} = 1; |
| 237 |
cjwatson |
2334 |
|
| 238 |
cjwatson |
1928 |
# Restore @ARGV so that |
| 239 |
|
|
# Debconf::Client::ConfModule::import() can use it. |
| 240 |
|
|
@ARGV = @_; |
| 241 |
|
|
} |
| 242 |
|
|
|
| 243 |
|
|
import Debconf::Client::ConfModule; |
| 244 |
|
|
} |
| 245 |
|
|
|
| 246 |
cjwatson |
1901 |
sub passthrough (@) { |
| 247 |
|
|
my $priority = Debconf::Client::ConfModule::get('debconf/priority'); |
| 248 |
|
|
|
| 249 |
|
|
defined(my $pid = fork) or die "$0: can't fork: $!\n"; |
| 250 |
|
|
if (!$pid) { |
| 251 |
|
|
close STATUS_READ; |
| 252 |
joeyh |
2105 |
close COMMAND_WRITE; |
| 253 |
cjwatson |
2214 |
close DEBCONF_COMMAND_READ; |
| 254 |
|
|
close DEBCONF_REPLY_WRITE; |
| 255 |
cjwatson |
1901 |
$^F = 6; # avoid close-on-exec |
| 256 |
joeyh |
2105 |
if (fileno(COMMAND_READ) != 0) { |
| 257 |
|
|
checkdup2(fileno(COMMAND_READ), 0); |
| 258 |
|
|
close COMMAND_READ; |
| 259 |
cjwatson |
1901 |
} |
| 260 |
|
|
if (fileno(APT_LOG) != 1) { |
| 261 |
|
|
checkclose(1); |
| 262 |
|
|
checkdup2(fileno(APT_LOG), 1); |
| 263 |
|
|
} |
| 264 |
|
|
if (fileno(APT_LOG) != 2) { |
| 265 |
|
|
checkclose(2); |
| 266 |
|
|
checkdup2(fileno(APT_LOG), 2); |
| 267 |
|
|
} |
| 268 |
|
|
close APT_LOG; |
| 269 |
|
|
delete $ENV{DEBIAN_HAS_FRONTEND}; |
| 270 |
|
|
delete $ENV{DEBCONF_REDIR}; |
| 271 |
|
|
delete $ENV{DEBCONF_SYSTEMRC}; |
| 272 |
|
|
delete $ENV{DEBCONF_PIPE}; # just in case ... |
| 273 |
|
|
$ENV{DEBIAN_FRONTEND} = 'passthrough'; |
| 274 |
|
|
$ENV{DEBIAN_PRIORITY} = $priority; |
| 275 |
|
|
$ENV{DEBCONF_READFD} = 5; |
| 276 |
|
|
$ENV{DEBCONF_WRITEFD} = 6; |
| 277 |
|
|
$ENV{APT_LISTCHANGES_FRONTEND} = 'none'; |
| 278 |
cjwatson |
2341 |
# If we already had a debconf frontend when we started, then |
| 279 |
|
|
# the passthrough child needs to use the same pipe-database |
| 280 |
|
|
# trick as we do. See start_debconf. |
| 281 |
cjwatson |
2343 |
if ($had_frontend) { |
| 282 |
cjwatson |
2341 |
$ENV{DEBCONF_DB_REPLACE} = 'configdb'; |
| 283 |
|
|
$ENV{DEBCONF_DB_OVERRIDE} = 'Pipe{infd:none outfd:none}'; |
| 284 |
|
|
} |
| 285 |
cjwatson |
1901 |
exec @_; |
| 286 |
|
|
} |
| 287 |
|
|
|
| 288 |
|
|
close STATUS_WRITE; |
| 289 |
joeyh |
2105 |
close COMMAND_READ; |
| 290 |
cjwatson |
2214 |
close DEBCONF_COMMAND_WRITE; |
| 291 |
|
|
close DEBCONF_REPLY_READ; |
| 292 |
cjwatson |
1901 |
return $pid; |
| 293 |
|
|
} |
| 294 |
|
|
|
| 295 |
cjwatson |
2214 |
sub handle_status ($$$) { |
| 296 |
|
|
my ($from, $to, $line) = @_; |
| 297 |
|
|
my ($status, $pkg, $percent, $description) = split ':', $line, 4; |
| 298 |
|
|
|
| 299 |
|
|
my ($min, $len); |
| 300 |
|
|
if ($status eq 'dlstatus') { |
| 301 |
|
|
$min = 0; |
| 302 |
joeyh |
2244 |
$len = $dlwaypoint; |
| 303 |
cjwatson |
2214 |
} |
| 304 |
|
|
elsif ($status eq 'pmstatus') { |
| 305 |
joeyh |
2244 |
$min = $dlwaypoint; |
| 306 |
|
|
$len = 100 - $dlwaypoint; |
| 307 |
cjwatson |
2214 |
} |
| 308 |
|
|
elsif ($status eq 'media-change') { |
| 309 |
|
|
Debconf::Client::ConfModule::subst( |
| 310 |
|
|
'debconf-apt-progress/media-change', 'MESSAGE', |
| 311 |
|
|
$description); |
| 312 |
|
|
my @ret = Debconf::Client::ConfModule::input( |
| 313 |
|
|
'critical', 'debconf-apt-progress/media-change'); |
| 314 |
|
|
$ret[0] == 0 or die "Can't display media change request!\n"; |
| 315 |
|
|
Debconf::Client::ConfModule::go(); |
| 316 |
|
|
print COMMAND_WRITE "\n" || die "can't talk to command fd: $!"; |
| 317 |
cjwatson |
2266 |
return; |
| 318 |
cjwatson |
2214 |
} |
| 319 |
|
|
else { |
| 320 |
cjwatson |
2266 |
return; |
| 321 |
cjwatson |
2214 |
} |
| 322 |
|
|
|
| 323 |
|
|
$percent = ($percent * $len / 100 + $min); |
| 324 |
|
|
$percent = ($percent * ($to - $from) / 100 + $from); |
| 325 |
|
|
$percent =~ s/\..*//; |
| 326 |
joeyh |
2236 |
if ($progress) { |
| 327 |
|
|
my @ret=Debconf::Client::ConfModule::progress('SET', $percent); |
| 328 |
|
|
if ($ret[0] eq '30') { |
| 329 |
|
|
cancel(); |
| 330 |
|
|
} |
| 331 |
|
|
} |
| 332 |
cjwatson |
2214 |
Debconf::Client::ConfModule::subst( |
| 333 |
|
|
'debconf-apt-progress/info', 'DESCRIPTION', $description); |
| 334 |
joeyh |
2236 |
my @ret=Debconf::Client::ConfModule::progress( |
| 335 |
cjwatson |
2214 |
'INFO', 'debconf-apt-progress/info'); |
| 336 |
joeyh |
2236 |
if ($ret[0] eq '30') { |
| 337 |
|
|
cancel(); |
| 338 |
|
|
} |
| 339 |
cjwatson |
2214 |
} |
| 340 |
|
|
|
| 341 |
|
|
sub handle_debconf_command ($) { |
| 342 |
|
|
my $line = shift; |
| 343 |
|
|
|
| 344 |
|
|
# Debconf::Client::ConfModule has already dealt with checking |
| 345 |
|
|
# DEBCONF_REDIR. |
| 346 |
|
|
print "$line\n" || die "can't write to stdout: $!"; |
| 347 |
|
|
my $ret = <STDIN>; |
| 348 |
|
|
chomp $ret; |
| 349 |
|
|
print DEBCONF_REPLY_WRITE "$ret\n" || |
| 350 |
|
|
die "can't write to DEBCONF_REPLY_WRITE: $!"; |
| 351 |
|
|
} |
| 352 |
|
|
|
| 353 |
joeyh |
2236 |
my $pid; |
| 354 |
cjwatson |
1901 |
sub run_progress ($$@) { |
| 355 |
|
|
my $from = shift; |
| 356 |
|
|
my $to = shift; |
| 357 |
|
|
my $command = shift; |
| 358 |
|
|
local (*STATUS_READ, *STATUS_WRITE); |
| 359 |
joeyh |
2105 |
local (*COMMAND_READ, *COMMAND_WRITE); |
| 360 |
cjwatson |
2214 |
local (*DEBCONF_COMMAND_READ, *DEBCONF_COMMAND_WRITE); |
| 361 |
|
|
local (*DEBCONF_REPLY_READ, *DEBCONF_REPLY_WRITE); |
| 362 |
cjwatson |
1901 |
local *APT_LOG; |
| 363 |
cjwatson |
2214 |
use IO::Handle; |
| 364 |
cjwatson |
1901 |
|
| 365 |
joeyh |
2236 |
if ($progress) { |
| 366 |
|
|
my @ret=Debconf::Client::ConfModule::progress( |
| 367 |
|
|
'INFO', 'debconf-apt-progress/preparing'); |
| 368 |
|
|
if ($ret[0] eq '30') { |
| 369 |
|
|
cancel(); |
| 370 |
cjwatson |
2348 |
return 30; |
| 371 |
joeyh |
2236 |
} |
| 372 |
|
|
} |
| 373 |
cjwatson |
1901 |
|
| 374 |
|
|
reservefds(4, 5, 6); |
| 375 |
|
|
|
| 376 |
cjwatson |
2214 |
pipe STATUS_READ, STATUS_WRITE |
| 377 |
|
|
or die "$0: can't create status pipe: $!"; |
| 378 |
|
|
nonblock(\*STATUS_READ); |
| 379 |
cjwatson |
1901 |
checkdup2(fileno(STATUS_WRITE), 4); |
| 380 |
|
|
open STATUS_WRITE, '>&=4' |
| 381 |
|
|
or die "$0: can't reopen STATUS_WRITE as fd 4: $!"; |
| 382 |
|
|
nocloexec(\*STATUS_WRITE); |
| 383 |
cjwatson |
2214 |
|
| 384 |
|
|
pipe COMMAND_READ, COMMAND_WRITE |
| 385 |
|
|
or die "$0: can't create command pipe: $!"; |
| 386 |
joeyh |
2105 |
nocloexec(\*COMMAND_READ); |
| 387 |
|
|
COMMAND_WRITE->autoflush(1); |
| 388 |
cjwatson |
1901 |
|
| 389 |
cjwatson |
2214 |
pipe DEBCONF_COMMAND_READ, DEBCONF_COMMAND_WRITE |
| 390 |
|
|
or die "$0: can't create debconf command pipe: $!"; |
| 391 |
|
|
nonblock(\*DEBCONF_COMMAND_READ); |
| 392 |
|
|
checkdup2(fileno(DEBCONF_COMMAND_WRITE), 6); |
| 393 |
|
|
open DEBCONF_COMMAND_WRITE, '>&=6' |
| 394 |
|
|
or die "$0: can't reopen DEBCONF_COMMAND_WRITE as fd 6: $!"; |
| 395 |
|
|
nocloexec(\*DEBCONF_COMMAND_WRITE); |
| 396 |
|
|
|
| 397 |
|
|
pipe DEBCONF_REPLY_READ, DEBCONF_REPLY_WRITE |
| 398 |
|
|
or die "$0: can't create debconf reply pipe: $!"; |
| 399 |
|
|
checkdup2(fileno(DEBCONF_REPLY_READ), 5); |
| 400 |
|
|
open DEBCONF_REPLY_READ, '<&=5' |
| 401 |
|
|
or die "$0: can't reopen DEBCONF_REPLY_READ as fd 5: $!"; |
| 402 |
|
|
nocloexec(\*DEBCONF_REPLY_READ); |
| 403 |
|
|
DEBCONF_REPLY_WRITE->autoflush(1); |
| 404 |
|
|
|
| 405 |
cjwatson |
1901 |
if (defined $logfile) { |
| 406 |
|
|
open APT_LOG, '>>', $logfile |
| 407 |
|
|
or die "$0: can't open $logfile: $!"; |
| 408 |
cjwatson |
1904 |
} elsif ($logstderr) { |
| 409 |
cjwatson |
1901 |
open APT_LOG, '>&STDERR' |
| 410 |
|
|
or die "$0: can't duplicate stderr: $!"; |
| 411 |
cjwatson |
1904 |
} else { |
| 412 |
|
|
open APT_LOG, '>', '/dev/null' |
| 413 |
|
|
or die "$0: can't open /dev/null: $!"; |
| 414 |
cjwatson |
1901 |
} |
| 415 |
|
|
nocloexec(\*APT_LOG); |
| 416 |
|
|
|
| 417 |
joeyh |
2236 |
$pid = passthrough $command, |
| 418 |
cjwatson |
1901 |
'-o', 'APT::Status-Fd=4', |
| 419 |
|
|
'-o', 'APT::Keep-Fds::=5', |
| 420 |
|
|
'-o', 'APT::Keep-Fds::=6', |
| 421 |
|
|
@_; |
| 422 |
|
|
|
| 423 |
cjwatson |
2214 |
my $status_eof = 0; |
| 424 |
|
|
my $debconf_command_eof = 0; |
| 425 |
|
|
my $status_buf = ''; |
| 426 |
|
|
my $debconf_command_buf = ''; |
| 427 |
cjwatson |
1901 |
|
| 428 |
cjwatson |
2249 |
# STATUS_READ should be the last fd to close. DEBCONF_COMMAND_WRITE |
| 429 |
|
|
# may end up captured by buggy daemons, so terminate the loop even |
| 430 |
|
|
# if we haven't hit $debconf_command_eof. |
| 431 |
|
|
while (not $status_eof) { |
| 432 |
cjwatson |
2214 |
my $rin = ''; |
| 433 |
|
|
my $rout; |
| 434 |
cjwatson |
2249 |
vec($rin, fileno(STATUS_READ), 1) = 1; |
| 435 |
cjwatson |
2214 |
vec($rin, fileno(DEBCONF_COMMAND_READ), 1) = 1 |
| 436 |
|
|
unless $debconf_command_eof; |
| 437 |
|
|
my $sel = select($rout = $rin, undef, undef, undef); |
| 438 |
|
|
if ($sel < 0) { |
| 439 |
|
|
next if $! == &POSIX::EINTR; |
| 440 |
|
|
die "$0: select failed: $!"; |
| 441 |
joeyh |
2119 |
} |
| 442 |
cjwatson |
2214 |
|
| 443 |
|
|
if (vec($rout, fileno(STATUS_READ), 1) == 1) { |
| 444 |
|
|
# Status message from apt. Transform into debconf |
| 445 |
|
|
# messages. |
| 446 |
|
|
while (1) { |
| 447 |
|
|
my $r = sysread(STATUS_READ, $status_buf, 4096, |
| 448 |
|
|
length $status_buf); |
| 449 |
|
|
if (not defined $r) { |
| 450 |
|
|
next if $! == &POSIX::EINTR; |
| 451 |
|
|
last if $! == &POSIX::EAGAIN or |
| 452 |
|
|
$! == &POSIX::EWOULDBLOCK; |
| 453 |
|
|
die "$0: read STATUS_READ failed: $!"; |
| 454 |
|
|
} |
| 455 |
|
|
elsif ($r == 0) { |
| 456 |
|
|
if ($status_buf ne '' and |
| 457 |
|
|
$status_buf !~ /\n$/) { |
| 458 |
|
|
$status_buf .= "\n"; |
| 459 |
|
|
} |
| 460 |
|
|
$status_eof = 1; |
| 461 |
|
|
last; |
| 462 |
|
|
} |
| 463 |
|
|
last if $status_buf =~ /\n/; |
| 464 |
|
|
} |
| 465 |
|
|
|
| 466 |
|
|
while ($status_buf =~ /\n/) { |
| 467 |
|
|
my $status_line; |
| 468 |
|
|
($status_line, $status_buf) = |
| 469 |
|
|
split /\n/, $status_buf, 2; |
| 470 |
|
|
handle_status $from, $to, $status_line; |
| 471 |
|
|
} |
| 472 |
joeyh |
2119 |
} |
| 473 |
cjwatson |
2214 |
|
| 474 |
|
|
if (vec($rout, fileno(DEBCONF_COMMAND_READ), 1) == 1) { |
| 475 |
|
|
# Debconf command. Pass straight through. |
| 476 |
|
|
while (1) { |
| 477 |
|
|
my $r = sysread(DEBCONF_COMMAND_READ, |
| 478 |
|
|
$debconf_command_buf, 4096, |
| 479 |
|
|
length $debconf_command_buf); |
| 480 |
|
|
if (not defined $r) { |
| 481 |
|
|
next if $! == &POSIX::EINTR; |
| 482 |
|
|
last if $! == &POSIX::EAGAIN or |
| 483 |
|
|
$! == &POSIX::EWOULDBLOCK; |
| 484 |
|
|
die "$0: read DEBCONF_COMMAND_READ " . |
| 485 |
|
|
"failed: $!"; |
| 486 |
|
|
} |
| 487 |
|
|
elsif ($r == 0) { |
| 488 |
|
|
if ($debconf_command_buf ne '' and |
| 489 |
|
|
$debconf_command_buf !~ /\n$/) { |
| 490 |
|
|
$debconf_command_buf .= "\n"; |
| 491 |
|
|
} |
| 492 |
|
|
$debconf_command_eof = 1; |
| 493 |
|
|
last; |
| 494 |
|
|
} |
| 495 |
|
|
last if $debconf_command_buf =~ /\n/; |
| 496 |
|
|
} |
| 497 |
|
|
|
| 498 |
|
|
while ($debconf_command_buf =~ /\n/) { |
| 499 |
|
|
my $debconf_command_line; |
| 500 |
|
|
($debconf_command_line, $debconf_command_buf) = |
| 501 |
|
|
split /\n/, $debconf_command_buf, 2; |
| 502 |
|
|
handle_debconf_command $debconf_command_line; |
| 503 |
|
|
} |
| 504 |
joeyh |
2119 |
} |
| 505 |
cjwatson |
1901 |
} |
| 506 |
|
|
|
| 507 |
|
|
waitpid $pid, 0; |
| 508 |
cjwatson |
2349 |
undef $pid; |
| 509 |
cjwatson |
1901 |
my $status = $?; |
| 510 |
|
|
|
| 511 |
|
|
# make sure that the progress bar always gets to the end |
| 512 |
cjwatson |
2349 |
if ($progress) { |
| 513 |
|
|
my @ret=Debconf::Client::ConfModule::progress('SET', $to); |
| 514 |
|
|
if ($ret[0] eq '30') { |
| 515 |
|
|
cancel(); |
| 516 |
|
|
} |
| 517 |
|
|
} |
| 518 |
cjwatson |
1901 |
|
| 519 |
joeyh |
2237 |
if ($status & 127) { |
| 520 |
|
|
return 127; |
| 521 |
|
|
} |
| 522 |
|
|
|
| 523 |
cjwatson |
1901 |
return ($status >> 8); |
| 524 |
|
|
} |
| 525 |
|
|
|
| 526 |
joeyh |
2244 |
# Called if the progress bar is cancelled. Starts with a SIGINT but |
| 527 |
joeyh |
2236 |
# if called repeatedly, falls back to SIGKILL. |
| 528 |
joeyh |
2240 |
my $cancelled=0; |
| 529 |
cjwatson |
2348 |
my $cancel_sent_signal=0; |
| 530 |
joeyh |
2236 |
sub cancel () { |
| 531 |
cjwatson |
2348 |
$cancelled++; |
| 532 |
joeyh |
2236 |
if (defined $pid) { |
| 533 |
cjwatson |
2348 |
$cancel_sent_signal++; |
| 534 |
|
|
if ($cancel_sent_signal == 1) { |
| 535 |
cjwatson |
2265 |
kill INT => $pid; |
| 536 |
joeyh |
2236 |
} |
| 537 |
|
|
else { |
| 538 |
cjwatson |
2265 |
kill KILL => $pid; |
| 539 |
joeyh |
2236 |
} |
| 540 |
|
|
} |
| 541 |
|
|
} |
| 542 |
|
|
|
| 543 |
cjwatson |
1912 |
sub start_bar ($$) { |
| 544 |
|
|
my ($from, $to) = @_; |
| 545 |
joeyh |
2236 |
if ($progress) { |
| 546 |
|
|
Debconf::Client::ConfModule::progress( |
| 547 |
|
|
'START', $from, $to, 'debconf-apt-progress/title'); |
| 548 |
|
|
my @ret=Debconf::Client::ConfModule::progress( |
| 549 |
|
|
'INFO', 'debconf-apt-progress/preparing'); |
| 550 |
|
|
if ($ret[0] eq '30') { |
| 551 |
|
|
cancel(); |
| 552 |
|
|
} |
| 553 |
|
|
} |
| 554 |
cjwatson |
1901 |
} |
| 555 |
|
|
|
| 556 |
|
|
sub stop_bar () { |
| 557 |
joeyh |
2233 |
Debconf::Client::ConfModule::progress('STOP') if $progress; |
| 558 |
cjwatson |
1909 |
# If we don't stop, we leave a zombie in case some daemon fails to |
| 559 |
cjwatson |
2334 |
# disconnect from fd 3. Don't do this if debconf was already |
| 560 |
|
|
# running, though, since in that case we're running as part of a |
| 561 |
|
|
# larger application which will need to take its own care to stop |
| 562 |
|
|
# when it's finished. |
| 563 |
cjwatson |
2343 |
Debconf::Client::ConfModule::stop() unless $had_frontend; |
| 564 |
cjwatson |
1901 |
} |
| 565 |
|
|
|
| 566 |
cjwatson |
1908 |
# Restore saved environment variables. |
| 567 |
|
|
if (envnonempty('DEBCONF_APT_PROGRESS_DB_REPLACE')) { |
| 568 |
|
|
$ENV{DEBCONF_DB_REPLACE} = $ENV{DEBCONF_APT_PROGRESS_DB_REPLACE}; |
| 569 |
|
|
} else { |
| 570 |
|
|
delete $ENV{DEBCONF_DB_REPLACE}; |
| 571 |
|
|
} |
| 572 |
|
|
if (envnonempty('DEBCONF_APT_PROGRESS_DB_OVERRIDE')) { |
| 573 |
|
|
$ENV{DEBCONF_DB_OVERRIDE} = $ENV{DEBCONF_APT_PROGRESS_DB_OVERRIDE}; |
| 574 |
|
|
} else { |
| 575 |
|
|
delete $ENV{DEBCONF_DB_OVERRIDE}; |
| 576 |
|
|
} |
| 577 |
cjwatson |
2343 |
$had_frontend = 1 unless $ENV{DEBCONF_APT_PROGRESS_NO_FRONTEND}; |
| 578 |
|
|
delete $ENV{DEBCONF_APT_PROGRESS_NO_FRONTEND}; # avoid inheritance |
| 579 |
cjwatson |
1908 |
|
| 580 |
cjwatson |
1901 |
my @saved_argv = @ARGV; |
| 581 |
|
|
|
| 582 |
joeyh |
2244 |
my $result = GetOptions('config' => \$config, |
| 583 |
|
|
'start' => \$start, |
| 584 |
|
|
'from=i' => \$from, |
| 585 |
|
|
'to=i' => \$to, |
| 586 |
|
|
'stop' => \$stop, |
| 587 |
|
|
'logfile=s' => \$logfile, |
| 588 |
|
|
'logstderr' => \$logstderr, |
| 589 |
|
|
'progress!' => \$progress, |
| 590 |
|
|
'dlwaypoint=i' => \$dlwaypoint, |
| 591 |
|
|
); |
| 592 |
cjwatson |
1901 |
|
| 593 |
joeyh |
2233 |
if (! $progress && ($start || $from || $to || $stop)) { |
| 594 |
|
|
die "--no-progress cannot be used with --start, --from, --to, or --stop\n"; |
| 595 |
|
|
} |
| 596 |
|
|
|
| 597 |
cjwatson |
1912 |
unless ($start) { |
| 598 |
|
|
if (defined $from and not defined $to) { |
| 599 |
|
|
die "$0: --from requires --to\n"; |
| 600 |
|
|
} elsif (defined $to and not defined $from) { |
| 601 |
|
|
die "$0: --to requires --from\n"; |
| 602 |
|
|
} |
| 603 |
cjwatson |
1901 |
} |
| 604 |
|
|
|
| 605 |
|
|
my $mutex = 0; |
| 606 |
|
|
++$mutex if $config; |
| 607 |
|
|
++$mutex if $start; |
| 608 |
|
|
++$mutex if $stop; |
| 609 |
|
|
if ($mutex > 1) { |
| 610 |
cjwatson |
1912 |
die "$0: must use only one of --config, --start, or --stop\n"; |
| 611 |
cjwatson |
1901 |
} |
| 612 |
|
|
|
| 613 |
cjwatson |
1912 |
if (($config or $stop) and (defined $from or defined $to)) { |
| 614 |
|
|
die "$0: cannot use --from or --to with --config or --stop\n"; |
| 615 |
|
|
} |
| 616 |
|
|
|
| 617 |
cjwatson |
1928 |
start_debconf(@saved_argv) unless $config; |
| 618 |
|
|
|
| 619 |
cjwatson |
1901 |
my $status = 0; |
| 620 |
|
|
|
| 621 |
|
|
if ($config) { |
| 622 |
cjwatson |
1908 |
print <<'EOF'; |
| 623 |
|
|
DEBCONF_APT_PROGRESS_DB_REPLACE="$DEBCONF_DB_REPLACE" |
| 624 |
|
|
DEBCONF_APT_PROGRESS_DB_OVERRIDE="$DEBCONF_DB_OVERRIDE" |
| 625 |
|
|
export DEBCONF_APT_PROGRESS_DB_REPLACE DEBCONF_APT_PROGRESS_DB_OVERRIDE |
| 626 |
|
|
DEBCONF_DB_REPLACE=configdb |
| 627 |
|
|
DEBCONF_DB_OVERRIDE='Pipe{infd:none outfd:none}' |
| 628 |
|
|
export DEBCONF_DB_REPLACE DEBCONF_DB_OVERRIDE |
| 629 |
|
|
EOF |
| 630 |
cjwatson |
1901 |
} elsif ($start) { |
| 631 |
cjwatson |
1912 |
$from = 0 unless defined $from; |
| 632 |
|
|
$to = 100 unless defined $to; |
| 633 |
|
|
start_bar($from, $to); |
| 634 |
cjwatson |
1901 |
} elsif (defined $from) { |
| 635 |
|
|
$status = run_progress($from, $to, @ARGV); |
| 636 |
|
|
} elsif ($stop) { |
| 637 |
|
|
stop_bar(); |
| 638 |
|
|
} else { |
| 639 |
cjwatson |
1912 |
start_bar(0, 100); |
| 640 |
cjwatson |
2348 |
if (! $cancelled) { |
| 641 |
|
|
$status = run_progress(0, 100, @ARGV); |
| 642 |
|
|
stop_bar(); |
| 643 |
|
|
} |
| 644 |
cjwatson |
1901 |
} |
| 645 |
|
|
|
| 646 |
joeyh |
2240 |
if ($cancelled) { |
| 647 |
joeyh |
2245 |
# This is pure paranoia. What if the child was in the |
| 648 |
|
|
# middle of writing a debconf command out, only to be |
| 649 |
|
|
# interrupted with a truncated write? Let's send a no-op |
| 650 |
|
|
# command to finish it out just in case. |
| 651 |
|
|
Debconf::Client::ConfModule::get("debconf/priority"); |
| 652 |
|
|
|
| 653 |
joeyh |
2238 |
exit 30; |
| 654 |
|
|
} |
| 655 |
joeyh |
2239 |
elsif ($status == 30) { |
| 656 |
|
|
exit 3; |
| 657 |
|
|
} |
| 658 |
joeyh |
2238 |
else { |
| 659 |
|
|
exit $status; |
| 660 |
|
|
} |
| 661 |
cjwatson |
1901 |
|
| 662 |
|
|
=head1 AUTHORS |
| 663 |
|
|
|
| 664 |
|
|
Colin Watson <cjwatson@debian.org> |
| 665 |
|
|
|
| 666 |
|
|
Joey Hess <joeyh@debian.org> |
| 667 |
|
|
|
| 668 |
|
|
=cut |