kiwi-el8/kiwi/filesystem/base.py
Marcus Schäfer 47167fd486
Use rsync in inplace transfer mode
Using the --inplace option in rsync helps to save space on
syncing the rootfs data and prevents e.g OBS workers from
running out of VM space when transfering root filesystem
data. Also using --inplace allows to keep hardlinks intact.
This is related to bsc#1096738
2020-07-15 16:56:28 +02:00

201 lines
6.3 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 logging
import copy
# project
from kiwi.defaults import Defaults
from kiwi.utils.sync import DataSync
from kiwi.mount_manager import MountManager
from kiwi.command import Command
from kiwi.exceptions import (
KiwiFileSystemSyncError
)
log = logging.getLogger('kiwi')
class FileSystemBase:
"""
**Implements base class for filesystem interface**
:param object device_provider:
Instance of a class based on DeviceProvider
required for filesystems which needs a block device for
creation. In most cases the DeviceProvider is a LoopDevice
:param string root_dir: root directory path name
:param dict custom_args: custom filesystem arguments
"""
def __init__(self, device_provider, root_dir=None, custom_args=None):
# filesystems created with a block device stores the mountpoint
# here. The file name of the file containing the filesystem is
# stored in the device_provider if the filesystem is represented
# as a file there
self.filesystem_mount = None
# bind the block device providing class instance to this object.
# This is done to guarantee the correct destructor order when
# the device should be released. This is only required if the
# filesystem required a block device to become created
self.device_provider = device_provider
self.root_dir = root_dir
# filesystems created without a block device stores the result
# filesystem file name here
self.filename = None
self.custom_args = {}
self.post_init(custom_args)
def post_init(self, custom_args):
"""
Post initialization method
Store dictionary of custom arguments if not empty. This
overrides the default custom argument hash
:param dict custom_args: custom arguments
.. code:: python
{
'create_options': ['option'],
'mount_options': ['option'],
'meta_data': {
'key': 'value'
}
}
"""
if custom_args:
self.custom_args = copy.deepcopy(custom_args)
if not self.custom_args.get('create_options'):
self.custom_args['create_options'] = []
if not self.custom_args.get('meta_data'):
self.custom_args['meta_data'] = {}
if not self.custom_args.get('mount_options'):
self.custom_args['mount_options'] = []
if not self.custom_args.get('fs_attributes'):
self.custom_args['fs_attributes'] = []
def create_on_device(self, label=None):
"""
Create filesystem on block device
Implement in specialized filesystem class for filesystems which
requires a block device for creation, e.g ext4.
:param string label: label name
"""
raise NotImplementedError
def create_on_file(self, filename, label=None, exclude=None):
"""
Create filesystem from root data tree
Implement in specialized filesystem class for filesystems which
requires a data tree for creation, e.g squashfs.
:param string filename: result file path name
:param string label: label name
:param list exclude: list of exclude dirs/files
"""
raise NotImplementedError
def get_mountpoint(self):
"""
Provides mount point directory
Effective use of the directory is guaranteed only after sync_data
:return: directory path name
:rtype: string
"""
if self.filesystem_mount:
return self.filesystem_mount.mountpoint
def sync_data(self, exclude=None):
"""
Copy root data tree into filesystem
:param list exclude: list of exclude dirs/files
"""
if not self.root_dir:
raise KiwiFileSystemSyncError(
'no root directory specified'
)
if not os.path.exists(self.root_dir):
raise KiwiFileSystemSyncError(
'given root directory %s does not exist' % self.root_dir
)
self.filesystem_mount = MountManager(
device=self.device_provider.get_device()
)
self.filesystem_mount.mount(
self.custom_args['mount_options']
)
self._apply_attributes()
data = DataSync(
self.root_dir, self.filesystem_mount.mountpoint
)
data.sync_data(
exclude=exclude, options=Defaults.get_sync_options()
)
def umount(self):
"""
Umounts the filesystem in case it is mounted, does nothing otherwise
"""
if self.filesystem_mount:
log.info('umount %s instance', type(self).__name__)
self.filesystem_mount.umount()
self.filesystem_mount = None
def _apply_attributes(self):
"""
Apply filesystem attributes
"""
attribute_map = {
'synchronous-updates': '+S',
'no-copy-on-write': '+C'
}
for attribute in self.custom_args['fs_attributes']:
if attribute_map.get(attribute):
log.info(
'--> setting {0} for {1}'.format(
attribute, self.filesystem_mount.mountpoint
)
)
Command.run(
[
'chattr', attribute_map.get(attribute),
self.filesystem_mount.mountpoint
]
)
def __del__(self):
log.info('Cleaning up %s instance', type(self).__name__)
self.umount()