Merge rev 113 from djanderson's genscripts repo
authorPaul Varner <fuzzyray@gentoo.org>
Tue, 8 Dec 2009 21:53:45 +0000 (21:53 +0000)
committerZac Medico <zmedico@gentoo.org>
Sat, 19 Jan 2013 04:56:46 +0000 (20:56 -0800)
svn path=/trunk/gentoolkit/; revision=703

http://git.overlays.gentoo.org/gitweb/?p=proj/gentoolkit.git;a=commit;h=acdf616efa73b77936963eaa8b5c715db97646d2

pym/portage/glsa.py

index af6e714379e56749a20850a62b614633c0b70f9b..155d3e5436509d53c0a297384b5adc271d118b9b 100644 (file)
@@ -28,7 +28,7 @@ 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
@@ -49,15 +49,15 @@ def get_applied_glsas(settings):
 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
@@ -68,7 +68,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
@@ -97,10 +97,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
        """
@@ -116,7 +116,7 @@ def get_glsa_list(myconfig):
        dirlist = os.listdir(repository)
        prefix = "glsa-"
        suffix = ".xml"
-       
+
        for f in dirlist:
                try:
                        if f[:len(prefix)] == prefix and f[-1*len(suffix):] == suffix:
@@ -128,7 +128,7 @@ 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
@@ -149,7 +149,7 @@ def getText(node, format, textfd = None):
        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
@@ -216,7 +216,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
@@ -232,9 +232,9 @@ def getMultiTagsText(rootnode, tagname, format):
 
 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
@@ -257,9 +257,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
@@ -279,17 +279,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
        """
@@ -305,15 +305,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
        """
@@ -334,22 +334,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 state of installed packages matches an atom in
-       I{vulnerableList} and returns an update path.
-
-        Return value is:
-         * None if the system is not affected
-         * a list of tuples (a,b) where
-                  a  is a cpv describing an installed vulnerable atom
-                  b  is a cpv describing an uninstalled unaffected atom
-                       in the same slot as a
-                     OR the empty string ("") which means no upgrade
-                       is possible
-       
+       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
+       I{unaffectedList} and is greater than the currently installed
+       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
@@ -360,10 +357,12 @@ 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:         List | None
-       @return:        None if unaffected or a list of (vuln, upgrade) atoms.
-       """
+
+       @rtype:         String | None
+       @return:        the lowest unaffected version that is greater than
+                               the installed version.
+       """
+       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], [])
 
@@ -385,17 +384,12 @@ def getMinUpgrade(vulnerableList, unaffectedList, portdbapi, vardbapi, minimize=
 
        for vuln in v_installed:
                update = ""
-               # find the best update path for the vuln atom
                for c in avail_updates:
                        c_pv = portage.catpkgsplit(c)
-                       if vercmp(c.version, vuln.version) <= 0:
-                               # c is less or equal than vuln
-                               continue
-                       if portdbapi._pkg_str(c, None).slot != \
-                          vardbapi._pkg_str(vuln, None).slot:
-                               # upgrade to a different slot
-                               continue
-                       if update == "" or (minimize ^ (vercmp(c.version, update.version) > 0)):
+                       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]
@@ -407,7 +401,7 @@ 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
@@ -417,16 +411,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')
@@ -438,7 +432,7 @@ class GlsaTypeException(Exception):
 
 class GlsaFormatException(Exception):
        pass
-                               
+
 class GlsaArgumentException(Exception):
        pass
 
@@ -450,9 +444,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
@@ -482,7 +476,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
                """
@@ -505,10 +499,10 @@ class Glsa:
 
        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
@@ -531,7 +525,7 @@ 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>
@@ -543,15 +537,15 @@ class Glsa:
                        count = revisedEl.getAttribute("count")
                elif (self.revised.find(":") >= 0):
                        (self.revised, count) = self.revised.split(":")
-               
+
                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")
@@ -559,7 +553,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")
@@ -569,7 +563,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")
@@ -601,10 +595,10 @@ class Glsa:
 
        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 utf-8).
-               
+
                @type   outstream: File
                @param  outfile: Stream that should be used for writing
                                                 (defaults to sys.stdout)
@@ -645,13 +639,13 @@ class Glsa:
                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
                """
@@ -666,12 +660,12 @@ class Glsa:
                                                                and None != getMinUpgrade(path["vul_atoms"], path["unaff_atoms"], \
                                                                                self.portdbapi, self.vardbapi))
                return rValue
-       
+
        def isInjected(self):
                """
                Looks if the GLSA ID is in the GLSA checkfile to check if this
                GLSA should be marked as applied.
-               
+
                @rtype:         Boolean
                @returns:       True if the GLSA is in the inject file, False if not
                """
@@ -683,7 +677,7 @@ class Glsa:
        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
@@ -698,13 +692,13 @@ class Glsa:
                        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