Merged from trunk -r11860:11867
authorFabian Groffen <grobian@gentoo.org>
Sat, 15 Nov 2008 16:24:03 +0000 (16:24 -0000)
committerFabian Groffen <grobian@gentoo.org>
Sat, 15 Nov 2008 16:24:03 +0000 (16:24 -0000)
   | 11861   | Bug #246451 - Inside SecuritySet._reduce(), use              |
   | zmedico | portdbapi.xmatch("match-all") in order do be consistent with |
   |         | portage.glsa.getMinUpgrade() which also uses match-all.      |

   | 11862   | Use a frozenset to optimize skiplist containment checks      |
   | zmedico | inside NewsManager.updateItems().                            |

   | 11863   | Instead of raising a TypeError from the NewsItem             |
   | zmedico | constructor, check the path inside NewsManager.updateItems() |
   |         | before the NewsItem constructor is called.                   |

   | 11864   | Inside NewsManager.updateItems(), use a mutable set for      |
   | zmedico | skiplist and sort the contents when writing the file.        |

   | 11865   | Sort the return value from cpv_all() inside                  |
   | zmedico | vardbapi._counter_hash() instead of sorting them in side     |
   |         | cpv_all(). Thanks to Brian Harring for the suggestion.       |

   | 11866   | * Add NewsItem.isValid() and DisplayRestriction.isValid()    |
   | zmedico | methods and use use them to check validity inside            |
   |         | NewsManager.updateItems(). * Make                            |
   |         | DisplayInstalledRestriction.isValid() check validity of the  |
   |         | atom.                                                        |

   | 11867   | Use a simpler approach instead of implementing               |
   | zmedico | NewsItem.__getattr__().                                      |
   Merged from trunk -r11867:11893

   | 11868   | Always return early from NewsManager.updateItems() if        |
   | zmedico | self.unread_path can't be created.                           |

   | 11869   | Return early from NewsManager.updateItems() if the news path |
   | zmedico | listdir call fails.                                          |

   | 11870   | Misc fixes and cleanups in NewsManager.                      |
   | zmedico |                                                              |

   | 11871   | Add quote from GLEP 42 about permission bits.                |
   | zmedico |                                                              |

   | 11872   | Use grabfile() instead of grablines(), to ignore empty       |
   | zmedico | lines.                                                       |

   | 11873   | Use apply_secpass_permissions() to avoid                     |
   | zmedico | OperationNotPermitted errors when not allowed to chown files |
   |         | to root uid.                                                 |

   | 11890   | Bug #244485 - Document the doman enhancement from bug        |
   | zmedico | #222439.                                                     |

   | 11891   | Bug #244485 - Document src_prepare() and src_configure()     |
   | zmedico | phase functions.                                             |

   | 11892   | Bug #244485 - Document the default phase function helpers.   |
   | zmedico |                                                              |

   | 11893   | Rename the "FUNCTIONS" section to "PHASE FUNCTIONS".         |
   | zmedico |                                                              |

svn path=/main/branches/prefix/; revision=11944

man/ebuild.5
pym/portage/dbapi/vartree.py
pym/portage/news.py
pym/portage/sets/security.py

index 0235f7be24413b70f20c2338bcf03cb9ce5e3d9d..d01b14f635231fa97e8ee2d71374c58e2322e8fb 100644 (file)
@@ -464,7 +464,7 @@ process.  Inherit may only be called once in an ebuild and it may \fBnever be
 wrapped within any conditionals\fR of any kind.  Specification of the eclasses
 contains only their name and not the \fI.eclass\fR extension.  Also note that
 the inherit statement must come before other variable declarations.
-.SH "FUNCTIONS"
+.SH "PHASE FUNCTIONS"
 .TP
 .B pkg_nofetch
 If you turn on \fIfetch\fR in \fBRESTRICT\fR, then this function will be
