Index: pym/portage.py =================================================================== --- pym/portage.py (revision 8155) +++ pym/portage.py (revision 8160) @@ -7160,6 +7160,10 @@ This class provides an interface to the installed package database At present this is implemented as a text backend in /var/db/pkg. """ + + import re + _normalize_needed = re.compile(r'//|^[^/]|.+/$') + def __init__(self, cat, pkg, myroot, mysettings, treetype=None, vartree=None): """ @@ -7293,7 +7297,10 @@ mylines=myc.readlines() myc.close() null_byte = "\0" - contents_file = os.path.join(self.dbdir, "CONTENTS") + normalize_needed = self._normalize_needed + myroot = self.myroot + if myroot == os.path.sep: + myroot = None pos = 0 for line in mylines: pos += 1 @@ -7307,8 +7314,12 @@ # we do this so we can remove from non-root filesystems # (use the ROOT var to allow maintenance on other partitions) try: - mydat[1] = normalize_path(os.path.join( - self.myroot, mydat[1].lstrip(os.path.sep))) + if normalize_needed.match(mydat[1]): + mydat[1] = normalize_path(mydat[1]) + if not mydat[1].startswith(os.path.sep): + mydat[1] = os.path.sep + mydat[1] + if myroot: + mydat[1] = os.path.join(myroot, mydat[1].lstrip(os.path.sep)) if mydat[0]=="obj": #format: type, mtime, md5sum pkgfiles[" ".join(mydat[1:-2])]=[mydat[0], mydat[-1], mydat[-2]] @@ -7712,9 +7723,14 @@ def isowner(self,filename,destroot): """ - Check if filename is a new file or belongs to this package - (for this or a previous version) - + Check if a file belongs to this package. This may + result in a stat call for the parent directory of + every installed file, since the inode numbers are + used to work around the problem of ambiguous paths + caused by symlinked directories. The results of + stat calls are cached to optimize multiple calls + to this method. + @param filename: @type filename: @param destroot: @@ -7731,23 +7747,46 @@ if pkgfiles and destfile in pkgfiles: return True if pkgfiles: + # Use stat rather than lstat since we want to follow + # any symlinks to the real parent directory. + parent_path = os.path.dirname(destfile) try: - mylstat = os.lstat(destfile) + parent_stat = os.stat(parent_path) except EnvironmentError, e: if e.errno != errno.ENOENT: raise del e - return True + return False if self._contents_inodes is None: - self._contents_inodes = set() + self._contents_inodes = {} + parent_paths = set() for x in pkgfiles: + p_path = os.path.dirname(x) + if p_path in parent_paths: + continue + parent_paths.add(p_path) try: - lstat = os.lstat(x) - self._contents_inodes.add((lstat.st_dev, lstat.st_ino)) + s = os.stat(p_path) except OSError: pass - if (mylstat.st_dev, mylstat.st_ino) in self._contents_inodes: - return True + else: + inode_key = (s.st_dev, s.st_ino) + # Use lists of paths in case multiple + # paths reference the same inode. + p_path_list = self._contents_inodes.get(inode_key) + if p_path_list is None: + p_path_list = [] + self._contents_inodes[inode_key] = p_path_list + if p_path not in p_path_list: + p_path_list.append(p_path) + p_path_list = self._contents_inodes.get( + (parent_stat.st_dev, parent_stat.st_ino)) + if p_path_list: + basename = os.path.basename(destfile) + for p_path in p_path_list: + x = os.path.join(p_path, basename) + if x in pkgfiles: + return True return False @@ -7979,11 +8018,6 @@ print "Searching all installed packages for file collisions..." print "Press Ctrl-C to Stop" print - """ Note: The isowner calls result in a stat call for *every* - single installed file, since the inode numbers are used to work - around the problem of ambiguous paths caused by symlinked files - and/or directories. Though it is slow, it is as accurate as - possible.""" found_owner = False for cpv in self.vartree.dbapi.cpv_all(): cat, pkg = catsplit(cpv) @@ -8037,15 +8071,21 @@ " Add \"collision-protect\" to FEATURES in make.conf" + \ " if you would like the merge to abort in cases like this." if self.settings.get("PORTAGE_QUIET") != "1": - msg += " If you have determined that one or more of the" + \ - " files actually belong to another installed package then" + \ - " go to http://bugs.gentoo.org and report it as a bug." + \ - " Be sure to identify both this package and the other" + \ - " installed package in the bug report. Use a command such" + \ - " as \\`equery belongs \\` to identify the" + \ - " installed package that owns a file. Do NOT file a bug" + \ - " without reporting exactly which two packages install" + \ - " the same file(s)." + msg += " You can use a command such as" + \ + " \\`portageq owners / \\` to identify the" + \ + " installed package that owns a file. If portageq" + \ + " reports that only one package owns a file then do NOT" + \ + " file a bug report. A bug report is only useful if it" + \ + " identifies at least two or more packages that are known" + \ + " install the same file(s). If a collision occurs and you" + \ + " can not explain where the file came from then you" + \ + " should simply ignore the collision since there is not" + \ + " enough information to determine if a real problem" + \ + " exists. Please do NOT file a bug report at" + \ + " http://bugs.gentoo.org unless you report exactly which" + \ + " two packages install the same file(s). Once again," + \ + " please do NOT file a bug report unless you have" + \ + " completely understood the above message." self.settings["EBUILD_PHASE"] = "preinst" cmd = "source '%s/isolated-functions.sh' ; " % PORTAGE_BIN_PATH Index: bin/portageq =================================================================== --- bin/portageq (revision 8155) +++ bin/portageq (revision 8160) @@ -124,6 +124,69 @@ metadata.uses_root = True +def owners(argv): + """ []+ + Given a list of files, print the packages that own the files and which + files belong to each package. Files owned by a package are listed on + the lines below it, indented by a single tab character (\\t). All file + paths must start with . Returns 1 if no owners could be found, + and 0 otherwise. + """ + if len(argv) < 2: + sys.stderr.write("ERROR: insufficient parameters!\n") + sys.stderr.flush() + return 2 + + from portage import catsplit, dblink + settings = portage.settings + root = settings["ROOT"] + vardb = portage.db[root]["vartree"].dbapi + + cwd = None + try: + cwd = os.getcwd() + except OSError: + pass + + files = [] + for f in argv[1:]: + f = portage.normalize_path(f) + if not f.startswith(os.path.sep): + if cwd is None: + sys.stderr.write("ERROR: cwd does not exist!\n") + sys.stderr.flush() + return 2 + f = os.path.join(cwd, f) + f = portage.normalize_path(f) + if not f.startswith(root): + sys.stderr.write("ERROR: file paths must begin with !\n") + sys.stderr.flush() + return 2 + files.append(f[len(root):]) + + found_owner = False + for cpv in vardb.cpv_all(): + cat, pkg = catsplit(cpv) + mylink = dblink(cat, pkg, root, settings, vartree=vardb.vartree) + myfiles = [] + for f in files: + if mylink.isowner(f, root): + myfiles.append(f) + if myfiles: + found_owner = True + sys.stdout.write("%s\n" % cpv) + for f in myfiles: + sys.stdout.write("\t%s\n" % \ + os.path.join(root, f.lstrip(os.path.sep))) + sys.stdout.flush() + if not found_owner: + sys.stderr.write("None of the installed packages claim the file(s).\n") + sys.stderr.flush() + return 1 + return 0 + +owners.uses_root = True + def best_visible(argv): """ []+ Returns category/package-version (without .ebuild). @@ -334,7 +397,9 @@ import portage if uses_root: sys.argv[2] = portage.root - function(sys.argv[2:]) + retval = function(sys.argv[2:]) + if retval: + sys.exit(retval) except KeyError: usage(sys.argv) sys.exit(os.EX_USAGE)