kiwi.mount_manager

  1# Copyright (c) 2015 SUSE Linux GmbH.  All rights reserved.
  2#
  3# This file is part of kiwi.
  4#
  5# kiwi is free software: you can redistribute it and/or modify
  6# it under the terms of the GNU General Public License as published by
  7# the Free Software Foundation, either version 3 of the License, or
  8# (at your option) any later version.
  9#
 10# kiwi is distributed in the hope that it will be useful,
 11# but WITHOUT ANY WARRANTY; without even the implied warranty of
 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13# GNU General Public License for more details.
 14#
 15# You should have received a copy of the GNU General Public License
 16# along with kiwi.  If not, see <http://www.gnu.org/licenses/>
 17#
 18import os
 19import time
 20import logging
 21from textwrap import dedent
 22from typing import (
 23    List, Dict
 24)
 25
 26# project
 27from kiwi.path import Path
 28from kiwi.utils.temporary import Temporary
 29from kiwi.command import Command
 30from kiwi.exceptions import KiwiCommandError, KiwiUmountBusyError
 31
 32log = logging.getLogger('kiwi')
 33
 34
 35class MountManager:
 36    """
 37    **Implements methods for mounting, umounting and mount checking**
 38
 39    The caller is responsible for unmounting the device if the MountManager is
 40    used as is.
 41
 42    The class also supports to be used as a context manager, where the device is
 43    unmounted once the context manager's with block is left
 44
 45    * :param string device: device node name
 46    * :param string mountpoint: mountpoint directory name
 47    * :param dict attributes: optional attributes to store
 48    """
 49    def __init__(
 50        self, device: str, mountpoint: str = '',
 51        attributes: Dict[str, str] = {}
 52    ):
 53        self.device = device
 54        self.attributes = attributes
 55        if not mountpoint:
 56            self.mountpoint_tempdir = Temporary(
 57                prefix='kiwi_mount_manager.'
 58            ).new_dir()
 59            self.mountpoint = self.mountpoint_tempdir.name
 60        else:
 61            Path.create(mountpoint)
 62            self.mountpoint = mountpoint
 63
 64    def __enter__(self) -> "MountManager":
 65        return self
 66
 67    def __exit__(self, exc_type, exc_value, traceback) -> None:
 68        self.umount()
 69
 70    def get_attributes(self) -> Dict[str, str]:
 71        """
 72        Return attributes dict for this mount manager
 73        """
 74        return self.attributes
 75
 76    def bind_mount(self) -> None:
 77        """
 78        Bind mount the device to the mountpoint
 79        """
 80        if self.device and not self.is_mounted():
 81            Command.run(
 82                ['mount', '-n', '--bind', self.device, self.mountpoint]
 83            )
 84
 85    def overlay_mount(self, lower: str) -> None:
 86        self.device = 'overlay'
 87        self.lower = lower
 88        self.upper = f'{self.mountpoint}_cow'
 89        self.work = f'{self.mountpoint}_work'
 90        Path.create(self.upper)
 91        Path.create(self.work)
 92        if not self.is_mounted():
 93            Command.run(
 94                [
 95                    'mount', '-t', 'overlay',
 96                    self.device, self.mountpoint, '-o',
 97                    'lowerdir={0},upperdir={1},workdir={2}'.format(
 98                        lower, self.upper, self.work
 99                    )
100                ]
101            )
102
103    def tmpfs_mount(self) -> None:
104        """
105        tmpfs mount the device to the mountpoint
106        """
107        if not self.is_mounted():
108            Command.run(
109                ['mount', '-t', 'tmpfs', 'tmpfs', self.mountpoint]
110            )
111
112    def mount(self, options: List[str] = []) -> None:
113        """
114        Standard mount the device to the mountpoint
115
116        :param list options: mount options
117        """
118        if self.device and not self.is_mounted():
119            option_list = []
120            if options:
121                option_list = ['-o'] + options
122            Command.run(
123                ['mount'] + option_list + [self.device, self.mountpoint]
124            )
125
126    def umount_lazy(self) -> None:
127        """
128        Umount by the mountpoint directory in lazy mode
129
130        Release the mount in any case, however the time when the mounted
131        resource is released by the kernel depends on when the resource
132        enters the non busy state
133        """
134        if self.is_mounted():
135            Command.run(['umount', '-l', self.mountpoint])
136
137    def umount(self, raise_on_busy: bool = True) -> bool:
138        """
139        Umount by the mountpoint directory
140
141        Wait up to 10sec trying to umount. If the resource stays
142        busy the call will raise an exception unless raise_on_busy
143        is set to False. In case the umount failed and raise_on_busy
144        is set to False, the method returns False to indicate the
145        error condition.
146
147        :return: True or False
148
149        :rtype: bool
150        """
151        if self.is_mounted():
152            umounted_successfully = False
153            for busy in range(0, 5):
154                try:
155                    Command.run(['umount', self.mountpoint])
156                    umounted_successfully = True
157                    break
158                except KiwiCommandError as err:
159                    log.warning(
160                        f'{busy} umount of {self.mountpoint} failed with: {err}'
161                    )
162                    time.sleep(1)
163            if not umounted_successfully:
164                try:
165                    Command.run(['umount', '--lazy', self.mountpoint])
166                    umounted_successfully = True
167                except KiwiCommandError as err:
168                    log.error(
169                        f'umount of {self.mountpoint} failed with: {err}'
170                    )
171            if not umounted_successfully:
172                if raise_on_busy:
173                    lsof = Path.which('lsof', access_mode=os.X_OK)
174                    if lsof:
175                        open_files = Command.run(
176                            [lsof, '+c', '0', self.mountpoint],
177                            raise_on_error=False
178                        )
179                        open_files_info = 'Open files status:{0}{1}'.format(
180                            os.linesep, open_files.output
181                        )
182                    else:
183                        open_files_info = 'For further details install: lsof'
184                    message = dedent('''\n
185                        Failed to umount: {0}.
186
187                        Your build host system is in an inconsistent state.
188                        The cleanup of the created resource was not possible
189                        because it is still busy. This resource and all nested
190                        resources stays active on your host and needs a manual
191                        cleanup.
192
193                        Please do not use the intermediate state of the image
194                        files created so far. There is no guarantee that the
195                        produced results are valid.
196
197                        {1}
198                    ''')
199                    raise KiwiUmountBusyError(
200                        message.format(self.mountpoint, open_files_info)
201                    )
202                else:
203                    log.warning(
204                        '{0} still busy at {1}'.format(
205                            self.mountpoint, type(self).__name__
206                        )
207                    )
208                    # skip removing the mountpoint directory
209                    return False
210        return True
211
212    def is_mounted(self) -> bool:
213        """
214        Check if mounted
215
216        :return: True or False
217
218        :rtype: bool
219        """
220        mountpoint_call = Command.run(
221            command=['mountpoint', '-q', self.mountpoint],
222            raise_on_error=False
223        )
224        return mountpoint_call.returncode == 0
log = <Logger kiwi (DEBUG)>
class MountManager:
 36class MountManager:
 37    """
 38    **Implements methods for mounting, umounting and mount checking**
 39
 40    The caller is responsible for unmounting the device if the MountManager is
 41    used as is.
 42
 43    The class also supports to be used as a context manager, where the device is
 44    unmounted once the context manager's with block is left
 45
 46    * :param string device: device node name
 47    * :param string mountpoint: mountpoint directory name
 48    * :param dict attributes: optional attributes to store
 49    """
 50    def __init__(
 51        self, device: str, mountpoint: str = '',
 52        attributes: Dict[str, str] = {}
 53    ):
 54        self.device = device
 55        self.attributes = attributes
 56        if not mountpoint:
 57            self.mountpoint_tempdir = Temporary(
 58                prefix='kiwi_mount_manager.'
 59            ).new_dir()
 60            self.mountpoint = self.mountpoint_tempdir.name
 61        else:
 62            Path.create(mountpoint)
 63            self.mountpoint = mountpoint
 64
 65    def __enter__(self) -> "MountManager":
 66        return self
 67
 68    def __exit__(self, exc_type, exc_value, traceback) -> None:
 69        self.umount()
 70
 71    def get_attributes(self) -> Dict[str, str]:
 72        """
 73        Return attributes dict for this mount manager
 74        """
 75        return self.attributes
 76
 77    def bind_mount(self) -> None:
 78        """
 79        Bind mount the device to the mountpoint
 80        """
 81        if self.device and not self.is_mounted():
 82            Command.run(
 83                ['mount', '-n', '--bind', self.device, self.mountpoint]
 84            )
 85
 86    def overlay_mount(self, lower: str) -> None:
 87        self.device = 'overlay'
 88        self.lower = lower
 89        self.upper = f'{self.mountpoint}_cow'
 90        self.work = f'{self.mountpoint}_work'
 91        Path.create(self.upper)
 92        Path.create(self.work)
 93        if not self.is_mounted():
 94            Command.run(
 95                [
 96                    'mount', '-t', 'overlay',
 97                    self.device, self.mountpoint, '-o',
 98                    'lowerdir={0},upperdir={1},workdir={2}'.format(
 99                        lower, self.upper, self.work
100                    )
101                ]
102            )
103
104    def tmpfs_mount(self) -> None:
105        """
106        tmpfs mount the device to the mountpoint
107        """
108        if not self.is_mounted():
109            Command.run(
110                ['mount', '-t', 'tmpfs', 'tmpfs', self.mountpoint]
111            )
112
113    def mount(self, options: List[str] = []) -> None:
114        """
115        Standard mount the device to the mountpoint
116
117        :param list options: mount options
118        """
119        if self.device and not self.is_mounted():
120            option_list = []
121            if options:
122                option_list = ['-o'] + options
123            Command.run(
124                ['mount'] + option_list + [self.device, self.mountpoint]
125            )
126
127    def umount_lazy(self) -> None:
128        """
129        Umount by the mountpoint directory in lazy mode
130
131        Release the mount in any case, however the time when the mounted
132        resource is released by the kernel depends on when the resource
133        enters the non busy state
134        """
135        if self.is_mounted():
136            Command.run(['umount', '-l', self.mountpoint])
137
138    def umount(self, raise_on_busy: bool = True) -> bool:
139        """
140        Umount by the mountpoint directory
141
142        Wait up to 10sec trying to umount. If the resource stays
143        busy the call will raise an exception unless raise_on_busy
144        is set to False. In case the umount failed and raise_on_busy
145        is set to False, the method returns False to indicate the
146        error condition.
147
148        :return: True or False
149
150        :rtype: bool
151        """
152        if self.is_mounted():
153            umounted_successfully = False
154            for busy in range(0, 5):
155                try:
156                    Command.run(['umount', self.mountpoint])
157                    umounted_successfully = True
158                    break
159                except KiwiCommandError as err:
160                    log.warning(
161                        f'{busy} umount of {self.mountpoint} failed with: {err}'
162                    )
163                    time.sleep(1)
164            if not umounted_successfully:
165                try:
166                    Command.run(['umount', '--lazy', self.mountpoint])
167                    umounted_successfully = True
168                except KiwiCommandError as err:
169                    log.error(
170                        f'umount of {self.mountpoint} failed with: {err}'
171                    )
172            if not umounted_successfully:
173                if raise_on_busy:
174                    lsof = Path.which('lsof', access_mode=os.X_OK)
175                    if lsof:
176                        open_files = Command.run(
177                            [lsof, '+c', '0', self.mountpoint],
178                            raise_on_error=False
179                        )
180                        open_files_info = 'Open files status:{0}{1}'.format(
181                            os.linesep, open_files.output
182                        )
183                    else:
184                        open_files_info = 'For further details install: lsof'
185                    message = dedent('''\n
186                        Failed to umount: {0}.
187
188                        Your build host system is in an inconsistent state.
189                        The cleanup of the created resource was not possible
190                        because it is still busy. This resource and all nested
191                        resources stays active on your host and needs a manual
192                        cleanup.
193
194                        Please do not use the intermediate state of the image
195                        files created so far. There is no guarantee that the
196                        produced results are valid.
197
198                        {1}
199                    ''')
200                    raise KiwiUmountBusyError(
201                        message.format(self.mountpoint, open_files_info)
202                    )
203                else:
204                    log.warning(
205                        '{0} still busy at {1}'.format(
206                            self.mountpoint, type(self).__name__
207                        )
208                    )
209                    # skip removing the mountpoint directory
210                    return False
211        return True
212
213    def is_mounted(self) -> bool:
214        """
215        Check if mounted
216
217        :return: True or False
218
219        :rtype: bool
220        """
221        mountpoint_call = Command.run(
222            command=['mountpoint', '-q', self.mountpoint],
223            raise_on_error=False
224        )
225        return mountpoint_call.returncode == 0

