pax_global_header00006660000000000000000000000064150650100040014501gustar00rootroot0000000000000052 comment=5e8156c144230d5e9e03dcb17159d7e7bee4349e unit-translator-0.8/000077500000000000000000000000001506501000400145165ustar00rootroot00000000000000unit-translator-0.8/.gitignore000066400000000000000000000000031506501000400164770ustar00rootroot00000000000000*~ unit-translator-0.8/ChangeLog000066400000000000000000000152741506501000400163010ustar00rootroot000000000000002025-09-24 Mark Hindley * Version 0.8 * backends/lsb: skip successfully incompatible (user, instantiated) units. * backends/lsb: support oneshot; requires init-d-script > 3.15-1. * utrans: remove unnecessary NetworkManager-wait-online hack. * utrans ignore network-online.target. * backends/openrc: demote insserv.conf dependencies to use. * backends/openrc: map Wants to want dependency. * utrans: remove $network from network-pre.target mapping. * utrans-rc: package name can be arch qualified, so ensure path is matched against the last field. * utrans-rc: identify unknown and skipped unit in message. * utrans-rc: pass DPKG_FORCE environment variable to ucf. * Ensure ucf uses the terminal for input when interactive. * utrans: make LSB and openrc scripts executable by everybody. * Support BindsTo, Requisite, PartOf and Upholds by mapping (imperfectly) to Requires or Wants. * Support socket Exec{Start,Stop}{Pre,Post} hooks. * Ensure socket directories are created when specifiers are used. * Support socket DirectoryMode. * Support user services (backends/openrc). * systemd.exec(5)-compatible EnvironmentalFile handling. * Support User/Group (!/!!) and skip expansion (+) special exec prefixes. * Support IgnoreSIGPIPE. * Add some test cases from src:system and ensure compatibility. * backends/lsb: output file is executable. * Export Environmental and EnvironmentalFile variables to sub-processes. 2025-01-28 Mark Hindley * Version 0.7 * utrans: handle multiple special exec prefixes. * backends/lsb: fix typo in SecureBits substitution. * backends/lsb: removed doubled quotation of embedded substitutions. * backends/openrc: fix SecureBits handling: convert arguments to integer. * t/input/sssd.service: add real world test of SecureBits. * backends/lsb: drop beta-quality warning. * utrans-deb: support UNIT_TRANSLATOR environment variable to override path to utrans for testing. * backends/cron: protect generated cron fragments from running if systemd is PID1 (and presumably the relevant .timer will be active). 2024-11-11 Mark Hindley * Version 0.6. * utrans-rc: don't take ownership of files already registered with ucfr. * utrans-rc: only register with ucfr if modifications have been made. 2024-11-04 Mark Hindley * Version 0.5. * utrans-rc: revert workaround for #816220, it breaks debconf. * utrans-rc: preserve environment variable DEBIAN_FRONTEND for ucf/debconf. 2024-11-01 Mark Hindley * Quote arguments to unset (shellcheck). * man/utrans-rc.1: document behaviour when source unit change; correct default unit source path. * utrans-rc: use invoke-rc.d(8) rather than service(8). * utrans: fix handling of display-manager Alias: unset and continue if the unit is not the default display-manager. 2024-10-15 Mark Hindley * utrans-rc: verify_installed(): only handle each owned file once. * utrans-rc: pass UCF_FORCE_CONFF* environment variables to ucf(1). * Support service[RootDirectory]. * README.org: add Debian packages and local git checkout instructions to usage. * README.org: update manpage link. * utrans-rc: ignore package source when updating existing translated units. * backends/lsb: support capabilities using setpriv(8). 2024-10-13 Mark Hindley * utrans: fix logic for detecting correct ulimit NPROC flag. * Rework ulimit handling. * Fix socket-activate commandline for ListenDatagram * backends/lsb: use s-s-d --startas so that socket activated services stop correctly. * backends/openrc: ignore '-' prefix to service[WorkingDirectory]. * backends/lsb: support service[WorkingDirectory]. * utrans-rc: minimise, cache and reuse expensive dpkg-query calls. 2024-10-07 Mark Hindley * Version 0.4: Release 2024-09-24 Mark Hindley * utrans: support comma separated list of backends. * utrans-deb: add with thanks to Ralph Ronnquist (rrq@rrq.au). 2024-09-15 Mark Hindley * utrans: chown runtime directories to service[User]:service[Group], if set. * backends/openrc: support RemainAfterExit=no. * backends/openrc: rework capabilities (again!). * utrans-rc: update translated files if source unit has changed. * b/openrc: don't produce linux-specific output on non-linux. * t/update: filter (ignore) expected missing backend errors. * backends/openrc: support service KillMode and SendSIGHUP. 2024-09-09 Mark Hindley * Version 0.3: Release * Fix RuntimeDirectoryPreserve which only controls deletion of RuntimeDirectory itself. * Fix CacheDirectory path. * backends/openrc: cleanup capabilities handling * Fix generated ACPower test * backends/lsb: support Nice, IOSchedulingClass, IOSchedulingPriority, User, Group and UMask 2024-09-01 Mark Hindley * Move shared constraint prefix and trigger handling to frontend. * t/: test each backend individually. * Support '=' backend prefix to skip loading of default backends and provide warning stubs for missing backends. 2024-08-28 Mark Hindley * Ensure environment directives are quoted to protect spaces. * backends/lsb: don't create a pidfile for service[Type]=forking. * Support service[Type] notify-reload. * backends/lsb: handle socket-activated services with start-stop-daemon. * backends/lsb: ensure environment variables are set before they are used. * Rework dependency mapping to avoid duplicates or conflicts. 2024-08-27 Mark Hindley * utrans-rc: support -b option. * utrans-rc: automatically select backends if none are specifically requested. 2024-08-26 Mark Hindley * backends/lsb: merge initial LSB support. * utrans: support DefaultDependencies. 2024-08-25 Mark Hindley * Version 0.2.1: Fixup release. * Makefile: don't remove man/*.1 in clean recipe. 2024-08-25 Mark Hindley * Version 0.2: Release. * man/: write manpages in raw groff. * utrans-rc: ensure variables are not inherited from environment. * utrans-rc: only use timestamp and -u option for default search path. * utrans-rc: include both /lib/systemd/system and /usr/lib/systemd/system by default. utrans-rc: skip install if a dpkg package already provides files in the installation directory. * utrans: fix handling of multiple Requires. * backends/openrc: fix ignoring unknown virtual dependencies. 2024-08-24 Mark Hindley * Version 0.1: Initial Release with openrc, cron, xinetd and inetd backends. unit-translator-0.8/LICENSE000066400000000000000000000025351506501000400155300ustar00rootroot00000000000000BSD-2-Clause License Copyright (c) 2020, K Gopal Krishna. All rights reserved. Copyright (c) 2023-, Mark Hindley. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. unit-translator-0.8/Makefile000066400000000000000000000020211506501000400161510ustar00rootroot00000000000000#! make -f VERSION=0.8 export UTRANS_DATA_DIR ?= $(CURDIR) export SYSTEMD_UNIT_PATH=./input/user:./input all: sed -i 's#^readonly VERSION=[[:digit:]]\+\(\.[[:digit:]]\)\+#readonly VERSION=$(VERSION)#' utrans sed -i -e '1 s#Version [[:digit:]]\+\(\.[[:digit:]]\)\+#Version $(VERSION)#' \ -e '1 s#"[[:digit:]]\{4\}-[[:digit:]]\{2\}-[[:digit:]]\{2\}"#$(shell date -I)#' man/*.1 check: shellcheck mancheck find ./t/expected -type f -executable -exec sh -n '{}' ';' cd ./t && ./run mancheck: man/*.1 groff -mandoc -t -K utf8 -ww -b -z $? shellcheck: ifeq (, $(shell which shellcheck)) $(warning "No shellcheck found in $(PATH), skipping") else shellcheck --external-sources --exclude SC2155 --severity=info utrans shellcheck utrans-rc endif systemd-lsb-tests: t/expected/lsb/init.d/exec-* cp t/test-exec_environmentfile.conf /tmp for lsb_test in $^; do \ # Run each one in a new program group: exec-ignoresigpipe-yes signals \ # the whole group \ setsid $$lsb_test start; done clean: update-expected: cd ./t && ./update unit-translator-0.8/README.org000066400000000000000000000026451506501000400161730ustar00rootroot00000000000000#+TITLE: Unit Translator #+AUTHOR: Mark Hindley * About Unit Translator is written (/almost purely/) in bash and converts systemd-style units to generic unix equivalents. It is based on Systemd Unit Translator, written by K Gopal Krishna as part of the Google Summer of Code, 2020. * Dependencies - GNU =grep=, =sed=, =awk= * Usage See the [[file:man/utrans.1][Manual]]. Packages for [[https://tracker.debian.org/pkg/unit-translator][Debian and derivatives]] are available. It can also be run directly from a local copy of the [[https://git.devuan.org/LeePen/unit-translator/][=git= repository]]. Set the UTRANS_DATA_DIR and UNIT_TRANSLATOR environment variables:- #+begin_src sh git clone https://git.devuan.org/LeePen/unit-translator UTRANS_DATA_DIR=./unit-translator ./unit-translator/utrans /lib/systemd/system/unit.service /tmp UTRANS_DATA_DIR=./unit-translator UNIT_TRANSLATOR=./unit-translator/utrans ./unit-translator/utrans-rc #+end_src * References - https://linux.die.net/man/8/xinetd - https://manpages.debian.org/unstable/socket-activate/socket-activate.1.en.html - https://www.freedesktop.org/software/systemd/man/systemd.service.html - https://www.freedesktop.org/software/systemd/man/systemd.socket.html - https://www.freedesktop.org/software/systemd/man/systemd.time.html# - https://www.freedesktop.org/software/systemd/man/systemd.timer.html - https://www.freedesktop.org/software/systemd/man/systemd.unit.html unit-translator-0.8/backends/000077500000000000000000000000001506501000400162705ustar00rootroot00000000000000unit-translator-0.8/backends/README000066400000000000000000000023421506501000400171510ustar00rootroot00000000000000Output backends for utrans Backends enable utrans to produce various output formats. The following types are available:- - service (openrc, lsb) - timer (cron) - inetd (xinetd, inetd) Valid backends:- - must be bash scripts. A shebang is not required and the files should not be executable as backends are sourced by the main program. Therefore they inherit the restrictions of the main script (currently errexit, pipefail, nounset, noglob). - must provide a function export_ for each type they wish to handle. The function is called with 2 arguments: the full path to the systemd unit and the base output directory. The backend is responsible for creating any subdirectories it requires and writing files within them. - should use the global associative arrays (unit, service, socket, timer, install) whose keys allow access to the parsed systemd unit. Systemd Before, After, Requires and Wants dependencies are translated to insserv/LSB-style equivalents accessible via the depends[{Before,Requires,Wants}] array. - are recommended to embed the output of gen_origin() at the top of any generated files. This will produce a header with the origin file sha256. unit-translator-0.8/backends/cron000066400000000000000000000045121506501000400171560ustar00rootroot00000000000000# cron output backend # shellcheck shell=bash eval_cron_expr() { local m h dom mon dow case "${timer[OnCalendar]}" in # handle special calendar event expressions minutely) ;; hourly) m=0 ;; daily) m=0 h=0 ;; weekly) m=0 h=0 dow=1 ;; monthly) m=0 h=0 dom=1 ;; quarterly) m=0 h=0 dom=1 m=1,4,7,10 ;; semiannually) m=0 h=0 dom=1 m=1,7 ;; yearly) m=0 h=0 dom=1 m=1 ;; # Parse *) read -ra oncal <<<"${timer[OnCalendar]}" for calsub in "${oncal[@]}"; do # echo "Sub: ${calsub}" if [[ "$calsub" =~ ^([,0-6]|\.\.|Sun|Mon|Tue|Wed|Thu|Fri|Sat)+$ ]]; then declare -l dow="${calsub//../-}" continue echo " dow: ${dow}" fi case "${calsub}" in *-*) # Date # cron is year agnostic, just require mon-dom and dom="${calsub##*-}" calsub="${calsub%-"$dom"}" mon="${calsub##*-}" continue echo " mon: $mon" echo " dom: $dom" ;; *:*) # Time # cron granularity is seconds h="${calsub%%:*}" calsub="${calsub#"${h}":}" m="${calsub%%:*}" continue echo " h: $h" echo " m: $m" ;; esac done ;; esac echo "${m:-*} ${h:-*} ${dom:-*} ${mon:-*} ${dow:-*}" } gen_cron_entry() { local sep crontab=$(eval_cron_expr) printf '# %s\n\n' 'm h dom mon dow user command' printf '%s %s [ -d /run/systemd/system ] || {' "${crontab}" "${service[User]:-root}" while read -r cmd; do printf '%s %s' "${sep:-}" "${cmd}" sep=';' done <<<"$(handle_exec_prefixes "${service[ExecStart]}")" printf '; }\n' printf '# %s\n' 'Alternatively, if your system is running openrc you could use' printf '# %s %s %s\n' "${crontab}" 'root' "rc-service ${systemd_unit_file%.timer} start" } export_timer() { systemd_unit=$1 base_dir=$2 cron_dir="${base_dir}/cron.d" if [[ -z "${timer[OnCalendar]:-}" ]] ; then # Empty, are any of OnActiveSec, OnBootSec, OnStartupSec, # OnUnitActiveSec, OnUnitInactiveSec, OnClockChange, # OnTimezoneChange supportable? echo "WARNING: timer[OnCalendar] empty, skipping" >&2 return fi mkdir -p "${cron_dir}" cron_d_file="${cron_dir}/$(basename "${systemd_unit}" .timer)" # embed source and sha256 gen_origin >"${cron_d_file}" # generate the cron entry into the appropriate file gen_cron_entry >>"${cron_d_file}" } unit-translator-0.8/backends/inetd000066400000000000000000000013521506501000400173170ustar00rootroot00000000000000# inetd output backend # shellcheck shell=bash export_inetd() { systemd_unit=$1 base_dir=$2 if [[ "${socket[ListenStream]:-}" ]]; then port="${socket[ListenStream]}" type="stream" proto="tcp" flags="wait" elif [[ "${socket[ListenDatagram]:-}" ]]; then port="${socket[ListenDatagram]}" type="dgram" proto="udp" flags="nowait" elif [[ "${socket[ListenSequentialPackets]:-}" ]]; then echo "Sequential Packets not supported by inetd backend" >&2 exit 1 fi # printf 'Run "update-inetd --add ' printf "'%s %s %s %s %s %s %s'\n" "$port" "$type" "$proto" "$flags" "${service[User]:-root}" "${service[ExecStart]// */}" "${service[ExecStart]#* }" } unit-translator-0.8/backends/lsb000066400000000000000000000246321506501000400170020ustar00rootroot00000000000000# LSB output backend # shellcheck shell=bash gen_shebang() { # Shebang printf '%s\n' '#! /bin/sh' } gen_init_d_script() { printf '%s\n' '# kFreeBSD does not accept scripts as interpreters, using #!/bin/sh and sourcing.' printf '%s\n' 'if [ true != "$INIT_D_SCRIPT_SOURCED" ] ; then' printf '%s\n' ' set "$0" "$@"; INIT_D_SCRIPT_SOURCED=true . /lib/init/init-d-script' printf '%s\n' 'fi' } gen_start() { case "${install[WantedBy]:-}" in '') ;; sysinit.target) printf 'S' ;; single.target|basic.target) printf '1' ;; *.target) printf '2 3 4 5' ;; *) echo "WARNING: unknown start target runlevel ${install[WantedBy]}" >&2 ;; esac } gen_stop() { case "${install[WantedBy]:-}" in '') ;; *.target) printf '0 1 6' ;; *) echo "WARNING: unknown stop target runlevel ${install[WantedBy]}" >&2 ;; esac } munge_deps() { self=$(local -; set +o noglob; find /etc/insserv.conf* -type f -exec awk "/$(basename "$lsb_init_script")/ {print \$1}" '{}' ';') for key in Requires Wants After Before; do for dep in ${depends[$key]:-}; do if [[ "$dep" == "$self" ]] ; then remove_depend "$dep" "$key" fi done done add_depends Wants ${depends[After]:-} for dep in ${depends[Requires]:-}; do remove_depend "$dep" Wants done for dep in ${depends[Wants]:-}; do remove_depend "$dep" Before done } gen_lsb_header() { # Uncomment to show the data parsed by the frontend # declare -p unit >&2 # declare -p service >&2 # declare -p depends >&2 # declare -p install >&2 munge_deps printf '%s\n' '### BEGIN INIT INFO' printf '%s %s\n' '# Provides:' "$(basename "${systemd_unit%.*}")${install[Alias]:+ ${install[Alias]%.*}}" printf '%s %s\n' '# Required-Start:' "${depends[Requires]:-}" printf '%s %s\n' '# Required-Stop:' "${depends[Requires]:-}" [[ "${depends[Wants]:-}" ]] && printf '%s %s\n' '# Should-Start:' "${depends[Wants]}" [[ "${depends[Wants]:-}" ]] && printf '%s %s\n' '# Should-Stop:' "${depends[Wants]}" printf '%s %s\n' '# Default-Start:' "$(gen_start)" printf '%s %s\n' '# Default-Stop:' "$(gen_stop)" [[ "${depends[Before]:-}" ]] && printf '%s %s\n' '# X-Start-Before:' "${depends[Before]}" [[ "${depends[Before]:-}" ]] && printf '%s %s\n' '# X-Stop-After:' "${depends[Before]}" # printf '%s %s\n' '# X-Interactive:' 'true' printf '%s %s\n' '# Description:' "${unit[Description]:-None provided}" printf '%s\n' '### END INIT INFO' } gen_lsb_environment() { gen_environment if [[ "${service[ExecStop]:-}${service[ExecReload]:-}" =~ '$MAINPID' ]]; then print_directive MAINPID '$([ ! -f "/run/${DAEMON}.pid" ] || cat "/run/${DAEMON}.pid")' fi } gen_directives() { if [[ "${unit[Description]:-}" ]]; then print_directive DESC "${unit[Description]}" fi print_directive umask "${service[UMask]:-}" if [[ -n "${service[ExecStart]:-}" ]]; then if [[ "${service[Type]}" == 'oneshot' ]] ; then print_directive DAEMON none print_directive NAME "$(basename "${systemd_unit%.*}")" print_directive TYPE oneshot gen_lsb_environment for key in "${!lsb[@]}"; do # Skip directives just for s-s-d [[ "$key" != START_ARGS ]] || continue print_directive "$key" "${lsb[$key]}" done print_sh_function do_start_cmd_override "$(handle_exec_prefixes "${service[ExecStart]}" runas)" else set -- ${service[ExecStart]} if [[ "${service[Type]}" == 'socket' ]]; then print_directive NAME "$(basename "${systemd_unit%.*}")" print_directive COMMAND_NAME none print_directive DAEMON none lsb[START_ARGS]+=' --startas /usr/bin/socket-activate' else case $1 in /*) print_directive DAEMON "$1" ;; -*) print_directive DAEMON "${1#-}" echo "TODO: ignore s-s-d exit status" >&2 ;; @*|+*|!*) echo "WARNING: ignoring special exec prefix" >&2 print_directive DAEMON "${1##@|+|!|!!}" ;; *) print_directive DAEMON "$(PATH=/usr/sbin:/usr/bin:/sbin:/bin: which "$1")" ;; esac fi shift gen_lsb_environment print_directive DAEMON_ARGS "$*" for key in "${!lsb[@]}"; do print_directive "$key" "${lsb[$key]}" done fi fi while read -r ulimit ; do printf 'ulimit %s\n' "$ulimit" done < <(gen_ulimit_args) } # Based on backends/openrc but with /lib/lsb/init-functions functions gen_pre_checks() { for constraint in Assert Condition; do for test in ACPower Architecture Capability ControlGroupController CPUFeature CPUs DirectoryNotEmpty Environment FileIsExecutable FileNotEmpty Firmware FirstBoot Group Host KernelCommandLine KernelVersion Memory NeedsUpdate OSRelease PathExists PathExistsGlob PathIsDirectory PathIsEncrypted PathIsMountPoint PathIsReadWrite PathIsSymbolicLink Security User Virtualization; do while read -r trigger read -r pre read -r p; do t=$(gen_test_case "$test" "$pre" "$p") if [ "${t}" ]; then if [ "${trigger}" ] ; then triggers+=$'\n'"( $t ) ||" else [ $constraint = 'Assert' ] && fail="echo \"Prohibited by ${constraint}${test} ${pre}${p}\"; exit 1" || fail="do_start_cmd_override() { log_warning_msg \" .. Skipped due to ${constraint}${test} ${pre}${p}\" ; }" echo "$t || $fail" fi else echo "WARNING: unsupported test: $constraint $test ${pre}${p}" >&2 echo ": # WARNING: skipped unsupported ${constraint}${test} ${pre}${p}" fi done < <(split_constraint "${unit[${constraint}${test}]:-}") done done if [[ "${triggers:-}" ]] ; then printf '( # Triggering conditions\n' # Remove the final trailing '||' print_lines "${triggers%||}" ' ' printf ') || do_start_cmd_override() { log_warning_msg " .. Skipped due to no Triggering Conditions" ; }' fi } gen_functions() { # Combine pre-checks and handling of special exec prefixes service[ExecStartPre]=$(cat<&2 exit 0 fi lsb_init_script="${init_dir}/$(basename "${systemd_unit%.*}")" if [[ -z "${lsb_init_script##*@}" ]] ; then echo "LSB backend doesn't support instantiated services, skipping" >&2 exit 0 fi # array to hold directives declare -A lsb case "${service[Type]}" in oneshot) ;; simple|exec|idle|dbus|socket) lsb[PIDFILE]="/run/$(basename "${lsb_init_script}").pid" lsb[START_ARGS]="--background --make-pidfile";; notify|notify-reload) lsb[PIDFILE]="/run/$(basename "${lsb_init_script}").pid" lsb[START_ARGS]="--background --make-pidfile --notify-await" if [[ "${service[Type]}" == 'notify-reload' ]]; then lsb[RELOAD_SIGNAL]=HUP fi ;; forking) lsb[PIDFILE]=none ;; esac # run as $user and $group if the systemd service provides one if [[ "${service[User]:-}" ]]; then local user="${service[User]}" if [[ "${service[Group]:-}" ]]; then local group="${service[Group]}" fi lsb[START_ARGS]+=" --chuid ${user}${group:+:"${group}"}" fi if [[ -n "${service[Nice]:-}" ]]; then lsb[START_ARGS]+=" --nicelevel ${service[Nice]}" fi if [[ -n "${service[IOSchedulingClass]:-}" || -n "${service[IOSchedulingPriority]:-}" ]]; then lsb[START_ARGS]+=" --iosched ${service[IOSchedulingClass]:-best-effort}:${service[IOSchedulingPriority]:-4}" fi if [[ -n "${service[UMask]:-}" ]]; then lsb[START_ARGS]+=" --umask ${service[UMask]:-}" fi if [[ -n "${service[WorkingDirectory]:-}" ]]; then lsb[START_ARGS]+=" --chdir ${service[WorkingDirectory]#-}" fi if [[ -n "${service[RootDirectory]:-}" ]]; then lsb[START_ARGS]+=" --chroot ${service[RootDirectory]}" fi if [[ "$(uname -s)" == 'Linux' ]]; then if [[ -n "${service[NoNewPrivileges]:-}" ]] && is_true "${service[NoNewPrivileges]}"; then lsb[SETPRIV_ARGS]+="--no-new-privs " fi if [[ -n "${service[SecureBits]:-}" ]]; then secbits=${service[SecureBits]/-/_} lsb[SETPRIV_ARGS]+="--securebits +${secbits//[$' \r\t\n']/,+} " fi if [[ "${service[CapabilityBoundingSet]+defined}" ]] ; then case "${service[CapabilityBoundingSet]}" in # start-stop-daemon requires CAP_SETUID and CAP_SETGID for --chuid '') lsb[SETPRIV_ARGS]+="--bounding-set -all${service[User]:+,+CAP_SETUID,+CAP_SETGID} " ;; ~*) caps="${service[CapabilityBoundingSet]//CAP_/,-}" if [[ "${service[User]:-}" ]] ; then caps="${caps//,-SET[UG]ID}" fi lsb[SETPRIV_ARGS]+="--bounding-set +all${caps//[$'~ \r\t\n']} " ;; *) caps="${service[CapabilityBoundingSet]//CAP_/,+}" lsb[SETPRIV_ARGS]+="--bounding-set -all${caps//[$' \r\t\n']} " ;; esac fi if [[ "${service[AmbientCapabilities]+defined}" ]] ; then case "${service[AmbientCapabilities]}" in '') lsb[SETPRIV_ARGS]+="--ambient-caps -all ";; ~*) caps="${service[AmbientCapabilities]//CAP_/,-}" lsb[SETPRIV_ARGS]+="--ambient-caps +all${caps//[$'~ \r\t\n']} " ;; *) caps="${service[AmbientCapabilities]//CAP_/,+}" lsb[SETPRIV_ARGS]+="--ambient-caps -all${caps//[$' \r\t\n']} " # TODO: does this require --secbits keep-cap? See # https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#AmbientCapabilities= ;; esac fi fi # shebang gen_shebang >"${lsb_init_script}" # embed source and sha256 gen_origin >>"${lsb_init_script}" # source init-d-script gen_init_d_script >>"${lsb_init_script}" gen_lsb_header >>"${lsb_init_script}" gen_directives >>"${lsb_init_script}" gen_functions >>"${lsb_init_script}" chmod +x "${lsb_init_script}" } unit-translator-0.8/backends/openrc000066400000000000000000000346471506501000400175170ustar00rootroot00000000000000# openrc output backend # shellcheck shell=bash # add openrc shebang # # rely on in-built variables and functions available via the # openrc-run interpreter gen_openrc_script_shebang() { printf '%s\n' '#!/sbin/openrc-run' } # Return the complement of the supplied capabilities cap_complement() { declare -A avail="( $(setpriv --list-caps | awk 'BEGIN{ORS=" ";} { print "CAP_" toupper($0), NR }') )" for cap do unset 'avail[${cap}]' done echo "${!avail[@]}" } #cap_complement CAP_CHOWN CAP_LL CAP_ANOTHET # generate variables from the values defined in service[key]. # gen_openrc_script_variables() { local ssd_args # add service description printf '%s\n' "description=\"${unit[Description]:-None provided}.\"" if [ "${unit[Documentation]:-}" ]; then printf '# %s:\n' 'Documentation' while read -r doc; do # shellcheck disable=SC2086 printf '# %s\n' ${doc} # Unquoted to split whitespace done <<<"${unit[Documentation]}" fi printf '\n' if [[ "${service[Type]}" != "forking" && "${service[Type]}" != oneshot ]]; then if [[ "${service[Restart]:-}" != no ]]; then # TODO: supervise-daemon doesn't distinguish Restart categories print_directive supervisor supervise-daemon fi fi gen_openrc_script_exec_event --start "${service[ExecStart]:-}" print_directive umask "${service[UMask]:-}" print_directive directory "${service[WorkingDirectory]:+"${service[WorkingDirectory]#-}"}" # Ignore '-' prefix if [[ -n "${service[RootDirectory]:-}" ]]; then ssd_args="--chroot ${service[RootDirectory]} " fi if [[ -n "${service[Nice]:-}" ]]; then ssd_args+="--nicelevel ${service[Nice]} " fi if [[ "$(uname -s)" == 'Linux' ]] ; then if [[ -n "${service[IOSchedulingClass]:-}" || -n "${service[IOSchedulingPriority]:-}" ]]; then ssd_args+="--iosched ${service[IOSchedulingClass]:-best-effort}:${service[IOSchedulingPriority]:-4} " fi if [[ -n "${service[NoNewPrivileges]:-}" ]] && is_true "${service[NoNewPrivileges]}"; then ssd_args+="--no-new-privs " fi if [[ -n "${service[SecureBits]:-}" ]]; then local secbits=0 for sbit in ${service[SecureBits]}; do case $sbit in # See linux/securebits.h noroot) ((secbits |= 1)) ;; noroot-locked) ((secbits |= 1 << 1)) ;; no-setuid-fixup) ((secbits |= 1 << 2)) ;; no-setuid-fixup-locked) ((secbits |= 1 << 3)) ;; keep-caps) ((secbits |= 1 << 4)) ;; keep-caps-locked) ((secbits |= 1 << 5)) ;; *) ;; esac done ssd_args+="--secbits ${secbits} " fi if [[ -n "${service[OOMScoreAdjust]:-}" ]]; then ssh_args+="--oom-score-adj ${service[OOMScoreAdjust]} " fi if [[ "${service[CapabilityBoundingSet]+defined}" ]] ; then # openrc --capabilities use cap_iab_from_text(3) which doesn't # support all/none. So build the complement manually. caps=$(cap_complement ${service[CapabilityBoundingSet]#'~'}) case ${service[CapabilityBoundingSet]} in '') caps_args="!${caps//[$' \n\r\t']/,!}" ;; ~*) caps_args="${caps//[$' \n\r\t']/,}" caps_args+="${service[CapabilityBoundingSet]//[$' ~\n\r\t']/,!}" ;; *) caps_args="!${caps//[$' \n\r\t']/,!}" caps_args+=",${service[CapabilityBoundingSet]//[$' \n\r\t']/,}" ;; esac fi if [[ "${service[AmbientCapabilities]+defined}" ]] ; then case ${service[AmbientCapabilities]} in '') ;; ~*) caps=$(cap_complement ${service[AmbientCapabilities]#'~'}) caps_args+="${caps_args:+,}^${caps//[$' \n\r\t']/,^}" ;; *) caps_args+="${caps_args:+,}^${service[AmbientCapabilities]//[$' \n\r\t']/,^}" ;; esac fi if [[ -n "${caps_args:-}" ]]; then ssd_args+="--capabilities $caps_args " fi fi print_directive start_stop_daemon_args "${ssd_args:-}" } # Replace insserv/LSB dependency with supplied expansion replace_depend() { dep=$1 key=$2 shift 2 # /etc/insserv.conf expansions are preceeded by +, i.e. optional, so move # Requires to Uses if [[ "$key" == Requires ]]; then append_key=Uses else append_key="$key" fi # Remove original remove_depend "$dep" "$key" # Add replacements add_depends "$append_key" "$@" # Remove circular dependencies remove_depend "${openrc_script}" "$key" } # depends[] is insserv/LSB style. Recursively expand virtual references. expand_lsb_depends() { while [[ "${depends[*]}" =~ '$' ]]; do for key in Requires Wants After Before Uses; do for dep in ${depends[$key]:-}; do case "$dep" in # TODO: taken from /etc/insserv.conf. Consider grep rather than harcoding. \$local_fs) replace_depend "$dep" "$key" mountall mountall-bootclean mountoverflowtmp umountfs ;; \$network) replace_depend "$dep" "$key" networking ifupdown ;; \$named) replace_depend "$dep" "$key" named dnsmasq lwresd bind9 unbound pdns-recursor \$network ;; \$remote_fs) replace_depend "$dep" "$key" \$local_fs mountnfs mountnfs-bootclean umountnfs sendsigs ;; \$syslog) replace_depend "$dep" "$key" rsyslog sysklogd syslog-ng dsyslog inetutils-syslogd ;; \$time) replace_depend "$dep" "$key" hwclock ;; # and /etc/insserv.conf.d \$portmap) replace_depend "$dep" "$key" rpcbind ;; \$*) echo "WARNING: ignoring unknown virtual dependency $dep" >&2 replace_depend "$dep" "$key" '' ;; esac done done done } # dependency resolution # # pending: special cases gen_openrc_script_functions() { # if dependencies exist then check which dependency exists. according to the # check, use either one of `need`, `use`, `before` and `after`. if [[ -n "${depends[*]}"|| -n "${install[Alias]:-}" ]]; then expand_lsb_depends printf '%s\n' 'depend() {' if [[ -n "${depends[Requires]:-}" ]]; then printf "%s\n" " need ${depends[Requires]}" fi if [[ -n "${depends[Wants]:-}" ]]; then printf "%s\n" " want ${depends[Wants]}" fi if [[ -n "${depends[Uses]:-}" ]]; then printf "%s\n" " use ${depends[Uses]}" fi if [[ -n "${depends[Before]:-}" ]]; then printf "%s\n" " before ${depends[Before]}" fi if [[ -n "${depends[After]:-}" ]]; then printf "%s\n" " after ${depends[After]}" fi if [[ -n "${install[Alias]:-}" ]]; then alias=${install[Alias]//.service} printf "%s\n" " provide ${alias#$}" # Could be lsb/insserv style; remove any leading $ fi printf '%s\n' '}' fi for t in Stop Start-Pre Start-Post Stop-Pre Stop-Post Reload ; do gen_openrc_script_exec_event "--${t@L}" "${service[Exec${t/-/}]:-}" done } # generate configuration required by the service gen_openrc_script_conf() { gen_environment if [[ "${service[ExecStop]:-}${service[ExecReload]:-}" =~ '$MAINPID' ]]; then print_directive MAINPID '$([ ! -f "/run/supervise-${RC_SVCNAME}.pid" ] || cat "/run/supervise-${RC_SVCNAME}.pid")' fi if [[ "${service[KillMode]:-control-group}" = 'control-group' ]]; then print_directive rc_cgroup_cleanup YES print_directive rc_send_sighup "${service[SendSIGHUP]:-no}" fi while read -r tmp ; do ulimit+="$tmp " done < <(gen_ulimit_args) print_directive rc_ulimit "${ulimit:-}" } # a generic function that handles all events of type: start, stop, reload, # start_pre, stop_pre, start_post, stop_post. # # this function accepts two arguments: first one is the type of execution # (start, stop, etc) and the second is the actual command performing said # execution. gen_openrc_script_exec_event() { local exec_name exec_args exec_type exec_event runas # type is {start,stop,restart,reload} and event is the actual $type command exec_type="${1}" if [[ "${service[Type]}" == oneshot || "${exec_type}" == "--*-*" ]]; then runas=runas fi exec_event=$(handle_exec_prefixes "${2}" "${runas:-}") # start generating the script case "${exec_type}" in "--start") # Handle oneshot with a custom start(). Multiple ExecStart possible if [[ "${service[Type]}" == oneshot ]]; then print_sh_function start "${exec_event}" else # Everything before the first space exec_name=${exec_event%% *} # Everything not in $exec_name exec_args=${exec_event#"${exec_name}"} printf '%s\n' "command=\"${exec_name}\"" printf '%s\n' "command_args=\"${exec_args# }\"" # trim initial whitespace fi # run as $user and $group if the systemd service provides one if [[ "${service[User]:-}" ]]; then local user user="${service[User]}" if [[ "${service[Group]:-}" ]]; then local group group="${service[Group]}" printf '%s\n' "command_user=\"${user}:${group}\"" else printf '%s\n' "command_user=\"${user}\"" fi fi # define PID if the systemd service provides one if [[ "${service[PIDFile]:-}" ]]; then local pid_file pid_file="${service[PIDFile]}" printf '%s\n' "pidfile=\"${pid_file}\"" fi ;; "--stop") print_sh_function stop "${exec_event}" ;; "--start-pre") chks=$(cat <&2 echo ": # WARNING: skipped unsupported ${constraint}${test} ${pre}${p}" fi unset t trigger pre done < <(split_constraint "${unit[${constraint}${test}]:-}") done done if [[ "${triggers:-}" ]] ; then printf '( # Triggering conditions\n' # Remove the final trailing '||' print_lines "${triggers%||}" ' ' printf ') || start() { einfo "Skipped due to no Triggering Conditions" ; }' fi } export_service() { systemd_unit=$1 base_dir=$2 if [[ "${unit_flavour}" == user ]]; then base_dir+=/user fi openrc_init_dir="${base_dir}/init.d" mkdir -p "${openrc_init_dir}" openrc_script=$(basename "${systemd_unit%.*}") # indicate instantiated units if [[ -z "${openrc_script#*@}" ]]; then instantiated=1 openrc_script="${openrc_script/\@/\.}" else instantiated=0 fi # Handle service Type specifics case "${service[Type]}" in oneshot) if ! is_true "${service[RemainAfterExit]:-no}" ; then service[ExecStartPost]+=$'\n!mark_service_stopped' fi ;; notify-reload) service[ExecReload]='supervise-daemon "${RC_SVCNAME}" -s HUP' ;; esac # generate the "#!/sbin/openrc-run" shebang which is required to run # openrc scripts gen_openrc_script_shebang >"${openrc_init_dir}/${openrc_script}" # embed source and sha256 gen_origin >>"${openrc_init_dir}/${openrc_script}" # generate and store necessary openrc variables in the appropriate file gen_openrc_script_variables | replace_specifiers >>"${openrc_init_dir}/${openrc_script}" # generate and store necessary openrc functions in the appropriate file gen_openrc_script_functions | replace_specifiers >>"${openrc_init_dir}/${openrc_script}" # make resulting script executable chmod +x "${openrc_init_dir}/${openrc_script}" # Handle directives for conf.d file conf_d_contents=$(gen_openrc_script_conf) if [[ -n "${conf_d_contents:-}" ]]; then openrc_conf_dir="${base_dir}/conf.d" mkdir -p "${openrc_conf_dir}" # generate and store the environment variables in the appropriate file gen_origin >"${openrc_conf_dir}/${openrc_script}" printf '%s\n' "$conf_d_contents" >>"${openrc_conf_dir}/${openrc_script}" fi } unit-translator-0.8/backends/xinetd000066400000000000000000000025031506501000400175060ustar00rootroot00000000000000# xinetd output backend # shellcheck shell=bash gen_xinetd_entry() { printf '%s\n' "service $(basename "${service[ExecStart]// */}")" printf '%s\n' '{' printf '%s\n' " disabled = no" if [[ "${socket[ListenStream]:-}" ]]; then printf '%s\n' " port = ${socket[ListenStream]}" printf '%s\n' " socket_type = stream" printf '%s\n' " wait = no" elif [[ "${socket[ListenDatagram]:-}" ]]; then printf '%s\n' " port = ${socket[ListenDatagram]}" printf '%s\n' " socket_type = dgram" printf '%s\n' " protocol = udp" printf '%s\n' " wait = yes" elif [[ "${socket[ListenSequentialPackets]:-}" ]]; then printf '%s\n' " port = ${socket[ListenSequentialPackets]}" printf '%s\n' " socket_type = seqpacket" printf '%s\n' " protocol = udp" printf '%s\n' " wait = yes" fi printf '%s\n' " user = ${service[User]:-root}" server=${service[ExecStart]%% *} printf '%s\n' " server = $server" server_args=${service[ExecStart]#"${server}"} printf '%s\n' " server_args = ${server_args# }" printf '%s\n' '}' } export_inetd() { systemd_unit=$1 base_dir=$2 xinetd_dir="${base_dir}/xinetd.d" mkdir -p "${xinetd_dir}" xinetd_file="${xinetd_dir}/$(basename "${systemd_unit}" .socket)" # generate a xinetd configuration file gen_xinetd_entry "${systemd_unit%%.socket}" >"${xinetd_file}" } unit-translator-0.8/man/000077500000000000000000000000001506501000400152715ustar00rootroot00000000000000unit-translator-0.8/man/utrans-deb.1000066400000000000000000000020371506501000400174210ustar00rootroot00000000000000.TH "UTRANS-DEB" "1" 2024-10-07 "Version 0.7" "User Commands" .SH "NAME" .PP \fCutrans-deb\fP \- apply \fIutrans\fP to all systemd units in given deb files .SH "SYNOPSIS" \fCutrans [ -b ] * \fP .SH "OPTIONS" .PP The following commandline options are supported:- .TP \fB-b \fP use the comma-separated list of backends for all \fIutrans\fP calls. The last mentioned backend of each type (service, socket, timer) will be used. .SH "DESCRIPTION" .PP \fCutrans-deb\fP uses \fIutrans\fP to translate systemd services, sockets and timers in given \fIdeb\fP files to corresponding generic UNIX configuration and scripting as used in alternative settings that exclude systemd. The translation creates a temporary working directory in the current directory for unpacking of all \fIdeb\fP given files and translation of their units. The result is packed up into a tar file named as the first \fIdeb\fP file with "-services.tgz" appended. .SH SEE ALSO .P utrans(1) .SH "AUTHORS" .PP Copyright 2024, Ralph Ronnquist unit-translator-0.8/man/utrans-rc.1000066400000000000000000000035661506501000400173030ustar00rootroot00000000000000.TH "UTRANS-RC" "1" 2024-09-09 "Version 0.7" "User Commands" .P .SH NAME .P utrans-rc - convert systemd units using utrans(1) and then save or install them .P .SH SYNOPSIS .P utrans-rc [-h] [-b .\&.\&] [-n | -o ] [-u | .\&.\&] .P .SH OPTIONS .P The following commandline options are supported:- .TP \fB-b \fR specify backend used by utrans(1). If unset, automatically detect the appropriate backends to use .TP \fB-h\fR show usage .TP \fB-o \fR save generated files to subdirectories of .TP \fB-n\fR no action; dry run to show what would be done .TP \fB-u\fR only check for new/modified/deleted units and update; ignored if or specified .SH DESCRIPTION .P utrans-rc scans the source directory/directories for systemd.\&service(5) and systemd.\&timer(5) units. If no source directory is specified, \fI[/usr]/lib/systemd/system\fR and \fI[/usr]/lib/user/system\fR are scanned. .P Each unit is converted using utrans(1). .P If is specified, generated files are saved there. Otherwise, the files are considered for installation beneath \fI/etc\fR using sudo(8) and ucf(1) to preserve user modifications. However, installation of the whole unit is skipped if .IP \(bu 2 the unit'\&s parent package already includes functionality in the installation directory .IP \(bu 2 it would overwrite any files in a package known to dpkg(1) .P utrans-rc also checks previously generated and installed files for changes in their source units. If any modified units are found they are re-translated. If the source units have been removed, the generated files are disabled and removed (if unmodified). .SH SEE ALSO .P utrans(1), ucf(1) .P .SH COPYRIGHT .P BSD-2-Clause License .P Copyright (c) 2023-2025, Mark Hindley .P .SH AUTHORS .P Mark Hindley unit-translator-0.8/man/utrans.1000066400000000000000000000104611506501000400166710ustar00rootroot00000000000000.TH "UTRANS" "1" 2024-09-09 "Version 0.7" "User Commands" .SH "NAME" .PP \fCutrans\fP \- convert systemd units to generic unix equivalents .SH "SYNOPSIS" \fCutrans [-b [=][,]] [-f ] [-h] [-u] [-v] \fP .SH "OPTIONS" .PP The following commandline options are supported:- .TP \fB-b \fP load the specified backend(s). Can be a comma separated list. See \fIBACKENDS\fP below for further details. .TP \fB-f \fP force; valid types are: .IP \fBoverwrite\fP: allow overwriting existing output files .TP \fB-h\fP open manpage .TP \fB-u\fP handle source as user unit .TP \fB-v\fP show version .SH "DESCRIPTION" .PP \fCutrans\fP translates systemd services, sockets and timers to corresponding generic UNIX configuration and scripting as used in alternative settings that exclude systemd. is the pathname for the unit file to translate. .IP \(bu 4 Service units are converted to \fBopenrc-run(8)\fP service scripts. .IP \(bu 4 Socket units are converted either to an \fBxinetd(8)\fP configuration or an \fBopenrc-run(8)\fP script using \fBsocket-activate(1)\fP, depending on the requirements of the socket. .IP \(bu 4 Timer units are converted to a \fBcrontab(5)\fP fragment mapping \fBsystemd.time(7)\fP events to a \fBcron(8)\fP-compatible expressions. .SH "USAGE" .PP \fCutrans\fP accepts two arguments: the \fBsystemd.unit(5)\fP file () and the output directory (). The \fBsystemd.unit(5)\fP file which can be one of \fBsystemd.service(5)\fP, \fBsystemd.socket(5)\fP or \fBsystemd.timer(5)\fP. .PP The script will create files in the following output subdirectories of depending on the unit type: .TS center,allbox; r lx . Unit Output Service T{ An \fBopenrc-run(8)\fP script is created in \fIinit.d\fP and a complementary file is created in \fIconf.d\fP if needed, for sourcing additional additional environment variables. T} Socket T{ Depending upon the socket's configuration, an \fBxinetd(8)\fP configuration fragment is created in \fIxinetd.d\fP, or an \fBopenrc-run(8)\fP script is created in \fIinit.d\fP to start the service with \fBsocket-activate(1)\fP, . T} Timer T{ A separate \fBcrontab(5)\fR fragment is created into \fIcron.d\fP for each timer unit. T} .TE .PP When the unit translation requires other units, then these are searched in the paths defined in the environment variable \fI$SYSTEMD_UNIT_PATH\fP, or in \fI/usr/lib/systemd/system:/lib/systemd/system\fP for system services or \fI/usr/lib/systemd/user:/lib/systemd/user\fP for user services. .SH "BACKENDS" .PP Backends are used for producing different output for different settings. \fButrans\fR finds and sources backend script fragments from the \fI$UTRANS_DATA_DIR/backends\fP directory or from \fI/usr/share/utrans/backends\fP by default. .PP If the backend is prefixed with \fC=\fP, the default backends are not loaded. .PP The following backends are available: .TS center,allbox; l l lx . Backend Unit Notes openrc Service Loaded by default xinetd Socket Loaded by default cron Timer Loaded by default inetd Socket T{ Only supports ListenStream and ListenDatagram using \fBupdate-inetd(8)\fP T} lsb Service T{ Alpha quality support for scripts with LSB-style \fBinsserv(8)\fP headers T} .TE .PP Contributions of other backends are welcome: see \fIbackends/README\fP for further details. .SH "LIMITATIONS" .PP The following are known cases of poor or variant functionality: .IP \(bu 4 Some special Executable Prefixes are ignored after warning. .IP \(bu 4 Unescaping specifiers doesn't distinguish context: if you require a filesystem path, start the instance with '-'. .IP \(bu 4 \fBBindsTo\fP, \fBRequisite\fP, \fBPartOf\fP and \fBUpholds\fP are mapped (imperfectly) to \fBRequires\fP or \fBWants\fP. .PP Patches are welcome at \fIhttps://git.devuan.org/LeePen/unit-translator\fP! .SH SEE ALSO .P utrans-rc(1), openrc(8), socket-activate(1) .SH "COPYRIGHT" .P BSD-2-Clause License \fIhttps://opensource.org/licenses/BSD-2-Clause\fP .P Copyright (c) 2020, K Gopal Krishna. .P Copyright (c) 2023-2025, Mark Hindley .SH "AUTHORS" .PP This project was started as part of Google Summer of Code, 2020 by K Gopal Krishna under the guidance of Benda Xu , Adam Borowski and Mo Zhou . .PP Continued by Mark Hindley unit-translator-0.8/t/000077500000000000000000000000001506501000400147615ustar00rootroot00000000000000unit-translator-0.8/t/expected/000077500000000000000000000000001506501000400165625ustar00rootroot00000000000000unit-translator-0.8/t/expected/cron/000077500000000000000000000000001506501000400175235ustar00rootroot00000000000000unit-translator-0.8/t/expected/cron/cron.d/000077500000000000000000000000001506501000400207065ustar00rootroot00000000000000unit-translator-0.8/t/expected/cron/cron.d/oneshot000066400000000000000000000007541506501000400223160ustar00rootroot00000000000000# Generated by ../utrans from: # c04adf70b0a86fb204f5229920c3383457549d9cfb1307ce6b26ea78414211c4 ./input/oneshot.timer # 42c736a23f12cf779a58c359fc069349634d869e2a81353bcefbaca0c5d648d5 ./input/oneshot.service # m h dom mon dow user command 10 03 * * sun nobody [ -d /run/systemd/system ] || { /usr/bin/ls /; binary_found_in_path; /bin/false || true; /no/expansion \$USER; } # Alternatively, if your system is running openrc you could use # 10 03 * * sun root rc-service oneshot start unit-translator-0.8/t/expected/inetd/000077500000000000000000000000001506501000400176655ustar00rootroot00000000000000unit-translator-0.8/t/expected/inetd/.empty000066400000000000000000000000001506501000400210120ustar00rootroot00000000000000unit-translator-0.8/t/expected/lsb/000077500000000000000000000000001506501000400173425ustar00rootroot00000000000000unit-translator-0.8/t/expected/lsb/init.d/000077500000000000000000000000001506501000400205275ustar00rootroot00000000000000unit-translator-0.8/t/expected/lsb/init.d/exec-environment000077500000000000000000000014641506501000400237500ustar00rootroot00000000000000#! /bin/sh # Generated by ../utrans from: # ab5c8d27e830a59dadbb448ea87f6278b04615b7333ba6cc35efb61dc66dcba0 ./input/systemd/exec-environment.service # kFreeBSD does not accept scripts as interpreters, using #!/bin/sh and sourcing. if [ true != "$INIT_D_SCRIPT_SOURCED" ] ; then set "$0" "$@"; INIT_D_SCRIPT_SOURCED=true . /lib/init/init-d-script fi ### BEGIN INIT INFO # Provides: exec-environment # Required-Start: $remote_fs # Required-Stop: $remote_fs # Default-Start: # Default-Stop: # Description: Test for Environment ### END INIT INFO DESC="Test for Environment" DAEMON="none" NAME="exec-environment" TYPE="oneshot" set -a VAR1='word1 word2' VAR2='word3' VAR3='$word 5 6' set +a do_start_cmd_override() { sh -x -c 'test "$VAR1" = "word1 word2" && test "$VAR2" = word3 && test "$VAR3" = "\$word 5 6"' } unit-translator-0.8/t/expected/lsb/init.d/exec-environment-empty000077500000000000000000000014261506501000400251020ustar00rootroot00000000000000#! /bin/sh # Generated by ../utrans from: # 44601a3a5f6d171399ee5cfee763a857f3a049eb4f6498126a8b733ecf6a0542 ./input/systemd/exec-environment-empty.service # kFreeBSD does not accept scripts as interpreters, using #!/bin/sh and sourcing. if [ true != "$INIT_D_SCRIPT_SOURCED" ] ; then set "$0" "$@"; INIT_D_SCRIPT_SOURCED=true . /lib/init/init-d-script fi ### BEGIN INIT INFO # Provides: exec-environment-empty # Required-Start: $remote_fs # Required-Stop: $remote_fs # Default-Start: # Default-Stop: # Description: Test for Environment ### END INIT INFO DESC="Test for Environment" DAEMON="none" NAME="exec-environment-empty" TYPE="oneshot" do_start_cmd_override() { sh -x -c 'test "${VAR1-unset}" = "unset" && test "${VAR2-unset}" = "unset" && test "${VAR3-unset}" = "unset"' } unit-translator-0.8/t/expected/lsb/init.d/exec-environment-multiple000077500000000000000000000015271506501000400256010ustar00rootroot00000000000000#! /bin/sh # Generated by ../utrans from: # 8363ac6652bdffe567946f5fc35a2cd493227141aa9c1d529d4c48d54bdb4113 ./input/systemd/exec-environment-multiple.service # kFreeBSD does not accept scripts as interpreters, using #!/bin/sh and sourcing. if [ true != "$INIT_D_SCRIPT_SOURCED" ] ; then set "$0" "$@"; INIT_D_SCRIPT_SOURCED=true . /lib/init/init-d-script fi ### BEGIN INIT INFO # Provides: exec-environment-multiple # Required-Start: $remote_fs # Required-Stop: $remote_fs # Default-Start: # Default-Stop: # Description: Test for Environment ### END INIT INFO DESC="Test for Environment" DAEMON="none" NAME="exec-environment-multiple" TYPE="oneshot" set -a VAR1='word1 word2' VAR2='word3' VAR3='$word 5 6' VAR3='foobar' set +a do_start_cmd_override() { sh -x -c 'test "$VAR1" = "word1 word2" && test "$VAR2" = word3 && test "$VAR3" = foobar' } unit-translator-0.8/t/expected/lsb/init.d/exec-environment-no-substitute000077500000000000000000000020201506501000400265600ustar00rootroot00000000000000#! /bin/sh # Generated by ../utrans from: # 039dcf48ee6c296011bfd5b0dc14c80ab5eff4d782f688abf5b666ed175bbd83 ./input/systemd/exec-environment-no-substitute.service # kFreeBSD does not accept scripts as interpreters, using #!/bin/sh and sourcing. if [ true != "$INIT_D_SCRIPT_SOURCED" ] ; then set "$0" "$@"; INIT_D_SCRIPT_SOURCED=true . /lib/init/init-d-script fi ### BEGIN INIT INFO # Provides: exec-environment-no-substitute # Required-Start: $remote_fs # Required-Stop: $remote_fs # Default-Start: # Default-Stop: # Description: Test for No Environment Variable Substitution ### END INIT INFO DESC="Test for No Environment Variable Substitution" DAEMON="none" NAME="exec-environment-no-substitute" TYPE="oneshot" set -a VAR2='word3' VAR3='$word 5 6' set +a do_start_cmd_override() { sh -x -c 'test "${VAR1-unset}" = "unset" && test "${VAR2}" = "word3" && test "${VAR3-unset}" = '\''$word 5 6'\''' /bin/sh -x -c 'test "\${VAR1-unset}" != "unset" && test "\${VAR2}" != "word3" && test "\${VAR3-unset}" != '\''\$word 5 6'\''' } unit-translator-0.8/t/expected/lsb/init.d/exec-environmentfile000077500000000000000000000020121506501000400245760ustar00rootroot00000000000000#! /bin/sh # Generated by ../utrans from: # 77f49e74b66e53f6cc9a71fe8ee3cbb03f8889eeaf84ce4f9b385bfdfdc01aa8 ./input/systemd/exec-environmentfile.service # kFreeBSD does not accept scripts as interpreters, using #!/bin/sh and sourcing. if [ true != "$INIT_D_SCRIPT_SOURCED" ] ; then set "$0" "$@"; INIT_D_SCRIPT_SOURCED=true . /lib/init/init-d-script fi ### BEGIN INIT INFO # Provides: exec-environmentfile # Required-Start: $remote_fs # Required-Stop: $remote_fs # Default-Start: # Default-Stop: # Description: Test for EnvironmentFile ### END INIT INFO DESC="Test for EnvironmentFile" DAEMON="none" NAME="exec-environmentfile" TYPE="oneshot" set -a . /dev/stdin <&2 output=$(mktemp -d) trap 'rm -rf ${output}' EXIT find ./input -maxdepth 2 ! -type d -print -exec "$UTRANS" -f overwrite -b ="${backend}" '{}' "${output}" ';' 2>&1 | # Filter expected missing backend errors. grep -vx "ERROR: backend for [a-z]\+ unit not available\." diff -u -r -x '.*' expected/"${backend}" "${output}" rm -rf "${output}" done unit-translator-0.8/t/test-exec_environmentfile.conf000066400000000000000000000002741506501000400230200ustar00rootroot00000000000000VAR1='word1 word2' VAR2=word3 # comment1 ; comment2 ; # comment3 line without an equal VAR3='$word 5 6' VAR4='new\nline' VAR5=password\with\backslashes VAR6=test \ continuation\ lines unit-translator-0.8/t/update000077500000000000000000000005741506501000400161770ustar00rootroot00000000000000#!/bin/sh set -e for backend in openrc cron xinetd inetd lsb ; do echo "Updating expected output for $backend backend" >&2 find ./input -maxdepth 2 ! -type d ! -name '*.~' -exec ../utrans -f overwrite -b ="${backend}" '{}' ./expected/"${backend}" ';' 2>&1 | # Filter expected missing backend errors. grep -vx "ERROR: backend for [a-z]\+ unit not available\." done unit-translator-0.8/utrans000077500000000000000000000566221506501000400157730ustar00rootroot00000000000000#!/usr/bin/env bash # NAME # ---- # utrans: convert systemd units to generic unix equivalents # # AUTHORS # ------- # [https://salsa.debian.org/kayg/systemd-unit-translator] # # K Gopal Krishna under the guidance of Benda Xu # , Adam Borowski and Mo Zhou # . # # This project was a part of Google Summer of Code, 2020. # # [https://git.devuan.org/leepen/unit-translator] # # Mark Hindley # # LICENSE # ------- # BSD-2-Clause License # # Copyright (c) 2020, K Gopal Krishna . All rights reserved. # Copyright (c) 2023-, Mark Hindley . All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Safety options for bash set -o errexit set -o pipefail set -o nounset set -o noglob set -o noclobber readonly VERSION=0.7 # +++ Miscellaneous Functions +++ # read a boolean value and returns true or false # usage: is_true val # val boolean value is_true() { case "$1" in 1 | [Oo][Nn] | [Tt]* | [Yy]*) true ;; *) false ;; esac } # print_usage(): # accepted options: none # accepted arguments: none # # print how to use the translator print_usage() { printf '%s\n' 'Unit Translator' printf '\n%s\n' ' Usage: utrans [-b ] [-h] [-v] /path/to/unit/file /path/to/destination/directory' printf '\n%s\n' ' Supported options:' printf '%s\n' ' -b : load backend(s)' printf '%s\n' ' -f : force' printf '%s\n' ' -h: open manpage' printf '%s\n' ' -v: show version' exit 1 } # print version print_version() { printf '%s version %s\n' 'Unit Translator' "$VERSION" exit } # find unit in load path unit_path_find() { unit=$1 IFS=':' read -ra systemd_unit_path <<< "${SYSTEMD_UNIT_PATH:-/usr/lib/systemd/${unit_flavour}:/lib/systemd/${unit_flavour}}" for dir in "${systemd_unit_path[@]}"; do if [[ -s "$dir/$unit" ]]; then echo "$dir/$unit" && return fi done } # handle or ignore special executable prefixes, literal $ and other # unit-specific quoting. handle_exec_prefixes() { exec_event=$1 special_exec_prefix="^(\@|\-|\:|\+|\!|\!\!)(.*)" readarray -t _exec <<<"${exec_event}" for exec_line in "${_exec[@]}"; do [ "${exec_line}" ] || continue exec_line=$(translate_quote "${exec_line}") if [ "${2:-}" ] && [ "${service[User]:-}${service[Group]:-}" ] ; then runas="runuser -u ${service[User]:-root}${service[Group]:+ -g ${service[Group]}}" else runas= fi while [[ "${exec_line}" =~ ${special_exec_prefix} ]]; do exec_line="${BASH_REMATCH[2]}" case "${BASH_REMATCH[1]}" in -) exec_line+=' || true' ;; :) exec_line=${exec_line//$/\\$} ;; +|!|!!) runas= ;; *) echo "WARNING: ignoring special exec prefix ${BASH_REMATCH[1]}" >&2 ;; esac done printf '%s%s\n' "${runas:+$runas -- }" "${exec_line}" done } # translate quoting to POSIX sh style translate_quote() { for arg do # Systemd escaping of single quotes is invalid in POSIX # sh. Convert \' to '\''. The quoting needs lots of care! arg="${arg//\\\'/\'\\\'\'}" # $ are doubled, undo: see systemd.service(5) arg="${arg//\$$/$}" # Backslashes are doubled, undo: see systemd.service(5) arg="${arg//\\\\/\\}" printf '%s\n' "${arg}" done } # print shell-style assignment directives, double quoted unless $3 is set. print_directive() { local d=$1 local s=${2:-} local q=${3:+\'} if [[ -n "$s" ]]; then printf '%s=%s%s%s\n' "${d}" "${q:=\"}" "${s%% }" "${q}" # Trim trailing whitespace fi } # print string, possibly multiline, optionally add prefix, skip any blank lines. print_lines() { str=$1 prfx=${2:-} while IFS= read -r line || [[ -n "$line" ]]; do [[ -z "$line" ]] || printf "%s%s\n" "${prfx}" "${line}" done <<< "$str" } # print shell-style function, if body not empty print_sh_function() { name=$1 body=$2 if [[ "$body" ]]; then printf '%s\n' "${name}() {" print_lines "${body}" ' ' printf '%s\n' "}" fi } # --- Miscellaneous Functions --- # +++ Backend functions +++ # Stubs missing_backend() { echo "ERROR: backend for $1 unit not available." >&2 exit 2 } export_service () { missing_backend service ; } export_socket () { missing_backend socket ; } export_inetd () { missing_backend inetd ; } export_timer () { missing_backend timer ; } # --- Backend functions --- # +++ Parsing-related Functions +++ # parse_section(): # accepted options: none # accepted arguments: two # "${1}": the file to filter # "${2}": the section to filter # parse_section() { local file section key start value local -A var file="${1}" section="${2}" start=0 while IFS= read -r line || [ -n "$line" ]; do if [[ "${start}" -eq 1 ]]; then if [[ "${line}" =~ ^\[.*\] ]]; then break elif [[ "${line}" =~ ^# ]]; then continue elif [[ "${line}" =~ .*=.* ]]; then while [[ "${line}" =~ \\$ ]]; do read -r continuation line="${line::-1} $continuation" done key="${line%%=*}" value="${line#*=}" if [[ -n "${var["${key}"]:-}" ]]; then var["${key}"]+=$'\n' fi if [[ "${value}" ]]; then var["${key}"]+="${value}" else var["${key}"]="${value}" # Empty value resets fi fi fi if [[ "${line}" == "[$section]" ]]; then start=1 fi done <"${file}" if [[ -n "${var[*]}" ]]; then for k in "${!var[@]}"; do printf '[%s]=%q\n' "$k" "${var[$k]}" done fi } # --- Parsing-related Functions --- # +++ Service-related Functions +++ # generate origin and sha256 header gen_origin() { printf '# Generated by %s from:\n# %s\n' "$0" "$(sha256sum "${systemd_unit}")" if [[ "${socket_service_unit:-}" ]] ; then printf '# %s\n' "$(sha256sum "${socket_service_unit}")" elif [[ -n "${systemd_unit##*.service}" ]] ; then printf '# %s\n' "$(sha256sum "${systemd_unit%.*}.service")" fi printf '\n' } # Setup exec environment. gen_environment() { is_true "${service[IgnoreSIGPIPE]:-}" && printf "trap '' PIPE\n" [[ -z "${service[Environment]:-}${service[EnvironmentFile]:-}" ]] && return printf '%s\n' "set -a" while read -r line; do print_directive "${line%=*}" "${line#*=}" single done <<<"$(xargs -n1 <<< "${service[Environment]:-}")" while read -r file; do [[ "${file}" ]] || continue if [[ -z "${file%%-*}" ]]; then printf '[ -r %s ] && ' "${file#-}" fi # Merge continuation lines; ignore - prefix; only accept lines # with '='. printf ". /dev/stdin </dev/null ; [ ${ac_chk:-} \$? -ne 1 ]" ;; # NeedsUpdate) ;; # FirstBoot) ;; PathExists) echo "[ ${pre:+$pre }-e '$p' ]" ;; PathExistsGlob) echo "[ ${pre:+$pre }-n \"\$(find $(dirname "$p") -maxdepth 1 -wholename '$p' -print -quit)\" ]" ;; PathIsDirectory) echo "[ ${pre:+$pre }-d '$p' ]" ;; PathIsSymbolicLink) echo "[ ${pre:+$pre }-L '$p' ]" ;; PathIsMountPoint) echo "${pre:+$pre }mountpoint -q '$p'" ;; PathIsReadWrite) echo "[ ${pre:+$pre }-r '$p' ] && [ ${pre:+$pre }-w '$p' ]" ;; # PathIsEncrypted) ;; DirectoryNotEmpty) echo "[ -d '$p' ] && files=\$(ls -qA -- '$p') && [ ${pre:+$pre }-n \"\$files\" ]" ;; FileNotEmpty) echo "[ ${pre:+$pre }-s '$p' ]" ;; FileIsExecutable) echo "[ ${pre:+$pre }-x '$p' ]" ;; User) if [ "$p" = '@system' ]; then chk='-lt' p=1000 else chk='-eq' fi echo "[ ${pre:+$pre }\"\$(id -u)\" ${chk} \"\$(id -u '$p')\" ]" ;; Group) echo "id -G | grep -qw${pre:+v} \"\$(getent group '$p' | awk -F: '{print \$3}')\"" ;; # ControlGroupController) ;; Memory) echo "[ \$(( \$(head -1 /proc/meminfo | tr -s '[:space:]' | cut -f2 -d' ') * 1024)) -ge '$p' ]" ;; CPUs) echo "[ \$(grep -c ^processor /proc/cpuinfo) -ge '$p' ]" ;; CPUFeature) echo "grep '^flags\s\+:' /proc/cpuinfo | grep -q '$p'" ;; # OSRelease) ;; esac } # Translate dependencies to insserv style names. Backends can process further, # as required. map_known_dependencies() { if [[ -n "${service[BusName]:-}" ]]; then service[Type]=dbus fi # Set default Type case "${service[Type]:=simple}" in simple) ;; exec) ;; oneshot) ;; idle) ;; dbus) add_depends Requires dbus add_depends After dbus ;; notify) ;; notify-reload) ;; forking) ;; esac if [[ "${unit_flavour}" == 'system' && "${unit[DefaultDependencies]:-yes}" == 'yes' ]]; then # sysinit.target add_depends Requires \$remote_fs add_depends After \$remote_fs # basic.target: not required explicitly as insserv.conf includes # $local_fs in $remote_fs. # add_depends After \$local_fs fi case "${unit[RequiresMountsFor]:-}" in /run*) add_depends Requires \$local_fs add_depends After \$local_fs ;; /*) add_depends Requires \$remote_fs add_depends After \$remote_fs ;; esac # Handle display-manager.service if [[ "${install[Alias]:-}" == 'display-manager.service' ]]; then if grep -q "${service[ExecStart]}" /etc/X11/default-display-manager ; then install[Alias]=\$x-display-manager else unset 'install[Alias]' fi fi # Handle runtime directories for dir in Runtime State Cache Logs Configuration; do if [[ -n "${service[${dir}Directory]:-}" ]]; then if [[ "${unit_flavour}" == system ]]; then add_depends Requires \$remote_fs add_depends After \$remote_fs fi case "$dir" in Runtime) if [[ "${unit_flavour}" == user ]]; then dirbase="\$XDG_RUNTIME_DIR" else dirbase='/run' fi is_true "${service[RuntimeDirectoryPreserve]:-no}" || service[ExecStopPost]+=$'\n'"rm -r \"\${${dir@U}_DIRECTORY:?}\"" ;; State) if [[ "${unit_flavour}" == user ]]; then dirbase="\${XDG_STATE_HOME:-~/.local/state}" else dirbase='/var/lib' fi ;; Cache) if [[ "${unit_flavour}" == user ]]; then dirbase="\${XDG_CACHE_HOME:-~/.cache}" else dirbase='/var/cache' fi ;; Logs) if [[ "${unit_flavour}" == user ]]; then dirbase="\${XDG_STATE_HOME:-~/.local}/log" else dirbase='/var/log' fi ;; Configuration) if [[ "${unit_flavour}" == user ]]; then dirbase="\${XDG_CONFIG_HOME:-~/.config}" else dirbase='/etc' fi ;; *) ! echo 'ERROR: unknown runtime directory' >&2 ;; esac rtdir="mkdir -p ${service[${dir}DirectoryMode]:+-m ${service[${dir}DirectoryMode]} }\"\${${dir@U}_DIRECTORY:?}\""$'\n' if [[ "${service[User]:-}" ]]; then rtdir+="chown \"${service[User]}${service[Group]:+:${service[Group]}}\" \"\${${dir@U}_DIRECTORY:?}\""$'\n' fi # Prepend service[ExecStartPre]="${rtdir}${service[ExecStartPre]:-}" service[Environment]+=$'\n'"${dir@U}_DIRECTORY=${dirbase}/${service[${dir}Directory]}" fi done for key in After Before Requires Wants BindsTo Requisite PartOf Upholds; do read -ra dependencies <<<"${unit[${key}]:-}" # Remap output key case "$key" in BindsTo) key=Requires ;; Requisite) key=Requires ;; PartOf) key=Requires ;; Upholds) key=Wants ;; esac for dependency in "${dependencies[@]}"; do case "${dependency}" in %N.socket) ;; # Ignore *%*.*) echo "WARNING: ignoring dependency with unsupported specifier ($dependency)" >&2 ;; sysinit.target) # Not for Before [[ "$key" == 'Before' ]] || add_depends "${key}" \$local_fs ;; local-fs.target) add_depends "${key}" \$local_fs ;; network-pre.target) add_depends "${key}" \$local_fs ;; network-online.target) ;; # Ignore 'active' unit (see systemd.special(7)) network*.target | systemd-networkd.service) add_depends "${key}" \$network ;; local-fs-pre.target) add_depends "${key}" mountkernfs ;; time-sync.target) add_depends "${key}" \$time ;; systemd-modules-load.service) add_depends "${key}" kmod ;; systemd-sysctl.service) add_depends "${key}" procps ;; nss-lookup.target) add_depends "${key}" \$named \$network ;; rpcbind.target) add_depends "${key}" \$portmap ;; remote-fs.target|basic.target) add_depends "${key}" \$remote_fs ;; syslog.socket) # systemd specific, ignore unit[${key}]=${unit[$key]/syslog.socket/} ;; display-manager.service|graphical.target) add_depends "${key}" \$x-display-manager ;; *.target) # TODO ;; *.mount) #TODO ;; *) [[ "${dependency%.*}" == "${systemd_unit_file%.*}" ]] && continue add_depends "${key}" "${dependency%.*}" ;; esac done done } # --- Service-related Functions --- # +++ Socket-related Functions +++ map_known_socket_types() { for listen_type in ListenStream ListenDatagram ListenSequentialPacket; do for listen_type_format in ${socket[${listen_type}]:-}; do if [[ "${listen_type_format:-}" =~ [1-9][0-9]{0,4} ]]; then if is_true "${socket[Accept]:-}"; then use_socket_activate=false fi elif [[ "${listen_type_format:-}" =~ ^/.* ]]; then use_socket_activate=true fi done done } # Add socket-activate to ExecStart. gen_socket_activate_cmdline() { printf '%s' '/usr/bin/socket-activate ' # handle Stream and Datagram listen-types while read -r listener; do [ "$listener" ] || continue printf '%s' "--unix::${listener} " done <<<"${socket[ListenStream]:-}" while read -r listener; do [ "$listener" ] || continue printf '%s' "--unix-dgram::${listener} " done <<<"${socket[ListenDatagram]:-}" # end of specifiying options printf '%s' '-- ' # append the service command printf '%s' "${service[ExecStart]}" } gen_ulimit_args() { # Missing in dash: LOCKS SIGPENDING MSGQUEUE NICE # Different in dash: NPROC -p # No equivalent: RTTIME for l in CPU FSIZE DATA STACK CORE RSS NOFILE AS NPROC MEMLOCK LOCKS SIGPENDING MSGQUEUE NICE RTPRIO; do for v in ${service[Limit${l}]:-}; do [ "$v" = infinity ] && v=unlimited case $l in CPU) p=-t ;; FSIZE) p=-f ;; DATA) p=-d ;; STACK) p=-s ;; CORE) p=-c ;; RSS) p=-m ;; NOFILE) p=-n ;; AS) p=-v ;; NPROC) p=-u ulimit $p >/dev/null 2>&1 && p=-p ;; MEMLOCK) p=-l ;; LOCKS) p=-x ;; SIGPENDING) p=-i ;; MSGQUEUE) p=-q ;; NICE) p=-e ;; RTPRIO) p=-r ;; RTTIME) continue ;; *) echo "WARNING: unexpected ulimit" >&2 continue ;; esac # Check that the ulimit flag is accepted before printing ulimit "$p" >/dev/null 2>&1 && printf '%s %s\n' "$p" "$v" done done } # --- Socket-related Functions --- # Helper function to determine implied unit flavour from parent directory. unit_flavour() { case "$(basename "${1}")" in user) echo user ;; *) echo system ;; esac } translate() { systemd_unit="${1}" systemd_unit_file="$(basename "${systemd_unit}")" # Set flavour if it has not been explicitly set on command line. : "${unit_flavour:=$(unit_flavour "${systemd_unit%"$systemd_unit_file"}")}" dest_dir="${2}" # parse key-value pairs from the [Unit] section declare -A unit="( $(parse_section "${systemd_unit}" Unit) )" # and the [Install] section declare -A install="( $(parse_section "${systemd_unit}" Install) )" # variable to pass calculated dependencies to backend declare -A depends # if the file provided is of type .service... if [[ "${systemd_unit}" == *.service ]]; then # Use a name-matched socket instead if [[ -f "${systemd_unit%.service}.socket" ]]; then echo "Using ${systemd_unit%.service}.socket" >&2 translate "${systemd_unit%.service}.socket" "$dest_dir" exit fi # parse key-value pairs from the [Service] section declare -A service="( $(parse_section "${systemd_unit}" Service) )" if [[ "${service[User]:-}" ]]; then service[Environment]+="$(printf '\n%s=%s' USER "${service[User]}")" service[Environment]+="$(printf '\n%s=%s' LOGNAME "${service[User]}")" if is_true "${service[SetLoginEnvironment]:-yes}" ; then service[Environment]+="$(getent passwd "${service[User]}" | awk -F: '{ print "\nHOME="$6 }')" service[Environment]+="$(getent passwd "${service[User]}" | awk -F: '{ print "\nSHELL="$7 }')" fi fi # map known dependencies map_known_dependencies for required in ${unit[Requires]:-} ; do case "$required" in *.socket|*.service) echo "$systemd_unit_file Requires ${required}" >&2 service_required_unit=$(unit_path_find "${required}") if [ "${service_required_unit}" ]; then echo "Found ${service_required_unit}" >&2 # Process in subshell ( set +o noclobber # may already have been processed, allow clobber translate "${service_required_unit}" "$dest_dir" ) # continue else echo "Failed to find required unit (${required})." >&2 exit 1 fi ;; esac done export_service "${systemd_unit}" "${dest_dir}" elif [[ "${systemd_unit}" == *.socket ]]; then # parse key-value pairs from the [Socket] section declare -A socket="( $(parse_section "${systemd_unit}" Socket) )" if is_true "${socket[Accept]:-}"; then socket_service_unit=$(unit_path_find "${socket[Service]:-${systemd_unit_file%%.socket}@.service}") else socket_service_unit=$(unit_path_find "${socket[Service]:-${systemd_unit_file%%.socket}.service}") fi if [[ -z "${socket_service_unit}" ]]; then echo "Failed to find ${socket[Service]:-${systemd_unit_file%%.socket}@.service}" >&2 exit 1 fi # parse key-value pairs from the [Service] section of the accompanying service file declare -A service="( $(parse_section "${socket_service_unit}" Service) )" # parse key-value pairs from the [Unit] section of the accompanying service file declare -A unit="( $(parse_section "${socket_service_unit}" Unit) )" # and append the [Install] section declare -A install+="( $(parse_section "${socket_service_unit}" Install) )" # map known dependencies map_known_dependencies # determine the type of listener and the tool to be used map_known_socket_types # Support socket Exec hooks. for hook in ExecStartPre ExecStartPost ExecStopPre ExecStopPost ; do service[$hook]+=${socket[$hook]:-} done # if the flag has been set to false... if [[ -n "${use_socket_activate:-}" && "${use_socket_activate}" != "true" ]]; then export_inetd "${systemd_unit}" "${dest_dir}" else service[Type]=socket # TODO handle fork/not in socket-activate service[ExecStart]="$(gen_socket_activate_cmdline)" # Make subdirectories for filesystem sockets in start_pre() while read -r listener; do if [[ -z "${listener%%[%/]*}" && -n "${listener%[%/]*/*}" ]]; then service[ExecStartPre]+="$(printf '\n%s\n' "mkdir -p ${socket[DirectoryMode]:+-m ${socket[DirectoryMode]} }$(dirname "${listener}")")" fi done <<<"${socket[ListenStream]:-} ${socket[ListenDatagram]:-}" export_service "${systemd_unit}" "${dest_dir}" fi elif [[ "${systemd_unit}" == *.timer ]]; then # parse key-value pairs from the [Timer] section declare -A timer="( $(parse_section "${systemd_unit}" Timer) )" # parse key-value pairs from the [Service] section from the accompanying service file declare -A service="( $(parse_section "${systemd_unit%%.timer}.service" Service) )" export_timer "${systemd_unit}" "${dest_dir}" fi } main() { default_backends='openrc xinetd cron' backends= while getopts "b:f:huv" o; do case "${o}" in b) if [ -z "${OPTARG##=*}" ] ; then default_backends= OPTARG="${OPTARG#=}" # Remove prefix fi backends+=" ${OPTARG//,/ }" ;; f) case "$OPTARG" in overwrite) set +o noclobber;; *) echo "ERROR: invalid argument to -f"; exit 1 ;; esac ;; h) man utrans ;; u) unit_flavour=user ;; v) print_version ;; *) print_usage ;; esac done shift $((OPTIND-1)) [ $# -lt 2 ] && print_usage if [[ ! -s ${1} ]]; then echo "WARNING: ignoring masked unit ${1}" >&2 else # Source backends for backend in ${default_backends} ${backends}; do # shellcheck source=./backends/cron # shellcheck source=./backends/openrc # shellcheck source=./backends/lsb # shellcheck source=./backends/xinetd # shellcheck source=./backends/inetd . "${UTRANS_DATA_DIR:-/usr/share/utrans}/backends/${backend}" done translate "$@" fi } main "${@}" unit-translator-0.8/utrans-deb000077500000000000000000000020301506501000400165030ustar00rootroot00000000000000#!/bin/bash # # [ -b ] * shopt -s nullglob TEMP="$(mktemp -d -p .)" trap "rm -rf $TEMP" 0 2 die() { echo "** ERROR: $*" ; exit 1 ; } BACKENDS= if [ "$1" = "-b" ] ; then BACKENDS="-b $2" # replace any ; by space shift 2 fi FIRST= for DEB in "${@}" ; do [ -f "$DEB" ] || die "Unkown deb file: $DEB" [ -z "$FIRST" ] && FIRST="${DEB##*/}" dpkg -x "$DEB" "$TEMP" || die "Cannot unpack $DEB" done export SYSTEMD_UNIT_PATH=$TEMP/usr/lib/systemd/system:$TEMP/lib/systemd/system:$TEMP/usr/lib/systemd/user:$TEMP/lib/systemd/user readarray -t UNITS </dev/null) EOF for S in "${UNITS[@]}" ; do F="${S##*/}" case "${F##*.}" in service|socket|timer) ${UNIT_TRANSLATOR:=utrans} $BACKENDS -f overwrite "$S" "$TEMP/out" || \ die "Failed translating $F" ;; esac done readarray -t OUT </dev/null) EOF [ -z "$OUT" ] && die "Nothing translated" tar czf "$FIRST-services.tgz" -C "$TEMP/out" "${OUT[@]#$TEMP/out/}" unit-translator-0.8/utrans-rc000077500000000000000000000201601506501000400163610ustar00rootroot00000000000000#!/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 <..] [ -n | -o ] [ -u | .. ] Options:- -b : specify output backend, see utrans(1) -h: show this usage -o : 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 </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 </dev/null <&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")" <&2 PKG_HAS_SUPPORT=1 break fi set -- "$target" "$@" done <&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}' <&2 else echo "Processing $u" >&2 translate_try_install "$u" "$binsrc" fi done # Update timestamp [ -z "$TS" ] || sudo sh -c ":>$TS"