#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail

_clear_service () {
    local SERVICE_NAME="${1:?_clear_service: missing argument}"
    systemctl stop "$SERVICE_NAME.service" 2>/dev/null || :
    rm -f  /{etc,run,usr/lib}/systemd/system/"$SERVICE_NAME".service
    rm -fr /{etc,run,usr/lib}/systemd/system/"$SERVICE_NAME".service.d
    rm -fr /{etc,run,usr/lib}/systemd/system/"$SERVICE_NAME".service.{wants,requires}
    if [[ $SERVICE_NAME == *@ ]]; then
        systemctl stop "$SERVICE_NAME"*.service 2>/dev/null || :
        rm -f  /{etc,run,usr/lib}/systemd/system/"$SERVICE_NAME"*.service
        rm -fr /{etc,run,usr/lib}/systemd/system/"$SERVICE_NAME"*.service.d
        rm -fr /{etc,run,usr/lib}/systemd/system/"$SERVICE_NAME"*.service.{wants,requires}
    fi
}

clear_services () {
    for u in "$@"; do
        _clear_service "$u"
    done
    systemctl daemon-reload
}

create_service () {
    local SERVICE_NAME="${1:?create_service: missing argument}"
    clear_services "$SERVICE_NAME"

    cat >/etc/systemd/system/"$SERVICE_NAME".service <<EOF
[Unit]
Description=$SERVICE_NAME unit

[Service]
ExecStart=sleep 100000
EOF
    mkdir -p /{etc,run,usr/lib}/systemd/system/"$SERVICE_NAME".service.{d,wants,requires}
}

create_services () {
    for u in "$@"; do
        create_service "$u"
    done
}

check_ok () {
    x="$(systemctl show --value -p "${2:?}" "${1:?}")"
    case "$x" in
        *${3:?}*) return 0 ;;
        *)        return 1 ;;
    esac
}

check_ko () {
    ! check_ok "$@"
}

test_basic_dropins () {
    echo "Testing basic dropins..."

    echo "*** test a wants b wants c"
    create_services test15-a test15-b test15-c
    ln -s ../test15-b.service /etc/systemd/system/test15-a.service.wants/
    ln -s ../test15-c.service /etc/systemd/system/test15-b.service.wants/
    check_ok test15-a Wants test15-b.service
    check_ok test15-b Wants test15-c.service

    echo "*** test a wants,requires b"
    create_services test15-a test15-b test15-c
    ln -s ../test15-b.service /etc/systemd/system/test15-a.service.wants/
    ln -s ../test15-b.service /etc/systemd/system/test15-a.service.requires/
    check_ok test15-a Wants test15-b.service
    check_ok test15-a Requires test15-b.service

    echo "*** test a wants nonexistent"
    create_service test15-a
    ln -s ../nonexistent.service /etc/systemd/system/test15-a.service.wants/
    check_ok test15-a Wants nonexistent.service
    systemctl start test15-a
    systemctl stop  test15-a

    echo "*** test a requires nonexistent"
    ln -sf ../nonexistent.service /etc/systemd/system/test15-a.service.requires/
    systemctl daemon-reload
    check_ok test15-a Requires nonexistent.service

    # 'b' is already loaded when 'c' pulls it in via a dropin.
    echo "*** test a,c require b"
    create_services test15-a test15-b test15-c
    ln -sf ../test15-b.service /etc/systemd/system/test15-a.service.requires/
    ln -sf ../test15-b.service /etc/systemd/system/test15-c.service.requires/
    systemctl start test15-a
    check_ok test15-c Requires test15-b.service
    systemctl stop test15-a test15-b

    # 'b'  is already loaded when 'c' pulls it in via an alias dropin.
    echo "*** test a wants alias"
    create_services test15-a test15-b test15-c
    ln -sf test15-c.service /etc/systemd/system/test15-c1.service
    ln -sf ../test15-c.service  /etc/systemd/system/test15-a.service.wants/
    ln -sf ../test15-c1.service /etc/systemd/system/test15-b.service.wants/
    systemctl start test15-a
    check_ok test15-a Wants test15-c.service
    check_ok test15-b Wants test15-c.service
    systemctl stop test15-a test15-c

    echo "*** test service.d/ top level drop-in"
    create_services test15-a test15-b
    check_ko test15-a ExecCondition "/bin/echo a"
    check_ko test15-b ExecCondition "/bin/echo b"
    mkdir -p /usr/lib/systemd/system/service.d
    cat >/usr/lib/systemd/system/service.d/override.conf <<EOF
[Service]
ExecCondition=/bin/echo %n
EOF
    systemctl daemon-reload
    check_ok test15-a ExecCondition "/bin/echo test15-a"
    check_ok test15-b ExecCondition "/bin/echo test15-b"
    rm -rf /usr/lib/systemd/system/service.d

    clear_services test15-a test15-b test15-c test15-c1
}

