--- /dev/null
+#
+# upgrade-engine
+#
+# Copyright (c) 2021 Samsung Electronics Co., Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the License);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+CMAKE_MINIMUM_REQUIRED(VERSION 2.82)
+PROJECT(upgrade C)
+
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/src/)
+
+IF("${CMAKE_BUILD_TYPE}" STREQUAL "")
+ SET(CMAKE_BUILD_TYPE "Release")
+ENDIF("${CMAKE_BUILD_TYPE}" STREQUAL "")
+MESSAGE("Build type: ${CMAKE_BUILD_TYPE}")
+
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS} -fPIC -fPIE")
+SET(CMAKE_C_FLAGS_DEBUG "-O0 -g")
+SET(CMAKE_C_FLAGS_RELEASE "-O2")
+
+FIND_PROGRAM(UNAME NAMES uname)
+IF("${ARCH}" STREQUAL "arm")
+ ADD_DEFINITIONS("-DTARGET")
+ MESSAGE("add -DTARGET")
+ENDIF("${ARCH}" STREQUAL "arm")
+
+ADD_DEFINITIONS("-DPREFIX=\"${PREFIX}\"")
+
+SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed -pie")
+
+ADD_SUBDIRECTORY(src/dmverity)
+ADD_SUBDIRECTORY(src/img-verifier)
+ADD_SUBDIRECTORY(src/upgrade-apply)
+ADD_SUBDIRECTORY(src/blkid-print)
+ADD_SUBDIRECTORY(data)
--- /dev/null
+Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. All rights reserved.\r
+\r
+ Apache License\r
+ Version 2.0, January 2004\r
+ http://www.apache.org/licenses/\r
+\r
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\r
+\r
+ 1. Definitions.\r
+\r
+ "License" shall mean the terms and conditions for use, reproduction,\r
+ and distribution as defined by Sections 1 through 9 of this document.\r
+\r
+ "Licensor" shall mean the copyright owner or entity authorized by\r
+ the copyright owner that is granting the License.\r
+\r
+ "Legal Entity" shall mean the union of the acting entity and all\r
+ other entities that control, are controlled by, or are under common\r
+ control with that entity. For the purposes of this definition,\r
+ "control" means (i) the power, direct or indirect, to cause the\r
+ direction or management of such entity, whether by contract or\r
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the\r
+ outstanding shares, or (iii) beneficial ownership of such entity.\r
+\r
+ "You" (or "Your") shall mean an individual or Legal Entity\r
+ exercising permissions granted by this License.\r
+\r
+ "Source" form shall mean the preferred form for making modifications,\r
+ including but not limited to software source code, documentation\r
+ source, and configuration files.\r
+\r
+ "Object" form shall mean any form resulting from mechanical\r
+ transformation or translation of a Source form, including but\r
+ not limited to compiled object code, generated documentation,\r
+ and conversions to other media types.\r
+\r
+ "Work" shall mean the work of authorship, whether in Source or\r
+ Object form, made available under the License, as indicated by a\r
+ copyright notice that is included in or attached to the work\r
+ (an example is provided in the Appendix below).\r
+\r
+ "Derivative Works" shall mean any work, whether in Source or Object\r
+ form, that is based on (or derived from) the Work and for which the\r
+ editorial revisions, annotations, elaborations, or other modifications\r
+ represent, as a whole, an original work of authorship. For the purposes\r
+ of this License, Derivative Works shall not include works that remain\r
+ separable from, or merely link (or bind by name) to the interfaces of,\r
+ the Work and Derivative Works thereof.\r
+\r
+ "Contribution" shall mean any work of authorship, including\r
+ the original version of the Work and any modifications or additions\r
+ to that Work or Derivative Works thereof, that is intentionally\r
+ submitted to Licensor for inclusion in the Work by the copyright owner\r
+ or by an individual or Legal Entity authorized to submit on behalf of\r
+ the copyright owner. For the purposes of this definition, "submitted"\r
+ means any form of electronic, verbal, or written communication sent\r
+ to the Licensor or its representatives, including but not limited to\r
+ communication on electronic mailing lists, source code control systems,\r
+ and issue tracking systems that are managed by, or on behalf of, the\r
+ Licensor for the purpose of discussing and improving the Work, but\r
+ excluding communication that is conspicuously marked or otherwise\r
+ designated in writing by the copyright owner as "Not a Contribution."\r
+\r
+ "Contributor" shall mean Licensor and any individual or Legal Entity\r
+ on behalf of whom a Contribution has been received by Licensor and\r
+ subsequently incorporated within the Work.\r
+\r
+ 2. Grant of Copyright License. Subject to the terms and conditions of\r
+ this License, each Contributor hereby grants to You a perpetual,\r
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r
+ copyright license to reproduce, prepare Derivative Works of,\r
+ publicly display, publicly perform, sublicense, and distribute the\r
+ Work and such Derivative Works in Source or Object form.\r
+\r
+ 3. Grant of Patent License. Subject to the terms and conditions of\r
+ this License, each Contributor hereby grants to You a perpetual,\r
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r
+ (except as stated in this section) patent license to make, have made,\r
+ use, offer to sell, sell, import, and otherwise transfer the Work,\r
+ where such license applies only to those patent claims licensable\r
+ by such Contributor that are necessarily infringed by their\r
+ Contribution(s) alone or by combination of their Contribution(s)\r
+ with the Work to which such Contribution(s) was submitted. If You\r
+ institute patent litigation against any entity (including a\r
+ cross-claim or counterclaim in a lawsuit) alleging that the Work\r
+ or a Contribution incorporated within the Work constitutes direct\r
+ or contributory patent infringement, then any patent licenses\r
+ granted to You under this License for that Work shall terminate\r
+ as of the date such litigation is filed.\r
+\r
+ 4. Redistribution. You may reproduce and distribute copies of the\r
+ Work or Derivative Works thereof in any medium, with or without\r
+ modifications, and in Source or Object form, provided that You\r
+ meet the following conditions:\r
+\r
+ (a) You must give any other recipients of the Work or\r
+ Derivative Works a copy of this License; and\r
+\r
+ (b) You must cause any modified files to carry prominent notices\r
+ stating that You changed the files; and\r
+\r
+ (c) You must retain, in the Source form of any Derivative Works\r
+ that You distribute, all copyright, patent, trademark, and\r
+ attribution notices from the Source form of the Work,\r
+ excluding those notices that do not pertain to any part of\r
+ the Derivative Works; and\r
+\r
+ (d) If the Work includes a "NOTICE" text file as part of its\r
+ distribution, then any Derivative Works that You distribute must\r
+ include a readable copy of the attribution notices contained\r
+ within such NOTICE file, excluding those notices that do not\r
+ pertain to any part of the Derivative Works, in at least one\r
+ of the following places: within a NOTICE text file distributed\r
+ as part of the Derivative Works; within the Source form or\r
+ documentation, if provided along with the Derivative Works; or,\r
+ within a display generated by the Derivative Works, if and\r
+ wherever such third-party notices normally appear. The contents\r
+ of the NOTICE file are for informational purposes only and\r
+ do not modify the License. You may add Your own attribution\r
+ notices within Derivative Works that You distribute, alongside\r
+ or as an addendum to the NOTICE text from the Work, provided\r
+ that such additional attribution notices cannot be construed\r
+ as modifying the License.\r
+\r
+ You may add Your own copyright statement to Your modifications and\r
+ may provide additional or different license terms and conditions\r
+ for use, reproduction, or distribution of Your modifications, or\r
+ for any such Derivative Works as a whole, provided Your use,\r
+ reproduction, and distribution of the Work otherwise complies with\r
+ the conditions stated in this License.\r
+\r
+ 5. Submission of Contributions. Unless You explicitly state otherwise,\r
+ any Contribution intentionally submitted for inclusion in the Work\r
+ by You to the Licensor shall be under the terms and conditions of\r
+ this License, without any additional terms or conditions.\r
+ Notwithstanding the above, nothing herein shall supersede or modify\r
+ the terms of any separate license agreement you may have executed\r
+ with Licensor regarding such Contributions.\r
+\r
+ 6. Trademarks. This License does not grant permission to use the trade\r
+ names, trademarks, service marks, or product names of the Licensor,\r
+ except as required for reasonable and customary use in describing the\r
+ origin of the Work and reproducing the content of the NOTICE file.\r
+\r
+ 7. Disclaimer of Warranty. Unless required by applicable law or\r
+ agreed to in writing, Licensor provides the Work (and each\r
+ Contributor provides its Contributions) on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\r
+ implied, including, without limitation, any warranties or conditions\r
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\r
+ PARTICULAR PURPOSE. You are solely responsible for determining the\r
+ appropriateness of using or redistributing the Work and assume any\r
+ risks associated with Your exercise of permissions under this License.\r
+\r
+ 8. Limitation of Liability. In no event and under no legal theory,\r
+ whether in tort (including negligence), contract, or otherwise,\r
+ unless required by applicable law (such as deliberate and grossly\r
+ negligent acts) or agreed to in writing, shall any Contributor be\r
+ liable to You for damages, including any direct, indirect, special,\r
+ incidental, or consequential damages of any character arising as a\r
+ result of this License or out of the use or inability to use the\r
+ Work (including but not limited to damages for loss of goodwill,\r
+ work stoppage, computer failure or malfunction, or any and all\r
+ other commercial damages or losses), even if such Contributor\r
+ has been advised of the possibility of such damages.\r
+\r
+ 9. Accepting Warranty or Additional Liability. While redistributing\r
+ the Work or Derivative Works thereof, You may choose to offer,\r
+ and charge a fee for, acceptance of support, warranty, indemnity,\r
+ or other liability obligations and/or rights consistent with this\r
+ License. However, in accepting such obligations, You may act only\r
+ on Your own behalf and on Your sole responsibility, not on behalf\r
+ of any other Contributor, and only if You agree to indemnify,\r
+ defend, and hold each Contributor harmless for any liability\r
+ incurred by, or claims asserted against, such Contributor by reason\r
+ of your accepting any such warranty or additional liability.\r
+\r
+ END OF TERMS AND CONDITIONS\r
+\r
+ APPENDIX: How to apply the Apache License to your work.\r
+\r
+ To apply the Apache License to your work, attach the following\r
+ boilerplate notice, with the fields enclosed by brackets "[]"\r
+ replaced with your own identifying information. (Don't include\r
+ the brackets!) The text should be enclosed in the appropriate\r
+ comment syntax for the file format. We also recommend that a\r
+ file or class name and description of purpose be included on the\r
+ same "printed page" as the copyright notice for easier\r
+ identification within third-party archives.\r
+\r
+ Copyright [yyyy] [name of copyright owner]\r
+\r
+ Licensed under the Apache License, Version 2.0 (the "License");\r
+ you may not use this file except in compliance with the License.\r
+ You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
--- /dev/null
+# Tizen System Update
+
+This repository contains following projects kept separately till Tizen 7.0:
+
+ - platorm/core/system/tota-ua, the "Tizen OTA Upgrade Agent", utility to
+ apply deltas
+
+ - platform/core/system/system-rw-update, systemd units to handle Read-Write
+ stage of upgrade
+
+ - platform/core/system/libtota, library handling legacy upgrade types (delta_fs)
+
+
+izen System Update is the one of Tizen feature. This implements
+the functionality of firmware update base on OTA mechanism.
+
+* Notice of Limitations
+ This functionality does NOT support official signing & validation method.
+This means it requires proper mechanism for binary validation offered by each
+manufacturers.
--- /dev/null
+# ---- Target contents ----------------------------------------------------- #
+WITHLIBS="
+${CMAKE_INSTALL_LIBDIR}/libcrypto.so.1.1
+${CMAKE_INSTALL_LIBDIR}/libtota.so.1.0.0
+/usr/bin/verity_handler
+/usr/sbin/img-verifier
+/bin/sed
+/bin/findmnt
+/bin/dd
+/bin/grep
+/bin/blkid-print
+"
+
+DIRECTORIES="
+@IMG_VERIFIER_ROOT_CA_DIR@
+"
+
+VERBATIMS="
+@IMG_VERIFIER_ROOT_CA_DIR@/*.pem
+/usr/bin/clone_partitions.sh
+/usr/libexec/upgrade-support/upgrade-common.inc
+/usr/libexec/upgrade-support/upgrade-prepare-partitions.sh
+"
--- /dev/null
+configure_file(40-upgrade.list.in 40-upgrade.list)
+install(FILES 40-upgrade.list DESTINATION ${UPGRADE_INITRD_LIST_DIR})
--- /dev/null
+%define upgrade_support_dir %{_libexecdir}/upgrade-support
+
+Name: upgrade-engine
+Summary: Upgrade engine for Tizen
+Version: 7.5.0
+Release: 0
+Group: System
+License: Apache-2.0
+Source0: %{name}-%{version}.tar.gz
+
+BuildRequires: cmake
+BuildRequires: ca-certificates-devel
+BuildRequires: pkgconfig(liblzma-tool)
+BuildRequires: libbrotli-devel
+BuildRequires: pkgconfig(openssl1.1)
+BuildRequires: pkgconfig(libtzplatform-config)
+BuildRequires: pkgconfig(dlog)
+Buildrequires: pkgconfig(glib-2.0)
+BuildRequires: pkgconfig(hal-api-device)
+BuildRequires: libtar-devel
+
+Requires: tar
+Requires: gzip
+
+%description
+Update engine for updating Tizen platform images using delta files
+generated by upgrade-tools.
+
+%prep
+%setup -q
+
+%build
+export LDFLAGS+="-Wl,--rpath=%{_libdir} -Wl,--as-needed"
+LDFLAGS="$LDFLAGS"
+
+%define system_bus_services_dir /usr/share/dbus-1/system-services
+%define systemd_dbus_conf_dir /etc/dbus-1/system.d
+
+# You must set CA directory in the RO section to prevent manipulation
+%define img_verifier_root_ca_dir %{TZ_SYS_RO_CA_DIR}/img-verifier
+%define upgrade_initrd_list_dir %{_datadir}/initrd-recovery/initrd.list.d
+
+%cmake \
+ -DCMAKE_INSTALL_PREFIX=%{_prefix} \
+ -DIMG_VERIFIER_ROOT_CA_DIR=%{img_verifier_root_ca_dir} \
+ -DUPGRADE_INITRD_LIST_DIR=%{upgrade_initrd_list_dir} \
+ .
+
+make %{?jobs:-j%jobs}
+
+%install
+%make_install
+
+mkdir -p %{buildroot}%{upgrade_support_dir}
+install -m 755 scripts/upgrade-support/* %{buildroot}%{upgrade_support_dir}
+
+%define fota_dir /opt/usr/data/fota
+mkdir -p %{buildroot}%{fota_dir}
+mkdir -p %{buildroot}%{img_verifier_root_ca_dir}
+mkdir -p %{buildroot}%{_unitdir}/multi-user.target.wants
+
+install -m 644 scripts/clone_partitions/clone_partitions.service %{buildroot}%{_unitdir}
+ln -s ../clone_partitions.service %{buildroot}%{_unitdir}/multi-user.target.wants/
+install -m 775 scripts/clone_partitions/clone_partitions.sh %{buildroot}%{_bindir}
+
+mkdir -p %{buildroot}%{_unitdir}/recovery.service.wants
+install -m 644 scripts/clone_partitions/clone_partitions_recovery.service %{buildroot}%{_unitdir}
+ln -s ../clone_partitions_recovery.service %{buildroot}%{_unitdir}/recovery.service.wants/
+
+%files
+%license LICENSE
+%manifest upgrade.manifest
+%doc README
+%attr(775, root, system_fw) %{fota_dir}
+%attr(700,-,-) %{upgrade_initrd_list_dir}/40-upgrade.list
+%defattr(-,root,root,-)
+%{_bindir}/clone_partitions.sh
+%attr(644, root, root) %{_unitdir}/clone_partitions.service
+%{_unitdir}/multi-user.target.wants/clone_partitions.service
+%attr(644, root, root) %{_unitdir}/clone_partitions_recovery.service
+%{_unitdir}/recovery.service.wants/clone_partitions_recovery.service
+%{upgrade_support_dir}/upgrade-trigger.sh
+%{upgrade_support_dir}/upgrade-partial.sh
+%{upgrade_support_dir}/upgrade-prepare-partitions.sh
+%{upgrade_support_dir}/upgrade-fota.sh
+%{upgrade_support_dir}/upgrade-legacy.sh
+%{upgrade_support_dir}/upgrade-common.inc
+%{_bindir}/upgrade-apply
+%{_bindir}/verity_handler
+%{_bindir}/blkid-print
+# Image verifier
+%{_sbindir}/img-verifier
+%attr(755,root,root) %{img_verifier_root_ca_dir}
--- /dev/null
+[Unit]
+Description=Clone partitions
+After=system-delayed-target-done.service
+
+[Service]
+Type=simple
+SmackProcessLabel=System
+ExecStart=/usr/bin/clone_partitions.sh background
+RemainAfterExit=true
+Nice=19
+IOSchedulingClass=idle
+IOSchedulingPriority=7
+
+[Install]
+WantedBy=multi-user.target
--- /dev/null
+#!/bin/bash
+PATH=/bin:/usr/bin:/sbin:/usr/sbin
+FLOCK_PATH="/var/lock/clone_partitions.lock"
+
+SCRIPT_NAME="clone_partitions.sh"
+
+#------------------------------------------------
+# critical_log msg [file]
+#------------------------------------------------
+critical_log() {
+ # log format: [script_name][tag]actual_log
+ LOG="[${SCRIPT_NAME}]$1"
+ dlogsend -k "$LOG"
+ if [ "$2" != "" ]; then
+ echo "$LOG" >> "$2"
+ fi
+ echo "$LOG"
+
+ return 0;
+}
+
+#------------------------------------------------
+# log msg [file]
+#------------------------------------------------
+log() {
+ # log format: [script_name][tag]actual_log
+ LOG="[${SCRIPT_NAME}]$1"
+ if [ "$2" != "" ]; then
+ echo "$LOG" >> "$2"
+ fi
+ echo "$LOG"
+
+ return 0;
+}
+
+do_clone() {
+ . /usr/libexec/upgrade-support/upgrade-common.inc
+
+ check_ab_partition_scheme
+ check_used_block_device
+ load_background_copy_list
+ if background_copy; then
+ device_board_set_partition_ab_cloned
+ log "[Info] Partitions have been cloned"
+ return 0
+ else
+ critical_log "[Error] Partitions have not been cloned"
+ return 1
+ fi
+}
+
+set_partition_status_ok(){
+ CURRENT_AB=$(device_board_get_current_partition)
+
+ if [ "$CURRENT_AB" == "a" ]; then
+ OPPOSITE_AB="b"
+ elif [ "$CURRENT_AB" == "b" ]; then
+ OPPOSITE_AB="a"
+ else
+ exit 1
+ fi
+
+ device_board_set_partition_status $OPPOSITE_AB "ok"
+}
+
+set -o errexit
+trap 'echo "Aborting due to errexit on ${0##*/}:$LINENO. Exit code: $?" >&2' ERR
+set -o errtrace -e -o pipefail
+
+if [ ! -z "${UPGRADE_DEBUG}" ]; then
+ set -x
+fi
+
+(
+flock 9
+case $1 in
+ "recovery")
+ /bin/device_board_clear_partition_ab_cloned
+ do_clone
+ set_partition_status_ok
+ ;;
+ "background")
+ # wait for 60 seconds to wait for bootup
+ sleep 60
+
+ BOOTMODE=$(device_board_get_boot_mode)
+ RET_BOOT_MODE=$?
+ if [ ${RET_BOOT_MODE} -ne 0 ]; then
+ critical_log "[Error] Cannot get boot mode: ${RET_BOOT_MODE}"
+ exit 1
+ fi
+
+ if [ "${BOOTMODE}" != "normal" ]; then
+ log "[Info] bootmode is not \"normal\": ${BOOTMODE}"
+ exit 0
+ fi
+
+ if [ "$(device_board_get_partition_ab_cloned)" -eq "1" ]; then
+ log "[Info] Partitions already cloned"
+ else
+ do_clone
+ fi
+ ;;
+ *)
+ if [ "$(device_board_get_partition_ab_cloned)" -eq "1" ]; then
+ log "[Info] Partitions already cloned"
+ else
+ do_clone
+ fi
+ ;;
+esac
+) 9> "$FLOCK_PATH"
--- /dev/null
+[Unit]
+Description=Clone partitions in the recovery mode
+DefaultDependencies=no
+After=sysinit.target
+Before=recovery.service
+ConditionKernelCommandLine=bootmode=recovery
+
+[Service]
+Type=oneshot
+SmackProcessLabel=System
+ExecStart=/usr/bin/clone_partitions.sh recovery
+RemainAfterExit=true
+
+[Install]
+WantedBy=recovery.service
--- /dev/null
+IMG_VERIFIER="/usr/sbin/img-verifier"
+STATUS_DIR="/opt/data/update"
+HAL_UPGRADE_CFG_DIR="/hal/etc/upgrade/"
+HAL_PART_MAP_FILE="label_map.list"
+HAL_PART_LIST_FILE="background_copy.list"
+CONFIG_FILE="update.cfg"
+SET_UPGRADE_STATUS="/usr/bin/device_board_set_upgrade_status"
+DO_RW_UPDATE_FILE="$FOTA_UPDATE_PREFIX/opt/.do_rw_update"
+TRUE=0
+FALSE=1
+
+#------------------------------------------------
+# critical_log msg [file]
+#------------------------------------------------
+critical_log() {
+ # log format: [script_name][tag]actual_log
+ LOG="[${SCRIPT_NAME}]$1"
+ dlogsend -k "$LOG"
+ if [ "$2" != "" ]; then
+ echo "$LOG" >> "$2"
+ fi
+ echo "$LOG"
+
+ return 0
+}
+
+critical_flog() {
+ critical_log "$1" "$LOG_FILE"
+}
+
+#------------------------------------------------
+# log msg [file]
+#------------------------------------------------
+log() {
+ # log format: [script_name][tag]actual_log
+ LOG="[${SCRIPT_NAME}]$1"
+ if [ "$2" != "" ]; then
+ echo "$LOG" >> "$2"
+ fi
+ echo "$LOG"
+
+ return 0
+}
+
+flog() {
+ log "$1" "$LOG_FILE"
+}
+
+untrap() {
+ trap '' ERR
+}
+
+retrap() {
+ trap 'echo "Aborting due to errexit on ${0##*/}:$LINENO. Exit code: $?" >&2' ERR
+}
+
+verify_file() {
+ FILE_NAME="$1"
+ FILE_PATH="$FOTA_DIR/$FILE_NAME"
+ VALID_CHECKSUM=$(awk "/$FILE_NAME/ {print \$1}" "$FOTA_DIR/checksum.SHA1")
+ if [ "$VALID_CHECKSUM" == "" ]; then
+ echo "[Error] No $FILE_NAME in checksum.SHA1"
+ return $FALSE
+ fi
+
+ if ! echo "$VALID_CHECKSUM $FILE_PATH" | sha1sum --check --status; then
+ echo "[Error] Checksum for file $FILE_NAME is invalid"
+ return $FALSE
+ fi
+
+ echo "[Info] Checksum of $FILE_NAME is OK"
+ return $TRUE
+}
+
+unpack_file() {
+ ARCHIVE_NAME="$1"
+ FILE_NAME="$2"
+ tar xpf "${ARCHIVE_NAME}" -C "${FOTA_DIR}" "${FILE_NAME}" 2> /dev/null
+ if [ ! -e "${FOTA_DIR}/${FILE_NAME}" ]; then
+ flog "[Error] There is no ${FILE_NAME}"
+ fi
+
+ if ! verify_file "$FILE_NAME"; then
+ exit_error
+ fi
+}
+
+verify_img() {
+ DELTA_FILE="$1"
+ if [ -e "$IMG_VERIFIER" ]; then
+ log "[Info] Package verifier is found. Verify $DELTA_FILE" "$LOG_FILE"
+ if ! "$IMG_VERIFIER" -i "$DELTA_FILE" -l "/opt/var/log/last_iv.log"; then
+ log "[Error] Update package verification FAILED..." "$LOG_FILE"
+ echo 5 > "$STATUS_DIR"/result
+ exit_error
+ else
+ log "[Info] Update package verification PASSED!" "$LOG_FILE"
+ fi
+ fi
+}
+
+check_ab_partition_scheme() {
+ CURRENT_AB="$(/bin/sed -E 's|.*(partition_ab=)([a-b]).*|\2|' /proc/cmdline)"
+ if [ "$CURRENT_AB" != "a" ] && [ "$CURRENT_AB" != "b" ]; then
+ flog "[Info] There is no A/B partition scheme"
+ exit_error
+ fi
+ NEXT_AB="b"
+ if [ "$CURRENT_AB" == "b" ]; then
+ NEXT_AB="a"
+ fi
+}
+
+check_used_block_device() {
+ local MAPPER_MATCH_REGEX='.*/dev/mapper.*'
+ ROOTFS_DEVICE="$(findmnt / -no SOURCE)"
+ MAPPER_DEVICE=""
+ if [[ "${ROOTFS_DEVICE}" =~ ${MAPPER_MATCH_REGEX} ]]; then
+ MAPPER_DEVICE="${ROOTFS_DEVICE}"
+ DM_NAME=$(basename "${MAPPER_DEVICE}")
+ DM_N=$(dmsetup ls -o blkdevname | grep "${DM_NAME}" | sed -e 's/^.*(\([^)]\+\))/\1/')
+ ROOTFS_DEVICE="/dev/$(ls /sys/class/block/${DM_N}/slaves/)"
+ fi
+
+ if ! EMMC_DEVICE="/dev/$(/bin/lsblk -ndo pkname "${ROOTFS_DEVICE}")" ||\
+ [ "$EMMC_DEVICE" == "/dev/" ]; then
+ flog "[Error] Unable to find used block device: $EMMC_DEVICE, for /"
+ exit_error
+ fi
+}
+
+background_copy() {
+ flog "[Info] Background copying A|B partitions for update..."
+ for partition_name in ${PARTITION_LIST}; do
+ # echo is there to suspend abort when partition will not be found e.g. hal
+ if ! CURRENT_PARTITION="$(/usr/bin/blkid-print "$EMMC_DEVICE" "$partition_name" "$CURRENT_AB" |\
+ sed -E 's|(part_nr: [0-9]+ )\((.*)\): (.*)|\3|' || echo "__FALSE__")" || \
+ [ "$CURRENT_PARTITION" == "__FALSE__" ]; then
+ flog "[Error] Unable to find: $partition_name current partition on $EMMC_DEVICE device on $CURRENT_AB slot"
+ check_optional_partition "$partition_name" 1
+ continue
+ fi
+ if ! NEXT_PARTITION="$(/usr/bin/blkid-print "$EMMC_DEVICE" "$partition_name" "$NEXT_AB" |\
+ sed -E 's|(part_nr: [0-9]+ )\((.*)\): (.*)|\3|')"; then
+ flog "[Error] Unable to find: $partition_name next partition on $EMMC_DEVICE device on $CURRENT_AB slot"
+ check_optional_partition "$partition_name" 1
+ continue
+ fi
+ if [ "$CURRENT_PARTITION" == "" ] || [ "$NEXT_PARTITION" = "" ]; then
+ flog "[Error] current: $CURRENT_PARTITION or next: $NEXT_PARTITION partition is empty on $EMMC_DEVICE device"
+ return $FALSE
+ fi
+ if [ "$CURRENT_PARTITION" == "$NEXT_PARTITION" ]; then
+ flog "[Info] $partition_name partition current and next are the same: $CURRENT_PARTITION on $EMMC_DEVICE device"
+ continue
+ fi
+ flog "[Info] Background copy $partition_name, from: $CURRENT_PARTITION to $NEXT_PARTITION"
+ /bin/dd if="$CURRENT_PARTITION" of="$NEXT_PARTITION" bs=4096
+ flog "[Info] Finished background copy $partition_name from $CURRENT_PARTITION to $NEXT_PARTITION"
+
+ if [ "$partition_name" == "rootfs" ]; then
+ flog "[Info] Checksum verification for $partition_name"
+ CUR_SHA1=$(sha1sum "$CURRENT_PARTITION" | awk '{print $1}')
+ NEXT_SHA1=$(sha1sum "$NEXT_PARTITION" | awk '{print $1}')
+ if [ "$CUR_SHA1" == "$NEXT_SHA1" ]; then
+ flog "[Info] Partition $partition_name was cloned correctly"
+ else
+ flog "[Error] Checksums are different: $CUR_SHA1 != $NEXT_SHA1"
+ exit_error
+ fi
+ fi
+ done
+ return $TRUE
+}
+
+load_background_copy_list() {
+ PARTITION_LIST=$(sed -e '/^#/d' -e '/^$/d' "${HAL_UPGRADE_CFG_DIR}/${HAL_PART_LIST_FILE}")
+}
+
+upgrade_images() {
+ DELTA_TAR="$1"
+ flog "[Info] Flash images for update..."
+
+ LABEL_MAP_PATH=${HAL_UPGRADE_CFG_DIR}/${HAL_}
+ while read LABEL PARTLABEL; do
+ declare "LABEL_MAP_${LABEL}=${PARTLABEL}"
+ done < <(sed -e '/^#/d' -e '/^$/d' "${LABEL_MAP_PATH}/${HAL_PART_MAP_FILE}")
+
+ # _OFFSET _SIZE _HASH1 _HASH2
+ while read -r LABEL_NAME DELTA_NAME TYPE DEV OFFSET OLD_SIZE NEW_SIZE OLD_SHA NEW_SHA
+ do
+ local LABEL_NAME="$(echo "$LABEL_NAME" | /bin/awk '{print tolower($0)}')"
+
+ # Translate LABEL to PARTLABEL using label_map.list
+ local TMP="LABEL_MAP_${LABEL_NAME}"
+ local PART_NAME=${!TMP}
+ if [ -z "${PART_NAME}" ]; then
+ PART_NAME=${LABEL_NAME}
+ fi
+
+ TYPE_S=(${TYPE//:/ })
+ # We support only FULL_IMAGE and DELTA_IMAGE
+ if [[ ! "${TYPE_S[0]}" =~ ^(FULL_IMAGE|DELTA_IMAGE)$ ]]; then
+ PARTS_NAME_TO_UPDATE+=( "$PART_NAME:$TYPE" )
+ continue
+ fi
+
+ if ! /bin/tar tf "$DELTA_TAR" "$DELTA_NAME"; then
+ flog "[Info] There is no delta $DELTA_NAME for label $LABEL_NAME from part $PART_NAME"
+ continue
+ fi
+
+ local NEXT_PARTITION="$(/usr/bin/blkid-print "$EMMC_DEVICE" "$PART_NAME" "$NEXT_AB" |\
+ /bin/sed -E 's|(part_nr: [0-9]+ )\((.*)\): (.*)|\3|')"
+ local CURR_PARTITION="$(/usr/bin/blkid-print "$EMMC_DEVICE" "$PART_NAME" "$CURRENT_AB" |\
+ /bin/sed -E 's|(part_nr: [0-9]+ )\((.*)\): (.*)|\3|')"
+
+ flog "[Info] Flashing $DELTA_NAME... to $NEXT_PARTITION"
+
+ local UP_RES
+ untrap
+ case "${TYPE_S[0]}" in
+ FULL_IMAGE)
+ "$FOTA_DIR/upgrade-apply" --archive "$DELTA_TAR" \
+ --dest "$NEXT_PARTITION" \
+ --dest-size "$NEW_SIZE" \
+ --archive-file "$DELTA_NAME" \
+ --kind raw \
+ --dest-sha1 "$NEW_SHA" && UP_RES=$? || UP_RES=$?
+ ;;
+ DELTA_IMAGE)
+ "$FOTA_DIR/upgrade-apply" --archive "$DELTA_TAR" \
+ --dest "$NEXT_PARTITION" \
+ --dest-size "$NEW_SIZE" \
+ --archive-file "$DELTA_NAME" \
+ --kind ss_brotli_patch \
+ --patch-orig "$CURR_PARTITION" \
+ --dest-sha1 "$NEW_SHA" && UP_RES=$? || UP_RES=$?
+ ;;
+ esac
+ retrap
+ case $UP_RES in
+ 0)
+ flog "[Info] Finished flashing $DELTA_NAME... to $NEXT_PARTITION"
+ ;;
+ *)
+ flog "[Info] Flashing error $DELTA_NAME... to $NEXT_PARTITION"
+ return $FALSE
+ ;;
+ esac
+ done < "$FOTA_DIR/$CONFIG_FILE"
+}
+
+run_setup_script() {
+ DELTA_TAR="$1"
+ SETUP_SCRIPT_NAME=setup.sh
+ SETUP_SCRIPT_PATH=$FOTA_DIR/$SETUP_SCRIPT_NAME
+
+ /bin/tar xvfp "$DELTA_TAR" -C "$FOTA_DIR" "$SETUP_SCRIPT_NAME" 2>/dev/null ||\
+ (log "[Info] setup.sh does not exist, skipping." "$LOG_FILE" && return)
+
+ if [ -e "$SETUP_SCRIPT_PATH" ]; then
+ /bin/sh "$SETUP_SCRIPT_PATH"
+ rm "$SETUP_SCRIPT_PATH"
+ fi
+}
+
+write_version_info() {
+ VERSION_FILE="$1"
+ if [ -f "$VERSION_FILE" ]; then
+ return
+ fi
+ OLD_VER=$(/bin/grep platform.version\" /etc/config/model-config.xml \
+ | sed -e 's/.*>\(.*\)<.*/\1/' | head -1)
+ i=0
+ VER=(0 0 0 0)
+ for ENT in $(echo "$OLD_VER" | tr "." "\n"); do
+ VER[$i]=$ENT
+ ((i++))
+ done
+ CVT_VER=${VER[0]}.${VER[1]}.${VER[2]}.${VER[3]}
+
+ echo "OLD_VER=$CVT_VER" > "$VERSION_FILE"
+}
+
+set_upgrade_status() {
+ VALUE="$1"
+ ${SET_UPGRADE_STATUS} ${VALUE}
+ if [ $? -eq 0 ]; then
+ critical_log "set_upgrade_status success: ${VALUE}" "$LOG_FILE"
+ else
+ critical_log "set_upgrade_status failed: ${VALUE}" "$LOG_FILE"
+ fi
+}
+
+exit_error() {
+ set_upgrade_status -1
+ exit 1
+}
+
+mount_partition() {
+ local GPT_LABEL="$1"
+ local DST="$2"
+ local MOUNT_OPTIONS="$3"
+ local SRC
+ if ! SRC="$(/usr/bin/blkid-print "$EMMC_DEVICE" "$GPT_LABEL" "$NEXT_AB" |\
+ /bin/sed -E 's|(part_nr: [0-9]+ )\((.*)\): (.*)|\3|')"; then
+ flog "[Error] Unable to find $GPT_LABEL partition on $EMMC_DEVICE device for $NEXT_AB slot"
+ return 1
+ fi
+ local MOUNTED="$(/bin/findmnt -n -o TARGET "$SRC" | /bin/head -n 1 || echo "")"
+ if [ "$MOUNTED" != "" ]; then
+ MOUNT_OPTIONS="bind,${MOUNT_OPTIONS}"
+ SRC="$MOUNTED"
+ fi
+ if ! /bin/mkdir -p "$DST"; then
+ flog "[Error] Unable to mkdir for mount destination: $DST"
+ return 1
+ fi
+ if ! /bin/mount -o "$MOUNT_OPTIONS" "$SRC" "$DST"; then
+ flog "[Error] Unable to mount $SRC to $DST, options: $MOUNT_OPTIONS"
+ return 1
+ fi
+ CLEANUP_PARTITION+=("$DST")
+ return 0
+}
+
+cleanup_partitions() {
+ # umount in reverse order
+ for (( idx=${#CLEANUP_PARTITION[@]}-1 ; idx>=0 ; idx-- )) ; do
+ local DST="${CLEANUP_PARTITION[idx]}"
+ if [ "$DST" == "" ]; then
+ flog "[Error] Partition for cleanup is empty, idx: $idx, partition count: ${#CLEANUP_PARTITION[@]}"
+ continue
+ fi
+ if ! /bin/umount "$DST"; then
+ flog "[Error] Unable to umount $DST"
+ fi
+
+ done
+ CLEANUP_PARTITION=()
+}
--- /dev/null
+#!/bin/bash
+set -o errexit
+trap 'echo "Aborting due to errexit on ${0##*/}:$LINENO. Exit code: $?" >&2' ERR
+set -o errtrace -e -o pipefail
+
+
+if [ ! -z "${UPGRADE_DEBUG}" ]; then
+ set -x
+fi
+
+PATH=/bin:/usr/bin:/sbin:/usr/sbin
+FOTA_DIR="/opt/usr/data/fota"
+
+. "$FOTA_DIR"/upgrade-common.inc
+
+FOTA_UPDATE_PREFIX="/run/upgrade-sysroot"
+VERSION_FILE="$FOTA_UPDATE_PREFIX/opt/etc/version"
+
+SCRIPT_NAME="upgrade-fota.sh"
+LOG_FILE="/tmp/update-fota.log"
+
+cleanup() {
+ cleanup_partitions
+ cleanup_files
+}
+
+cleanup_files() {
+ rm -f -- "$FOTA_DIR"/upgrade-common.inc
+ rm -f -- "$FOTA_DIR/$SCRIPT_NAME"
+ rm -f -- "$FOTA_DIR/$CONFIG_FILE"
+ rm -f -- "$FOTA_DIR/upgrade-trigger.sh"
+ rm -f -- "$FOTA_DIR/checksum.SHA1"
+}
+
+mount() {
+ # We need to mount the system-data partition to update /opt/etc/version and
+ # create a .do_rw_update file
+ mkdir -p "$FOTA_UPDATE_PREFIX/opt"
+
+ if ! mount_partition system-data "$FOTA_UPDATE_PREFIX/opt" "rw"; then
+ critical_flog "[Error] Unable to mount opt"
+ return 1
+ fi
+ return 0
+}
+
+reboot_to_fota() {
+ flog "[Info] Write paths..."
+ touch "$DO_RW_UPDATE_FILE"
+ flog "[Info] calling sync"
+
+ flog "[Info] Switching board partition from $CURRENT_AB, to $NEXT_AB"
+ if ! device_board_switch_partition $NEXT_AB; then
+ critical_flog "[Error] Failed to switch board slot to $NEXT_AB"
+ exit 1
+ fi
+ cleanup_files
+ /bin/sync
+ flog "[Info] Rebooting to fota"
+ if ! /sbin/reboot fota; then
+ critical_flog "[Error] Failed to reboot fota"
+ exit 1
+ fi
+}
+
+prepare_fota_update_dir() {
+ mkdir -p ${FOTA_UPDATE_PREFIX}
+}
+
+prepare_fota_update_dir
+check_ab_partition_scheme
+check_used_block_device
+if ! mount; then
+ cleanup
+ exit 1
+fi
+if ! write_version_info "$VERSION_FILE"; then
+ cleanup
+ exit 1
+fi
+
+if ! reboot_to_fota; then
+ cleanup
+ exit 1
+fi
+cleanup
+
+critical_log "RO update: $SCRIPT_NAME success" "$LOG_FILE"
--- /dev/null
+#!/bin/bash
+# This script handles non-A/B upgrade, with flashing RO & RW after reboot in 'bootmode=fota'
+set -o errexit
+trap 'echo "Aborting due to errexit on ${0##*/}:$LINENO. Exit code: $?" >&2' ERR
+set -o errtrace -e -o pipefail
+
+PATH=/bin:/usr/bin:/sbin:/usr/sbin
+FOTA_DIR="/opt/usr/data/fota"
+STATUS_DIR="/opt/data/update"
+DOWNLOAD_DELTA=$1
+
+. "$FOTA_DIR"/upgrade-common.inc
+
+SCRIPT_NAME="upgrade-trigger.sh"
+LOG_FILE="/tmp/upgrade-trigger.log"
+
+
+if [ "$#" != "1" ] || [ ! -f "$1" ]; then
+ log "[Error] Usage: $0 path_to_delta.tar[.gz]" "$LOG_FILE"
+ exit 1
+fi
+
+if tar ztf "$DOWNLOAD_DELTA" 1>/dev/null 2>&1; then
+ DELTA_TAR="$FOTA_DIR/delta.tar.gz"
+ DELTA_TAR_GZ="TRUE"
+elif tar tf "$DOWNLOAD_DELTA" 1>/dev/null 2>&1; then
+ DELTA_TAR="$FOTA_DIR/delta.tar"
+else
+ log "[Error] Usage $0 path_to_delta.tar[.gz]" "$LOG_FILE"
+ exit 1
+fi
+
+log "[Info] Using <$DELTA_TAR> delta file." "$LOG_FILE"
+
+flash_pre_image() {
+ log "[Info] Flash images for update..." "$LOG_FILE"
+ CONFIG_FILE="update.cfg"
+
+ /bin/tar xvfp "$DELTA_TAR" -C "$FOTA_DIR" "$CONFIG_FILE"
+ if [ ! -e "$FOTA_DIR/$CONFIG_FILE" ]; then
+ critical_log "[Error] There is no $CONFIG_FILE" "$LOG_FILE"
+ return
+ fi
+
+ while read -r PART_NAME DELTA_NAME TYPE DEV _OFFSET _SIZE _HASH1 _HASH2
+ do
+ if [ "$TYPE" = "FULL_IMAGE:BEFORE_BOOT_FOTA" ]; then
+ if ! /bin/tar tf "$DELTA_TAR" "$DELTA_NAME"; then
+ log "[Info] There is no delta for $PART_NAME" "$LOG_FILE"
+ continue
+ fi
+
+ EMMC_DEVICE="/dev/mmcblk0"
+ DEV_NUM=$(/sbin/partx -s "$EMMC_DEVICE" | sed "\|$PART_NAME|!d")
+ if [ "$DEV_NUM" != "" ]; then
+ DEV_NUM="$(echo "$DEV_NUM"| \
+ { read -r NUM REST; echo "$NUM"; })"
+ fi
+ if [ "$DEV_NUM" == "" ]; then
+ DEV_NUM=$(/sbin/blkid -L "$PART_NAME" -o device -l | sed "\|$EMMC_DEVICE|!d" | \
+ sed 's/\/dev\/mmcblk0p//')
+ fi
+ if [ "$DEV_NUM" != "" ]; then
+ DEV="${EMMC_DEVICE}p${DEV_NUM}"
+ fi
+
+ log "[Info] Flashing $DELTA_NAME... to $DEV" "$LOG_FILE"
+ /bin/tar xOf "$DELTA_TAR" "$DELTA_NAME" > "$DEV"
+ fi
+ done < "$FOTA_DIR/$CONFIG_FILE"
+}
+
+if [ "$DOWNLOAD_DELTA" -ef "$DELTA_TAR" ]; then
+ log "[Info] delta.tar[.gz] is already placed in correct directory." "$LOG_FILE"
+else
+ log "[Info] Copy delta.tar[.gz]..." "$LOG_FILE"
+ /bin/cp "$DOWNLOAD_DELTA" "$DELTA_TAR"
+fi
+
+# delta.tar.gz has to be uncompressed to .tar beacause ua and libtota does not support .tar.gz
+if [ "$DELTA_TAR_GZ" == "TRUE" ]; then
+ gunzip -c "$DOWNLOAD_DELTA" > "$FOTA_DIR/delta.tar"
+ rm "$DELTA_TAR"
+ DELTA_TAR="$FOTA_DIR/delta.tar"
+fi
+
+sync
+
+# Run setup script if exist
+run_setup_script "$DELTA_TAR"
+
+# Flash images
+# - in case of some new image was included
+# - in case of some image should be flashed before update
+flash_pre_image
+
+# Extract delta.ua
+log "[Info] Extract delta.ua..." "$LOG_FILE"
+unpack_file "$DELTA_TAR" delta.ua
+
+# FOTA: /usr/bin/rw-update-prepare.sh
+
+# Write delta saved path
+log "[Info] Write paths..." "$LOG_FILE"
+echo "$FOTA_DIR" > "$STATUS_DIR/DELTA.PATH"
+dirname "$DOWNLOAD_DELTA" > "$STATUS_DIR/DOWNLOAD.PATH"
+sync
+
+# go to fota mode
+log "[Info] Go TOTA update..." "$LOG_FILE"
+if ! /sbin/reboot fota; then
+ critical_log "[Error] Failed to reboot fota" "$LOG_FILE"
+ exit 100
+fi
+
+critical_log "RO update: $SCRIPT_NAME success" "$LOG_FILE"
--- /dev/null
+#!/bin/bash
+set -o errexit
+trap 'echo "Aborting due to errexit on ${0##*/}:$LINENO. Exit code: $?" >&2' ERR
+set -o errtrace -e -o pipefail
+
+if [ ! -z "${UPGRADE_DEBUG}" ]; then
+ set -x
+fi
+
+PATH=/bin:/usr/bin:/sbin:/usr/sbin
+FOTA_DIR="/opt/usr/data/fota"
+
+. "$FOTA_DIR"/upgrade-common.inc
+
+SCRIPT_NAME="upgrade-partial.sh"
+LOG_FILE="/tmp/upgrade-partial.log"
+
+check_args() {
+ DELTA_TAR="$1"
+ if [ ! -f "$DELTA_TAR" ]; then
+ flog "[Error] Usage: $0 path_to_delta.tar[.gz]"
+ exit 1
+ fi
+
+ flog "[Info] Using <$DELTA_TAR> delta file."
+}
+
+FOTA_UPDATE_PREFIX="/run/upgrade-sysroot"
+
+cleanup() {
+ cleanup_partitions
+ cleanup_files
+}
+
+cleanup_files() {
+ rm -f -- "$DELTA_TAR"
+ rm -f -- "$FOTA_DIR/upgrade-apply"
+ rm -f -- "$FOTA_DIR/$SCRIPT_NAME"
+}
+
+prepare_fota_update_dir() {
+ mkdir -p ${FOTA_UPDATE_PREFIX}
+}
+
+prepare_fota_update_dir
+check_ab_partition_scheme
+check_used_block_device
+check_args "$@"
+unpack_file "$DELTA_TAR" "$CONFIG_FILE"
+unpack_file "$DELTA_TAR" "upgrade-apply"
+/bin/chmod +x "$FOTA_DIR/upgrade-apply"
+if ! upgrade_images "$DELTA_TAR"; then
+ critical_flog "[Error] Unable to upgrade_images"
+ cleanup
+ exit_error
+fi
+
+if ! run_setup_script "$DELTA_TAR"; then
+ critical_flog "[Error] Unable to run_setup_script"
+ cleanup
+ exit_error
+fi
+
+cleanup
+
+critical_log "RO update: $SCRIPT_NAME success" "$LOG_FILE"
--- /dev/null
+#!/bin/bash
+set -o errexit
+trap 'echo "Aborting due to errexit on ${0##*/}:$LINENO. Exit code: $?" >&2' ERR
+set -o errtrace -e -o pipefail
+
+if [ ! -z "${UPGRADE_DEBUG}" ]; then
+ set -x
+fi
+
+PATH=/bin:/usr/bin:/sbin:/usr/sbin
+
+FOTA_DIR="/opt/usr/data/fota"
+
+. "$FOTA_DIR"/upgrade-common.inc
+
+SCRIPT_NAME="upgrade-prepare-partitions.sh"
+LOG_FILE="/tmp/upgrade-prepare-partitions.log"
+
+check_optional_partition() {
+ partition_name="$1"
+ exit_code="$2"
+ optional_partitions=(hal)
+ for part in "${optional_partitions[@]}"; do
+ if [ "$partition_name" == "$part" ]; then
+ return
+ fi
+ done
+ exit "$exit_code"
+}
+
+cleanup_files() {
+ rm -f -- "$FOTA_DIR/$SCRIPT_NAME"
+}
+
+check_ab_partition_scheme
+check_used_block_device
+load_background_copy_list
+if ! background_copy; then
+ exit_error
+fi
+cleanup_files
+
+critical_log "RO update preparation: $SCRIPT_NAME success" "$LOG_FILE"
--- /dev/null
+#!/bin/bash
+# Master script for upgrade. Called by update-manager/manually to start upgrade procedure.
+set -o errexit
+trap 'echo "Aborting due to errexit on ${0##*/}:$LINENO. Exit code: $?" >&2' ERR
+set -o errtrace -e -o pipefail
+
+if [ ! -z "${UPGRADE_DEBUG}" ]; then
+ set -x
+fi
+
+PATH=/bin:/usr/bin:/sbin:/usr/sbin
+FOTA_DIR="/opt/usr/data/fota"
+STATUS_DIR="/opt/data/update"
+DOWNLOAD_DELTA="$1"
+SCRIPT_NAME="upgrade-trigger.sh"
+LOG_FILE="/tmp/upgrade-trigger.log"
+SCRIPT_UPGRADE_LEGACY="upgrade-legacy.sh"
+SCRIPT_UPGRADE_PREPARE_PARTITIONS="upgrade-prepare-partitions.sh"
+SCRIPT_UPGRADE_PARTIAL="upgrade-partial.sh"
+SCRIPT_UPGRADE_FOTA="upgrade-fota.sh"
+FLOCK_PATH="/var/lock/clone_partitions.lock"
+
+prepare_fota_dir() {
+ if [ -d "$FOTA_DIR" ]; then
+ # Cleanup FOTA_DIR
+ if [ "$(dirname $DOWNLOAD_DELTA)" = "$FOTA_DIR" ]; then
+ # If provided delta is from inside the FOTA_DIR, delete everything else
+ DELTA_FILE_NAME=$(basename $DOWNLOAD_DELTA)
+ rm -rf -- "$FOTA_DIR/!($DELTA_FILE_NAME)"
+ else
+ # If provided delta is from outside the FOTA_DIR delete everything
+ rm -rf -- "$FOTA_DIR/*"
+ fi
+ else
+ log "[Info] Create fota dir..." "$LOG_FILE"
+ mkdir -p "$FOTA_DIR"
+ fi
+
+ if [ ! -d "$STATUS_DIR" ]; then
+ log "[Info] Create status dir..." "$LOG_FILE"
+ mkdir -p "$STATUS_DIR"
+ fi
+}
+
+log() {
+ # log format: [script_name][tag]actual_log
+ LOG="[${SCRIPT_NAME}]$1"
+ if [ "$2" != "" ]; then
+ echo "$LOG" >> "$2"
+ fi
+ echo "$LOG"
+
+ return 0
+}
+
+is_ab_upgrade() {
+ if grep -q partition_ab= /proc/cmdline; then
+ return $TRUE
+ fi
+ return $FALSE
+}
+
+do_update() {
+ if is_ab_upgrade; then
+ set_upgrade_status 5
+
+ if [ "$(device_board_get_partition_ab_cloned)" -eq 0 ]; then
+ unpack_file "${DOWNLOAD_DELTA}" "${SCRIPT_UPGRADE_PREPARE_PARTITIONS}"
+ if ! "${FOTA_DIR}/${SCRIPT_UPGRADE_PREPARE_PARTITIONS}"; then
+ exit_error
+ fi
+ else
+ echo "[Info] Partitions already cloned"
+ fi
+
+ set_upgrade_status 20
+
+ # In the next steps the content of the partitions of the second slot will
+ # be modified, so they will not be a clone of the current partitions
+ device_board_clear_partition_ab_cloned
+
+ unpack_file "${DOWNLOAD_DELTA}" "${SCRIPT_UPGRADE_PARTIAL}"
+ unpack_file "${DOWNLOAD_DELTA}" "${SCRIPT_UPGRADE_FOTA}"
+
+ if ! "${FOTA_DIR}/${SCRIPT_UPGRADE_PARTIAL}" "${DOWNLOAD_DELTA}"; then
+ exit_error
+ fi
+
+ set_upgrade_status 40
+
+ if ! "${FOTA_DIR}/${SCRIPT_UPGRADE_FOTA}"; then
+ exit_error
+ fi
+
+ set_upgrade_status 80
+ else
+ # Legacy upgrade (non-A/B)
+ unpack_file "${DOWNLOAD_DELTA}" "${SCRIPT_UPGRADE_LEGACY}"
+ "${FOTA_DIR}/${SCRIPT_UPGRADE_LEGACY}" "${DOWNLOAD_DELTA}"
+ fi
+}
+
+verify_file() {
+ FILE_PATH="$1"
+ FILE_NAME=$(basename "$FILE_PATH")
+
+ VALID_CHECKSUM=$(awk "/$FILE_NAME/ {print \$1}" "$FOTA_DIR/checksum.SHA1")
+ if [ "$VALID_CHECKSUM" == "" ]; then
+ echo "[Error] No $FILE_NAME in checksum.SHA1"
+ device_board_set_upgrade_status -1
+ exit 1
+ fi
+
+ if ! echo "$VALID_CHECKSUM $FILE_PATH" | sha1sum --check --status; then
+ echo "[Error] Checksum for file $FILE_NAME is invalid"
+ device_board_set_upgrade_status -1
+ exit 1
+ fi
+
+ echo "[Info] Checksum of $FILE_NAME is OK"
+}
+
+(
+flock 9
+
+rm -f "$LOG_FILE" && touch "$LOG_FILE"
+
+if [ "$#" != "1" ] || [ ! -f "$1" ]; then
+ log "[Error] Usage: $0 path_to_delta.tar[.gz]" "$LOG_FILE"
+ exit 1
+fi
+
+log "[Info] Using <$DOWNLOAD_DELTA> delta file." "$LOG_FILE"
+
+prepare_fota_dir
+tar xfp "$DOWNLOAD_DELTA" -C "$FOTA_DIR" checksum.SHA1
+verify_file "$0"
+tar xfp "$DOWNLOAD_DELTA" -C "$FOTA_DIR" upgrade-common.inc
+verify_file "$FOTA_DIR/upgrade-common.inc"
+
+. "$FOTA_DIR"/upgrade-common.inc
+
+set_upgrade_status 1
+verify_img "${DOWNLOAD_DELTA}"
+do_update
+
+critical_log "RO update: $SCRIPT_NAME success" "$LOG_FILE"
+) 9> "$FLOCK_PATH"
--- /dev/null
+INCLUDE(FindPkgConfig)
+
+pkg_check_modules(blkid_pkgs REQUIRED blkid)
+SET(BLKIDSRCS blkid-print.c)
+SET(BLKIDEXENAME "blkid-print")
+
+FOREACH(flag ${pkgs_CFLAGS})
+ SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
+ENDFOREACH(flag)
+
+ADD_EXECUTABLE(${BLKIDEXENAME} ${BLKIDSRCS})
+TARGET_LINK_LIBRARIES(${BLKIDEXENAME} ${blkid_pkgs_LDFLAGS})
+INSTALL(TARGETS ${BLKIDEXENAME} DESTINATION ${BINDIR})
--- /dev/null
+/*
+ * tota-ua
+ *
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "blkid-api.h"
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+blkid_partlist get_part_list(char *device_name, blkid_probe *pr) {
+ assert(pr);
+
+ *pr = blkid_new_probe_from_filename(device_name);
+ if (*pr == NULL)
+ return NULL;
+ blkid_partlist ls = blkid_probe_get_partitions(*pr);
+ return ls;
+}
+
+int get_part_number_by_name(blkid_partlist ls, const char *part_name, const char *new_slot) {
+ int nparts = blkid_partlist_numof_partitions(ls);
+ char part_name_with_slot[MAX_PARTNAME_LEN];
+ int found_part_n = -1;
+
+ if (snprintf(part_name_with_slot, MAX_PARTNAME_LEN, "%s_%s", part_name, new_slot) < 0) {
+ return -1;
+ }
+
+ for (int it = 0; it < nparts; ++it) {
+ blkid_partition part = blkid_partlist_get_partition(ls, it);
+ const char *p;
+
+ p = blkid_partition_get_name(part);
+ if (!p)
+ continue;
+
+ if (found_part_n < 0 && strncmp(p, part_name, strlen(part_name)+1) == 0)
+ found_part_n = it + 1;
+ else if (strncmp(p, part_name_with_slot, strlen(part_name_with_slot)+1) == 0)
+ found_part_n = it + 1;
+ }
+ if (found_part_n >= 0)
+ return found_part_n;
+
+ // nothing found
+ return -1;
+}
+
--- /dev/null
+/*
+ * tota-ua
+ *
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+#include <blkid/blkid.h>
+
+#ifndef MAX_PARTNAME_LEN
+#define MAX_PARTNAME_LEN 10000
+#endif
+blkid_partlist get_part_list(char *device_name, blkid_probe *pr);
+int get_part_number_by_name(blkid_partlist ls, const char *part_name, const char *new_slot);
--- /dev/null
+/*
+ * tota-ua
+ *
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <stdlib.h>
+#include "blkid-api.h"
+
+void usage(const char* msg) {
+ if (msg) {
+ printf("%s\n", msg);
+ }
+ printf("USAGE: blkid-print device_name partition_label suffix\n"
+ "device_name: block device e.g. /dev/mmcblk0\n"
+ "partition_label: partiton label to search for e.g. rootfs,ramdisk etc.\n"
+ "suffix: a|b partiton slot\n");
+}
+
+blkid_partition partition_for(char *device_name, char *part_name, char *new_slot) {
+ char part_name_with_slot[MAX_PARTNAME_LEN];
+ int found_part_n = -1;
+ if (!device_name || !part_name || !new_slot){
+ printf("partition_for: one or more argument is NULL\n");
+ return NULL;
+ }
+ if (snprintf(part_name_with_slot, MAX_PARTNAME_LEN, "%s_%s", part_name, new_slot) < 0) {
+ return NULL;
+ }
+ blkid_probe pr = blkid_new_probe_from_filename(device_name);
+ if (pr == NULL) {
+ printf("ERROR: blkid error for %s\n", device_name);
+ usage(NULL);
+ return NULL;
+ }
+ blkid_partlist ls = blkid_probe_get_partitions(pr);
+ if (!ls) {
+ printf("ERROR: unable to probe partitions.\n");
+ blkid_free_probe(pr);
+ return NULL;
+ }
+ int nparts = blkid_partlist_numof_partitions(ls);
+ if (nparts == -1) {
+ printf("ERROR: unable to get partition count\n");
+ blkid_free_probe(pr);
+ return NULL;
+ }
+
+
+ for (int i = 0; i < nparts; i++) {
+ blkid_partition part = blkid_partlist_get_partition(ls, i);
+ const char *p;
+
+ p = blkid_partition_get_name(part);
+ if (!p)
+ continue;
+
+ if (found_part_n < 0 && strncmp(p, part_name, strlen(part_name)+1) == 0)
+ found_part_n = i;
+ else if (strncmp(p, part_name_with_slot, strlen(part_name_with_slot)+1) == 0)
+ found_part_n = i;
+ }
+
+ blkid_free_probe(pr);
+ if (found_part_n >= 0) {
+ blkid_partition par = blkid_partlist_get_partition(ls, found_part_n);
+ return par;
+ }
+ return NULL;
+}
+
+const char *get_part_label(blkid_partlist ls, int part_nr) {
+ blkid_partition part = blkid_partlist_get_partition(ls, part_nr);
+ return blkid_partition_get_name(part);
+}
+
+int main(int argc, char *argv[]) {
+ char device_name_path[PATH_MAX];
+ if (argc != 4) {
+ usage("Please specify correct argument number\n");
+ return 1;
+ }
+ char *device_name = realpath(argv[1], device_name_path);
+ if (!device_name) {
+ printf("ERROR: Unable to determine realpath for: %s\n", argv[1]);
+ usage(NULL);
+ return 1;
+ }
+ char *partition_label = argv[2];
+ char *suffix = argv[3];
+ blkid_partition part = partition_for(device_name, partition_label, suffix);
+ if (part == NULL) {
+ printf("ERROR: Partition '%s' not found.\n", partition_label);
+ usage(NULL);
+ return 2;
+ }
+ int part_nr = blkid_partition_get_partno(part);
+ if (part_nr < 0) {
+ printf("ERROR: Partition '%s' not found", partition_label);
+ return 3;
+ }
+ const char *part_label = blkid_partition_get_name(part);
+ char part_path[PATH_MAX];
+ // /dev/sda1 vs /dev/mmcblk0p1 /dev/nvme0n1p1
+ // no_separator vs "p" separator
+ if (strncmp("/dev/sd", device_name, strlen(device_name) + 1) == 0)
+ snprintf(part_path, PATH_MAX, "%s%d", device_name, part_nr);
+ else
+ snprintf(part_path, PATH_MAX, "%sp%d", device_name, part_nr);
+ printf("part_nr: %d (%s): %s\n", part_nr, part_label, part_path);
+ return 0;
+}
+
--- /dev/null
+SET(PREFIX ${CMAKE_INSTALL_PREFIX})
+SET(BINDIR "${PREFIX}/bin")
+SET(VERITY_HANDLER "verity_handler")
+SET(DMVERITY_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+
+SET(DMVERITY_SRCS ${DMVERITY_DIR}/verity_handler.c)
+INCLUDE_DIRECTORIES(${DMVERITY_DIR})
+
+INCLUDE(FindPkgConfig)
+pkg_check_modules(dmverity_pkgs REQUIRED)
+
+FOREACH(flag ${dmverity_pkgs_CFLAGS})
+ SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
+ENDFOREACH(flag)
+
+ADD_EXECUTABLE(${VERITY_HANDLER} ${DMVERITY_SRCS})
+TARGET_LINK_LIBRARIES(${VERITY_HANDLER} ${dmverity_pkgs_LDFLAGS} ${LIBS} -lpthread)
+
+INSTALL(TARGETS ${VERITY_HANDLER} DESTINATION ${BINDIR})
--- /dev/null
+/*
+ * verity-handler
+ *
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#include "verity_handler.h"
+
+#define TMP_DIR "/tmp/upgrade"
+#define PROGRESS_FILE TMP_DIR "/ro_progress"
+
+/*-----------------------------------------------------------------------------
+ _system_cmd_wait
+ ----------------------------------------------------------------------------*/
+static int _system_cmd_wait(const char *command)
+{
+ int pid = 0;
+ int status = 0;
+ char* const environ[2] = { "DISPLAY=:0", NULL };
+
+ if (command == NULL)
+ return -1;
+
+ pid = fork();
+
+ if (pid == -1)
+ return -1;
+
+ if (pid == 0) {
+ char *argv[4];
+ argv[0] = "sh";
+ argv[1] = "-c";
+ argv[2] = (char*)command;
+ argv[3] = 0;
+ execve("/bin/sh", argv, environ);
+ exit(127);
+ }
+
+ do {
+ if (waitpid(pid, &status, 0) == -1) {
+ if (errno != EINTR)
+ return -1;
+ } else {
+ return status;
+ }
+ } while(1);
+}
+
+/*-----------------------------------------------------------------------------
+ fota_gui_update_progress
+ ----------------------------------------------------------------------------*/
+void fota_gui_update_progress(int percent)
+{
+ int ret;
+ int fd;
+ struct stat s;
+
+ // Check directory
+ ret = stat(TMP_DIR, &s);
+ if (ret == 0) {
+ // TMP_DIR exists but it is not directory
+ if (!S_ISDIR(s.st_mode))
+ goto remove_file;
+ else
+ goto update_progress;
+ } else if (errno == ENOENT) // TMP_DIR not exists
+ goto make_directory;
+ else {
+ LOG("stat failed : %m\n");
+ return;
+ }
+
+remove_file:
+ ret = remove(TMP_DIR);
+ if (ret != 0) {
+ LOG("remove failed : %m\n");
+ return;
+ }
+
+make_directory:
+ ret = mkdir(TMP_DIR, 0755);
+ if (ret != 0) {
+ LOG("mkdir failed : %m\n");
+ return;
+ }
+
+update_progress:
+ fd = creat(PROGRESS_FILE, 0644);
+ if (fd < 0) {
+ LOG("creat failed : %m\n");
+ return;
+ }
+
+ ret = dprintf(fd, "%d\n", percent);
+ if (close(fd) != 0) {
+ LOG("close failed : %m\n");
+ return;
+ }
+ if (ret < 2) {
+ LOG("write failed (%d) : %m\n", ret);
+ return;
+ }
+
+ LOG("Progress : %d%%\n", percent);
+}
+
+/*-----------------------------------------------------------------------------
+ __thread_make_hash
+ ----------------------------------------------------------------------------*/
+static void *__thread_make_hash(void *arg)
+{
+ int ret;
+ char cmd[1024];
+
+ snprintf(cmd, sizeof(cmd)-1, "/usr/bin/verityctl format %s", (char*)arg);
+ LOG("cmd = %s\n", cmd);
+ ret = _system_cmd_wait(cmd);
+ LOG("ret = %d, exit status = %d\n", ret, WEXITSTATUS(ret));
+
+ return NULL;
+}
+
+/*-----------------------------------------------------------------------------
+ __thread_draw_progress
+ ----------------------------------------------------------------------------*/
+static void *__thread_draw_progress(void *arg)
+{
+ int expected_duration = 45; /* sec */
+ int count = 20;
+ useconds_t period = expected_duration * 1000000 / count;
+ int from = 82;
+ int to = 98;
+ float progress = from;
+ float progress_increment = (float)(to - from) / (float)count;
+
+ /**
+ * Convert tick to progress
+ *
+ * 0 ~ count -> from ~ to
+ */
+ for (int tick = 0; tick < count; tick++) {
+ fota_gui_update_progress((int)progress);
+ progress += progress_increment;
+ pthread_testcancel();
+ usleep(period);
+ }
+
+ return NULL;
+}
+
+
+/*-----------------------------------------------------------------------------
+ main
+ ----------------------------------------------------------------------------*/
+int main(int argc, char **argv)
+{
+ int error = 0;
+ pthread_t th_id_hash;
+ pthread_t th_id_progress;
+
+ /* check argument */
+ if ((argc != 2) || (argv[1] == NULL) || (strlen(argv[1]) == 0)) {
+ return -1;
+ }
+
+ /* argv[1] is a path of block device whose hash will be made */
+ error = pthread_create(&th_id_hash, NULL, __thread_make_hash, argv[1]);
+ if (error != 0) {
+ LOG("pthread_create(th_id_hash) failed (err = %d)\n", error);
+ return -1;
+ }
+
+ error = pthread_create(&th_id_progress, NULL, __thread_draw_progress, NULL);
+ if (error != 0) {
+ LOG("pthread_create(th_id_progress) failed (err = %d)\n", error);
+ return -1;
+ }
+
+ error = pthread_join(th_id_hash, NULL);
+ if (error != 0) {
+ LOG("pthread_join(th_id_hash) failed (err = %d)\n", error);
+ return -1;
+ } else
+ LOG("pthread_join(th_id_hash) succeeded \n");
+
+ error = pthread_cancel(th_id_progress);
+ if (error != 0)
+ LOG("progress thread is already terminated \n");
+
+ error = pthread_join(th_id_progress, NULL);
+ if (error != 0) {
+ LOG("pthread_join(th_id_progress) failed (err = %d)\n", error);
+ return -1;
+ } else
+ LOG("pthread_join(th_id_hash) succeeded \n");
+
+ fota_gui_update_progress(100);
+
+ return 0;
+}
--- /dev/null
+/*
+ * verity-handler
+ *
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __VERITY_HANDLER_H__
+#define __VERITY_HANDLER_H__
+
+#define DEBUG_STDOUT
+//#define DEBUG_FILE
+
+#define LOG_PRFIX "VERITY_HANDLER"
+
+#ifdef DEBUG_STDOUT
+#define LOGE(s, args...) printf(LOG_PRFIX "/ERROR(%s) " s, __func__, ##args) // Error log
+#define LOGL(s, args...) do{ printf(LOG_PRFIX "/(%s): " s,__func__, ##args);}while(0)
+#define LOG(s, args...) LOGL(s, ##args)
+#endif
+
+
+#endif /* __VERITY_HANDLER_H__ */
--- /dev/null
+ADD_DEFINITIONS("-DIMG_VERIFIER_ROOT_CA_DIR=\"${IMG_VERIFIER_ROOT_CA_DIR}\"")
+
+SET(PREFIX ${CMAKE_INSTALL_PREFIX})
+SET(SBINDIR "${PREFIX}/sbin")
+SET(VERIFIER "img-verifier")
+SET(VERIFIER_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+
+SET(VERIFIER_SRCS ${VERIFIER_DIR}/${VERIFIER}.c)
+INCLUDE_DIRECTORIES(${VERIFIER_DIR})
+
+INCLUDE(FindPkgConfig)
+pkg_check_modules(verify_pkgs REQUIRED openssl1.1)
+
+FOREACH(flag ${verify_pkgs_CFLAGS})
+ SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
+ENDFOREACH(flag)
+
+ADD_EXECUTABLE(${VERIFIER} ${VERIFIER_SRCS})
+TARGET_LINK_LIBRARIES(${VERIFIER} ${verify_pkgs_LDFLAGS})
+INSTALL(TARGETS ${VERIFIER} DESTINATION ${SBINDIR})
--- /dev/null
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <openssl/err.h>
+#include <openssl/x509.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "img-verifier.h"
+
+static struct signed_file signed_file;
+static X509 *signed_file_cert;
+static X509_STORE *ca_store;
+static X509_STORE_CTX *ca_store_ctx;
+static EVP_MD_CTX *evp_md_ctx;
+
+
+static bool check_signed_file(const char *path)
+{
+ const int metadata_size = sizeof(signed_file.delta_size) + sizeof(signed_file.signature_size) + sizeof(signed_file.certificate_size) + MAGIC_NUMBER_SIZE;
+ int ret = -1;
+ _CLEANUP_CLOSE_ int signed_file_fd = -1;
+ long long int offset = (off_t)-1;
+ unsigned int data_size = 0;
+
+ _I("Check signed file...");
+
+ ASSERT_RETV(path, EINVAL, "Invalid parameter");
+ _D("Signed file path : %s", path);
+
+ signed_file_fd = open(path, O_RDONLY);
+ ASSERT_RETV(signed_file_fd != -1, false, "Failed to open %s : %m", path);
+
+ // Read metadata
+ offset = lseek(signed_file_fd, -metadata_size, SEEK_END);
+ ASSERT_RETV(offset != (off_t)-1, false, "Failed to move file offset : %m");
+
+ ret = read(signed_file_fd, &signed_file.delta_size, metadata_size);
+ ASSERT_RETV(ret == metadata_size, false, "Failed to read metadata : %m");
+
+ // Check magic number
+ _D("Magic number : %s", signed_file.magic_number);
+
+ ret = memcmp(signed_file.magic_number, MAGIC_NUMBER, MAGIC_NUMBER_SIZE);
+ ASSERT_RETV(ret == 0, false, "Invalid magic number");
+
+ // Check file size
+ _D("Delta size : %d", signed_file.delta_size);
+ _D("Signature size : %d", signed_file.signature_size);
+ _D("Certificate size : %d", signed_file.certificate_size);
+
+ data_size = signed_file.delta_size + signed_file.signature_size + signed_file.certificate_size;
+
+ offset = lseek(signed_file_fd, -metadata_size, SEEK_END);
+ ASSERT_RETV(offset == data_size, false, "Invalid file size : Expected(%u), Real(%lld)", data_size, offset);
+
+ return true;
+}
+
+static int print_openssl_error(void)
+{
+ unsigned long err = ERR_get_error();
+
+ _E("OpenSSL error : %s", ERR_error_string(err, NULL));
+
+ return -EIO;
+}
+
+static int read_root_ca(void)
+{
+ int ret;
+ int num_root_ca = 0;
+ _CLEANUP_DIR_ DIR *dirp = NULL;
+ struct dirent *dirent = NULL;
+ const char *ext = NULL;
+ char root_ca_path[PATH_MAX];
+
+ _I("Read root CA...");
+
+ dirp = opendir(IMG_VERIFIER_ROOT_CA_DIR);
+ ASSERT_RETV(dirp, -errno, "Failed to open %s : %m", IMG_VERIFIER_ROOT_CA_DIR);
+
+ ca_store = X509_STORE_new();
+ ASSERT_RETV(ca_store, -ENOMEM, "X509_STORE_new failed");
+
+ _D("Root CA directory : %s", IMG_VERIFIER_ROOT_CA_DIR);
+ while ((dirent = readdir(dirp))) {
+ if ((ext = strrchr(dirent->d_name, '.')) && !strncmp(ext + 1, "pem", 4)) {
+ snprintf(root_ca_path, sizeof(root_ca_path), "%s/%s",
+ IMG_VERIFIER_ROOT_CA_DIR, dirent->d_name);
+ _D("Find root CA : %s", dirent->d_name);
+ ret = X509_STORE_load_locations(ca_store, root_ca_path, NULL);
+ ASSERT_RETV(ret == 1, print_openssl_error(),
+ "X509_STORE_load_locations for %s failed", root_ca_path);
+ ++num_root_ca;
+ }
+ }
+
+ return num_root_ca;
+}
+
+static int read_signed_file(const char *path)
+{
+ int ret = -1;
+ _CLEANUP_CLOSE_ int signed_file_fd = -1;
+ long long int offset = (off_t)-1;
+
+ _I("Read signed file...");
+
+ ASSERT_RETV(path, EINVAL, "Invalid parameter");
+ _D("Signed file path : %s", path);
+
+ signed_file_fd = open(path, O_RDONLY);
+ ASSERT_RETV(signed_file_fd != -1, errno, "Failed to open %s : %m", path);
+
+ // Read data
+ offset = lseek(signed_file_fd, signed_file.delta_size, SEEK_SET);
+ ASSERT_RETV(offset != (off_t)-1, false, "Failed to move file offset : %m");
+
+ signed_file.signature = malloc(signed_file.signature_size);
+ ASSERT_RETV(signed_file.signature, ENOMEM, "Not enough memory");
+
+ ret = read(signed_file_fd, signed_file.signature, signed_file.signature_size);
+ ASSERT_RETV(ret == signed_file.signature_size, errno, "Failed to read signature : %m");
+
+ signed_file.certificate = malloc(signed_file.certificate_size);
+ ASSERT_RETV(signed_file.certificate, ENOMEM, "Not enough memory");
+
+ ret = read(signed_file_fd, signed_file.certificate, signed_file.certificate_size);
+ ASSERT_RETV(ret == signed_file.certificate_size, errno, "Failed to read certificate : %m");
+
+ return SUCCEED;
+}
+
+static int verify_certificate(void)
+{
+ int ret = -1;
+ unsigned char *pcert = NULL;
+ STACK_OF(X509_OBJECT *) ca_object_list = NULL;
+ X509_OBJECT *ca_object = NULL;
+ X509 *ca = NULL;
+
+ _I("Verify certificate...");
+
+ pcert = signed_file.certificate;
+ ASSERT_RETV(pcert, EINVAL, "Invalid certificate");
+
+ signed_file_cert = d2i_X509(NULL, (const unsigned char **)&pcert, signed_file.certificate_size);
+ ASSERT_RETV(signed_file_cert, print_openssl_error(), "d2i_X509 failed");
+
+ // Reject the certificate same with CA
+ ca_object_list = X509_STORE_get0_objects(ca_store);
+ _D("The number of CA : %d", sk_X509_OBJECT_num(ca_object_list));
+ for (int idx = 0; idx < sk_X509_OBJECT_num(ca_object_list); idx++) {
+ ca_object = sk_X509_OBJECT_value(ca_object_list, idx);
+ ca = X509_OBJECT_get0_X509(ca_object);
+
+ ASSERT_RETV(X509_cmp(signed_file_cert, ca) != 0, EPERM, "Signing with CA is prohibited");
+ }
+
+ // Check whether image has CA's child certificate
+ ca_store_ctx = X509_STORE_CTX_new();
+ ASSERT_RETV(ca_store_ctx, ENOMEM, "X509_STORE_CTX_new failed");
+
+ ret = X509_STORE_CTX_init(ca_store_ctx, ca_store, signed_file_cert, NULL);
+ ASSERT_RETV(ret == 1, print_openssl_error(), "X509_STORE_CTX_init failed");
+
+ X509_VERIFY_PARAM *x509_verify_param = X509_STORE_CTX_get0_param(ca_store_ctx);
+ ASSERT_RETV(x509_verify_param, print_openssl_error(), "X509_STORE_CTX_get0_param failed");
+
+ /**
+ * We don't check the validity period of certificates
+ * because RPi doesn't have RTC(Real Time Clock).
+ */
+ ret = X509_VERIFY_PARAM_set_flags(x509_verify_param, X509_V_FLAG_NO_CHECK_TIME);
+ ASSERT_RETV(ret == 1, print_openssl_error(), "X509_VERIFY_PARAM_set_flags failed");
+
+ ret = X509_verify_cert(ca_store_ctx);
+ if (ret != 1) {
+ _E("X509_verify_cert failed : %s", X509_verify_cert_error_string((long)X509_STORE_CTX_get_error(ca_store_ctx)));
+ return EPERM;
+ }
+
+ _I("Verify result : VALID");
+ return SUCCEED;
+}
+
+static int verify_delta(const char *path)
+{
+ int ret = -1;
+ static EVP_PKEY *signed_file_public_key = NULL;
+ _CLEANUP_CLOSE_ int signed_file_fd = -1;
+ unsigned char delta_block[512];
+ unsigned int read_delta_size = 0;
+
+ _I("Verify delta...");
+
+ ASSERT_RETV(signed_file_cert, EINVAL, "Invalid X509 certificate");
+
+ signed_file_public_key = X509_get0_pubkey(signed_file_cert);
+ ASSERT_RETV(signed_file_public_key, print_openssl_error(), "X509_get_pubkey_failed");
+
+ evp_md_ctx = EVP_MD_CTX_new();
+ ASSERT_RETV(evp_md_ctx, ENOMEM, "EVP_MD_CTX_new failed");
+
+ ret = EVP_DigestVerifyInit(evp_md_ctx, NULL, EVP_sha256(), NULL, signed_file_public_key);
+ ASSERT_RETV(ret == 1, print_openssl_error(), "EVP_DigestVerifyInit failed");
+
+ // Hash delta
+ signed_file_fd = open(path, O_RDONLY);
+ ASSERT_RETV(signed_file_fd != -1, false, "Failed to open %s : %m", path);
+
+ while (read_delta_size < signed_file.delta_size) {
+ memset(delta_block, 0, sizeof(delta_block));
+ ret = read(signed_file_fd, delta_block, sizeof(delta_block));
+ ASSERT_RETV(ret != -1, errno, "Failed to read delta : %m");
+ read_delta_size += ret;
+
+ ret = EVP_DigestVerifyUpdate(evp_md_ctx, delta_block, sizeof(delta_block));
+ ASSERT_RETV(ret == 1, print_openssl_error(), "EVP_DigestVerifyUpdate failed");
+ }
+
+ // Decrypt signature and compare with hashed delta
+ ret = EVP_DigestVerifyFinal(evp_md_ctx, signed_file.signature, signed_file.signature_size);
+ ASSERT_RETV(ret == 1, print_openssl_error(), "EVP_DigestVerifyFinal failed");
+
+ _I("Verify result : VALID");
+ return SUCCEED;
+}
+
+static int free_data(int ret)
+{
+ _I("Free data...");
+
+ if (signed_file.signature)
+ free(signed_file.signature);
+
+ if (signed_file.certificate)
+ free(signed_file.certificate);
+
+ if (signed_file_cert)
+ X509_free(signed_file_cert);
+
+ if (ca_store)
+ X509_STORE_free(ca_store);
+
+ if (ca_store_ctx)
+ X509_STORE_CTX_free(ca_store_ctx);
+
+ if (evp_md_ctx)
+ EVP_MD_CTX_free(evp_md_ctx);
+
+ return ret;
+}
+
+int main(int argc, char *argv[])
+{
+ int ret;
+ int opt;
+ const char *image_path = NULL;
+ const char *log_path = NULL;
+ bool is_signed_file;
+ int num_root_ca;
+
+ // Check argument
+ while ((opt = getopt(argc, argv, "i:l:")) != -1) {
+ switch (opt) {
+ case 'i':
+ image_path = optarg;
+ break;
+ case 'l':
+ log_path = optarg;
+ break;
+ default:
+ _E("Invalid option : %c", (char)opt);
+ return EINVAL;
+ }
+ }
+
+ if (log_path) {
+ _CLEANUP_CLOSE_ int log_fd = -1;
+
+ log_fd = open(log_path, O_WRONLY | O_CREAT, 0644);
+ ASSERT_RETV(log_fd != -1, errno, "Failed to open log file fd : %m");
+
+ ret = dup2(log_fd, STDOUT_FILENO);
+ ASSERT_RETV(ret != -1, errno, "Failed to duplicate fd : %m");
+ }
+
+ ASSERT_RETV(image_path, EINVAL, "You must input image path");
+
+ _I("Start to verify image");
+
+ is_signed_file = check_signed_file(image_path);
+
+ num_root_ca = read_root_ca();
+ ASSERT_RETV(num_root_ca >= 0, free_data(num_root_ca), "Failed to read root CA (%d)", num_root_ca);
+
+ /**
+ * Root CA / Signed File | Action
+ * -------------------------------------------
+ * X X | Skip verification
+ * X O | Deny
+ * O X | Deny
+ * O O | Try to verify
+ */
+ _D("Signed(%d), Root CA(%d)", is_signed_file, num_root_ca);
+ if (is_signed_file) {
+ if (num_root_ca <= 0) {
+ _E("Root CA not exist");
+ return EPERM;
+ }
+ } else {
+ if (num_root_ca > 0) {
+ _E("%s isn't signed file", image_path);
+ return EPERM;
+ } else {
+ _W("Skip verification");
+ return SUCCEED;
+ }
+ }
+
+ ret = read_signed_file(image_path);
+ ASSERT_RETV(ret == SUCCEED, free_data(ret), "Failed to read data (%d)", ret);
+
+ ret = verify_certificate();
+ ASSERT_RETV(ret == SUCCEED, free_data(ret), "Failed to verify certificate (%d)", ret);
+
+ ret = verify_delta(image_path);
+ ASSERT_RETV(ret == SUCCEED, free_data(ret), "Failed to verify delta (%d)", ret);
+
+ free_data(SUCCEED);
+
+ _I("Succeed to verify image!");
+ return SUCCEED;
+}
--- /dev/null
+#ifndef __IMG_VERIFIER_H__
+#define __IMG_VERIFIER_H__
+
+#include <unistd.h>
+
+#define LOG_VERBOSE
+
+#define _L(lvl, fmt, arg...) printf("IV/" lvl fmt "\n", ##arg)
+
+#ifdef LOG_VERBOSE
+#define _D(fmt, arg...) _L("DEBUG ", fmt, ##arg)
+#else
+#define _D(fmt, arg...)
+#endif
+#define _I(fmt, arg...) _L("INFO ", fmt, ##arg)
+#define _W(fmt, arg...) _L("WARN ", fmt, ##arg)
+#define _E(fmt, arg...) _L("ERROR ", fmt, ##arg)
+
+
+#define SUCCEED 0
+
+#define ASSERT_RETV(cond, retv, msg, arg...) \
+{ \
+ if (!(cond)) { \
+ _E(msg, ##arg); \
+ return retv; \
+ } \
+}
+
+static void close_fd(int *fd)
+{
+ if (fd && *fd >= 0)
+ close(*fd);
+}
+
+static void close_dir(DIR **dirp)
+{
+ if (dirp && *dirp)
+ closedir(*dirp);
+}
+
+#define _CLEANUP_CLOSE_ __attribute__((__cleanup__(close_fd)))
+#define _CLEANUP_DIR_ __attribute__((__cleanup__(close_dir)))
+
+#define MAGIC_NUMBER "IMG_SIGNED_V1"
+#define MAGIC_NUMBER_SIZE (sizeof(MAGIC_NUMBER) - 1)
+
+/**
+ * This structure is same with signed image.
+ * So, please sync it with signer.
+ */
+struct signed_file {
+ // Data
+// unsigned char *delta; (not used, just to understand structure)
+ unsigned char *signature;
+ unsigned char *certificate;
+ // Metadata
+ unsigned int delta_size;
+ unsigned int signature_size;
+ unsigned int certificate_size;
+ unsigned char magic_number[MAGIC_NUMBER_SIZE];
+};
+
+#endif /* __IMG_VERIFIER_H__ */
--- /dev/null
+find_package(PkgConfig)
+pkg_check_modules(DEPS REQUIRED IMPORTED_TARGET zlib liblzma-tool)
+
+ADD_DEFINITIONS("-D_FILE_OFFSET_BITS=64")
+
+set(
+ upgrade-apply_SRCS
+ main.c
+ patch/patch.c
+ patch/brotli.c
+ patch/lzma.c
+ sha1/sha1.c
+)
+add_executable(upgrade-apply ${upgrade-apply_SRCS})
+target_link_libraries(upgrade-apply PRIVATE PkgConfig::DEPS)
+# Unfortunately, libtar is neither CMake- nor pkgconfig-aware.
+# Even more unfortunately, pkgconfig detection of static libraries is broken for all version of CMake before 3.24,
+# and as of writing 3.24 is not even released yet (see https://gitlab.kitware.com/cmake/cmake/-/merge_requests/7070).
+# This would require a bit of hacking (but also libc.a) if we wanted full static linking,
+# but here we need something even different: static linking for all but libc.
+# This requires especially brutal hacks. Maybe we should use Meson next time? ;)
+# In the meantime, TODO: make this better.
+# Also, note that zlib has been used in the normal way (i.e. with dynamic linking).
+# That's because the current setup does have libz.so in the execution environment.
+# Though in the future, it might be good to make it consistent (TODO).
+target_link_libraries(upgrade-apply PRIVATE libbrotlidec-static.a libbrotlicommon-static.a libtar.a)
+install(TARGETS upgrade-apply)
--- /dev/null
+/*
+ * tota-ua
+ *
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libtar.h>
+#include <zlib.h>
+
+#include "patch/patch.h"
+#include "patch/brotli.h"
+#include "patch/lzma.h"
+#include "sha1/sha1.h"
+
+void fd_cleanup(int *fd)
+{
+ if (!fd || *fd < 0)
+ return;
+ close(*fd);
+}
+
+void tar_cleanup(TAR **tar)
+{
+ if (!tar || !*tar)
+ return;
+ tar_close(*tar);
+}
+
+enum archive_kind {
+ KIND_RAW,
+ KIND_BROTLI_PATCH,
+ KIND_LZMA_PATCH
+};
+
+struct parse_result {
+ enum {
+ PARSE_OK,
+ PARSE_HELP,
+ PARSE_REPEATED_ARGUMENT,
+ PARSE_MISSING_ARGUMENT,
+ PARSE_PATCH_MISTAKE,
+ PARSE_BAD_KIND,
+ PARSE_BAD_SIZE,
+ PARSE_NO_PARSE,
+ } result;
+
+ const char *archive;
+ const char *dest;
+ const char *archive_file;
+ const char *patch_orig;
+ const char *dest_sha1;
+ size_t dest_size;
+ enum archive_kind kind;
+};
+
+struct parse_result parse_args(int argc, char **argv)
+{
+ const char *archive = NULL;
+ const char *dest = NULL;
+ const char *archive_file = NULL;
+ const char *patch_orig = NULL;
+ const char *dest_sha1 = NULL;
+ long long int dest_size = 0;
+ enum archive_kind kind = -1;
+ bool help = false;
+
+ for (;;) {
+ const struct option long_options[] = {
+ {"archive", required_argument, NULL, 0 },
+ {"dest", required_argument, NULL, 1 },
+ {"archive-file", required_argument, NULL, 2 },
+ {"kind", required_argument, NULL, 3 },
+ {"patch-orig", required_argument, NULL, 4 },
+ {"dest-sha1", required_argument, NULL, 5 },
+ {"dest-size", required_argument, NULL, 6 },
+ {"help", no_argument, NULL, 'h'},
+ {0}
+ };
+ int option = getopt_long(argc, argv, "h", long_options, NULL);
+ if (option < 0)
+ break;
+
+ switch (option) {
+ case 0: // archive
+ if (archive != NULL)
+ return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
+ archive = optarg;
+ break;
+
+ case 1: // dest
+ if (dest != NULL)
+ return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
+ dest = optarg;
+ break;
+
+ case 2: // archive-file
+ if (archive_file != NULL)
+ return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
+ archive_file = optarg;
+ break;
+
+ case 3: // kind
+ if (kind != -1)
+ return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
+ if (strcmp(optarg, "raw") == 0)
+ kind = KIND_RAW;
+ else if (strcmp(optarg, "ss_brotli_patch") == 0)
+ kind = KIND_BROTLI_PATCH;
+ else if (strcmp(optarg, "ss_lzma_patch") == 0)
+ kind = KIND_LZMA_PATCH;
+ else
+ return (struct parse_result) { .result = PARSE_BAD_KIND };
+ break;
+
+ case 4: // patch-orig
+ if (patch_orig != NULL)
+ return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
+ patch_orig = optarg;
+ break;
+
+ case 5: // dest-sha1
+ if (dest_sha1 != NULL)
+ return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
+ dest_sha1 = optarg;
+ break;
+
+ case 6: // dest-size
+ if (dest_size != 0)
+ return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
+ dest_size = atoi(optarg);
+ if (dest_size <= 0 || dest_size > SIZE_MAX)
+ return (struct parse_result) { .result = PARSE_BAD_SIZE };
+ break;
+
+ case 'h': // help
+ if (help)
+ return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
+ help = true;
+ break;
+
+ default:
+ return (struct parse_result) { .result = PARSE_NO_PARSE };
+ }
+ }
+
+ if (help) {
+ if (archive != NULL || dest != NULL || archive_file != NULL || patch_orig != NULL || kind != -1)
+ return (struct parse_result) { .result = PARSE_REPEATED_ARGUMENT };
+ return (struct parse_result) { .result = PARSE_HELP };
+ }
+
+ if (archive == NULL)
+ return (struct parse_result) { .result = PARSE_MISSING_ARGUMENT };
+ if (dest == NULL)
+ return (struct parse_result) { .result = PARSE_MISSING_ARGUMENT };
+ if (archive_file == NULL)
+ return (struct parse_result) { .result = PARSE_MISSING_ARGUMENT };
+ if (kind == -1)
+ return (struct parse_result) { .result = PARSE_MISSING_ARGUMENT };
+
+ if (patch_orig == NULL && (kind == KIND_BROTLI_PATCH || kind == KIND_LZMA_PATCH))
+ return (struct parse_result) { .result = PARSE_PATCH_MISTAKE };
+ if (patch_orig != NULL && kind == KIND_RAW)
+ return (struct parse_result) { .result = PARSE_PATCH_MISTAKE };
+
+ return (struct parse_result) {
+ .result = PARSE_OK,
+ .archive = archive,
+ .dest = dest,
+ .archive_file = archive_file,
+ .patch_orig = patch_orig,
+ .dest_sha1 = dest_sha1,
+ .dest_size = dest_size,
+ .kind = kind,
+ };
+}
+
+// The TAR structure can only hold an int as a FD.
+// libtar example uses some brutal hack which we don't want to do.
+// We don't want to open multiple archive at the time, so let's just have 1 as FD,
+// and instead hold the gzip structure globally.
+static gzFile gzip_file;
+
+int gzip_open(const char *pathname, int oflags, ...)
+{
+ if (gzip_file != NULL) {
+ errno = EALREADY;
+ return -1;
+ }
+
+ // We assume oflags == O_RDONLY, since that's what we actually use.
+ // Also we don't care about the mode since we are lazy.
+ if (oflags != O_RDONLY) {
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ __attribute__((cleanup(fd_cleanup))) int inner_fd = open(pathname, O_RDONLY);
+ if (inner_fd == -1)
+ return -1;
+
+ gzip_file = gzopen(pathname, "r");
+ if (gzip_file == NULL)
+ return -1;
+ inner_fd = -1;
+
+ return 1;
+}
+
+int gzip_close(__attribute__((unused)) int useless_fd)
+{
+ if (gzip_file == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return gzclose(gzip_file);
+}
+
+ssize_t gzip_read(__attribute__((unused)) int useless_fd, void *buf, size_t len)
+{
+ if (gzip_file == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return gzread(gzip_file, buf, len);
+}
+
+ssize_t gzip_write(__attribute__((unused)) int useless_fd, __attribute__((unused)) const void *buf, __attribute__((unused)) size_t len)
+{
+ // Again, we do not use this.
+ errno = ENOTSUP;
+ return -1;
+}
+
+void help(char *name)
+{
+ fprintf(stderr,
+ "Usage: %s --archive ARCHIVE --archive-file FILE --dest DESTINATION --kind KIND [--patch-orig ORIGIN] [--dest-sha1 SHA1] [--dest-size SIZE]\n"
+ "\n"
+ "Patches a partition using an archive.\n"
+ "It will look for a file named FILE in an archive called ARCHIVE; the archive should have an extension\n"
+ "of either .tar, .tar.gz or .tgz; it will be read in the way corresponding to the extension.\n"
+ "The KIND parameter specifies how the file will be treated:\n"
+ " - raw means that the file should be treated as an image to be written,\n"
+ " - ss_brotli_patch means that the file should be treated as a delta in the (Tizen-specific) ss_brotli_patch\n"
+ " - ss_lzma_patch means that the file should be treated as a delta in the (Tizen-specific) ss_lzma_patch\n"
+ " format. In this case, ORIGIN is needed and specifies the partition that a delta is based on.\n"
+ "The results will be written to the DESTINATION partition.\n"
+ "If SHA1 is provided (lowercase hexadecimal), it is compared to the one calculated on DESTINATION after writing.\n"
+ "Additionally, if SIZE is provided, it is used as the destination size; this matters for SHA1 comparision,\n"
+ "and also if ss_brotli_patch or ss_lzma_patch is used.\n",
+ name);
+}
+
+int apply_raw(const char *dest, TAR *tar)
+{
+ int size = th_get_size(tar);
+
+ __attribute__((cleanup(fd_cleanup))) int out = open(dest, O_WRONLY);
+ if (out == -1) {
+ fprintf(stderr, "Couldn't open the target (errno: %m)\n");
+ return -1;
+ }
+
+ while (size > 0) {
+ char buf[T_BLOCKSIZE];
+ int r_read = tar_block_read(tar, buf);
+ switch (r_read) {
+ case -1:
+ fprintf(stderr, "Couldn't read from the archive (errno: %m)\n");
+ return -1;
+ case 0:
+ fprintf(stderr, "We have reached EOF unexpectedly\n");
+ return -1;
+ }
+
+ if (r_read > size) {
+ r_read = size;
+ }
+ size -= r_read;
+
+ int written = 0;
+ while (written - r_read) {
+ int r_write = write(out, buf + written, r_read - written);
+ if (r_write == -1) {
+ fprintf(stderr, "Couldn't write to the partition (errno: %m)\n");
+ return -1;
+ }
+ written += r_write;
+ }
+ }
+
+ return 0;
+}
+
+int check_sha1(const char *dest, const char *sha1, size_t read_bytes)
+{
+ const size_t SHA1_LEN = 20;
+ const char HEX_DIGITS[] = "0123456789abcdef";
+
+ if (strlen(sha1) != 2 * SHA1_LEN) {
+ fprintf(stderr, "Invalid SHA1 length\n");
+ return -1;
+ }
+
+ __attribute__((cleanup(fd_cleanup))) int dest_fd = open(dest, O_RDONLY);
+ if (dest_fd == -1) {
+ fprintf(stderr, "Couldn't open the target (errno: %m)\n");
+ return -1;
+ }
+
+ SHA1_CTX context;
+ SHA1Init(&context);
+
+ if (read_bytes == 0)
+ read_bytes = SIZE_MAX;
+
+ while (read_bytes > 0) {
+ // This size doesn't really matter, so let's just pick a value that is big enough
+ // to not use too many syscalls.
+ unsigned char buf[1 << 16];
+
+ int r_read = read(dest_fd, buf, read_bytes < sizeof(buf) ? read_bytes : sizeof(buf));
+ read_bytes -= r_read;
+ switch (r_read) {
+ case -1:
+ fprintf(stderr, "Couldn't read from the destination (errno: %m)\n");
+ return -1;
+ case 0:
+ // We read all the data. Let's check the results!
+ // ... wait, there is no two-layer (switch and while) break in C.
+ // Ugh, we have to goto instead.
+ goto while_done;
+ }
+
+ SHA1Update(&context, buf, r_read);
+ }
+while_done:;
+
+ unsigned char correct_sha1[SHA1_LEN + 1];
+ SHA1Final(correct_sha1, &context);
+
+ char correct_sha1_hex[2 * SHA1_LEN + 1];
+ for (int i = 0; i < SHA1_LEN; ++i) {
+ correct_sha1_hex[2 * i] = HEX_DIGITS[correct_sha1[i] / 16];
+ correct_sha1_hex[2 * i + 1] = HEX_DIGITS[correct_sha1[i] % 16];
+ }
+ correct_sha1_hex[sizeof(correct_sha1_hex) - 1] = '\0';
+
+ if (strcmp(correct_sha1_hex, sha1) != 0) {
+ fprintf(stderr, "Malformed or invalid SHA1: correct one is %s\n", correct_sha1_hex);
+ return -1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ struct parse_result parsed = parse_args(argc, argv);
+ switch (parsed.result) {
+ case PARSE_REPEATED_ARGUMENT:
+ fprintf(stderr, "Argument incorrectly repeated\n");
+ help(argv[0]);
+ return EXIT_FAILURE;
+ case PARSE_MISSING_ARGUMENT:
+ fprintf(stderr, "Argument incorrectly missing\n");
+ help(argv[0]);
+ return EXIT_FAILURE;
+ case PARSE_PATCH_MISTAKE:
+ fprintf(stderr, "`patch-orig` parameter doesn't fit the kind\n");
+ help(argv[0]);
+ return EXIT_FAILURE;
+ case PARSE_BAD_KIND:
+ fprintf(stderr, "Invalid `kind` parameter (possible: `raw`, `ss_brotli_patch`)\n");
+ help(argv[0]);
+ return EXIT_FAILURE;
+ case PARSE_BAD_SIZE:
+ fprintf(stderr, "Invalid `dest-size` parameter (it should be a positive number that fits in size_t)\n");
+ help(argv[0]);
+ return EXIT_FAILURE;
+ case PARSE_NO_PARSE:
+ fprintf(stderr, "Invalid parameters passed\n");
+ help(argv[0]);
+ return EXIT_FAILURE;
+ case PARSE_HELP:
+ help(argv[0]);
+ return EXIT_SUCCESS;
+ case PARSE_OK:
+ break;
+ }
+
+ tartype_t *type;
+ static tartype_t gzip_type = { gzip_open, gzip_close, gzip_read, gzip_write };
+ if (strcmp(parsed.archive + strlen(parsed.archive) - strlen(".tar"), ".tar") == 0)
+ type = NULL;
+ else if (strcmp(parsed.archive + strlen(parsed.archive) - strlen(".tar.gz"), ".tar.gz") == 0)
+ type = &gzip_type;
+ else if (strcmp(parsed.archive + strlen(parsed.archive) - strlen(".tgz"), ".tgz") == 0)
+ type = &gzip_type;
+ else {
+ fprintf(stderr, "Unknown archive extension\n");
+ help(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ __attribute__ ((cleanup(tar_cleanup))) TAR *tar;
+ if (tar_open(&tar, parsed.archive, type, O_RDONLY, 0, 0) == -1) {
+ /* Usually we would instead initialize tar to NULL, and tar_open would not touch it in case of failure.
+ * Alas, that's not what happens; tar_open sets tar to a newly allocated address, does stuff there,
+ * and in case of failure frees the address but does not reset it. This means that in case of failure
+ * tar contains an invalid address. So we have to NULL it out (so tar_cleanup doesn't crash our program),
+ * and then we don't have to initialize tar to NULL in first place. */
+ tar = NULL;
+
+ fprintf(stderr, "Couldn't open the archive (errno: %m)\n");
+ return EXIT_FAILURE;
+ }
+
+ for (;;) {
+ switch (th_read(tar)) {
+ case -1:
+ fprintf(stderr, "Couldn't read the archive header (errno: %m)\n");
+ return EXIT_FAILURE;
+ case 1:
+ // All files processed, which means...
+ fprintf(stderr, "File not found in archive\n");
+ return EXIT_FAILURE;
+ case 0:
+ break;
+ }
+
+ const char *this_name = th_get_pathname(tar);
+ if (this_name == NULL) {
+ // TODO: Can this even happen? I don't think docs say anything about this.
+ fprintf(stderr, "Couldn't extract the filename from the archive (malformed archive?)\n");
+ return EXIT_FAILURE;
+ }
+
+ if (strcmp(this_name, parsed.archive_file) != 0) {
+ if (TH_ISREG(tar)) {
+ if (tar_skip_regfile(tar) == -1) {
+ fprintf(stderr, "Couldn't skip an intermediate file from the archive (errno: %m; malformed archive?)\n");
+ return EXIT_FAILURE;
+ }
+ }
+ continue;
+ }
+
+ fprintf(stderr, "File found. Starting to write\n");
+
+ int r = EXIT_FAILURE;
+
+
+ switch (parsed.kind) {
+ case KIND_RAW:
+ r = apply_raw(parsed.dest, tar);
+ break;
+
+
+ case KIND_BROTLI_PATCH:
+ {
+ struct dec_funcs funcs = { init_brotli, decompress_brotli };
+ r = apply_patch(parsed.patch_orig, parsed.dest, tar, parsed.dest_size, &funcs);
+ break;
+ }
+ case KIND_LZMA_PATCH:
+ {
+ struct dec_funcs funcs = { init_lzma, decompress_lzma, clean_lzma };
+ r = apply_patch(parsed.patch_orig, parsed.dest, tar, parsed.dest_size, &funcs);
+ break;
+ }
+ }
+
+ if (r != 0) {
+ fprintf(stderr, "Couldn't apply; you might need to restore from the backup!\n");
+ return EXIT_FAILURE;
+ }
+
+ if (parsed.dest_sha1 != NULL) {
+ fprintf(stderr, "Write successful. Now checking SHA1\n");
+ if (check_sha1(parsed.dest, parsed.dest_sha1, parsed.dest_size) != 0) {
+ fprintf(stderr, "Couldn't check SHA1; you might need to restore from the backup!\n");
+ return EXIT_FAILURE;
+ }
+ }
+
+ fprintf(stderr, "Everything written successfully\n");
+ return EXIT_SUCCESS;
+ }
+}
--- /dev/null
+This is copied from platform/core/system/libtota @ b25d02d618797b5fb2fbe61a188d6a28dc720bf8.
+
+The code has been adapted to read patch from a tarball instead of a file descriptor, as well as some other minor improvements. It's mostly untouched otherwise.
--- /dev/null
+/*
+ * tota-ua (previously libtota)
+ *
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <stdio.h>
+#include <assert.h>
+#include <brotli/decode.h>
+#include <string.h>
+#include "patch.h"
+
+int init_brotli(struct bs_data *data)
+{
+ assert(data);
+ data->state = BrotliDecoderCreateInstance(NULL, NULL, NULL);
+ return 0;
+}
+
+int decompress_brotli(struct bs_data *data)
+{
+ assert(data);
+ BrotliDecoderState *bstate = (BrotliDecoderState*)data->state;
+ int result = BrotliDecoderDecompressStream(bstate,
+ &data->available_in,
+ &data->compressed_pos,
+ &data->available_out,
+ &data->decompressed_pos,
+ &data->total_size);
+
+ switch (result) {
+ case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:
+ return PF_NEED_MORE_DATA;
+ case BROTLI_DECODER_RESULT_ERROR:
+ return PF_ERROR_DECOMPRESSION;
+ }
+ return PF_OK;
+}
--- /dev/null
+/*
+ * tota-ua (previously libtota)
+ *
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+#include "patch.h"
+
+int init_brotli(struct bs_data *data);
+int decompress_brotli(struct bs_data *data);
--- /dev/null
+/*
+ * tota-ua (previously libtota)
+ *
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <assert.h>
+#include "patch.h"
+#include <LzmaDec.h>
+#include <Alloc.h>
+
+#define HEADER_PLUS_SIZE_LEN LZMA_PROPS_SIZE+8
+
+struct lzma_data {
+ bool initialized;
+ CLzmaDec *state;
+};
+
+static void *SzAlloc(void *p, size_t size)
+{
+ return MyAlloc(size);
+}
+
+
+static void SzFree(void *p, void *address)
+{
+ MyFree(address);
+}
+
+ISzAlloc g_alloc = { SzAlloc, SzFree };
+
+int init_lzma(struct bs_data *data)
+{
+ assert(data);
+ struct lzma_data *internal_data = (struct lzma_data*)malloc(sizeof(struct lzma_data));
+ if (!internal_data)
+ return PF_ERROR_DECOMPRESSION;
+
+ CLzmaDec *state = (CLzmaDec*)malloc(sizeof(CLzmaDec));
+ if (!state) {
+ free(internal_data);
+ return PF_ERROR_DECOMPRESSION;
+ }
+
+ data->state = internal_data;
+ internal_data->state = state;
+ internal_data->initialized = false;
+
+ LzmaDec_Construct(state);
+
+ return PF_OK;
+}
+
+void clean_lzma(struct bs_data *data)
+{
+ assert(data);
+ if (!data->state)
+ return;
+
+ struct lzma_data *internal_data = data->state;
+ LzmaDec_Free(internal_data->state, &g_alloc);
+ free(internal_data->state);
+ free(internal_data);
+ data->state = NULL;
+}
+
+static int init_lzma_internal(struct bs_data *data)
+{
+ assert(data);
+ if (data->available_in < HEADER_PLUS_SIZE_LEN)
+ return PF_NEED_MORE_DATA;
+ struct lzma_data *internal_data = data->state;
+
+ int res = LzmaDec_Allocate(internal_data->state,
+ data->compressed_pos,
+ LZMA_PROPS_SIZE,
+ &g_alloc);
+ if (res != SZ_OK)
+ return PF_ERROR_DECOMPRESSION;
+
+ data->available_in -= HEADER_PLUS_SIZE_LEN;
+ data->compressed_pos += HEADER_PLUS_SIZE_LEN;
+
+ LzmaDec_Init(internal_data->state);
+ internal_data->initialized = true;
+ return PF_OK;
+}
+
+int decompress_lzma(struct bs_data *data)
+{
+ assert(data);
+ int res;
+ struct lzma_data *internal_data = data->state;
+
+
+ if (!internal_data->initialized) {
+ res = init_lzma_internal(data);
+ if (res != PF_OK)
+ return res;
+ }
+
+ ELzmaStatus status;
+ size_t out_processed = data->available_out;
+ size_t in_processed = data->available_in;
+
+ res = LzmaDec_DecodeToBuf(internal_data->state,
+ data->decompressed_pos,
+ &out_processed,
+ data->compressed_pos,
+ &in_processed,
+ LZMA_FINISH_ANY,
+ &status);
+
+ data->available_out -= out_processed;
+ data->available_in -= in_processed;
+ data->decompressed_pos += out_processed;
+ data->compressed_pos += in_processed;
+
+ if (res != SZ_OK)
+ return PF_ERROR_DECOMPRESSION;
+
+ if (status == LZMA_STATUS_NEEDS_MORE_INPUT && data->available_in == 0) {
+ if (data->patch_remaining == 0)
+ return PF_OK; // Since we don't have anything to read, we're done.
+ return PF_NEED_MORE_DATA;
+ }
+
+ return PF_OK;
+}
--- /dev/null
+/*
+ * tota-ua
+ *
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+#include "patch.h"
+
+int init_lzma(struct bs_data *data);
+int decompress_lzma(struct bs_data *data);
+void clean_lzma(struct bs_data *data);
--- /dev/null
+/*
+ * tota-ua (previously libtota)
+ *
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <libtar.h>
+#include <inttypes.h>
+#include "patch.h"
+
+#if INTPTR_MAX == INT32_MAX
+// We need to map two partitions, so
+// on a 32-bit architecture one mmapping
+// cannot be more than (3GB - 256M)/2
+#define MAX_MMAP_LEN (3*1024*1024*1024ULL/2 - 256*1024*1024)
+#else
+#define MAX_MMAP_LEN UINT64_MAX
+#endif
+
+#define SSINT_LEN 8
+#define BLOCK_COUNT_REPORT 5000
+
+const char SSDIFF_MAGIC[] = "SSDIFF40";
+
+static void free_data(struct bs_data *data, struct dec_funcs *funcs)
+{
+ if (data == NULL)
+ return;
+
+ if (funcs->clean)
+ funcs->clean(data);
+
+ if (data->src.ptr) munmap(data->src.ptr, data->src.mmapped_len);
+ if (data->dest.ptr) munmap(data->dest.ptr, data->dest.mmapped_len);
+
+ if (data->src.fd) close(data->src.fd);
+ if (data->dest.fd) close(data->dest.fd);
+}
+
+static int open_file(const char *file_name, int mode)
+{
+ assert(file_name);
+ int fd = open(file_name, mode, S_IWUSR | S_IRUSR);
+ if (fd < 0)
+ fprintf(stderr, "Open file %s error: %m (%d)\n", file_name, errno);
+ return fd;
+}
+
+static size_t get_file_len(int fd)
+{
+ assert(fd >= 0);
+ size_t result = lseek(fd, 0, SEEK_END);
+ lseek(fd, 0, SEEK_SET);
+ return result;
+}
+
+static int remmap(struct file_info *finfo, off_t offset)
+{
+ if (finfo->ptr) munmap(finfo->ptr, finfo->mmapped_len);
+
+ off_t new_offset = offset & (~(getpagesize()-1));
+
+ if (finfo->len - new_offset > MAX_MMAP_LEN) {
+ // If the rest of the file is greather than MAX_MMAP_LEN
+ // then we only mmap MAX_MMAP_LEN
+ finfo->mmapped_len = MAX_MMAP_LEN;
+ } else {
+ // If the rest of the file is smaller than MAX_MMAP_LEN
+ if (finfo->len > MAX_MMAP_LEN) {
+ // If the whole file is greather than MAX_MMAP_LEN
+ // we mmap as much as possible to reduce the number of
+ // redundant remmappings
+ finfo->mmapped_len = MAX_MMAP_LEN;
+ new_offset = finfo->len - MAX_MMAP_LEN;
+ } else {
+ // File is smaller than MAX_MMAP_LEN so we can mmap the entire file
+ finfo->mmapped_len = finfo->len;
+ new_offset = 0;
+ }
+ }
+
+ finfo->ptr = mmap(NULL, finfo->mmapped_len, finfo->mmap_prot, finfo->mmap_flags, finfo->fd, new_offset);
+ if (finfo->ptr == MAP_FAILED) {
+ fprintf(stderr, "mmap file error: %m (%d)\n", errno);
+ return PF_ERROR_MMAP;
+ }
+
+ finfo->offset = new_offset;
+
+ return PF_OK;
+}
+
+static int set_new_pos(off_t new_pos, struct file_info *finfo)
+{
+ int res = PF_OK;
+ off_t local_pos = new_pos - finfo->offset;
+ if (local_pos < 0 || local_pos > finfo->mmapped_len) {
+ res = remmap(finfo, new_pos);
+ }
+ if (res == PF_OK) {
+ finfo->pos = finfo->ptr + new_pos - finfo->offset;
+ }
+ return res;
+}
+
+static int remmap_and_save_position(struct file_info *finfo)
+{
+ off_t global_pos = finfo->pos - (uint8_t*)finfo->ptr + finfo->offset;
+ int res = remmap(finfo, global_pos);
+ if (res == PF_OK) {
+ finfo->pos = finfo->ptr + global_pos - finfo->offset;
+ }
+ return res;
+}
+
+static int remmap_if_needed(struct file_info *finfo)
+{
+ int res = PF_OK;
+ if (finfo->pos - (uint8_t*)finfo->ptr >= finfo->mmapped_len) {
+ res = remmap_and_save_position(finfo);
+ }
+ return res;
+}
+
+static int remmap_if_no_space(struct file_info *finfo, int64_t size)
+{
+ int res = PF_OK;
+ if (finfo->pos + size > (uint8_t*)finfo->ptr + finfo->mmapped_len) {
+ res = remmap_and_save_position(finfo);
+ }
+ return res;
+}
+
+static size_t decompress_bytes(struct dec_funcs *funcs, struct bs_data *data, size_t keep_offset)
+{
+ assert(data);
+ if (keep_offset > 0) {
+ memcpy(data->buff_out, data->buff_out + sizeof(data->buff_out) - keep_offset, keep_offset);
+ }
+ data->decompressed_pos = data->buff_out + keep_offset;
+ data->available_out = sizeof(data->buff_out) - keep_offset;
+
+ int result;
+
+ for (;;) {
+ result = funcs->decompress(data);
+
+ if (result == PF_ERROR_DECOMPRESSION) {
+ fprintf(stderr, "Brotli decompression errored\n");
+ return PF_ERROR_DECOMPRESSION;
+ } else if (result == PF_NEED_MORE_DATA) {
+ if (data->patch_remaining <= 0) {
+ fprintf(stderr, "We have ran out of data and decompression is still in progress\n");
+ return PF_ERROR_DECOMPRESSION;
+ }
+
+ int r_read = tar_block_read(data->patch_tar, data->buff_in);
+ switch (r_read) {
+ case -1:
+ fprintf(stderr, "Couldn't read from the archive (errno: %m)\n");
+ return PF_ERROR_DECOMPRESSION;
+ case 0:
+ fprintf(stderr, "We have reached EOF unexpectedly\n");
+ return PF_ERROR_DECOMPRESSION;
+ }
+
+ if (r_read > data->patch_remaining) {
+ r_read = data->patch_remaining;
+ }
+ data->patch_remaining -= r_read;
+
+ data->available_in = r_read;
+ data->compressed_pos = data->buff_in;
+ } else {
+ break;
+ }
+ }
+
+ return PF_OK;
+}
+
+static int open_files(struct bs_data *data, const char *source_file, const char *dest_file, TAR *patch_tar, size_t dest_size)
+{
+ assert(data);
+ assert(source_file);
+ assert(dest_file);
+ assert(patch_tar);
+
+ data->src.fd = open_file(source_file, O_RDONLY);
+ data->dest.fd = open_file(dest_file, O_RDWR);
+ if (data->src.fd < 0 ||
+ data->dest.fd < 0)
+ return PF_ERROR_OPEN_FILE;
+
+ data->patch_tar = patch_tar;
+
+ data->src.len = get_file_len(data->src.fd);
+ data->patch_len = th_get_size(data->patch_tar);
+ data->dest.len = dest_size == 0 ? get_file_len(data->dest.fd) : dest_size;
+
+ int res;
+ if ((res = remmap(&data->src, 0)) != PF_OK)
+ return res;
+
+ if ((res = remmap(&data->dest, 0)) != PF_OK)
+ return res;
+
+ data->patch_remaining = data->patch_len;
+
+ return PF_OK;
+}
+
+static void init_file_info(struct file_info *finfo)
+{
+ finfo->fd = -1;
+ finfo->ptr = NULL;
+ finfo->offset = 0;
+ finfo->mmapped_len = 0;
+ finfo->pos = 0;
+ finfo->len = 0;
+}
+
+static void init_data(struct bs_data *data)
+{
+ assert(data);
+
+ init_file_info(&data->src);
+ data->src.mmap_prot = PROT_READ;
+ data->src.mmap_flags = MAP_PRIVATE;
+ init_file_info(&data->dest);
+ data->dest.mmap_prot = PROT_WRITE;
+ data->dest.mmap_flags = MAP_SHARED;
+ data->patch_tar = NULL;
+ data->patch_len = 0;
+ data->patch_remaining = 0;
+ data->available_in = 0;
+ data->compressed_pos = 0;
+ data->available_out = 0;
+ data->decompressed_pos = 0;
+ data->state = NULL;
+}
+
+static int64_t parse_ssint(unsigned char *buff)
+{
+ assert(buff);
+ /*
+ * From bsdiff 4.0 documentation:
+ *
+ * INTEGER type:
+ *
+ * offset size data type value
+ * 0 1 byte x0
+ * 1 1 byte x1
+ * 2 1 byte x2
+ * 3 1 byte x3
+ * 4 1 byte x4
+ * 5 1 byte x5
+ * 6 1 byte x6
+ * 7 1 byte x7 + 128 * s
+ *
+ * The values x0, x2, x2, x3, x4, x5, x6 are between 0 and 255 (inclusive).
+ * The value x7 is between 0 and 127 (inclusive). The value s is 0 or 1.
+ *
+ * The INTEGER is parsed as:
+ * (x0 + x1 * 256 + x2 * 256^2 + x3 * 256^3 + x4 * 256^4 +
+ * x5 * 256^5 + x6 * 256^6 + x7 * 256^7) * (-1)^s
+ *
+ * (In other words, an INTEGER is a 64-byte signed integer in sign-magnitude
+ * format, stored in little-endian byte order.)
+ */
+ int64_t result = *(int64_t*)buff & 0x7fffffff;
+ if ((buff[7] & 0x80) != 0)
+ result = -result;
+
+ return result;
+}
+
+int read_header(struct dec_funcs *funcs, struct bs_data *data, uint8_t **buff_out_pos)
+{
+ assert(data);
+ assert(buff_out_pos);
+
+ *buff_out_pos = data->buff_out;
+
+ if (*buff_out_pos + sizeof(SSDIFF_MAGIC) > data->decompressed_pos ||
+ memcmp(data->buff_out, SSDIFF_MAGIC, sizeof(SSDIFF_MAGIC) - 1) != 0) {
+ fprintf(stderr, "Invalid patch file\n");
+ return PF_ERROR_INVALID_PATCH_FILE;
+ } else {
+ fprintf(stderr, "Looks like SSDIFF\n");
+ }
+
+ *buff_out_pos += sizeof(SSDIFF_MAGIC) - 1;
+
+ if (*buff_out_pos + SSINT_LEN > data->decompressed_pos) {
+ decompress_bytes(funcs, data, data->decompressed_pos - *buff_out_pos);
+ *buff_out_pos = data->buff_out;
+ }
+
+ size_t target_size = parse_ssint(*buff_out_pos);
+ fprintf(stderr, "target_size: 0x%zx (%zu)\n", target_size, target_size);
+
+ if (target_size != data->dest.len) {
+ fprintf(stderr, "Declared target size differs from that read from the patch\n");
+ return PF_ERROR_INVALID_PATCH_FILE;
+ }
+
+ *buff_out_pos += SSINT_LEN;
+
+ return PF_OK;
+}
+
+int apply_patch(const char *source_file, const char *dest_file, TAR *patch_tar, size_t dest_size, struct dec_funcs *funcs)
+{
+ assert(source_file);
+ assert(dest_file);
+ assert(patch_tar);
+
+ int result;
+ uint64_t blocks = 0;
+ struct bs_data data;
+
+ init_data(&data);
+ if ((result = funcs->init(&data)) != PF_OK)
+ goto exit;
+
+ if ((result = open_files(&data, source_file, dest_file, patch_tar, dest_size)) != PF_OK)
+ goto exit;
+
+ if ((result = decompress_bytes(funcs, &data, 0)) != PF_OK)
+ goto exit;
+
+ uint8_t *buff_out_pos;
+
+ if ((result = read_header(funcs, &data, &buff_out_pos)) != PF_OK)
+ goto exit;
+
+ uint64_t total_write = 0;
+
+ while (total_write < data.dest.len) {
+ /*
+ * Make sure we can read the block header
+ */
+ if (buff_out_pos + 4*8 > data.decompressed_pos) {
+ if ((result = decompress_bytes(funcs, &data, data.decompressed_pos - buff_out_pos)) != PF_OK)
+ goto exit;
+ buff_out_pos = data.buff_out;
+ }
+
+ /*
+ * Read the block header
+ */
+ int64_t diff_len = parse_ssint(buff_out_pos+0*8);
+ int64_t extra_len = parse_ssint(buff_out_pos+1*8);
+ int64_t old_pos = parse_ssint(buff_out_pos+2*8);
+ int64_t new_pos = parse_ssint(buff_out_pos+3*8);
+ buff_out_pos += 4*8;
+
+ /*
+ * Prepare pointers
+ */
+ if ((result = set_new_pos(old_pos, &data.src)) != PF_OK)
+ goto exit;
+ if ((result = set_new_pos(new_pos, &data.dest)) != PF_OK)
+ goto exit;
+ /*
+ * Read diff data
+ */
+ int64_t write = 0;
+ while (write < diff_len) {
+ if (buff_out_pos >= data.decompressed_pos) {
+ if ((result = decompress_bytes(funcs, &data, 0)) != PF_OK)
+ goto exit;
+ buff_out_pos = data.buff_out;
+ }
+
+ while (write < diff_len && buff_out_pos < data.decompressed_pos) {
+ if ((result = remmap_if_needed(&data.src)) != PF_OK)
+ goto exit;
+ if ((result = remmap_if_needed(&data.dest)) != PF_OK)
+ goto exit;
+ *data.dest.pos = *(uint8_t*)data.src.pos + *(uint8_t*)buff_out_pos;
+ data.dest.pos++;
+ data.src.pos++;
+ buff_out_pos++;
+ write++;
+ }
+ }
+ total_write += write;
+ /*
+ * Read extra data
+ */
+ write = 0;
+ while (write < extra_len) {
+ if (buff_out_pos >= data.decompressed_pos) {
+ if ((result = decompress_bytes(funcs, &data, 0)) != PF_OK)
+ goto exit;
+ buff_out_pos = data.buff_out;
+ }
+ int64_t chunk_size = extra_len - write;
+ if (buff_out_pos + chunk_size > data.decompressed_pos) {
+ chunk_size = data.decompressed_pos - buff_out_pos;
+ }
+ if ((result = remmap_if_no_space(&data.dest, chunk_size)) != PF_OK)
+ goto exit;
+ memcpy(data.dest.pos, buff_out_pos, chunk_size);
+ data.dest.pos += chunk_size;
+ buff_out_pos += chunk_size;
+ write += chunk_size;
+ }
+ total_write += write;
+
+ blocks++;
+ if (blocks % BLOCK_COUNT_REPORT == 0) {
+ fprintf(stderr, "Number of processed patch blocks: %"PRIu64"\n", blocks);
+ }
+ }
+
+ result = PF_OK;
+
+exit:
+ fprintf(stderr, "Total processed blocks: %"PRIu64"\n", blocks);
+ free_data(&data, funcs);
+ return result;
+}
--- /dev/null
+/*
+ * tota-ua (previously libtota)
+ *
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <unistd.h>
+
+#include <libtar.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#define PF_OK 0
+#define PF_ERROR_OPEN_FILE 1
+#define PF_ERROR_MMAP 2
+#define PF_ERROR_INVALID_PATCH_FILE 3
+#define PF_ERROR_DECOMPRESSION 4
+#define PF_NEED_MORE_DATA 5
+
+#define BUFF_OUT_LEN 4096
+
+struct file_info {
+ int fd;
+ void *ptr;
+ off_t offset;
+ size_t mmapped_len;
+ uint8_t* pos;
+ size_t len;
+ int mmap_prot;
+ int mmap_flags;
+};
+
+struct bs_data {
+ struct file_info src;
+ struct file_info dest;
+ TAR *patch_tar;
+ size_t patch_len;
+ unsigned char buff_in[T_BLOCKSIZE];
+ unsigned char buff_out[BUFF_OUT_LEN];
+ size_t patch_remaining;
+ size_t available_in, available_out;
+ const uint8_t *compressed_pos;
+ uint8_t *decompressed_pos;
+ size_t total_size;
+ void *state;
+};
+
+struct dec_funcs {
+ int (*init)(struct bs_data *data);
+ int (*decompress)(struct bs_data *);
+ void (*clean)(struct bs_data *data);
+};
+
+extern int apply_patch(const char *source_file, const char *dest_file, TAR *patch_tar, size_t dest_size, struct dec_funcs *funcs);
--- /dev/null
+/*
+SHA-1 in C
+By Steve Reid <steve@edmweb.com>
+100% Public Domain
+
+Test Vectors (from FIPS PUB 180-1)
+"abc"
+ A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
+"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
+ 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
+A million repetitions of "a"
+ 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
+*/
+
+/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */
+/* #define SHA1HANDSOFF * Copies data before messing with it. */
+
+#define SHA1HANDSOFF
+
+#include <stdio.h>
+#include <string.h>
+
+/* for uint32_t */
+#include <stdint.h>
+
+#include "sha1.h"
+
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+/* blk0() and blk() perform the initial expand. */
+/* I got the idea of expanding during the round function from SSLeay */
+#if BYTE_ORDER == LITTLE_ENDIAN
+#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
+ |(rol(block->l[i],8)&0x00FF00FF))
+#elif BYTE_ORDER == BIG_ENDIAN
+#define blk0(i) block->l[i]
+#else
+#error "Endianness not defined!"
+#endif
+#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
+ ^block->l[(i+2)&15]^block->l[i&15],1))
+
+/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
+#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
+#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
+#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
+
+
+/* Hash a single 512-bit block. This is the core of the algorithm. */
+
+void SHA1Transform(
+ uint32_t state[5],
+ const unsigned char buffer[64]
+)
+{
+ uint32_t a, b, c, d, e;
+
+ typedef union
+ {
+ unsigned char c[64];
+ uint32_t l[16];
+ } CHAR64LONG16;
+
+#ifdef SHA1HANDSOFF
+ CHAR64LONG16 block[1]; /* use array to appear as a pointer */
+
+ memcpy(block, buffer, 64);
+#else
+ /* The following had better never be used because it causes the
+ * pointer-to-const buffer to be cast into a pointer to non-const.
+ * And the result is written through. I threw a "const" in, hoping
+ * this will cause a diagnostic.
+ */
+ CHAR64LONG16 *block = (const CHAR64LONG16 *) buffer;
+#endif
+ /* Copy context->state[] to working vars */
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+ /* 4 rounds of 20 operations each. Loop unrolled. */
+ R0(a, b, c, d, e, 0);
+ R0(e, a, b, c, d, 1);
+ R0(d, e, a, b, c, 2);
+ R0(c, d, e, a, b, 3);
+ R0(b, c, d, e, a, 4);
+ R0(a, b, c, d, e, 5);
+ R0(e, a, b, c, d, 6);
+ R0(d, e, a, b, c, 7);
+ R0(c, d, e, a, b, 8);
+ R0(b, c, d, e, a, 9);
+ R0(a, b, c, d, e, 10);
+ R0(e, a, b, c, d, 11);
+ R0(d, e, a, b, c, 12);
+ R0(c, d, e, a, b, 13);
+ R0(b, c, d, e, a, 14);
+ R0(a, b, c, d, e, 15);
+ R1(e, a, b, c, d, 16);
+ R1(d, e, a, b, c, 17);
+ R1(c, d, e, a, b, 18);
+ R1(b, c, d, e, a, 19);
+ R2(a, b, c, d, e, 20);
+ R2(e, a, b, c, d, 21);
+ R2(d, e, a, b, c, 22);
+ R2(c, d, e, a, b, 23);
+ R2(b, c, d, e, a, 24);
+ R2(a, b, c, d, e, 25);
+ R2(e, a, b, c, d, 26);
+ R2(d, e, a, b, c, 27);
+ R2(c, d, e, a, b, 28);
+ R2(b, c, d, e, a, 29);
+ R2(a, b, c, d, e, 30);
+ R2(e, a, b, c, d, 31);
+ R2(d, e, a, b, c, 32);
+ R2(c, d, e, a, b, 33);
+ R2(b, c, d, e, a, 34);
+ R2(a, b, c, d, e, 35);
+ R2(e, a, b, c, d, 36);
+ R2(d, e, a, b, c, 37);
+ R2(c, d, e, a, b, 38);
+ R2(b, c, d, e, a, 39);
+ R3(a, b, c, d, e, 40);
+ R3(e, a, b, c, d, 41);
+ R3(d, e, a, b, c, 42);
+ R3(c, d, e, a, b, 43);
+ R3(b, c, d, e, a, 44);
+ R3(a, b, c, d, e, 45);
+ R3(e, a, b, c, d, 46);
+ R3(d, e, a, b, c, 47);
+ R3(c, d, e, a, b, 48);
+ R3(b, c, d, e, a, 49);
+ R3(a, b, c, d, e, 50);
+ R3(e, a, b, c, d, 51);
+ R3(d, e, a, b, c, 52);
+ R3(c, d, e, a, b, 53);
+ R3(b, c, d, e, a, 54);
+ R3(a, b, c, d, e, 55);
+ R3(e, a, b, c, d, 56);
+ R3(d, e, a, b, c, 57);
+ R3(c, d, e, a, b, 58);
+ R3(b, c, d, e, a, 59);
+ R4(a, b, c, d, e, 60);
+ R4(e, a, b, c, d, 61);
+ R4(d, e, a, b, c, 62);
+ R4(c, d, e, a, b, 63);
+ R4(b, c, d, e, a, 64);
+ R4(a, b, c, d, e, 65);
+ R4(e, a, b, c, d, 66);
+ R4(d, e, a, b, c, 67);
+ R4(c, d, e, a, b, 68);
+ R4(b, c, d, e, a, 69);
+ R4(a, b, c, d, e, 70);
+ R4(e, a, b, c, d, 71);
+ R4(d, e, a, b, c, 72);
+ R4(c, d, e, a, b, 73);
+ R4(b, c, d, e, a, 74);
+ R4(a, b, c, d, e, 75);
+ R4(e, a, b, c, d, 76);
+ R4(d, e, a, b, c, 77);
+ R4(c, d, e, a, b, 78);
+ R4(b, c, d, e, a, 79);
+ /* Add the working vars back into context.state[] */
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+ /* Wipe variables */
+ a = b = c = d = e = 0;
+#ifdef SHA1HANDSOFF
+ memset(block, '\0', sizeof(block));
+#endif
+}
+
+
+/* SHA1Init - Initialize new context */
+
+void SHA1Init(
+ SHA1_CTX * context
+)
+{
+ /* SHA1 initialization constants */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xEFCDAB89;
+ context->state[2] = 0x98BADCFE;
+ context->state[3] = 0x10325476;
+ context->state[4] = 0xC3D2E1F0;
+ context->count[0] = context->count[1] = 0;
+}
+
+
+/* Run your data through this. */
+
+void SHA1Update(
+ SHA1_CTX * context,
+ const unsigned char *data,
+ uint32_t len
+)
+{
+ uint32_t i;
+
+ uint32_t j;
+
+ j = context->count[0];
+ if ((context->count[0] += len << 3) < j)
+ context->count[1]++;
+ context->count[1] += (len >> 29);
+ j = (j >> 3) & 63;
+ if ((j + len) > 63)
+ {
+ memcpy(&context->buffer[j], data, (i = 64 - j));
+ SHA1Transform(context->state, context->buffer);
+ for (; i + 63 < len; i += 64)
+ {
+ SHA1Transform(context->state, &data[i]);
+ }
+ j = 0;
+ }
+ else
+ i = 0;
+ memcpy(&context->buffer[j], &data[i], len - i);
+}
+
+
+/* Add padding and return the message digest. */
+
+void SHA1Final(
+ unsigned char digest[20],
+ SHA1_CTX * context
+)
+{
+ unsigned i;
+
+ unsigned char finalcount[8];
+
+ unsigned char c;
+
+#if 0 /* untested "improvement" by DHR */
+ /* Convert context->count to a sequence of bytes
+ * in finalcount. Second element first, but
+ * big-endian order within element.
+ * But we do it all backwards.
+ */
+ unsigned char *fcp = &finalcount[8];
+
+ for (i = 0; i < 2; i++)
+ {
+ uint32_t t = context->count[i];
+
+ int j;
+
+ for (j = 0; j < 4; t >>= 8, j++)
+ *--fcp = (unsigned char) t}
+#else
+ for (i = 0; i < 8; i++)
+ {
+ finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */
+ }
+#endif
+ c = 0200;
+ SHA1Update(context, &c, 1);
+ while ((context->count[0] & 504) != 448)
+ {
+ c = 0000;
+ SHA1Update(context, &c, 1);
+ }
+ SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
+ for (i = 0; i < 20; i++)
+ {
+ digest[i] = (unsigned char)
+ ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
+ }
+ /* Wipe variables */
+ memset(context, '\0', sizeof(*context));
+ memset(&finalcount, '\0', sizeof(finalcount));
+}
+
+void SHA1(
+ char *hash_out,
+ const char *str,
+ int len)
+{
+ SHA1_CTX ctx;
+ unsigned int ii;
+
+ SHA1Init(&ctx);
+ for (ii=0; ii<len; ii+=1)
+ SHA1Update(&ctx, (const unsigned char*)str + ii, 1);
+ SHA1Final((unsigned char *)hash_out, &ctx);
+}
+
--- /dev/null
+#ifndef SHA1_H
+#define SHA1_H
+
+/*
+ SHA-1 in C
+ By Steve Reid <steve@edmweb.com>
+ 100% Public Domain
+ */
+
+#include "stdint.h"
+
+typedef struct
+{
+ uint32_t state[5];
+ uint32_t count[2];
+ unsigned char buffer[64];
+} SHA1_CTX;
+
+void SHA1Transform(
+ uint32_t state[5],
+ const unsigned char buffer[64]
+ );
+
+void SHA1Init(
+ SHA1_CTX * context
+ );
+
+void SHA1Update(
+ SHA1_CTX * context,
+ const unsigned char *data,
+ uint32_t len
+ );
+
+void SHA1Final(
+ unsigned char digest[20],
+ SHA1_CTX * context
+ );
+
+void SHA1(
+ char *hash_out,
+ const char *str,
+ int len);
+
+#endif /* SHA1_H */
--- /dev/null
+<manifest>
+ <request>
+ <domain name="_" />
+ </request>
+ <assign>
+ <filesystem path="/usr/bin/*" exec_label="none" />
+ </assign>
+</manifest>
+