@@ -486,8 +486,22 @@ patches and other pre configure/compile modifications should be done here.
 .br
 Initial working directory of $WORKDIR.
 .TP
+.B src_prepare
+All preparation of source code, such as application of patches, should be done
+here. This function is supported beginning with \fBEAPI 2\fR.
+.br
+Initial working directory of $S.
+.TP
+.B src_configure
+All necessary steps for configuration should be done here. This function is
+supported beginning with \fBEAPI 2\fR.
+.br
+Initial working directory of $S.
+.TP
 .B src_compile
-All necessary steps for configuration and compilation should be done in here.
+With less than \fBEAPI 2\fR, all necessary steps for both configuration and
+compilation should be done here. Beginning with \fBEAPI 2\fR, only compilation
+steps should be done here.
 .br
 Initial working directory of $S.
 .TP
@@ -519,6 +533,35 @@ Initial working directory of $PWD.
 This function should contain optional basic configuration steps.
 .br
 Initial working directory of $PWD.
+.SH "HELPER FUNCTIONS: PHASES"
+.TP
+.B default
+Calls the default phase function implementation for the currently executing
+phase. This function is supported beginning with \fBEAPI 2\fR.
+.TP
+.B default_pkg_nofetch
+Calls the default pkg_nofetch phase function implementation.
+This function is supported beginning with \fBEAPI 2\fR.
+.TP
+.B default_src_unpack
+Calls the default src_unpack phase function implementation.
+This function is supported beginning with \fBEAPI 2\fR.
+.TP
+.B default_src_prepare
+Calls the default src_prepare phase function implementation.
+This function is supported beginning with \fBEAPI 2\fR.
+.TP
+.B default_src_configure
+Calls the default src_configure phase function implementation.
+This function is supported beginning with \fBEAPI 2\fR.
+.TP
+.B default_src_compile
+Calls the default src_compile phase function implementation.
+This function is supported beginning with \fBEAPI 2\fR.
+.TP
+.B default_src_test
+Calls the default src_test phase function implementation.
+This function is supported beginning with \fBEAPI 2\fR.
 .SH "HELPER FUNCTIONS: GENERAL"
 .TP
 \fBdie\fR \fI[reason]\fR
@@ -832,6 +875,12 @@ Installs manual\-pages into /usr/share/man/man[0\-9n] depending on the
 manual file ending.  The files are compressed if they are not already.  You
 can specify locale\-specific manpages with the \fI\-i18n\fR option.  Then the
 man\-page will be installed into /usr/share/man/\fI<locale>\fR/man[0\-9n].
+Beginning with \fBEAPI 2\fR, a locale\-specific manpage which contains a locale
+in the file name will be installed in /usr/share/man/\fI<locale>\fR/man[0\-9n],
+with the locale portion of the file name removed, and the \fI\-i18n\fR option
+has no effect. For example, with \fBEAPI 2\fR, a manpage named
+foo.\fI<locale>\fR.1 will be installed as
+/usr/share/man/\fI<locale>\fR/man1/foo.1.
 .PD 0
 .TP
 \fBdohard\fR \fI<filename> <linkname>\fR
index 99d27f26384cac4b3d71d7fdda0f204a2429e3c6..3ed3c55b6f369fdcbb72fe7ca43fed5b3a305689 100644 (file)
@@ -1305,7 +1305,9 @@ class vardbapi(dbapi):
                        from md5 import new as new_hash
                h = new_hash()
                aux_keys = ["COUNTER"]
-               for cpv in self.cpv_all():
+               cpv_list = self.cpv_all()
+               cpv_list.sort()
+               for cpv in cpv_list:
                        try:
                                counter, = self.aux_get(cpv, aux_keys)
                        except KeyError:
@@ -1456,7 +1458,7 @@ class vardbapi(dbapi):
                                        self.invalidentry(self.getpath(subpath))
                                        continue
                                returnme.append(subpath)