test_linked_units () {
    echo "Testing linked units..."
    echo "*** test linked unit (same basename)"

    create_service test15-a
    mv /etc/systemd/system/test15-a.service /
    ln -s /test15-a.service /etc/systemd/system/
    ln -s test15-a.service /etc/systemd/system/test15-b.service

    check_ok test15-a Names test15-a.service
    check_ok test15-a Names test15-b.service

    echo "*** test linked unit (cross basename)"

    mv /test15-a.service /test15-a@.scope
    ln -fs /test15-a@.scope /etc/systemd/system/test15-a.service
    systemctl daemon-reload

    check_ok test15-a Names test15-a.service
    check_ok test15-a Names test15-b.service
    check_ko test15-a Names test15-a@     # test15-a@.scope is the symlink target.
                                          # Make sure it is completely ignored.

    rm /test15-a@.scope
    clear_services test15-a test15-b
}

test_template_alias() {
    echo "Testing instance alias..."
    echo "*** forward"

    create_service test15-a@
    ln -s test15-a@inst.service /etc/systemd/system/test15-b@inst.service  # alias

    check_ok test15-a@inst Names test15-a@inst.service
    check_ok test15-a@inst Names test15-b@inst.service

    check_ok test15-a@other Names test15-a@other.service
    check_ko test15-a@other Names test15-b@other.service

    echo "*** reverse"

    systemctl daemon-reload

    check_ok test15-b@inst Names test15-a@inst.service
    check_ok test15-b@inst Names test15-b@inst.service

    check_ko test15-b@other Names test15-a@other.service
    check_ok test15-b@other Names test15-b@other.service

    clear_services test15-a@ test15-b@
}

test_hierarchical_dropins () {
    echo "Testing hierarchical dropins..."
    echo "*** test service.d/ top level drop-in"
    create_services a-b-c
    check_ko a-b-c ExecCondition "/bin/echo service.d"
    check_ko a-b-c ExecCondition "/bin/echo a-.service.d"
    check_ko a-b-c ExecCondition "/bin/echo a-b-.service.d"
    check_ko a-b-c ExecCondition "/bin/echo a-b-c.service.d"

    for dropin in service.d a-.service.d a-b-.service.d a-b-c.service.d; do
        mkdir -p /usr/lib/systemd/system/$dropin
        echo "
[Service]
ExecCondition=/bin/echo $dropin
        " >/usr/lib/systemd/system/$dropin/override.conf
        systemctl daemon-reload
        check_ok a-b-c ExecCondition "/bin/echo $dropin"
    done
    for dropin in service.d a-.service.d a-b-.service.d a-b-c.service.d; do
        rm -rf /usr/lib/systemd/system/$dropin
    done

    clear_services a-b-c
}

