Skip to main content

Source code file content

Revision: 3045

18397632 pt gets updated with linked image states even if there are no linked images
» Project Revision History

» Checkout URL

pkg-gate / src / modules / altroot.py

Size: 14666 bytes, 1 line
#!/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) 2011, Oracle and/or its affiliates. All rights reserved.
#

"""
Generic interfaces for manipulating files in an alternate root.  There
routines guarantee that if you perform operations on a specified path, and
that path is contained within "root", then those operations will not affect
any files living outside of "root".  These routines mainly protect us when
accessing paths which could contain symbolic links which might otherwise
redirect us to an unexpected file system object.
"""

# standard python classes
import errno
import os
import stat

# pkg classes
#
# pkg.syscallat is only needed until we have a newer version of python which
# has native support for all the *at(2) system calls we use below.
import pkg.syscallat as sat

# ---------------------------------------------------------------------------
# Misc Functions
#
def __path_abs_to_relative(path):
        """Strip the leading '/' from a path using os.path.split()."""

        path_new = None
        while True:
                (path, tail) = os.path.split(path)
                if not tail:
                        break
                if path_new:
                        path_new = os.path.join(tail, path_new)
                else:
                        path_new = tail
        return path_new

def __fd_to_path(fd):
        """Given a file descriptor return the path to that file descriptor.

        The readlink() call below can return ENOENT due to 6964121.  Normally
        images live on zfs filesystems so this isn't a problem, but the pkg(5)
        test suite runs all its tests on tmpfs filesystems, so this function
        always fails in that case.  Either 6964121 should be fixed before fcs,
        or we should come up with a different safe way to open files in an
        alternate root."""

        path = "/proc/%d/path/%d" % (os.getpid(), fd)
        return os.readlink(path)

# ---------------------------------------------------------------------------
# Functions for accessing files in an alternate image
#
def ar_open(root, path, flags,
    mode=None, create=False, truncate=False):
        """A function similar to os.open() that ensures that the path
        we're accessing resides within a specified directory subtree.

        'root' is a directory that path must reside in.

        'path' is a path that is interpreted relative to 'root'.  i.e., 'root'
        is prepended to path.  'path' can not contain any symbolic links that
        would cause an access to be redirected outside of 'root'.  If this
        happens we'll raise an OSError exception with errno set to EREMOTE

        'mode' optional permissions mask used if we create 'path'

        'create' optional flag indicating if we should create 'path'

        'truncate' optional flag indicating if we should truncate 'path' after
        opening it."""

        # all paths must be absolute
        assert os.path.isabs(root)

        # only allow read/write flags
        assert (flags & ~(os.O_WRONLY|os.O_RDONLY)) == 0

        # we can't truncate a file unless we open it for writing
        assert not truncate or (flags & os.O_WRONLY)

        # if create is true the user must supply a mode mask
        assert not create or mode != None

        # we're going to update root and path so prepare an error
        # message with the existing values now.
        eremote = _("Path outside alternate root: root=%(root)s, "
            "path=%(path)s") % {"root": root, "path": path}

        # make target into a relative path
        if os.path.isabs(path):
                path = __path_abs_to_relative(path)

        # now open the alternate root and get its path
        # done to eliminate any links/mounts/etc in the path
        root_fd = os.open(root, os.O_RDONLY)
        try:
                root = __fd_to_path(root_fd)
        except OSError, e:
                # W0511 XXX / FIXME Comments; pylint: disable=W0511
                # XXX: __fd_to_path() can return ENOENT due to 6964121
                # pylint: enable=W0511
                if e.errno != errno.ENOENT:
                        os.close(root_fd)
                        raise e
        os.close(root_fd)

        # now open the target file, get its path, and make sure it
        # lives in the alternate root
        path_fd = None
        try:
                path_tmp = os.path.join(root, path)
                path_fd = os.open(path_tmp, flags)
        except OSError, e:
                if e.errno != errno.ENOENT or not create:
                        raise e

        assert path_fd or create
        if not path_fd:
                # the file doesn't exist so we should try to create it.
                # we'll do this by first opening the directory which
                # will contain the file and then using openat within
                # that directory.
                path_dir = os.path.dirname(path)
                path_file = os.path.basename(path)
                try:
                        path_dir_fd = \
                            ar_open(root, path_dir, os.O_RDONLY)
                except OSError, e:
                        if e.errno != errno.EREMOTE:
                                raise e
                        raise OSError(errno.EREMOTE, eremote)

                # we opened the directory, now create the file
                try:
                        path_fd = sat.openat(path_dir_fd, path_file,
                            flags|os.O_CREAT|os.O_EXCL, mode)
                except OSError, e:
                        os.close(path_dir_fd)
                        raise e

                # we created the file
                assert path_fd
                os.close(path_dir_fd)

        # verify that the file we opened lives in the alternate root
        try:
                path = __fd_to_path(path_fd)
        except OSError, e:
                # W0511 XXX / FIXME Comments; pylint: disable=W0511
                # XXX: __fd_to_path() can return ENOENT due to 6964121
                # pylint: enable=W0511
                if e.errno != errno.ENOENT:
                        os.close(path_fd)
                        raise e
                path = os.path.join(root, path)

        if not path.startswith(root):
                os.close(path_fd)
                raise OSError(errno.EREMOTE, eremote)

        if truncate:
                # the user wanted us to truncate the file
                try:
                        os.ftruncate(path_fd, 0)
                except OSError, e:
                        os.close(path_fd)
                        raise e

        return path_fd