-               returnme.sort()
+
                return returnme
 
        def cp_all(self, use_cache=1):
index e366287bb3b668ba60cc73d445ed6577e5709ae2..aa979dabd921657e175e8cbc1abd1ebcc61b3dbf 100644 (file)
@@ -7,14 +7,16 @@ __all__ = ["NewsManager", "NewsItem", "DisplayRestriction",
        "DisplayProfileRestriction", "DisplayKeywordRestriction",
        "DisplayInstalledRestriction"]
 
-import errno
+import logging
 import os
 import re
-from portage.util import apply_permissions, ensure_dirs, grabfile, \
-       grablines, normalize_path, write_atomic
+from portage.util import apply_secpass_permissions, ensure_dirs, \
+       grabfile, normalize_path, write_atomic, writemsg_level
 from portage.data import portage_gid
+from portage.dep import isvalidatom
 from portage.locks import lockfile, unlockfile
-from portage.exception import OperationNotPermitted
+from portage.exception import InvalidLocation, OperationNotPermitted, \
+       PermissionDenied
 
 class NewsManager(object):
        """
@@ -38,6 +40,16 @@ class NewsManager(object):
                self.vdb = vardb
                self.portdb = portdb
 
+               # GLEP 42 says:
+               #   All news item related files should be root owned and in the
+               #   portage group with the group write (and, for directories,
+               #   execute) bits set. News files should be world readable.
+               self._uid = int(self.config["PORTAGE_INST_UID"])
+               self._gid = portage_gid
+               self._file_mode = 00064
+               self._dir_mode  = 00074
+               self._mode_mask = 00000
+
                portdir = portdb.porttree_root
                profiles_base = os.path.join(portdir, 'profiles') + os.path.sep
                profile_path = None
@@ -48,14 +60,17 @@ class NewsManager(object):
                                profile_path = profile_path[len(profiles_base):]
                self._profile_path = profile_path
 
-               # Ensure that the unread path exists and is writable.
-               dirmode  = 02070
-               modemask =    02
-               try:
-                       ensure_dirs(self.unread_path, mode=dirmode,
-                               mask=modemask, gid=portage_gid)
-               except OperationNotPermitted:
-                       pass
+       def _unread_filename(self, repoid):
+               return os.path.join(self.unread_path, 'news-%s.unread' % repoid)
+
+       def _skip_filename(self, repoid):
+               return os.path.join(self.unread_path, 'news-%s.skip' % repoid)
+
+       def _news_dir(self, repoid):
+               repo_path = self.portdb.getRepositoryPath(repoid)
+               if repo_path is None:
+                       raise AssertionError("Invalid repoID: %s" % repoid)
+               return os.path.join(repo_path, self.news_path)
 
        def updateItems(self, repoid):
                """