test_template_dropins () {
    echo "Testing template dropins..."

    create_services foo bar@ yup@

    # Declare some deps to check if the body was loaded
    cat >>/etc/systemd/system/bar@.service <<EOF
[Unit]
After=bar-template-after.device
EOF

    cat >>/etc/systemd/system/yup@.service <<EOF
[Unit]
After=yup-template-after.device
EOF

    ln -s /etc/systemd/system/bar@.service /etc/systemd/system/foo.service.wants/bar@1.service
    check_ok foo Wants bar@1.service

    echo "*** test bar-alias@.service→bar@.service, but instance symlinks point to yup@.service ***"
    ln -s bar@.service  /etc/systemd/system/bar-alias@.service
    ln -s bar@1.service /etc/systemd/system/bar-alias@1.service
    ln -s yup@.service  /etc/systemd/system/bar-alias@2.service
    ln -s yup@3.service /etc/systemd/system/bar-alias@3.service

    # create some dropin deps
    mkdir -p /etc/systemd/system/bar@{,0,1,2,3}.service.requires/
    mkdir -p /etc/systemd/system/yup@{,0,1,2,3}.service.requires/
    mkdir -p /etc/systemd/system/bar-alias@{,0,1,2,3}.service.requires/

    ln -s ../bar-template-requires.device /etc/systemd/system/bar@.service.requires/
    ln -s ../bar-0-requires.device /etc/systemd/system/bar@0.service.requires/
    ln -s ../bar-1-requires.device /etc/systemd/system/bar@1.service.requires/
    ln -s ../bar-2-requires.device /etc/systemd/system/bar@2.service.requires/
    ln -s ../bar-3-requires.device /etc/systemd/system/bar@3.service.requires/

    ln -s ../yup-template-requires.device /etc/systemd/system/yup@.service.requires/
    ln -s ../yup-0-requires.device /etc/systemd/system/yup@0.service.requires/
    ln -s ../yup-1-requires.device /etc/systemd/system/yup@1.service.requires/
    ln -s ../yup-2-requires.device /etc/systemd/system/yup@2.service.requires/
    ln -s ../yup-3-requires.device /etc/systemd/system/yup@3.service.requires/

    ln -s ../bar-alias-template-requires.device /etc/systemd/system/bar-alias@.service.requires/
    ln -s ../bar-alias-0-requires.device /etc/systemd/system/bar-alias@0.service.requires/
    ln -s ../bar-alias-1-requires.device /etc/systemd/system/bar-alias@1.service.requires/
    ln -s ../bar-alias-2-requires.device /etc/systemd/system/bar-alias@2.service.requires/
    ln -s ../bar-alias-3-requires.device /etc/systemd/system/bar-alias@3.service.requires/

    systemctl daemon-reload

    echo '*** bar@0 is aliased by bar-alias@0 ***'
    systemctl show -p Names,Requires bar@0
    systemctl show -p Names,Requires bar-alias@0
    check_ok bar@0 Names bar@0
    check_ok bar@0 Names bar-alias@0

    check_ok bar@0 After bar-template-after.device

    check_ok bar@0 Requires bar-0-requires.device
    check_ok bar@0 Requires bar-alias-0-requires.device
    check_ok bar@0 Requires bar-template-requires.device
    check_ok bar@0 Requires bar-alias-template-requires.device
    check_ko bar@0 Requires yup-template-requires.device

    check_ok bar-alias@0 After bar-template-after.device

    check_ok bar-alias@0 Requires bar-0-requires.device
    check_ok bar-alias@0 Requires bar-alias-0-requires.device
    check_ok bar-alias@0 Requires bar-template-requires.device
    check_ok bar-alias@0 Requires bar-alias-template-requires.device
    check_ko bar-alias@0 Requires yup-template-requires.device
    check_ko bar-alias@0 Requires yup-0-requires.device

    echo '*** bar@1 is aliased by bar-alias@1 ***'
    systemctl show -p Names,Requires bar@1
    systemctl show -p Names,Requires bar-alias@1
    check_ok bar@1 Names bar@1
    check_ok bar@1 Names bar-alias@1

    check_ok bar@1 After bar-template-after.device

    check_ok bar@1 Requires bar-1-requires.device
    check_ok bar@1 Requires bar-alias-1-requires.device
    check_ok bar@1 Requires bar-template-requires.device
    # See https://github.com/systemd/systemd/pull/13119#discussion_r308145418
    check_ok bar@1 Requires bar-alias-template-requires.device
    check_ko bar@1 Requires yup-template-requires.device
    check_ko bar@1 Requires yup-1-requires.device

    check_ok bar-alias@1 After bar-template-after.device

    check_ok bar-alias@1 Requires bar-1-requires.device
    check_ok bar-alias@1 Requires bar-alias-1-requires.device
    check_ok bar-alias@1 Requires bar-template-requires.device
    check_ok bar-alias@1 Requires bar-alias-template-requires.device
    check_ko bar-alias@1 Requires yup-template-requires.device
    check_ko bar-alias@1 Requires yup-1-requires.device

    echo '*** bar-alias@2 aliases yup@2, bar@2 is independent ***'
    systemctl show -p Names,Requires bar@2
    systemctl show -p Names,Requires bar-alias@2
    check_ok bar@2 Names bar@2
    check_ko bar@2 Names bar-alias@2

    check_ok bar@2 After bar-template-after.device

    check_ok bar@2 Requires bar-2-requires.device
    check_ko bar@2 Requires bar-alias-2-requires.device
    check_ok bar@2 Requires bar-template-requires.device
    check_ko bar@2 Requires bar-alias-template-requires.device
    check_ko bar@2 Requires yup-template-requires.device
    check_ko bar@2 Requires yup-2-requires.device

    check_ko bar-alias@2 After bar-template-after.device

    check_ko bar-alias@2 Requires bar-2-requires.device
    check_ok bar-alias@2 Requires bar-alias-2-requires.device
    check_ko bar-alias@2 Requires bar-template-requires.device
    check_ok bar-alias@2 Requires bar-alias-template-requires.device
    check_ok bar-alias@2 Requires yup-template-requires.device
    check_ok bar-alias@2 Requires yup-2-requires.device

    echo '*** bar-alias@3 aliases yup@3, bar@3 is independent ***'
    systemctl show -p Names,Requires bar@3
    systemctl show -p Names,Requires bar-alias@3
    check_ok bar@3 Names bar@3
    check_ko bar@3 Names bar-alias@3

    check_ok bar@3 After bar-template-after.device

    check_ok bar@3 Requires bar-3-requires.device
    check_ko bar@3 Requires bar-alias-3-requires.device
    check_ok bar@3 Requires bar-template-requires.device
    check_ko bar@3 Requires bar-alias-template-requires.device
    check_ko bar@3 Requires yup-template-requires.device
    check_ko bar@3 Requires yup-3-requires.device

    check_ko bar-alias@3 After bar-template-after.device

    check_ko bar-alias@3 Requires bar-3-requires.device
    check_ok bar-alias@3 Requires bar-alias-3-requires.device
    check_ko bar-alias@3 Requires bar-template-requires.device
    check_ok bar-alias@3 Requires bar-alias-template-requires.device
    check_ok bar-alias@3 Requires yup-template-requires.device
    check_ok bar-alias@3 Requires yup-3-requires.device

    clear_services foo {bar,yup,bar-alias}@{,1,2,3}
}

