--- a/src/modules/actions/__init__.py Wed Feb 06 13:01:46 2013 -0800
+++ b/src/modules/actions/__init__.py Thu Mar 07 10:46:12 2013 +0530
@@ -21,7 +21,7 @@
#
#
-# Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
#
"""
@@ -32,8 +32,12 @@
package directory and they'll just be picked up. The current package contents
can be seen in the section "PACKAGE CONTENTS", below.
-This package has one data member: "types". This is a dictionary which maps the
-action names to the classes that represent them.
+This package has two data members:
+ "types", a dictionary which maps the action names to the classes that
+ represent them.
+
+ "payload_types", a dictionary which maps action names that deliver payload
+ to the classes that represent them.
This package also has one function: "fromstr", which creates an action instance
based on a str() representation of an action.
@@ -53,6 +57,12 @@
# A dictionary of all the types in this package, mapping to the classes that
# define them.
types = {}
+
+# A dictionary of the action names in this package that deliver payload,
+# according to the 'has_payload' member in each class. The dictionary is keyed
+# by the action name, and has the action class as its values.
+payload_types = {}
+
for modname in __all__:
module = __import__("%s.%s" % (__name__, modname),
globals(), locals(), [modname])
@@ -69,6 +79,8 @@
for cls in classes:
if hasattr(cls, "name"):
types[cls.name] = cls
+ if hasattr(cls, "has_payload") and cls.has_payload:
+ payload_types[cls.name] = cls
# Clean up after ourselves
del f, modname, module, nvlist, classes, c, cls
--- a/src/modules/client/progress.py Wed Feb 06 13:01:46 2013 -0800
+++ b/src/modules/client/progress.py Thu Mar 07 10:46:12 2013 +0530
@@ -21,7 +21,7 @@
#
#
-# Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
#
#
@@ -576,6 +576,26 @@
def _ver_output_done(self): pass
@pt_abstract
+ def _repo_ver_output(self, outspec, repository_scan=False): pass
+
+ @pt_abstract
+ def _repo_ver_output_error(self, errors): pass
+
+ @pt_abstract
+ def _repo_ver_output_done(self): pass
+
+ def _repo_fix_output(self, outspec): pass
+
+ @pt_abstract
+ def _repo_fix_output_error(self, errors): pass
+
+ @pt_abstract
+ def _repo_fix_output_info(self, errors): pass
+
+ @pt_abstract
+ def _repo_fix_output_done(self): pass
+
+ @pt_abstract
def _archive_output(self, outspec): pass
@pt_abstract
@@ -661,6 +681,8 @@
JOB_REPO_ANALYZE_REPO = 310
JOB_REPO_RM_MFST = 311
JOB_REPO_RM_FILES = 312
+ JOB_REPO_VERIFY_REPO = 313
+ JOB_REPO_FIX_REPO = 314
# Operation purpose. This set of modes is used by callers to indicate
# to the progress tracker what's going on at a high level. This allows
@@ -819,6 +841,47 @@
@pt_abstract
def verify_done(self): pass
+ # verifying the content of a repository
+ @pt_abstract
+ def repo_verify_start(self, npkgs): pass
+
+ @pt_abstract
+ def repo_verify_start_pkg(self, pkgfmri, repository_scan=False): pass
+
+ @pt_abstract
+ def repo_verify_add_progress(self, pkgfmri): pass
+
+ @pt_abstract
+ def repo_verify_yield_error(self, pkgfmri, errors): pass
+
+ @pt_abstract
+ def repo_verify_yield_warning(self, pkgfmri, warnings): pass
+
+ @pt_abstract
+ def repo_verify_yield_info(self, pkgfmri, info): pass
+
+ @pt_abstract
+ def repo_verify_end_pkg(self, pkgfmri): pass
+
+ @pt_abstract
+ def repo_verify_done(self): pass
+
+ # fixing the content of a repository
+ @pt_abstract
+ def repo_fix_start(self, npkgs): pass
+
+ @pt_abstract
+ def repo_fix_add_progress(self, pkgfmri): pass
+
+ @pt_abstract
+ def repo_fix_yield_error(self, pkgfmri, errors): pass
+
+ @pt_abstract
+ def repo_fix_yield_info(self, pkgfmri, info): pass
+
+ @pt_abstract
+ def repo_fix_done(self): pass
+
# archiving to .p5p files
@pt_abstract
def archive_set_goal(self, arcname, nitems, nbytes): pass
@@ -997,6 +1060,9 @@
self.mfst_commit = GoalTrackerItem(_("Committed Manifests"))
self.ver_pkgs = GoalTrackerItem(_("Verify Packages"))
+ self.repo_ver_pkgs = GoalTrackerItem(
+ _("Verify Repository Content"))
+ self.repo_fix = GoalTrackerItem(_("Fix Repository Content"))
# archiving support
self.archive_items = GoalTrackerItem(_("Archived items"))
@@ -1066,7 +1132,12 @@
self.JOB_REPO_RM_MFST:
GoalTrackerItem(_("Removing package manifests")),
self.JOB_REPO_RM_FILES:
- GoalTrackerItem(_("Removing package files"))
+ GoalTrackerItem(_("Removing package files")),
+ self.JOB_REPO_VERIFY_REPO:
+ GoalTrackerItem(_("Verifying repository content")),
+ self.JOB_REPO_FIX_REPO:
+ GoalTrackerItem(_("Fixing repository content"))
+
}
self.reset_download()
@@ -1258,6 +1329,46 @@
def verify_done(self):
self.ver_pkgs.done()
+ def repo_verify_start(self, npkgs):
+ self.repo_ver_pkgs.reset()
+ self.repo_ver_pkgs.goalitems = npkgs
+
+ def repo_verify_start_pkg(self, pkgfmri, repository_scan=False):
+ if pkgfmri != self.repo_ver_pkgs.curinfo:
+ self.repo_ver_pkgs.items += 1
+ self.repo_ver_pkgs.curinfo = pkgfmri
+ self._repo_ver_output(OutSpec(changed=["startpkg"]),
+ repository_scan=repository_scan)
+
+ def repo_verify_add_progress(self, pkgfmri):
+ self._repo_ver_output(OutSpec())
+
+ def repo_verify_yield_error(self, pkgfmri, errors):
+ self._repo_ver_output_error(errors)
+
+ def repo_verify_end_pkg(self, pkgfmri):
+ self._repo_ver_output(OutSpec(changed=["endpkg"]))
+ self.repo_ver_pkgs.curinfo = None
+
+ def repo_verify_done(self):
+ self.repo_ver_pkgs.done()
+
+ def repo_fix_start(self, nitems):
+ self.repo_fix.reset()
+ self.repo_fix.goalitems = nitems
+
+ def repo_fix_add_progress(self, pkgfmri):
+ self._repo_fix_output(OutSpec())
+
+ def repo_fix_yield_error(self, pkgfmri, errors):
+ self._repo_fix_output_error(errors)
+
+ def repo_fix_yield_info(self, pkgfmri, info):
+ self._repo_fix_output_info(info)
+
+ def repo_fix_done(self):
+ self.repo_fix.done()
+
def archive_set_goal(self, arcname, nitems, nbytes):
self._archive_name = arcname # pylint: disable=W0201
self.archive_items.goalitems = nitems
@@ -1891,6 +2002,33 @@
def _ver_output_done(self):
pass
+ def _repo_ver_output(self, outspec, repository_scan=False):
+ pass
+
+ def _repo_ver_output_error(self, errors):
+ self._pe.cprint(errors)
+
+ def _repo_ver_output_warning(self, warnings):
+ pass
+
+ def _repo_ver_output_info(self, info):
+ pass
+
+ def _repo_ver_output_done(self):
+ pass
+
+ def _repo_fix_output(self, outspec):
+ pass
+
+ def _repo_fix_output_error(self, errors):
+ self._pe.cprint(errors)
+
+ def _repo_fix_output_info(self, info):
+ self._pe.cprint(info)
+
+ def _repo_fix_output_done(self):
+ pass
+
def _dl_output(self, outspec):
if not self._ptimer.time_to_print() and not outspec.first and \
not outspec.last:
@@ -2284,6 +2422,46 @@
# We just erase the "Verifying" progress line.
self._pe.cprint("", end='', erase=True)
+ def _repo_ver_output(self, outspec, repository_scan=False):
+ """If 'repository_scan' is set and we have no FRMRI set, we emit
+ a message saying that we're performing a scan if the repository.
+ If we have no FMRI, we emit a message saying we don't know what
+ package we're looking at.
+
+ """
+ if not self.repo_ver_pkgs.curinfo:
+ if repository_scan:
+ pkg_stem = _("Scanning repository "
+ "(this could take some time)")
+ else:
+ pkg_stem = _("Unknown package")
+ else:
+ pkg_stem = self.repo_ver_pkgs.curinfo.get_pkg_stem()
+ if not self._ptimer.time_to_print() and not outspec:
+ return
+ if "endpkg" in outspec.changed:
+ self._pe.cprint("", end='', erase=True)
+ return
+ s = "%-64s %s %c" % \
+ (pkg_stem, self.repo_ver_pkgs.pair(), self._spinner())
+ self._pe.cprint(s, end='', erase=True)
+
+ def _repo_ver_output_error(self, errors):
+ self._output_flush()
+ self._pe.cprint(errors)
+
+ def _repo_fix_output(self, outspec):
+ s = "%c" % self._spinner()
+ self._pe.cprint(s, end='', erase=True)
+
+ def _repo_fix_output_error(self, errors):
+ self._output_flush()
+ self._pe.cprint(errors)
+
+ def _repo_fix_output_info(self, info):
+ self._output_flush()
+ self._pe.cprint(info)
+
def _archive_output(self, outspec):
if not self._ptimer.time_to_print() and not outspec:
return
--- a/src/modules/file_layout/file_manager.py Wed Feb 06 13:01:46 2013 -0800
+++ b/src/modules/file_layout/file_manager.py Thu Mar 07 10:46:12 2013 +0530
@@ -19,7 +19,7 @@
#
# CDDL HEADER END
#
-# Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
"""centralized object for insert, lookup, and removal of files.
@@ -159,7 +159,7 @@
return cur_full_path, dest_full_path
return None, dest_full_path
- def lookup(self, hashval, opener=False):
+ def lookup(self, hashval, opener=False, check_existence=True):
"""Find the file for hashval.
The "hashval" parameter contains the name of the file to be
@@ -169,7 +169,7 @@
return a path or an open file handle."""
cur_full_path, dest_full_path = self.__select_path(hashval,
- True)
+ check_existence)
if not cur_full_path:
return None
--- a/src/modules/manifest.py Wed Feb 06 13:01:46 2013 -0800
+++ b/src/modules/manifest.py Thu Mar 07 10:46:12 2013 +0530
@@ -21,7 +21,7 @@
#
#
-# Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
#
from collections import namedtuple, defaultdict
@@ -489,7 +489,7 @@
yield a
def gen_key_attribute_value_by_type(self, atype, excludes=EmptyI):
- """Generate the value of the key atrribute for each action
+ """Generate the value of the key attribute for each action
of type "type" in the manifest."""
return (
--- a/src/modules/server/repository.py Wed Feb 06 13:01:46 2013 -0800
+++ b/src/modules/server/repository.py Thu Mar 07 10:46:12 2013 +0530
@@ -19,21 +19,24 @@
#
# CDDL HEADER END
#
-# Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
import cStringIO
import codecs
import datetime
import errno
+import hashlib
import logging
import os
import os.path
-import platform
import shutil
import stat
import sys
import tempfile
import urllib
+import zlib
+
+import M2Crypto as m2
import pkg.actions as actions
import pkg.catalog as catalog
@@ -59,6 +62,22 @@
import pkg.version
CURRENT_REPO_VERSION = 4
+
+REPO_QUARANTINE_DIR = "pkg5-quarantine"
+
+REPO_VERIFY_BADHASH = 0
+REPO_VERIFY_BADMANIFEST = 1
+REPO_VERIFY_BADGZIP = 2
+REPO_VERIFY_NOFILE = 3
+REPO_VERIFY_PERM = 4
+REPO_VERIFY_MFPERM = 5
+REPO_VERIFY_BADSIG = 6
+REPO_VERIFY_WARN_OPENPERMS = 7
+REPO_VERIFY_UNKNOWN = 99
+
+REPO_FIX_ITEM = 0
+REPO_FIX_FAILED = 1
+
from pkg.pkggzip import PkgGzipFile
class RepositoryError(Exception):
@@ -237,18 +256,37 @@
class RepositoryVersionError(RepositoryError):
- """Raised when the repository specified uses an unsupported format
- (version).
+ """Raised when the repository specified uses a format greater than the
+ current format (version).
"""
- def __init__(self, location, version):
+ def __init__(self, location, version, current_version):
RepositoryError.__init__(self)
self.location = location
self.version = version
+ self.current_version = current_version
def __str__(self):
return("The repository at '%(location)s' is version "
- "'%(version)s'; only versions up to are supported.") % \
+ "'%(version)s'; only versions up to %(current_version)s are"
+ " supported.") % self.__dict__
+
+
+class RepositoryInvalidVersionError(RepositoryError):
+ """Raised when the repository specified uses an unsupported format.
+ (version).
+ """
+
+ def __init__(self, location, version, supported):
+ RepositoryError.__init__(self)
+ self.location = location
+ self.version = version
+ self.supported = supported
+
+ def __str__(self):
+ return("The repository at '%(location)s' is version "
+ "'%(version)s'; only version %(supported)s repositories are"
+ " supported.") % \
self.__dict__
@@ -261,6 +299,22 @@
return("Operation not supported for this configuration.")
+class RepositoryQuarantinedPathExistsError(RepositoryError):
+ """Raised when the repository is unable to quarantine a file because
+ a file of that name is already in quarantine.
+ """
+
+ def __str__(self):
+ return _("Quarantined path already exists.")
+
+
+class RepositorySigNoTrustAnchorDirError(RepositoryError):
+ """Raised when the repository trust anchor directory could not be found
+ while performing repository verification."""
+
+ def __str__(self):
+ return _("Unable to find trust anchor directory %s") % self.data
+
class _RepoStore(object):
"""The _RepoStore object provides an interface for performing operations
on a set of package data contained within a repository. This class is
@@ -1802,6 +1856,597 @@
if fn and os.path.exists(fn):
os.unlink(fn)
+ def __build_verify_error(self, error, path, reason):
+ """Given an integer error code, a path within the repository,
+ and a 'reason' dictionary containing more information about
+ the error, return a tuple of the form:
+
+ (error_code, message, path, reason)
+ """
+
+ # if we're looking at a path to a file hash, and we have a pfmri
+ # we'd like to print the 'path' attribute as well as its
+ # location within the repository.
+ hsh = reason.get("hash")
+ pfmri = reason.get("pkg")
+ if hsh and pfmri:
+ m = self._get_manifest(pfmri)
+ for ac in m.gen_actions_by_types(
+ actions.payload_types.keys()):
+ if ac.hash == hsh:
+ fpath = ac.attrs.get("path")
+ if fpath:
+ reason["fpath"] = fpath
+
+ message = _("Unknown error")
+ if error == REPO_VERIFY_BADHASH:
+ message = _("Invalid file hash: %s") % reason["hash"]
+ del reason["hash"]
+ elif error == REPO_VERIFY_BADMANIFEST:
+ message = _("Corrupt manifest.")
+ reason["err"] = _("Use pkglint(1) for more details.")
+ elif error == REPO_VERIFY_NOFILE:
+ message = _("Missing file: %s") % reason["hash"]
+ del reason["hash"]
+ elif error == REPO_VERIFY_BADGZIP:
+ message = _("Corrupted gzip file.")
+ elif error in [REPO_VERIFY_PERM, REPO_VERIFY_MFPERM]:
+ message = _("Verification failure: %s") % reason["err"]
+ del reason["err"]
+ elif error == REPO_VERIFY_UNKNOWN:
+ message = _("Bad manifest.")
+ elif error == REPO_VERIFY_BADSIG:
+ message = _("Bad signature.")
+ elif error == REPO_VERIFY_WARN_OPENPERMS:
+ # This message constitutes a warning rather than
+ # an error. No attempt is made by 'pkgrepo fix'
+ # to rectify this error, as it is potentially
+ # outside our responsibility to do so.
+ message = _("Restrictive permissions.")
+ reason["err"] = \
+ _("Some repository content for publisher '%s' "
+ "or paths leading to the repository were not "
+ "world-readable or were not readable by "
+ "'pkg5srv:pkg5srv', which can cause access errors "
+ "if the repository contents are served by the "
+ "following services:\n"
+ " svc:/application/pkg/server\n"
+ " svc:/application/pkg/system-repository.\n"
+ "Only the first path found with restrictive "
+ "permissions is shown.") % reason["pub"]
+ del reason["pub"]
+ else:
+ raise Exception(
+ "Unknown repository verify error code: %s" % error)
+
+ return error, path, message, reason
+
+ def __get_hashes(self, path, pfmri):
+ """Given an PkgFmri, return a set containing all of the
+ hashes of the files its manifest references."""
+
+ hashes = set()
+ errors = []
+ try:
+ m = self._get_manifest(pfmri)
+ for a in m.gen_actions():
+ if not a.has_payload or not a.hash:
+ continue
+
+ # Action payload.
+ hashes.add(a.hash)
+
+ # Signature actions have additional payloads
+ if a.name == "signature":
+ hashes.update(
+ a.attrs.get("chain", "").split())
+ except apx.PermissionsException:
+ errors.append((REPO_VERIFY_MFPERM, path,
+ {"err": _("Permission denied.")}))
+ return hashes, errors
+
+ def __verify_manifest(self, path, pfmri):
+ """Verify that a manifest can be found for this pfmri"""
+ try:
+ m = self._get_manifest(pfmri)
+ except apx.InvalidPackageErrors:
+ return (REPO_VERIFY_BADMANIFEST, path, {})
+ except apx.PermissionsException, e:
+ return (REPO_VERIFY_PERM, path, {"err": str(e),
+ "pkg": pfmri})
+
+ def __verify_hash(self, path, pfmri, h):
+ """Perform hash verification on the given gzip file."""
+
+ gzf = None
+ hash = os.path.basename(path)
+ try:
+ gzf = PkgGzipFile(fileobj=open(path, "rb"))
+ fhash = hashlib.sha1()
+ fhash.update(gzf.read())
+ actual = fhash.hexdigest()
+ if actual != h:
+ return (REPO_VERIFY_BADHASH, path,
+ {"actual": actual, "hash": hash,
+ "pkg": pfmri})
+ except (ValueError, zlib.error), e:
+ return (REPO_VERIFY_BADGZIP, path,
+ {"hash": hash, "pkg": pfmri})
+ except IOError, e:
+ if e.errno in [errno.EACCES, errno.EPERM]:
+ return (REPO_VERIFY_PERM, path,
+ {"err": str(e), "hash": hash,
+ "pkg": pfmri})
+ else:
+ return (REPO_VERIFY_BADGZIP, path,
+ {"hash": hash, "pkg": pfmri})
+ finally:
+ if gzf:
+ gzf.close()
+
+ def __verify_perm(self, path, pfmri, h):
+ """Check that we don't get any permissions errors when
+ trying to stat the given path."""
+ try:
+ st = os.stat(path)
+ # if it's a directory, we'll try to list it
+ if stat.S_ISDIR(st.st_mode):
+ os.listdir(path)
+ except OSError, e:
+ if e.errno in [errno.EPERM, errno.EACCES]:
+ if not pfmri:
+ return (REPO_VERIFY_MFPERM, path,
+ {"err": str(e)})
+ return (REPO_VERIFY_PERM, path,
+ {"hash": h, "err": str(e), "pkg": pfmri})
+ else:
+ return (REPO_VERIFY_NOFILE, path,
+ {"hash": h, "err": str(e), "pkg": pfmri})
+
+ def __verify_signature(self, path, pfmri, pub, trust_anchors,
+ sig_required_names, use_crls):
+ """Verify signatures on a given FMRI."""
+ m = self._get_manifest(pfmri)
+ errors = []
+ for sig in m.gen_actions_by_type("signature"):
+ try:
+ sig.verify_sig(m.gen_actions(), pub,
+ trust_anchors, use_crls,
+ required_names=sig_required_names)
+ except apx.UnverifiedSignature, e:
+ errors.append((REPO_VERIFY_BADSIG, path,
+ {"err": str(e), "pkg": pfmri}))
+ except apx.CertificateException, e:
+ errors.append((REPO_VERIFY_BADSIG, path,
+ {"err": str(e), "pkg": pfmri}))
+ return errors
+
+ def __verify_permissions(self):
+ """Determine if any of the files or directories in the
+ repository are not readable by either the pkg5srv user or group,
+ or are not world readable. We return as soon as we find one
+ inaccessible file, returning the name of that file or directory.
+
+ We also walk up the directory path from the repository to '/'
+ checking that the repository would be accessible.
+
+ We only return the first file found because for large
+ repositories with many files affected, we'd be flooding the user
+ with information.
+
+ This check exists as well as verify_perm() since it causes a
+ warning to be emitted if non-world readable files are found,
+ whereas verify_perm() will emit an error.
+ """
+
+ pkg5srv_uid = \
+ pkg.portable.get_user_by_name("pkg5srv", "/etc", None)
+ pkg5srv_gid = \
+ pkg.portable.get_group_by_name("pkg5srv", "/etc", None)
+
+ def pkg5srv_readable(st):
+ """Returns true if the pkg5srv user can read
+ this file/dir"""
+ if stat.S_ISDIR(st.st_mode):
+ if (st.st_uid == pkg5srv_uid and
+ st.st_mode & stat.S_IXUSR and
+ st.st_mode & stat.S_IRUSR):
+ return True
+ if (st.st_gid == pkg5srv_gid and
+ st.st_mode & stat.S_IXGRP and
+ st.st_mode & stat.S_IRGRP):
+ return True
+ else:
+ if (st.st_uid == pkg5srv_uid and
+ st.st_mode & stat.S_IRUSR):
+ return True
+ if (st.st_gid == pkg5srv_gid and
+ st.st_mode & stat.S_IRGRP):
+ return True
+ return False
+
+ def world_readable(st):
+ """Returns true if anyone can read this
+ file/dir."""
+ if stat.S_ISDIR(st.st_mode):
+ if (st.st_mode & stat.S_IROTH and
+ st.st_mode & stat.S_IXOTH):
+ return True
+ else:
+ if st.st_mode & stat.S_IROTH:
+ return True
+ return False
+
+ # Scan directories checking file permissions. First we
+ # walk up the directory path from self.__root
+ components = self.__root.split("/")
+ path = ""
+ for comp in components:
+ if not comp:
+ continue
+ path = "%s/%s" % (path, comp)
+ st = os.stat(path)
+ if not (pkg5srv_readable(st) or
+ world_readable(st)):
+ return False, path
+
+ for path, dirnames, filenames in os.walk(self.__root):
+ # we don't care about anything in our quarantine
+ # directory.
+ if REPO_QUARANTINE_DIR in path:
+ continue
+ st = os.stat(path)
+ if not (pkg5srv_readable(st) or
+ world_readable(st)):
+ return False, path
+ for fname in filenames:
+ pth = os.path.join(path, fname)
+ st = os.stat(pth)
+ if not (pkg5srv_readable(st) or
+ world_readable(st)):
+ return False, pth
+ return True, None
+
+ def __gen_verify(self, progtrack, pub, trust_anchors,
+ sig_required_names, use_crls):
+ """A generator that produces verify errors, each a tuple
+ of the form (error_code, path, message, details)"""
+ # We may not have a manifest_root directory if no
+ # packages have ever been published for this publisher.
+ if not os.path.exists(self.manifest_root):
+ return
+
+ err = self.__verify_perm(self.manifest_root, None, None)
+ if err:
+ yield self.__build_verify_error(*err)
+ return
+
+ # Build a list of all of the manifests that must be
+ # verified.
+ mflist = os.listdir(self.manifest_root)
+ goal = len(mflist)
+
+ # If there is more than one version in the manifest dir,
+ # then we add each one to the goal.
+ for name in mflist:
+ try:
+ mfdir = os.path.join(self.manifest_root, name)
+ vers = len(os.listdir(mfdir))
+ if vers > 1:
+ goal += vers - 1
+ except OSError, e:
+ # being unable to read the manifest dir is bad,
+ # but we'll deal with it later
+ continue
+
+ # Add the repo permissions error check to the number of items
+ # goal.
+ goal +=1
+ progtrack.repo_verify_start(goal)
+
+ # Scan the entire repository for bad file permissions
+ progtrack.repo_verify_start_pkg(None, repository_scan=True)
+ valid_perms, path = self.__verify_permissions()
+ if not valid_perms:
+ # We intentionally don't use the path as the first
+ # argument to __build_verify_error(..) as we do not want
+ # 'pkgrepo fix' to attempt to fix this particular error,
+ # since it can include paths outside the repository.
+ yield self.__build_verify_error(
+ REPO_VERIFY_WARN_OPENPERMS, None,
+ {"permissionspath": path, "pub": pub.prefix})
+ progtrack.repo_verify_end_pkg(None)
+
+ for name in mflist:
+ pdir = os.path.join(self.manifest_root, name)
+ err = self.__verify_perm(pdir, None, None)
+ if err:
+ yield self.__build_verify_error(*err)
+ continue
+
+ # Stem must be decoded before use.
+ try:
+ pname = urllib.unquote(name)
+ except Exception:
+ # Assume error is result of an
+ # unexpected file in the directory. We
+ # don't know the FMRI here, so use None.
+ progtrack.repo_verify_start_pkg(None)
+ progtrack.repo_verify_add_progress(None)
+ yield self.__build_verify_error(
+ REPO_VERIFY_UNKNOWN, pdir, {"err": str(e)})
+ progtrack.repo_verify_end_pkg(None)
+ continue
+
+ for ver in os.listdir(pdir):
+ path = os.path.join(pdir, ver)
+ # Version must be decoded before
+ # use.
+ pver = urllib.unquote(ver)
+ try:
+ pfmri = fmri.PkgFmri("@".join((pname,
+ pver)),
+ publisher=self.publisher)
+ if not os.path.isfile(path):
+ raise Exception(
+ "%s is not a file" % path)
+ except Exception, e:
+ # Assume the error is result of an
+ # unexpected file in the directory. We
+ # don't know the FMRI here, so use None.
+ progtrack.repo_verify_start_pkg(None)
+ progtrack.repo_verify_add_progress(None)
+ yield self.__build_verify_error(
+ REPO_VERIFY_UNKNOWN, path,
+ {"err": str(e)})
+ progtrack.repo_verify_end_pkg(None)
+ continue
+
+ progtrack.repo_verify_start_pkg(pfmri)
+ err = self.__verify_manifest(path, pfmri)
+ if err:
+ # with a bad manifest, we can go no
+ # further
+ yield self.__build_verify_error(*err)
+ progtrack.repo_verify_end_pkg(None)
+ continue
+
+ hashes, errors = self.__get_hashes(path, pfmri)
+ for err in errors:
+ yield self.__build_verify_error(*err)
+
+ # verify manifest signatures
+ errs = self.__verify_signature(path, pfmri, pub,
+ trust_anchors, sig_required_names, use_crls)
+ for err in errs:
+ yield self.__build_verify_error(*err)
+
+ # verify payload delivered by this pkg
+ errors = []
+ for h in hashes:
+ try:
+ path = self.cache_store.lookup(
+ h, check_existence=False)
+ except apx.PermissionsException, e:
+ # if we can't even get the path
+ # within the repository, then
+ # we'll do the best we can to
+ # report the problem.
+ errors.append((REPO_VERIFY_PERM,
+ pfmri, {"hash": h,
+ "err": _("Permission "
+ "denied.", "path", h)}))
+ continue
+
+ err = self.__verify_perm(path, pfmri, h)
+ if err:
+ errors.append(err)
+ continue
+ err = self.__verify_hash(path, pfmri, h)
+ if err:
+ errors.append(err)
+ for err in errors:
+ yield self.__build_verify_error(*err)
+
+ progtrack.repo_verify_end_pkg(fmri)
+ progtrack.job_done(progtrack.JOB_REPO_VERIFY_REPO)
+
+ def verify(self, pub=None, progtrack=None,
+ trust_anchor_dir=None, sig_required_names=None, use_crls=False):
+ """A generator which verifies the contents of the repository
+ store, checking for several different types of errors.
+ No modifying operations may be performed until complete.
+
+ 'progtrack' is an optional ProgressTracker object.
+
+ 'trust_anchor_dir' is set in the repository configuration and
+ corresponds to the image property of the same name.
+
+ 'sig_required_names' is set in the repository configuration and
+ corresponds to the image property of the same name.
+
+ 'use_crls' is set in the repository configuration and
+ corresponds to the image property of the same name.
+
+ The generator yields tuples of the form:
+
+ (error_code, path, message, reason) where
+
+ 'error_code' an integer error, correponding to REPO_VERIFY_*
+ 'path' the path to the broken file in the repository
+ 'message' a human-readable summary of the error
+ 'reason' a dictionary of strings containing more detail
+ about the nature of the error.
+ """
+
+ if not self.catalog_root or self.catalog_version < 1:
+ raise RepositoryUnsupportedOperationError()
+ if not self.manifest_root:
+ raise RepositoryUnsupportedOperationError()
+
+ if not progtrack:
+ progtrack = progtrack.NullProgressTracker()
+
+ # For signature verification, we need to setup a publisher
+ # meta_root, and build a dictionary of trust-anchors.
+ tmp_metaroot = tempfile.mkdtemp(prefix="pkgrepo-verify.")
+ pub.meta_root = tmp_metaroot
+ trust_anchors = {}
+
+ if not os.path.isdir(trust_anchor_dir):
+ raise RepositorySigNoTrustAnchorDirError(
+ trust_anchor_dir)
+
+ for fn in os.listdir(trust_anchor_dir):
+ pth = os.path.join(trust_anchor_dir, fn)
+ if os.path.islink(pth):
+ continue
+ trusted_ca = m2.X509.load_cert(pth)
+ # M2Crypto's subject hash doesn't match
+ # openssl's subject hash so recompute it so all
+ # hashes are in the same universe.
+ s = trusted_ca.get_subject().as_hash()
+ trust_anchors.setdefault(s, [])
+ trust_anchors[s].append(trusted_ca)
+
+ self.__lock_rstore()
+ try:
+ for err in self.__gen_verify(progtrack, pub,
+ trust_anchors, sig_required_names, use_crls):
+ yield err
+ except (Exception, EnvironmentError), e:
+ import traceback
+ traceback.print_exc(e)
+ raise apx._convert_error(e)
+ finally:
+ self.__unlock_rstore()
+ shutil.rmtree(tmp_metaroot)
+
+ def fix(self, pub=None, progtrack=None, verify_callback=None,
+ trust_anchor_dir=None, sig_required_names=None, use_crls=False):
+ """Verify, then quarantine any packages in the repository that
+ were found to be faulty, according to self.verify(..).
+
+ This method yields tuples of the form:
+
+ (status_code, fmri, message, reason) where
+
+ 'status_code' an int status code, corresponding to REPO_FIX_*
+ 'path' the path that was fixed
+ 'message' a summary of the operation performed
+ 'reason' a dictionary of strings describing the operation
+
+ Note, the 'fmri' value may not be a valid FMRI if the manifest
+ being fixed was corrupt, in which case a path to the corrupted
+ manifest in the repository is used instead.
+
+ If any object referred to by a manifest is quarantined, then
+ the manifest for that package is also quarantined, however other
+ files referenced by the manifest are not moved to quarantine
+ in case they are referenced by other packages.
+ """
+
+ if self.read_only:
+ raise RepositoryReadOnlyError()
+
+ if not progtrack:
+ progtrack = progress.NullProgressTracker()
+
+ broken_items = set()
+ for error, path, message, reason in self.verify(pub=pub,
+ progtrack=progtrack, trust_anchor_dir=trust_anchor_dir,
+ sig_required_names=sig_required_names, use_crls=use_crls):
+ if verify_callback:
+ verify_callback(progtrack, (error, path,
+ message, reason))
+ # we don't attempt to fix this error, since it can
+ # involve paths outside the repository.
+ if error == REPO_VERIFY_WARN_OPENPERMS:
+ continue
+ fmri = reason.get("pkg")
+ broken_items.add((path, fmri))
+
+ quarantine_root = None
+
+ def _make_quarantine_root():
+ """Make a directory where we can quarantine content."""
+ quarantine_base = os.path.join(self.root,
+ REPO_QUARANTINE_DIR)
+ if not os.path.exists(quarantine_base):
+ os.mkdir(quarantine_base)
+ qroot = tempfile.mkdtemp(prefix="fix.",
+ dir=quarantine_base)
+ return qroot
+
+ # look for broken package content where the bad file doesn't
+ # match the path to the manifest (eg. file content) and add
+ # the manifest path to the list of broken items, so that we
+ # move the manifest to quarantine as well.
+ for path, fmri in broken_items.copy():
+ if not fmri:
+ continue
+ mpath = self.manifest(fmri)
+ if path == mpath:
+ continue
+ broken_items.add((mpath, fmri))
+
+ progtrack.job_start(progtrack.JOB_REPO_FIX_REPO,
+ goal=len(broken_items))
+
+ # keep a set of all the paths we've applied fixes to
+ fixed_paths = set()
+
+ for path, fmri in broken_items:
+ progtrack.job_add_progress(progtrack.JOB_REPO_FIX_REPO)
+
+ # we've already applied a fix to this path
+ if path in fixed_paths:
+ continue
+
+ # we can't do anything about missing files
+ if not os.path.exists(path):
+ yield (REPO_FIX_ITEM, path,
+ _("Missing file %s must be fixed by "
+ "republishing the package.") % path,
+ {"pkg": fmri})
+ continue
+
+ basename = os.path.basename(path)
+ dir = os.path.dirname(path)
+ dir = dir.replace(self.root, "", 1).lstrip("/")
+ if not quarantine_root:
+ quarantine_root = _make_quarantine_root()
+ qdir = os.path.join(quarantine_root, dir)
+ try:
+ os.makedirs(qdir)
+ except OSError, e:
+ if e.errno != errno.EEXIST:
+ raise
+ dest = os.path.join(qdir, basename)
+ if os.path.exists(dest):
+ # this should never happen, since we have a
+ # unique quarantine root per fix(..) call
+ raise RepositoryQuarantinedPathExistsError()
+
+ message = _("Moving %(src)s to %(dest)s") % {
+ "src": path, "dest": dest}
+ status = REPO_FIX_ITEM
+ reason = {"dest": dest, "pkg": fmri}
+ try:
+ shutil.move(path, qdir)
+ fixed_paths.add(path)
+ except Exception, e:
+ status = REPO_FIX_FAILED
+ message = _("Unable to quarantine %(path)s: "
+ "%(err)s") % {"path": path, "err": e}
+ finally:
+ yield(status, path, message, reason)
+
+ progtrack.job_done(progtrack.JOB_REPO_FIX_REPO)
+
+ if broken_items:
+ self.rebuild()
+
def valid_new_fmri(self, pfmri):
"""Check that the FMRI supplied as an argument would be valid
to add to the repository catalog. This checks to make sure
@@ -1990,7 +2635,7 @@
if self.version > CURRENT_REPO_VERSION:
raise RepositoryVersionError(self.root,
- self.version)
+ self.version, CURRENT_REPO_VERSION)
if self.version == 4:
if self.root and not self.pub_root:
# Don't create the publisher root at this point,
@@ -2849,6 +3494,82 @@
# Update the publisher's configuration.
rstore.update_publisher(pub)
+ def verify(self, progtrack=None, pub=None):
+ """A generator that verifies that repository content matches
+ expected state for all or specified publishers.
+
+ 'progtrack' is an optional ProgressTracker object.
+
+ 'pub' is an optional publisher prefix to limit the operation to.
+
+ The generator yields tuples of the form:
+
+ (error_code, path, message, details) where
+
+ 'error_code' an integer error, correponding to REPO_VERIFY_*
+ 'path' the path to the broken file in the repository
+ 'message' a summary of the error
+ 'details' a dictionary of strings containing more detail
+ about the nature of the error.
+ """
+
+ if self.cfg.get_property("repository", "version") != 4:
+ raise RepositoryInvalidVersionError(self.root,
+ self.cfg.get_property("repository", "version"), 4)
+
+ rstore = self.get_pub_rstore(pub.prefix)
+
+ trust_anchor_dir = self.cfg.get_property("repository",
+ "trust-anchor-directory")
+ sig_required_names = set(self.cfg.get_property("repository",
+ "signature-required-names"))
+ use_crls = self.cfg.get_property("repository",
+ "check-certificate-revocation")
+
+ return rstore.verify(progtrack=progtrack, pub=pub,
+ trust_anchor_dir=trust_anchor_dir,
+ sig_required_names=sig_required_names, use_crls=use_crls)
+
+ def fix(self, progtrack=None, pub=None, verify_callback=None):
+ """A generator that corrects any problems in the repository.
+
+ 'progtrack' is an optional ProgressTracker object.
+
+ 'pub' is an optional publisher prefix to limit the operation to.
+
+ During the operation, we emit progress, printing the details
+ using 'verify_callback', a method which requires the following
+ arguments, progtrack, error_code, message, reason, which
+ correspond exactly to the tuple generated by self.verify(..)
+
+ This method yields tuples of the form:
+
+ (status_code, message, details) where
+
+ 'status_code' an integerstatus code, corresponding to REPO_FIX*
+ 'message' a summary of the operation performed
+ 'details' a dictionary of strings describing the operation
+
+ """
+
+ if self.cfg.get_property("repository", "version") != 4:
+ raise RepositoryInvalidVersionError(self.root,
+ self.cfg.get_property("repository", "version"), 4)
+
+ rstore = self.get_pub_rstore(pub.prefix)
+
+ trust_anchor_dir = self.cfg.get_property("repository",
+ "trust-anchor-directory")
+ sig_required_names = set(self.cfg.get_property("repository",
+ "signature-required-names"))
+ use_crls = self.cfg.get_property("repository",
+ "check-certificate-revocation")
+
+ return rstore.fix(progtrack=progtrack, pub=pub,
+ verify_callback=verify_callback,
+ trust_anchor_dir=trust_anchor_dir,
+ sig_required_names=sig_required_names, use_crls=use_crls)
+
def valid_new_fmri(self, pfmri):
"""Check that the FMRI supplied as an argument would be valid
to add to the repository catalog. This checks to make sure
@@ -2978,6 +3699,10 @@
]),
cfg.PropertySection("repository", [
cfg.PropInt("version"),
+ cfg.Property("trust-anchor-directory",
+ default="/etc/certs/CA/"),
+ cfg.PropList("signature-required-names"),
+ cfg.PropBool("check-certificate-revocation", default=False)
]),
],
}
--- a/src/pkgrepo.py Wed Feb 06 13:01:46 2013 -0800
+++ b/src/pkgrepo.py Thu Mar 07 10:46:12 2013 +0530
@@ -21,7 +21,7 @@
#
#
-# Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
#
PKG_CLIENT_NAME = "pkgrepo"
@@ -50,6 +50,7 @@
import shutil
import sys
import tempfile
+import textwrap
import traceback
import warnings
@@ -155,6 +156,10 @@
section/property[+|-]=[value] ... or
section/property[+|-]=([value]) ...
+ pkgrepo verify [-p publisher ...] -s repo_uri_or_path
+
+ pkgrepo fix [-v] [-p publisher ...] -s repo_uri_or_path
+
pkgrepo help
pkgrepo version
@@ -256,7 +261,7 @@
return sr.Repository(read_only=read_only, root=path)
-def setup_transport(conf, subcommand=None):
+def setup_transport(conf, subcommand=None, verbose=False):
repo_uri = conf.get("repo_uri", None)
if not repo_uri:
usage(_("No repository location specified."), cmd=subcommand)
@@ -1193,6 +1198,250 @@
return EXIT_OK
+verify_error_header = None
+verify_warning_header = None
+verify_reason_headers = None
+
+def __load_verify_msgs():
+ """Since our gettext isn't loaded we need to ensure our globals have
+ correct content by calling this method. These values are used by both
+ fix when in verbose mode, and verify"""
+
+ global verify_error_header
+ global verify_warning_header
+ global verify_reason_headers
+
+ # A map of error detail types to the human-readable description of each
+ # type. These correspond to keys in the dictionary returned by
+ # sr.Repository.verify(..)
+ verify_reason_headers = {
+ "path": _("Repository path"),
+ "actual": _("Computed hash"),
+ "fpath": _("Path"),
+ "permissionspath": _("Path"),
+ "pkg": _("Package"),
+ "err": _("Detail")
+ }
+
+ verify_error_header = _("ERROR")
+ verify_warning_header = _("WARNING")
+
+
+def __fmt_verify(verify_tuple):
+ """Format a verify_tuple, of the form (error, path, message, reason)
+ returning a formatted error message, and an FMRI indicating what
+ packages within the repository are affected. Note that the returned FMRI
+ may not be valid, in which case a path to the broken manifest in the
+ repository is returned instead."""
+
+ error, path, message, reason = verify_tuple
+
+ formatted_message = "%(error_type)16s: %(message)s\n" % \
+ {"error_type": verify_error_header, "message": message}
+ reason["path"] = path
+
+ if error == sr.REPO_VERIFY_BADMANIFEST:
+ reason_keys = ["path", "err"]
+ elif error in [sr.REPO_VERIFY_PERM, sr.REPO_VERIFY_MFPERM]:
+ reason_keys = ["pkg", "path"]
+ elif error == sr.REPO_VERIFY_BADHASH:
+ reason_keys = ["pkg", "path", "actual", "fpath"]
+ elif error == sr.REPO_VERIFY_UNKNOWN:
+ reason_keys = ["path", "err"]
+ elif error == sr.REPO_VERIFY_BADSIG:
+ reason_keys = ["pkg", "path", "err"]
+ elif error == sr.REPO_VERIFY_WARN_OPENPERMS:
+ formatted_message = \
+ "%(error_type)16s: %(message)s\n" % \
+ {"error_type": verify_warning_header, "message": message}
+ reason_keys = ["permissionspath", "err"]
+ else:
+ # A list of the details we provide. Some error codes
+ # have different details associated with them.
+ reason_keys = ["pkg", "path", "fpath"]
+
+
+ # the detailed error message can be long, so we'll wrap it. If what we
+ # have fits on a single line, use it, otherwise begin displaying the
+ # message on the next line.
+ if "err" in reason_keys:
+ err_str = ""
+ lines = textwrap.wrap(reason["err"])
+ if len(lines) != 1:
+ for line in lines:
+ err_str += "%18s\n" % line
+ reason["err"] = "\n" + err_str.rstrip()
+ else:
+ reason["err"] = lines[0]
+
+ for key in reason_keys:
+ # sometimes we don't have the key we want, for example we may
+ # not have a file path from the package if the error is a
+ # missing repository file for a 'license' action (which don't
+ # have 'path' attributes, hence no 'fpath' dictionary entry)
+ if key not in reason:
+ continue
+ formatted_message += "%(key)16s: %(value)s\n" % \
+ {"key": verify_reason_headers[key], "value": reason[key]}
+
+ formatted_message += "\n"
+
+ if error == sr.REPO_VERIFY_WARN_OPENPERMS:
+ return formatted_message, None
+ elif "pkg" in reason:
+ return formatted_message, reason["pkg"]
+ return formatted_message, reason["path"]
+
+
+def subcmd_verify(conf, args):
+ """Verify the repository content (file and manifest content only)."""
+
+ subcommand = "verify"
+ __load_verify_msgs()
+
+ opts, pargs = getopt.getopt(args, "p:s:")
+ pubs = set()
+ for opt, arg in opts:
+ if opt == "-s":
+ conf["repo_uri"] = parse_uri(arg)
+ if opt == "-p":
+ if not misc.valid_pub_prefix(arg):
+ error(_("Invalid publisher prefix '%s'") % arg,
+ cmd=subcommand)
+ pubs.add(arg)
+
+ if pargs:
+ usage(_("command does not take operands"), cmd=subcommand)
+
+ repo_uri = conf.get("repo_uri", None)
+ if not repo_uri:
+ usage(_("A package repository location must be provided "
+ "using -s."), cmd=subcommand)
+
+ if repo_uri.scheme != "file":
+ usage(_("Network repositories are not currently supported "
+ "for this operation."), cmd=subcommand)
+
+ xport, xpub, tmp_dir = setup_transport(conf, subcommand=subcommand)
+ rval, found, pub_data = _get_matching_pubs(subcommand, pubs, xport,
+ xpub)
+ if rval == EXIT_OOPS:
+ return rval
+
+ logger.info("Initiating repository verification.")
+ bad_fmris = set()
+ for pfx in found:
+ progtrack = get_tracker()
+ repo = sr.Repository(root=repo_uri.get_pathname())
+ xpub.prefix = pfx
+ xpub.transport = xport
+ for verify_tuple in repo.verify(pub=xpub, progtrack=progtrack):
+ message, bad_fmri = __fmt_verify(verify_tuple)
+ if bad_fmri:
+ bad_fmris.add(bad_fmri)
+ progtrack.repo_verify_yield_error(bad_fmri, message)
+ if bad_fmris:
+ return EXIT_OOPS
+ return EXIT_OK
+
+
+def subcmd_fix(conf, args):
+ """Fix the repository content (file and manifest content only)
+ For index and catalog content corruption, a rebuild should be
+ performed."""
+
+ subcommand = "fix"
+ __load_verify_msgs()
+ verbose = False
+
+ opts, pargs = getopt.getopt(args, "vp:s:")
+ pubs = set()
+ for opt, arg in opts:
+ if opt == "-s":
+ conf["repo_uri"] = parse_uri(arg)
+ if opt == "-v":
+ verbose = True
+ if opt == "-p":
+ if not misc.valid_pub_prefix(arg):
+ error(_("Invalid publisher prefix '%s'") % arg,
+ cmd=subcommand)
+ pubs.add(arg)
+
+ if pargs:
+ usage(_("command does not take operands"), cmd=subcommand)
+
+ repo_uri = conf.get("repo_uri", None)
+ if not repo_uri:
+ usage(_("A package repository location must be provided "
+ "using -s."), cmd=subcommand)
+
+ if repo_uri.scheme != "file":
+ usage(_("Network repositories are not currently supported "
+ "for this operation."), cmd=subcommand)
+
+ xport, xpub, tmp_dir = setup_transport(conf, subcommand=subcommand)
+ rval, found, pub_data = _get_matching_pubs(subcommand, pubs, xport,
+ xpub)
+ if rval == EXIT_OOPS:
+ return rval
+
+ logger.info("Initiating repository fix.")
+ progtrack = get_tracker()
+
+ def verify_cb(tracker, verify_tuple):
+ """A method passed to sr.Repository.fix(..) to emit verify
+ messages if verbose mode is enabled."""
+ if not verbose:
+ return
+ formatted_message, bad_fmri = __fmt_verify(verify_tuple)
+ tracker.repo_verify_yield_error(bad_fmri, formatted_message)
+
+ repo = sr.Repository(root=repo_uri.get_pathname())
+ broken_fmris = set()
+ failed_fix_paths = set()
+ for pfx in found:
+ xpub.prefix = pfx
+ xpub.transport = xport
+ progtrack = get_tracker()
+ for status_code, path, message, reason in \
+ repo.fix(pub=xpub, progtrack=progtrack,
+ verify_callback=verify_cb):
+ if status_code == sr.REPO_FIX_ITEM:
+ # When we can't get the FMRI, eg. in the case
+ # of a corrupt manifest, use the path instead.
+ fmri = reason["pkg"]
+ if not fmri:
+ fmri = path
+ broken_fmris.add(fmri)
+ if verbose:
+ progtrack.repo_fix_yield_info(fmri,
+ message)
+ else:
+ failed_fix_paths.add(path)
+
+ progtrack.flush()
+ logger.info("")
+
+ if broken_fmris:
+ logger.info(_("Use pkgsend(1) or pkgrecv(1) to republish the\n"
+ "following packages or paths which were quarantined:\n\n\t"
+ "%s") % \
+ "\n\t".join([str(f) for f in broken_fmris]))
+ if failed_fix_paths:
+ logger.info(_("\npkgrepo could not repair the following paths "
+ "in the repository:\n\n\t%s") %
+ "\n\t".join([p for p in failed_fix_paths]))
+
+ if not (broken_fmris or failed_fix_paths):
+ logger.info(_("No repository fixes required."))
+ else:
+ logger.info(_("Repository repairs completed."))
+
+ if failed_fix_paths:
+ return EXIT_OOPS
+ return EXIT_OK
+
+
def main_func():
global_settings.client_name = PKG_CLIENT_NAME
--- a/src/tests/cli/t_pkgrepo.py Wed Feb 06 13:01:46 2013 -0800
+++ b/src/tests/cli/t_pkgrepo.py Thu Mar 07 10:46:12 2013 +0530
@@ -21,7 +21,7 @@
#
#
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
#
import testutils
@@ -35,6 +35,7 @@
import pkg.catalog
import pkg.depotcontroller as dc
import pkg.fmri as fmri
+import pkg.pkggzip
import pkg.misc as misc
import pkg.server.repository as sr
import shutil
@@ -118,7 +119,8 @@
def setUp(self):
pkg5unittest.SingleDepotTestCase.setUp(self)
- self.make_misc_files(["tmp/empty", "tmp/truck1", "tmp/truck2"])
+ self.make_misc_files(["tmp/empty", "tmp/truck1", "tmp/truck2",
+ "tmp/other"])
def test_00_base(self):
"""Verify pkgrepo handles basic option and subcommand parsing
@@ -1632,6 +1634,532 @@
self.pkgrepo("list -s %s -H -F tsv nosuchpackage" % repo_path,
exit=1)
+ def __get_mf_path(self, fmri_str):
+ """Given an FMRI, return the path to its manifest in our
+ repository."""
+
+ path_comps = [self.dc.get_repodir(), "publisher",
+ "test", "pkg"]
+ pfmri = pkg.fmri.PkgFmri(fmri_str)
+ path_comps.append(pfmri.get_name())
+ path_comps.append(pfmri.get_link_path().split("@")[1])
+ return os.path.sep.join(path_comps)
+
+ def __get_file_path(self, path):
+ """Returns the path to a file in the repository. The path name
+ must be present in self.fhashes."""
+
+ fpath = os.path.sep.join([self.dc.get_repodir(), "publisher",
+ "test", "file"])
+ fhash = self.fhashes[path]
+ return os.path.sep.join([fpath, fhash[0:2], fhash])
+
+ def __inject_badhash(self, path, valid_gzip=True):
+ """Corrupt a file in the repository with the given path, where
+ that path is a key in self.fhashes, returning the repository
+ path of the file that was corrupted. If valid_gzip is set,
+ we write a gzip file into the repository, otherwise the file
+ contains plaintext."""
+
+ corrupt_path = self.__get_file_path(path)
+ other_path = os.path.join(self.test_root, "tmp/other")
+
+ with open(other_path, "rb") as other:
+ with open(corrupt_path, "wb") as corrupt:
+ if valid_gzip:
+ gz = pkg.pkggzip.PkgGzipFile(
+ fileobj=corrupt)
+ gz.write(other.read())
+ gz.close()
+ else:
+ corrupt.write(other.read())
+ return corrupt_path
+
+ def __repair_badhash(self, path):
+ """Fix a file in the repository that corresponds to this path.
+ """
+ fpath = self.__get_file_path(path)
+ fixed_path = os.path.join(self.test_root, path)
+ with open(fixed_path, "rb") as fixed:
+ with open(fpath, "wb") as broken:
+ gz = pkg.pkggzip.PkgGzipFile(
+ fileobj=broken)
+ gz.write(fixed.read())
+ gz.close()
+
+ def __inject_badmanifest(self, fmri_str):
+ """Corrupt a manifest in the repository for the given fmri,
+ returning the path to that FMRI."""
+ mpath = self.__get_mf_path(fmri_str)
+ other_path = os.path.join(self.test_root, "tmp/other")
+
+ with open(other_path, "r") as other:
+ with open(mpath, "a") as mf:
+ mf.write(other.read())
+ return mpath
+
+ def __inject_dir_for_manifest(self, fmri_str):
+ """Put a dir in place of a manifest."""
+ mpath = self.__get_mf_path(fmri_str)
+ os.remove(mpath)
+ os.mkdir(mpath)
+ return mpath
+
+ def __inject_nofile(self, path):
+ fpath = self.__get_file_path(path)
+ os.remove(fpath)
+ return fpath
+
+ def __inject_perm(self, path=None, fmri_str=None, parent=False,
+ chown=False):
+ """Set restrictive file permissions on the given path or fmri in
+ the repository. If 'parent' is set to True, we change
+ permissions on the parent directory of the file or fmri.
+ If chown is set, we chown the file and its parent dir to root.
+ """
+ if path:
+ fpath = self.__get_file_path(path)
+ if parent:
+ fpath = os.path.dirname(fpath)
+ os.chmod(fpath, 0000)
+ if chown:
+ os.chown(fpath, 0, 0)
+ os.chown(os.path.dirname(mpath), 0, 0)
+ return fpath
+ elif fmri_str:
+ mpath = self.__get_mf_path(fmri_str)
+ if parent:
+ mpath = os.path.dirname(mpath)
+ os.chmod(mpath, 0000)
+ if chown:
+ os.chown(mpath, 0, 0)
+ os.chown(os.path.dirname(mpath), 0, 0)
+ return mpath
+ else:
+ assert_(False, "Invalid use of __inject_perm(..)!")
+
+ def __repair_perm(self, path, parent=False):
+ """Repair errors introduced by __inject_perm."""
+ if parent:
+ fpath = os.path.dirname(path)
+ else:
+ fpath = path
+ if os.path.isdir(fpath):
+ os.chmod(fpath, misc.PKG_DIR_MODE)
+ elif os.path.isfile(fpath):
+ os.chmod(fpath, misc.PKG_FILE_MODE)
+
+ def __inject_badsig(self, fmri_str):
+ mpath = self.__get_mf_path(fmri_str)
+ with open(mpath, "ab+") as mf:
+ mf.write("set name=pkg.t_pkgrepo.bad_sig value=foo")
+ return mpath
+
+ def __repair_badsig(self, path):
+ mpath_new = path + ".new"
+ with open(path, "rb") as mf:
+ with open(mpath_new, "wb") as mf_new:
+ for line in mf.readlines():
+ if not "pkg.t_pkgrepo.bad_sig" in line:
+ mf_new.write(line)
+ os.rename(mpath_new, path)
+
+ def __inject_unknown(self):
+ pass
+
+ def test_11_verify_badhash(self):
+ """Test that verify finds bad hashes and invalid gzip files."""
+
+ repo_path = self.dc.get_repodir()
+ repo_uri = self.dc.get_repo_url()
+
+ # publish a single package and make sure the repository is clean
+ fmris = self.pkgsend_bulk(repo_path, (self.tree10))
+ self.pkgrepo("-s %s verify" % repo_path, exit=0)
+
+ # break a file in the repository and ensure we spot it
+ bad_hash_path = self.__inject_badhash("tmp/truck1")
+
+ self.pkgrepo("-s %s verify" % repo_path, exit=1)
+ self.assert_(
+ self.output.count("ERROR: Invalid file hash") == 1)
+ self.assert_(bad_hash_path in self.output)
+ self.assert_(fmris[0] in self.output)
+
+ # fix the file in the repository, and publish another package
+ self.__repair_badhash("tmp/truck1")
+ fmris += self.pkgsend_bulk(repo_path, (self.truck10))
+ self.pkgrepo("-s %s verify" % repo_path, exit=0)
+ self.assert_(bad_hash_path not in self.output)
+ bad_hash_path = self.__inject_badhash("tmp/truck1")
+
+ # verify we now get two errors when verifying the repository
+ self.pkgrepo("-s %s verify" % repo_path, exit=1)
+ self.assert_(
+ self.output.count("ERROR: Invalid file hash") == 2)
+ for fmri in fmris:
+ self.assert_(fmri in self.output)
+ self.assert_(bad_hash_path in self.output)
+ # check we also print paths which deliver that corrupted file
+ self.assert_("etc/truck1" in self.output)
+ self.assert_("etc/trailer" in self.output)
+
+ # finally, corrupt another file to see that we can also spot
+ # files that aren't gzipped.
+ fmris += self.pkgsend_bulk(repo_path, (self.truck20))
+ bad_gzip_path = self.__inject_badhash("tmp/truck2",
+ valid_gzip=False)
+ self.pkgrepo("-s %s verify" % repo_path, exit=1)
+ # we should get 3 bad file hashes now, since we've added truck20
+ # which also references etc/truck1, which is bad.
+ self.assert_(
+ self.output.count("ERROR: Invalid file hash") == 3)
+ self.assert_(
+ self.output.count("ERROR: Corrupted gzip file") == 1)
+ self.assert_(bad_gzip_path in self.output)
+
+
+ def test_12_verify_badmanifest(self):
+ """Test that verify finds bad manifests."""
+ repo_path = self.dc.get_repodir()
+
+ # publish a single package
+ fmris = self.pkgsend_bulk(repo_path, (self.tree10))
+
+ # corrupt a manifest and make sure pkglint agrees
+ bad_mf = self.__inject_badmanifest(fmris[0])
+ self.pkglint(bad_mf, exit=1)
+
+ self.pkgrepo("-s %s verify" % repo_path, exit=1)
+ self.assert_(bad_mf in self.output)
+ self.assert_("Corrupt manifest." in self.output)
+
+ # publish more packages, and verify we still get the one error
+ fmris += self.pkgsend_bulk(repo_path, (self.truck10,
+ self.amber10))
+ self.pkgrepo("-s %s verify" % repo_path, exit=1)
+ self.assert_(
+ self.output.count("ERROR: Corrupt manifest.") == 1)
+
+ # break another manifest, and check we get two errors
+ another_bad_mf = self.__inject_badmanifest(fmris[-1])
+ self.pkgrepo("-s %s verify" % repo_path, exit=1)
+ self.assert_(another_bad_mf in self.output)
+ self.assert_(bad_mf in self.output)
+ self.assert_(
+ self.output.count("Corrupt manifest.") == 2)
+
+ def test_13_verify_nofile(self):
+ """Test that verify finds missing files."""
+
+ repo_path = self.dc.get_repodir()
+
+ # publish a single package and break it
+ fmris = self.pkgsend_bulk(repo_path, (self.tree10))
+ missing_file = self.__inject_nofile("tmp/truck1")
+
+ self.pkgrepo("-s %s verify" % repo_path, exit=1)
+ self.assert_(missing_file in self.output)
+ self.assert_("ERROR: Missing file: %s" %
+ self.fhashes["tmp/truck1"] in self.output)
+ self.assert_(fmris[0] in self.output)
+
+ # publish another package that also delivers the file
+ # and inject the error again, checking that both manifests
+ # appear in the output
+ fmris += self.pkgsend_bulk(repo_path, (self.truck10))
+ self.__inject_nofile("tmp/truck1")
+
+ self.pkgrepo("-s %s verify" % repo_path, exit=1)
+ self.assert_(missing_file in self.output)
+ self.assert_(self.output.count("ERROR: Missing file: %s" %
+ self.fhashes["tmp/truck1"]) == 2)
+ for f in fmris:
+ self.assert_(f in self.output)
+
+ def test_14_verify_permissions(self):
+ """Check that we can find files and manifests in the
+ repository that have invalid permissions."""
+
+ repo_path = self.dc.get_repodir()
+
+ shutil.rmtree(repo_path)
+ os.mkdir(repo_path)
+ os.chmod(repo_path, 0777)
+ self.pkgrepo("create %s" % repo_path, su_wrap=True)
+ self.pkgrepo("set -s %s publisher/prefix=test" % repo_path,
+ su_wrap=True)
+ # publish a single package and break it
+ fmris = self.pkgsend_bulk(repo_path, (self.truck10),
+ su_wrap=True)
+ bad_path = self.__inject_perm(path="tmp/truck1")
+ self.pkgrepo("-s %s verify" % repo_path, exit=1, su_wrap=True)
+ self.assert_(bad_path in self.output)
+ self.assert_("ERROR: Verification failure" in self.output)
+ self.assert_(fmris[0] in self.output)
+ self.__repair_perm(bad_path)
+
+ # Just break the parent directory, we should still report the
+ # hash file as unreadable
+ bad_parent = self.__inject_perm(path="tmp/truck1", parent=True)
+ self.pkgrepo("-s %s verify" % repo_path, exit=1, su_wrap=True)
+ self.assert_(bad_path in self.output)
+ self.assert_("ERROR: Verification failure" in self.output)
+ self.assert_(fmris[0] in self.output)
+ self.__repair_perm(bad_parent)
+ # break some manifests
+ fmris = self.pkgsend_bulk(repo_path, (self.truck20),
+ su_wrap=True)
+
+ bad_mf_path = self.__inject_perm(fmri_str=fmris[0])
+ self.pkgrepo("-s %s verify" % repo_path, exit=1, su_wrap=True)
+ self.assert_(bad_mf_path in self.output)
+ self.assert_("ERROR: Verification failure" in self.output)
+
+ # this should cause both manifests to report errors
+ bad_mf_path = self.__inject_perm(fmri_str=fmris[0], parent=True)
+ self.pkgrepo("-s %s verify" % repo_path, exit=1, su_wrap=True)
+ self.assert_(bad_mf_path in self.output)
+ self.assert_("ERROR: Verification failure" in self.output)
+ self.__repair_perm(bad_mf_path)
+
+ def test_15_verify_badsig(self):
+ repo_path = self.dc.get_repodir()
+
+ # publish a single package and break it
+ fmris = self.pkgsend_bulk(repo_path, (self.truck10))
+ self.pkgsign(repo_path, "\*")
+ self.pkgrepo("-s %s verify" % repo_path)
+ bad_path = self.__inject_badsig(fmris[0])
+ self.pkgrepo("-s %s verify" % repo_path, exit=1)
+ self.assert_(bad_path in self.output)
+ self.assert_("ERROR: Bad signature." in self.output)
+ self.__repair_badsig(bad_path)
+ self.pkgrepo("-s %s verify" % repo_path, exit=0)
+
+ # now sign with a key, cert and chain cert and check we fail
+ # to verify
+ self.pkgsign_simple(repo_path, "\*")
+ self.pkgrepo("-s %s verify" % repo_path, exit=1)
+ self.assert_("ERROR: Bad signature." in self.output)
+
+ # now set a trust anchor directory, and expect that pkgrepo
+ # verify will now pass
+ ta_dir = os.path.join(self.test_root,
+ "ro_data/signing_certs/produced/ta3")
+ self.pkgrepo("-s %s set repository/trust-anchor-directory=%s" %
+ (repo_path, ta_dir))
+ self.pkgrepo("-s %s verify" % repo_path, exit=0)
+
+ def test_16_verify_warn_openperms(self):
+ """Test that we emit a warning message when the repository is
+ not world-readable."""
+
+ repo_path = self.dc.get_repodir()
+ fmris = self.pkgsend_bulk(repo_path, (self.truck10))
+ os.chmod(self.test_root, 0700)
+ self.pkgrepo("-s %s verify" % repo_path, exit=0)
+ self.assert_("WARNING: " in self.output)
+ self.assert_("svc:/application/pkg/system-repository" \
+ in self.output)
+ self.assert_("ERROR: " not in self.output)
+ os.chmod(self.test_root, 0755)
+ self.pkgrepo("-s %s verify" % repo_path, exit=0)
+ self.assert_("WARNING: " not in self.output)
+
+ def test_17_verify_empty_pub(self):
+ """Test that we can verify a repository that contains a
+ publisher with no packages."""
+ repo_path = self.dc.get_repodir()
+ fmris = self.pkgsend_bulk(repo_path, (self.truck10))
+ self.pkgrepo("-s %s add-publisher empty" % repo_path)
+ self.pkgrepo("-s %s verify -p empty" % repo_path)
+
+ def test_18_verify_invalid_repos(self):
+ """Test that we exit with a usage message for v3 repos and
+ network repositories."""
+ repo_path = self.dc.get_repodir()
+ fmris = self.pkgsend_bulk(repo_path, (self.truck10))
+ depot_uri = self.dc.get_depot_url()
+ self.dc.start()
+ self.pkgrepo("-s %s verify" % depot_uri, exit=2)
+ self.dc.stop()
+ shutil.rmtree(repo_path)
+ self.pkgrepo("create --version=3 %s" % repo_path)
+ self.pkgrepo("set -s %s publisher/prefix=test" % repo_path)
+ fmris = self.pkgsend_bulk(repo_path, (self.truck10))
+ self.pkgrepo("-s %s verify" % repo_path, exit=1)
+
+ def __get_fhashes(self, repodir, pub):
+ """Returns a list of file hashes for the publisher
+ pub in a given repository."""
+ fhashes = []
+ files_dir = os.path.sep.join(["publisher", pub, "file"])
+ for dirpath, dirs, files in os.walk(os.path.join(repodir,
+ files_dir)):
+ fhashes.extend(files)
+ return fhashes
+
+ def test_19_fix_brokenmanifest(self):
+ """Test that fix operations correct a bad manifest in a file
+ repo."""
+
+ repo_path = self.dc.get_repodir()
+ fmris = self.pkgsend_bulk(repo_path, (self.truck10))
+ self.pkgsign(repo_path, "\*")
+
+ self.pkgrepo("-s %s fix" % repo_path)
+
+ valid_hashes = self.__get_fhashes(repo_path, "test")
+ self.debug(valid_hashes)
+ bad_path = self.__inject_badsig(fmris[0])
+ self.pkgrepo("-s %s fix" % repo_path)
+ self.assert_(fmris[0] in self.output)
+ self.assert_(not os.path.exists(bad_path))
+
+ # check our quarantine dir has been created
+ q_dir = os.path.sep.join([repo_path, "publisher", "test",
+ "pkg5-quarantine"])
+ self.assert_(os.path.exists(q_dir))
+
+ # check the broken manifest is in the quarantine dir
+ mf_path_sub = bad_path.replace(
+ os.path.sep.join([repo_path, "publisher", "test"]), "")
+ # quarantined items are stored in a new tmpdir per-session
+ q_dir_tmp = os.listdir(q_dir)[0]
+ q_mf_path = os.path.sep.join([q_dir, q_dir_tmp, mf_path_sub])
+ self.assert_(os.path.exists(q_mf_path))
+
+ # make sure the package no longer appears in the catalog
+ self.pkgrepo("-s %s list -F tsv" % repo_path)
+ self.assert_(fmris[0] not in self.output)
+
+ # ensure that only the manifest was quarantined - file hashes
+ # were left alone.
+ remaining_hashes = self.__get_fhashes(repo_path, "test")
+ self.assert_(set(valid_hashes) == set(remaining_hashes))
+
+ # finally, ensure we can republish this package
+ fmris = self.pkgsend_bulk(repo_path, (self.truck10))
+ self.pkgrepo("-s %s list -F tsv" % repo_path)
+ self.assert_(fmris[0] in self.output)
+
+ def test_20_fix_brokenfile(self):
+ """Test that operations that cause us to fix a file shared
+ by several packages cause all of those packages to be
+ quarantined.
+
+ This also tests the -v option of pkg fix, which prints the
+ pkgrepo verify output and prints details of which files are
+ being quarantined.
+ """
+
+ repo_path = self.dc.get_repodir()
+ fmris = self.pkgsend_bulk(repo_path, (self.tree10,
+ self.truck10))
+ self.pkgrepo("-s %s fix" % repo_path)
+ bad_file = self.__inject_badhash("tmp/truck1")
+
+ old_hashes = self.__get_fhashes(repo_path, "test1")
+
+ self.pkgrepo("-s %s fix -v" % repo_path)
+
+ # since the file was shared by two manifests, we should get
+ # the manifest name printed twice: once when we encounter the
+ # broken file, then again when we print the summary of which
+ # packages need to be republished.
+ self.assert_(self.output.count(fmris[0]) == 2)
+ self.assert_(self.output.count(fmris[1]) == 2)
+ self.assert_(self.output.count("ERROR: Invalid file hash") == 2)
+
+ # the bad file name gets printed 3 times, once for each time
+ # a manifest references it and verify discovered the error,
+ # and once when we report where the file was moved to.
+ self.assert_(self.output.count(bad_file) == 3)
+
+ # check the broken file is in the quarantine dir and that
+ # we printed where it moved to
+ bad_file_sub = bad_file.replace(
+ os.path.sep.join([repo_path, "publisher", "test"]), "")
+ # quarantined items are stored in a new tmpdir per-session
+ q_dir = os.path.sep.join([repo_path, "publisher", "test",
+ "pkg5-quarantine"])
+ q_dir_tmp = os.listdir(q_dir)[0]
+ q_file_path = os.path.normpath(
+ os.path.sep.join([q_dir, q_dir_tmp, bad_file_sub]))
+ self.assert_(os.path.exists(q_file_path))
+ self.debug(q_file_path)
+ self.assert_(q_file_path in self.output)
+
+ remaining_hashes = self.__get_fhashes(repo_path, "test1")
+ # check that we only quarantined the bad hash file
+ self.assert_(set(remaining_hashes) == \
+ set(old_hashes) - set(os.path.basename(bad_file)))
+
+ # Make sure the repository is now clean, and remains so even
+ # after we republish the packages, and that all file content is
+ # replaced.
+ self.pkgrepo("-s %s fix" % repo_path)
+ fmris = self.pkgsend_bulk(repo_path, (self.tree10,
+ self.truck10))
+ new_hashes = self.__get_fhashes(repo_path, "test1")
+ self.assert_(set(new_hashes) == set(old_hashes))
+ self.pkgrepo("-s %s fix" % repo_path)
+
+ def test_21_fix_brokenperm(self):
+ """Tests that when running fix as an unpriviliged user that we
+ fail to fix the repository."""
+
+ repo_path = self.dc.get_repodir()
+
+ shutil.rmtree(repo_path)
+ os.mkdir(repo_path)
+ os.chmod(repo_path, 0777)
+ self.pkgrepo("create %s" % repo_path, su_wrap=True)
+ self.pkgrepo("set -s %s publisher/prefix=test" % repo_path,
+ su_wrap=True)
+ # publish a single package and break it
+ fmris = self.pkgsend_bulk(repo_path, (self.truck10),
+ su_wrap=True)
+ # this breaks the permissions of one of the manifests and
+ # chowns is such that we shouldn't be able to quarantine it.
+ bad_path = self.__inject_perm(fmri_str=fmris[0], chown=True)
+
+ self.pkgrepo("-s %s fix" % repo_path, exit=1, stderr=True,
+ su_wrap=True)
+ self.assert_(bad_path in self.errout)
+ # the fix should succeed now, but not emit any output, because
+ # it's running as root, and the permissions are fine according
+ # to a root user
+ self.pkgrepo("-s %s fix" % repo_path)
+ # but we should still be able to warn about the bad path for
+ # pkg5srv access.
+ self.pkgrepo("-s %s verify" % repo_path)
+ self.assert_("WARNING: " in self.output)
+
+ def test_22_fix_unsupported_repo(self):
+ """Tests that when running fix on a v3 repo fails"""
+
+ repo_path = self.dc.get_repodir()
+ shutil.rmtree(repo_path)
+ self.pkgrepo("create --version=3 %s" % repo_path)
+ self.pkgrepo("-s %s set publisher/prefix=test" % repo_path)
+ fmris = self.pkgsend_bulk(repo_path, (self.truck10))
+ self.pkgrepo("-s %s fix" % repo_path, exit=1)
+ self.assert_("only version 4 repositories are supported." in
+ self.errout)
+
+ def test_23_fix_empty_missing_pub(self):
+ """Test that we can attempt to fix a repository that contains a
+ publisher with no packages, and that we fail on missing pubs"""
+
+ repo_path = self.dc.get_repodir()
+ fmris = self.pkgsend_bulk(repo_path, (self.truck10))
+ self.pkgrepo("-s %s add-publisher empty" % repo_path)
+ self.pkgrepo("-s %s fix -p test" % repo_path)
+ self.pkgrepo("-s %s fix -p missing" % repo_path, exit=1)
+ self.assert_("no matching publishers" in self.errout)
+
if __name__ == "__main__":
unittest.main()
--- a/src/tests/pkg5unittest.py Wed Feb 06 13:01:46 2013 -0800
+++ b/src/tests/pkg5unittest.py Thu Mar 07 10:46:12 2013 +0530
@@ -18,7 +18,7 @@
# CDDL HEADER END
#
-# Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
#
# Define the basic classes that all test cases are inherited from.
@@ -54,6 +54,7 @@
import unittest
import urllib2
import urlparse
+import operator
import platform
import pty
import pwd
@@ -2124,15 +2125,19 @@
return res
-def get_su_wrap_user():
+def get_su_wrap_user(uid_gid=False):
for u in ["noaccess", "nobody"]:
try:
- pwd.getpwnam(u)
+ pw = pwd.getpwnam(u)
+ if uid_gid:
+ return operator.attrgetter(
+ 'pw_uid', 'pw_gid')(pw)
return u
except (KeyError, NameError):
pass
raise RuntimeError("Unable to determine user for su.")
+
class DepotTracebackException(Pkg5CommonException):
def __init__(self, logfile, output):
Pkg5CommonException.__init__(self)
@@ -2416,10 +2421,11 @@
return self.cmdline_run(cmdline, comment=comment, exit=exit,
su_wrap=su_wrap)
- def pkgrepo(self, command, comment="", exit=0, su_wrap=False):
+ def pkgrepo(self, command, comment="", exit=0, su_wrap=False,
+ stderr=False):
cmdline = "%s/usr/bin/pkgrepo %s" % (g_proto_area, command)
return self.cmdline_run(cmdline, comment=comment, exit=exit,
- su_wrap=su_wrap)
+ su_wrap=su_wrap, stderr=stderr)
def pkgsign(self, depot_url, command, exit=0, comment=""):
args = []
@@ -2445,7 +2451,7 @@
return self.pkgsign(depot_url, sign_args, exit=exit)
def pkgsend(self, depot_url="", command="", exit=0, comment="",
- allow_timestamp=False):
+ allow_timestamp=False, su_wrap=False):
args = []
if allow_timestamp:
args.append("-D allow-timestamp")
@@ -2460,7 +2466,8 @@
" ".join(args))
retcode, out = self.cmdline_run(cmdline, comment=comment,
- exit=exit, out=True, prefix=prefix, raise_error=False)
+ exit=exit, out=True, prefix=prefix, raise_error=False,
+ su_wrap=su_wrap)
errout = self.errout
cmdop = command.split(' ')[0]
@@ -2495,7 +2502,7 @@
return retcode, published
def pkgsend_bulk(self, depot_url, commands, exit=0, comment="",
- no_catalog=False, refresh_index=False):
+ no_catalog=False, refresh_index=False, su_wrap=False):
""" Send a series of packaging commands; useful for quickly
doing a bulk-load of stuff into the repo. All commands are
expected to work; if not, the transaction is abandoned. If
@@ -2543,6 +2550,10 @@
for l in accumulate:
os.write(fd, "%s\n" % l)
os.close(fd)
+ if su_wrap:
+ os.chown(f_path,
+ *get_su_wrap_user(
+ uid_gid=True))
try:
cmd = "publish %s -d %s %s" % (
extra_opts, self.test_root,
@@ -2557,7 +2568,8 @@
# package behaviour.
retcode, published = \
self.pkgsend(depot_url, cmd,
- allow_timestamp=True)
+ allow_timestamp=True,
+ su_wrap=su_wrap)
if retcode == 0 and published:
plist.append(published)
except:
@@ -2575,7 +2587,7 @@
if exit == 0 and refresh_index:
self.pkgrepo("-s %s refresh --no-catalog" %
- depot_url)
+ depot_url, su_wrap=su_wrap)
except UnexpectedExitCodeException, e:
if e.exitcode != exit:
raise