def ar_unlink(root, path, noent_ok=False):
        """A function similar to os.unlink() that ensures that the path
        we're accessing resides within a specified directory subtree.

        'noent_ok' optional flag indicating if it's ok for 'path' to be
        missing.

        For all other parameters, refer to the 'ar_open' function
        for an explanation of their usage and effects."""

        # all paths must be absolute
        assert os.path.isabs(root)

        # make target into a relative path
        if os.path.isabs(path):
                path = __path_abs_to_relative(path)

        path_dir = os.path.dirname(path)
        path_file = os.path.basename(path)

        try:
                path_dir_fd = ar_open(root, path_dir, os.O_RDONLY)
        except OSError, e:
                if noent_ok and e.errno == errno.ENOENT:
                        return
                raise e

        try:
                sat.unlinkat(path_dir_fd, path_file, 0)
        except OSError, e:
                os.close(path_dir_fd)
                if noent_ok and e.errno == errno.ENOENT:
                        return
                raise e

        os.close(path_dir_fd)
        return

def ar_rename(root, src, dst):
        """A function similar to os.rename() that ensures that the path
        we're accessing resides within a specified directory subtree.

        'src' and 'dst' are paths that are interpreted relative to 'root'.
        i.e., 'root' is prepended to both.  'src' and 'dst' can not contain
        any symbolic links that would cause an access to be redirected outside
        of 'root'.  If this happens we'll raise an OSError exception with
        errno set to EREMOTE

        For all other parameters, refer to the 'ar_open' function
        for an explanation of their usage and effects."""

        # all paths must be absolute
        assert os.path.isabs(root)

        # make target into a relative path
        if os.path.isabs(src):
                src = __path_abs_to_relative(src)
        if os.path.isabs(dst):
                dst = __path_abs_to_relative(dst)

        src_dir = os.path.dirname(src)
        src_file = os.path.basename(src)
        dst_dir = os.path.dirname(dst)
        dst_file = os.path.basename(dst)

        src_dir_fd = ar_open(root, src_dir, os.O_RDONLY)
        try:
                dst_dir_fd = ar_open(root, dst_dir, os.O_RDONLY)
        except OSError, e:
                os.close(src_dir_fd)
                raise e

        try:
                sat.renameat(src_dir_fd, src_file, dst_dir_fd, dst_file)
        except OSError, e:
                os.close(src_dir_fd)
                os.close(dst_dir_fd)
                raise e

        os.close(src_dir_fd)
        os.close(dst_dir_fd)
        return