test_alias_dropins () {
    echo "Testing alias dropins..."

    echo "*** test a wants b1 alias of b"
    create_services test15-a test15-b
    ln -sf test15-b.service /etc/systemd/system/test15-b1.service
    ln -sf ../test15-b1.service /etc/systemd/system/test15-a.service.wants/
    check_ok test15-a Wants test15-b.service
    systemctl start test15-a
    systemctl --quiet is-active test15-b
    systemctl stop test15-a test15-b
    rm /etc/systemd/system/test15-b1.service
    clear_services test15-a test15-b

    # Check that dependencies don't vary.
    echo "*** test 2"
    create_services test15-a test15-x test15-y
    mkdir -p /etc/systemd/system/test15-a1.service.wants/
    ln -sf test15-a.service /etc/systemd/system/test15-a1.service
    ln -sf ../test15-x.service /etc/systemd/system/test15-a.service.wants/
    ln -sf ../test15-y.service /etc/systemd/system/test15-a1.service.wants/
    check_ok test15-a1 Wants test15-x.service # see [1]
    check_ok test15-a1 Wants test15-y.service
    systemctl start test15-a
    check_ok test15-a1 Wants test15-x.service # see [2]
    check_ok test15-a1 Wants test15-y.service
    systemctl stop test15-a test15-x test15-y
    rm /etc/systemd/system/test15-a1.service

    clear_services test15-a test15-x test15-y
}

