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
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
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
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
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
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 )
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
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
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
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
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