Ubuntu 10.10 package download of large files can fail with OverflowError: signed integer is less than minimum

I was doing a distribution upgrade on a 10.10 system to 11.04 via do-release-upgrade. The system has games installed, so the total files to download are over 2.5 Gigabytes e.g. games like Nexuiz has a data file that is about 273 Megabytes. The Internet access is low speed broadband (about 70 KBytes per second download maximum) with other machines using this ADSL line so that’s about 10 hours for the whole release.

The  do-release-upgrade downloads can fail on these larger files on congested lines at and if you look at the log file, i.e. tail /var/log/dist-upgrade/main.log ,  it will say something like,

  File "/tmp/update-manager-HXahEI/DistUpgradeViewText.py", line 42, in pulse
    apt.progress.text.AcquireProgress.pulse(self, owner)

  File "/usr/lib/python2.6/dist-packages/apt/progress/text.py", line 164, in pulse
    apt_pkg.time_to_str(eta))

OverflowError: signed integer is less than minimum

If you look at that code in /usr/lib/python2.6/dist-packages/apt/progress/text.py (it’s Python) around line 161 onwards and think about what can happen on …

            eta = int(float(self.total_bytes - self.current_bytes) /
                      self.current_cps)

There are a number of issues here with this. That eta value isn’t checked before it is passed to the apt_pkg.time_to_str() and that’s not good because,

1) I think the self.current_cps can be a float less than 1 and as the size of an int on this system (64bit Athlon with Python 2.6.6 ) is,

>>> import sys
>>> print sys.maxint
9223372036854775807

then the eta could be quite large e.g. if that maximum value was seconds and converted to years would be just under 300 billion years.

2) But the actual error is “signed integer is less than minimum” not an overflow of a maximum so this bug seems to be about some magic number. Now if you enter in the python program,

>>> import apt_pkg
>>> print apt_pkg.time_to_str(-1)
213503982334601d 7h 0min 15s

and if you enter in other odd numbers then you can trigger the “signed integer is less than minimum” e.g. see these examples,

>>> apt_pkg.TimeToStr(-2147483648)
'213503982309746d 3h 46min 8s'
>>> apt_pkg.TimeToStr(-2147483649)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: signed integer is less than minimum

so it clearly can have values over 24855 days i.e. apt_pkg.time_to_str(2147483647) so looks like there are some odd boundaries that cause OverflowError: signed integer is less than minimum as well as a OverflowError: signed integer is greater than maximum. I ran a loop that incremented an integer by 1 and gave up and Control-C it when it went through 836477585 which is 9681d 10h 53min 5s so if there are any boundary conditions it’s not obvious. I suspect that the garbage is with what is fed to the TimeToStr() and not a flaw in TimeToStr(). I suspect that if the download process resets itself then the file size self.total_bytes is temporarily nonsensical e.g. 0 whilst the program resets the download.

So that code section needs sanity limits on the eta because looks like we can’t trust any of self.current_cps to be reasonable or self.total_bytes to be accurate but I think self.current_bytes may always be fine, e.g. I changed line 164 end =… to have some range checking,

            if eta < 0:
                end = " %sB/s ~%s" % (apt_pkg.size_to_str(self.current_cps),apt_pkg.time_to_str(0))
            elif eta > (30 * 24 *60 * 60):
                end = " %sB/s >%s" % (apt_pkg.size_to_str(self.current_cps),apt_pkg.time_to_str(30 * 24 *60 * 60))
            else:
                end = " %sB/s %s" % (apt_pkg.size_to_str(self.current_cps),
                                 apt_pkg.time_to_str(eta))

where the 30*24*60*60 means 30 days but there are other ways of doing this e.g. check  self.total_bytes is greater or equal to self.current_bytes or limit eta to a range and then keep existing calculation.

Once you edit that file you can simply restart the do-release-upgrade console and it will use your new code on the fly.

The bug is in other distributions e.g. see this bug report https://bugs.launchpad.net/ubuntu/+source/update-manager/+bug/884625 but not fixed. The problem is that the apt_pkg.time_to_str(), which is actually apt_pkg.TimeToStr() is probably being passed nonsensical values for self.total_bytes due to file downloads being reset for large and/or poor circuits. The apt_pkg.TimeToStr() should also reasonably handle negative times and not display nonsense but that’s another problem.