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