setup-gummiboot-conf: Add HDMI/DP specific splash .conf entries
[platform/adaptation/setup-scripts.git] / setup-scripts-clone
1 #!/bin/sh -euf
2
3 # Copyright 2013-2014 Intel Corporation
4 # Author: Artem Bityutskiy
5 # License: GPLv2
6
7 PROG="setup-scripts-clone"
8 VER="1.0"
9
10 srcdir="$(readlink -ev -- ${0%/*})"
11
12 if [ -f "$srcdir/setup-scripts-sh-functions" ]; then
13         . "$srcdir/setup-scripts-sh-functions"
14         . "$srcdir/installerfw-sh-functions"
15 else
16         .  /usr/share/setup-scripts/setup-scripts-sh-functions
17         .  /usr/share/setup-scripts/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                 local context=$(cat "/proc/$$/attr/current")
323                 echo -n '_' > "/proc/$$/attr/current"
324                 rsync  -WaHXSh --exclude "${tmpdir}" "$src_mntdir/" "$dst_mntdir/"
325                 echo -n "$context" > "/proc/$$/attr/current"
326
327                 pnum="$(($pnum + 1))"
328         done
329 }
330
331 # This function creates proper target hierarchy by mounting destination
332 # partition under the right mount points. By the time this function is called,
333 # all the destination partitions are mounted under "$dstbase/$pnum", so "/boot"
334 # is not under "/", etc. This function mounts them under "$dstbase/root"
335 create_dst_hierarchy()
336 {
337         # Create a sorted list of mount points
338         local list=
339         local mountpoint=
340         local pnum=0
341         while [ "$pnum" -lt "$part_count" ]; do
342                 eval mountpoint="\$INSTALLERFW_PART${pnum}_MOUNTPOINT"
343                 list_append "$mountpoint" "list"
344                 pnum="$(($pnum+1))"
345         done
346
347         list="$(printf "%s" "$list" | sort)"
348
349         mkdir $verbose -- "$dstbase/root" >&2
350
351         local devnode=
352         local mntdir=
353         local IFS="$br"
354         for mountpoint in $list; do
355                 installerfw_get_part_info "$mountpoint" "DEVNODE_NOW" "devnode"
356                 mntdir="$dstbase/root$mountpoint"
357
358                 verbose "mounting \"$devnode\" under \"$mntdir\""
359                 mount $verbose -- "$devnode" "$mntdir" >&2
360
361                 list_prepend "$mntdir" "unmount_list"
362         done
363 }
364
365 # Amend the /etc/installerfw-environment file at the destination.
366 amend_installerfw_environment()
367 {
368         # Most of the variables were already changed, let's take care about the
369         # rest.
370         export INSTALLERFW_INSTALLER_NAME="$PROG"
371         export INSTALLERFW_MOUNT_PREFIX="$dstbase/root"
372
373         local pnum=0
374         while [ "$pnum" -lt "$part_count" ]; do
375                 eval "unset INSTALLERFW_PART${pnum}_DEVNODE"
376                 eval "unset INSTALLERFW_PART${pnum}_DISK_DEVNODE"
377                 pnum="$(($pnum+1))"
378         done
379
380         installerfw_save_env
381 }
382
383 # Check if the block device is mounted
384 # TODO: the much better way to do this is to use the open(2) "O_EXCL" flag
385 # which has special meaning for block devices. This would need to write a
386 # C helper program, something like setup-scripts-open-helper.
387 is_mounted()
388 {
389         local devnode="$(esc_regexp "$1")"
390
391         grep -q -e "^$devnode[p0-9]* " /proc/mounts
392 }
393
394 show_usage()
395 {
396         cat <<-EOF
397 Usage: $PROG [options] <dstdisk>
398
399 Clone a running Tizen system to a different disk. The mandatory argument
400 "<dstdisk>" is the device node name to clone to (e.g., /dev/sda). Be careful,
401 this program will destroy all the "<dstdisk>" data!
402
403 The program roughly works like this.
404
405 1. Reads the "/etc/installerfw-environment" file to get information about the
406    partitions of the running system.
407 2. Partitions "<dstdisk>" according to "/etc/installerfw-environment" (the last
408    partition gets re-sized to consume all the space on "<dstdisk>").
409 3. Formats all the newly created partitions on "<dstdisk>".
410 4. Copies all the data from the disk Tizen currently runs from to
411    "<dstdisk>", partition-by-partition.
412 5. Configures the cloned system on "<dstdisk>" by "setup-scripts-boot".
413 6. Reboots the system.
414
415 Options:
416   -v, --verbose  be verbose
417   --version      show the program version and exit
418   -h, --help     show this text and exit
419 EOF
420 }
421
422 show_usage_fail()
423 {
424         IFS= printf "%s\n\n" "$PROG: error: $*" >&2
425         show_usage >&2
426         exit 1
427 }
428
429 # Parse the options
430 tmp=`getopt -n $PROG -o v,h --long verbose,version,help -- "$@"` ||
431         show_usage_fail "cannot parse command-line options"
432 eval set -- "$tmp"
433
434 dstdisk=
435 verbose=
436 quiet="-q"
437 while true; do
438         case "$1" in
439         -v|--verbose)
440                 verbose="-v"
441                 quiet=
442                 ;;
443         --version)
444                 printf "%s\n" "$VER"
445                 exit 0
446                 ;;
447         -h|--help)
448                 show_usage
449                 exit 0
450                 ;;
451         --) shift; break
452                 ;;
453         *) show_usage_fail "unrecognized option \"$1\""
454                 ;;
455         esac
456         shift
457 done
458
459 [ "$#" -eq 1 ] || \
460         show_usage_fail "please, provide exactly one \"disk\" argument"
461
462 dstdisk="$1"
463 [ -b "$dstdisk" ] || \
464         fatal "\"$dstdisk\" does not exist or is not a block device node"
465
466 is_mounted "$dstdisk" && fatal "\"$dstdisk\" is mounted"
467
468 # Load the installer framework data
469 if ! installerfw_available; then
470         installerfw_restore_env
471 fi
472
473 # Install the cleanup handler (the first parameter will be our exit code)
474 trap 'cleanup_handler $?' EXIT
475 trap 'cleanup_handler 1' HUP PIPE INT QUIT TERM
476
477 # Create our work directory
478 tmpdir="$(mktemp --tmpdir="/tmp" -d -t -- "$PROG.XXXX")"
479 verbose "tmpdir is \"$tmpdir\""
480
481 srcbase="$tmpdir/src"
482 dstbase="$tmpdir/dst"
483 mkdir $verbose -- "$srcbase" >&2
484 mkdir $verbose -- "$dstbase" >&2
485
486 # Fetch some installer framework variables
487 installerfw_verify_defined "INSTALLERFW_PTABLE_FORMAT"
488 installerfw_verify_defined "INSTALLERFW_PART_COUNT"
489
490 ptable_format="$INSTALLERFW_PTABLE_FORMAT"
491 part_count="$INSTALLERFW_PART_COUNT"
492
493 # Some general checks
494 if [ "$ptable_format" != "gpt" ]; then
495         fatal "partition table type \"$ptable_format\" is not supported"
496 fi
497
498 # Disable the Tizen automount agent, otherwise it may start mounting the
499 # $dstdisk partitions that we are going to create
500 automount_status="$(systemctl --user is-active udisks-automount-agent 2>/dev/null ||:)"
501 if [ "$automount_status" = "active" ]; then
502         message "disabling udisks-automount-agent"
503         if ! systemctl --user stop udisks-automount-agent; then
504                 warning "cannot stop udisks-automount-agent"
505                 automount_status="dunno"
506         fi
507 fi
508
509 # Create partitions on the destination disk
510 message "partitioning $dstdisk"
511 create_partitions
512
513 # Make sure the device nodes are created
514 udevadm settle > /dev/null 2>&1 ||:
515
516 # Find device nodes for source and destination partitions
517 find_devnodes
518
519 # Format the destination partitions
520 message "formatting $dstdisk"
521 format_dst_partitions
522
523 # Mount the source and destination partitions
524 mount_partitions
525
526 # Copy all the data
527 message "copying all the data to $dstdisk"
528 copy_the_data
529
530 # Create proper target file-system hierarchy in order to be able to execute
531 # installer framework scripts for the target.
532 create_dst_hierarchy
533
534 message "finished copying, configuring the cloned OS"
535
536 # Amend the /etc/installerfw-environment file on the destination
537 amend_installerfw_environment
538
539 # Configure the target system
540 $srcdir/setup-scripts-boot $verbose
541
542 # Note, unmount happens in 'cleanup_handler()'
543 message "done, synchronizing the data"
544 sync