1 #!/bin/ksh -p
2 #
3 # CDDL HEADER START
4 #
5 # The contents of this file are subject to the terms of the
6 # Common Development and Distribution License (the "License").
7 # You may not use this file except in compliance with the License.
8 #
9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 # or http://www.opensolaris.org/os/licensing.
11 # See the License for the specific language governing permissions
12 # and limitations under the License.
13 #
14 # When distributing Covered Code, include this CDDL HEADER in each
15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 # If applicable, add the following below this CDDL HEADER, with the
17 # fields enclosed by brackets "[]" replaced with your own identifying
18 # information: Portions Copyright [yyyy] [name of copyright owner]
19 #
20 # CDDL HEADER END
21 #
22 #
23 # Copyright 2009 Sun Microsystems, Inc. All rights reserved.
24 # Use is subject to license terms.
25 #
26 # Copyright (C) 2013 by Jim Klimov - implemented the previously absent
27 # cloning of zones from specified snapshot, and avoidance of sys-unconfig
28 #
29 # Copyright 2018 Nexenta Systems, Inc. All rights reserved.
30
31 . /usr/lib/brand/ipkg/common.ksh
32
33 m_usage=$(gettext "clone {sourcezone}")
34 f_nosource=$(gettext "Error: unable to determine source zone dataset.")
35 f_badsource=$(gettext "Error: specified snapshot is invalid for this source zone.")
36 f_baddestpool=$(gettext "Error: Can not clone, source and target pools differ.")
37
38 ZFS=/usr/sbin/zfs
39 ZONEADM=/usr/sbin/zoneadm
40
41 # Clean up on failure
42 trap_exit()
43 {
44 if (( $ZONE_IS_MOUNTED != 0 )); then
45 error "$v_unmount"
46 $ZONEADM -z $ZONENAME unmount
47 fi
48
49 exit $ZONE_SUBPROC_INCOMPLETE
50 }
51
52 # Set up ZFS dataset hierarchy for the zone.
53
54 ROOT="rpool/ROOT"
55
56 # Use clone or copy method to dupilcate zone datasets.
57 do_copy=false
58
59 # Other brand clone options are invalid for this brand.
60 while getopts "m:R:s:Xz:" opt; do
61 case $opt in
62 m) case "$OPTARG" in
63 "copy")
64 ZONEPATH=`$ZONEADM -z $2 list -p | \
65 awk -F: '{print $4}'`
66 do_copy=true
67 ;;
68 *) fail_usage "";;
69 esac
70 ;;
71 R) ZONEPATH="$OPTARG" ;;
72 s) case "$OPTARG" in
73 *@*) # Full snapshot name was provided, or just "@snap"
74 # Split this into dataset name (even if empty) and
75 # snapshot name (also may be empty)
76 SNAPNAME="`echo "$OPTARG" | sed 's/^[^@]*@//'`"
77 REQUESTED_DS="`echo "$OPTARG" | sed 's/\([^@]*\)@.*$/\1/'`"
78 ;;
79 */*) # Only dataset name was passed, so we will make a
80 # snapshot there automatically and clone off it
81 SNAPNAME=""
82 REQUESTED_DS="$OPTARG"
83 ;;
84 *) # Only snapshot name was passed, so we will clone
85 # the source zone's active ZBE and this snapshot
86 SNAPNAME="$OPTARG"
87 REQUESTED_DS=""
88 ;;
89 esac
90 ;;
91 X) NO_SYSUNCONFIG=yes ;;
92 z) ZONENAME="$OPTARG" ;;
93 *) fail_usage "";;
94 esac
95 done
96 shift $((OPTIND-1))
97
98 if [ $# -ne 1 ]; then
99 fail_usage "";
100 fi
101
102 sourcezone="$1"
103 get_current_gzbe
104
105 if [ -z "$REQUESTED_DS" ]; then
106 # Find the active source zone dataset to clone.
107 sourcezonepath=`$ZONEADM -z $sourcezone list -p | awk -F: '{print $4}'`
108 if [ -z "$sourcezonepath" ]; then
109 fail_fatal "$f_nosource"
110 fi
111
112 get_zonepath_ds $sourcezonepath
113 get_active_ds $CURRENT_GZBE $ZONEPATH_DS
114
115 spdir=`/usr/bin/dirname $sourcezonepath`
116 get_zonepath_ds $spdir
117 spdir_ds=$ZONEPATH_DS
118 else
119 # Sanity-check the provided dataset (should exist and be an IPS ZBE)
120 REQUESTED_DS="`echo "$REQUESTED_DS" | egrep '^.*/'"$sourcezone"'/ROOT/[^/]+$'`"
121 if [ $? != 0 -o x"$REQUESTED_DS" = x ]; then
122 fail_fatal "$f_badsource"
123 fi
124 $ZFS list -H -o \
125 org.opensolaris.libbe:parentbe,org.opensolaris.libbe:active \
126 "$REQUESTED_DS" > /dev/null || \
127 fail_fatal "$f_badsource"
128 ACTIVE_DS="$REQUESTED_DS"
129 fi
130
131 # Another sanity-check: requested snapshot exists for default or requested ZBE
132 if [ x"$SNAPNAME" != x ]; then
133 $ZFS list -H "$ACTIVE_DS@$SNAPNAME" > /dev/null || \
134 fail_fatal "$f_badsource"
135 fi
136
137 #
138 # Now set up the zone's datasets
139 #
140
141 #
142 # First make the top-level dataset.
143 #
144
145 pdir=`/usr/bin/dirname $ZONEPATH`
146 zpname=`/usr/bin/basename $ZONEPATH`
147
148 get_zonepath_ds $pdir
149 zpds=$ZONEPATH_DS
150
151 fail_zonepath_in_rootds $zpds
152
153 #
154 # Make sure zone is cloned within the same zpool
155 #
156 if [[ $do_copy != true ]]; then
157 case $zpds in
158 $spdir_ds)
159 break
160 ;;
161 *)
162 fail_fatal "$f_baddestpool"
163 break
164 ;;
165 esac
166 fi
167
168 #
169 # We need to tolerate errors while creating the datasets and making the
170 # mountpoint, since these could already exist from some other BE.
171 #
172
173 $ZFS create $zpds/$zpname
174
175 $ZFS create -o mountpoint=legacy -o zoned=on $zpds/$zpname/ROOT
176
177 if [ x"$SNAPNAME" = x ]; then
178 # make snapshot
179 SNAPNAME=${ZONENAME}_snap
180 SNAPNUM=0
181 while [ $SNAPNUM -lt 100 ]; do
182 $ZFS snapshot $ACTIVE_DS@$SNAPNAME
183 if [ $? = 0 ]; then
184 break
185 fi
186 SNAPNUM=`expr $SNAPNUM + 1`
187 SNAPNAME="${ZONENAME}_snap$SNAPNUM"
188 done
189
190 # NOTE: This artificially limits us to 100 clones of a "golden" zone
191 # into a same-named (test?) zone, unless clones are based on some
192 # same snapshot via command-line
193 if [ $SNAPNUM -ge 100 ]; then
194 fail_fatal "$f_zfs_create"
195 fi
196 fi
197
198 LOGFILE=$(/usr/bin/mktemp -t -p /var/tmp $ZONENAME.clone_log.XXXXXX)
199 if [[ -z "$LOGFILE" ]]; then
200 fatal "$e_tmpfile"
201 fi
202 exec 2>>"$LOGFILE"
203
204 # do clone
205 #
206 # If there is already an existing zone BE for this zone it's likely it belongs
207 # to another global zone BE. If that is the case the name of the zone BE
208 # dataset is ajusted to avoid name collisions.
209 #
210 # If do_copy is set (the -m copy option was used) zfs send/recv is used so
211 # the zone can be cloned across pools.
212 #
213 BENAME=zbe
214 BENUM=0
215 while [ $BENUM -lt 100 ]; do
216 if $do_copy; then
217 log "Copy source zoneroot to new zoneroot"
218 $ZFS send $ACTIVE_DS@$SNAPNAME | \
219 $ZFS recv $zpds/$zpname/ROOT/$BENAME
220 if [ $? = 0 ]; then
221 $ZFS destroy $ACTIVE_DS@$SNAPNAME
222 break
223 fi
224 else
225 log "Clone zone root dataset"
226 $ZFS clone $ACTIVE_DS@$SNAPNAME $zpds/$zpname/ROOT/$BENAME
227 if [ $? = 0 ]; then
228 break
229 fi
230 fi
231 BENUM=`expr $BENUM + 1`
232 BENAME="zbe-$BENUM"
233 done
234
235 if [ $BENUM -ge 100 ]; then
236 fail_fatal "$f_zfs_create"
237 fi
238
239 $ZFS set $PROP_ACTIVE=on $zpds/$zpname/ROOT/$BENAME || \
240 fail_incomplete "$f_zfs_create"
241
242 $ZFS set $PROP_PARENT=$CURRENT_GZBE $zpds/$zpname/ROOT/$BENAME || \
243 fail_incomplete "$f_zfs_create"
244
245 $ZFS set canmount=noauto $zpds/$zpname/ROOT/$BENAME || \
246 fail_incomplete "$f_zfs_create"
247
248 if [ ! -d $ZONEPATH/root ]; then
249 /usr/bin/mkdir -p $ZONEPATH/root
250 /usr/bin/chmod 700 $ZONEPATH
251 fi
252
253 ZONE_IS_MOUNTED=0
254 trap trap_exit EXIT
255
256 #
257 # Completion of unconfigure_zone will leave the zone root mounted for
258 # ipkg brand zones. The root won't be mounted for labeled brand zones.
259 #
260 is_brand_labeled
261 (( $? == 0 )) && if [ x"$NO_SYSUNCONFIG" = xyes ]; then
262 vlog "$v_mounting"
263 ZONE_IS_MOUNTED=1
264 $ZONEADM -z $ZONENAME mount -f || fatal "$e_badmount"
265 else
266 unconfigure_zone
267 fi
268
269 trap - EXIT
270 exit $ZONE_SUBPROC_OK