# 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 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]
Command.run(
['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