Implements methods for mounting, umounting and mount checking

The caller is responsible for unmounting the device if the MountManager is used as is.

The class also supports to be used as a context manager, where the device is unmounted once the context manager's with block is left

  • :param string device: device node name
  • :param string mountpoint: mountpoint directory name
  • :param dict attributes: optional attributes to store
MountManager(device: str, mountpoint: str = '', attributes: Dict[str, str] = {})
50    def __init__(
51        self, device: str, mountpoint: str = '',
52        attributes: Dict[str, str] = {}
53    ):
54        self.device = device
55        self.attributes = attributes
56        if not mountpoint:
57            self.mountpoint_tempdir = Temporary(
58                prefix='kiwi_mount_manager.'
59            ).new_dir()
60            self.mountpoint = self.mountpoint_tempdir.name
61        else:
62            Path.create(mountpoint)
63            self.mountpoint = mountpoint
device
attributes
def get_attributes(self) -> Dict[str, str]:
71    def get_attributes(self) -> Dict[str, str]:
72        """
73        Return attributes dict for this mount manager
74        """
75        return self.attributes

Return attributes dict for this mount manager

def bind_mount(self) -> None:
77    def bind_mount(self) -> None:
78        """
79        Bind mount the device to the mountpoint
80        """
81        if self.device and not self.is_mounted():
82            Command.run(
83                ['mount', '-n', '--bind', self.device, self.mountpoint]
84            )

