kiwi-el8/kiwi/path.py
Marcus Schäfer d29ba29276 Add restrictions to Path.remove_hierarchy
When an iso file is used as repo, this iso will be loop mounted on
the host and bind mounted into the image root as long as the image
builds. When the mount is released a recursive cleanup of the complete
path happens. This is done by calling Path.remove_hierarchy. However
if a sub path of the mount path contains a system root directory
which is mandatory for the Linux root system it is not allowed
to be deleted even if it is empty at the time of the mount cleanup.
Thus this patch adds a lookup for protected directory names and
only runs the recursive deletion as long as no protected member
is part of the path. This fixes bsc#1080301
2018-02-12 15:56:49 +01:00

155 lines
4.9 KiB
Python

# Copyright (c) 2015 SUSE Linux GmbH. All rights reserved.
#
# This file is part of kiwi.
#
# kiwi is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# kiwi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with kiwi. If not, see <http://www.gnu.org/licenses/>
#
import os
import collections
# project
from .command import Command
from .logger import log
class Path(object):
"""
Directory path helpers
"""
@classmethod
def sort_by_hierarchy(self, path_list):
"""
Sort given list of path names by their hierachy in the tree
:param list path_list: list of path names
:return: sorted path_list
:rtype: list
"""
paths_at_depth = {}
for path in path_list:
path_elements = path.split('/')
path_depth = len(path_elements)
if path_depth not in paths_at_depth:
paths_at_depth[path_depth] = []
paths_at_depth[path_depth].append(path)
ordered_paths_at_depth = collections.OrderedDict(
sorted(paths_at_depth.items())
)
ordered_paths = []
for path_depth in ordered_paths_at_depth:
for path in ordered_paths_at_depth[path_depth]:
ordered_paths.append(path)
return ordered_paths
@classmethod
def create(self, path):
"""
Create path and all sub directories to target
:param string path: path name
"""
Command.run(
['mkdir', '-p', path]
)
@classmethod
def wipe(self, path):
"""
Delete path and all contents
:param string path: path name
"""
Command.run(
['rm', '-r', '-f', path]
)
@classmethod
def remove(self, path):
"""
Delete empty path, causes an error if target is not empty
:param string path: path name
"""
Command.run(
['rmdir', path]
)
@classmethod
def remove_hierarchy(self, path):
"""
Recursively remove an empty path and its sub directories
ignore non empty or protected paths and leave them untouched
:param string path: path name
"""
Command.run(
['rmdir', '--ignore-fail-on-non-empty', path]
)
path_elements = path.split(os.sep)
protected_elements = [
'boot', 'dev', 'proc', 'run', 'sys', 'tmp'
]
for path_index in reversed(range(0, len(path_elements))):
sub_path = os.sep.join(path_elements[0:path_index])
if path_elements[path_index - 1] in protected_elements:
log.warning(
'remove_hierarchy: path {0} is protected'.format(sub_path)
)
return
Command.run(
['rmdir', '--ignore-fail-on-non-empty', sub_path]
)
@classmethod
def which(
self, filename, alternative_lookup_paths=None,
custom_env=None, access_mode=None
):
"""
Lookup file name in PATH
:param string filename: file base name
:param list alternative_lookup_paths: list of additional lookup paths
:param list custom_env: a custom os.environ
:param int mode: one of the os access modes or a combination of
them (os.R_OK, os.W_OK and os.X_OK). If the provided access mode
does not match the file is considered not existing
"""
lookup_paths = []
multipart_message = [
'"%s": ' % filename, 'exists: unknown', 'mode match: not checked'
]
system_path = os.environ.get('PATH')
if custom_env:
system_path = custom_env.get('PATH')
if system_path:
lookup_paths = system_path.split(os.pathsep)
if alternative_lookup_paths:
lookup_paths += alternative_lookup_paths
multipart_message[0] += 'in paths "%s"' % ':'.join(lookup_paths)
for path in lookup_paths:
location = os.path.join(path, filename)
file_exists = os.path.exists(location)
multipart_message[1] = 'exists: "%s"' % file_exists
if access_mode and file_exists:
mode_match = os.access(location, access_mode)
multipart_message[2] = 'mode match: "%s"' % mode_match
if mode_match:
return location
elif file_exists:
return location
log.debug(' '.join(multipart_message))