ALCApt.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. # vim:set fileencoding=utf-8 et ts=4 sts=4 sw=4:
  2. #
  3. # apt-listchanges - Show changelog entries between the installed versions
  4. # of a set of packages and the versions contained in
  5. # corresponding .deb files
  6. #
  7. # Copyright (C) 2000-2006 Matt Zimmerman <mdz@debian.org>
  8. # Copyright (C) 2006 Pierre Habouzit <madcoder@debian.org>
  9. # Copyright (C) 2016 Robert Luberda <robert@debian.org>
  10. #
  11. # This program is free software; you can redistribute it and/or modify
  12. # it under the terms of the GNU General Public License as published by
  13. # the Free Software Foundation; either version 2 of the License, or
  14. # (at your option) any later version.
  15. #
  16. # This program is distributed in the hope that it will be useful,
  17. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. # GNU General Public License for more details.
  20. #
  21. # You should have received a copy of the GNU General Public
  22. # License along with this program; if not, write to the Free
  23. # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
  24. # MA 02111-1307 USA
  25. #
  26. import os
  27. import sys
  28. import ALCConfig
  29. import ALCLog
  30. from ALChacks import _
  31. def _parse_apt_bool(value):
  32. # Based on StringToBool() from apt-pkg/contrib/strutl.cc in apt source
  33. # and should return same result as StringToBool(value, false)
  34. return value.lower() in [ '1', 'yes', 'true', 'with', 'on', 'enable' ]
  35. def _parse_apt_int(value):
  36. # This function should match Configuration::FindI() from apt's
  37. # apt-pkg/contrib/configuration.cc, except for values like '1something'
  38. try:
  39. return int(value)
  40. except:
  41. return 0
  42. class AptPipelineError(Exception):
  43. pass
  44. class AptPipeline(object):
  45. def __init__(self, config):
  46. super().__init__()
  47. self._config = config
  48. def read(self):
  49. fd = self._open_apt_fd()
  50. if self._config.debug:
  51. ALCLog.debug(_("APT pipeline messages:"))
  52. self._read_version(fd)
  53. self._read_options(fd)
  54. debs = self._read_packages(fd)
  55. if self._config.debug:
  56. ALCLog.debug(_("Packages list:"))
  57. for d in debs:
  58. ALCLog.debug("\t%s" % d)
  59. ALCLog.debug("")
  60. return debs
  61. def _open_apt_fd(self):
  62. if not 'APT_HOOK_INFO_FD' in os.environ:
  63. raise AptPipelineError(_("APT_HOOK_INFO_FD environment variable is not defined\n"
  64. "(is Dpkg::Tools::Options::/usr/bin/apt-listchanges::InfoFD set to 20?)"))
  65. try:
  66. apt_hook_info_fd_val = int(os.environ['APT_HOOK_INFO_FD'])
  67. except Exception as ex:
  68. raise AptPipelineError(_("Invalid (non-numeric) value of APT_HOOK_INFO_FD"
  69. " environment variable")) from ex
  70. if self._config.debug:
  71. ALCLog.debug(_("Will read apt pipeline messages from file descriptor %d") % apt_hook_info_fd_val)
  72. if apt_hook_info_fd_val < 3:
  73. raise AptPipelineError(_("APT_HOOK_INFO_FD environment variable is incorrectly defined\n"
  74. "(Dpkg::Tools::Options::/usr/bin/apt-listchanges::InfoFD should be greater than 2)."))
  75. try:
  76. return os.fdopen(apt_hook_info_fd_val, 'rt')
  77. except Exception as ex:
  78. raise AptPipelineError(_("Cannot read from file descriptor %(fd)d: %(errmsg)s")
  79. % {'fd': apt_hook_info_fd_val, 'errmsg': str(ex) }) from ex
  80. def _read_version(self, fd):
  81. version = fd.readline().rstrip()
  82. if version != "VERSION 2":
  83. raise AptPipelineError(_("Wrong or missing VERSION from apt pipeline\n"
  84. "(is Dpkg::Tools::Options::/usr/bin/apt-listchanges::Version set to 2?)"))
  85. if self._config.debug:
  86. ALCLog.debug("\t%s" % version)
  87. def _read_options(self, fd):
  88. while True:
  89. line = fd.readline().rstrip()
  90. if self._config.debug:
  91. ALCLog.debug("\t%s" % line)
  92. if not line:
  93. return
  94. if (not self._config.ignore_apt_assume and
  95. line.startswith('APT::Get::Assume-Yes=') and
  96. _parse_apt_bool(line[len('APT::Get::Assume-Yes='):]) ):
  97. self._config.confirm = False
  98. # Set self._config.quiet as well to force non-interactive frontend
  99. self._config.quiet = max(1, self._config.quiet)
  100. elif line.startswith('quiet='):
  101. self._config.quiet = max(_parse_apt_int(line[len('quiet='):]), self._config.quiet)
  102. def _read_packages(self, fd):
  103. filenames = {}
  104. toconfig = []
  105. toremove = []
  106. hasupgrade = False
  107. for pkgline in fd.readlines():
  108. pkgline = pkgline.rstrip()
  109. if self._config.debug:
  110. ALCLog.debug("\t%s" % pkgline)
  111. if not pkgline:
  112. break
  113. (pkgname, oldversion, compare, newversion, filename) = pkgline.split(None, 4)
  114. if compare != '<': # ignore downgrades or re-installations
  115. continue
  116. if filename == '**REMOVE**' or filename == '**ERROR**':
  117. toremove.append(pkgname)
  118. continue
  119. # New installs (oldversion equal to '-') are not ignored to support
  120. # a case when changelog is moved from one package to a dependent
  121. # package built from the same source (see p7zip-full 15.09+dfsg-3).
  122. if oldversion != '-':
  123. hasupgrade = True
  124. if filename == '**CONFIGURE**':
  125. toconfig.append(pkgname)
  126. else:
  127. filenames[pkgname] = filename
  128. # Quit early if no package has been upgraded (e.g. only new installs or removals)
  129. if not hasupgrade:
  130. return []
  131. # Sort by configuration order. THIS IS IMPORTANT. Sometimes, a
  132. # situation exists where package X contains changelog.gz (upstream
  133. # changelog) and depends on package Y which contains
  134. # changelog.Debian.gz (Debian changelog). Until we have a more
  135. # reliable method for determining whether a package is Debian
  136. # native, this allows things to work, since Y will always be
  137. # configured first.
  138. # apt doesn't explicitly configure everything anymore, so sort
  139. # the things to be configured first, and then do everything else
  140. # in alphabetical order. Also, drop from the list everything
  141. # that's to be removed.
  142. for pkg in toremove:
  143. if pkg in filenames:
  144. del filenames[pkg]
  145. ordered_filenames = []
  146. for pkg in toconfig:
  147. if pkg in filenames:
  148. ordered_filenames.append(filenames[pkg])
  149. del filenames[pkg]
  150. ordered_filenames.extend(sorted(filenames.values()))
  151. return ordered_filenames