8591dcf731
Add the following flags to rsync: * `-H, --hard-links preserve hard links` * `-A, --acls preserve ACLs (implies --perms)` * `-X, --xattrs preserve extended attributes` (i.e., the preservation options not already implied by -a). Also exclude the special paths that do not make sense to rsync, because reading the extended attributes from those can cause errors, at least with SELinux enabled. This fixes installation of Fedora systems with SELinux enabled.
234 lines
8.6 KiB
Python
234 lines
8.6 KiB
Python
#!/usr/bin/env python3
|
|
# encoding: utf-8
|
|
# === This file is part of Calamares - <http://github.com/calamares> ===
|
|
#
|
|
# Copyright 2014, Teo Mrnjavac <teo@kde.org>
|
|
# Copyright 2014, Daniel Hillenbrand <codeworkx@bbqlinux.org>
|
|
# Copyright 2014, Philip Müller <philm@manjaro.org>
|
|
#
|
|
# Calamares 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.
|
|
#
|
|
# Calamares 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 Calamares. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from collections import namedtuple
|
|
|
|
from libcalamares import *
|
|
|
|
class UnpackEntry:
|
|
__slots__= ['source', 'sourcefs', 'destination', 'copied', 'total']
|
|
|
|
def __init__(self, source, sourcefs, destination):
|
|
self.source = source
|
|
self.sourcefs = sourcefs
|
|
self.destination = destination
|
|
self.copied = 0
|
|
self.total = 0
|
|
|
|
ON_POSIX = 'posix' in sys.builtin_module_names
|
|
|
|
|
|
def file_copy(source, dest, progress_cb):
|
|
# Environment used for executing rsync properly
|
|
# Setting locale to C (fix issue with tr_TR locale)
|
|
at_env = os.environ
|
|
at_env["LC_ALL"] = "C"
|
|
|
|
# `source` *must* end with '/' otherwise a directory named after the source
|
|
# will be created in `dest`: ie if `source` is "/foo/bar" and `dest` is
|
|
# "/dest", then files will be copied in "/dest/bar".
|
|
source += "/"
|
|
|
|
process = subprocess.Popen(['rsync', '-aHAXr', '--exclude', '/dev/', '--exclude', '/proc/', '--exclude', '/sys/', '--exclude', '/run/', '--progress', source, dest],
|
|
env=at_env,
|
|
bufsize=1,
|
|
stdout=subprocess.PIPE,
|
|
close_fds=ON_POSIX)
|
|
|
|
for line in iter(process.stdout.readline, b''):
|
|
# small comment on this regexp.
|
|
# rsync outputs three parameters in the progress.
|
|
# xfer#x => i try to interpret it as 'file copy try no. x'
|
|
# to-check=x/y, where:
|
|
# - x = number of files yet to be checked
|
|
# - y = currently calculated total number of files.
|
|
# but if you're copying directory with some links in it, the xfer#
|
|
# might not be a reliable counter (for one increase of xfer, many
|
|
# files may be created).
|
|
# In case of manjaro, we pre-compute the total number of files.
|
|
# therefore we can easily subtract x from y in order to get real files
|
|
# copied / processed count.
|
|
m = re.findall(r'xfr#(\d+), ir-chk=(\d+)/(\d+)', line.decode())
|
|
if m:
|
|
# we've got a percentage update
|
|
num_files_remaining = int(m[0][1])
|
|
num_files_total_local = int(m[0][2])
|
|
# adjusting the offset so that progressbar can be continuesly drawn
|
|
num_files_copied = num_files_total_local - num_files_remaining
|
|
|
|
# I guess we're updating every 100 files...
|
|
if num_files_copied % 100 == 0:
|
|
progress_cb(num_files_copied)
|
|
process.wait()
|
|
if process.returncode != 0:
|
|
return "rsync failed with error code {}.".format(process.returncode)
|
|
return None
|
|
|
|
|
|
class UnpackOperation:
|
|
|
|
def __init__(self, entries):
|
|
self.entries = entries
|
|
self.entry_for_source = dict((x.source, x) for x in self.entries)
|
|
|
|
def report_progress(self):
|
|
progress = float(0)
|
|
for entry in self.entries:
|
|
if entry.total == 0:
|
|
continue
|
|
|
|
partialprogress = 0.05 # Having a total !=0 gives 5%
|
|
|
|
partialprogress += 0.95 * \
|
|
(entry.copied / float(entry.total))
|
|
progress += partialprogress / len(self.entries)
|
|
|
|
job.setprogress(progress)
|
|
|
|
def run(self):
|
|
source_mount_path = tempfile.mkdtemp()
|
|
try:
|
|
for entry in self.entries:
|
|
imgbasename = os.path.splitext(
|
|
os.path.basename(entry.source))[0]
|
|
imgmountdir = os.path.join(source_mount_path, imgbasename)
|
|
os.mkdir(imgmountdir)
|
|
|
|
self.mount_image(entry, imgmountdir)
|
|
|
|
fslist = ""
|
|
|
|
if entry.sourcefs == "squashfs":
|
|
if shutil.which("unsquashfs") == None:
|
|
return ("Failed to unpack image", "Failed to find unsquashfs, make sure you have "
|
|
"the squashfs-tools package installed")
|
|
|
|
fslist = subprocess.check_output(["unsquashfs",
|
|
"-l",
|
|
entry.source])
|
|
if entry.sourcefs == "ext4":
|
|
fslist = subprocess.check_output(["find",
|
|
imgmountdir,
|
|
"-type", "f"])
|
|
entry.total = len(fslist.splitlines())
|
|
|
|
self.report_progress()
|
|
error_msg = self.unpack_image(entry, imgmountdir)
|
|
if error_msg:
|
|
return ("Failed to unpack image {}".format(entry.source),
|
|
error_msg)
|
|
return None
|
|
finally:
|
|
shutil.rmtree(source_mount_path)
|
|
|
|
def mount_image(self, entry, imgmountdir):
|
|
subprocess.check_call(["mount",
|
|
entry.source,
|
|
imgmountdir,
|
|
"-t",
|
|
entry.sourcefs,
|
|
"-o", "loop"])
|
|
|
|
def unpack_image(self, entry, imgmountdir):
|
|
def progress_cb(copied):
|
|
entry.copied = copied
|
|
self.report_progress()
|
|
|
|
try:
|
|
return file_copy(imgmountdir,
|
|
entry.destination,
|
|
progress_cb)
|
|
finally:
|
|
subprocess.check_call(["umount", "-l", imgmountdir])
|
|
|
|
|
|
def run():
|
|
# from globalstorage: rootMountPoint
|
|
# from job.configuration:
|
|
# the path to where to mount the source image(s) for copying
|
|
# an ordered list of unpack mappings for image file <-> target dir relative
|
|
# to rootMountPoint, e.g.:
|
|
# configuration:
|
|
# unpack:
|
|
# - source: "/path/to/filesystem.img"
|
|
# sourcefs: "ext4"
|
|
# destination: ""
|
|
# - source: "/path/to/another/filesystem.sqfs"
|
|
# sourcefs: "squashfs"
|
|
# destination: ""
|
|
|
|
PATH_PROCFS = '/proc/filesystems'
|
|
|
|
root_mount_point = globalstorage.value("rootMountPoint")
|
|
if not root_mount_point:
|
|
return ("No mount point for root partition in globalstorage",
|
|
"globalstorage does not contain a \"rootMountPoint\" key, "
|
|
"doing nothing")
|
|
if not os.path.exists(root_mount_point):
|
|
return ("Bad mount point for root partition in globalstorage",
|
|
"globalstorage[\"rootMountPoint\"] is \"{}\", which does not "
|
|
"exist, doing nothing".format(root_mount_point))
|
|
unpack = list()
|
|
|
|
for entry in job.configuration["unpack"]:
|
|
source = os.path.abspath(entry["source"])
|
|
|
|
sourcefs = entry["sourcefs"]
|
|
|
|
# Get supported filesystems
|
|
fs_is_supported = False
|
|
|
|
if os.path.isfile(PATH_PROCFS) and os.access(PATH_PROCFS, os.R_OK):
|
|
procfile = open(PATH_PROCFS, 'r')
|
|
filesystems = procfile.read()
|
|
procfile.close
|
|
|
|
filesystems = filesystems.replace("nodev", "")
|
|
filesystems = filesystems.replace("\t", "")
|
|
filesystems = filesystems.splitlines()
|
|
|
|
# Check if the source filesystem is supported
|
|
for fs in filesystems:
|
|
if fs == sourcefs:
|
|
fs_is_supported = True
|
|
|
|
if fs_is_supported == False:
|
|
return "Bad filesystem", "sourcefs=\"{}\"".format(sourcefs)
|
|
|
|
destination = os.path.abspath(root_mount_point + entry["destination"])
|
|
|
|
if not os.path.exists(source) or os.path.isdir(source):
|
|
return ("Bad source", "source=\"{}\"".format(source))
|
|
if not os.path.isdir(destination):
|
|
return ("Bad destination",
|
|
"destination=\"{}\"".format(destination))
|
|
|
|
unpack.append(UnpackEntry(source, sourcefs, destination))
|
|
|
|
unpackop = UnpackOperation(unpack)
|
|
return unpackop.run()
|