1#
2# CDDL HEADER START
3#
4# The contents of this file are subject to the terms of the
5# Common Development and Distribution License (the "License").
6# You may not use this file except in compliance with the License.
7#
8# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9# or http://www.opensolaris.org/os/licensing.
10# See the License for the specific language governing permissions
11# and limitations under the License.
12#
13# When distributing Covered Code, include this CDDL HEADER in each
14# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15# If applicable, add the following below this CDDL HEADER, with the
16# fields enclosed by brackets "[]" replaced with your own identifying
17# information: Portions Copyright [yyyy] [name of copyright owner]
18#
19# CDDL HEADER END
20#
21
22#
23# Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
24# Use is subject to license terms.
25#
26
27#
28# Copyright (c) 2013, 2016 by Delphix. All rights reserved.
29#
30
31. $STF_SUITE/include/libtest.shlib
32. $STF_SUITE/tests/functional/history/history.cfg
33
34function run_and_verify
35{
36	typeset user pool
37	while getopts "p:u:" opt; do
38		case $opt in
39		p)
40			pool=$OPTARG
41			;;
42		u)
43			user=$OPTARG
44			;;
45		esac
46	done
47	shift $(($OPTIND - 1))
48
49	pool=${pool:-$TESTPOOL}
50	user=${user:-"root"}
51	fullcmd="$1"
52	flags="$2"
53
54	histcmd=$(echo $fullcmd | sed 's/\/usr\/sbin\///g')
55	cmd=$(echo $histcmd | awk '{print $1}')
56	subcmd=$(echo $histcmd | awk '{print $2}')
57
58	# If we aren't running zpool or zfs, something is wrong
59	[[ $cmd == "zpool" || $cmd == "zfs" ]] || \
60	    log_fail "run_and_verify called with \"$cmd ($fullcmd)\""
61
62	# If this is a 'zfs receive' truncate the stdin redirect
63	[[ $subcmd == "receive" || $subcmd == "recv" ]] && \
64	    histcmd=${histcmd%% <*}
65
66	# Run the command as the specified user, and find the new history.
67	zpool history $flags $pool > $OLD_HISTORY 2>/dev/null
68	if [[ $user == "root" ]]; then
69		log_must eval "$fullcmd"
70	else
71		log_must su $user -c "eval $fullcmd"
72	fi
73	zpool history $flags $pool > $TMP_HISTORY 2>/dev/null
74	diff $OLD_HISTORY $TMP_HISTORY | grep "^> " | sed 's/^> //g' \
75	    > $NEW_HISTORY
76
77	# Verify what's common to every case, regardless of zpool history flags.
78	grep "$histcmd" $NEW_HISTORY >/dev/null 2>&1 || \
79	    log_fail "Didn't find \"$histcmd\" in pool history"
80
81	# If 'zpool history' was called without any flags, then we're done.
82	[[ -z $flags ]] && return
83
84	# Verify the new history in cases that are more interesting because
85	# additional information is logged with -i or -l.
86
87	[[ $flags =~ "i" ]] && log_must verify_$subcmd "$histcmd" "$subcmd" \
88	    "$flags"
89	[[ $flags =~ "l" ]] && log_must verify_long "$histcmd" "$user" "$flags"
90}
91
92function verify_long
93{
94	typeset cmd=$1
95	typeset user=$2
96	typeset flags=$3
97
98	[[ $flags =~ "l" ]] || return 1
99
100	typeset uid=$(id -u $user)
101	typeset hname=$(hostname)
102	if ! is_global_zone; then
103		hname=$hname:$(zonename)
104	fi
105
106	grep "$cmd \[user $uid ($user) on $hname\]" $NEW_HISTORY >/dev/null \
107	    2>&1
108	if [[ $? != 0 ]]; then
109		log_note "Couldn't find long information for \"$cmd\""
110		return 1
111	fi
112
113	return 0
114}
115
116function verify_hold
117{
118	typeset cmd=$1
119	typeset subcmd=$2
120	typeset flags=$3
121
122	[[ $flags =~ "i" ]] || return 1
123
124	typeset tag=$(echo $cmd | awk '{print $4}')
125	typeset fullname=${cmd##* }
126	typeset dsname=${fullname%%@*}
127	typeset snapname=${fullname##*@}
128
129	# This works whether or not the hold was recursive
130	for ds in $(zfs list -r -Ho name -t snapshot $dsname | \
131	    grep "@$snapname"); do
132		grep "$subcmd $ds ([0-9]*) tag=$tag" $NEW_HISTORY \
133		    >/dev/null 2>&1
134		if [[ $? != 0 ]]; then
135			log_note "Didn't find hold on $ds with $tag"
136			return 1
137		fi
138	done
139
140	return 0
141}
142
143function verify_release
144{
145	# hold and release formats only differ by the subcommand name, so
146	# simply reuse the hold function.
147	verify_hold "$1" "release" "$3"
148}
149
150function verify_rollback
151{
152	typeset cmd=$1
153	typeset flags=$3
154
155	[[ $flags =~ "i" ]] || return 1
156
157	typeset fullname=${cmd##* }
158	typeset dsname=${fullname%%@*}
159	typeset parent_fs=${dsname##*/}
160	typeset rb_fs=${dsname}/%rollback
161	typeset snapname=${fullname##*@}
162
163	grep "clone swap $rb_fs ([0-9]*) parent=$parent_fs" $NEW_HISTORY \
164	    >/dev/null 2>&1
165	if [[ $? != 0 ]]; then
166		log_note "Didn't find rollback clone swap in pool history"
167		return 1
168	fi
169
170	grep "destroy $rb_fs" $NEW_HISTORY >/dev/null 2>&1
171	if [[ $? != 0 ]]; then
172		log_note "Didn't find rollback destroy in pool history"
173		return 1
174	fi
175
176	return 0
177}
178
179function verify_inherit
180{
181	typeset cmd=$1
182	typeset flags=$3
183
184	[[ $flags =~ "i" ]] || return 1
185
186	typeset dsname=${cmd##* }
187	typeset prop=${cmd% *}
188	prop=${prop##* }
189
190	# This works whether or not the inherit was recursive
191	for ds in $(zfs list -r -Ho name -t filesystem $dsname); do
192		grep "$subcmd $ds ([0-9]*) ${prop}=" $NEW_HISTORY >/dev/null \
193		    2>&1
194		if [[ $? != 0 ]]; then
195			log_note "Didn't find inherit history for $ds"
196			return 1
197		fi
198	done
199
200	return 0
201}
202
203function verify_allow
204{
205	typeset cmd=$1
206	typeset subcmd=$2
207	typeset flags=$3
208
209	[[ $flags =~ "i" ]] || return 1
210	[[ $subcmd == "allow" ]] && subcmd="update"
211	[[ $subcmd == "unallow" ]] && subcmd="remove"
212	typeset is_set lflag dflag dsname gname gid uname uid opt str code tmp
213
214	#
215	# Here, we determine three things:
216	# - Whether we're operating on a set or an indivdual permission (which
217	#   dictates the case of the first character in the code)
218	# - The name of the dataset we're operating on.
219	# - Whether the operation applies locally or to descendent datasets (or
220	#   both)
221	#
222	echo $cmd  | awk '{i = NF - 1; print $i}' | grep '@' >/dev/null \
223	    2>&1 && is_set=1
224	dsname=${cmd##* }
225	[[ $cmd =~ "-l " ]] && lflag=1
226	[[ $cmd =~ "-d " ]] && dflag=1
227	if [[ -z $lflag && -z $dflag ]]; then
228		lflag=1
229		dflag=1
230	fi
231
232	#
233	# For each of the five cases below, the operation is essentially the
234	# same. First, use the command passed in to determine what the code at
235	# the end of the pool history will be. The specifics of the code are
236	# described in a block comment at the top of dsl_deleg.c. Once that's
237	# been assembled, check for its presence in the history, and return
238	# success or failure accordingly.
239	#
240	if [[ $cmd =~ "-s " ]]; then
241		str="s-\$@"
242		[[ -n $is_set ]] && str="S-\$@"
243		tmp=${cmd#*@}
244		code="$str${tmp% *}"
245		grep "permission $subcmd $dsname ([0-9]*) $code" \
246		    $NEW_HISTORY >/dev/null 2>&1
247		if [[ $? != 0 ]]; then
248			 log_note "Couldn't find $code in $NEW_HISTORY"
249			 return 1
250		 fi
251	elif [[ $cmd =~ "-c " ]]; then
252		str="c-\$"
253		[[ -n $is_set ]] && str="C-\$"
254		tmp=${cmd#*-c}
255		code="$str${tmp% *}"
256		grep "permission $subcmd $dsname ([0-9]*) $code" \
257		    $NEW_HISTORY >/dev/null 2>&1
258		if [ $? != 0 ]]; then
259			 log_note "Couldn't find $code in $NEW_HISTORY"
260			 return 1
261		fi
262	elif [[ $cmd =~ "-u " ]]; then
263		str="u"
264		[[ -n $is_set ]] && str="U"
265		tmp=${cmd##*-u }
266		opt=$(echo $tmp | awk '{print $2}')
267		uid=$(id -u ${tmp%% *})
268		if [[ -n $lflag ]]; then
269			code="${str}l\$$uid $opt"
270			grep "permission $subcmd $dsname ([0-9]*) $code" \
271			    $NEW_HISTORY >/dev/null 2>&1
272			if [ $? != 0 ]]; then
273				 log_note "Couldn't find $code in $NEW_HISTORY"
274				 return 1
275			fi
276		fi
277		if [[ -n $dflag ]]; then
278			code="${str}d\$$uid $opt"
279			grep "permission $subcmd $dsname ([0-9]*) $code" \
280			    $NEW_HISTORY >/dev/null 2>&1
281			if [ $? != 0 ]]; then
282				 log_note "Couldn't find $code in $NEW_HISTORY"
283				 return 1
284			fi
285		fi
286	elif [[ $cmd =~ "-g " ]]; then
287		str="g"
288		[[ -n $is_set ]] && str="G"
289		tmp=${cmd##*-g }
290		opt=$(echo $tmp | awk '{print $2}')
291		gid=$(awk -F: "/^${tmp%% *}:/ {print \$3}" /etc/group)
292		if [[ -n $lflag ]]; then
293			code="${str}l\$$gid $opt"
294			grep "permission $subcmd $dsname ([0-9]*) $code" \
295			    $NEW_HISTORY >/dev/null 2>&1
296			if [ $? != 0 ]]; then
297				 log_note "Couldn't find $code in $NEW_HISTORY"
298				 return 1
299			fi
300		fi
301		if [[ -n $dflag ]]; then
302			code="${str}d\$$gid $opt"
303			grep "permission $subcmd $dsname ([0-9]*) $code" \
304			    $NEW_HISTORY >/dev/null 2>&1
305			if [ $? != 0 ]]; then
306				 log_note "Couldn't find $code in $NEW_HISTORY"
307				 return 1
308			fi
309		fi
310	elif [[ $cmd =~ "-e " ]]; then
311		str="e"
312		[[ -n $is_set ]] && str="E"
313		opt=${cmd##*-e }
314		opt=${opt%% *}
315		if [[ -n $lflag ]]; then
316			code="${str}l\$ $opt"
317			grep "permission $subcmd $dsname ([0-9]*) $code" \
318			    $NEW_HISTORY >/dev/null 2>&1
319			if [ $? != 0 ]]; then
320				 log_note "Couldn't find $code in $NEW_HISTORY"
321				 return 1
322			fi
323		fi
324		if [[ -n $dflag ]]; then
325			code="${str}d\$ $opt"
326			grep "permission $subcmd $dsname ([0-9]*) $code" \
327			    $NEW_HISTORY >/dev/null 2>&1
328			if [ $? != 0 ]]; then
329				 log_note "Couldn't find $code in $NEW_HISTORY"
330				 return 1
331			fi
332		fi
333	else
334		log_note "Can't parse command \"$cmd\""
335		return 1
336	fi
337
338	return 0
339}
340
341function verify_unallow
342{
343	#
344	# The unallow and allow history have the same format, except the former
345	# logs "permission removed" and the latter "permission updated" so
346	# simply reuse the allow function.
347	#
348	verify_allow "$1" "unallow" "$3"
349}
350
351function verify_destroy
352{
353	typeset cmd=$1
354	typeset flags=$3
355
356	# This function doesn't currently verifiy the zpool command.
357	[[ ${cmd%% *} == "zfs" ]] || return 1
358	[[ $flags =~ "i" ]] || return 1
359
360	typeset dsname=${cmd##* }
361	[[ $dsname =~ "@" ]] && typeset is_snap=1
362
363	if [[ -n $is_snap ]]; then
364		grep "ioctl destroy_snaps" $NEW_HISTORY >/dev/null 2>&1
365		if [[ $? != 0 ]]; then
366			log_note "Didn't find ioctl while destroying $dsname"
367			return 1
368		fi
369	fi
370
371	# This should be present for datasets and snapshots alike
372	grep "destroy $dsname" $NEW_HISTORY >/dev/null 2>&1
373	if [[ $? != 0 ]]; then
374		log_note "Didn't find \"destroy\" for $dsname"
375		return 1
376	fi
377
378	return 0
379}
380
381function verify_snapshot
382{
383	typeset cmd=$1
384	typeset flags=$3
385
386	[[ $flags =~ "i" ]] || return 1
387
388	typeset fullname=${cmd##* }
389	typeset dsname=${fullname%%@*}
390	typeset snapname=${fullname##*@}
391
392	grep "\[txg:[0-9]*\] $subcmd $fullname ([0-9]*)" $NEW_HISTORY \
393	    >/dev/null 2>&1
394	if [[ $? != 0 ]]; then
395		log_note "Didn't find snapshot command for $fullname"
396		return 1
397	fi
398
399	# This works whether or not the snapshot was recursive
400	for ds in $(zfs list -r -Ho name -t snapshot $dsname | \
401	    grep "@$snapname"); do
402		grep "^[ ]* $ds$" $NEW_HISTORY >/dev/null 2>&1
403		if [[ $? != 0 ]]; then
404			log_note "Didn't find \"ioctl snapshot\" for $ds"
405			return 1
406		fi
407	done
408
409	return 0
410}
411