directory.py revision 2987
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger#!/usr/bin/python
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger#
e3ae5c822699ae375f274bec0a24c1a0b36b731fJulian Kornberger# CDDL HEADER START
8020ba658ffa8f8c9a14ba3a7d2e9257ea400becTim Reddehase#
8020ba658ffa8f8c9a14ba3a7d2e9257ea400becTim Reddehase# The contents of this file are subject to the terms of the
8020ba658ffa8f8c9a14ba3a7d2e9257ea400becTim Reddehase# Common Development and Distribution License (the "License").
8020ba658ffa8f8c9a14ba3a7d2e9257ea400becTim Reddehase# You may not use this file except in compliance with the License.
e3ae5c822699ae375f274bec0a24c1a0b36b731fJulian Kornberger#
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger# or http://www.opensolaris.org/os/licensing.
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger# See the License for the specific language governing permissions
cfe6bf1bc38ab15b759dee8bb5c86c3727058a2eTim Reddehase# and limitations under the License.
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger#
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger# When distributing Covered Code, include this CDDL HEADER in each
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger# If applicable, add the following below this CDDL HEADER, with the
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger# fields enclosed by brackets "[]" replaced with your own identifying
ef68dc9d6ba7fc331ecdef35f74cff6bb12835b8Tim Reddehase# information: Portions Copyright [yyyy] [name of copyright owner]
ef68dc9d6ba7fc331ecdef35f74cff6bb12835b8Tim Reddehase#
789e293810190b918998fa027c76126e7aa4bf38henning mueller# CDDL HEADER END
ef68dc9d6ba7fc331ecdef35f74cff6bb12835b8Tim Reddehase#
ef68dc9d6ba7fc331ecdef35f74cff6bb12835b8Tim Reddehase
ef68dc9d6ba7fc331ecdef35f74cff6bb12835b8Tim Reddehase#
ef68dc9d6ba7fc331ecdef35f74cff6bb12835b8Tim Reddehase# Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
ef68dc9d6ba7fc331ecdef35f74cff6bb12835b8Tim Reddehase#
ef68dc9d6ba7fc331ecdef35f74cff6bb12835b8Tim Reddehase
ef68dc9d6ba7fc331ecdef35f74cff6bb12835b8Tim Reddehase"""module describing a directory packaging object
3c4b1bd39fa36d241f2ef0d6f7ebbf2a9a6f4d36henning mueller
3c4b1bd39fa36d241f2ef0d6f7ebbf2a9a6f4d36henning muellerThis module contains the DirectoryAction class, which represents a
3c4b1bd39fa36d241f2ef0d6f7ebbf2a9a6f4d36henning muellerdirectory-type packaging object."""
3c4b1bd39fa36d241f2ef0d6f7ebbf2a9a6f4d36henning mueller
6bbb03b1a673f7709df590df43aaed9e8740f671Tim Reddehaseimport errno
6bbb03b1a673f7709df590df43aaed9e8740f671Tim Reddehaseimport generic
8bc65b8b85b830b0432ea7701f2047acd552cedcTim Reddehaseimport os
8bc65b8b85b830b0432ea7701f2047acd552cedcTim Reddehaseimport pkg.portable as portable
6bbb03b1a673f7709df590df43aaed9e8740f671Tim Reddehaseimport pkg.actions
8bc65b8b85b830b0432ea7701f2047acd552cedcTim Reddehaseimport pkg.client.api_errors as apx
8bc65b8b85b830b0432ea7701f2047acd552cedcTim Reddehaseimport stat
8bc65b8b85b830b0432ea7701f2047acd552cedcTim Reddehase
6bbb03b1a673f7709df590df43aaed9e8740f671Tim Reddehaseclass DirectoryAction(generic.Action):
6bbb03b1a673f7709df590df43aaed9e8740f671Tim Reddehase """Class representing a directory-type packaging object."""
6bbb03b1a673f7709df590df43aaed9e8740f671Tim Reddehase
8bc65b8b85b830b0432ea7701f2047acd552cedcTim Reddehase __slots__ = []
8bc65b8b85b830b0432ea7701f2047acd552cedcTim Reddehase
8bc65b8b85b830b0432ea7701f2047acd552cedcTim Reddehase name = "dir"
fd556d3aae9e1b50625e815f5ff9141ddbfecae3Eugen Kuksa key_attr = "path"
fd556d3aae9e1b50625e815f5ff9141ddbfecae3Eugen Kuksa unique_attrs = "path", "mode", "owner", "group"
fd556d3aae9e1b50625e815f5ff9141ddbfecae3Eugen Kuksa globally_identical = True
fd556d3aae9e1b50625e815f5ff9141ddbfecae3Eugen Kuksa refcountable = True
fd556d3aae9e1b50625e815f5ff9141ddbfecae3Eugen Kuksa namespace_group = "path"
fd556d3aae9e1b50625e815f5ff9141ddbfecae3Eugen Kuksa ordinality = generic._orderdict[name]
fd556d3aae9e1b50625e815f5ff9141ddbfecae3Eugen Kuksa
fd556d3aae9e1b50625e815f5ff9141ddbfecae3Eugen Kuksa def compare(self, other):
fd556d3aae9e1b50625e815f5ff9141ddbfecae3Eugen Kuksa return cmp(self.attrs["path"], other.attrs["path"])
fd556d3aae9e1b50625e815f5ff9141ddbfecae3Eugen Kuksa
fd556d3aae9e1b50625e815f5ff9141ddbfecae3Eugen Kuksa def differences(self, other):
fd556d3aae9e1b50625e815f5ff9141ddbfecae3Eugen Kuksa """Returns a list of attributes that have different values
fd556d3aae9e1b50625e815f5ff9141ddbfecae3Eugen Kuksa between 'other' and 'self'. This differs from the generic
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger Action's differences() method in that it normalizes the 'mode'
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger attribute so that, say, '0755' and '755' are treated as
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger identical."""
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger diffs = generic.Action.differences(self, other)
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger
cfe6bf1bc38ab15b759dee8bb5c86c3727058a2eTim Reddehase if "mode" in diffs and \
d7341d330c5d19aef3ad9176971457f034f87ecdTim Reddehase int(self.attrs.get("mode", "0"), 8) == int(other.attrs.get("mode", "0"), 8):
cfe6bf1bc38ab15b759dee8bb5c86c3727058a2eTim Reddehase diffs.remove("mode")
cfe6bf1bc38ab15b759dee8bb5c86c3727058a2eTim Reddehase
836ef862a9f5bd2a1d5c68bbccb11c00b248758aTim Reddehase return diffs
836ef862a9f5bd2a1d5c68bbccb11c00b248758aTim Reddehase
836ef862a9f5bd2a1d5c68bbccb11c00b248758aTim Reddehase def directory_references(self):
cfd2c55efa79cd0073bdfc17e68ff1b05fab7255Tim Reddehase return [os.path.normpath(self.attrs["path"])]
836ef862a9f5bd2a1d5c68bbccb11c00b248758aTim Reddehase
836ef862a9f5bd2a1d5c68bbccb11c00b248758aTim Reddehase def install(self, pkgplan, orig):
cfe6bf1bc38ab15b759dee8bb5c86c3727058a2eTim Reddehase """Client-side method that installs a directory."""
cfd2c55efa79cd0073bdfc17e68ff1b05fab7255Tim Reddehase
cfe6bf1bc38ab15b759dee8bb5c86c3727058a2eTim Reddehase mode = None
cfe6bf1bc38ab15b759dee8bb5c86c3727058a2eTim Reddehase try:
cfe6bf1bc38ab15b759dee8bb5c86c3727058a2eTim Reddehase mode = int(self.attrs.get("mode", None), 8)
6876ece18854869a08606c12e0e814435fa73a29Tim Reddehase except (TypeError, ValueError):
6876ece18854869a08606c12e0e814435fa73a29Tim Reddehase # Mode isn't valid, so let validate raise a more
6876ece18854869a08606c12e0e814435fa73a29Tim Reddehase # informative error.
6876ece18854869a08606c12e0e814435fa73a29Tim Reddehase self.validate(fmri=pkgplan.destination_fmri)
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger omode = oowner = ogroup = None
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger owner, group = self.get_fsobj_uid_gid(pkgplan,
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger pkgplan.destination_fmri)
61467f8ae537212ba3c895868c91235a21985cb8Tim Reddehase if orig:
61467f8ae537212ba3c895868c91235a21985cb8Tim Reddehase try:
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger omode = int(orig.attrs.get("mode", None), 8)
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger except (TypeError, ValueError):
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger # Mode isn't valid, so let validate raise a more
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger # informative error.
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger orig.validate(fmri=pkgplan.origin_fmri)
e437b19c184243f688c2dd9b3bf083a454c804c7Julian Kornberger oowner, ogroup = orig.get_fsobj_uid_gid(pkgplan,
pkgplan.origin_fmri)
path = os.path.normpath(os.path.sep.join((
pkgplan.image.get_root(), self.attrs["path"])))
# Don't allow installation through symlinks.
self.fsobj_checkpath(pkgplan, path)
# XXX Hack! (See below comment.)
if not portable.is_admin():
mode |= stat.S_IWUSR
if not orig:
try:
self.makedirs(path, mode=mode,
fmri=pkgplan.destination_fmri)
except OSError, e:
if e.filename != path:
# makedirs failed for some component
# of the path.
raise
fs = os.lstat(path)
fs_mode = stat.S_IFMT(fs.st_mode)
if e.errno == errno.EROFS:
# Treat EROFS like EEXIST if both are
# applicable, since we'll end up with
# EROFS instead.
if stat.S_ISDIR(fs_mode):
return
raise
elif e.errno != errno.EEXIST:
raise
if stat.S_ISLNK(fs_mode):
# User has replaced directory with a
# link, or a package has been poorly
# implemented. It isn't safe to
# simply re-create the directory as
# that won't restore the files that
# are supposed to be contained within.
err_txt = _("Unable to create "
"directory %s; it has been "
"replaced with a link. To "
"continue, please remove the "
"link or restore the directory "
"to its original location and "
"try again.") % path
raise apx.ActionExecutionError(
self, details=err_txt, error=e,
fmri=pkgplan.destination_fmri)
elif stat.S_ISREG(fs_mode):
# User has replaced directory with a
# file, or a package has been poorly
# implemented. Salvage what's there,
# and drive on.
pkgplan.salvage(path)
os.mkdir(path, mode)
elif stat.S_ISDIR(fs_mode):
# The directory already exists, but
# ensure that the mode matches what's
# expected.
os.chmod(path, mode)
# The downside of chmodding the directory is that as a non-root
# user, if we set perms u-w, we won't be able to put anything in
# it, which is often not what we want at install time. We save
# the chmods for the postinstall phase, but it's always possible
# that a later package install will want to place something in
# this directory and then be unable to. So perhaps we need to
# (in all action types) chmod the parent directory to u+w on
# failure, and chmod it back aftwards. The trick is to
# recognize failure due to missing file_dac_write in contrast to
# other failures. Or can we require that everyone simply have
# file_dac_write who wants to use the tools. Probably not.
elif mode != omode:
os.chmod(path, mode)
# if we're salvaging contents, move 'em now.
# directories with "salvage-from" attribute
# set will scavenge any available contents
# that matches specified directory and
# move it underneath itself on install or update.
# This is here to support directory rename
# when old directory has unpackaged contents, or
# consolidation of content from older directories.
for salvage_from in self.attrlist("salvage-from"):
pkgplan.salvage_from(salvage_from, path)
if not orig or oowner != owner or ogroup != group:
try:
portable.chown(path, owner, group)
except OSError, e:
if e.errno != errno.EPERM and \
e.errno != errno.ENOSYS:
raise
def verify(self, img, **args):
"""Returns a tuple of lists of the form (errors, warnings,
info). The error list will be empty if the action has been
correctly installed in the given image."""
lstat, errors, warnings, info, abort = \
self.verify_fsobj_common(img, stat.S_IFDIR)
return errors, warnings, info
def remove(self, pkgplan):
path = os.path.normpath(os.path.sep.join(
(pkgplan.image.get_root(), self.attrs["path"])))
try:
os.rmdir(path)
except OSError, e:
if e.errno == errno.ENOENT:
pass
elif e.errno in (errno.EEXIST, errno.ENOTEMPTY):
# Cannot remove directory since it's
# not empty.
pkgplan.salvage(path)
elif e.errno == errno.ENOTDIR:
# Either the user or another package has changed
# this directory into a link or file. Salvage
# what's there and drive on.
pkgplan.salvage(path)
elif e.errno == errno.EBUSY and os.path.ismount(path):
# User has replaced directory with mountpoint,
# or a package has been poorly implemented.
if not self.attrs.get("implicit"):
err_txt = _("Unable to remove %s; it is "
"in use as a mountpoint. To "
"continue, please unmount the "
"filesystem at the target "
"location and try again.") % path
raise apx.ActionExecutionError(self,
details=err_txt, error=e,
fmri=pkgplan.origin_fmri)
elif e.errno == errno.EBUSY:
# os.path.ismount() is broken for lofs
# filesystems, so give a more generic
# error.
if not self.attrs.get("implicit"):
err_txt = _("Unable to remove %s; it "
"is in use by the system, another "
"process, or as a mountpoint.") \
% path
raise apx.ActionExecutionError(self,
details=err_txt, error=e,
fmri=pkgplan.origin_fmri)
elif e.errno != errno.EACCES: # this happens on Windows
raise
def generate_indices(self):
"""Generates the indices needed by the search dictionary. See
generic.py for a more detailed explanation."""
return [
(self.name, "basename",
os.path.basename(self.attrs["path"].rstrip(os.path.sep)),
None),
(self.name, "path", os.path.sep + self.attrs["path"],
None)
]
def validate(self, fmri=None):
"""Performs additional validation of action attributes that
for performance or other reasons cannot or should not be done
during Action object creation. An ActionError exception (or
subclass of) will be raised if any attributes are not valid.
This is primarily intended for use during publication or during
error handling to provide additional diagonostics.
'fmri' is an optional package FMRI (object or string) indicating
what package contained this action."""
errors = generic.Action._validate(self, fmri=fmri,
raise_errors=False, required_attrs=("owner", "group"))
errors.extend(self._validate_fsobj_common())
if errors:
raise pkg.actions.InvalidActionAttributesError(self,
errors, fmri=fmri)