First time Ubuntu’s software updater got stuck after multiple reboots and retries:
A snippet of output of ps -ef --forest
root 3643 1 4 13:43 ? 00:01:27 /usr/bin/python3 /usr/sbin/aptd
root 643225 3643 0 14:12 pts/2 00:00:00 \_ /usr/bin/python3 /usr/sbin/aptd
root 644605 643225 0 14:12 pts/3 00:00:00 \_ /usr/bin/dpkg --status-fd 60 --configure --pending
root 644606 644605 3 14:12 pts/3 00:00:01 \_ /usr/bin/perl -w /usr/share/debconf/frontend /var/lib/dpkg/info/grub-efi-amd64-signed.postinst configure 1.192+2.06-2ubuntu16
root 644624 644606 0 14:12 pts/3 00:00:00 \_ /bin/sh /var/lib/dpkg/info/grub-efi-amd64-signed.postinst configure 1.192+2.06-2ubuntu16
root 644630 644624 1 14:12 pts/3 00:00:00 \_ /bin/bash /usr/lib/grub/grub-multi-install --target=x86_64-efi
root 745852 644630 0 14:12 pts/3 00:00:00 \_ /bin/bash /usr/lib/grub/grub-multi-install --target=x86_64-efi
root 745853 745852 0 14:12 pts/3 00:00:00 \_ /bin/bash /usr/lib/grub/grub-multi-install --target=x86_64-efi
root 745854 745852 0 14:12 pts/3 00:00:00 \_ sort -t: -k2 -u
It’s just on an endless stream of “Use of uninitialized value $_[1] in join or string at /usr/share/perl5/Debconf/DbDriver/Stack.pm line 111, line
(insert_integer_here)”.
So I went to check out that file referenced by the error message…
Stack.pm
#!/usr/bin/perl -w
# This file was preprocessed, do not edit!
package Debconf::DbDriver::Stack;
use strict;
use Debconf::Log qw{:all};
use Debconf::Iterator;
use base 'Debconf::DbDriver::Copy';
use fields qw(stack stack_change_errors);
sub init {
my $this=shift;
if (! ref $this->{stack}) {
my @stack;
foreach my $name (split(/\s*,\s/, $this->{stack})) {
my $driver=$this->driver($name);
unless (defined $driver) {
$this->error("could not find a db named \"$name\" to use in the stack (it should be defined before the stack in the config file)");
next;
}
push @stack, $driver;
}
$this->{stack}=[@stack];
}
$this->error("no stack set") if ! ref $this->{stack};
$this->error("stack is empty") if ! @{$this->{stack}};
}
sub iterator {
my $this=shift;
my %seen;
my @iterators = map { $_->iterator } @{$this->{stack}};
my $i = pop @iterators;
my $iterator=Debconf::Iterator->new(callback => sub {
for (;;) {
while (my $ret = $i->iterate) {
next if $seen{$ret};
$seen{$ret}=1;
return $ret;
}
$i = pop @iterators;
return undef unless defined $i;
}
});
}
sub shutdown {
my $this=shift;
my $ret=1;
foreach my $driver (@{$this->{stack}}) {
$ret=undef if not defined $driver->shutdown(@_);
}
if ($this->{stack_change_errors}) {
$this->error("unable to save changes to: ".
join(" ", @{$this->{stack_change_errors}}));
$ret=undef;
}
return $ret;
}
sub exists {
my $this=shift;
foreach my $driver (@{$this->{stack}}) {
return 1 if $driver->exists(@_);
}
return 0;
}
sub _query {
my $this=shift;
my $command=shift;
shift; # this again
debug "db $this->{name}" => "trying to $command(@_) ..";
foreach my $driver (@{$this->{stack}}) {
if (wantarray) {
my @ret=$driver->$command(@_);
debug "db $this->{name}" => "$command done by $driver->{name}" if @ret;
return @ret if @ret;
}
else {
my $ret=$driver->$command(@_);
debug "db $this->{name}" => "$command done by $driver->{name}" if defined $ret;
return $ret if defined $ret;
}
}
return; # failure
}
sub _change {
my $this=shift;
my $command=shift;
shift; # this again
my $item=shift;
debug "db $this->{name}" => "trying to $command($item @_) ..";
foreach my $driver (@{$this->{stack}}) {
if ($driver->exists($item)) {
last if $driver->{readonly}; # nope, hit a readonly one
debug "db $this->{name}" => "passing to $driver->{name} ..";
return $driver->$command($item, @_);
}
}
my $src=0;
foreach my $driver (@{$this->{stack}}) {
if ($driver->exists($item)) {
my $ret=$this->_nochange($driver, $command, $item, @_);
if (defined $ret) {
debug "db $this->{name}" => "skipped $command($item) as it would have no effect";
return $ret;
}
$src=$driver;
last
}
}
my $writer;
foreach my $driver (@{$this->{stack}}) {
if ($driver == $src) {
push @{$this->{stack_change_errors}}, $item;
return;
}
if (! $driver->{readonly}) {
if ($command eq 'addowner') {
if ($driver->accept($item, $_[1])) {
$writer=$driver;
last;
}
}
elsif ($driver->accept($item)) {
$writer=$driver;
last;
}
}
}
unless ($writer) {
debug "db $this->{name}" => "FAILED $command";
return;
}
if ($src) {
$this->copy($item, $src, $writer);
}
debug "db $this->{name}" => "passing to $writer->{name} ..";
return $writer->$command($item, @_);
}
sub _nochange {
my $this=shift;
my $driver=shift;
my $command=shift;
my $item=shift;
if ($command eq 'addowner') {
my $value=shift;
foreach my $owner ($driver->owners($item)) {
return $value if $owner eq $value;
}
return;
}
elsif ($command eq 'removeowner') {
my $value=shift;
foreach my $owner ($driver->owners($item)) {
return if $owner eq $value;
}
return $value; # no change
}
elsif ($command eq 'removefield') {
my $value=shift;
foreach my $field ($driver->fields($item)) {
return if $field eq $value;
}
return $value; # no change
}
my @list;
my $get;
if ($command eq 'setfield') {
@list=$driver->fields($item);
$get='getfield';
}
elsif ($command eq 'setflag') {
@list=$driver->flags($item);
$get='getflag';
}
elsif ($command eq 'setvariable') {
@list=$driver->variables($item);
$get='getvariable';
}
else {
$this->error("internal error; bad command: $command");
}
my $thing=shift;
my $value=shift;
my $currentvalue=$driver->$get($item, $thing);
my $exists=0;
foreach my $i (@list) {
$exists=1, last if $thing eq $i;
}
return $currentvalue unless $exists;
return $currentvalue if $currentvalue eq $value;
return undef;
}
sub addowner { $_[0]->_change('addowner', @_) }
sub removeowner { $_[0]->_change('removeowner', @_) }
sub owners { $_[0]->_query('owners', @_) }
sub getfield { $_[0]->_query('getfield', @_) }
sub setfield { $_[0]->_change('setfield', @_) }
sub removefield { $_[0]->_change('removefield', @_) }
sub fields { $_[0]->_query('fields', @_) }
sub getflag { $_[0]->_query('getflag', @_) }
sub setflag { $_[0]->_change('setflag', @_) }
sub flags { $_[0]->_query('flags', @_) }
sub getvariable { $_[0]->_query('getvariable', @_) }
sub setvariable { $_[0]->_change('setvariable', @_) }
sub variables { $_[0]->_query('variables', @_) }
1
I have neither read nor written a line of Perl prior to this day in my life. But I can tell that line of code puts me at a starting point that is deep in the rabbit hole.
To find my way up, I’ve been trying to recursively determine all the dependencies on that code.
- The problem line referenced in the error message is a debug statement at the top of the
_change
subroutine within the package Debconf::DbDriver::Stack
. At the very bottom of the same file are 12 subroutines that depend on sub _change
. (Their names match the regular expression (add|remove)(field|flag|owner|variable)
.)
-
package Debconf::Db
depends on the stuff in package Debconf::DbDriver
. It has a sub makedriver
, which I believe is responsible for initializing an instance of one of the packages under the Debconf::DbDriver
namespace.
- There are three packages that call
sub makedriver
, but only package Debconf::Config
calls it with driver => "Stack"
. But no matter, where is the input data that feeds the subroutines? There is a curious line in package Debconf::Db
that reads die "driver type not specified (perhaps you need to re-read debconf.conf(5))"
.
- Reading debconf.conf(5) takes me to the file
/etc/debconf.conf
which references “Stack” exactly once:Name: configdb
Driver: Stack
Stack: config, passwords
- The “Stack” driver is supposed to act as a collection of of sorts. Looking for the definition of “config” and “passwords” leads me to the files
/var/cache/debconf/config.dat
and /var/cache/debconf/passwords.dat
respectively.
Currently trying to inspect the state of the variables at the point of the error message to determine the offending entry…
EDIT: Ah… weird. After killing perl
a few times, the Software Updater says my system is “up to date.” Hmm… suspicious.
I have this feeling that Ubuntu just swept a problem under the rug.