@@ -64,66 +79,64 @@ class NewsManager(object):
                items into the news.repoid.unread file.
                """
 
-               repos = self.portdb.getRepositories()
-               if repoid not in repos:
-                       raise ValueError("Invalid repoID: %s" % repoid)
+               # Ensure that the unread path exists and is writable.
 
-               path = os.path.join(self.portdb.getRepositoryPath(repoid), self.news_path)
+               try:
+                       ensure_dirs(self.unread_path, uid=self._uid, gid=self._gid,
+                               mode=self._dir_mode, mask=self._mode_mask)
+               except (OperationNotPermitted, PermissionDenied):
+                       return
 
-               # Skip reading news for repoid if the news dir does not exist.  Requested by
-               # NightMorph :)
-               if not os.path.exists(path):
-                       return None
-               news = os.listdir(path)
+               if not os.access(self.unread_path, os.W_OK):
+                       return
 
-               skipfile = os.path.join(self.unread_path, "news-%s.skip" % repoid)
-               skiplist = grabfile(skipfile)
-               updates = []
-               for itemid in news:
-                       if itemid in skiplist:
-                               continue
-                       try:
-                               filename = os.path.join(path, itemid, itemid + "." + self.language_id + ".txt")
-                               item = NewsItem(filename, itemid)
-                       except (TypeError):
-                               continue
-                       if item.isRelevant(profile=self._profile_path,
-                               config=self.config, vardb=self.vdb):
-                               updates.append(item)
-               del path
-               
-               path = os.path.join(self.unread_path, 'news-%s.unread' % repoid)
-               unread_lock = None
+               news_dir = self._news_dir(repoid)
                try:
-                       unread_lock = lockfile(path)
-                       if not os.path.exists(path):
-                               #create the file if it does not exist
-                               open(path, "w")
-                       # Ensure correct perms on the unread file.
-                       apply_permissions( filename=path,
-                               uid=int(self.config['PORTAGE_INST_UID']), gid=portage_gid, mode=0664)
-                       # Make sure we have the correct permissions when created
-                       unread_file = open(path, 'a')
-
-                       for item in updates:
-                               unread_file.write(item.name + "\n")
-                               skiplist.append(item.name)
-                       unread_file.close()
-               finally:
-                       if unread_lock:
-                               unlockfile(unread_lock)
-                       write_atomic(skipfile, "\n".join(skiplist)+"\n")
+                       news = os.listdir(news_dir)
+               except OSError:
+                       return
+
+               skip_filename = self._skip_filename(repoid)
+               unread_filename = self._unread_filename(repoid)
+               unread_lock = lockfile(unread_filename, wantnewlockfile=1)
                try:
-                       apply_permissions(filename=skipfile, 
-                               uid=int(self.config["PORTAGE_INST_UID"]), gid=portage_gid, mode=0664)
-               except OperationNotPermitted, e:
-                       import errno
-                       # skip "permission denied" errors as we're likely running in pretend mode
-                       # with reduced priviledges
-                       if e.errno == errno.EPERM:
-                               pass
-                       else:
-                               raise
+                       unread = set(grabfile(unread_filename))
+                       unread_orig = unread.copy()
+                       skip = set(grabfile(skip_filename))
+                       skip_orig = skip.copy()
+
+                       updates = []
+                       for itemid in news:
+                               if itemid in skip:
+                                       continue
+                               filename = os.path.join(news_dir, itemid,
+                                       itemid + "." + self.language_id + ".txt")
+                               if not os.path.isfile(filename):
+                                       continue
+                               item = NewsItem(filename, itemid)
+                               if not item.isValid():
+                                       continue
+                               if item.isRelevant(profile=self._profile_path,
+                                       config=self.config, vardb=self.vdb):
+                                       unread.add(item.name)
+                                       skip.add(item.name)
+
+                       if unread != unread_orig:
+                               write_atomic(unread_filename,
+                                       "".join("%s\n" % x for x in sorted(unread)))
+                               apply_secpass_permissions(unread_filename,
+                                       uid=self._uid, gid=self._gid,
+                                       mode=self._file_mode, mask=self._mode_mask)
+
+                       if skip != skip_orig:
+                               write_atomic(skip_filename,
+                                       "".join("%s\n" % x for x in sorted(skip)))
+                               apply_secpass_permissions(skip_filename,
+                                       uid=self._uid, gid=self._gid,
+                                       mode=self._file_mode, mask=self._mode_mask)
+
+               finally:
+                       unlockfile(unread_lock)
 
        def getUnreadItems(self, repoid, update=False):
                """
@@ -136,15 +149,14 @@ class NewsManager(object):
                if update:
                        self.updateItems(repoid)
                
-               unreadfile = os.path.join(self.unread_path, 'news-%s.unread' % repoid)
+               unread_filename = self._unread_filename(repoid)
                unread_lock = None
                try:
