From 4af69aba51d79c401c71f06ae57b52176779d5dc Mon Sep 17 00:00:00 2001 From: Jan Schneider Date: Sun, 18 Jun 2017 23:27:29 +0200 Subject: [PATCH] Replace /lib just before reboot, patch cmdline.txt and /etc/fstab --- addon/lib/rmupdate.tcl | 131 +++++++++++++++++++++++++++++++++++++---- addon/www/index.html | 23 ++++++-- addon/www/rest.cgi | 3 +- rmupdate | 13 +++- rmupdate.tar.gz | Bin 9296 -> 9335 bytes 5 files changed, 152 insertions(+), 18 deletions(-) diff --git a/addon/lib/rmupdate.tcl b/addon/lib/rmupdate.tcl index ea30144..1f8224a 100644 --- a/addon/lib/rmupdate.tcl +++ b/addon/lib/rmupdate.tcl @@ -128,6 +128,66 @@ proc ::rmupdate::is_system_upgradeable {} { return 1 } +proc ::rmupdate::get_part_id {device} { + #set data [exec blkid $device] + #foreach d [split $data "\n"] { + # # recent busybox version needed + # regexp {PARTUUID="([^"]+)"} $d match partuuid + # if { [info exists partuuid] } { + # return $partuuid + # } + #} + foreach f [glob /dev/disk/by-partuuid/*] { + set d "" + catch { + set d [file readlink $f] + } + if { [file tail $d] == [file tail $device] } { + return [file tail $f] + } + } + return "" +} + +proc ::rmupdate::update_cmdline {cmdline root} { + set fd [open $cmdline r] + set data [read $fd] + close $fd + + regsub -all "root=\[a-zA-Z0-9=/-\]+\s" $data "root=${root} " data + + set fd [open $cmdline w] + puts $fd $data + close $fd +} + +proc ::rmupdate::update_fstab {fstab {boot ""} {root ""} {user ""}} { + set ndata "" + set fd [open $fstab r] + set data [read $fd] + foreach d [split $data "\n"] { + set filesystem "" + regexp {^([^#]\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+).*} $d match filesystem mountpoint type options dump pass + if { [info exists filesystem] } { + if {$filesystem != ""} { + if {$mountpoint == "/" && $root != ""} { + regsub -all $filesystem $d $root d + } elseif {$mountpoint == "/boot" && $boot != ""} { + regsub -all $filesystem $d $boot d + } elseif {$mountpoint == "/usr/local" && $user != ""} { + regsub -all $filesystem $d $user d + } + } + } + append ndata "${d}\n" + } + close $fd + + set fd [open $fstab w] + puts $fd $ndata + close $fd +} + proc ::rmupdate::mount_image_partition {image partition mountpoint} { variable loop_dev variable sys_dev @@ -211,6 +271,8 @@ proc ::rmupdate::check_sizes {image} { proc ::rmupdate::update_filesystems {image {dryrun 0}} { variable mnt_new variable mnt_cur + variable sys_dev + set extra_args "" if {$dryrun != 0} { set extra_args "--dry-run" @@ -228,9 +290,30 @@ proc ::rmupdate::update_filesystems {image {dryrun 0}} { mount_system_partition $partition $mnt_cur write_log "Rsyncing filesystem of partition ${partition}." - set data [exec rsync ${extra_args} --progress --archive --delete "${mnt_new}/" "${mnt_cur}"] + if {$partition == 2} { + exec rsync ${extra_args} --progress --archive --delete --exclude=/lib "${mnt_new}/" "${mnt_cur}" + exec rsync ${extra_args} --progress --archive --delete "${mnt_new}/lib/" "${mnt_cur}/lib.rmupdate" + } else { + exec rsync ${extra_args} --progress --archive --delete "${mnt_new}/" "${mnt_cur}" + } write_log "Rsync finished." + if {$partition == 1} { + write_log "Update cmdline." + if {$dryrun == 0} { + update_cmdline "${mnt_cur}/cmdline.txt" "${sys_dev}p2" + #set partid [get_part_id "${sys_dev}p2"] + #if { $partid != "" } { + # set_root_in_cmdline "${mnt_cur}/cmdline.txt" "PARTUUID=${partid}" + #} + } + } elseif {$partition == 2} { + write_log "Update fstab." + if {$dryrun == 0} { + update_fstab "${mnt_cur}/etc/fstab" "${sys_dev}p1" "/dev/root" "${sys_dev}p3" + } + } + umount $mnt_new umount $mnt_cur } @@ -261,7 +344,7 @@ proc ::rmupdate::get_available_firmware_downloads {} { variable release_url set rpi_version [get_rpi_version] set download_urls [list] - set data [exec wget "${release_url}" --no-check-certificate -q -O-] + set data [exec /usr/bin/wget "${release_url}" --no-check-certificate -q -O-] foreach d [split $data ">"] { set href "" regexp {<\s*a\s+href\s*=\s*"([^"]+/releases/download/[^"]+)\.zip"} $d match href @@ -309,15 +392,15 @@ proc ::rmupdate::download_firmware {version} { set archive_file "${img_dir}/${archive_file}" file mkdir $img_dir if {$log_file != ""} { - exec wget "${download_url}" --show-progress --progress=dot:giga --no-check-certificate --quiet --output-document=$archive_file 2>>${log_file} + exec /usr/bin/wget "${download_url}" --show-progress --progress=dot:giga --no-check-certificate --quiet --output-document=$archive_file 2>>${log_file} write_log "" } else { - exec wget "${download_url}" --no-check-certificate --quiet --output-document=$archive_file + exec /usr/bin/wget "${download_url}" --no-check-certificate --quiet --output-document=$archive_file } write_log "Download completed." write_log "Extracting firmware ${archive_file}." - set data [exec unzip -ql "${archive_file}"] + set data [exec /usr/bin/unzip -ql "${archive_file}"] set img_file "" foreach d [split $data "\n"] { regexp {\s+(\S+\.img)\s*$} $d match img_file @@ -328,7 +411,7 @@ proc ::rmupdate::download_firmware {version} { if { $img_file == "" } { error "Failed to extract image from archive." } - exec unzip "${archive_file}" "${img_file}" -o -d "${img_dir}" + exec /usr/bin/unzip "${archive_file}" "${img_file}" -o -d "${img_dir}" set img_file "${img_dir}/${img_file}" puts "${img_file} ${image_file}" if {$img_file != $image_file} { @@ -418,7 +501,7 @@ proc ::rmupdate::delete_firmware_image {version} { eval {file delete [glob "${img_dir}/*${version}.img"]} } -proc ::rmupdate::install_firmware_version {version {reboot 1}} { +proc ::rmupdate::install_firmware_version {version {reboot 1} {dryrun 0}} { if {[rmupdate::install_process_running]} { error "Another install process is running." } @@ -447,13 +530,41 @@ proc ::rmupdate::install_firmware_version {version {reboot 1}} { } check_sizes $firmware_image - update_filesystems $firmware_image + update_filesystems $firmware_image $dryrun file delete $install_lock if {$reboot} { - write_log "Rebooting system." - exec /bin/sh -c "/bin/sleep 5; /sbin/reboot -f" & + if { [file exist /lib.rmupdate] } { + write_log "Replacing /lib and rebooting system." + } else { + write_log "Rebooting system." + } + } + # Write success marker for web interface + write_log "INSTALL_FIRMWARE_SUCCESS" + after 3000 + + if {$reboot} { + if { [file exist /lib.rmupdate] } { + exec mount -o remount,rw / + exec rsync --archive --delete /lib.rmupdate/ /lib + + set fd [open /proc/sys/kernel/sysrq "a"] + puts $fd "1" + close $fd + set fd [open /proc/sysrq-trigger "a"] + puts $fd "s" + close $fd + set fd [open /proc/sysrq-trigger "a"] + puts $fd "u" + close $fd + set fd [open /proc/sysrq-trigger "a"] + puts $fd "b" + close $fd + } else { + exec /bin/sh -c "sleep 5; reboot -f " & + } } } diff --git a/addon/www/index.html b/addon/www/index.html index 3fe3e1e..7a9df49 100644 --- a/addon/www/index.html +++ b/addon/www/index.html @@ -83,8 +83,23 @@ along with this program. If not, see . }); } + function installation_finished() { + if (installation_running) { + var version = installation_running; + $('[data-install-version="' + version + '"]').removeClass('loading'); + installation_running = ""; + msg = 'Firmware ' + version + ' successfully installed.' + display_message('success', msg, 6000000); + } + } + function update_install_log() { rest("GET", "/read_install_log", null, function(data) { + var regex_success = /INSTALL_FIRMWARE_SUCCESS\n/g; + if (regex_success.test(data)) { + installation_finished(); + } + data = data.replace(regex_success, ""); $('#log-content').html(data.replace(/\n/g, '
')); $('#modal-log').modal('refresh'); $('#log-content').scrollTop($('#log-content').prop("scrollHeight")); @@ -117,9 +132,7 @@ along with this program. If not, see . clear_message(); rest("POST", "/start_install_firmware", JSON.stringify({"version":version, "reboot":reboot, "dryrun":dryrun}), function(data) { - $('[data-install-version="' + installation_running + '"]').removeClass('loading'); - installation_running = ""; - display_message('success', 'Firmware ' + version + ' successfully installed.', 6000000); + installation_finished(); if (!reboot) { get_firmware_info(); } @@ -128,8 +141,8 @@ along with this program. If not, see . $('[data-install-version="' + installation_running + '"]').removeClass('loading'); installation_running = ""; //$('#modal-log').modal('hide'); - if (!reboot) { - get_firmware_info(); + if (installation_running) { + //get_firmware_info(); default_error_callback(xhr, ajaxOptions, thrownError); } } diff --git a/addon/www/rest.cgi b/addon/www/rest.cgi index f89ed66..f9c1921 100644 --- a/addon/www/rest.cgi +++ b/addon/www/rest.cgi @@ -75,7 +75,8 @@ proc process {} { } elseif {[lindex $path 1] == "delete_firmware_image"} { regexp {\"version\"\s*:\s*\"([\d\.]+)\"} $data match version if { [info exists version] && $version != "" } { - return "\"[rmupdate::delete_firmware_image $version]\"" + set res [rmupdate::delete_firmware_image $version] + return "\"${res}\"" } else { error "Invalid version: ${data}" } diff --git a/rmupdate b/rmupdate index e6c0a92..b580a0f 100755 --- a/rmupdate +++ b/rmupdate @@ -6,10 +6,19 @@ ADDON_DIR=/usr/local/addons/${ADDON_NAME} RCD_DIR=${CONFIG_DIR}/rc.d case "$1" in - ""|start|stop|restart) + ""|start) + if [ -e /lib.rmupdate ]; then + mount -o remount,rw / + rm -r /lib.rmupdate + mount -o remount,ro / + fi + echo "Done." + ;; + + stop|restart) echo "Nothing to do." ;; - + info) version=$(cat ${ADDON_DIR}/VERSION) echo "Info: ${ADDON_NAME} addon
" diff --git a/rmupdate.tar.gz b/rmupdate.tar.gz index a74fb9bd1820e9d3729600649f0894e2118647ce..95ef77970048840152a678a0004d33bcb8bf2eb4 100644 GIT binary patch delta 9328 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