base-files: add sysupgrade -k to save list of pkgs
[openwrt/staging/stintel.git] / package / base-files / files / sbin / sysupgrade
1 #!/bin/sh
2
3 . /lib/functions.sh
4 . /lib/functions/system.sh
5
6 # initialize defaults
7 export MTD_CONFIG_ARGS=""
8 export INTERACTIVE=0
9 export VERBOSE=1
10 export SAVE_CONFIG=1
11 export SAVE_OVERLAY=0
12 export SAVE_OVERLAY_PATH=
13 export SAVE_PARTITIONS=1
14 export SAVE_INSTALLED_PKGS=0
15 export SKIP_UNCHANGED=0
16 export CONF_IMAGE=
17 export CONF_BACKUP_LIST=0
18 export CONF_BACKUP=
19 export CONF_RESTORE=
20 export NEED_IMAGE=
21 export HELP=0
22 export FORCE=0
23 export TEST=0
24 export UMOUNT_ETCBACKUP_DIR=0
25
26 # parse options
27 while [ -n "$1" ]; do
28 case "$1" in
29 -i) export INTERACTIVE=1;;
30 -v) export VERBOSE="$(($VERBOSE + 1))";;
31 -q) export VERBOSE="$(($VERBOSE - 1))";;
32 -n) export SAVE_CONFIG=0;;
33 -c) export SAVE_OVERLAY=1 SAVE_OVERLAY_PATH=/etc;;
34 -o) export SAVE_OVERLAY=1 SAVE_OVERLAY_PATH=/;;
35 -p) export SAVE_PARTITIONS=0;;
36 -k) export SAVE_INSTALLED_PKGS=1;;
37 -u) export SKIP_UNCHANGED=1;;
38 -b|--create-backup) export CONF_BACKUP="$2" NEED_IMAGE=1; shift;;
39 -r|--restore-backup) export CONF_RESTORE="$2" NEED_IMAGE=1; shift;;
40 -l|--list-backup) export CONF_BACKUP_LIST=1;;
41 -f) export CONF_IMAGE="$2"; shift;;
42 -F|--force) export FORCE=1;;
43 -T|--test) export TEST=1;;
44 -h|--help) export HELP=1; break;;
45 -*)
46 echo "Invalid option: $1" >&2
47 exit 1
48 ;;
49 *) break;;
50 esac
51 shift;
52 done
53
54 export CONFFILES=/tmp/sysupgrade.conffiles
55 export CONF_TAR=/tmp/sysupgrade.tgz
56 export ETCBACKUP_DIR=/etc/backup
57 export INSTALLED_PACKAGES=${ETCBACKUP_DIR}/installed_packages.txt
58
59 IMAGE="$1"
60
61 [ -z "$IMAGE" -a -z "$NEED_IMAGE" -a $CONF_BACKUP_LIST -eq 0 -o $HELP -gt 0 ] && {
62 cat <<EOF
63 Usage: $0 [<upgrade-option>...] <image file or URL>
64 $0 [-q] [-i] [-c] [-u] [-o] [-k] <backup-command> <file>
65
66 upgrade-option:
67 -f <config> restore configuration from .tar.gz (file or url)
68 -i interactive mode
69 -c attempt to preserve all changed files in /etc/
70 -o attempt to preserve all changed files in /, except those
71 from packages but including changed confs.
72 -u skip from backup files that are equal to those in /rom
73 -n do not save configuration over reflash
74 -p do not attempt to restore the partition table after flash.
75 -k include in backup a list of current installed packages at
76 $INSTALLED_PACKAGES
77 -T | --test
78 Verify image and config .tar.gz but do not actually flash.
79 -F | --force
80 Flash image even if image checks fail, this is dangerous!
81 -q less verbose
82 -v more verbose
83 -h | --help display this help
84
85 backup-command:
86 -b | --create-backup <file>
87 create .tar.gz of files specified in sysupgrade.conf
88 then exit. Does not flash an image. If file is '-',
89 i.e. stdout, verbosity is set to 0 (i.e. quiet).
90 -r | --restore-backup <file>
91 restore a .tar.gz created with sysupgrade -b
92 then exit. Does not flash an image. If file is '-',
93 the archive is read from stdin.
94 -l | --list-backup
95 list the files that would be backed up when calling
96 sysupgrade -b. Does not create a backup file.
97
98 EOF
99 exit 1
100 }
101
102 [ -n "$IMAGE" -a -n "$NEED_IMAGE" ] && {
103 cat <<-EOF
104 -b|--create-backup and -r|--restore-backup do not perform a firmware upgrade.
105 Do not specify both -b|-r and a firmware image.
106 EOF
107 exit 1
108 }
109
110 # prevent messages from clobbering the tarball when using stdout
111 [ "$CONF_BACKUP" = "-" ] && export VERBOSE=0
112
113
114 list_conffiles() {
115 awk '
116 BEGIN { conffiles = 0 }
117 /^Conffiles:/ { conffiles = 1; next }
118 !/^ / { conffiles = 0; next }
119 conffiles == 1 { print }
120 ' /usr/lib/opkg/status
121 }
122
123 list_changed_conffiles() {
124 # Cannot handle spaces in filenames - but opkg cannot either...
125 list_conffiles | while read file csum; do
126 [ -r "$file" ] || continue
127
128 echo "${csum} ${file}" | sha256sum -sc - || echo "$file"
129 done
130 }
131
132 add_conffiles() {
133 local file="$1"
134 ( find $(sed -ne '/^[[:space:]]*$/d; /^#/d; p' \
135 /etc/sysupgrade.conf /lib/upgrade/keep.d/* 2>/dev/null) \
136 \( -type f -o -type l \) $find_filter 2>/dev/null;
137 list_changed_conffiles ) | sort -u > "$file"
138 return 0
139 }
140
141 add_overlayfiles() {
142 local file="$1"
143
144 local packagesfiles=$1.packagesfiles
145 touch "$packagesfiles"
146
147 if [ "$SAVE_OVERLAY_PATH" = / ]; then
148 local conffiles=$1.conffiles
149 local keepfiles=$1.keepfiles
150
151 list_conffiles | cut -f2 -d ' ' | sort -u > "$conffiles"
152
153 # backup files from /etc/sysupgrade.conf and /lib/upgrade/keep.d, but
154 # ignore those aready controlled by opkg conffiles
155 find $(sed -ne '/^[[:space:]]*$/d; /^#/d; p' \
156 /etc/sysupgrade.conf /lib/upgrade/keep.d/* 2>/dev/null) \
157 \( -type f -o -type l \) 2>/dev/null | sort -u |
158 grep -h -v -x -F -f $conffiles > "$keepfiles"
159
160 # backup conffiles, but only those changed if '-u'
161 [ $SKIP_UNCHANGED = 1 ] &&
162 list_changed_conffiles | sort -u > "$conffiles"
163
164 # do not backup files from packages, except those listed
165 # in conffiles and keep.d
166 {
167 find /usr/lib/opkg/info -type f -name "*.list" -exec cat {} \;
168 find /usr/lib/opkg/info -type f -name "*.control" -exec sed \
169 -ne '/^Alternatives/{s/^Alternatives: //;s/, /\n/g;p}' {} \; |
170 cut -f2 -d:
171 } | grep -v -x -F -f $conffiles |
172 grep -v -x -F -f $keepfiles | sort -u > "$packagesfiles"
173 rm -f "$keepfiles" "$conffiles"
174 fi
175
176 # busybox grep bug when file is empty
177 [ -s "$packagesfiles" ] || echo > $packagesfiles
178
179 ( cd /overlay/upper/; find .$SAVE_OVERLAY_PATH \( -type f -o -type l \) $find_filter | sed \
180 -e 's,^\.,,' \
181 -e '\,^/etc/board.json$,d' \
182 -e '\,/[^/]*-opkg$,d' \
183 -e '\,^/etc/urandom.seed$,d' \
184 -e "\,^$INSTALLED_PACKAGES$,d" \
185 -e '\,^/usr/lib/opkg/.*,d' \
186 ) | grep -v -x -F -f $packagesfiles > "$file"
187
188 rm -f "$packagesfiles"
189
190 return 0
191 }
192
193 # hooks
194 sysupgrade_image_check="fwtool_check_signature fwtool_check_image platform_check_image"
195
196 if [ $SAVE_OVERLAY = 1 ]; then
197 [ ! -d /overlay/upper/etc ] && {
198 echo "Cannot find '/overlay/upper/etc', required for '-c'" >&2
199 exit 1
200 }
201 sysupgrade_init_conffiles="add_overlayfiles"
202 else
203 sysupgrade_init_conffiles="add_conffiles"
204 fi
205
206 find_filter=""
207 if [ $SKIP_UNCHANGED = 1 ]; then
208 [ ! -d /rom/ ] && {
209 echo "'/rom/' is required by '-u'"
210 exit 1
211 }
212 find_filter='( ( -exec test -e /rom/{} ; -exec cmp -s /{} /rom/{} ; ) -o -print )'
213 fi
214
215 include /lib/upgrade
216
217 do_save_conffiles() {
218 local conf_tar="$1"
219
220 [ -z "$(rootfs_type)" ] && {
221 echo "Cannot save config while running from ramdisk." >&2
222 ask_bool 0 "Abort" && exit
223 rm -f "$conf_tar"
224 return 0
225 }
226 run_hooks "$CONFFILES" $sysupgrade_init_conffiles
227 ask_bool 0 "Edit config file list" && vi "$CONFFILES"
228
229 if [ "$SAVE_INSTALLED_PKGS" -eq 1 ]; then
230 echo "${INSTALLED_PACKAGES}" >> "$CONFFILES"
231 mkdir -p "$ETCBACKUP_DIR"
232 # Avoid touching filesystem on each backup
233 RAMFS="$(mktemp -d -t sysupgrade.XXXXXX)"
234 mkdir -p "$RAMFS/upper" "$RAMFS/work"
235 mount -t overlay overlay -o lowerdir=$ETCBACKUP_DIR,upperdir=$RAMFS/upper,workdir=$RAMFS/work $ETCBACKUP_DIR &&
236 UMOUNT_ETCBACKUP_DIR=1 || {
237 echo "Cannot mount '$ETCBACKUP_DIR' as tmpfs to avoid touching disk while saving the list of installed packages." >&2
238 ask_bool 0 "Abort" && exit
239 }
240
241 # Format: pkg-name<TAB>{rom,overlay,unkown}
242 # rom is used for pkgs in /rom, even if updated later
243 find /usr/lib/opkg/info -name "*.control" \( \
244 \( -exec test -f /rom/{} \; -exec echo {} rom \; \) -o \
245 \( -exec test -f /overlay/upper/{} \; -exec echo {} overlay \; \) -o \
246 \( -exec echo {} unknown \; \) \
247 \) | sed -e 's,.*/,,;s/\.control /\t/' > ${INSTALLED_PACKAGES}
248 fi
249
250 v "Saving config files..."
251 [ "$VERBOSE" -gt 1 ] && TAR_V="v" || TAR_V=""
252 tar c${TAR_V}zf "$conf_tar" -T "$CONFFILES" 2>/dev/null
253 if [ "$?" -ne 0 ]; then
254 echo "Failed to create the configuration backup."
255 rm -f "$conf_tar"
256 exit 1
257 fi
258
259 [ "$UMOUNT_ETCBACKUP_DIR" -eq 1 ] && {
260 umount "$ETCBACKUP_DIR"
261 rm -rf "$RAMFS"
262 }
263 rm -f "$CONFFILES"
264 }
265
266 if [ $CONF_BACKUP_LIST -eq 1 ]; then
267 run_hooks "$CONFFILES" $sysupgrade_init_conffiles
268 [ "$SAVE_INSTALLED_PKGS" -eq 1 ] && echo ${INSTALLED_PACKAGES} >> "$CONFFILES"
269 cat "$CONFFILES"
270 rm -f "$CONFFILES"
271 exit 0
272 fi
273
274 if [ -n "$CONF_BACKUP" ]; then
275 do_save_conffiles "$CONF_BACKUP"
276 exit $?
277 fi
278
279 if [ -n "$CONF_RESTORE" ]; then
280 if [ "$CONF_RESTORE" != "-" ] && [ ! -f "$CONF_RESTORE" ]; then
281 echo "Backup archive '$CONF_RESTORE' not found." >&2
282 exit 1
283 fi
284
285 [ "$VERBOSE" -gt 1 ] && TAR_V="v" || TAR_V=""
286 tar -C / -x${TAR_V}zf "$CONF_RESTORE"
287 exit $?
288 fi
289
290 type platform_check_image >/dev/null 2>/dev/null || {
291 echo "Firmware upgrade is not implemented for this platform." >&2
292 exit 1
293 }
294
295 case "$IMAGE" in
296 http://*|\
297 https://*)
298 wget -O/tmp/sysupgrade.img "$IMAGE"
299 IMAGE=/tmp/sysupgrade.img
300 ;;
301 esac
302
303 IMAGE="$(readlink -f "$IMAGE")"
304
305 case "$IMAGE" in
306 '')
307 echo "Image file not found." >&2
308 exit 1
309 ;;
310 /tmp/*) ;;
311 *)
312 v "Image not in /tmp, copying..."
313 cp -f "$IMAGE" /tmp/sysupgrade.img
314 IMAGE=/tmp/sysupgrade.img
315 ;;
316 esac
317
318 export ARGV="$IMAGE"
319 export ARGC=1
320
321 for check in $sysupgrade_image_check; do
322 ( $check "$IMAGE" ) || {
323 if [ $FORCE -eq 1 ]; then
324 echo "Image check '$check' failed but --force given - will update anyway!" >&2
325 break
326 else
327 echo "Image check '$check' failed." >&2
328 exit 1
329 fi
330 }
331 done
332
333 if [ -n "$CONF_IMAGE" ]; then
334 case "$(get_magic_word $CONF_IMAGE cat)" in
335 # .gz files
336 1f8b) ;;
337 *)
338 echo "Invalid config file. Please use only .tar.gz files" >&2
339 exit 1
340 ;;
341 esac
342 get_image "$CONF_IMAGE" "cat" > "$CONF_TAR"
343 export SAVE_CONFIG=1
344 elif ask_bool $SAVE_CONFIG "Keep config files over reflash"; then
345 [ $TEST -eq 1 ] || do_save_conffiles "$CONF_TAR"
346 export SAVE_CONFIG=1
347 else
348 [ $TEST -eq 1 ] || rm -f "$CONF_TAR"
349 export SAVE_CONFIG=0
350 fi
351
352 if [ $TEST -eq 1 ]; then
353 exit 0
354 fi
355
356 if [ $SAVE_PARTITIONS -eq 0 ]; then
357 touch /tmp/sysupgrade.always.overwrite.bootdisk.partmap
358 else
359 rm -f /tmp/sysupgrade.always.overwrite.bootdisk.partmap
360 fi
361
362 install_bin /sbin/upgraded
363 v "Commencing upgrade. Closing all shell sessions."
364
365 COMMAND='. /lib/functions.sh; include /lib/upgrade; do_upgrade_stage2'
366
367 if [ -n "$FAILSAFE" ]; then
368 printf '%s\x00%s\x00%s' "$RAM_ROOT" "$IMAGE" "$COMMAND" >/tmp/sysupgrade
369 lock -u /tmp/.failsafe
370 else
371 ubus call system sysupgrade "{
372 \"prefix\": $(json_string "$RAM_ROOT"),
373 \"path\": $(json_string "$IMAGE"),
374 \"command\": $(json_string "$COMMAND")
375 }"
376 fi