#!/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 <mail@kayg.org> under the guidance of Benda Xu
# <heroxbd@gentoo.org>, Adam Borowski <kilobyte@angband.pl> and Mo Zhou
# <lumin@debian.org>.
#
# This project was a part of Google Summer of Code, 2020.
#
# [https://git.devuan.org/leepen/unit-translator]
#
# Mark Hindley <mark@hindley.org.uk>
#
# LICENSE
# -------
# BSD-2-Clause License
#
# Copyright (c) 2020, K Gopal Krishna <mail@kayg.org>. All rights reserved.
# Copyright (c) 2023-, Mark Hindley <mark@hindley.org.uk>. 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 <backend>] [-h] [-v] /path/to/unit/file /path/to/destination/directory'
	printf '\n%s\n' '  Supported options:'
	printf '%s\n' '    -b <backend>: load backend(s)'
	printf '%s\n' '    -f <type>: 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 <<EOF\n\$(%s %s | %s)\nEOF\n" \
		       "sed -z 's#[[:space:]]*\\\\\n#\\\\#g'" \
		       "${file#-}" \
		       "grep '^[[:space:]]*[^#;].\\+='"
	done <<<"${service[EnvironmentFile]:-}"
	printf '%s\n' "set +a"
}

# Add dependencies, avoid duplicates and excess whitespace.
add_depends() {
	key=$1
	shift
	for add in "$@"; do
		for d in ${depends[$key]:-}; do
		    [[ "$d" == "$add" ]] && continue 2 # Already present
		done
		[[ -z "${depends[$key]:-}" ]] || depends[$key]+=' '
		depends[$key]+="$add"
	done
}

# Remove dependency from depends[key]
remove_depend() {
    	dep=$1
	key=$2
	shift 2
	# shellcheck disable=SC2086
	set -- ${depends[$key]:-} # split whitespace
	for d ; do
	    shift
	    [[ "$d" == "$dep" ]] && continue
	    set -- "$@" "$d"
	done
	depends[$key]="$*"
}

# Split constraints into trigger prefix condition
split_constraint() {
 	while read -r constraint; do
		unset trigger prefix
		case "$constraint" in
		    "") return ;;
		    !*) prefix='!' ;;
		    \|!*) trigger='|' prefix='!' ;;
		    \|*) trigger='|' ;;
		esac
		cat<<EOF
${trigger:-}
${prefix:-}
${constraint#"${trigger:-}${prefix:-}"}
EOF
	done <<<"$@"
}
#split_constraint '/test' '!/not' '|!/not trigger' '|/trigger'

# Generate POSIX sh compatible test case
gen_test_case() {
	test=$1
	pre=$2
	p=$3
	case $test in
		Architecture) echo "[ \"\$(uname -m)\" ${pre:-}= '$p' ]" ;;
		# Firmware) ;;
		Virtualization)
		    case "$p" in
			# Would anybody write !no or !yes?
			yes | true) echo "ischroot || grep -qa 'container=' /proc/1/environ" ;;
			no | false) echo "! ischroot || ! grep -qa 'container=' /proc/1/environ" ;;
			container) echo "${pre:-} ischroot || ${pre:-} grep -qa 'container=' /proc/1/environ" ;;
			lxc*) echo "${pre:-} grep -qa 'container=lxc' /proc/1/environ" ;;
		    esac
		    ;;
		Host) echo "[ \"\$(hostname)\" ${pre:-}= '$p' ]" ;;
		KernelCommandLine) echo "${pre:+$pre }grep -q '$p' /proc/cmdline" ;;
		KernelVersion) echo "[ \"\$(uname -r)\" = '$p' ]" ;;
		Environment) echo "env | ${pre:+$pre }grep -q '^$p'" ;;
		# Security) ;;
		Capability) echo "${pre:+$pre }capsh --has-p=$p" ;;
		ACPower)
		    # Both /lib/rc/bin/on_ac_power and on_ac_power(1) return
		    #  0: yes
		    #  1: no
		    #  255: unknown
		    # systemd.unit specifies
		    #  ACPower=true for yes or unknown
		    #  ACpower=false for no
		    is_true "$p" || ac_chk='!'
		    echo "on_ac_power 2>/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 "${@}"
