1#!/usr/bin/env bash
2# SPDX-License-Identifier: LGPL-2.1-or-later
3
4set -eux
5set -o pipefail
6
7systemd-analyze log-level debug
8systemd-analyze log-target console
9
10unit=testsuite-38-sleep.service
11
12start_test_service() {
13    systemctl daemon-reload
14    systemctl start "${unit}"
15}
16
17dbus_freeze() {
18    local name object_path suffix
19
20    suffix="${1##*.}"
21    name="${1%.$suffix}"
22    object_path="/org/freedesktop/systemd1/unit/${name//-/_2d}_2e${suffix}"
23
24    busctl call \
25           org.freedesktop.systemd1 \
26           "${object_path}" \
27           org.freedesktop.systemd1.Unit \
28           Freeze
29}
30
31dbus_thaw() {
32    local name object_path suffix
33
34    suffix="${1##*.}"
35    name="${1%.$suffix}"
36    object_path="/org/freedesktop/systemd1/unit/${name//-/_2d}_2e${suffix}"
37
38    busctl call \
39           org.freedesktop.systemd1 \
40           "${object_path}" \
41           org.freedesktop.systemd1.Unit \
42           Thaw
43}
44
45dbus_freeze_unit() {
46    busctl call \
47           org.freedesktop.systemd1 \
48           /org/freedesktop/systemd1 \
49           org.freedesktop.systemd1.Manager \
50           FreezeUnit \
51           s \
52           "$1"
53}
54
55dbus_thaw_unit() {
56    busctl call \
57           org.freedesktop.systemd1 \
58           /org/freedesktop/systemd1 \
59           org.freedesktop.systemd1.Manager \
60           ThawUnit \
61           s \
62           "$1"
63}
64
65dbus_can_freeze() {
66    local name object_path suffix
67
68    suffix="${1##*.}"
69    name="${1%.$suffix}"
70    object_path="/org/freedesktop/systemd1/unit/${name//-/_2d}_2e${suffix}"
71
72    busctl get-property \
73           org.freedesktop.systemd1 \
74           "${object_path}" \
75           org.freedesktop.systemd1.Unit \
76           CanFreeze
77}
78
79check_freezer_state() {
80    local name object_path suffix
81
82    suffix="${1##*.}"
83    name="${1%.$suffix}"
84    object_path="/org/freedesktop/systemd1/unit/${name//-/_2d}_2e${suffix}"
85
86    for _ in {0..10}; do
87        state=$(busctl get-property \
88                       org.freedesktop.systemd1 \
89                       "${object_path}" \
90                       org.freedesktop.systemd1.Unit \
91                       FreezerState | cut -d " " -f2 | tr -d '"')
92
93        # Ignore the intermediate freezing & thawing states in case we check
94        # the unit state too quickly
95        [[ "$state" =~ ^(freezing|thawing)$ ]] || break
96        sleep .5
97    done
98
99    [ "$state" = "$2" ] || {
100        echo "error: unexpected freezer state, expected: $2, actual: $state" >&2
101        exit 1
102    }
103}
104
105check_cgroup_state() {
106    grep -q "frozen $2" /sys/fs/cgroup/system.slice/"$1"/cgroup.events
107}
108
109test_dbus_api() {
110    echo "Test that DBus API works:"
111    echo -n "  - Freeze(): "
112    dbus_freeze "${unit}"
113    check_freezer_state "${unit}" "frozen"
114    check_cgroup_state "$unit" 1
115    echo "[ OK ]"
116
117    echo -n "  - Thaw(): "
118    dbus_thaw "${unit}"
119    check_freezer_state "${unit}" "running"
120    check_cgroup_state "$unit" 0
121    echo "[ OK ]"
122
123    echo -n "  - FreezeUnit(): "
124    dbus_freeze_unit "${unit}"
125    check_freezer_state "${unit}" "frozen"
126    check_cgroup_state "$unit" 1
127    echo "[ OK ]"
128
129    echo -n "  - ThawUnit(): "
130    dbus_thaw_unit "${unit}"
131    check_freezer_state "${unit}" "running"
132    check_cgroup_state "$unit" 0
133    echo "[ OK ]"
134
135    echo -n "  - CanFreeze(): "
136    output=$(dbus_can_freeze "${unit}")
137    [ "$output" = "b true" ]
138    echo "[ OK ]"
139
140    echo
141}
142
143test_jobs() {
144    local pid_before=
145    local pid_after=
146    echo "Test that it is possible to apply jobs on frozen units:"
147
148    systemctl start "${unit}"
149    dbus_freeze "${unit}"
150    check_freezer_state "${unit}" "frozen"
151
152    echo -n "  - restart: "
153    pid_before=$(systemctl show -p MainPID "${unit}" --value)
154    systemctl restart "${unit}"
155    pid_after=$(systemctl show -p MainPID "${unit}" --value)
156    [ "$pid_before" != "$pid_after" ] && echo "[ OK ]"
157
158    dbus_freeze "${unit}"
159    check_freezer_state "${unit}" "frozen"
160
161    echo -n "  - stop: "
162    timeout 5s systemctl stop "${unit}"
163    echo "[ OK ]"
164
165    echo
166}
167
168test_systemctl() {
169    echo "Test that systemctl freeze/thaw verbs:"
170
171    systemctl start "$unit"
172
173    echo -n "  - freeze: "
174    systemctl freeze "$unit"
175    check_freezer_state "${unit}" "frozen"
176    check_cgroup_state "$unit" 1
177    # Freezing already frozen unit should be NOP and return quickly
178    timeout 3s systemctl freeze "$unit"
179    echo "[ OK ]"
180
181    echo -n "  - thaw: "
182    systemctl thaw "$unit"
183    check_freezer_state "${unit}" "running"
184    check_cgroup_state "$unit" 0
185    # Likewise thawing already running unit shouldn't block
186    timeout 3s systemctl thaw "$unit"
187    echo "[ OK ]"
188
189    systemctl stop "$unit"
190
191    echo
192}
193
194test_systemctl_show() {
195    echo "Test systemctl show integration:"
196
197    systemctl start "$unit"
198
199    echo -n "  - FreezerState property: "
200    state=$(systemctl show -p FreezerState --value "$unit")
201    [ "$state" = "running" ]
202    systemctl freeze "$unit"
203    state=$(systemctl show -p FreezerState --value "$unit")
204    [ "$state" = "frozen" ]
205    systemctl thaw "$unit"
206    echo "[ OK ]"
207
208    echo -n "  - CanFreeze property: "
209    state=$(systemctl show -p CanFreeze --value "$unit")
210    [ "$state" = "yes" ]
211    echo "[ OK ]"
212
213    systemctl stop "$unit"
214    echo
215}
216
217test_recursive() {
218    local slice="bar.slice"
219    local unit="baz.service"
220
221    systemd-run --unit "$unit" --slice "$slice" sleep 3600 >/dev/null 2>&1
222
223    echo "Test recursive freezing:"
224
225    echo -n "  - freeze: "
226    systemctl freeze "$slice"
227    check_freezer_state "${slice}" "frozen"
228    check_freezer_state "${unit}" "frozen"
229    grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/cgroup.events
230    grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events
231    echo "[ OK ]"
232
233    echo -n "  - thaw: "
234    systemctl thaw "$slice"
235    check_freezer_state "${unit}" "running"
236    check_freezer_state "${slice}" "running"
237    grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/cgroup.events
238    grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events
239    echo "[ OK ]"
240
241    systemctl stop "$unit"
242    systemctl stop "$slice"
243
244    echo
245}
246
247test_preserve_state() {
248    local slice="bar.slice"
249    local unit="baz.service"
250
251    systemd-run --unit "$unit" --slice "$slice" sleep 3600 >/dev/null 2>&1
252
253    echo "Test that freezer state is preserved when recursive freezing is initiated from outside (e.g. by manager up the tree):"
254
255    echo -n "  - freeze from outside: "
256    echo 1 >/sys/fs/cgroup/"${slice}"/cgroup.freeze
257    # Give kernel some time to freeze the slice
258    sleep 1
259
260    # Our state should not be affected
261    check_freezer_state "${slice}" "running"
262    check_freezer_state "${unit}" "running"
263
264    # However actual kernel state should be frozen
265    grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/cgroup.events
266    grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events
267    echo "[ OK ]"
268
269    echo -n "  - thaw from outside: "
270    echo 0 >/sys/fs/cgroup/"${slice}"/cgroup.freeze
271    sleep 1
272
273    check_freezer_state "${unit}" "running"
274    check_freezer_state "${slice}" "running"
275    grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/cgroup.events
276    grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events
277    echo "[ OK ]"
278
279    echo -n "  - thaw from outside while inner service is frozen: "
280    systemctl freeze "$unit"
281    check_freezer_state "${unit}" "frozen"
282    echo 1 >/sys/fs/cgroup/"${slice}"/cgroup.freeze
283    echo 0 >/sys/fs/cgroup/"${slice}"/cgroup.freeze
284    check_freezer_state "${slice}" "running"
285    check_freezer_state "${unit}" "frozen"
286    echo "[ OK ]"
287
288    systemctl stop "$unit"
289    systemctl stop "$slice"
290
291    echo
292}
293
294test -e /sys/fs/cgroup/system.slice/cgroup.freeze && {
295    start_test_service
296    test_dbus_api
297    test_systemctl
298    test_systemctl_show
299    test_jobs
300    test_recursive
301    test_preserve_state
302}
303
304echo OK >/testok
305exit 0
306