test_masked_dropins () {
    echo "Testing masked dropins..."

    create_services test15-a test15-b

    # 'b' is masked for both deps
    echo "*** test a wants,requires b is masked"
    ln -sf /dev/null /etc/systemd/system/test15-a.service.wants/test15-b.service
    ln -sf /dev/null /etc/systemd/system/test15-a.service.requires/test15-b.service
    check_ko test15-a Wants test15-b.service
    check_ko test15-a Requires test15-b.service

    # 'a' wants 'b' and 'b' is masked at a lower level
    echo "*** test a wants b, mask override"
    ln -sf ../test15-b.service /etc/systemd/system/test15-a.service.wants/test15-b.service
    ln -sf /dev/null /usr/lib/systemd/system/test15-a.service.wants/test15-b.service
    check_ok test15-a Wants test15-b.service

    # 'a' wants 'b' and 'b' is masked at a higher level
    echo "*** test a wants b, mask"
    ln -sf /dev/null /etc/systemd/system/test15-a.service.wants/test15-b.service
    ln -sf ../test15-b.service /usr/lib/systemd/system/test15-a.service.wants/test15-b.service
    check_ko test15-a Wants test15-b.service

    # 'a' is masked but has an override config file
    echo "*** test a is masked but has an override"
    create_services test15-a test15-b
    ln -sf /dev/null /etc/systemd/system/test15-a.service
    cat >/usr/lib/systemd/system/test15-a.service.d/override.conf <<EOF
[Unit]
After=test15-b.service
EOF
    check_ok test15-a UnitFileState masked

    # 'b1' is an alias for 'b': masking 'b' dep should not influence 'b1' dep
    echo "*** test a wants b, b1, and one is masked"
    create_services test15-a test15-b
    ln -sf test15-b.service /etc/systemd/system/test15-b1.service
    ln -sf /dev/null /etc/systemd/system/test15-a.service.wants/test15-b.service
    ln -sf ../test15-b1.service /usr/lib/systemd/system/test15-a.service.wants/test15-b1.service
    systemctl cat test15-a
    systemctl show -p Wants,Requires test15-a
    systemctl cat test15-b1
    systemctl show -p Wants,Requires test15-b1
    check_ok test15-a Wants test15-b.service
    check_ko test15-a Wants test15-b1.service # the alias does not show up in the list of units
    rm /etc/systemd/system/test15-b1.service

    # 'b1' is an alias for 'b': masking 'b1' should not influence 'b' dep
    echo "*** test a wants b, alias dep is masked"
    create_services test15-a test15-b
    ln -sf test15-b.service /etc/systemd/system/test15-b1.service
    ln -sf /dev/null /etc/systemd/system/test15-a.service.wants/test15-b1.service
    ln -sf ../test15-b.service /usr/lib/systemd/system/test15-a.service.wants/test15-b.service
    check_ok test15-a Wants test15-b.service
    check_ko test15-a Wants test15-b1.service # the alias does not show up in the list of units
    rm /etc/systemd/system/test15-b1.service

    # 'a' has Wants=b.service but also has a masking
    # dropin 'b': 'b' should still be pulled in.
    echo "*** test a wants b both ways"
    create_services test15-a test15-b
    ln -sf /dev/null /etc/systemd/system/test15-a.service.wants/test15-b.service
    cat >/usr/lib/systemd/system/test15-a.service.d/wants-b.conf<<EOF
[Unit]
Wants=test15-b.service
EOF
    check_ok test15-a Wants test15-b.service

    # mask a dropin that points to an nonexistent unit.
    echo "*** test a wants nonexistent is masked"
    create_services test15-a
    ln -sf /dev/null /etc/systemd/system/test15-a.service.requires/nonexistent.service
    ln -sf ../nonexistent.service /usr/lib/systemd/system/test15-a.service.requires/
    check_ko test15-a Requires nonexistent.service

    # 'b' is already loaded when 'c' pulls it in via a dropin but 'b' is
    # masked at a higher level.
    echo "*** test a wants b is masked"
    create_services test15-a test15-b test15-c
    ln -sf ../test15-b.service /etc/systemd/system/test15-a.service.requires/
    ln -sf ../test15-b.service /run/systemd/system/test15-c.service.requires/
    ln -sf /dev/null /etc/systemd/system/test15-c.service.requires/test15-b.service
    systemctl start test15-a
    check_ko test15-c Requires test15-b.service
    systemctl stop test15-a test15-b

    # 'b' is already loaded when 'c' pulls it in via a dropin but 'b' is
    # masked at a lower level.
    echo "*** test a requires b is masked"
    create_services test15-a test15-b test15-c
    ln -sf ../test15-b.service /etc/systemd/system/test15-a.service.requires/
    ln -sf ../test15-b.service /etc/systemd/system/test15-c.service.requires/
    ln -sf /dev/null /run/systemd/system/test15-c.service.requires/test15-b.service
    systemctl start test15-a
    check_ok test15-c Requires test15-b.service
    systemctl stop test15-a test15-b

    # 'a' requires 2 aliases of 'b' and one of them is a mask.
    echo "*** test a requires alias of b, other alias masked"
    create_services test15-a test15-b
    ln -sf test15-b.service /etc/systemd/system/test15-b1.service
    ln -sf test15-b.service /etc/systemd/system/test15-b2.service
    ln -sf /dev/null /etc/systemd/system/test15-a.service.requires/test15-b1.service
    ln -sf ../test15-b1.service /run/systemd/system/test15-a.service.requires/
    ln -sf ../test15-b2.service /usr/lib/systemd/system/test15-a.service.requires/
    check_ok test15-a Requires test15-b

    # Same as above but now 'b' is masked.
    echo "*** test a requires alias of b, b dep masked"
    create_services test15-a test15-b
    ln -sf test15-b.service /etc/systemd/system/test15-b1.service
    ln -sf test15-b.service /etc/systemd/system/test15-b2.service
    ln -sf ../test15-b1.service /run/systemd/system/test15-a.service.requires/
    ln -sf ../test15-b2.service /usr/lib/systemd/system/test15-a.service.requires/
    ln -sf /dev/null /etc/systemd/system/test15-a.service.requires/test15-b.service
    check_ok test15-a Requires test15-b

    clear_services test15-a test15-b
}

