Blob Blame History Raw
#!/bin/bash
# based on code from dracut convertfs.sh

ROOT=

is_usrmerged() {
    local r=1
    for dir in bin sbin lib lib64; do
	[ -d "$ROOT/$dir" ] || continue
	[ -L "$ROOT/$dir" ] || return 1
	r=0
    done
    return "$r"
}

# check if there's anything to do
is_usrmerged && exit 0

# the package is installed with AutoReq off, so no guarantee that
# coreutils actually works
if ! { cp --help && find --help; } > /dev/null; then
	echo "tools not functional, exit"
	exit 1
fi

# clean up after ourselves no matter how we die.
cleanup() {
    echo "UsrMerge conversion failed, cleaning up"
    for dir in bin sbin lib lib64; do
	rm -rf -- "$ROOT/usr/${dir}.usrmerge"
    done
    echo "!!! ATTENTION: Do NOT proceed if you see this message during"
    echo "!!! distribution upgrade. Chances are high that your system might"
    echo "!!! break beyond repair if you do."
}

trap 'ret=$?; [[ $ret -ne 0 ]] && cleanup;exit $ret;' EXIT
trap 'exit 1;' SIGINT

set -e

if [ "$(stat -f -c %T "${ROOT:-/}")" = "overlayfs" ]; then
	echo "UsrMerge conversion does not work on overlayfs"
	exit 1
fi

CP_HARDLINK="-l"
while read dev mp other; do
    [ "$mp" = "$ROOT/usr" ] && CP_HARDLINK=""
    for dir in bin sbin lib lib64; do
	[ -d "$ROOT/$dir" ] || continue
	if [ "${mp#$ROOT/$dir}" != "$mp" ] || [ "${mp#$ROOT/usr/$dir}" != "$mp" ]; then
	    echo "Please unmount $mp before the conversion"
	    exit 1
	fi
    done
done < /proc/mounts

# merge / and /usr in new dir in /usr
for dir in bin sbin lib lib64; do
    rm -rf -- "$ROOT/usr/${dir}.usrmerge"
    [[ -L "$ROOT/$dir" ]] && continue
    [[ -d "$ROOT/$dir" ]] || continue
    echo "Make a copy of \`$ROOT/$dir'."
    [[ -d "$ROOT/$dir" ]] \
        && cp -ax $CP_HARDLINK "$ROOT/$dir" "$ROOT/usr/${dir}.usrmerge"
    # cp can't handle copying a dir over non-directories. So move
    # those away in advance. Happened with /lib/udev existing as
    # link on older distros
    while read d; do
	f="$ROOT/usr/$dir.usrmerge/$d"
	if test -L "$f" -o \( -e "$f" -a ! -d "$f" \); then
	    echo "Warning: /$dir/$d conflicts with directory /usr/$dir/$d and will be removed"
	    rm -rf "$f.usrmerge~"
	    mv "$f" "$f.usrmerge~"
	fi
    done < <(find "$ROOT/usr/$dir" -xdev -type d -printf "%P\n" )
    echo "Merge the copy with \`$ROOT/usr/$dir'."
    [[ -d "$ROOT/usr/${dir}.usrmerge" ]] \
        || mkdir -p "$ROOT/usr/${dir}.usrmerge"
    cp -axT -l --backup --suffix=.usrmerge~ "$ROOT/usr/$dir" "$ROOT/usr/${dir}.usrmerge"
    echo "Clean up duplicates in \`$ROOT/usr/$dir'."
    # delete all symlinks that have been backed up. /usr versions
    # override / ones
    find "$ROOT/usr/${dir}.usrmerge" -xdev -type l -name '*.usrmerge~' -delete || :
    # in rare cases the symlinks may point from /usr to /, so remove
    # the link in that case
    while read file; do
	o=${file%%.usrmerge~}
	[ -L "$o" ] && mv -f "$file" "$o"
    done < <(find "$ROOT/usr/${dir}.usrmerge" \
        -xdev -name '*.usrmerge~' -type f)
done
# switch over merged dirs in /usr
for dir in bin sbin lib lib64; do
    if [ -d "$ROOT/usr/${dir}.usrmerge" ]; then
	echo "Switch to new \`$ROOT/usr/$dir'."
	/usr/libexec/xmv "$ROOT/usr/${dir}.usrmerge" "$ROOT/usr/$dir"
    fi
done

# replace dirs in / with links to /usr
for dir in bin sbin lib lib64; do
    if [ ! -L "$ROOT/$dir" -a -d "$ROOT/$dir" ]; then
	  echo "Create \`$ROOT/$dir' symlink."
	  rm --one-file-system -rf "$ROOT/${dir}.usrmerge" || :
	  ln -s usr/$dir "$ROOT/${dir}.usrmerge"
	  /usr/libexec/xmv "$ROOT/$dir" "$ROOT/${dir}.usrmerge"
    fi
done

echo "Clean up backup files."
# everything seems to work; cleanup
for dir in bin sbin lib lib64; do
    for pfx in usr/ /; do
	 # if we get killed in the middle of "rm -rf", ensure not to leave
	 # an incomplete directory, which is moved back by cleanup()
	d="$ROOT/${pfx}${dir}.usrmerge"
	if [ -d "$d" ]; then
	    echo "$d ..."
	    mv "$d" "$d~"
	    rm --one-file-system -rf "$d~"
	fi
    done
done

# XXX: confirm this is needed
for dir in lib lib64; do
    [[ -d "$ROOT/$dir" ]] || continue
    for lib in "$ROOT"/usr/${dir}/lib*.so*.usrmerge~; do
        [[ -f $lib ]] || continue
        mv -v $lib ${lib/.so/_so}
    done
done

set +e

echo "Run ldconfig."
ldconfig -r "$ROOT" || :