3 # Copyright 2013-2014 Intel Corporation
4 # Author: Artem Bityutskiy
10 srcdir="$(readlink -ev -- ${0%/*})"
12 if [ -f "$srcdir/setup-ivi-sh-functions" ]; then
13 . "$srcdir/setup-ivi-sh-functions"
14 . "$srcdir/installerfw-sh-functions"
16 . /usr/share/setup-ivi/setup-ivi-sh-functions
17 . /usr/share/setup-ivi/installerfw-sh-functions
20 # This is a small trick which I use to make sure my scripts are portable -
21 # check if 'dash' is present, and if yes - use it.
22 if can_switch_to_dash; then
23 exec dash -euf -- "$srcdir/$PROG" "$@"
27 # These are used in the cleanup handler, so have to be defined before the
33 # Unmount all partitions which we possibly mounted
36 printf "%s\n" "$unmount_list" | while IFS= read -r dir; do
37 if [ -n "$dir" ]; then
38 verbose "unmount \"$dir\""
39 umount $verbose -- "$dir" >&2 ||:
44 # This function is called on exit, crashes, interrupts, etc. We clean up
45 # everything here before the program is terminated
50 # Restore the default exit handler
53 verbose "cleanup and exit with code $exitcode"
56 rm -r $verbose -- "$tmpdir" >&2
58 if [ "$automount_status" = "active" ]; then
59 message "re-enabling udisks-automount-agent"
60 systemctl --user start udisks-automount-agent || \
61 warning "cannot start udisks-automount-agent"
67 # Append a line to a list, which is just text where each line is considered to
68 # be an element of the list. The first parameter is the line to append, the
69 # second parameter is the variable name to append to (the variable is defined
70 # outside of this function and contains the text to prepend the line to).
73 local __value="$1"; shift
74 local __list_name="$1"
75 eval local "__list=\$$__list_name"
77 [ -z "$__list" ] && eval "$__list_name=\"\$__value\"" || \
78 eval "$__list_name=\"\${__list}\${br}\${__value}\""
81 # Similar to 'list_append()', but prepends a list with a line.
84 local __value="$1"; shift
85 local __list_name="$1"
86 eval local "__list=\$$__list_name"
88 [ -z "$__list" ] && eval "$__list_name=\"\$__value\"" || \
89 eval "$__list_name=\"\${__value}\${br}\${__list}\""
92 # Create partitions on the destination disk. This function also updates the
93 # following installer framework environment variables:
94 # o INSTALLERFW_PARTx_PARTUUID
95 # o INSTALLERFW_PARTx_TYPE_ID
98 # The GPT partition type GUID for Linux partitions
99 local linuxfs_type_id="0fc63daf-8483-4772-8e79-3d69d8477de4"
101 verbose "destroying all partitions at \"$dstdisk\""
102 sgdisk -z "$dstdisk" -- > /dev/null 2>&1 ||:
103 sgdisk -Z "$dstdisk" -- > /dev/null 2>&1 ||:
105 # Create all partitions one-by-one
108 while [ "$pnum" -lt "$part_count" ]; do
109 installerfw_verify_defined "INSTALLERFW_PART${pnum}_SIZE"
111 eval local size="\$INSTALLERFW_PART${pnum}_SIZE"
112 eval local bootflag="\${INSTALLERFW_PART${pnum}_BOOTFLAG:-}"
113 eval local type_id="\${INSTALLERFW_PART${pnum}_TYPE_ID:-$linuxfs_type_id}"
115 if [ "$gdisk_pnum" -eq "$part_count" ]; then
116 # Make the last partition take the rest of the space
117 verbose "creating the last partition $pnum"
118 sgdisk -n "$gdisk_pnum:+0:-1s" -- "$dstdisk" > /dev/null
120 verbose "creating partition $pnum of size $size MiB"
121 sgdisk -n "$gdisk_pnum:+0:+${size}M" -- "$dstdisk" > /dev/null
125 local partuuid="$(uuidgen)"
126 verbose "setting partition $pnum PARTUUID to $partuuid"
127 sgdisk -u "$gdisk_pnum:$partuuid" -- "$dstdisk" > /dev/null
129 # Take care of the boot flag
130 if [ "$bootflag" = "True" ]; then
131 verbose "setting the boot flag for partition $pnum"
132 sgdisk -A "$gdisk_pnum:set:2" -- "$dstdisk" > /dev/null
135 verbose "setting partition $pnum type ID to $type_id"
136 sgdisk -t "$gdisk_pnum:$type_id" -- "$dstdisk" > /dev/null
138 # Re-define some installer framework variables
139 eval "export INSTALLERFW_PART${pnum}_PARTUUID=$partuuid"
140 eval "export INSTALLERFW_PART${pnum}_TYPE_ID=$type_id"
142 pnum="$(($pnum + 1))"
143 gdisk_pnum="$(($gdisk_pnum + 1))"
147 # Find the device node name by its major:minor numbers ($1:$2).
148 find_devnode_by_nums()
150 local major="$1"; shift
153 ls -1 /dev | while IFS= read -r node; do
154 local ma="$(printf "%d" "0x$(stat -c '%t' -- "/dev/$node")")"
155 local mi="$(printf "%d" "0x$(stat -c '%T' -- "/dev/$node")")"
157 if [ -b "/dev/$node" ] && [ "$ma" -eq "$major" ] && \
158 [ "$mi" -eq "$minor" ]; then
159 printf "%s" "/dev/$node"
165 # Find the device node name by a file name. In other words, for a given file,
166 # fine the device node of the partition this file resides on.
167 find_devnode_by_file()
170 local devnum="$(stat -c '%d' -- "$file")"
171 local major=$((($devnum / 256) % 256))
172 local minor=$(($devnum % 256))
174 find_devnode_by_nums "$major" "$minor"
177 # Find all the source and destination device nodes and declare change the
178 # following installer framework environment variables:
179 # o INSTALLERFW_PARTx_DEVNODE_NOW
180 # o INSTALLERFW_PARTx_DISK_DEVNODE_NOW
181 # And for the source partitions, the device node names are stored in
182 # "srcpartX_devnode" variables (X is the partition number).
185 # Find source device nodes
188 while [ "$pnum" -lt "$part_count" ]; do
189 installerfw_verify_defined "INSTALLERFW_PART${pnum}_MOUNTPOINT"
191 eval local mountpoint="\$INSTALLERFW_PART${pnum}_MOUNTPOINT"
192 local devnode="$(find_devnode_by_file "$mountpoint")"
194 [ -n "$devnode" ] || fatal "cannot find device node for" \
195 "source partition $mountpoint"
197 verbose "source partition $mountpoint device node is: $devnode"
199 eval "srcpart${pnum}_devnode=$devnode"
201 pnum="$(($pnum + 1))"
204 # Find destination device nodes
206 local major="$(printf "%d" "0x$(stat -c '%t' -- "$dstdisk")")"
207 local minor="$(printf "%d" "0x$(stat -c '%T' -- "$dstdisk")")"
210 while [ "$pnum" -lt "$part_count" ]; do
211 local part_minor="$(($minor + $pnum + 1))"
212 devnode="$(find_devnode_by_nums "$major" "$part_minor")"
214 [ -n "$devnode" ] || fatal "cannot find device node for" \
215 "destination partition $pnum"
217 verbose "destination partition $pnum device node is: $devnode"
219 eval "export INSTALLERFW_PART${pnum}_DEVNODE_NOW=$devnode"
220 eval "export INSTALLERFW_PART${pnum}_DISK_DEVNODE_NOW=$dstdisk"
222 pnum="$(($pnum + 1))"
227 # Format all the destination partitions and changes the
228 # "INSTALLERFW_PARTx_UUID" installer framework environment variables.
229 format_dst_partitions()
232 while [ "$pnum" -lt "$part_count" ]; do
233 installerfw_verify_defined "INSTALLERFW_PART${pnum}_FSTYPE"
234 eval local fstype="\$INSTALLERFW_PART${pnum}_FSTYPE"
235 eval local label="\${INSTALLERFW_PART${pnum}_LABEL:-}"
236 eval local devnode="\$INSTALLERFW_PART${pnum}_DEVNODE_NOW"
238 local uuid="$(uuidgen)"
240 verbose "format partition $pnum ($devnode) with mkfs.$fstype"
242 if [ "$fstype" != "vfat" ]; then
243 verbose "partition $pnum label is \"$label\", UUID is \"$uuid\""
245 [ -z "$label" ] || label="-L $label"
247 mkfs.$fstype $verbose $quiet $label -U "$uuid" -- "$devnode" >&2
249 # FATFS Volume ID is an XXXXXXXX (32-bit) string, where
250 # X is are uppercase hex digits.
251 uuid="$(printf "%s" "$uuid" | head -c8 | \
252 tr "[:lower:]" "[:upper:]")"
254 verbose "partition $pnum label is \"$label\", UUID is \"$uuid\""
256 [ -z "$label" ] || label="-n $label"
258 if [ -n "$verbose" ]; then
259 mkfs.vfat $verbose $label -i "$uuid" -- "$devnode" >&2
261 # mkfs.vfat does not accept -q
262 mkfs.vfat $label -i "$uuid" -- "$devnode" > /dev/null
265 # However, the installer framework variable has to have
266 # the XXXX-XXXX format, not XXXXXXXX. Unfortunately,
267 # mkfs.vfat does not like the "-", while 'mount'
269 uuid="$(printf "%s" "$uuid" | LC_ALL=C sed -e 's/.\{4\}/&-/')"
272 eval "export INSTALLERFW_PART${pnum}_UUID=$uuid"
278 # Mount source and destination partition
282 while [ "$pnum" -lt "$part_count" ]; do
283 eval local mountpoint="\$INSTALLERFW_PART${pnum}_MOUNTPOINT"
285 # Mount the source partition
286 eval local devnode="\$srcpart${pnum}_devnode"
287 local mntdir="$srcbase/$pnum"
289 verbose "mounting source partition $pnum" \
290 "($devnode, $mountpoint) to $mntdir"
292 mkdir -p $verbose -- "$mntdir" >&2
293 mount $verbose -- "$devnode" "$mntdir" >&2
294 list_prepend "$mntdir" "unmount_list"
296 # Mount the destination partition
297 eval devnode="\$INSTALLERFW_PART${pnum}_DEVNODE_NOW"
298 mntdir="$dstbase/$pnum"
300 verbose "mounting destination partition $pnum" \
301 "($devnode, $mountpoint) to $mntdir"
303 mkdir -p $verbose -- "$mntdir" >&2
304 mount $verbose -- "$devnode" "$mntdir" >&2
305 list_prepend "$mntdir" "unmount_list"
307 pnum="$(($pnum + 1))"
311 # Copy data from all the source partitions to destination partitions
315 while [ "$pnum" -lt "$part_count" ]; do
316 local src_mntdir="$srcbase/$pnum"
317 local dst_mntdir="$dstbase/$pnum"
319 verbose "copy partition $pnum:" \
320 "\"$src_mntdir\" -> \"$dst_mntdir\""
322 rsync -WaHXSh --exclude "${tmpdir}" "$src_mntdir/" "$dst_mntdir/"
324 pnum="$(($pnum + 1))"
328 # This function creates proper target hierarchy by mounting destination
329 # partition under the right mount points. By the time this function is called,
330 # all the destination partitions are mounted under "$dstbase/$pnum", so "/boot"
331 # is not under "/", etc. This function mounts them under "$dstbase/root"
332 create_dst_hierarchy()
334 # Create a sorted list of mount points
338 while [ "$pnum" -lt "$part_count" ]; do
339 eval mountpoint="\$INSTALLERFW_PART${pnum}_MOUNTPOINT"
340 list_append "$mountpoint" "list"
344 list="$(printf "%s" "$list" | sort)"
346 mkdir $verbose -- "$dstbase/root" >&2
351 for mountpoint in $list; do
352 installerfw_get_part_info "$mountpoint" "DEVNODE_NOW" "devnode"
353 mntdir="$dstbase/root$mountpoint"
355 verbose "mounting \"$devnode\" under \"$mntdir\""
356 mount $verbose -- "$devnode" "$mntdir" >&2
358 list_prepend "$mntdir" "unmount_list"
362 # Amend the /etc/installerfw-environment file at the destination.
363 amend_installerfw_environment()
365 # Most of the variables were already changed, let's take care about the
367 export INSTALLERFW_INSTALLER_NAME="$PROG"
368 export INSTALLERFW_MOUNT_PREFIX="$dstbase/root"
371 while [ "$pnum" -lt "$part_count" ]; do
372 eval "unset INSTALLERFW_PART${pnum}_DEVNODE"
373 eval "unset INSTALLERFW_PART${pnum}_DISK_DEVNODE"
380 # Check if the block device is mounted
381 # TODO: the much better way to do this is to use the open(2) "O_EXCL" flag
382 # which has special meaning for block devices. This would need to write a
383 # C helper program, something like setup-ivi-open-helper.
386 local devnode="$(esc_regexp "$1")"
388 grep -q -e "^$devnode[p0-9]* " /proc/mounts
394 Usage: $PROG [options] <dstdisk>
396 Clone a running Tizen IVI system to a different disk. The mandatory argument
397 "<dstdisk>" is the device node name to clone to (e.g., /dev/sda). Be careful,
398 this program will destroy all the "<dstdisk>" data!
400 The program roughly works like this.
402 1. Reads the "/etc/installerfw-environment" file to get information about the
403 partitions of the running system.
404 2. Partitions "<dstdisk>" according to "/etc/installerfw-environment" (the last
405 partition gets re-sized to consume all the space on "<dstdisk>").
406 3. Formats all the newly created partitions on "<dstdisk>".
407 4. Copies all the data from the disk Tizen IVI currently runs from to
408 "<dstdisk>", partition-by-partition.
409 5. Configures the cloned system on "<dstdisk>" by "setup-ivi-boot".
410 6. Reboots the system.
413 -v, --verbose be verbose
414 --version show the program version and exit
415 -h, --help show this text and exit
421 IFS= printf "%s\n\n" "$PROG: error: $*" >&2
427 tmp=`getopt -n $PROG -o v,h --long verbose,version,help -- "$@"` ||
428 show_usage_fail "cannot parse command-line options"
450 *) show_usage_fail "unrecognized option \"$1\""
457 show_usage_fail "please, provide exactly one \"disk\" argument"
460 [ -b "$dstdisk" ] || \
461 fatal "\"$dstdisk\" does not exist or is not a block device node"
463 is_mounted "$dstdisk" && fatal "\"$dstdisk\" is mounted"
465 # Load the installer framework data
466 if ! installerfw_available; then
467 installerfw_restore_env
470 # Install the cleanup handler (the first parameter will be our exit code)
471 trap 'cleanup_handler $?' EXIT
472 trap 'cleanup_handler 1' HUP PIPE INT QUIT TERM
474 # Create our work directory
475 tmpdir="$(mktemp --tmpdir="/tmp" -d -t -- "$PROG.XXXX")"
476 verbose "tmpdir is \"$tmpdir\""
478 srcbase="$tmpdir/src"
479 dstbase="$tmpdir/dst"
480 mkdir $verbose -- "$srcbase" >&2
481 mkdir $verbose -- "$dstbase" >&2
483 # Fetch some installer framework variables
484 installerfw_verify_defined "INSTALLERFW_PTABLE_FORMAT"
485 installerfw_verify_defined "INSTALLERFW_PART_COUNT"
487 ptable_format="$INSTALLERFW_PTABLE_FORMAT"
488 part_count="$INSTALLERFW_PART_COUNT"
490 # Some general checks
491 if [ "$ptable_format" != "gpt" ]; then
492 fatal "partition table type \"$ptable_format\" is not supported"
495 # Disable the Tizen IVI automount agent, otherwise it may start mounting the
496 # $dstdisk partitions that we are going to create
497 automount_status="$(systemctl --user is-active udisks-automount-agent 2>/dev/null ||:)"
498 if [ "$automount_status" = "active" ]; then
499 message "disabling udisks-automount-agent"
500 if ! systemctl --user stop udisks-automount-agent; then
501 warning "cannot stop udisks-automount-agent"
502 automount_status="dunno"
506 # Create partitions on the destination disk
507 message "partitioning $dstdisk"
510 # Make sure the device nodes are created
511 udevadm settle > /dev/null 2>&1 ||:
513 # Find device nodes for source and destination partitions
516 # Format the destination partitions
517 message "formatting $dstdisk"
518 format_dst_partitions
520 # Mount the source and destination partitions
524 message "copying all the data to $dstdisk"
527 # Create proper target file-system hierarchy in order to be able to execute
528 # installer framework scripts for the target.
531 message "finished copying, configuring the cloned OS"
533 # Amend the /etc/installerfw-environment file on the destination
534 amend_installerfw_environment
536 # Configure the target system
537 $srcdir/setup-ivi-boot $verbose
539 # Note, unmount happens in 'cleanup_handler()'
540 message "done, synchronizing the data"