test_invalid_dropins () {
    echo "Testing invalid dropins..."
    # Assertion failed on earlier versions, command exits unsuccessfully on later versions
    systemctl cat nonexistent@.service || true
    create_services a
    systemctl daemon-reload
    # Assertion failed on earlier versions, command exits unsuccessfully on later versions
    systemctl cat a@.service || true
    systemctl stop a
    clear_services a
    return 0
}

test_symlink_dropin_directory () {
    # For issue #21920.
    echo "Testing symlink drop-in directory..."
    create_services test15-a
    rmdir /{etc,run,usr/lib}/systemd/system/test15-a.service.d
    mkdir -p /tmp/testsuite-15-test15-a-dropin-directory
    ln -s /tmp/testsuite-15-test15-a-dropin-directory /etc/systemd/system/test15-a.service.d
    cat >/tmp/testsuite-15-test15-a-dropin-directory/override.conf <<EOF
[Unit]
Description=hogehoge
EOF
    ln -s /tmp/testsuite-15-test15-a-dropin-directory-nonexistent /run/systemd/system/test15-a.service.d
    touch /tmp/testsuite-15-test15-a-dropin-directory-regular
    ln -s /tmp/testsuite-15-test15-a-dropin-directory-regular /usr/lib/systemd/system/test15-a.service.d
    check_ok test15-a Description hogehoge

    clear_services test15-a
}

test_basic_dropins
test_linked_units
test_template_alias
test_hierarchical_dropins
test_template_dropins
test_alias_dropins
test_masked_dropins
test_invalid_dropins
test_symlink_dropin_directory

touch /testok
