You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

330 lines
11 KiB

  1. import os
  2. import re
  3. from calculate.utils.device import udev, get_uuid_dict
  4. from . import files
  5. import xattr
  6. import errno
  7. from contextlib import contextmanager
  8. from .tools import SingletonParam, flat_iterable
  9. import tempfile
  10. def is_mount(device_name):
  11. '''Возвращает путь к точке монтирования, если устройство примонтировано
  12. или '''
  13. def find_names(old_device_name):
  14. device_name = os.path.abspath(old_device_name)
  15. yield device_name
  16. if device_name.startswith('/dev'):
  17. info = udev.get_device_info(name=device_name)
  18. if 'DM_VG_NAME' in info and 'DM_LV_NAME' in info:
  19. yield '/dev/mapper/{vg}-{lv}'.format(vg=info['DM_VG_NAME'],
  20. lv=info['DM_LV_NAME'])
  21. def get_overlay_mounts(line):
  22. mounts = line.split(' ')
  23. yield mounts[1]
  24. for device_name in re.findall(
  25. '(?:lowerdir=|upperdir=|workdir=)([^,]+)',
  26. mounts[3]):
  27. yield device_name
  28. find_data = set(find_names(device_name))
  29. output = ''
  30. for mtab_line in files.read_file_lines('/etc/mtab'):
  31. if 'overlay' not in mtab_line:
  32. if ' ' in mtab_line:
  33. mounts = set(mtab_line.split(' ')[:2])
  34. if mounts & find_data:
  35. output = (mounts - find_data).pop()
  36. else:
  37. mounts = set(get_overlay_mounts(mtab_line))
  38. dest = mtab_line.split(' ')[1]
  39. if mounts & find_data:
  40. if dest in find_data:
  41. output = 'overlay'
  42. else:
  43. return dest
  44. return output
  45. class MountHelperError(Exception):
  46. pass
  47. class MountHelperNotFound(Exception):
  48. pass
  49. class MountHelper:
  50. '''Базовый класс для чтения файлов /etc/fstab и /etc/mtab.'''
  51. DATA_FILE = '/etc/fstab'
  52. NAME, DIR, TYPE, OPTS, FREQ, PASSNO = range(0, 6)
  53. def __init__(self, data_file=None, devices=()):
  54. if data_file:
  55. self.DATA_FILE = data_file
  56. self.cache = []
  57. self.rotated_cache = []
  58. self.uuid_dictionary = get_uuid_dict(devices=devices)
  59. self.update_cache()
  60. def _read_data(self):
  61. with open(self.DATA_FILE, 'r') as data_file:
  62. return data_file.read()
  63. def update_cache(self):
  64. def setlen(ar):
  65. return ar[:6] + [''] * (6 - len(ar))
  66. self.cache = []
  67. for line in self._read_data().split('\n'):
  68. line = line.strip()
  69. if line and not line.startswith('#'):
  70. separated_line = []
  71. for part in line.split():
  72. part = part.strip()
  73. separated_line.append(part)
  74. self.cache.append(separated_line)
  75. for data in self.cache:
  76. device_name = self.uuid_dictionary.get(data[0], data[0])
  77. if device_name.startswith('/'):
  78. device_name = os.path.realpath(device_name)
  79. data[0] = udev.get_device_info(name=device_name).get(
  80. 'DEVNAME', data[0])
  81. data[1] = data[1] if data[2] != 'swap' else 'swap'
  82. self.rotated_cache = list(zip(*self.cache))
  83. def get_from_fstab(self, what=DIR, where=NAME, is_in=None, is_equal=None,
  84. contains=None, is_not_equal=None, all_entry=True):
  85. if is_equal is not None:
  86. def filter_function(tab_line):
  87. return tab_line[where] == is_equal
  88. elif is_in is not None:
  89. def filter_function(tab_line):
  90. return tab_line[where] in is_in
  91. elif contains is not None:
  92. def filter_function(tab_line):
  93. return contains in tab_line[where]
  94. else:
  95. def filter_function(tab_line):
  96. return tab_line[where] != is_not_equal
  97. output = []
  98. for line in filter(filter_function, self.cache):
  99. output.append(line[what])
  100. if all_entry:
  101. return output
  102. else:
  103. return '' if not output else output[-1]
  104. def get_fields(self, *fields):
  105. fstab_fields = [self.rotated_cache[field] for field in fields]
  106. return list(zip(*fstab_fields))
  107. def is_read_only(self, what=DIR, is_equal=None):
  108. tab_to_check = list(filter(lambda fstab_tab:
  109. fstab_tab[what] == is_equal, self.cache))
  110. if tab_to_check:
  111. for data in tab_to_check:
  112. options = data[self.OPTS].split(',')
  113. if 'ro' in options:
  114. return True
  115. else:
  116. return False
  117. else:
  118. raise MountHelperNotFound(is_equal)
  119. def is_exist(self, what=DIR, is_equal=None, is_not_equal=None):
  120. if is_equal is not None:
  121. def filter_function(tab_line):
  122. tab_line[what] == is_equal
  123. else:
  124. def filter_function(tab_line):
  125. tab_line[what] != is_not_equal
  126. return bool(filter(filter_function, self.cache))
  127. @property
  128. def writable(self, directory_path):
  129. return not self.is_read_only(is_equal=directory_path)
  130. @property
  131. def read_only(self, directory_path):
  132. return self.is_read_only(is_equal=directory_path)
  133. @property
  134. def exists(self, directory_path):
  135. return self.is_exist(is_equal=directory_path)\
  136. or self.is_exist(what=self.NAME,
  137. is_equal=directory_path)
  138. class FStab(MountHelper, metaclass=SingletonParam):
  139. '''Класс для чтения содержимого /etc/fstab и его кеширования.'''
  140. DATA_FILE = '/etc/fstab'
  141. class Mounts(MountHelper):
  142. '''Класс для чтения содержимого /etc/mtab и его кеширования.'''
  143. DATA_FILE = '/etc/mtab'
  144. class DiskSpaceError(Exception):
  145. pass
  146. class DiskSpace:
  147. def __init__(self):
  148. self.df_command = files.get_program_path('/bin/df')
  149. def get_free(self, device=None, device_path=None):
  150. if device:
  151. mount_path = is_mount(device)
  152. if not mount_path:
  153. raise DiskSpaceError('Device {} must be mounted.'.
  154. format(device))
  155. device_path = device
  156. df_process = files.Process(self.df_command, device_path, '-B1')
  157. if df_process.success():
  158. df_data = df_process.read().strip()
  159. df_lines = df_data.split('\n')
  160. if len(df_lines) >= 2:
  161. columns = df_lines[1].split()
  162. if len(columns) == 6:
  163. return int(columns[3])
  164. raise DiskSpaceError('Wrong df output:\n{}'.format(df_data))
  165. else:
  166. raise DiskSpaceError(str(df_process.read_error()))
  167. def get_child_mounts(path_name):
  168. '''Получить все точки монтирования, содержащиеся в пути.'''
  169. mtab_file_path = '/etc/mtab'
  170. if not os.access(mtab_file_path, os.R_OK):
  171. return ''
  172. with open(mtab_file_path) as mtab_file:
  173. output = []
  174. mtab = list(map(lambda line: line.split(' '), mtab_file))
  175. mtab = [[line[0], line[1]] for line in mtab]
  176. if path_name != 'none':
  177. abs_path = os.path.abspath(path_name)
  178. for tab_line in mtab:
  179. if os.path.commonpath([abs_path, tab_line[1]]) == abs_path:
  180. output.append(tab_line)
  181. else:
  182. abs_path = path_name
  183. for tab_line in mtab:
  184. if tab_line[0] == abs_path:
  185. output.append(tab_line)
  186. return output
  187. class MountError(Exception):
  188. pass
  189. def mount(source, target, fstype=None, options=None):
  190. parameters = [source, target]
  191. if options is not None:
  192. if isinstance(options, list) or isinstance(options, tuple):
  193. options = ','.join(flat_iterable(options))
  194. parameters.insert(0, '-o {}'.format(options))
  195. if fstype is not None:
  196. parameters.insert(0, '-t {}'.format(fstype))
  197. mount_process = files.Process('/bin/mount', *parameters)
  198. if mount_process.success():
  199. return True
  200. else:
  201. raise MountError('Failed to mount {source} to {target}: {stderr}'
  202. .format(source=source, target=target,
  203. stderr=str(mount_process.read_error())))
  204. def umount(target):
  205. umount_process = files.Process('/bin/umount', target)
  206. if umount_process.success():
  207. return True
  208. else:
  209. raise MountError('Failed to umount {target}: {stderr}'
  210. .format(target=target,
  211. stderr=umount_process.read_error()))
  212. class BtrfsError(Exception):
  213. pass
  214. class Btrfs:
  215. check_path = None
  216. def __init__(self, block_device):
  217. self.block_device = block_device
  218. if not os.path.exists(block_device):
  219. raise BtrfsError('Device is not found.')
  220. @contextmanager
  221. def mount(self):
  222. tempfile_path = None
  223. try:
  224. files.make_directory(self.check_path)
  225. tempfile_path = tempfile.mkdtemp(prefix='btrfscheck-',
  226. dir=self.check_path)
  227. mount(self.block_device, tempfile_path, 'btrfs')
  228. yield tempfile_path
  229. except KeyboardInterrupt:
  230. raise
  231. except MountError as error:
  232. if 'wrong fs type' in str(error):
  233. raise BtrfsError('{} is not btrfs'.format(self.block))
  234. else:
  235. raise BtrfsError(str(error))
  236. finally:
  237. if tempfile_path:
  238. if os.path.ismount(tempfile_path):
  239. umount(tempfile_path)
  240. os.rmdir(tempfile_path)
  241. def get_compression(self, relative_path):
  242. relative_path = relative_path.lstrip('/')
  243. with self.mount() as device_path:
  244. try:
  245. absolute_path = os.path.join(device_path, relative_path)
  246. if os.path.exists(absolute_path):
  247. return xattr.get(absolute_path, 'btrfs.compression')
  248. else:
  249. return ''
  250. except IOError as error:
  251. if error.errno == errno.ENODATA:
  252. return ''
  253. raise BtrfsError('Failed to get btrfs compression.')
  254. @property
  255. def compression(self):
  256. return self.get_compression('')
  257. def set_compression(self, relative_path, value):
  258. relative_path = relative_path.lstrip('/')
  259. with self.mount() as device_path:
  260. try:
  261. absolute_path = os.path.join(device_path, relative_path)
  262. if not os.path.exists(absolute_path):
  263. files.make_directory(absolute_path)
  264. return xattr.set(absolute_path, 'btrfs.compression', value)
  265. except IOError as error:
  266. if error.errno == errno.ENODATA:
  267. return ''
  268. raise BtrfsError('Failed to set btrfs compression.')
  269. @compression.setter
  270. def compression(self, value):
  271. self.set_compression('', value)