Source code for kiwi.utils.sync

# 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
# 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 <>
import os
import errno
import logging
from stat import ST_MODE
from typing import List

# project
from kiwi.command import Command

log = logging.getLogger('kiwi')

[docs] class DataSync: """ **Sync data from a source directory to a target directory** """ def __init__(self, source_dir: str, target_dir: str) -> None: """ Create a new DataSync instance and initialize sync source and target :param str source_dir: source directory path name :param str target_dir: target directory path name """ self.source_dir = source_dir self.target_dir = target_dir
[docs] def sync_data( self, options: List[str] = [], exclude: List[str] = [], force_trailing_slash: bool = False ) -> None: """ Sync data from source to target using the rsync protocol :param list options: rsync options :param list exclude: file patterns to exclude :param bool force_trailing_slash: add '/' to source_dir if not present A speciality of the rsync tool is that it behaves differently if the given source_dir ends with a '/' or not. If it ends with a slash the data structure below will be synced to the target_dir. If it does not end with a slash the source_dir and its contents are synced to the target_dir. For example .. code:: bash source └── some_data 1. $ rsync -a source target target └── source └── some_data 2. $ rsync -a source/ target target └── some_data The parameter force_trailing_slash can be used to make sure rsync behaves like shown in the second case. If set to true a '/' is appended to the given source_dir if not already present """ if force_trailing_slash and not self.source_dir.endswith(os.sep): self.source_dir += os.sep target_entry_permissions = None exclude_options = [] rsync_options = [] if options: rsync_options = options if not self.target_supports_extended_attributes(): warn_me = False if '--xattrs' in rsync_options: rsync_options.remove('--xattrs') warn_me = True if '--acls' in rsync_options: rsync_options.remove('--acls') warn_me = True if warn_me: log.warning( 'Extended attributes not supported for target: %s', self.target_dir ) if exclude: for item in exclude: exclude_options.append('--exclude') exclude_options.append( '/' + item ) if os.path.exists(self.target_dir): target_entry_permissions = os.stat(self.target_dir)[ST_MODE] ['rsync'] + rsync_options + exclude_options + [ self.source_dir, self.target_dir ] ) if target_entry_permissions: # rsync applies the permissions of the source directory # also to the target directory which is unwanted because # only permissions of the files and directories from the # source directory and its contents should be transfered # but not from the source directory itself. Therefore # the permission bits of the target directory before the # sync are applied back after sync to ensure they have # not changed os.chmod(self.target_dir, target_entry_permissions)
[docs] def target_supports_extended_attributes(self) -> bool: """ Check if the target directory supports extended filesystem attributes :return: True or False :rtype: bool """ try: os.getxattr(self.target_dir, 'user.mime_type') except OSError as e: log.debug( f'Check for extended attributes on {self.target_dir} said: {e}' ) if e.errno == errno.ENOTSUP: return False return True