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