installerfw-sh-functions: add missing newlines
[platform/adaptation/setup-scripts.git] / setup-ivi-clone
1 #!/bin/sh -euf
2
3 # Copyright 2013-2014 Intel Corporation
4 # Author: Artem Bityutskiy
5 # License: GPLv2
6
7 PROG="setup-ivi-clone"
8 VER="1.0"
9
10 srcdir="$(readlink -ev -- ${0%/*})"
11
12 if [ -f "$srcdir/setup-ivi-sh-functions" ]; then
13         . "$srcdir/setup-ivi-sh-functions"
14         . "$srcdir/installerfw-sh-functions"
15 else
16         .  /usr/share/setup-ivi/setup-ivi-sh-functions
17         .  /usr/share/setup-ivi/installerfw-sh-functions
18 fi
19
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" "$@"
24         exit $?
25 fi
26
27 # These are used in the cleanup handler, so have to be defined before the
28 # function
29 tmpdir=
30 unmount_list=
31 automount_status=
32
33 # Unmount all partitions which we possibly mounted
34 unmount_partitions()
35 {
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 ||:
40                 fi
41         done
42 }
43
44 # This function is called on exit, crashes, interrupts, etc. We clean up
45 # everything here before the program is terminated
46 cleanup_handler()
47 {
48         local exitcode="$1"
49
50         # Restore the default exit handler
51         trap - EXIT
52
53         verbose "cleanup and exit with code $exitcode"
54
55         unmount_partitions
56         rm -r $verbose -- "$tmpdir" >&2
57
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"
62         fi
63
64         exit "$exitcode"
65 }
66
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).
71 list_append()
72 {
73         local __value="$1"; shift
74         local __list_name="$1"
75         eval local "__list=\$$__list_name"
76
77         [ -z "$__list" ] && eval "$__list_name=\"\$__value\"" || \
78                             eval "$__list_name=\"\${__list}\${br}\${__value}\""
79 }
80
81 # Similar to 'list_append()', but prepends a list with a line.
82 list_prepend()
83 {
84         local __value="$1"; shift
85         local __list_name="$1"
86         eval local "__list=\$$__list_name"
87
88         [ -z "$__list" ] && eval "$__list_name=\"\$__value\"" || \
89                             eval "$__list_name=\"\${__value}\${br}\${__list}\""
90 }
91
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
96 create_partitions()
97 {
98         # The GPT partition type GUID for Linux partitions
99         local linuxfs_type_id="0fc63daf-8483-4772-8e79-3d69d8477de4"
100
101         verbose "destroying all partitions at \"$dstdisk\""
102         sgdisk -z "$dstdisk" -- > /dev/null 2>&1 ||:
103         sgdisk -Z "$dstdisk" -- > /dev/null 2>&1 ||:
104
105         # Create all partitions one-by-one
106         local pnum=0
107         local gdisk_pnum=1
108         while [ "$pnum" -lt "$part_count" ]; do
109                 installerfw_verify_defined "INSTALLERFW_PART${pnum}_SIZE"
110
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}"
114
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
119                 else
120                         verbose "creating partition $pnum of size $size MiB"
121                         sgdisk -n "$gdisk_pnum:+0:+${size}M" -- "$dstdisk" > /dev/null
122                 fi
123
124                 # Re-set the UUID
125                 local partuuid="$(uuidgen)"
126                 verbose "setting partition $pnum PARTUUID to $partuuid"
127                 sgdisk -u "$gdisk_pnum:$partuuid" -- "$dstdisk" > /dev/null
128
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
133                 fi
134
135                 verbose "setting partition $pnum type ID to $type_id"
136                 sgdisk -t "$gdisk_pnum:$type_id" -- "$dstdisk" > /dev/null
137
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"
141
142                 pnum="$(($pnum + 1))"
143                 gdisk_pnum="$(($gdisk_pnum + 1))"
144         done
145 }
146
147 # Find the device node name by its major:minor numbers ($1:$2).
148 find_devnode_by_nums()
149 {
150         local major="$1"; shift
151         local minor="$1"
152
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")")"
156
157                 if [ -b "/dev/$node" ] && [ "$ma" -eq "$major" ] && \
158                    [ "$mi" -eq "$minor" ]; then
159                         printf "%s" "/dev/$node"
160                         break
161                 fi
162         done
163 }
164
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()
168 {
169         local file="$1"
170         local devnum="$(stat -c '%d' -- "$file")"
171         local major=$((($devnum / 256) % 256))
172         local minor=$(($devnum % 256))
173
174         find_devnode_by_nums "$major" "$minor"
175 }
176
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).
183 find_devnodes()
184 {
185         # Find source device nodes
186
187         local pnum=0
188         while [ "$pnum" -lt "$part_count" ]; do
189                 installerfw_verify_defined "INSTALLERFW_PART${pnum}_MOUNTPOINT"
190
191                 eval local mountpoint="\$INSTALLERFW_PART${pnum}_MOUNTPOINT"
192                 local devnode="$(find_devnode_by_file "$mountpoint")"
193
194                 [ -n "$devnode" ] || fatal "cannot find device node for" \
195                                            "source partition $mountpoint"
196
197                 verbose "source partition $mountpoint device node is: $devnode"
198
199                 eval "srcpart${pnum}_devnode=$devnode"
200
201                 pnum="$(($pnum + 1))"
202         done
203
204         # Find destination device nodes
205
206         local major="$(printf "%d" "0x$(stat -c '%t' -- "$dstdisk")")"
207         local minor="$(printf "%d" "0x$(stat -c '%T' -- "$dstdisk")")"
208
209         pnum=0
210         while [ "$pnum" -lt "$part_count" ]; do
211                 local part_minor="$(($minor + $pnum + 1))"
212                 devnode="$(find_devnode_by_nums "$major" "$part_minor")"
213
214                 [ -n "$devnode" ] || fatal "cannot find device node for" \
215                                            "destination partition $pnum"
216
217                 verbose "destination partition $pnum device node is: $devnode"
218
219                 eval "export INSTALLERFW_PART${pnum}_DEVNODE_NOW=$devnode"
220                 eval "export INSTALLERFW_PART${pnum}_DISK_DEVNODE_NOW=$dstdisk"
221
222                 pnum="$(($pnum + 1))"
223         done
224
225 }
226
227 # Format all the destination partitions and changes the
228 # "INSTALLERFW_PARTx_UUID" installer framework environment variables.
229 format_dst_partitions()
230 {
231         local pnum=0
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"
237
238                 local uuid="$(uuidgen)"
239
240                 verbose "format partition $pnum ($devnode) with mkfs.$fstype"
241
242                 if [ "$fstype" != "vfat" ]; then
243                         verbose "partition $pnum label is \"$label\", UUID is \"$uuid\""
244
245                         [ -z "$label" ] || label="-L $label"
246
247                         mkfs.$fstype $verbose $quiet $label -U "$uuid" -- "$devnode" >&2
248                 else
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:]")"
253
254                         verbose "partition $pnum label is \"$label\", UUID is \"$uuid\""
255
256                         [ -z "$label" ] || label="-n $label"
257
258                         if [ -n "$verbose" ]; then
259                                 mkfs.vfat $verbose $label -i "$uuid" -- "$devnode" >&2
260                         else
261                                 # mkfs.vfat does not accept -q
262                                 mkfs.vfat $label -i "$uuid" -- "$devnode" > /dev/null
263                         fi
264
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'
268                         # requires the "-".
269                         uuid="$(printf "%s" "$uuid" | LC_ALL=C sed -e 's/.\{4\}/&-/')"
270                 fi
271
272                 eval "export INSTALLERFW_PART${pnum}_UUID=$uuid"
273
274                 pnum="$(($pnum+1))"
275         done
276 }
277
278 # Mount source and destination partition
279 mount_partitions()
280 {
281         local pnum=0
282         while [ "$pnum" -lt "$part_count" ]; do
283                 eval local mountpoint="\$INSTALLERFW_PART${pnum}_MOUNTPOINT"
284
285                 # Mount the source partition
286                 eval local devnode="\$srcpart${pnum}_devnode"
287                 local mntdir="$srcbase/$pnum"
288
289                 verbose "mounting source partition $pnum" \
290                         "($devnode, $mountpoint) to $mntdir"
291
292                 mkdir -p $verbose -- "$mntdir" >&2
293                 mount $verbose -- "$devnode" "$mntdir" >&2
294                 list_prepend "$mntdir" "unmount_list"
295
296                 # Mount the destination partition
297                 eval devnode="\$INSTALLERFW_PART${pnum}_DEVNODE_NOW"
298                 mntdir="$dstbase/$pnum"
299
300                 verbose "mounting destination partition $pnum" \
301                         "($devnode, $mountpoint) to $mntdir"
302
303                 mkdir -p $verbose -- "$mntdir" >&2
304                 mount $verbose -- "$devnode" "$mntdir" >&2
305                 list_prepend "$mntdir" "unmount_list"
306
307                 pnum="$(($pnum + 1))"
308         done
309 }
310
311 # Copy data from all the source partitions to destination partitions
312 copy_the_data()
313 {
314         local pnum=0
315         while [ "$pnum" -lt "$part_count" ]; do
316                 local src_mntdir="$srcbase/$pnum"
317                 local dst_mntdir="$dstbase/$pnum"
318
319                 verbose "copy partition $pnum:" \
320                         "\"$src_mntdir\" -> \"$dst_mntdir\""
321
322                 rsync  -WaHXSh --exclude "${tmpdir}" "$src_mntdir/" "$dst_mntdir/"
323
324                 pnum="$(($pnum + 1))"
325         done
326 }
327
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()
333 {
334         # Create a sorted list of mount points
335         local list=
336         local mountpoint=
337         local pnum=0
338         while [ "$pnum" -lt "$part_count" ]; do
339                 eval mountpoint="\$INSTALLERFW_PART${pnum}_MOUNTPOINT"
340                 list_append "$mountpoint" "list"
341                 pnum="$(($pnum+1))"
342         done
343
344         list="$(printf "%s" "$list" | sort)"
345
346         mkdir $verbose -- "$dstbase/root" >&2
347
348         local devnode=
349         local mntdir=
350         local IFS="$br"
351         for mountpoint in $list; do
352                 installerfw_get_part_info "$mountpoint" "DEVNODE_NOW" "devnode"
353                 mntdir="$dstbase/root$mountpoint"
354
355                 verbose "mounting \"$devnode\" under \"$mntdir\""
356                 mount $verbose -- "$devnode" "$mntdir" >&2
357
358                 list_prepend "$mntdir" "unmount_list"
359         done
360 }
361
362 # Amend the /etc/installerfw-environment file at the destination.
363 amend_installerfw_environment()
364 {
365         # Most of the variables were already changed, let's take care about the
366         # rest.
367         export INSTALLERFW_INSTALLER_NAME="$PROG"
368         export INSTALLERFW_MOUNT_PREFIX="$dstbase/root"
369
370         local pnum=0
371         while [ "$pnum" -lt "$part_count" ]; do
372                 eval "unset INSTALLERFW_PART${pnum}_DEVNODE"
373                 eval "unset INSTALLERFW_PART${pnum}_DISK_DEVNODE"
374                 pnum="$(($pnum+1))"
375         done
376
377         installerfw_save_env
378 }
379
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.
384 is_mounted()
385 {
386         local devnode="$(esc_regexp "$1")"
387
388         grep -q -e "^$devnode[p0-9]* " /proc/mounts
389 }
390
391 show_usage()
392 {
393         cat <<-EOF
394 Usage: $PROG [options] <dstdisk>
395
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!
399
400 The program roughly works like this.
401
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.
411
412 Options:
413   -v, --verbose  be verbose
414   --version      show the program version and exit
415   -h, --help     show this text and exit
416 EOF
417 }
418
419 show_usage_fail()
420 {
421         IFS= printf "%s\n\n" "$PROG: error: $*" >&2
422         show_usage >&2
423         exit 1
424 }
425
426 # Parse the options
427 tmp=`getopt -n $PROG -o v,h --long verbose,version,help -- "$@"` ||
428         show_usage_fail "cannot parse command-line options"
429 eval set -- "$tmp"
430
431 dstdisk=
432 verbose=
433 quiet="-q"
434 while true; do
435         case "$1" in
436         -v|--verbose)
437                 verbose="-v"
438                 quiet=
439                 ;;
440         --version)
441                 printf "%s\n" "$VER"
442                 exit 0
443                 ;;
444         -h|--help)
445                 show_usage
446                 exit 0
447                 ;;
448         --) shift; break
449                 ;;
450         *) show_usage_fail "unrecognized option \"$1\""
451                 ;;
452         esac
453         shift
454 done
455
456 [ "$#" -eq 1 ] || \
457         show_usage_fail "please, provide exactly one \"disk\" argument"
458
459 dstdisk="$1"
460 [ -b "$dstdisk" ] || \
461         fatal "\"$dstdisk\" does not exist or is not a block device node"
462
463 is_mounted "$dstdisk" && fatal "\"$dstdisk\" is mounted"
464
465 # Load the installer framework data
466 if ! installerfw_available; then
467         installerfw_restore_env
468 fi
469
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
473
474 # Create our work directory
475 tmpdir="$(mktemp --tmpdir="/tmp" -d -t -- "$PROG.XXXX")"
476 verbose "tmpdir is \"$tmpdir\""
477
478 srcbase="$tmpdir/src"
479 dstbase="$tmpdir/dst"
480 mkdir $verbose -- "$srcbase" >&2
481 mkdir $verbose -- "$dstbase" >&2
482
483 # Fetch some installer framework variables
484 installerfw_verify_defined "INSTALLERFW_PTABLE_FORMAT"
485 installerfw_verify_defined "INSTALLERFW_PART_COUNT"
486
487 ptable_format="$INSTALLERFW_PTABLE_FORMAT"
488 part_count="$INSTALLERFW_PART_COUNT"
489
490 # Some general checks
491 if [ "$ptable_format" != "gpt" ]; then
492         fatal "partition table type \"$ptable_format\" is not supported"
493 fi
494
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"
503         fi
504 fi
505
506 # Create partitions on the destination disk
507 message "partitioning $dstdisk"
508 create_partitions
509
510 # Make sure the device nodes are created
511 udevadm settle > /dev/null 2>&1 ||:
512
513 # Find device nodes for source and destination partitions
514 find_devnodes
515
516 # Format the destination partitions
517 message "formatting $dstdisk"
518 format_dst_partitions
519
520 # Mount the source and destination partitions
521 mount_partitions
522
523 # Copy all the data
524 message "copying all the data to $dstdisk"
525 copy_the_data
526
527 # Create proper target file-system hierarchy in order to be able to execute
528 # installer framework scripts for the target.
529 create_dst_hierarchy
530
531 message "finished copying, configuring the cloned OS"
532
533 # Amend the /etc/installerfw-environment file on the destination
534 amend_installerfw_environment
535
536 # Configure the target system
537 $srcdir/setup-ivi-boot $verbose
538
539 # Note, unmount happens in 'cleanup_handler()'
540 message "done, synchronizing the data"
541 sync