1#!/bin/bash
2# Copyright (c) International Business Machines  Corp., 2008
3# Author: Matt Helsley <matthltc@us.ibm.com>
4#
5# This library is free software; you can redistribute it and/or
6# modify it under the terms of the GNU Lesser General Public
7# License as published by the Free Software Foundation; either
8# version 2.1 of the License, or (at your option) any later version.
9#
10# This library is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13# Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public
16# License along with this library; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18#
19#
20
21# A library of cgroup test functions for testing the cgroup freezer and,
22# if present, a cgroup signal subsystem.
23#
24# Most of these assume the current directory is the cgroup of interest.
25# mount_{freezer|signal} and make_sample_cgroup are the exceptions to this rule.
26#
27# On failure, a message indicating what failed is printed and the
28#    exit status of the failing command is returned. If "result" is unset
29#    then we assign the exit status of the failed command to it.
30#
31# The variable "result" holds the exit status of the first command that failed,
32#    $UNFINISHED if no command has failed yet, or $FINISHED if no command
33#    has failed and we've finished all significant parts of the test. Note:
34#    significant parts usually do not include the cleanup of the test.
35#
36
37
38# xargs 4.1.20 only accepts -i instead of -I
39# However -I is added and -i deprecated somewhere between (4.1.20, 4.2.32]
40XRGSV=$(xargs --version | sed -e 's/^[^[:digit:]]*//')
41export XARGS_REPL_STR="%"
42case ${XRGSV} in
43[456789].[23456789][0-9]*.*|[1-9][0-9][0-9]*.*.*) # version > 4.1.*
44	export XARGS_REPL_OPT="-I${XARGS_REPL_STR}"
45	;;
464.1.*|*)
47	export XARGS_REPL_OPT="-i${XARGS_REPL_STR}"
48	;;
49esac
50unset XRGSV
51
52export UNFINISHED=""
53export FINISHED=0
54export TMP=${TMP:-/tmp}
55
56#
57# Tests are either running or cleaning up. Cleanup phases do not emit TFAIL.
58#
59export LIB_TEST_STATE="running"
60
61export max_state_samples=5 # number of times to sample
62export sample_state_period=1  # number of seconds between samplings
63export sample_sleep=5     # how long the sample program should sleep
64export result="$UNFINISHED"
65
66# These are echo'd to freezer.state.
67export FREEZE='FROZEN'
68export THAW='THAWED'
69
70# We use /bin/echo to write to cgroup files because it's exit status will not
71# hide write errors (bash's echo does not indicate if the write succeeded).
72export CG_FILE_WRITE=/bin/echo
73
74declare -r UNFINISHED FINISHED FREEZE THAW max_state_samples sample_state_period
75
76# When we're running we want to issue failure results when things go wrong.
77function running_cgroup_test()
78{
79	export LIB_TEST_STATE="TFAIL"
80}
81
82# But when we're cleaning up we want to issue warnings (if not TINFO).
83function cleanup_cgroup_test()
84{
85	export LIB_TEST_STATE="TWARN"
86}
87
88# Mounts the cgroup filesystem somewhere and pushes pwd onto the dir stack
89function mount_cgroup_subsys()
90{
91	local rc=0
92
93	mkdir -p $TMP/${cgroup_subsys}_test > /dev/null 2>&1
94	rc=$?
95
96	# Don't test status because we don't care if the directory already
97	# exists.
98
99	if [ ! -d $TMP/${cgroup_subsys}_test ]; then
100		result=${result:-$rc}
101		tst_brkm TBROK "Failed to make mount point for cgroup filesystem"
102		return $result
103	fi
104
105	mount -t cgroup -o${cgroup_subsys} test_cgroup_${cgroup_subsys} $TMP/${cgroup_subsys}_test
106	rc=$?
107	if [ $rc -ne 0 ]; then
108		result=${result:-$rc}
109		tst_resm TINFO "Failed to mount cgroup filesystem with ${cgroup_subsys} subsystem."
110		rmdir $TMP/${cgroup_subsys}_test 2> /dev/null
111		return $rc
112	fi
113
114	export mount_cgroup_freezer_saved_dir="$(pwd)"
115	cd $TMP/${cgroup_subsys}_test > /dev/null 2>&1
116	rc=$?
117	if [ $rc -ne 0 ]; then
118		result=${result:-$rc}
119		tst_brkm TBROK "Failed to change working directory into cgroup filesystem (pwd: \"$(pwd)\")"
120		umount $TMP/${cgroup_subsys}_test || umount -l $TMP/${cgroup_subsys}_test || tst_brkm TBROK "Failed to unmount cgroup filesystem with ${cgroup_subsys} subsystem"
121		rmdir $TMP/${cgroup_subsys}_test 2> /dev/null
122		return $rc
123	fi
124
125	return 0
126}
127
128function mount_freezer()
129{
130	export cgroup_subsys=freezer
131	mount_cgroup_subsys
132}
133
134function mount_signal()
135{
136	export cgroup_subsys=signal
137	mount_cgroup_subsys
138}
139
140# umounts the cgroup filesystem and pops the dir stack
141function umount_cgroup_subsys()
142{
143	cd "${mount_cgroup_freezer_saved_dir}"
144	local cwd_result=$?
145	umount $TMP/${cgroup_subsys}_test > /dev/null 2>&1 \
146	  || umount -l $TMP/${cgroup_subsys}_test || \
147	    tst_brkm TBROK "Failed to unmount cgroup filesystem (umount exit status: $?)"
148	local umnt_result=$?
149	local rc=0
150
151	if [ $cwd_result -ne 0 ]; then
152		result=${result:-$cwd_result}
153		rc=$cwd_result
154	elif [ $umnt_result -ne 0 ]; then
155		result=${result:-$umnt_result}
156		rc=$umnt_result
157	elif [ $umnt_result -eq 0 -a $cwd_result -eq 0 ]; then
158		rmdir $TMP/${cgroup_subsys}_test
159		return 0
160	fi
161
162	return $rc
163}
164
165function umount_freezer()
166{
167	[ "${cgroup_subsys}" != "freezer" ] && {
168		result=${result:-5}
169		exit -1
170	}
171	umount_cgroup_subsys
172	unset cgroup_subsys
173}
174
175function cleanup_freezer()
176{
177	local save_result="${result}"
178	local save_pwd="$(pwd)"
179
180	mount_freezer && {
181		# Run these commands in bash because we need $(cmd subst) and
182		# we need to redirect to different freezer.state files for each
183		# group
184		# Kill any leftover tasks
185		disown -a
186		find $TMP/${cgroup_subsys}_test -mindepth 1 -depth -type d -print0 | \
187		xargs -0r -n 1 ${XARGS_REPL_OPT} /bin/bash -c 'kill $(cat "'"${XARGS_REPL_STR}"'/tasks") 2> /dev/null'
188
189		# For each group in the freezer hierarch, that its tasks
190		find $TMP/${cgroup_subsys}_test -mindepth 1 -depth -type d -print0 | \
191		xargs -0r -n 1 ${XARGS_REPL_OPT} /bin/bash -c "\"${CG_FILE_WRITE}\" \"${THAW}\" > '${XARGS_REPL_STR}/freezer.state'"
192
193		# Kill any leftover tasks
194		find $TMP/${cgroup_subsys}_test -mindepth 1 -depth -type d -print0 | \
195		xargs -0r -n 1 ${XARGS_REPL_OPT} /bin/bash -c 'kill $(cat "'"${XARGS_REPL_STR}"'/tasks") 2> /dev/null'
196
197		sleep 2
198
199		# Really kill any leftover tasks
200		find $TMP/${cgroup_subsys}_test -mindepth 1 -depth -type d -print0 | \
201		xargs -0r -n 1 ${XARGS_REPL_OPT} /bin/bash -c 'kill -s SIGKILL $(cat "'"${XARGS_REPL_STR}"'/tasks") 2> /dev/null'
202
203		# Don't need to run these xargs commands in bash since we want
204		# to see what's left on stdout
205		LINES=$(find $TMP/${cgroup_subsys}_test -mindepth 1 -depth -type d -print0 | \
206			xargs -0r -n 1 ${XARGS_REPL_OPT} cat "${XARGS_REPL_STR}/tasks" | wc -l)
207		if (( LINES == 0 )); then
208			# Remove the empty groups
209			find $TMP/${cgroup_subsys}_test -mindepth 1 -depth -type d -print0 | xargs -r0 rmdir
210		else
211			tst_resm TWARN "Could not cleanup:"
212			find $TMP/${cgroup_subsys}_test -mindepth 1 -depth -type d -print0 | xargs -0r -n 1 ${XARGS_REPL_OPT} ls -ld "${XARGS_REPL_STR}/tasks"
213		fi
214
215		umount_freezer
216	}
217
218	if [ "$save_pwd" != `pwd` ]; then
219		tst_resm TWARN "libcgroup_subsys: cleanup_freezer() is broken"
220		cd "$save_pwd"
221	fi
222
223	result="${save_result}"
224}
225
226function umount_signal()
227{
228	[ "${cgroup_subsys}" != "signal" ] && {
229		result=${result:-6}
230		exit -1
231	}
232	umount_cgroup_subsys
233	unset cgroup_subsys
234}
235
236function cleanup_signal()
237{
238	local save_result="${result}"
239	local save_pwd="$(pwd)"
240
241	mount_signal && {
242		find $TMP/${cgroup_subsys}_test -mindepth 1 -depth -type d -print0 | xargs -r0 rmdir
243		umount_signal
244	}
245
246	if [ "$save_pwd" != `pwd` ]; then
247		tst_resm TWARN "libcgroup_subsys: cleanup_signal() is broken"
248		cd "$save_pwd"
249	fi
250	result="${save_result}"
251}
252
253function assert_cgroup_rwfile()
254{
255	local file="$1"
256	local descr="$2"
257	local rc=0
258
259	if [ ! -e "${file}" ]; then
260		tst_resm ${LIB_TEST_STATE} "$descr missing"
261		rc=1
262	fi
263
264	if [ ! -f "${file}" ]; then
265		tst_resm ${LIB_TEST_STATE} "$descr is not a regular file"
266		rc=2
267	fi
268
269	if [ ! -r "${file}" ]; then
270		tst_resm ${LIB_TEST_STATE} "$descr is not readable"
271		rc=3
272	fi
273
274	if [ ! -w "${file}" ]; then
275		tst_resm ${LIB_TEST_STATE} "$descr is not writeable"
276		rc=4
277	fi
278
279	[ $rc -ne 0 ] && {
280		result=${result:-$rc}
281		local s="$(stat "${file}")"
282		tst_resm ${LIB_TEST_STATE} "${s}"
283	}
284
285	return $rc
286}
287
288function assert_cgroup_tasks_rwfile()
289{
290	assert_cgroup_rwfile "tasks" "task list"
291	return $?
292}
293
294function dump_named_cgroup_tasks()
295{
296	local cgroup_name="$1"
297	local tasks
298
299	tasks=( $(cat "${cgroup_name}/tasks") ) # don't assign directly (bash bug)
300	if [ -z "${tasks[*]}" ]; then
301		return 0
302	fi
303	ps -p "${tasks[*]}" -o 'pid,ppid,pgid,tid,tpgid,blocked,ignored,pending,stat,tty,args'
304}
305
306function dump_cgroup_tasks()
307{
308	dump_named_cgroup_tasks "$(pwd)"
309}
310
311function assert_cgroup_tasks_empty()
312{
313	local nlines=$(( `cat tasks | wc -l` + 0))
314	local rc=$?
315
316	[ $rc -eq 0 -a $nlines -eq 0 ] && return 0
317	rc=$?
318	result=${result:-$rc}
319	tst_resm ${LIB_TEST_STATE} "cgroup task list is not empty: "
320	dump_cgroup_tasks 1>&2
321	return $rc
322}
323
324function assert_task_in_named_cgroup()
325{
326	local task_pid=$1
327	local cgroup_name="$2"
328
329	cat "${cgroup_name}/tasks" | grep -E "^${task_pid}\$" > /dev/null 2>&1 && return 0
330	local rc=$?
331	result=${result:-$rc}
332	tst_resm ${LIB_TEST_STATE} "Expected pid ${task_pid} is not in \"${cgroup_name}\" task list"
333	dump_named_cgroup_tasks "${cgroup_name}" 1>&2
334	return $rc
335}
336
337function assert_task_not_in_named_cgroup()
338{
339	local task_pid=$1
340	local cgroup_name="$2"
341
342	cat "${cgroup_name}/tasks" | grep -E "^${task_pid}\$" > /dev/null 2>&1 || return 0
343	local rc=1 # $? == 0 is an error in this case
344	result=${result:-$rc}
345	tst_resm ${LIB_TEST_STATE} "Expected pid ${task_pid} is in \"${cgroup_name}\" task list"
346	dump_named_cgroup_tasks "${cgroup_name}" 1>&2
347	return $rc
348}
349
350function assert_task_in_cgroup()
351{
352	assert_task_in_named_cgroup $1 "$(pwd)"
353	return $?
354}
355
356function assert_task_not_in_cgroup()
357{
358	assert_task_not_in_named_cgroup $1 "$(pwd)"
359	return $?
360}
361
362function assert_sample_proc_in_cgroup()
363{
364	assert_task_in_cgroup $sample_proc
365	return $?
366}
367
368function assert_sample_proc_not_in_cgroup()
369{
370	assert_task_not_in_cgroup $sample_proc
371	return $?
372}
373
374function assert_sample_proc_in_named_cgroup()
375{
376	assert_task_in_named_cgroup $sample_proc "$1"
377	return $?
378}
379
380function assert_sample_proc_not_in_named_cgroup()
381{
382	assert_task_not_in_named_cgroup $sample_proc "$1"
383	return $?
384}
385
386function get_task_state()
387{
388	ps -p $1 -o 'state=' 2>/dev/null
389}
390
391# TODO Check: Do these need to ignore case differences?
392function assert_task_state()
393{
394	local task_pid=$1
395	local expected_state="$2"
396	local ps_state="$(get_task_state ${task_pid})"
397	local rc=$?
398
399	[ $rc -eq 0 -a "$ps_state" == "${expected_state}" ] && return 0
400	rc=$?
401	result=${result:-$rc}
402	tst_resm ${LIB_TEST_STATE} "Expected task ${task_pid} to be in state \"${expected_state}\""
403	return $rc
404}
405
406#
407# Check that the specified task is not in the specified state
408#
409function assert_task_not_in_state()
410{
411	local task_pid=$1
412	local expected_state="$2"
413	local ps_state="$(get_task_state ${task_pid})"
414 	local rc=$?
415
416	[ $rc -eq 0 -a "$ps_state" != "${expected_state}" ] && return 0
417	rc=$?
418	result=${result:-$rc}
419	tst_resm ${LIB_TEST_STATE} "Expected task ${task_pid} to not be in state \"${expected_state}\""
420	return $rc
421}
422
423#
424# Frozen tasks are in the "D" state according to ps
425# tasks in "T" state may also be in a "frozen" state
426#
427function assert_task_not_frozen()
428{
429	local task_pid=$1
430	local ps_state="$(ps -p $task_pid -o 'state=')"
431	local rc=$?
432
433	[ $rc -eq 0 -a "$ps_state" != "D" ] && return 0
434	rc=$?
435	result=${result:-$rc}
436	tst_resm ${LIB_TEST_STATE} "Expected task ${task_pid} is not frozen (unexpected task state: \"$ps_state\")"
437	return $rc
438}
439
440function assert_task_is_frozen()
441{
442	local task_pid=$1
443	local ps_state="$(ps -p $task_pid -o 'state=')"
444	local rc=$?
445
446	[ $rc -eq 0 -a "$ps_state" == "D" -o "$ps_state" == "T" ] && return 0
447	rc=$?
448	result=${result:-$rc}
449	tst_resm ${LIB_TEST_STATE} "Expected task ${task_pid} to be frozen (unexpected task state: \"$ps_state\")"
450	return $rc
451}
452
453function assert_sample_proc_not_frozen()
454{
455	assert_task_not_frozen $sample_proc
456	return $?
457}
458
459function assert_sample_proc_is_frozen()
460{
461	assert_task_is_frozen $sample_proc
462	return $?
463}
464
465function assert_sample_proc_stopped()
466{
467	assert_task_state $sample_proc 'T'
468	return $?
469}
470
471function assert_sample_proc_not_stopped()
472{
473	assert_task_not_in_state $sample_proc 'T'
474	return $?
475}
476
477function assert_sample_proc_sleeping()
478{
479	assert_task_state $sample_proc 'S'
480	return $?
481}
482
483function assert_sample_proc_not_sleeping()
484{
485	assert_task_not_in_state $sample_proc 'S'
486	return $?
487}
488
489function assert_cgroup_subsys_state_rwfile()
490{
491	if [ "${cgroup_subsys}" == "freezer" ]; then
492		assert_cgroup_rwfile "freezer.state" "freezer state"
493		return $?
494	elif [ "${cgroup_subsys}" == "freezer" ]; then
495		assert_cgroup_rwfile "signal.kill" "signal file"
496		return $?
497	else
498		return -1
499	fi
500}
501
502function get_freezer_state()
503{
504	local state="$(cat freezer.state)"
505	local rc=$?
506
507	if [ $rc -ne 0 ]; then
508		result=${result:-$rc}
509		tst_resm ${LIB_TEST_STATE} "Failed to read freezer state."
510		return $rc
511	fi
512	echo "${state}"
513	return 0
514}
515
516function assert_cgroup_freezer_state()
517{
518	local goal_state="$1"
519	local state="$(get_freezer_state)"
520	local rc=$?
521
522	[ $rc -eq 0 -a "${state}" == "${goal_state}" ] && return 0
523	rc=$?
524	result=${result:-$rc}
525	tst_resm ${LIB_TEST_STATE} "Expected freezer state \"$2\" but found freezer state: \"$state\""
526	return $rc
527}
528
529function make_sample_cgroup_named()
530{
531	local name="$1"
532	local saved_dir="$(pwd)"
533	mkdir "${name}"
534	local rc=$?
535
536	# So long as we made the directory we don't care
537	if [ ! -d "${name}" -a $rc -ne 0 ]; then
538		# But if it doesn't exist report the exit status of mkdir
539		result=${result:-$rc}
540		return $rc
541	fi
542
543	cd "${name}" > /dev/null 2>&1
544
545	rc=$?
546	if [ $rc -ne 0 ]; then
547		result=${result:-$rc}
548		return $rc
549	fi
550
551	assert_cgroup_tasks_rwfile || {
552		cd "${saved_dir}"
553		return $?
554	}
555	assert_cgroup_tasks_empty || {
556		cd "${saved_dir}"
557		return $?
558	}
559	assert_cgroup_subsys_state_rwfile || {
560		cd "${saved_dir}"
561		return $?
562	}
563	cd "${saved_dir}"
564	return 0
565}
566
567function make_sample_cgroup()
568{
569	make_sample_cgroup_named "child"
570	local rc=$?
571
572	# So long as we made the directory we don't care
573	if [ $rc -ne 0 ]; then
574		return $rc
575	fi
576
577	cd "child" # we know this will succeed since make_sample_cgroup_named
578		   # tested this
579	return 0
580}
581
582function rm_sample_cgroup_named()
583{
584	local cgroup_name="$1"
585	local saved_dir="$(pwd)"
586	local rc=0
587
588	cd "${cgroup_name}" && {
589		assert_cgroup_tasks_rwfile || {
590			cd "${saved_dir}"
591			return $?
592		}
593		assert_cgroup_tasks_empty || {
594			cd "${saved_dir}"
595			return $?
596		}
597		assert_cgroup_subsys_state_rwfile || {
598			cd "${saved_dir}"
599			return $?
600		}
601		cd "${saved_dir}"
602	} || {
603		rc=$?
604		result=${result:-$rc}
605		return $rc
606	}
607
608	[ -d "${cgroup_name}" ] && rmdir "${cgroup_name}" && return 0
609	rc=$?
610	tst_resm TWARN "Failed to remove cgroup \"${cgroup_name}\""
611	result=${result:-$rc}
612	return $rc
613}
614
615function rm_sample_cgroup()
616{
617	local cgroup_name="$(basename $(pwd))"
618	local rc=0
619
620	cd .. || {
621		rc=$?
622		result=${result:-$rc}
623		return $rc
624	}
625	rm_sample_cgroup_named "${cgroup_name}"
626	return $?
627}
628
629function ls_pids()
630{
631	ps -e -o 'pid=' | sed -e 's/[[:space:]]\+//g'
632}
633
634function assert_task_exists()
635{
636	local task_pid=$1
637
638	ls_pids | grep -E "^${task_pid}\$" > /dev/null 2>&1 && return 0
639	local rc=$?
640
641	result=${result:-$rc}
642	tst_resm ${LIB_TEST_STATE} "Expected pid ${task_pid} does not exist"
643	return $rc
644}
645
646function assert_task_does_not_exist()
647{
648	local task_pid=$1
649
650	ls_pids | grep -E "^${task_pid}\$" > /dev/null 2>&1 || return 0
651	local rc=1 # $? == 0 is an error in this case
652
653	result=${result:-$rc}
654	tst_resm ${LIB_TEST_STATE} "Did not expect pid ${task_pid} to exist"
655	return $rc
656}
657
658function assert_sample_proc_exists()
659{
660	assert_task_exists $sample_proc
661	return $?
662}
663
664function assert_sample_proc_does_not_exist()
665{
666	assert_task_does_not_exist $sample_proc
667	return $rc
668}
669
670function start_sample_proc()
671{
672	local sample_cmd="/bin/sleep"
673	local args
674
675	args=( $sample_sleep ) # can't assign directly because of bash v2/v3 inconsistency
676	if [ $# -gt 0 ]; then
677		sample_cmd="$1"
678		shift 1
679		args=( "$@" )
680	fi
681
682	[ -n "$sample_proc" ] && assert_sample_proc_does_not_exist
683
684	"$sample_cmd" "${args[@]}" &
685	local rc=$?
686	export sample_proc=$!
687	assert_sample_proc_exists
688
689	return $rc
690}
691
692function add_sample_proc_to_named_cgroup()
693{
694	local cgroup_name="$1"
695
696	assert_sample_proc_exists
697	"${CG_FILE_WRITE}" $sample_proc > "${cgroup_name}/tasks"
698	local rc=$?
699	if [ $rc -ne 0 ]; then
700		result=${result:-$rc}
701		tst_resm ${LIB_TEST_STATE} "Failed to add sample process $sample_proc to cgroup \"${cgroup_name}\""
702		return $rc
703	fi
704	assert_task_in_named_cgroup $sample_proc "${cgroup_name}"
705	return $?
706}
707
708function add_sample_proc_to_cgroup()
709{
710	add_sample_proc_to_named_cgroup "$(pwd)"
711	return $?
712}
713
714function kill_sample_proc()
715{
716	if [ -z "$sample_proc" ]; then
717		# It's no longer running or never started.
718		# If it was supposed to have started but did not then that
719		# should be determined by checking start_sample_proc results.
720		return 0
721	fi
722
723	# Hey, bash, don't print out any of your messy job status notices
724	disown -a
725
726	if [ "$(get_task_state $sample_proc)" == "D" ]; then
727		tst_resm TWARN "sample process is frozen stiff"
728		kill $sample_proc
729		local rc=$?
730		result=${result:-$rc}
731		return $rc
732	fi
733
734	# kill child processes of the sample process
735	while pgrep -P $sample_proc ; do
736		pkill -SIGTERM -P $sample_proc
737		sleep 1
738		pkill -SIGKILL -P $sample_proc
739
740		ps -p $(pgrep -P $sample_proc) -o 'state=' | grep -v D && continue
741		# Give up if all the child processes are frozen in D state or
742		# if there aren't any more child processes
743		break
744	done
745	# DEBUG dump pstree under $sample_proc:
746	# pstree -A -p $sample_proc
747	kill $sample_proc > /dev/null 2>&1 || kill -s SIGKILL $sample_proc > /dev/null 2>&1 || {
748		local rc=$?
749
750		ps -p $sample_proc -o 'state=' > /dev/null 2>&1
751		if [ $? -eq 1 ]; then
752			# It's dead. We're OK.
753			return 0
754		fi
755		# It's still alive somehow! Give up.
756		result=${result:-$rc}
757		tst_resm TWARN "Failed to kill sample process $sample_proc (kill exit status: $rc)"
758	}
759	assert_sample_proc_not_in_cgroup
760	assert_sample_proc_does_not_exist
761	return $?
762}
763
764function issue_freeze_cmd()
765{
766	local goal_state="FROZEN"
767	local sample_state_count=1
768	local state="$(get_freezer_state)"
769	local rc=$?
770
771	if [ $rc -ne 0 ]; then
772		return $rc
773	fi
774
775	while [ "${state}" != "${goal_state}" ]; do
776		"${CG_FILE_WRITE}" "${FREEZE}" > freezer.state
777		sleep $sample_state_period
778		state="$(get_freezer_state)"
779		rc=$?
780		if [ $rc -ne 0 ]; then
781			break
782		fi
783
784		((sample_state_count++))
785		if [ "$sample_state_count" -ge "$max_state_samples" ]; then
786			break
787		fi
788	done
789
790	if [ "${state}" == "${goal_state}" ]; then
791		return 0
792	fi
793
794	result=${result:-$rc}
795	tst_resm ${LIB_TEST_STATE} "Failed to issue freeze command (freezer state: \"`get_freezer_state`\")."
796	return $rc
797}
798
799# If we're trying to "freeze" tasks with SIGTOP
800function issue_stop_as_freeze_cmd()
801{
802	local goal_state="T"
803	local sample_state_count=1
804	local ps_state="$(get_task_state ${task_pid})"
805	local rc=$?
806
807	if [ $rc -ne 0 ]; then
808		return $rc
809	fi
810
811	while [ "${ps_state}" != "${goal_state}" ]; do
812		kill -s SIGSTOP $sample_proc
813		sleep $sample_state_period
814		ps_state="$(get_task_state ${task_pid})"
815		rc=$?
816		if [ $rc -ne 0 ]; then
817			break
818		fi
819
820		((sample_state_count++))
821		if [ "$sample_state_count" -ge "$max_state_samples" ]; then
822			break
823		fi
824	done
825
826	if [ "${ps_state}" == "${goal_state}" ]; then
827		return 0
828	fi
829
830	result=${result:-$rc}
831	tst_resm ${LIB_TEST_STATE} "Failed to issue stop (freeze) command (task state: \"${ps_state}\")."
832	return $rc
833}
834
835function send_signal()
836{
837	"${CG_FILE_WRITE}" $1 > 'signal.kill' && return 0
838	local rc=$?
839	result=${result:-$rc}
840	tst_resm ${LIB_TEST_STATE} "Failed to send signal: $1 to tasks in cgroup (rc: $rc)"
841 	return $rc
842}
843
844function wait_until_goal_state_or_timeout()
845{
846	local goal_state="$1"
847	local sample_state_count=1
848	local state="$(get_freezer_state)"
849	local rc=$?
850
851	if [ $rc -ne 0 ]; then
852		return $rc
853	fi
854
855	while [ "${state}" != "${goal_state}" ]; do
856		sleep $sample_state_period
857		state="$(get_freezer_state)"
858		rc=$?
859		if [ $rc -ne 0 ]; then
860			break
861		fi
862
863		((sample_state_count++))
864		if [ "$sample_state_count" -ge "$max_state_samples" ]; then
865			break
866		fi
867	done
868	return $rc
869}
870
871# TODO convert signal scripts -- insert task between until and goal
872function wait_until_sample_proc_goal_state_or_timeout()
873{
874	local goal_state="$1"
875	local sample_state_count=1
876	local ps_state="$(get_task_state ${sample_proc})"
877	local rc=$?
878
879	while [ $rc -eq 0 -a "${ps_state}" != "${goal_state}" -a \
880		"$sample_state_count" -lt "$max_state_samples" ]; do
881		sleep $sample_state_period
882		ps_state="$(get_task_state ${sample_proc})"
883		rc=$?
884		if [ $rc -ne 0 ]; then
885			result=${result:-$rc}
886			tst_resm ${LIB_TEST_STATE} "Failed to read process state."
887			break
888		fi
889
890		((sample_state_count++))
891	done
892	return $rc
893}
894
895# TODO convert signal scripts -- insert task between until and not
896function wait_until_sample_proc_not_goal_state_or_timeout()
897{
898	local goal_state="$1"
899	local sample_state_count=1
900	local ps_state="$(get_task_state ${sample_proc})"
901	local rc=$?
902
903	while [ $rc -eq 0 -a "${ps_state}" == "${goal_state}" -a \
904		"$sample_state_count" -lt "$max_state_samples" ]; do
905		sleep $sample_state_period
906		ps_state="$(get_task_state ${sample_proc})"
907		rc=$?
908		if [ $rc -ne 0 ]; then
909			result=${result:-$rc}
910			tst_resm ${LIB_TEST_STATE} "Failed to read process state."
911			break
912		fi
913
914		((sample_state_count++))
915	done
916	return $rc
917}
918
919function wait_until_frozen()
920{
921	wait_until_goal_state_or_timeout "FROZEN" || return $?
922	assert_cgroup_freezer_state "FROZEN" "ERROR: failed to freeze cgroup"
923	# TODO assert all tasks in cgroup are in 'D' or 'T' state
924	# TODO assert that trying to add a task to cgroup results in EBUSY
925	return $?
926}
927
928function issue_thaw_cmd()
929{
930	"${CG_FILE_WRITE}" "${THAW}" > freezer.state && return 0
931	local rc=$?
932	result=${result:-$rc}
933	tst_resm ${LIB_TEST_STATE} "Failed to issue thaw command."
934	return $rc
935}
936
937function issue_cont_as_thaw_cmd()
938{
939	local goal_state="T"
940	local sample_state_count=1
941	local ps_state="$(get_task_state ${task_pid})"
942	local rc=$?
943
944	if [ $rc -ne 0 ]; then
945		return $rc
946	fi
947
948	while [ "${ps_state}" == "${goal_state}" ]; do
949		kill -s SIGCONT $sample_proc
950		sleep $sample_state_period
951		ps_state="$(get_task_state ${task_pid})"
952		rc=$?
953		if [ $rc -ne 0 ]; then
954			break
955		fi
956
957		((sample_state_count++))
958		if [ "$sample_state_count" -ge "$max_state_samples" ]; then
959			break
960		fi
961	done
962
963	if [ "${ps_state}" != "${goal_state}" ]; then
964		return 0
965	fi
966
967	result=${result:-$rc}
968	tst_resm ${LIB_TEST_STATE} "Failed to issue continue (thaw) command (task state: \"${ps_state}\")."
969	return $rc
970}
971
972function wait_until_thawed()
973{
974	wait_until_goal_state_or_timeout "THAWED" || return $?
975	assert_cgroup_freezer_state "THAWED" "ERROR: Failed to thaw cgroup."
976	return $?
977}
978
979function wait_until_freezing()
980{
981	wait_until_goal_state_or_timeout "FREEZING"
982	# Time critical -- we race with the kernel as it freezes tasks in the
983	# cgroup. So rather than assert "FREEZING" we just return
984	return $?
985}
986
987function wait_until_sample_proc_stopped()
988{
989	wait_until_sample_proc_state_or_timeout 'T' || return $?
990	assert_sample_proc_stopped
991	return $?
992}
993
994function wait_until_sample_proc_not_stopped()
995{
996	wait_until_sample_proc_not_goal_state_or_timeout 'T' || return $?
997	assert_sample_proc_not_stopped
998	return $?
999}
1000