Bind mount the device to the mountpoint

def overlay_mount(self, lower: str) -> None:
 86    def overlay_mount(self, lower: str) -> None:
 87        self.device = 'overlay'
 88        self.lower = lower
 89        self.upper = f'{self.mountpoint}_cow'
 90        self.work = f'{self.mountpoint}_work'
 91        Path.create(self.upper)
 92        Path.create(self.work)
 93        if not self.is_mounted():
 94            Command.run(
 95                [
 96                    'mount', '-t', 'overlay',
 97                    self.device, self.mountpoint, '-o',
 98                    'lowerdir={0},upperdir={1},workdir={2}'.format(
 99                        lower, self.upper, self.work
100                    )
101                ]
102            )
def tmpfs_mount(self) -> None:
104    def tmpfs_mount(self) -> None:
105        """
106        tmpfs mount the device to the mountpoint
107        """
108        if not self.is_mounted():
109            Command.run(
110                ['mount', '-t', 'tmpfs', 'tmpfs', self.mountpoint]
111            )

tmpfs mount the device to the mountpoint

def mount(self, options: List[str] = []) -> None:
113    def mount(self, options: List[str] = []) -> None:
114        """
115        Standard mount the device to the mountpoint
116
117        :param list options: mount options
118        """
119        if self.device and not self.is_mounted():
120            option_list = []
121            if options:
122                option_list = ['-o'] + options
123            Command.run(
124                ['mount'] + option_list + [self.device, self.mountpoint]
125            )