def ar_mkdir(root, path, mode):
        """A function similar to os.mkdir() that ensures that the path we're
        opening resides within a specified directory subtree.

        For all other parameters, refer to the 'ar_open' function
        for an explanation of their usage and effects."""

        # all paths must be absolute
        assert os.path.isabs(root)

        # make target into a relative path
        if os.path.isabs(path):
                path = __path_abs_to_relative(path)

        path_dir = os.path.dirname(path)
        path_file = os.path.basename(path)

        path_dir_fd = ar_open(root, path_dir, os.O_RDONLY)
        try:
                sat.mkdirat(path_dir_fd, path_file, mode)
        except OSError, e:
                os.close(path_dir_fd)
                raise e

        os.close(path_dir_fd)
        return

def ar_stat(root, path):
        """A function similar to os.stat() that ensures that the path
        we're accessing resides within a specified directory subtree.

        For all other parameters, refer to the 'ar_open' function
        for an explanation of their usage and effects."""

        try:
                fd = ar_open(root, path, os.O_RDONLY)
        except OSError, e:
                raise e
        si = os.fstat(fd)
        os.close(fd)
        return si

def ar_isdir(root, path):
        """A function similar to os.path.isdir() that ensures that the path
        we're accessing resides within a specified directory subtree.

        For all other parameters, refer to the 'ar_open' function
        for an explanation of their usage and effects."""

        try:
                si = ar_stat(root, path)
        except OSError, e:
                if e.errno == errno.ENOENT:
                        return False
                raise e

        if stat.S_ISDIR(si.st_mode):
                return True
        return False

def ar_exists(root, path):
        """A function similar to os.path.exists() that ensures that the path
        we're accessing resides within a specified directory subtree.

        For all other parameters, refer to the 'ar_open' function
        for an explanation of their usage and effects."""

        try:
                fd = ar_open(root, path, os.O_RDONLY)
        except OSError, e:
                if e.errno == errno.ENOENT:
                        return False
                raise e
        os.close(fd)
        return True

def ar_diff(root, path1, path2):
        """A function similar to filecmp.cmp() that ensures that the path
        we're accessing resides within a specified directory subtree.

        For all other parameters, refer to the 'ar_open' function
        for an explanation of their usage and effects."""

        fd1 = fd2 = None

        diff = False
        try:
                fd1 = ar_open(root, path1, os.O_RDONLY)
                fd2 = ar_open(root, path2, os.O_RDONLY)

                while True:
                        b1 = os.read(fd1, 1024)
                        b2 = os.read(fd2, 1024)
                        if len(b1) == 0 and len(b2) == 0:
                                # we're done
                                break
                        if len(b1) != len(b2) or b1 != b2:
                                diff = True
                                break
        except OSError, e:
                if fd1:
                        os.close(fd1)
                if fd2:
                        os.close(fd2)
                raise e

        os.close(fd1)
        os.close(fd2)
        return diff

def ar_img_prefix(root):
        """A function that attempts to determine if a user or root pkg(5)
        managed image can be found at 'root'.  If 'root' does point to a
        pkg(5) image, then we return the relative path to the image metadata
        directory."""

        import pkg.client.image as image

        user_img = False
        root_img = False

        if ar_isdir(root, image.img_user_prefix):
                user_img = True

        if ar_isdir(root, image.img_root_prefix):
                root_img = True

        if user_img and root_img:
                #
                # why would an image have two pkg metadata directories.
                # is this image corrupt?
                #
                return None
        if user_img:
                return image.img_user_prefix
        if root_img:
                return image.img_root_prefix
        return None
 
 
Close
loading
Please Confirm
Close