-                       if os.access(os.path.dirname(unreadfile), os.W_OK):
-                               # TODO: implement shared readonly locks
-                               unread_lock = lockfile(unreadfile)
-
-                       return len(grablines(unreadfile))
-
+                       unread_lock = lockfile(unread_filename, wantnewlockfile=1)
+               except (InvalidLocation, OperationNotPermitted, PermissionDenied):
+                       return 0
+               try:
+                       return len(grabfile(unread_filename))
                finally:
                        if unread_lock:
                                unlockfile(unread_lock)
@@ -169,11 +181,10 @@ class NewsItem(object):
                """ 
                For a given news item we only want if it path is a file.
                """
-               if not os.path.isfile(path):
-                       raise TypeError("%s is no regular file" % path)
                self.path = path
                self.name = name
                self._parsed = False
+               self._valid = True
 
        def isRelevant(self, vardb, config, profile):
                """
@@ -184,6 +195,9 @@ class NewsItem(object):
                or raise a ValueError exception if the required object is not present.
                """
 
+               if not self._parsed:
+                       self.parse()
+
                if not len(self.restrictions):
                        return True # no restrictions to match means everyone should see it
                
@@ -198,10 +212,16 @@ class NewsItem(object):
                        
                return False # No restrictions were met; thus we aren't relevant :(
 
+       def isValid(self):
+               if not self._parsed:
+                       self.parse()
+               return self._valid
+
        def parse(self):
                lines = open(self.path).readlines()
                self.restrictions = []
-               for line in lines:
+               invalids = []
+               for i, line in enumerate(lines):
                        #Optimization to ignore regex matchines on lines that
                        #will never match
                        if not line.startswith('D'):
@@ -213,13 +233,19 @@ class NewsItem(object):
                                match = regex.match(line)
                                if match:
                                        self.restrictions.append(restriction(match.groups()[0].strip()))
+                                       if not self.restrictions[-1].isValid():
+                                               invalids.append((i + 1, line.rstrip("\n")))
                                        continue
-               self._parsed = True
+               if invalids:
+                       self._valid = False
+                       msg = []
+                       msg.append("Invalid news item: %s" % (self.path,))
+                       for lineno, line in invalids:
+                               msg.append("  line %d: %s" % (lineno, line))
+                       writemsg_level("".join("!!! %s\n" % x for x in msg),
+                               level=logging.ERROR, noiselevel=-1)
 
-       def __getattr__(self, attr):
-               if not self._parsed:
-                       self.parse()
-               return self.__dict__[attr]
+               self._parsed = True
 
 class DisplayRestriction(object):
        """
@@ -230,6 +256,9 @@ class DisplayRestriction(object):
        are met, then it is displayed
        """
 
+       def isValid(self):
+               return True
+
        def checkRestriction(self, **kwargs):
                raise NotImplementedError('Derived class should over-ride this method')
 
@@ -267,11 +296,14 @@ class DisplayInstalledRestriction(DisplayRestriction):
        if the user has that item installed.
        """
        
-       def __init__(self, cpv):
-               self.cpv = cpv
+       def __init__(self, atom):
+               self.atom = atom
+
+       def isValid(self):
+               return isvalidatom(self.atom)
 
        def checkRestriction(self, **kwargs):
                vdb = kwargs['vardb']
-               if vdb.match(self.cpv):
+               if vdb.match(self.atom):
                        return True
                return False
index 928fecbcfb68f08a0b7dcbba15a00b6ee7582d86..12503d9a5ce5ee5d6741e752a555995b7c980c9a 100644 (file)
@@ -46,7 +46,7 @@ class SecuritySet(PackageSet):
        def _reduce(self, atomlist):
                mydict = {}
                for atom in atomlist[:]:
-                       cpv = self._portdbapi.match(atom)[0]
+                       cpv = self._portdbapi.xmatch("match-all", atom)[0]
                        slot = self._portdbapi.aux_get(cpv, ["SLOT"])[0]
                        cps = "/".join(catpkgsplit(cpv)[0:2]) + ":" + slot
                        if not cps in mydict: