src/modules/actions/hardlink.py
author Shawn Walker <shawn.walker@oracle.com>
Fri, 11 Mar 2011 10:43:38 -0800
changeset 2254 ab6a2324f73a
parent 2205 53d0be594162
permissions -rw-r--r--
17096 multi-valued path attributes cause file, link, and directory action traceback

#!/usr/bin/python
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#

#
# Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
#

"""module describing a (hard) link packaging object

This module contains the HardLinkAction class, which represents a hardlink-type
packaging object."""

import errno
import link
import os
import stat

from pkg import misc
import pkg.actions
from pkg.client.api_errors import ActionExecutionError


class HardLinkAction(link.LinkAction):
        """Class representing a hardlink-type packaging object."""

        __slots__ = []

        name = "hardlink"

        # Tells parent class that leading slash should not be stripped
        # from "path" attribute.
        _strip_path = False

        def get_target_path(self):
                """ return a path for target that is relative to image"""

                target = self.attrs["target"]

                # paths are either relative to path or absolute;
                # both need to be passed through os.path.normpath to ensure
                # that all ".." are removed to constrain target to image

                if target[0] != "/":
                        path = self.attrs["path"]
                        target = os.path.normpath(
                            os.path.join(os.path.split(path)[0], target))
                else:
                        target = os.path.normpath(target)[1:]

                return target

        def install(self, pkgplan, orig):
                """Client-side method that installs a hard link."""

                path = self.attrs["path"]
                target = self.get_target_path()

                path = os.path.normpath(os.path.sep.join(
                    (pkgplan.image.get_root(), path)))

                # Don't allow installation through symlinks.
                self.fsobj_checkpath(pkgplan, path)

                if not os.path.exists(os.path.dirname(path)):
                        self.makedirs(os.path.dirname(path),
                            mode=misc.PKG_DIR_MODE,
                            fmri=pkgplan.destination_fmri)
                elif os.path.exists(path):
                        self.remove(pkgplan)

                fulltarget = os.path.normpath(os.path.sep.join(
                    (pkgplan.image.get_root(), target)))

                try:
                        os.link(fulltarget, path)
                except EnvironmentError, e:
                        if e.errno != errno.ENOENT:
                                raise ActionExecutionError(self, error=e)

                        # User or another process has removed target for
                        # hardlink, a package hasn't declared correct
                        # dependencies, or the target hasn't been installed
                        # yet.
                        err_txt = _("Unable to create hard link %(path)s; "
                            "target %(target)s is missing.") % {
                            "path": path, "target": fulltarget }
                        raise ActionExecutionError(self, details=err_txt,
                            error=e, fmri=pkgplan.destination_fmri)

        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."""

                #
                # We only allow hard links to regular files, so the hard
                # link should lstat() as a regular file.
                #
                lstat, errors, warnings, info, abort = \
                    self.verify_fsobj_common(img, stat.S_IFREG)
                if abort:
                        assert errors
                        return errors, warnings, info

                target = self.get_target_path()
                path = os.path.normpath(os.path.sep.join(
                    (img.get_root(), self.attrs["path"])))
                target = os.path.normpath(os.path.sep.join(
                    (img.get_root(), target)))

                if not os.path.exists(target):
                        errors.append(_("Target '%s' does not exist") %
                            self.attrs["target"])

                # No point in continuing if no target
                if errors:
                        return errors, warnings, info

                try:
                        if os.stat(path).st_ino != os.stat(target).st_ino:
                                errors.append(_("Broken: Path and Target (%s) "
                                    "inodes not the same") %
                                    self.get_target_path())
                except OSError, e:
                        errors.append(_("Unexpected Error: %s") % e)

                return errors, warnings, info