aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'portage_with_autodep/pym/portage/glsa.py')
-rw-r--r--portage_with_autodep/pym/portage/glsa.py309
1 files changed, 169 insertions, 140 deletions
diff --git a/portage_with_autodep/pym/portage/glsa.py b/portage_with_autodep/pym/portage/glsa.py
index 1857695..cac0f1a 100644
--- a/portage_with_autodep/pym/portage/glsa.py
+++ b/portage_with_autodep/pym/portage/glsa.py
@@ -1,7 +1,7 @@
# Copyright 2003-2012 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
-from __future__ import absolute_import
+from __future__ import absolute_import, unicode_literals
import io
import sys
@@ -9,8 +9,12 @@ try:
from urllib.request import urlopen as urllib_request_urlopen
except ImportError:
from urllib import urlopen as urllib_request_urlopen
+import codecs
import re
+import operator
import xml.dom.minidom
+from io import StringIO
+from functools import reduce
import portage
from portage import os
@@ -19,13 +23,13 @@ from portage import _unicode_decode
from portage import _unicode_encode
from portage.versions import pkgsplit, vercmp, best
from portage.util import grabfile
-from portage.const import CACHE_PATH
+from portage.const import PRIVATE_PATH
from portage.localization import _
from portage.dep import _slot_separator
# Note: the space for rgt and rlt is important !!
# FIXME: use slot deps instead, requires GLSA format versioning
-opMapping = {"le": "<=", "lt": "<", "eq": "=", "gt": ">", "ge": ">=",
+opMapping = {"le": "<=", "lt": "<", "eq": "=", "gt": ">", "ge": ">=",
"rge": ">=~", "rle": "<=~", "rgt": " >~", "rlt": " <~"}
NEWLINE_ESCAPE = "!;\\n" # some random string to mark newlines that should be preserved
SPACE_ESCAPE = "!;_" # some random string to mark spaces that should be preserved
@@ -39,22 +43,22 @@ def get_applied_glsas(settings):
@rtype: list
@return: list of glsa IDs
"""
- return grabfile(os.path.join(settings["EROOT"], CACHE_PATH, "glsa"))
+ return grabfile(os.path.join(settings["EROOT"], PRIVATE_PATH, "glsa_injected"))
# TODO: use the textwrap module instead
def wrap(text, width, caption=""):
"""
Wraps the given text at column I{width}, optionally indenting
- it so that no text is under I{caption}. It's possible to encode
+ it so that no text is under I{caption}. It's possible to encode
hard linebreaks in I{text} with L{NEWLINE_ESCAPE}.
-
+
@type text: String
@param text: the text to be wrapped
@type width: Integer
@param width: the column at which the text should be wrapped
@type caption: String
- @param caption: this string is inserted at the beginning of the
+ @param caption: this string is inserted at the beginning of the
return value and the paragraph is indented up to
C{len(caption)}.
@rtype: String
@@ -65,7 +69,7 @@ def wrap(text, width, caption=""):
text = text.replace(2*NEWLINE_ESCAPE, NEWLINE_ESCAPE+" "+NEWLINE_ESCAPE)
words = text.split()
indentLevel = len(caption)+1
-
+
for w in words:
if line != "" and line[-1] == "\n":
rValue += line
@@ -94,10 +98,10 @@ def get_glsa_list(myconfig):
Returns a list of all available GLSAs in the given repository
by comparing the filelist there with the pattern described in
the config.
-
+
@type myconfig: portage.config
@param myconfig: Portage settings instance
-
+
@rtype: List of Strings
@return: a list of GLSA IDs in this repository
"""
@@ -113,10 +117,10 @@ def get_glsa_list(myconfig):
dirlist = os.listdir(repository)
prefix = "glsa-"
suffix = ".xml"
-
+
for f in dirlist:
try:
- if f[:len(prefix)] == prefix:
+ if f[:len(prefix)] == prefix and f[-1*len(suffix):] == suffix:
rValue.append(f[len(prefix):-1*len(suffix)])
except IndexError:
pass
@@ -125,22 +129,20 @@ def get_glsa_list(myconfig):
def getListElements(listnode):
"""
Get all <li> elements for a given <ol> or <ul> node.
-
+
@type listnode: xml.dom.Node
@param listnode: <ul> or <ol> list to get the elements for
@rtype: List of Strings
@return: a list that contains the value of the <li> elements
"""
- rValue = []
if not listnode.nodeName in ["ul", "ol"]:
raise GlsaFormatException("Invalid function call: listnode is not <ul> or <ol>")
- for li in listnode.childNodes:
- if li.nodeType != xml.dom.Node.ELEMENT_NODE:
- continue
- rValue.append(getText(li, format="strip"))
+ rValue = [getText(li, format="strip") \
+ for li in listnode.childNodes \
+ if li.nodeType == xml.dom.Node.ELEMENT_NODE]
return rValue
-def getText(node, format):
+def getText(node, format, textfd = None):
"""
This is the main parser function. It takes a node and traverses
recursive over the subnodes, getting the text of each (and the
@@ -148,7 +150,7 @@ def getText(node, format):
parameter the text might be formatted by adding/removing newlines,
tabs and spaces. This function is only useful for the GLSA DTD,
it's not applicable for other DTDs.
-
+
@type node: xml.dom.Node
@param node: the root node to start with the parsing
@type format: String
@@ -158,45 +160,54 @@ def getText(node, format):
replaces multiple spaces with one space.
I{xml} does some more formatting, depending on the
type of the encountered nodes.
+ @type textfd: writable file-like object
+ @param textfd: the file-like object to write the output to
@rtype: String
@return: the (formatted) content of the node and its subnodes
+ except if textfd was not none
"""
- rValue = ""
+ if not textfd:
+ textfd = StringIO()
+ returnNone = False
+ else:
+ returnNone = True
if format in ["strip", "keep"]:
if node.nodeName in ["uri", "mail"]:
- rValue += node.childNodes[0].data+": "+node.getAttribute("link")
+ textfd.write(node.childNodes[0].data+": "+node.getAttribute("link"))
else:
for subnode in node.childNodes:
if subnode.nodeName == "#text":
- rValue += subnode.data
+ textfd.write(subnode.data)
else:
- rValue += getText(subnode, format)
- else:
+ getText(subnode, format, textfd)
+ else: # format = "xml"
for subnode in node.childNodes:
if subnode.nodeName == "p":
for p_subnode in subnode.childNodes:
if p_subnode.nodeName == "#text":
- rValue += p_subnode.data.strip()
+ textfd.write(p_subnode.data.strip())
elif p_subnode.nodeName in ["uri", "mail"]:
- rValue += p_subnode.childNodes[0].data
- rValue += " ( "+p_subnode.getAttribute("link")+" )"
- rValue += NEWLINE_ESCAPE
+ textfd.write(p_subnode.childNodes[0].data)
+ textfd.write(" ( "+p_subnode.getAttribute("link")+" )")
+ textfd.write(NEWLINE_ESCAPE)
elif subnode.nodeName == "ul":
for li in getListElements(subnode):
- rValue += "-"+SPACE_ESCAPE+li+NEWLINE_ESCAPE+" "
+ textfd.write("-"+SPACE_ESCAPE+li+NEWLINE_ESCAPE+" ")
elif subnode.nodeName == "ol":
i = 0
for li in getListElements(subnode):
i = i+1
- rValue += str(i)+"."+SPACE_ESCAPE+li+NEWLINE_ESCAPE+" "
+ textfd.write(str(i)+"."+SPACE_ESCAPE+li+NEWLINE_ESCAPE+" ")
elif subnode.nodeName == "code":
- rValue += getText(subnode, format="keep").replace("\n", NEWLINE_ESCAPE)
- if rValue[-1*len(NEWLINE_ESCAPE):] != NEWLINE_ESCAPE:
- rValue += NEWLINE_ESCAPE
+ textfd.write(getText(subnode, format="keep").lstrip().replace("\n", NEWLINE_ESCAPE))
+ textfd.write(NEWLINE_ESCAPE)
elif subnode.nodeName == "#text":
- rValue += subnode.data
+ textfd.write(subnode.data)
else:
raise GlsaFormatException(_("Invalid Tag found: "), subnode.nodeName)
+ if returnNone:
+ return None
+ rValue = textfd.getvalue()
if format == "strip":
rValue = rValue.strip(" \n\t")
rValue = re.sub("[\s]{2,}", " ", rValue)
@@ -206,7 +217,7 @@ def getMultiTagsText(rootnode, tagname, format):
"""
Returns a list with the text of all subnodes of type I{tagname}
under I{rootnode} (which itself is not parsed) using the given I{format}.
-
+
@type rootnode: xml.dom.Node
@param rootnode: the node to search for I{tagname}
@type tagname: String
@@ -216,16 +227,15 @@ def getMultiTagsText(rootnode, tagname, format):
@rtype: List of Strings
@return: a list containing the text of all I{tagname} childnodes
"""
- rValue = []
- for e in rootnode.getElementsByTagName(tagname):
- rValue.append(getText(e, format))
+ rValue = [getText(e, format) \
+ for e in rootnode.getElementsByTagName(tagname)]
return rValue
def makeAtom(pkgname, versionNode):
"""
- creates from the given package name and information in the
+ creates from the given package name and information in the
I{versionNode} a (syntactical) valid portage atom.
-
+
@type pkgname: String
@param pkgname: the name of the package for this atom
@type versionNode: xml.dom.Node
@@ -248,9 +258,9 @@ def makeAtom(pkgname, versionNode):
def makeVersion(versionNode):
"""
- creates from the information in the I{versionNode} a
+ creates from the information in the I{versionNode} a
version string (format <op><version>).
-
+
@type versionNode: xml.dom.Node
@param versionNode: a <vulnerable> or <unaffected> Node that
contains the version information for this atom
@@ -270,17 +280,17 @@ def makeVersion(versionNode):
def match(atom, dbapi, match_type="default"):
"""
- wrapper that calls revisionMatch() or portage.dbapi.dbapi.match() depending on
+ wrapper that calls revisionMatch() or portage.dbapi.dbapi.match() depending on
the given atom.
-
+
@type atom: string
@param atom: a <~ or >~ atom or a normal portage atom that contains the atom to match against
@type dbapi: portage.dbapi.dbapi
@param dbapi: one of the portage databases to use as information source
@type match_type: string
- @param match_type: if != "default" passed as first argument to dbapi.xmatch
+ @param match_type: if != "default" passed as first argument to dbapi.xmatch
to apply the wanted visibility filters
-
+
@rtype: list of strings
@return: a list with the matching versions
"""
@@ -296,15 +306,15 @@ def revisionMatch(revisionAtom, dbapi, match_type="default"):
handler for the special >~, >=~, <=~ and <~ atoms that are supposed to behave
as > and < except that they are limited to the same version, the range only
applies to the revision part.
-
+
@type revisionAtom: string
@param revisionAtom: a <~ or >~ atom that contains the atom to match against
@type dbapi: portage.dbapi.dbapi
@param dbapi: one of the portage databases to use as information source
@type match_type: string
- @param match_type: if != "default" passed as first argument to portdb.xmatch
+ @param match_type: if != "default" passed as first argument to portdb.xmatch
to apply the wanted visibility filters
-
+
@rtype: list of strings
@return: a list with the matching versions
"""
@@ -325,18 +335,19 @@ def revisionMatch(revisionAtom, dbapi, match_type="default"):
if eval(r1+" "+revisionAtom[0:2]+" "+r2):
rValue.append(v)
return rValue
-
+
def getMinUpgrade(vulnerableList, unaffectedList, portdbapi, vardbapi, minimize=True):
"""
Checks if the systemstate is matching an atom in
I{vulnerableList} and returns string describing
- the lowest version for the package that matches an atom in
+ the lowest version for the package that matches an atom in
I{unaffectedList} and is greater than the currently installed
- version or None if the system is not affected. Both
- I{vulnerableList} and I{unaffectedList} should have the
+ version. It will return an empty list if the system is affected,
+ and no upgrade is possible or None if the system is not affected.
+ Both I{vulnerableList} and I{unaffectedList} should have the
same base package.
-
+
@type vulnerableList: List of Strings
@param vulnerableList: atoms matching vulnerable package versions
@type unaffectedList: List of Strings
@@ -347,46 +358,51 @@ def getMinUpgrade(vulnerableList, unaffectedList, portdbapi, vardbapi, minimize=
@param vardbapi: Installed package repository
@type minimize: Boolean
@param minimize: True for a least-change upgrade, False for emerge-like algorithm
-
+
@rtype: String | None
@return: the lowest unaffected version that is greater than
the installed version.
- """
- rValue = None
- v_installed = []
- u_installed = []
- for v in vulnerableList:
- v_installed += match(v, vardbapi)
+ """
+ rValue = ""
+ v_installed = reduce(operator.add, [match(v, vardbapi) for v in vulnerableList], [])
+ u_installed = reduce(operator.add, [match(u, vardbapi) for u in unaffectedList], [])
- for u in unaffectedList:
- u_installed += match(u, vardbapi)
-
- install_unaffected = True
- for i in v_installed:
- if i not in u_installed:
- install_unaffected = False
+ # remove all unaffected atoms from vulnerable list
+ v_installed = list(set(v_installed).difference(set(u_installed)))
- if install_unaffected:
- return rValue
-
+ if not v_installed:
+ return None
+
+ # this tuple holds all vulnerable atoms, and the related upgrade atom
+ vuln_update = []
+ avail_updates = set()
for u in unaffectedList:
- mylist = match(u, portdbapi, match_type="match-all")
- for c in mylist:
- i = best(v_installed)
- if vercmp(c.version, i.version) > 0 \
- and (rValue == None \
- or not match("="+rValue, portdbapi) \
- or (minimize ^ (vercmp(c.version, rValue.version) > 0)) \
- and match("="+c, portdbapi)) \
- and portdbapi.aux_get(c, ["SLOT"]) == vardbapi.aux_get(best(v_installed), ["SLOT"]):
- rValue = c
- return rValue
+ # TODO: This had match_type="match-all" before. I don't think it should
+ # since we disregarded masked items later anyway (match(=rValue, "porttree"))
+ avail_updates.update(match(u, portdbapi))
+ # if an atom is already installed, we should not consider it for upgrades
+ avail_updates.difference_update(u_installed)
+
+ for vuln in v_installed:
+ update = ""
+ for c in avail_updates:
+ c_pv = portage.catpkgsplit(c)
+ if vercmp(c.version, vuln.version) > 0 \
+ and (update == "" \
+ or (minimize ^ (vercmp(c.version, update.version) > 0))) \
+ and portdbapi._pkg_str(c, None).slot == vardbapi._pkg_str(vuln, None).slot:
+ update = c_pv[0]+"/"+c_pv[1]+"-"+c_pv[2]
+ if c_pv[3] != "r0": # we don't like -r0 for display
+ update += "-"+c_pv[3]
+ vuln_update.append([vuln, update])
+
+ return vuln_update
def format_date(datestr):
"""
Takes a date (announced, revised) date from a GLSA and formats
it as readable text (i.e. "January 1, 2008").
-
+
@type date: String
@param date: the date string to reformat
@rtype: String
@@ -396,16 +412,16 @@ def format_date(datestr):
splitdate = datestr.split("-", 2)
if len(splitdate) != 3:
return datestr
-
+
# This cannot raise an error as we use () instead of []
splitdate = (int(x) for x in splitdate)
-
+
from datetime import date
try:
d = date(*splitdate)
except ValueError:
return datestr
-
+
# TODO We could format to local date format '%x' here?
return _unicode_decode(d.strftime("%B %d, %Y"),
encoding=_encodings['content'], errors='replace')
@@ -417,7 +433,7 @@ class GlsaTypeException(Exception):
class GlsaFormatException(Exception):
pass
-
+
class GlsaArgumentException(Exception):
pass
@@ -429,9 +445,9 @@ class Glsa:
"""
def __init__(self, myid, myconfig, vardbapi, portdbapi):
"""
- Simple constructor to set the ID, store the config and gets the
+ Simple constructor to set the ID, store the config and gets the
XML data by calling C{self.read()}.
-
+
@type myid: String
@param myid: String describing the id for the GLSA object (standard
GLSAs have an ID of the form YYYYMM-nn) or an existing
@@ -461,7 +477,7 @@ class Glsa:
"""
Here we build the filename from the config and the ID and pass
it to urllib to fetch it from the filesystem or a remote server.
-
+
@rtype: None
@return: None
"""
@@ -473,15 +489,21 @@ class Glsa:
myurl = "file://"+self.nr
else:
myurl = repository + "glsa-%s.xml" % str(self.nr)
- self.parse(urllib_request_urlopen(myurl))
+
+ f = urllib_request_urlopen(myurl)
+ try:
+ self.parse(f)
+ finally:
+ f.close()
+
return None
def parse(self, myfile):
"""
- This method parses the XML file and sets up the internal data
+ This method parses the XML file and sets up the internal data
structures by calling the different helper functions in this
module.
-
+
@type myfile: String
@param myfile: Filename to grab the XML data from
@rtype: None
@@ -504,27 +526,27 @@ class Glsa:
self.title = getText(myroot.getElementsByTagName("title")[0], format="strip")
self.synopsis = getText(myroot.getElementsByTagName("synopsis")[0], format="strip")
self.announced = format_date(getText(myroot.getElementsByTagName("announced")[0], format="strip"))
-
- count = 1
+
# Support both formats of revised:
# <revised>December 30, 2007: 02</revised>
# <revised count="2">2007-12-30</revised>
revisedEl = myroot.getElementsByTagName("revised")[0]
self.revised = getText(revisedEl, format="strip")
- if ((sys.hexversion >= 0x3000000 and "count" in revisedEl.attributes) or
- (sys.hexversion < 0x3000000 and revisedEl.attributes.has_key("count"))):
- count = revisedEl.getAttribute("count")
- elif (self.revised.find(":") >= 0):
- (self.revised, count) = self.revised.split(":")
-
+ count = revisedEl.attributes.get("count")
+ if count is None:
+ if self.revised.find(":") >= 0:
+ (self.revised, count) = self.revised.split(":")
+ else:
+ count = 1
+
self.revised = format_date(self.revised)
-
+
try:
self.count = int(count)
except ValueError:
# TODO should this raise a GlsaFormatException?
self.count = 1
-
+
# now the optional and 0-n toplevel, #PCDATA tags and references
try:
self.access = getText(myroot.getElementsByTagName("access")[0], format="strip")
@@ -532,7 +554,7 @@ class Glsa:
self.access = ""
self.bugs = getMultiTagsText(myroot, "bug", format="strip")
self.references = getMultiTagsText(myroot.getElementsByTagName("references")[0], "uri", format="keep")
-
+
# and now the formatted text elements
self.description = getText(myroot.getElementsByTagName("description")[0], format="xml")
self.workaround = getText(myroot.getElementsByTagName("workaround")[0], format="xml")
@@ -542,7 +564,7 @@ class Glsa:
try:
self.background = getText(myroot.getElementsByTagName("background")[0], format="xml")
except IndexError:
- self.background = ""
+ self.background = ""
# finally the interesting tags (product, affected, package)
self.glsatype = myroot.getElementsByTagName("product")[0].getAttribute("type")
@@ -572,16 +594,18 @@ class Glsa:
self.services = self.affected.getElementsByTagName("service")
return None
- def dump(self, outstream=sys.stdout):
+ def dump(self, outstream=sys.stdout, encoding="utf-8"):
"""
- Dumps a plaintext representation of this GLSA to I{outfile} or
+ Dumps a plaintext representation of this GLSA to I{outfile} or
B{stdout} if it is ommitted. You can specify an alternate
- I{encoding} if needed (default is latin1).
-
+ I{encoding} if needed (default is utf-8).
+
@type outstream: File
@param outfile: Stream that should be used for writing
(defaults to sys.stdout)
"""
+ outstream = getattr(outstream, "buffer", outstream)
+ outstream = codecs.getwriter(encoding)(outstream)
width = 76
outstream.write(("GLSA %s: \n%s" % (self.nr, self.title)).center(width)+"\n")
outstream.write((width*"=")+"\n")
@@ -606,30 +630,24 @@ class Glsa:
pass
if len(self.bugs) > 0:
outstream.write(_("\nRelated bugs: "))
- for i in range(0, len(self.bugs)):
- outstream.write(self.bugs[i])
- if i < len(self.bugs)-1:
- outstream.write(", ")
- else:
- outstream.write("\n")
+ outstream.write(", ".join(self.bugs))
+ outstream.write("\n")
if self.background:
outstream.write("\n"+wrap(self.background, width, caption=_("Background: ")))
outstream.write("\n"+wrap(self.description, width, caption=_("Description: ")))
outstream.write("\n"+wrap(self.impact_text, width, caption=_("Impact: ")))
outstream.write("\n"+wrap(self.workaround, width, caption=_("Workaround: ")))
outstream.write("\n"+wrap(self.resolution, width, caption=_("Resolution: ")))
- myreferences = ""
- for r in self.references:
- myreferences += (r.replace(" ", SPACE_ESCAPE)+NEWLINE_ESCAPE+" ")
+ myreferences = " ".join(r.replace(" ", SPACE_ESCAPE)+NEWLINE_ESCAPE for r in self.references)
outstream.write("\n"+wrap(myreferences, width, caption=_("References: ")))
outstream.write("\n")
-
+
def isVulnerable(self):
"""
Tests if the system is affected by this GLSA by checking if any
vulnerable package versions are installed. Also checks for affected
architectures.
-
+
@rtype: Boolean
@return: True if the system is affected, False if not
"""
@@ -641,56 +659,67 @@ class Glsa:
for v in path["vul_atoms"]:
rValue = rValue \
or (len(match(v, self.vardbapi)) > 0 \
- and getMinUpgrade(path["vul_atoms"], path["unaff_atoms"], \
+ and None != getMinUpgrade(path["vul_atoms"], path["unaff_atoms"], \
self.portdbapi, self.vardbapi))
return rValue
-
- def isApplied(self):
+
+ def isInjected(self):
"""
- Looks if the GLSA IDis in the GLSA checkfile to check if this
- GLSA was already applied.
-
+ Looks if the GLSA ID is in the GLSA checkfile to check if this
+ GLSA should be marked as applied.
+
@rtype: Boolean
- @return: True if the GLSA was applied, False if not
+ @returns: True if the GLSA is in the inject file, False if not
"""
+ if not os.access(os.path.join(self.config["EROOT"],
+ PRIVATE_PATH, "glsa_injected"), os.R_OK):
+ return False
return (self.nr in get_applied_glsas(self.config))
def inject(self):
"""
Puts the ID of this GLSA into the GLSA checkfile, so it won't
- show up on future checks. Should be called after a GLSA is
+ show up on future checks. Should be called after a GLSA is
applied or on explicit user request.
@rtype: None
@return: None
"""
- if not self.isApplied():
+ if not self.isInjected():
checkfile = io.open(
_unicode_encode(os.path.join(self.config["EROOT"],
- CACHE_PATH, "glsa"),
- encoding=_encodings['fs'], errors='strict'),
+ PRIVATE_PATH, "glsa_injected"),
+ encoding=_encodings['fs'], errors='strict'),
mode='a+', encoding=_encodings['content'], errors='strict')
checkfile.write(_unicode_decode(self.nr + "\n"))
checkfile.close()
return None
-
+
def getMergeList(self, least_change=True):
"""
Returns the list of package-versions that have to be merged to
- apply this GLSA properly. The versions are as low as possible
+ apply this GLSA properly. The versions are as low as possible
while avoiding downgrades (see L{getMinUpgrade}).
-
+
@type least_change: Boolean
@param least_change: True if the smallest possible upgrade should be selected,
False for an emerge-like algorithm
@rtype: List of Strings
@return: list of package-versions that have to be merged
"""
- rValue = []
- for pkg in self.packages:
+ return list(set(update for (vuln, update) in self.getAffectionTable(least_change) if update))
+
+ def getAffectionTable(self, least_change=True):
+ """
+ Will initialize the self.systemAffection list of
+ atoms installed on the system that are affected
+ by this GLSA, and the atoms that are minimal upgrades.
+ """
+ systemAffection = []
+ for pkg in self.packages.keys():
for path in self.packages[pkg]:
- update = getMinUpgrade(path["vul_atoms"], path["unaff_atoms"], \
+ update = getMinUpgrade(path["vul_atoms"], path["unaff_atoms"],
self.portdbapi, self.vardbapi, minimize=least_change)
if update:
- rValue.append(update)
- return rValue
+ systemAffection.extend(update)
+ return systemAffection