Standard mount the device to the mountpoint

Parameters
  • list options: mount options
def umount_lazy(self) -> None:
127    def umount_lazy(self) -> None:
128        """
129        Umount by the mountpoint directory in lazy mode
130
131        Release the mount in any case, however the time when the mounted
132        resource is released by the kernel depends on when the resource
133        enters the non busy state
134        """
135        if self.is_mounted():
136            Command.run(['umount', '-l', self.mountpoint])

Umount by the mountpoint directory in lazy mode

Release the mount in any case, however the time when the mounted resource is released by the kernel depends on when the resource enters the non busy state

def umount(self, raise_on_busy: bool = True) -> bool:
138    def umount(self, raise_on_busy: bool = True) -> bool:
139        """
140        Umount by the mountpoint directory
141
142        Wait up to 10sec trying to umount. If the resource stays
143        busy the call will raise an exception unless raise_on_busy
144        is set to False. In case the umount failed and raise_on_busy
145        is set to False, the method returns False to indicate the
146        error condition.
147
148        :return: True or False
149
150        :rtype: bool
151        """
152        if self.is_mounted():
153            umounted_successfully = False
154            for busy in range(0, 5):
155                try:
156                    Command.run(['umount', self.mountpoint])
157                    umounted_successfully = True
158                    break
159                except KiwiCommandError as err:
160                    log.warning(
161                        f'{busy} umount of {self.mountpoint} failed with: {err}'
162                    )
163                    time.sleep(1)
164            if not umounted_successfully:
165                try:
166                    Command.run(['umount', '--lazy', self.mountpoint])
167                    umounted_successfully = True
168                except KiwiCommandError as err:
169                    log.error(
170                        f'umount of {self.mountpoint} failed with: {err}'
171                    )
172            if not umounted_successfully:
173                if raise_on_busy:
174                    lsof = Path.which('lsof', access_mode=os.X_OK)
175                    if lsof:
176                        open_files = Command.run(
177                            [lsof, '+c', '0', self.mountpoint],
178                            raise_on_error=False
179                        )
180                        open_files_info = 'Open files status:{0}{1}'.format(
181                            os.linesep, open_files.output
182                        )
183                    else:
184                        open_files_info = 'For further details install: lsof'
185                    message = dedent('''\n
186                        Failed to umount: {0}.
187
188                        Your build host system is in an inconsistent state.
189                        The cleanup of the created resource was not possible
190                        because it is still busy. This resource and all nested
191                        resources stays active on your host and needs a manual
192                        cleanup.
193
194                        Please do not use the intermediate state of the image
195                        files created so far. There is no guarantee that the
196                        produced results are valid.
197
198                        {1}
199                    ''')
200                    raise KiwiUmountBusyError(
201                        message.format(self.mountpoint, open_files_info)
202                    )
203                else:
204                    log.warning(
205                        '{0} still busy at {1}'.format(
206                            self.mountpoint, type(self).__name__
207                        )
208                    )
209                    # skip removing the mountpoint directory
210                    return False
211        return True

Umount by the mountpoint directory

Wait up to 10sec trying to umount. If the resource stays busy the call will raise an exception unless raise_on_busy is set to False. In case the umount failed and raise_on_busy is set to False, the method returns False to indicate the error condition.

Returns

True or False

def is_mounted(self) -> bool:
213    def is_mounted(self) -> bool:
214        """
215        Check if mounted
216
217        :return: True or False
218
219        :rtype: bool
220        """
221        mountpoint_call = Command.run(
222            command=['mountpoint', '-q', self.mountpoint],
223            raise_on_error=False
224        )
225        return mountpoint_call.returncode == 0

Check if mounted

Returns

True or False