1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
|
#!/bin/sh
set -e
readonly PACKAGE_NAME=utrans-rc
TS="/var/tmp/${PACKAGE_NAME}.stamp"
exec 3<&0 # Dup stdin for ucf
# args: exit status
usage() {
cat <<EOF
Usage: $(basename "$0") [-h] [-b <BACKEND>..] [ -n | -o <OUTPUT-DIR>] [ -u | <SOURCE-DIR>.. ]
Options:-
-b <BACKEND>: specify output backend, see utrans(1)
-h: show this usage
-o <OUTPUT-DIR>: save ouput to subdirectories of this directory
-n: no install, just show what would be done
-u: update mode
EOF
exit $(($1+0))
}
# Return status
dpkg_known_files() {
ret=1 # False
# Some equivalent LSB scripts have .sh appended to their name.
for file do
[ -z "${file##/etc/init.d/*}" ] && set -- "$@" "${file}.sh"
done
for file do
while read -r owner conffile ; do
[ -z "$owner" ] || [ -z "$conffile" ] ||
[ "$file" != "$conffile" ] && continue
echo "Skipping: $conffile owned by ${owner%:}" >&2
ret=0 # True
done <<EOF
${DPKG_KNOWN:=$(dpkg-query -S /etc/init.d/* /etc/conf.d/* /etc/cron.d/* /etc/xinetd.d/* 2>/dev/null)}
EOF
done
return $ret
}
do_install() {
for target do
source="${OUTPUT_DIR}${target#/etc}"
owner=$(ucfq -w "$target" | awk -F: '{print $2}')
case "$owner" in
'') ;;
"$PACKAGE_NAME")
cmp --quiet "$source" "$target" &&
continue ;;
*) echo "Skipping $target already registered to $owner" >&2
continue ;;
esac
echo "Considering new $target" >&2
if [ "$NO_ACT" ] ; then
echo "DRYRUN: try install $target" >&2
continue
fi
if [ -f "$target" ] ; then
sha256=$(sha256sum "$target")
mode=$(stat -c '%a' "$target")
fi
sudo chown root:root "$source"
target_dir=$(dirname "$target")
sudo mkdir -p "${target_dir}"
# This can be interactive, but do_install() is called with stdin
# redirected to a HEREDOC, so the saved stdin
sudo --preserve-env=DPKG_FORCE,UCF_FORCE_CONFFNEW,UCF_FORCE_CONFFOLD,UCF_FORCE_CONFFMISS,DEBIAN_FRONTEND \
ucf "$source" "$target" <&3
if [ -z "$owner" ] && [ -f "$target" ] &&
sha256sum --status --check 2>/dev/null <<EOF ; then
$sha256
EOF
# No changes made by ucf: continue to avoid taking ownership.
continue
fi
sudo ucfr "$PACKAGE_NAME" "$target"
case "$target_dir" in
/etc/init.d|/etc/user/init.d)
rc_name=${target#"${target_dir}"/}
# Make ucf droppings non-executable
find "$target_dir" -name "${rc_name}.ucf-*" -exec sudo chmod a-x '{}' ';'
if [ -f "$target" ] ; then
if ! sha256sum --status --check 2>/dev/null <<EOF || [ "$mode" != "755" ] ; then
$sha256
EOF
echo "Setting up new $target" >&2
sudo chmod 755 "$target"
# Default install for openrc-user services is handled in
# /etc/conf.d/user (src:openrc)
[ "$target_dir" = /etc/init.d ] || continue
# awk could be mawk with incomplete POSIX regex support
origin=$(awk '/^# [0-9a-f]+ .*\.service$/ {print $3}' "$target")
awk -F= '/^WantedBy=.*\.target$/ {print $2}' "$origin" |
while IFS= read -r wb; do
if binary_in_path rc-update ; then
case "$wb" in
'') echo "WARNING: no WantedBy in $origin, skipping runlevel setup" >&2 && continue ;;
poweroff.target|reboot.target) orl=off ;;
rescue.target) orl=recovery ;;
shutdown.target) orl=shutdown ;;
sysinit.target) orl=sysinit ;;
*.target) orl=default ;;
esac
sudo rc-update show "$orl" | grep -q "$rc_name | $orl" && continue
sudo rc-update add "$rc_name" "$orl"
else
sudo update-rc.d "$rc_name" defaults
fi
done
fi
fi
;;
esac
done
}
# Verify source units for files we have registered with ucf
verify_installed() {
# Reverse sort: process /etc/init.d before /etc/conf.d
for owned in $(ucfq -w "$PACKAGE_NAME" | awk -F: '{if ($3) print $1}' | sort -r); do
for f in $(awk '/^# [0-9a-f]+ .+\..+$/ {print $2, $3}' "$owned" |
sha256sum -c 2>/dev/null | awk -F: '/FAILED/ {print $1}') ; do
if [ -f "$f" ] ; then
echo "Modified source unit: $f" >&2
translate_try_install "$f" || continue
else
echo "Deleted source unit: $f" >&2
case "$owned" in
/etc/init.d/*)
echo "Disabling $owned" >&2
rc_name=$(basename "$owned")
sudo invoke-rc.d "$rc_name" stop || true
sudo update-rc.d "$rc_name" remove || true
sudo chmod -x "$owned"
esac
if [ "$(ucfq -w "$owned")" = "$owned:$PACKAGE_NAME:Yes:No" ] ; then
echo "Removing unmodified $owned" >&2
sudo rm -f "$owned" "$owned.ucf-"*
fi
echo "Disowning $owned" >&2
sudo ucfr --purge "$PACKAGE_NAME" "$owned"
sudo ucf --purge "$owned"
fi
continue 2 # Next $owned
done
done
}
binary_in_path() {
PATH=/usr/sbin:/usr/bin:/sbin:/bin: command -v "$1" >/dev/null
}
detect_backends() {
binary_in_path openrc || set -- lsb
binary_in_path xinetd || set -- "$@" inetd
if [ $# -gt 0 ] ; then
echo "Using auto backends: $*" >&2
echo "$@" | sed 's/^\| / -b /g'
fi
}
try_install() {
unit=$1
pkg=$2
if [ "$pkg" ] && [ "$last_pkg" != "$pkg" ] ; then
# This is expensive, so reuse if possible.
pkg_list=$(dpkg-query -L "$pkg")
last_pkg="$pkg"
fi
PKG_HAS_SUPPORT=
# Handle all files generated by unit together
set --
while read -r file ; do
if [ -z "$file" ] ; then
echo "WARNING: no output files, skipping." >&2
return
fi
target="/etc${file#"$OUTPUT_DIR"}"
if grep -q "^$(dirname "$target")" <<EOF
$pkg_list
EOF
then
echo "$pkg already provides files in $(dirname "$target"), skipping" >&2
PKG_HAS_SUPPORT=1
break
fi
set -- "$target" "$@"
done <<EOF
$(find "$OUTPUT_DIR" ! -type d -print)
EOF
# Skip if package already provides support or any files are owned by
# a package.
[ "$PKG_HAS_SUPPORT" ] || dpkg_known_files "$@" || do_install "$@"
# Only runs when OUTPUT_DIR is a temporary directory.
# Clean, ensure error if OUTPUT_DIR unset.
find "${OUTPUT_DIR:?}" ! -type d -delete
}
translate_try_install() {
unit=$1
binsrc=$2 # Optional, not set when updating existing
# Split BACKENDS on whitepsace:
# shellcheck disable=SC2086
"${UNIT_TRANSLATOR}" $BACKENDS "$unit" "${OUTPUT_DIR}"
if [ "$TRY_INSTALL" ] ; then
try_install "$unit" "$binsrc"
fi
}
: "${UNIT_TRANSLATOR:=utrans}"
# Ensure other internal variables are not inherited
unset NO_ACT TRY_INSTALL PKG_HAS_SUPPORT OP_MODE OUTPUT_DIR BACKENDS
while getopts b:hno:u-: OPT; do
case "$OPT" in
b) BACKENDS="$BACKENDS -b $OPTARG" ;;
h) usage ;;
o) OUTPUT_DIR=$OPTARG ;;
n) NO_ACT=1; unset TS ;;
u)
if [ -f "$TS" ] ; then
OP_MODE=update
else
echo "WARNING: $TS not found, processing all units" >&2
fi
;;
-*)
echo "ERROR: long options not supported" >&2
usage 1
;;
\?) usage 1 ;; # Bad short option from getopts
esac
done
shift $((OPTIND-1))
if [ -z "$BACKENDS" ] ; then
BACKENDS=$(detect_backends)
fi
if [ -z "$OUTPUT_DIR" ]; then
OUTPUT_DIR=$(mktemp -d)
TRY_INSTALL=1
trap 'rm -rf "$OUTPUT_DIR"' EXIT
verify_installed
else
unset TS OP_MODE
fi
if [ $# -eq 0 ] ; then
set -- /usr/lib/systemd/system /lib/systemd/system /usr/lib/systemd/user /lib/systemd/user
else
if [ "$OP_MODE" ] ; then
echo "WARNING: ignoring update mode for non-default SOURCE_DIR" >&2
unset OP_MODE
fi
unset TS
fi
# Sigh, dpkg-query can't be sure to find the package with an
# absolute path after usrmerge moving. So use a dpkg-query
# pattern-match and anchor with awk at beginning (directory) and
# end.
unit_owners() {
for src_path do
shift
set "$@" "${src_path#/}"
done
dpkg-query -S "$@" | sort -u 2>/dev/null
}
DPKG_SOURCE=$(unit_owners "$@")
unit_binsrc() {
unit=$1
awk -F: -v regex="${unit#/usr}\$" '$NF ~ regex {print $1}' <<EOF
${DPKG_SOURCE}
EOF
}
find -L "$@" ! -type d -size +0 \( -name '*.service' -o -name '*.timer' \) ! -name '*@*' ${OP_MODE:+-cnewer "$TS"} \
-exec readlink --canonicalize-existing '{}' ';' | sort -u |
while read -r u; do
binsrc=$(unit_binsrc "$u")
if [ -z "$binsrc" ] ; then
echo "WARNING: $u is not known to dpkg. Install translated files manually." >&2
else
echo "Processing $u" >&2
translate_try_install "$u" "$binsrc"
fi
done
# Update timestamp
[ -z "$TS" ] || sudo sh -c ":>$TS"
|