From 9fececf1ad5fc06169c170cea555cf17079fc91a Mon Sep 17 00:00:00 2001 From: Sunmin Lee Date: Mon, 10 Jul 2017 15:55:45 +0900 Subject: [PATCH 02/13] Initial codes of libtota Change-Id: I38d3ff5839645d71cc81c77336a5d5d015d222b6 --- CMakeLists.txt | 74 ++ LICENSE.Apache-2.0 | 202 ++++ LICENSE.BSD-2-Clause | 19 + LICENSE.BSD-3-Clause | 28 + UPG/CreatePatch.py | 1137 +++++++++++++++++++++ UPG/dzImagescript.sh | 255 +++++ UPG/ss_bsdiff | Bin 0 -> 212699 bytes UPG/ss_bspatch | Bin 0 -> 180101 bytes UPG/unpack.sh | 236 +++++ bsdiff/CMakeLists.txt | 23 + bsdiff/ss_bsdiff.c | 597 +++++++++++ bsdiff/ss_bspatch.c | 411 ++++++++ packaging/libtota.spec | 79 ++ ss_engine/SS_Common.c | 117 +++ ss_engine/SS_Common.h | 64 ++ ss_engine/SS_Engine_Errors.h | 116 +++ ss_engine/SS_Engine_Update.h | 551 ++++++++++ ss_engine/SS_FSUpdate.h | 349 +++++++ ss_engine/SS_ImageUpdate.c | 34 + ss_engine/SS_ImageUpdate.h | 186 ++++ ss_engine/SS_MultiProcessUpdate.h | 174 ++++ ss_engine/SS_Nand.c | 123 +++ ss_engine/SS_Nand.h | 31 + ss_engine/SS_UPI.c | 1987 +++++++++++++++++++++++++++++++++++++ ss_engine/SS_UPI.h | 86 ++ ss_engine/fota_common.h | 142 +++ ss_engine/fota_log.h | 76 ++ ss_engine/fota_tar.c | 1114 +++++++++++++++++++++ ss_engine/fota_tar.h | 31 + ss_engine/ua.h | 125 +++ ss_patch/sha1.c | 405 ++++++++ ss_patch/sha1.h | 114 +++ ss_patch/ss_bspatch.c | 368 +++++++ ss_patch/ss_patchdelta.c | 1230 +++++++++++++++++++++++ ss_patch/ss_patchdelta.h | 79 ++ tota.pc.in | 13 + 36 files changed, 10576 insertions(+) create mode 100755 CMakeLists.txt create mode 100755 LICENSE.Apache-2.0 create mode 100755 LICENSE.BSD-2-Clause create mode 100755 LICENSE.BSD-3-Clause create mode 100755 UPG/CreatePatch.py create mode 100755 UPG/dzImagescript.sh create mode 100755 UPG/ss_bsdiff create mode 100755 UPG/ss_bspatch create mode 100755 UPG/unpack.sh create mode 100755 bsdiff/CMakeLists.txt create mode 100755 bsdiff/ss_bsdiff.c create mode 100755 bsdiff/ss_bspatch.c create mode 100755 packaging/libtota.spec create mode 100755 ss_engine/SS_Common.c create mode 100755 ss_engine/SS_Common.h create mode 100755 ss_engine/SS_Engine_Errors.h create mode 100755 ss_engine/SS_Engine_Update.h create mode 100755 ss_engine/SS_FSUpdate.h create mode 100755 ss_engine/SS_ImageUpdate.c create mode 100755 ss_engine/SS_ImageUpdate.h create mode 100755 ss_engine/SS_MultiProcessUpdate.h create mode 100755 ss_engine/SS_Nand.c create mode 100755 ss_engine/SS_Nand.h create mode 100755 ss_engine/SS_UPI.c create mode 100755 ss_engine/SS_UPI.h create mode 100755 ss_engine/fota_common.h create mode 100755 ss_engine/fota_log.h create mode 100755 ss_engine/fota_tar.c create mode 100755 ss_engine/fota_tar.h create mode 100755 ss_engine/ua.h create mode 100755 ss_patch/sha1.c create mode 100755 ss_patch/sha1.h create mode 100755 ss_patch/ss_bspatch.c create mode 100755 ss_patch/ss_patchdelta.c create mode 100755 ss_patch/ss_patchdelta.h create mode 100755 tota.pc.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..814ee22 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,74 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +PROJECT(tota C) + +SET(SRCS + ss_engine/SS_Common.c + ss_patch/sha1.c + ss_engine/SS_UPI.c + ss_engine/fota_tar.c + ss_patch/ss_bspatch.c + ss_patch/ss_patchdelta.c +) +SET(HEADERS + ss_engine/fota_common.h + ss_engine/fota_log.h + ss_engine/fota_tar.h + ss_engine/SS_Common.h + ss_engine/SS_Engine_Errors.h + ss_engine/SS_Engine_Update.h + ss_engine/SS_FSUpdate.h + ss_engine/SS_ImageUpdate.h + ss_engine/SS_MultiProcessUpdate.h + ss_engine/SS_Nand.h + ss_engine/SS_UPI.h +) +#SET(EXEC_PREFIX "\${prefix}") +SET(PREFIX ${CMAKE_INSTALL_PREFIX}) +SET(LIBDIR "${PREFIX}") +SET(VERSION_MAJOR 1) +SET(VERSION ${VERSION_MAJOR}.0.0) +SET(LIBNAME "tota") + +#INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/ss_engine) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/ss_patch) + +INCLUDE(FindPkgConfig) +pkg_check_modules(packages REQUIRED + lib7zip +) + +FOREACH(flag ${packages_CFLAGS}) + SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") +ENDFOREACH(flag) + +SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fvisibility=hidden -Wall") +SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -g ") + +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}") + +FIND_PROGRAM(UNAME NAMES uname) +EXEC_PROGRAM("${UNAME}" ARGS "-m" OUTPUT_VARIABLE "ARCH") +IF("${ARCH}" STREQUAL "arm") + ADD_DEFINITIONS("-DTARGET") + MESSAGE("add -DTARGET") +ENDIF("${ARCH}" STREQUAL "arm") + +#FIND_PROGRAM(MARSHALTOOL NAMES glib-genmarshal) +#FIND_PROGRAM(DBUS_BINDING_TOOL NAMES dbus-binding-tool) +SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") + +CONFIGURE_FILE(tota.pc.in tota.pc @ONLY) + +ADD_LIBRARY(${LIBNAME} STATIC ${SRCS}) +TARGET_LINK_LIBRARIES(${LIBNAME} ${packages_LDFLAGS}) +#CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/modulename-api.pc.in ${CMAKE_CURRENT_BINARY_DIR}/modulename-api.pc @ONLY) +#INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/modulename-api.pc DESTINATION lib/pkgconfig) +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/tota.pc DESTINATION lib/pkgconfig) +FOREACH(hfile ${HEADERS}) + INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${hfile} DESTINATION include) +ENDFOREACH(hfile) +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/libtota.a DESTINATION /usr/lib) + +ADD_SUBDIRECTORY(bsdiff) diff --git a/LICENSE.Apache-2.0 b/LICENSE.Apache-2.0 new file mode 100755 index 0000000..fef8c29 --- /dev/null +++ b/LICENSE.Apache-2.0 @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + diff --git a/LICENSE.BSD-2-Clause b/LICENSE.BSD-2-Clause new file mode 100755 index 0000000..5a81ff9 --- /dev/null +++ b/LICENSE.BSD-2-Clause @@ -0,0 +1,19 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSE.BSD-3-Clause b/LICENSE.BSD-3-Clause new file mode 100755 index 0000000..e436781 --- /dev/null +++ b/LICENSE.BSD-3-Clause @@ -0,0 +1,28 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + Neither the name of the Cisco Systems, Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/UPG/CreatePatch.py b/UPG/CreatePatch.py new file mode 100755 index 0000000..f45f3e3 --- /dev/null +++ b/UPG/CreatePatch.py @@ -0,0 +1,1137 @@ +#!/usr/bin/python + +import sys + + +if sys.hexversion < 0x02040000: + print >> sys.stderr, "Python 2.4 or newer is required." + sys.exit(1) + +import sys +import os +import filecmp +import shutil +import subprocess +import re +import ntpath +import zipfile +import datetime +import hashlib +import operator +import locale +import errno +import logging +import glob +import apt + +''' +Diff two folders and create delta using SS_BSDIFF +Will maintain same format of script that will be generated when we use diffutil + +1. Create a list of files in each Base folders, +2. These files will fall into one these below categories: + 1) Only in OLD - Should be deleted + 2) Only in NEW - Should be added or renamed accordingly + 3) File exists in both directories but contents are different - Create Diff. + 4) File name is same but TYPE can change (File to Folder, Folder to Link etc.) + 5) Duplicates in the list of Deletes and News + 6) Close matching diffs even though name changes across directories. (for matching extension) + 7) Clearing empty directories after Moves or diffs under Rename. + +Current Case +1. Given two folders, from list of REMOVED and NEW files find if there +is version change and create diff between them + +TODO +Want to extend the same script for entire DIFF generation and replace TOTAlib.sh file +Catching errors at all stages. SHOULD exit & return error in case of failure +''' + +def global_paths(): + global DIFF_UTIL + global ZIPUTIL + global NEW_FILES_PATH + global NEW_FILES_FOLDER + global NEW_FILES_ZIP_NAME + global SYMLINK_TYPE + global ATTR_DOC_EXT + global SYMLINK_DOC_NAME + global DIFF_PREFIX + global DIFF_SUFFIX + global SUPPORT_RENAME + global NEW_PREFIX + global DIFFPATCH_UTIL + global SUPPORT_CONTAINERS + global FULL_IMG + global DELTA_IMG + global DELTA_FS + global EXTRA + global COMMON_BIN_PATH + global MEM_REQ + global EMPTY + +COMMON_BIN_PATH = "../../common/bin/" +DIFF_UTIL = "./ss_bsdiff" +DIFFPATCH_UTIL = "./ss_bspatch" +ZIPUTIL = "p7zip " +#ZIPUTIL = "7z a system.7z " +NEW_FILES_PATH = "system" +NEW_FILES_FOLDER = "system" +NEW_FILES_ZIP_NAME = "system.7z" +SYMLINK_TYPE = "SYM" +ATTR_DOC_EXT = "_attr.txt" +SYMLINK_DOC_NAME = "_sym.txt" +PART_DOC_EXT = ".txt" +DIFF_PREFIX = "diff" +DIFF_SUFFIX = ".delta" +NEW_PREFIX = 'new' +FULL_IMG = "FULL_IMG" +DELTA_IMG = "DELTA_IMG" +DELTA_FS = "DELTA_FS" +EXTRA = "EXTRA" +LOGFILE = "Delta.log" +EMPTY = "" +MEM_REQ = 0 + +SUPPORT_RENAME = "TRUE" #Use appropriate name +SUPPORT_CONTAINERS = "FALSE" +SUPPORT_DZIMAGE = "TRUE" + +TEST_MODE = "FALSE" + +def main(): + logging.basicConfig(filename=LOGFILE, level=logging.DEBUG) + global AttributeFile + global GenerateDiffAttr + try: + + if len(sys.argv) < 5: + sys.exit('Usage: CreatePatch.py UPDATE_TYPE PARTNAME OLDBASE NEWBASE OUTFOLDER') + UPDATE_TYPE = sys.argv[1] + PART_NAME = sys.argv[2] # lets make this also optional + + BASE_OLD = sys.argv[3] + BASE_NEW = sys.argv[4] + OUT_DIR = sys.argv[5] + ATTR_OLD = EMPTY + ATTR_NEW = EMPTY + UPDATE_CFG_PATH = EMPTY + GenerateDiffAttr = "FALSE" + if UPDATE_TYPE == DELTA_FS: + #instead of arguments check it in outdirectory ? + if len(sys.argv) == 9: + ATTR_OLD = sys.argv[6] + ATTR_NEW = sys.argv[7] + UPDATE_CFG_PATH = '../'+sys.argv[8] + GenerateDiffAttr = "TRUE" + + elif UPDATE_TYPE == DELTA_IMG or UPDATE_TYPE == FULL_IMG: + if len(sys.argv) == 7: + #Use path in better way + UPDATE_CFG_PATH = '../'+sys.argv[6] + + global DIFF_UTIL + global DIFFPATCH_UTIL + if not (os.path.isfile(DIFF_UTIL) and os.access(DIFF_UTIL, os.X_OK)): + DIFF_UTIL = COMMON_BIN_PATH+DIFF_UTIL + DIFFPATCH_UTIL = COMMON_BIN_PATH+DIFFPATCH_UTIL + if not (os.path.isfile(DIFF_UTIL) and os.access(DIFF_UTIL, os.X_OK)): + print >> sys.stderr, "Diff Util Does NOT exist -- ABORT" + logging.info ('Diff Util Does NOT exist -- ABORT') + sys.exit(1) + + start = datetime.datetime.now().time() + logging.info('*************** ENTERED PYTHON SCRIPT *****************') + logging.info('Arguments Passed: [UpdateType - %s][Part Name - %s] [BaseOld - %s] [BaseNew - %s] \n [OUTPUTDir - %s] [BASE ATTR - %s] [TARGET ATTR - %s]'% (UPDATE_TYPE, PART_NAME, BASE_OLD, BASE_NEW, OUT_DIR, ATTR_OLD, ATTR_NEW)) + + ensure_dir_exists(OUT_DIR) + if GenerateDiffAttr == "TRUE": + if not (os.path.isfile(ATTR_OLD) and os.path.isfile(ATTR_NEW)): + print >> sys.stderr, "Attributes missing -- ABORT" + sys.exit(1) + + + # Should check if APT is supported on other linux flavours + cache = apt.Cache() + if cache['p7zip'].is_installed and cache['attr'].is_installed and cache['tar'].is_installed: + logging.info ('Basic utils installed') + + if UPDATE_TYPE == FULL_IMG: + SS_mk_full_img(BASE_OLD, BASE_NEW, OUT_DIR, PART_NAME, UPDATE_CFG_PATH) + elif UPDATE_TYPE == DELTA_IMG: + SS_mk_delta_img(BASE_OLD, BASE_NEW, OUT_DIR, PART_NAME, UPDATE_CFG_PATH) + elif UPDATE_TYPE == DELTA_FS: + AttributeFile = ATTR_NEW + ATTR_FILE = OUT_DIR+'/'+PART_NAME+ATTR_DOC_EXT + Diff_AttrFiles(ATTR_OLD, ATTR_NEW, ATTR_FILE) + Old_files, Old_dirs = Get_Files(BASE_OLD) + New_files, New_dirs = Get_Files(BASE_NEW) + SS_Generate_Delta(PART_NAME, BASE_OLD, Old_files, Old_dirs, BASE_NEW, New_files, New_dirs, OUT_DIR, ATTR_FILE) + + if not UPDATE_CFG_PATH == EMPTY: + SS_update_cfg(PART_NAME, UPDATE_CFG_PATH) + + + elif UPDATE_TYPE == EXTRA: + print('UPDATE_TYPE ---- EXTRA') + else: + print('UPDATE_TYPE ---- UNKNOWN FORMAT') + + if GenerateDiffAttr == "TRUE": + os.remove(ATTR_OLD) + os.remove(ATTR_NEW) + end = datetime.datetime.now().time() + + logging.info('Max Memory requried to upgrade [%s] is [%d]' % (PART_NAME, MEM_REQ)) + logging.info('*************** DONE WITH PYTHON SCRIPT ***************') + logging.info('Time start [%s] - Time end [%s]' % (start, end)) + print('Done with [%s][%d] ---- Time start [%s] - Time end [%s]' % (PART_NAME, MEM_REQ, start, end)) + except: + logging.error('Usage: {} '.format(os.path.basename(sys.argv[0]))) + raise + + +def SS_update_cfg(DELTA_BIN, UPDATE_CFG_PATH): + f = open(UPDATE_CFG_PATH, 'r') + lines = f.readlines() + f.close() + f = open(UPDATE_CFG_PATH, 'w') + for line in lines: + ConfigItems = line.split() + if ConfigItems[0] == DELTA_BIN: + DELTA = ConfigItems[1] + logging.info ('Updating %s config' % DELTA_BIN) + line = line.rstrip('\n') + Value = MEM_REQ + line = line.replace(line, line+'\t'+str(Value)+'\n') + f.write(line) + else: + f.write(line) + f.close() + +def SS_mk_delta_img(BASE_OLD, BASE_NEW, OUT_DIR, DELTA_BIN, UPDATE_CFG_PATH): + #for sizes + + ZIMAGE_SCRIPT = COMMON_BIN_PATH+'./dzImagescript.sh' + ZIMAGE_OLD = BASE_OLD+'_unpacked' + ZIMAGE_NEW = BASE_NEW+'_unpacked' + DZIMAGE_HEADER = 'UnpackdzImage' + DZIMAGE_SEP = ':' + + oldsize_d= os.path.getsize(BASE_OLD) + newsize_d= os.path.getsize(BASE_NEW) + SHA_BIN_DEST= hash_file(BASE_NEW) + SHA_BIN_BASE=hash_file(BASE_OLD) + + #incase UPDATE CFG is empty + DELTA = DELTA_BIN + SS_UpdateSize(BASE_OLD, BASE_NEW) + #Should throw error if PART NAME NOT found?? + if not UPDATE_CFG_PATH == EMPTY: + f = open(UPDATE_CFG_PATH, 'r') + lines = f.readlines() + f.close() + f = open(UPDATE_CFG_PATH, 'w') + for line in lines: + ConfigItems = line.split() + if ConfigItems[0] == DELTA_BIN: + logging.info ('Updating %s config' % DELTA_BIN) + DELTA = ConfigItems[1] + line = line.rstrip('\n') + line = line.replace(line, line+'\t'+str(oldsize_d)+'\t\t'+str(newsize_d)+'\t\t'+str(SHA_BIN_BASE)+'\t\t'+str(SHA_BIN_DEST)+'\n') + f.write(line) + else: + f.write(line) + f.close() + + #Any validation checks required? + if (DELTA_BIN == "zImage" or DELTA_BIN == "dzImage" or DELTA_BIN == "KERNEL" or DELTA_BIN == "BOOT") and SUPPORT_DZIMAGE == "TRUE": + + #Unpack Old and New Images for creating delta + subprocess.call([ZIMAGE_SCRIPT, '-u', BASE_OLD]) + subprocess.call([ZIMAGE_SCRIPT, '-u', BASE_NEW]) + + DeltaFiles = [] + Old_files, Old_dirs = Get_Files(ZIMAGE_OLD) + New_files, New_dirs = Get_Files(ZIMAGE_NEW) + + patchLoc = '%s/%s_temp' % (OUT_DIR, DELTA_BIN) + ensure_dir_exists(patchLoc) + + for elt in New_files: + if elt in Old_files: + src_file = ZIMAGE_OLD+'/'+elt + dst_file = ZIMAGE_NEW+'/'+elt + if not filecmp.cmp(src_file, dst_file): + patch = '%s/%s' % (patchLoc,elt) + DeltaFiles.append(patch) + subprocess.call([DIFF_UTIL,src_file,dst_file,patch]) + logging.info('Make dz Image %s <--> %s ==> %s %s' % (src_file, dst_file , DELTA_BIN, patch)) + + #Append all delta files to make image.delta + + #HEADER FORMAT MAGICNAME:FILECOUNT:[FILENAME:FILESIZE:][FILECONTENT/S] + HeaderStr = DZIMAGE_HEADER+DZIMAGE_SEP+'%d' % len(DeltaFiles) + HeaderStr = HeaderStr+DZIMAGE_SEP + + with open(OUT_DIR+'/'+DELTA, 'w') as DeltaFile: + for fname in DeltaFiles: + DeltaSize = os.path.getsize(fname) + HeaderStr = HeaderStr+path_leaf(fname)+DZIMAGE_SEP+'%d' % DeltaSize + HeaderStr = HeaderStr+DZIMAGE_SEP + #Using 128 bytes as max Header. + logging.info('zImage Header - %s' % HeaderStr.ljust(128,'0')) + DeltaFile.write(HeaderStr.ljust(128,'0')) + for fname in DeltaFiles: + with open(fname) as infile: + DeltaFile.write(infile.read()) + infile.close() + + DeltaFile.close() + shutil.rmtree(patchLoc) + shutil.rmtree(ZIMAGE_OLD) + shutil.rmtree(ZIMAGE_NEW) + #Do we need to incorprate Max memory required for backup?? + + else: + patchLoc = '%s/%s' % (OUT_DIR, DELTA) + subprocess.call([DIFF_UTIL,BASE_OLD,BASE_NEW,patchLoc]) + logging.info('Make Delta Image %s <--> %s ==> %s %s' % (BASE_OLD, BASE_NEW , DELTA_BIN, patchLoc)) + + + +def SS_mk_full_img(BASE_OLD, BASE_NEW, OUT_DIR, DELTA_BIN ,UPDATE_CFG_PATH): + logging.info('Make Full Image %s <--> %s ==> %s' % (BASE_OLD, BASE_NEW ,DELTA_BIN)) + oldsize_d= os.path.getsize(BASE_OLD) + newsize_d= os.path.getsize(BASE_NEW) + SHA_BIN_DEST= hash_file(BASE_NEW) + SHA_BIN_BASE=hash_file(BASE_OLD) + #echo -e "\t${oldsize_d}\t\t${newsize_d}\t\t${SHA_BIN_BASE}\t\t${SHA_BIN_DEST}" >> ${DATA_DIR}/update_new.cfg + SS_UpdateSize(BASE_OLD, BASE_NEW) + + if not UPDATE_CFG_PATH == EMPTY: + f = open(UPDATE_CFG_PATH, 'r') + lines = f.readlines() + f.close() + f = open(UPDATE_CFG_PATH, 'w') + for line in lines: + ConfigItems = line.split() + if ConfigItems[0] == DELTA_BIN: + logging.info ('Updating %s config' % DELTA_BIN) + DELTA = ConfigItems[1] + line = line.rstrip('\n') + line = line.replace(line, line+'\t'+str(oldsize_d)+'\t\t'+str(newsize_d)+'\t\t'+str(SHA_BIN_BASE)+'\t\t'+str(SHA_BIN_DEST)+'\n') + f.write(line) + else: + f.write(line) + f.close() + +def zipdir(path, zip): + for root, dirs, files in os.walk(path): + for file in files: + zip.write(os.path.join(root, file)) + +def ensure_dir_exists(path): + if not os.path.exists(path): + os.makedirs(path) + #shutil.rmtree(path) + #os.makedirs(path) + + +def path_leaf(path): + head, tail = ntpath.split(path) #This is for windows?? Recheck + return tail + +def path_head(path): + head, tail = ntpath.split(path) + return head + +def difflines(list1, list2): + c = set(list1).union(set(list2)) + d = set(list1).intersection(set(list2)) + return list(c-d) + +#Creating Diff between OLD and NEW attribute files v12 +def Diff_AttrFiles(ATTR_OLD, ATTR_NEW, ATTR_FILE): + if GenerateDiffAttr == "FALSE": + return + with open(ATTR_OLD, 'r') as f_old: + lines1 = set(f_old.read().splitlines()) + + with open(ATTR_NEW, 'r') as f_new: + lines2 = set(f_new.read().splitlines()) + + lines = difflines(lines2, lines1) + with open(ATTR_FILE, 'w+') as file_out: + for line in lines: + if line not in lines1: + logging.info('Diff_AttrFiles - %s' % line) + file_out.write(line+'\n') + + f_new.close() + f_old.close() + file_out.close() + + + +def Update_Attr(RequestedPath, Type, File_Attibutes, Sym_Attibutes): + #Full File Path should MATCH + if GenerateDiffAttr == "FALSE": + return + FilePath = '"/'+RequestedPath+'"' + #print ('FilePath - %s'% (FilePath)) + with open(AttributeFile) as f: + for line in f: + if FilePath in line: + if Type == SYMLINK_TYPE: + Sym_Attibutes.append(line) + else: + File_Attibutes.append(line) + + +'''This function returns the SHA-1 hash of the file passed into it''' +def hash_file(filename): + + # make a hash object + h = hashlib.sha1() + + # open file for reading in binary mode + with open(filename,'rb') as file: + # loop till the end of the file + chunk = 0 + while chunk != b'': + # read only 1024 bytes at a time + chunk = file.read(1024*1024) + h.update(chunk) + + # return the hex representation of digest + return h.hexdigest() + +def find_dupes_dir(BASE_OLD, BASE_NEW): + dups = {} + fdupes = {} + print('Finding Duplicates in - %s %s' % (BASE_OLD, BASE_NEW)) + logging.info('Finding Duplicates in - %s %s' % (BASE_OLD, BASE_NEW)) + for rootbase, subdirsB, fileListB in os.walk(BASE_OLD): + #print('Scanning %s...' % rootbase) + for filename in fileListB: + path = os.path.join(rootbase, filename) + if os.path.islink(path): + continue + # Calculate hash + file_hash = hash_file(path) + dups[file_hash] = path + + for roottarget, subdirsT, fileListT in os.walk(BASE_NEW): + #print('Scanning %s...' % roottarget) + for filename in fileListT: + # Get the path to the file + path = os.path.join(roottarget, filename) + if os.path.islink(path): + continue + # Calculate hash + file_hash = hash_file(path) + # Add or append the file path + if file_hash in dups: + BaseStr = dups.get(file_hash) + Baseloc = path.find('/') + TarLoc = BaseStr.find('/') + if not path[Baseloc:] == BaseStr[TarLoc:]: + logging.info('Dupes - %s ==> %s' % (path[Baseloc:], BaseStr[TarLoc:])) + fdupes[path] = BaseStr + logging.info('Total Duplicate files %d' % (len(fdupes))) + return fdupes + + +def find_dupes_list(BASE_OLD, BASE_NEW, fileListB, fileListT): + dups = {} + fdupes = {} + print('Finding Duplicates in - %s %s' % (BASE_OLD, BASE_NEW)) + + for filename in fileListB: + Src_File = BASE_OLD+'/'+filename + if os.path.islink(Src_File) or os.path.isdir(Src_File): + continue + # Calculate hash + file_hash = hash_file(Src_File) + dups[file_hash] = Src_File + + + for filename in fileListT: + Dest_File = BASE_NEW+'/'+filename + if os.path.islink(Dest_File) or os.path.isdir(Dest_File): + continue + # Calculate hash + file_hash = hash_file(Dest_File) + if file_hash in dups: + BaseStr = dups.get(file_hash) + Baseloc = BaseStr.find('/') + if not BaseStr[Baseloc:] == filename: + #print('Dupes - %s ==> %s' % (BaseStr[Baseloc:], filename)) + fdupes[BaseStr] = filename + + logging.info('Total Duplicate files %d' % (len(fdupes))) + return fdupes + +def SS_UpdateSize(src_file, dst_file): + global MEM_REQ + oldsize_d= os.path.getsize(src_file) + newsize_d= os.path.getsize(dst_file) + if oldsize_d >= newsize_d: + Max = newsize_d + else: + Max = oldsize_d + if MEM_REQ < Max: + MEM_REQ = Max + + + +def SS_Generate_Delta(PART_NAME, BASE_OLD, Old_files, Old_dirs, BASE_NEW, New_files, New_dirs, OUT_DIR, ATTR_FILE): + print('Going from %d files to %d files' % (len(Old_files), len(New_files))) + logging.info('Going from %d files to %d files' % (len(Old_files), len(New_files))) + + # First let's fill up these categories + files_new = [] + files_removed = [] + Dir_removed = [] + Dir_Added = [] + files_changed = [] + files_unchanged = [] + files_renamed = [] + File_Attibutes = [] + Sym_Attibutes = [] + + files_Del_List = {} + files_New_List = {} + MyDict_Patches = {} + + + + PWD = os.getcwd() + + # Generate NEW List + for elt in New_files: + if elt not in Old_files: + files_new.append(elt) + logging.info('New files %s' % elt) + + # Generate Delete List + for elt in Old_files: + if elt not in New_files: + # Cant we just append it here only if this is NOT a directory???? so that we have list of removed files ONLY. including directories + files_removed.append(elt) + logging.info('Old files %s' % elt) + + + for elt in Old_dirs: + #print('List of Old Dirs %s' % elt) + if elt not in New_dirs: + Dir_removed.append(elt) + #print('Old Dirs %s' % elt+'/') + + for elt in New_dirs: + if elt not in Old_dirs: + Dir_Added.append(elt) + #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + # What files have changed contents but not name/path? + for elt in New_files: + if elt in Old_files: + #Both are symbolic linkes and they differ + src_file = BASE_OLD+'/'+elt + dst_file = BASE_NEW+'/'+elt + #print('Files Changed - %s -%s' % (src_file,dst_file)) + if os.path.islink(src_file) and os.path.islink(dst_file): + if not os.readlink(src_file) == os.readlink(dst_file): + files_changed.append(elt) + #print('%d Sym link files changed' % len(files_changed)) + logging.info('Sym links Changed - %s' % elt) + else: + files_unchanged.append(elt) + #Both are Normal files and they differ. (Is file returns true in case of symlink also, so additional check to find either of the file is symlink) + elif (not os.path.islink(src_file) or os.path.islink(dst_file)) and os.path.isfile(src_file) and os.path.isfile(dst_file): + if not filecmp.cmp(src_file, dst_file): + files_changed.append(elt) + #print('%d Normal files changed' % len(files_changed)) + #print('Files Changed - %s' % elt) + else: + files_unchanged.append(elt) + #File types differ between BASE and TARGET + else: + logging.info('Files are of diff types but same names Src- %s Des- %s' % (src_file, dst_file)) + #Both file types have changed and they differ + #Case 1: First Delete the OLD entry file type (Be it anything) + #Processing and updating partition txt file will be done under REMOVED case and NEW files case accordingly, we just make an entry here + files_removed.append(elt) + files_new.append(elt) + + + + #Currently if Version or number is the first character of the file, then we are NOT making any diffs. + if SUPPORT_RENAME == "TRUE": + for elt in files_removed: + if os.path.isfile(BASE_OLD+'/'+elt): + FileName = path_leaf(elt) + entries = re.split('[0-9]' , FileName) + #Gives the STRING part of NAME. if name starts with version then later part wil b string + #print('Entires under removed list after split - %s %s - %s' % (FileName, entries[0], elt)) + #If version is starting at the begining of the string?? shd we hav additional check for such cases?? + if len(entries[0]) > 0: + files_Del_List.update({entries[0]: elt}) + + for elt in files_new: + if os.path.isfile(BASE_NEW+'/'+elt): + FileName = path_leaf(elt) + entries = re.split('[0-9]' , FileName) + #print('Entires under NEWfiles list after split - %s %s - %s' % (FileName, entries[0], elt)) + if len(entries[0]) > 0: + files_New_List.update({entries[0]: elt}) + + for key, value in files_Del_List.iteritems(): + #print('Key value pair -%s -%s' % (key, value)) + if key in files_New_List: + # this file is the same name in both! + src_file = BASE_OLD+'/'+value + dst_file = BASE_NEW+'/'+files_New_List[key] + olddirpath = path_head(files_New_List[key]) + newdirpath = path_head(value) + if os.path.islink(src_file) or os.path.islink(dst_file): + logging.debug('Cannot diff as one of them is Symlink') + else: + #Pick the best diff of same type and diff names + files_renamed.append([files_New_List[key], value]) + files_removed.remove(value) + files_new.remove(files_New_List[key]) + + ''' + Patch Section + Partition.txt contains Protocol for UPI + Types Supported: DIFFS, MOVES, NEWS, DELETES, SYMDIFFS, SYMNEWS. + ''' + Sym_Diff_Cnt = 0 + Sym_New_Cnt = 0; + Del_Cnt = 0 + New_Cnt = 0 + Diff_Cnt = 0 + Move_Cnt = 0 + + SymLinkDoc = OUT_DIR+'/'+PART_NAME+SYMLINK_DOC_NAME + Partition_Doc = open(OUT_DIR+'/'+PART_NAME+'.txt','w') + Partition_Doc_SymLinks = open(SymLinkDoc,'w') + + print("writing diff'ed changed files...") + for elt in files_changed: + dst_file = BASE_NEW+'/'+elt + src_file = BASE_OLD+'/'+elt + #Both files are symbolic links and they differ + if os.path.islink(dst_file) and os.path.islink(src_file): + #Both are symlinks and they differ + logging.debug(' File Changed is Link %s ' % dst_file) + patch = os.readlink(dst_file) + Sym_Diff_Cnt = Sym_Diff_Cnt + 1 + Partition_Doc_SymLinks.write('SYM:DIFF:%s:%s:%s\n' % (elt, elt, patch)) + Update_Attr(elt, "SYM", File_Attibutes, Sym_Attibutes) + #Both are NORMAL files and they differ + elif (not os.path.islink(src_file) or os.path.islink(dst_file)) and os.path.isfile(dst_file) and os.path.isfile(src_file): + #Both are files and they differ + Diff_Cnt = Diff_Cnt + 1 + patchName = (DIFF_PREFIX+'%d_%s'+DIFF_SUFFIX) % (Diff_Cnt, path_leaf(elt)) + patchLoc = '%s/%s' % (OUT_DIR, patchName) + logging.debug(' File Differ %s %s' % (src_file, dst_file)) + SS_UpdateSize(src_file, dst_file) + if SUPPORT_CONTAINERS == "TRUE": + if src_file.endswith('.zip') and dst_file.endswith('.zip'): + FORMAT = "ZIP" + Partition_Doc.write('DIFF:ZIP:%s:%s:%s:%s:%s/\n' % (elt, elt, hash_file(src_file), hash_file(dst_file), patchName)) + compute_containerdelta(src_file, dst_file, FORMAT, OUT_DIR+'/'+patchName, Partition_Doc) + elif src_file.endswith('.tpk') and dst_file.endswith('.tpk'): + FORMAT = "TPK" + Partition_Doc.write('DIFF:TPK:%s:%s:%s:%s:%s/\n' % (elt, elt, hash_file(src_file), hash_file(dst_file), patchName)) + compute_containerdelta(src_file, dst_file, FORMAT, OUT_DIR+'/'+patchName, Partition_Doc) + else: + FORMAT = "REG" + Partition_Doc.write('DIFF:REG:%s:%s:%s:%s:%s\n' % (elt, elt, hash_file(src_file), hash_file(dst_file), patchName)) + subprocess.call([DIFF_UTIL,src_file,dst_file,patchLoc]) + else: + FORMAT = "REG" + Partition_Doc.write('DIFF:REG:%s:%s:%s:%s:%s\n' % (elt, elt, hash_file(src_file), hash_file(dst_file), patchName)) + subprocess.call([DIFF_UTIL,src_file,dst_file,patchLoc]) + Update_Attr(elt, "FILE", File_Attibutes, Sym_Attibutes) + #Both differ but they are of diff types + else: + #Processing and updating partition txt file will be done under REMOVED case and NEW files case accordingly, we just make an entry here + files_removed.append(elt) + files_new.append(elt) + + fdupes = find_dupes_list(BASE_OLD, BASE_NEW, files_removed, files_new) + for oldpath, newpath in fdupes.iteritems(): + logging.info('Dupes %s -> %s' % (oldpath, newpath)) + + for elt in files_removed: + src_file = BASE_OLD+'/'+elt + if src_file in fdupes.keys(): + dst_file = BASE_NEW+'/'+ fdupes[src_file] + logging.debug(' File Moved %s ==> %s' % (src_file, dst_file)) + Move_Cnt = Move_Cnt + 1 + Partition_Doc.write('MOVE:REG:%s:%s:%s\n' % (elt, fdupes[src_file], hash_file(src_file))) + #directories should b taken care?? +++++++++++++++++++++++++++++++++++++ PARENT DIREC if not present etc + files_removed.remove(elt) + files_new.remove(fdupes[src_file]) + + #Should be placed after removing duplicates, else they will be filtered here. + # loop shd b for all NEW files, rather than for all delete files ?? + DelList = files_removed[:] + NewList = files_new[:] + for new_file in NewList: + if os.path.islink(BASE_NEW+'/'+new_file): + continue + elif os.path.isdir(BASE_NEW+'/'+new_file): + continue + else:# os.path.isfile(BASE_NEW+'/'+new_file): + DirPathNew = path_head(new_file) + FileNameNew = path_leaf(new_file) + DiffSize = 0 + winning_patch_sz = os.path.getsize(BASE_NEW+'/'+new_file) + winning_file = '' + for del_file in DelList: + #print '+++++' + if os.path.islink(BASE_OLD+'/'+del_file): + continue + elif os.path.isdir(BASE_OLD+'/'+del_file): + continue + else: #if os.path.isfile(BASE_OLD+'/'+del_file): + FileNameOld = path_leaf(del_file) + if (FileNameOld.startswith(FileNameNew[:len(FileNameNew)/2]) and (os.path.splitext(FileNameNew)[1] == os.path.splitext(del_file)[1])): + #winning_patch_sz = 0.9 * os.path.getsize(BASE_NEW+'/'+new_file) + #logging.debug('I can compute diff between %s %s' % (del_file, new_file)) + DiffSize = measure_two_filediffs(BASE_OLD+'/'+del_file, BASE_NEW+'/'+new_file) + if (DiffSize < 0.8 * winning_patch_sz): + winning_patch_sz = DiffSize + winning_file = del_file + if len(winning_file) > 0: + logging.debug('Best Pick -%s ==> %s [%d]' % (winning_file, new_file, DiffSize)) + files_renamed.append([new_file, winning_file]) + DelList.remove(winning_file) + files_removed.remove(winning_file) + files_new.remove(new_file) + + #********************** SHOULD CHECK THIS LOGIC *********************** + + if SUPPORT_RENAME == "TRUE": + for elt in files_renamed: + src_file = BASE_OLD+'/'+elt[1] + dst_file = BASE_NEW+'/'+elt[0] + Diff_Cnt = Diff_Cnt + 1 + patchName = (DIFF_PREFIX+'%d_%s'+DIFF_SUFFIX) % (Diff_Cnt, path_leaf(elt[1])) + #patchName = (DIFF_PREFIX+'_%s'+DIFF_SUFFIX) % (path_leaf(elt[0])) + patchLoc = '%s/%s' % (OUT_DIR, patchName) + logging.debug(' File Renamed %s ==> %s' % (src_file, dst_file)) + # Should be careful of renaming files?? + # Should we consider measure_two_filediffs ?? so that patch size is NOT greater than actual file? + # What if folder path has numerics?? + + if os.path.isdir(src_file) or os.path.isdir(dst_file): + #This case never occurs?? + Partition_Doc.write('"%s" and "%s" renamed 0 0\n' % (elt[0], elt[1])) + Update_Attr(elt[0], "FILE", File_Attibutes, Sym_Attibutes) + else: #Make sure these files are PROPER and they shd NOT be symlinks + if filecmp.cmp(src_file, dst_file): + Move_Cnt = Move_Cnt + 1 + Diff_Cnt = Diff_Cnt - 1 + Partition_Doc.write('MOVE:REG:%s:%s:%s\n' % (elt[1], elt[0], hash_file(src_file))) + elif SUPPORT_CONTAINERS == "TRUE": + if src_file.endswith('.zip') and dst_file.endswith('.zip'): + FORMAT = "ZIP" + Partition_Doc.write('DIFF:ZIP:%s:%s:%s:%s:%s/\n' % (elt[1], elt[0], hash_file(src_file), hash_file(dst_file), patchName)) + compute_containerdelta(src_file, dst_file, FORMAT, OUT_DIR+'/'+patchName, Partition_Doc) + elif src_file.endswith('.tpk') and dst_file.endswith('.tpk'): + FORMAT = "TPK" + Partition_Doc.write('DIFF:TPK:%s:%s:%s:%s:%s/\n' % (elt[1], elt[0], hash_file(src_file), hash_file(dst_file), patchName)) + compute_containerdelta(src_file, dst_file, FORMAT, OUT_DIR+'/'+patchName, Partition_Doc) + else: + FORMAT = "REG" + Partition_Doc.write('DIFF:REG:%s:%s:%s:%s:%s\n' % (elt[1], elt[0], hash_file(src_file), hash_file(dst_file), patchName)) + subprocess.call([DIFF_UTIL,src_file,dst_file,patchLoc]) + else: + FORMAT = "REG" + Partition_Doc.write('DIFF:REG:%s:%s:%s:%s:%s\n' % (elt[1], elt[0], hash_file(src_file), hash_file(dst_file), patchName)) + subprocess.call([DIFF_UTIL,src_file,dst_file,patchLoc]) + + SS_UpdateSize(src_file, dst_file) + Update_Attr(elt[0], "FILE", File_Attibutes, Sym_Attibutes) + + + for elt in files_removed: + #if files are part of patches after renaming, we shd remove them as part of removed. + src_file = BASE_OLD+'/'+elt + if os.path.islink(src_file): + Partition_Doc.write('DEL:SYM:%s\n' % (elt)) + elif os.path.isdir(src_file): + Partition_Doc.write('DEL:DIR:%s\n' % (elt)) + else: + Partition_Doc.write('DEL:REG:%s:%s\n' % (elt, hash_file(src_file))) + logging.debug(' File Deleted %s' % src_file) + Del_Cnt = Del_Cnt + 1 + + for elt in Dir_removed: + #if Dir is empty, add it to the removed list. + src_file = BASE_OLD+'/'+elt + #Irrespective of weather files are MOVED or DIFF'ed, we can delete the folders. This action can be performed at the end. + #It covers symlinks also, as NEW symlinks cannot point to NON existant folders of TARGET (NEW binary) + if os.path.isdir(src_file): + Partition_Doc.write('DEL:END:%s\n' % (elt)) + Del_Cnt = Del_Cnt + 1 + logging.debug(' Dir Deleted- %s' % src_file) + + + for elt in files_new: + dst_file = BASE_NEW+'/'+elt + newfiles_dest_path = 'system/' + ensure_dir_exists(newfiles_dest_path) + if os.path.islink(dst_file): + patch = os.readlink(dst_file) + logging.debug(' File New Links %s' % elt) + Partition_Doc_SymLinks.write('SYM:NEW:%s:%s\n' % (elt, patch)) + #What if this is only a new sym link and folder already exists??? Should recheck + destpath = newfiles_dest_path + elt + if not os.path.exists(path_head(destpath)): + os.makedirs(path_head(destpath)) + logging.info('New SymLink - Adding missing Dir') + #Update_Attr(elt, "SYM", File_Attibutes, Sym_Attibutes) + Sym_New_Cnt = Sym_New_Cnt + 1 + elif os.path.isdir(dst_file): # We create just empty directory here + destpath = newfiles_dest_path + elt + if not os.path.exists(destpath): + os.makedirs(destpath) + logging.debug(' File New Dir %s' % destpath) + New_Cnt = New_Cnt + 1 + else: + New_Cnt = New_Cnt + 1 + #newfiles_dest_path = OUT_DIR + '/system/' + destpath = newfiles_dest_path + elt + destdir = os.path.dirname(destpath) + logging.debug('New files - %s ==> %s' % (dst_file, destdir)) + + if not os.path.isdir(destdir): + try: + os.makedirs(destdir) + except: + logging.critical('Error in NEW files DIR entry -%s' % destdir) + raise + + try: + shutil.copy2(dst_file, destpath) + logging.debug('New files copied from- %s to- %s' % (dst_file, destpath)) + except: + logging.critical('Error in NEW files entry -%s -%s' % (dst_file, destpath)) + raise + + for elt in Dir_Added: + newfiles_dest_path = 'system/' + ensure_dir_exists(newfiles_dest_path) + destpath = newfiles_dest_path + elt + if not os.path.exists(destpath): + os.makedirs(destpath) + logging.debug(' DirList New Dir %s' % destpath) + New_Cnt = New_Cnt + 1 + + #Base directory should be system + print 'Compressing New files' + if (New_Cnt > 0): + WorkingDir = os.getcwd() + os.chdir(os.getcwd()+"/"+NEW_FILES_PATH) + logging.info('Curr Working Dir - %s' % os.getcwd()) + os.system(ZIPUTIL+NEW_FILES_PATH+" >> " + LOGFILE) + shutil.move(NEW_FILES_ZIP_NAME, WorkingDir+"/"+OUT_DIR) + #New file size?? cos, we extract system.7z from delta.tar and then proceed with decompression + SS_UpdateSize(WorkingDir+"/"+OUT_DIR+"/"+NEW_FILES_ZIP_NAME, WorkingDir+"/"+OUT_DIR+"/"+NEW_FILES_ZIP_NAME) + os.chdir(WorkingDir) + shutil.rmtree(NEW_FILES_PATH) + # use 7z a system.7z ./* + + #logging.info('%d Dir to be removed' % len(Dir_removed)) + logging.info('%d files unchanged' % len(files_unchanged)) + logging.info('%d files files_renamed' % len(files_renamed)) + logging.info('%d files NEW' % len(files_new)) + logging.info('%d File attr' % len(File_Attibutes)) + logging.info('%d Sym attr' % len(Sym_Attibutes)) + logging.info('PaTcHCoUnT:Diffs-%d Moves-%d News-%d Delets-%d SymDiffs-%d SymNews-%d\n' % (Diff_Cnt, Move_Cnt, New_Cnt, Del_Cnt, Sym_Diff_Cnt, Sym_New_Cnt)) + print('PaTcHCoUnT:Diffs-%d Moves-%d News-%d Delets-%d SymDiffs-%d SymNews-%d\n' % (Diff_Cnt, Move_Cnt, New_Cnt, Del_Cnt, Sym_Diff_Cnt, Sym_New_Cnt)) + + #There could be duplicates, TODO, can check before adding.. + ATTR_FILE = open(ATTR_FILE,'a+') + for elt in File_Attibutes: + ATTR_FILE.write(elt) + for elt in Sym_Attibutes: + ATTR_FILE.write(elt) + + ATTR_FILE.close() + + Partition_Doc_SymLinks.close() + Partition_Read_SymLinks = open(SymLinkDoc,'r+') + Partition_Doc.write(Partition_Read_SymLinks.read()) + Partition_Doc.write('PaTcHCoUnT:%d %d %d %d %d %d\n' % (Diff_Cnt, Move_Cnt, New_Cnt, Del_Cnt, Sym_Diff_Cnt, Sym_New_Cnt)) + Partition_Doc_SymLinks.close() + Partition_Doc.close() + os.remove(SymLinkDoc) + + +def Apply_Container_Delta(a_apk, b_apk, new_apk, a_folder, g_output_dir): + + #CONTROL NAMES, AND PRINTS AND ERROR CASES... SHOULD NOT PROCEED. + print 'ApplyContainerDelta - ', b_apk, a_folder, g_output_dir + shutil.copy2(g_output_dir+'/'+b_apk, g_output_dir+'/temp') + temp_apk = '../'+g_output_dir+'/'+b_apk + Patch = 'Patch_'+b_apk + ensure_dir_exists(Patch) + shutil.copy2(g_output_dir+'/'+b_apk, Patch+'/'+b_apk) + + #Size issue on Device side?? shd check this + subprocess.call(['unzip','-q', Patch+'/'+b_apk, '-d', Patch]) + with open(g_output_dir+'/PATCH.txt', 'r') as f_new: + lines = set(f_new.read().splitlines()) + for line in lines: + #print('Action ==> %s' % line) + #Action, Path, Patch = line.split('|') + Items = line.split('|') + Action = Items[0] + Path = Items[1] + ActualPath = a_folder+'/'+Path + PatchPath = Patch+'/'+Path + SrcPath = g_output_dir+'/'+path_leaf(Path) + #print('Action ==> %s Path ==> %s ' % (Action, Path)) + if line[0] == 'c': + patchName = g_output_dir+'/'+Items[2] + #print('Apply Patch: ActualPath %s SrcPath %s PatchLoc %s ' % (PatchPath, ActualPath, patchName)) + subprocess.call([DIFFPATCH_UTIL,ActualPath,ActualPath,patchName]) + WorkingDir = os.getcwd() + os.chdir(WorkingDir+"/"+"temp_a") + subprocess.call(['cp', '--parents', Path, '../'+Patch]) + os.chdir(WorkingDir) + elif line[0] == 's': + WorkingDir = os.getcwd() + os.chdir(WorkingDir+"/"+"temp_a") + subprocess.call(['cp', '--parents', Path, '../'+Patch]) + os.chdir(WorkingDir) + else: + print('Unknown Error') + #print('Touch all files and set common attributes for DIFF generation') + WorkingDir = os.getcwd() + os.chdir(WorkingDir+"/"+Patch) + + CONTAINER_DATE = '200011111111.11' + CONTAINER_MODE = '0755' + subprocess.call(['find', '.', '-type', 'l', '-exec', 'rm', '-rf', '{}', ';']) + subprocess.call(['find', '.', '-exec', 'touch', '-t', CONTAINER_DATE, '{}', ';']) + subprocess.call(['chmod', '-R', CONTAINER_MODE, '../'+Patch]) + + print 'Update Intermediate Archive' + #subprocess.call(['zip','-ryX', b_apk, '*']) + subprocess.call(['zip','-ryX', b_apk] + glob.glob('*')) + os.chdir(WorkingDir) + #print('Apply Path completed - Now create diff for this and place in patch folder') + #print os.getcwd() + print('Patch Applied, Create Final Diff - %s %s' % (g_output_dir+'/'+b_apk,new_apk)) + patchName = ('New'+'_%s'+DIFF_SUFFIX) % (b_apk) + patchLoc = '%s/%s' % (g_output_dir, patchName) + + subprocess.call([DIFF_UTIL, Patch+'/'+b_apk ,new_apk,patchLoc]) + + #Only on HOST... for testing + if TEST_MODE == 'TRUE': + UpgradedName = '%s_Upgraded' % (b_apk) + subprocess.call([DIFFPATCH_UTIL,Patch+'/'+b_apk,UpgradedName,patchLoc]) + + #This is file only with NEWS and empty diffs and same files. + if TEST_MODE == 'FALSE': + os.remove(g_output_dir+'/'+b_apk) + os.rename(g_output_dir+'/temp', g_output_dir+'/'+b_apk) + shutil.rmtree(Patch) + +def IsSymlink(info): + return (info.external_attr >> 16) == 0120777 + + +def compute_containerdelta(src_file, dst_file, FORMAT, patchName, Partition_Doc): + + a_apk = src_file + b_apk = dst_file + a_folder = 'temp_a' + b_folder = 'temp_b' + + g_output_dir = patchName + + logging.info('Uncompressing Containers... [%s][%s]' % (src_file, dst_file)) + logging.info('Out Dir -%s' %(g_output_dir)) + ensure_dir_exists(a_folder) + zipf = zipfile.ZipFile(a_apk, 'r'); + zipf.extractall(a_folder) + zipf.close() + + ensure_dir_exists(b_folder) + zipf = zipfile.ZipFile(b_apk, 'r'); + zipf.extractall(b_folder) + + + symlinks = [] + for info in zipf.infolist(): + basefilename = info.filename[7:] + if IsSymlink(info): + symlinks.append(info.filename) + os.remove(b_folder+'/'+info.filename) + zipf.close() + + a_files, a_dirs = Get_Files(a_folder) + b_files, b_dirs = Get_Files(b_folder) + + logging.info('Going from %d files %d files' % (len(a_files), len(b_files))) + + # First let's fill up these categories + C_files_new = [] + C_files_removed = [] + C_files_changed = [] + C_files_unchanged = [] + + # What files appear in B but not in A? + for elt in b_files: + if elt not in a_files: + #if not elt.endswith('.so'): + C_files_new.append(elt) + + # What files appear in A but not in B? + for elt in a_files: + if elt not in b_files: + C_files_removed.append(elt) + + # What files have changed contents but not name/path? + for elt in b_files: + if elt in a_files: + if os.path.islink(a_folder+'/'+elt) or os.path.islink(b_folder+'/'+elt): + print 'links - skip' + elif not filecmp.cmp(a_folder+'/'+elt, b_folder+'/'+elt): + C_files_changed.append(elt) + else: + C_files_unchanged.append(elt) + + + print('%d new files' % len(C_files_new)) + print('%d removed files' % len(C_files_removed)) + print('%d files changed' % len(C_files_changed)) + print('%d files unchanged' % len(C_files_unchanged)) + + # temp dir where we're assembling the patch + ensure_dir_exists(g_output_dir) + + unique_fileid = 0 + toc = open(g_output_dir+'/PATCH.txt','w') + print("writing diff'ed changed files...") + + for elt in C_files_changed: + dst_file = b_folder+'/'+elt + src_file = a_folder+'/'+elt + patchName = (DIFF_PREFIX+'%d_%s'+DIFF_SUFFIX) % (unique_fileid, path_leaf(elt)) + patchLoc = '%s/%s' % (g_output_dir, patchName) + #print('src - %s dest -%s patch -%s' % (src_file ,dst_file,patchLoc)) + subprocess.call([DIFF_UTIL,src_file ,dst_file,patchLoc]) + toc.write('c%d|%s|%s\n' % (unique_fileid, elt, patchName)) + unique_fileid = unique_fileid + 1 + + for elt in C_files_unchanged: + dst_file = b_folder+'/'+elt + src_file = a_folder+'/'+elt + #print('Same Files src - %s dest -%s' % (src_file ,dst_file)) + toc.write('s%d|%s\n' % (unique_fileid, elt)) + unique_fileid = unique_fileid + 1 + + #Create NEW TPK with empty data for below files and NEW files + shutil.copy2(b_apk, g_output_dir) + + #May b for host?? + #temp_apk = '../'+g_output_dir+'/'+b_apk + temp_apk = '../'+g_output_dir+'/'+path_leaf(b_apk) + + for elt in C_files_changed: + dst_file = b_folder+'/'+elt + #print dst_file + open(dst_file, 'w').close() + + for elt in C_files_unchanged: + dst_file = b_folder+'/'+elt + open(dst_file, 'w').close() + + WorkingDir = os.getcwd() + os.chdir(WorkingDir+"/"+b_folder) + + #for elt in files_changed: + # subprocess.call(['zip', temp_apk, elt]) # confirm ZIP options, extra fields etc.. jus zip it, shd do all at once.. else time taking + + #for elt in files_unchanged: + # subprocess.call(['zip', temp_apk, elt]) + + subprocess.call(['zip','-ryq', temp_apk, '*']) + os.chdir(WorkingDir) + toc.close() + + Apply_Container_Delta(path_leaf(a_apk), path_leaf(b_apk), b_apk, a_folder, g_output_dir) + shutil.rmtree(a_folder) + shutil.rmtree(b_folder) + +def NewFiles(src, dest): + print src,dest + subprocess.call(['cp','-rp', src,dest]) + #try: + #shutil.copytree(src, dest) + #except OSError as e: + # If the error was caused because the source wasn't a directory + #if e.errno == errno.ENOTDIR: + #shutil.copy2(src, dest) + #else: + #print('Directory not copied. Error: %s' % e) + +def measure_two_filediffs(src, dst): + patchLoc = 'temp.patch' + subprocess.call([DIFF_UTIL,src,dst,patchLoc]) + result_size = os.path.getsize(patchLoc) + os.remove(patchLoc) + return result_size + +def Get_Files(path): + all_files = [] + all_dirs = [] + + for root, directories, filenames in os.walk(path, topdown=False, followlinks=False): + for directory in directories: + #DirName = os.path.join(root+'/',directory) + DirName = os.path.join(root,directory) + if os.path.islink(DirName): + logging.debug('This is symlink pointing to dir -%s' % DirName) + all_files.append(os.path.relpath(DirName, path)) + elif not os.listdir(DirName): + #print('*****Empty Directory******* -%s', DirName) + #This should NOT be appended ??? Empty dir shd b considered + all_dirs.append(os.path.relpath(DirName, path)) + else: + all_dirs.append(os.path.relpath(DirName, path)) + for filename in filenames: + FileName = os.path.join(root,filename) + all_files.append(os.path.relpath(FileName, path)) + + all_files.sort() + all_dirs.sort() + return all_files, all_dirs + + +USAGE_DOCSTRING = """ + Generate Delta using BASEOLD AND BASE NEW + Attributes is optional +""" + +def Usage(docstring): + print docstring.rstrip("\n") + print COMMON_DOCSTRING + + + +if __name__ == '__main__': + main() + diff --git a/UPG/dzImagescript.sh b/UPG/dzImagescript.sh new file mode 100755 index 0000000..f96bc4f --- /dev/null +++ b/UPG/dzImagescript.sh @@ -0,0 +1,255 @@ +#!/bin/bash + +pname="${0##*/}" +args=("$@") +cur_dir="$(pwd)" + +# file names: +decompression_code="decompression_code" +piggy_gz_piggy_trailer="piggy.gz+piggy_trailer" +piggy="piggy" +piggy_gz="piggy.gz" +padding_piggy="padding_piggy" +piggy_trailer="piggy_trailer" +ramfs_gz_part3="initramfs.cpio+part3" +ramfs_cpio_gz="initramfs.cpio.gz" +padding3="padding3" +part3="part3" +kernel_img="kernel.img" +ramfs_cpio="initramfs.cpio" +ramfs_dir="initramfs" +sizes="sizes" +ramfs_part3="ramfs+part3" +ramfs_list="initramfs_list" +cpio_t="cpio-t" + + +cpio="cpio_set0" + +# We dup2 stderr to 3 so an error path is always available (even +# during commands where stderr is redirected to /dev/null). If option +# -v is set, we dup2 sterr to 9 also so commands (and some of their +# results if redirected to &9) are printed also. +exec 9>/dev/null # kill diagnostic ouput (will be >&2 if -v) +exec 3>&2 # an always open error channel + +# +########### Start of functions +# + +# Emit an error message and abort +fatal(){ + # Syntax: fatal + # Output error message, then abort + echo >&3 + echo >&3 "$pname: $*" + kill $$ + exit 1 +} + +# Execute a command, displaying the command if -v: +cmd(){ + # Syntax: cmd + # Execute , echo command line if -v + echo >&9 "$*" + "$@" +} + +# Execute a required command, displaying the command if -v, abort on +# error: +rqd(){ + # Syntax: cmd + # Execute , echo commandline if -v, abort on error + cmd "$@" || fatal "$* failed." +} + +findByteSequence(){ + # Syntax: findByteSequence [] + # Returns: position (offset) on stdout, empty string if nothing found + file="$1" + local opt + if [ "$2" = "lzma" ]; then + srch=$'\x5d....\xff\xff\xff\xff\xff' + opt= + else + srch="${2:-$'\x1f\x8b\x08'}" # Default: search for gzip header + opt="-F" + fi + pos=$(LC_ALL=C grep $opt -a --byte-offset -m 1 --only-matching -e "$srch" -- "$file") + echo ${pos%%:*} +} + +getFileSize(){ + # Syntax: getFileSize + # Returns size of the file on stdout. + # Aborts if file doesn't exist. + rqd stat -c %s "$1" +} +checkNUL(){ + # Syntax: checkNUL file offset + # Returns true (0) if byte there is 0x0. + [ "$(rqd 2>/dev/null dd if="$1" skip=$2 bs=1 count=1)" = $'\0' ] +} + +gunzipWithTrailer(){ + # Syntax gunzipWithTrailer + # + # : the input file + # , , : + # The output files. For the gzipped part, both the + # compressed and the uncompressed output is generated, so we have + # 4 output files. + local file="$1" + local gz_result="$2.gz" + local result="$2" + local padding="$3" + local trailer="$4" + local tmpfile="/tmp/gunzipWithTrailer.$$.gz" + local original_size=$(getFileSize "$file") + local d=$(( (original_size+1) / 2)) + local direction fini at_min=0 + local results_at_min=() + local size=$d + local at_min= + echo "Separating gzipped part from trailer in "$file"" + echo -n "Trying size: $size" + while :; do + rqd dd if="$file" of="$tmpfile" bs=$size count=1 2>/dev/null + cmd gunzip >/dev/null 2>&1 -c "$tmpfile" + res=$? + if [ "$d" -eq 1 ]; then + : $((at_min++)) + results_at_min[$size]=1 + [ "$at_min" -gt 3 ] && break + fi + d=$(((d+1)/2)) + case $res in + # 1: too small + 1) size=$((size+d)); direction="↑";; + # 2: trailing garbage + 2) size=$((size-d)); direction="↓";; + # OK + 0) break;; + *) fatal "gunzip returned $res while checking "$file"";; + esac + echo -n " $size" + done + if [ "$at_min" -gt 3 ]; then + echo -e "\ngunzip result is oscillating between 'too small' and 'too large' at size: ${!results_at_min[*]}" + echo -n "Trying lower nearby values: " + fini= + for ((d=1; d < 30; d++)); do + : $((size--)) + echo -n " $size" + rqd dd if="$file" of="$tmpfile" bs=$size count=1 2>/dev/null + if cmd gunzip >/dev/null 2>&1 -c "$tmpfile"; then + echo -n " - OK" + fini=1 + break + fi + done + [ -z "$fini" ] && fatal 'oscillating gunzip result, giving up.' + fi + # We've found the end of the gzipped part. This is not the real + # end since gzip allows for some trailing padding to be appended + # before it barfs. First, go back until we find a non-null + # character: + echo -ne "\npadding check (may take some time): " + real_end=$((size-1)) + while checkNUL "$file" $real_end; do + : $((real_end--)) + done + # Second, try if gunzip still succeeds. If not, add trailing + # null(s) until it succeeds: + while :; do + rqd dd if="$file" of="$tmpfile" bs=$real_end count=1 2>/dev/null + gunzip >/dev/null 2>&1 -c "$tmpfile" + case $? in + # 1: too small + 1) : $((real_end++));; + *) break;; + esac + done + real_next_start=$size + # Now, skip NULs forward until we reach a non-null byte. This is + # considered as being the start of the next part. + while checkNUL "$file" $real_next_start; do + : $((real_next_start++)) + done + echo $((real_next_start - real_end)) + echo + rm "$tmpfile" + # Using the numbers we got so far, create the output files which + # reflect the parts we've found so far: + rqd dd 2>&9 if="$file" of="$gz_result" bs=$real_end count=1 + rqd dd 2>&9 if="$file" of="$padding" skip=$real_end bs=1 count=$((real_next_start - real_end)) + rqd dd 2>&9 if="$file" of="$trailer" bs=$real_next_start skip=1 + rqd gunzip -c "$gz_result" > "$result" +} + + +unpack()( + [ -d "$unpacked" ] && echo "\ +Warning: there is aready an unpacking directory. If you have files added on +your own there, the repacking result may not reflect the result of the +current unpacking process." + rqd mkdir -p "$unpacked" + rqd cd "$unpacked" + sizes="$unpacked/sizes" + echo "# Unpacking sizes" > "$sizes" + + piggy_start=$(findByteSequence "$cur_dir/$zImage") + if [ -z "$piggy_start" ]; then + fatal "Can't find a gzip header in file '$zImage'" + fi + + rqd dd 2>&9 if="$cur_dir/$zImage" bs="$piggy_start" count=1 of="$decompression_code" + rqd dd 2>&9 if="$cur_dir/$zImage" bs="$piggy_start" skip=1 of="$piggy_gz_piggy_trailer" + + gunzipWithTrailer "$piggy_gz_piggy_trailer" \ + "$piggy" "$padding_piggy" "$piggy_trailer" + + echo + sudo rm -rf "piggy.gz" "piggy.gz+piggy_trailer" "sizes" + echo "Success." + echo "The unpacked files and the initramfs directory are in "$unpacked"" +) + +#### start of main program +while getopts xv12345sgrpuhtz-: argv; do + case $argv in + p|u|z|1|2|3|4|5|t|r|g) eval opt_$argv=1;; + v) exec 9>&2; opt_v=1;; + s) cpio="cpio";; + x) set -x;; + -) if [ "$OPTARG" = "version" ]; then + echo "$pname $version" + exit 0 + else + echo "Wrong Usage, use -u to unpack" + fi;; + h|-) echo "Wrong Usage, use -u to unpack";; + *) fatal "Illegal option";; + esac +done +shift $((OPTIND-1)) +zImage="${1:-zImage}" +unpacked="$cur_dir/${zImage}_unpacked" +packing="$cur_dir/${zImage}_packing" +shift +if [ -n "$*" ]; then + fatal "Excess arguments: '$*'" +fi + +if [ -n "$opt_u" ]; then + [ -f "$zImage" ] || fatal "file '$zImage': not found" + unpack +fi +if [ -z "$opt_u" ]; then + echo >&2 "$pname: Need at least -u option." + echo >&2 "$pname: Type '$pname --help' for usage info." + exit 1 +fi + +exit + diff --git a/UPG/ss_bsdiff b/UPG/ss_bsdiff new file mode 100755 index 0000000000000000000000000000000000000000..d1293cd30d4fb106cff93e499a1530c939830ebb GIT binary patch literal 212699 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⁢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 literal 0 HcmV?d00001 diff --git a/UPG/unpack.sh b/UPG/unpack.sh new file mode 100755 index 0000000..869881d --- /dev/null +++ b/UPG/unpack.sh @@ -0,0 +1,236 @@ +#!/bin/bash + +pname="${0##*/}" +args=("$@") +cur_dir="$(pwd)" + +# file names: +decompression_code="decompression_code" +piggy_gz_piggy_trailer="piggy.gz+piggy_trailer" +piggy="piggy" +piggy_gz="piggy.gz" +padding_piggy="padding_piggy" +piggy_trailer="piggy_trailer" +padding3="padding3" +sizes="sizes" + +# We dup2 stderr to 3 so an error path is always available (even +# during commands where stderr is redirected to /dev/null). If option +# -v is set, we dup2 sterr to 9 also so commands (and some of their +# results if redirected to &9) are printed also. +exec 9>/dev/null # kill diagnostic ouput (will be >&2 if -v) +exec 3>&2 # an always open error channel + +# +########### Start of functions +# + +# Emit an error message and abort +fatal(){ + # Syntax: fatal + # Output error message, then abort + echo >&3 + echo >&3 "$pname: $*" + kill $$ + exit 1 +} + +# Execute a command, displaying the command if -v: +cmd(){ + # Syntax: cmd + # Execute , echo command line if -v + echo >>"$workspace/log_file" "$*" + "$@" +} + +# Execute a required command, displaying the command if -v, abort on +# error: +rqd(){ + # Syntax: cmd + # Execute , echo commandline if -v, abort on error + cmd "$@" || fatal "$* failed." +} + +checkNUL(){ + # Syntax: checkNUL file offset + # Returns true (0) if byte there is 0x0. + [ "$(rqd 2>>"$workspace/log_file" "$workspace/dd" if="$1" skip=$2 bs=1 count=1)" = $'\0' ] +} + +gunzipWithTrailer(){ + # Syntax gunzipWithTrailer + # + # : the input file + # , , : + # The output files. For the gzipped part, both the + # compressed and the uncompressed output is generated, so we have + # 4 output files. + local file="$1" + local gz_result="$2.gz" + local result="$2" + local padding="$3" + local trailer="$4" + local tmpfile="/tmp/gunzipWithTrailer.$$.gz" + local original_size=$("$workspace/stat" -c %s "$unpacked/$file") 2>>"$workspace/log_file" + echo "Original size is $original_size" >> "$workspace/log_file" + local d=$(( (original_size+1) / 2)) + local direction fini at_min=0 + local results_at_min=() + local size=$d + local at_min= + rm -rf /tmp/test_file + echo "Separating gzipped part from trailer in "$unpacked/$file"" >> "$workspace/log_file" + echo -n "Trying size: $size" >> "$workspace/log_file" + while :; do + rqd "$workspace/dd" if="$unpacked/$file" of="$tmpfile" bs=$size count=1 2>>"$workspace/log_file" + cmd "$workspace/gzip" >/tmp/test_file 2>>"$workspace/log_file" -d -c "$tmpfile" + res=$? + echo "result for gunzip is $res" >>"$workspace/log_file" + if [ "$d" -eq 1 ]; then + : $((at_min++)) + results_at_min[$size]=1 + [ "$at_min" -gt 3 ] && break + fi + d=$(((d+1)/2)) + case $res in + # 1: too small + 1) echo "In case 1" >> "$workspace/log_file" + size=$((size+d)); direction="↑";; + # 2: trailing garbage + 2) echo "In case 2" >> "$workspace/log_file" + size=$((size-d)); direction="↓";; + # OK + 0) echo "Breaking" >> "$workspace/log_file" + break;; + *) echo "In case *" >> "$workspace/log_file" + fatal "gunzip returned $res while checking "$unpacked/$file"";; + esac + echo -n " $size" >> "$workspace/log_file" + done + if [ "$at_min" -gt 3 ]; then + echo -e "\ngunzip result is oscillating between 'too small' and 'too large' at size: ${!results_at_min[*]}" >> "$workspace/log_file" + echo -n "Trying lower nearby values: " >> "$workspace/log_file" + fini= + for ((d=1; d < 30; d++)); do + : $((size--)) + echo -n " $size" >> "$workspace/log_file" + rqd "$workspace/dd" if="$unpacked/$file" of="$tmpfile" bs=$size count=1 2>/dev/null + if cmd "$workspace/gzip" >/dev/null 2>&1 -d -c "$tmpfile"; then + echo -n " - OK" >> "$workspace/log_file" + fini=1 + break + fi + done + [ -z "$fini" ] && fatal 'oscillating gunzip result, giving up.' + fi + # We've found the end of the gzipped part. This is not the real + # end since gzip allows for some trailing padding to be appended + # before it barfs. First, go back until we find a non-null + # character: + echo -ne "\npadding check (may take some time): " >> "$workspace/log_file" + real_end=$((size-1)) + while checkNUL "$unpacked/$file" $real_end; do + : $((real_end--)) + done + echo "Found real end at $real_end" >> "$workspace/log_file" + # Second, try if gunzip still succeeds. If not, add trailing + # null(s) until it succeeds: + while :; do + rqd "$workspace/dd" if="$unpacked/$file" of="$tmpfile" bs=$real_end count=1 2>>"$workspace/log_file" + "$workspace/gzip" >/tmp/test_file2 2>>"$workspace/log_file" -d -c "$tmpfile" + case $? in + # 1: too small + 1) echo "In case 1" >> "$workspace/log_file" + : $((real_end++));; + *) echo "Case other $?" >> "$workspace/log_file" + break;; + esac + done + echo "Done with add trailing null(s) until it succeeds" >> "$workspace/log_file" + real_next_start=$size + # Now, skip NULs forward until we reach a non-null byte. This is + # considered as being the start of the next part. + while checkNUL "$unpacked/$file" $real_next_start; do + : $((real_next_start++)) + done + echo $((real_next_start - real_end)) >> "$workspace/log_file" + echo >> "$workspace/log_file" + rm "$tmpfile" + # Using the numbers we got so far, create the output files which + # reflect the parts we've found so far: + rqd "$workspace/dd" if="$unpacked/$file" of="$unpacked/$gz_result" bs=$real_end count=1 + rqd "$workspace/dd" if="$unpacked/$file" of="$unpacked/$padding" skip=$real_end bs=1 count=$((real_next_start - real_end)) + rqd "$workspace/dd" if="$unpacked/$file" of="$unpacked/$trailer" bs=$real_next_start skip=1 + rqd "$workspace/gzip" -c -d "$unpacked/$gz_result" > "$unpacked/$result" +} + +unpack()( + [ -d "$unpacked" ] && echo "\ +Warning: there is aready an unpacking directory. If you have files added on +your own there, the repacking result may not reflect the result of the +current unpacking process." + rqd mkdir -p "$unpacked" + rqd cd "$unpacked" + sizes="$unpacked/sizes" + echo "# Unpacking sizes" > "$sizes" + log_file="$unpacked/log_file" + #piggy_start=$1 + if [ -z "$piggy_start" ]; then + fatal "Can't find a gzip header in file '$zImage'" >> "$workspace/log_file" + fatal "Can't find a gzip header in file '$zImage'" + else + echo "start is $piggy_start" >> "$sizes" + fi + + rqd "$workspace/dd" if="$zImage" bs="$piggy_start" count=1 of="$unpacked/$decompression_code" + rqd "$workspace/dd" if="$zImage" bs="$piggy_start" skip=1 of="$piggy_gz_piggy_trailer" + + gunzipWithTrailer "$piggy_gz_piggy_trailer" \ + "$piggy" "$padding_piggy" "$piggy_trailer" + + echo + echo "Success." +) + +#### start of main program +while getopts xv12345sgrpuhtz-: argv; do + case $argv in + p|z|1|2|3|4|5|t|r|g) eval opt_$argv=1;; + u) opt_u=1 + workspace=$2 + zImage="$2/$3" + piggy_start=$4 + unpacked="${zImage}_unpacked" + packing="${zImage}_packing";; + -) if [ "$OPTARG" = "version" ]; then + echo "$pname $version" + exit 0 + else + echo "Wrong Usage, use -u to unpack" + fi;; + h|-) echo "Wrong Usage, use -u to unpack";; + *) fatal "Illegal option";; + esac +done +if [ -n "$opt_u" ]; then + [ -f "$zImage" ] || fatal "file '$zImage': not found" + unpack +fi +if [ -n "$opt_p" ]; then + work_dir=$2 + tgt_file=$3 + cmd_dir=$4 + rqd cd "$work_dir" + #remove all links before proceeding with zip processing + "$cmd_dir/find" . -type l -exec rm {} \; + "$cmd_dir/find" . -exec touch -t 200011111111.11 {} \; + "$cmd_dir/find" . -exec chmod 0755 {} \; + "$cmd_dir/zip" -ryX "$tgt_file" * +fi +if [ -z "$opt_u$opt_p" ]; then + echo >&2 "$pname: Need -u or -p option to work" + echo >&2 "$pname: Type '$pname --help' for usage info." + exit 1 +fi + +exit diff --git a/bsdiff/CMakeLists.txt b/bsdiff/CMakeLists.txt new file mode 100755 index 0000000..cbdd658 --- /dev/null +++ b/bsdiff/CMakeLists.txt @@ -0,0 +1,23 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +PROJECT(ss_bsdiff C) + +SET(ss_bsdiff_SRCS ss_bsdiff.c) +SET(ss_bspatch_SRCS ss_bspatch.c) + +INCLUDE(FindPkgConfig) +pkg_check_modules(${PROJECT_NAME}_pkgs REQUIRED lib7zip libdivsufsort) + +FOREACH(flag ${${PROJECT_NAME}_pkgs_CFLAGS}) + SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") +ENDFOREACH(flag) + +SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -I./include") +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}") + +ADD_EXECUTABLE(ss_bsdiff ${ss_bsdiff_SRCS}) +TARGET_LINK_LIBRARIES(ss_bsdiff ${${PROJECT_NAME}_pkgs_LDFLAGS} "-g" "-pthread") +INSTALL(TARGETS ss_bsdiff DESTINATION bin) + +ADD_EXECUTABLE(ss_bspatch ${ss_bspatch_SRCS}) +TARGET_LINK_LIBRARIES(ss_bspatch ${${PROJECT_NAME}_pkgs_LDFLAGS} "-g" "-pthread") +INSTALL(TARGETS ss_bspatch DESTINATION bin) diff --git a/bsdiff/ss_bsdiff.c b/bsdiff/ss_bsdiff.c new file mode 100755 index 0000000..cea3385 --- /dev/null +++ b/bsdiff/ss_bsdiff.c @@ -0,0 +1,597 @@ +/*- + * Copyright 2003-2005 Colin Percival + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +// This file is a nearly copy of bsdiff.c from the +// bsdiff-4.3 distribution; the primary differences being how the +// input and output data are read, data post processing, +// search function modification and the error handling. + +#define _CRT_SECURE_NO_WARNINGS +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include <7zFile.h> +#include <7zVersion.h> +#include +#include + +#define SUFSORT_MOD // Change suffix sorting algorithm from Qsufsort to Divsufsort +//#define ZLIB_MOD // Change compression algorithm +#define SEARCH_RECURSIVE // Change recursion of Search function to Iteration +//#define MAX_MATCH_SIZE // define ( MAX_MATCH_SIZE or CONST_MEMORY_USAGE ) or ( none of them ); use max_match memory for old file at patch side +#define CONST_MEMORY_USAGE 16384 // patch in m+O(1); m=size of new file; use only const memory for old file at patch side; +#define PATCH_FILE_FORMAT_MOD // no accumulation of diff and extra in db and eb; immediate write; also write all 3 parts of control stmt at same time +#define MULTI_THREADING 1 // only with #define CONST_MEMORY_USAGE or #define MAX_MATCH_SIZE +#define TIME_LIMIT_CHECK 300 + +/* Take care : +1) Use either (MAX_MATCH_SIZE or CONST_MEMORY_USAGE) or (none of both). +2.) PATCH_FILE_FORMAT_MOD may or may not be used, independently of everything else. +3.) MULTI_THREADING can be used only with (CONST_MEMORY_USAGE or MAX_MATCH_SIZE). +*/ +#ifdef TIME_LIMIT_CHECK +long long outer_count = 0; +char ts1[256]; +void get_time_stamp(void) +{ + struct timeval tv; + int sec, msec; + + gettimeofday(&tv, NULL); + sec = (int)tv.tv_sec; + msec = (int)(tv.tv_usec / 1000); + snprintf(ts1, 256, "%06d.%03d", sec % 100000, msec); +} +#endif +#ifdef SUFSORT_MOD +//supporting only 32 bit divsufsort for now. +#include "divsufsort.h" +#endif + +#define MIN(x,y) (((x)<(y)) ? (x) : (y)) + +#ifdef MULTI_THREADING + +struct data_thread +{ + unsigned num_threads; + off_t size_thread; + + int fd; + int rc; + u_char *old; + u_char **new; + off_t oldsize; + off_t newsize; + + saidx_t *I; + u_char buf[8]; + u_char header[16]; + u_char buf2[32]; + + off_t lenn; + off_t lenn2; + + FILE * pf; + int bz2err; + FILE * pfbz2; +}; + +struct data_thread data; + +void* Function(int); + + +#endif + +static off_t matchlen(u_char *old, off_t oldsize,u_char *new, off_t newsize) +{ + off_t i; + for (i = 0; (i < oldsize) && (i < newsize); i++) + if (old[i] != new[i]) + break; + return i; +} + +static off_t search(saidx_t *I, u_char *old, off_t oldsize, + u_char *new, off_t newsize, off_t st, off_t en, off_t *pos) +{ + off_t x, y; + while (en - st >= 2) { + x = st + (en - st) / 2; + if (memcmp(old + I[x], new, MIN(oldsize - I[x], newsize)) < 0) { + st = x; + } else { + en = x; + } + } + + if (en - st < 2) { + x = matchlen(old + I[st], oldsize - I[st], new, newsize); + y = matchlen(old + I[en], oldsize - I[en], new, newsize); + + if (x > y) { + *pos = I[st]; + return x; + } else { + *pos = I[en]; + return y; + } + }; +} + +static void offtout(off_t x, u_char *buf) +{ + off_t y; + + if (x < 0) + y = -x; + else + y = x; + + buf[0] = y % 256; + y -= buf[0]; + y = y / 256; + buf[1] = y % 256; + y -= buf[1]; + y = y / 256; + buf[2] = y % 256; + y -= buf[2]; + y = y / 256; + buf[3] = y % 256; + y -= buf[3]; + y = y / 256; + buf[4] = y % 256; + y -= buf[4]; + y = y / 256; + buf[5] = y % 256; + y -= buf[5]; + y = y / 256; + buf[6] = y % 256; + y -= buf[6]; + y = y / 256; + buf[7] = y % 256; + + if (x < 0) + buf[7] |= 0x80; +} + +int create_patch(int argc, char *argv[], int offset_oldscore) +{ + data.num_threads = MULTI_THREADING; + data.new = (u_char **)malloc(sizeof(u_char *)*data.num_threads); + if (argc != 4) + errx(1, "usage: %s oldfile newfile patchfile\n", argv[0]); + + /* Allocate oldsize+1 bytes instead of oldsize bytes to ensure + that we never try to malloc(0) and get a NULL pointer */ + if (((data.fd = open(argv[1], O_RDONLY, 0)) < 0) || + ((data.oldsize = lseek(data.fd, 0, SEEK_END)) == -1) || + ((data.old = malloc(data.oldsize + 1)) == NULL) || + (lseek(data.fd, 0, SEEK_SET) != 0) || + (read(data.fd, data.old, data.oldsize) != data.oldsize) || + (close(data.fd) == -1)) + err(1, "%s", argv[1]); + + data.I = malloc((data.oldsize + 1) * sizeof(saidx_t)); + divsufsort(data.old, data.I, data.oldsize); + + /* Allocate newsize+1 bytes instead of newsize bytes to ensure + that we never try to malloc(0) and get a NULL pointer */ + if (((data.fd = open(argv[2], O_RDONLY, 0)) < 0) || + ((data.newsize = lseek(data.fd, 0, SEEK_END)) == -1) || + (lseek(data.fd, 0, SEEK_SET) != 0)) + err(1, "%s", argv[2]); + data.size_thread = (data.newsize / data.num_threads); + + unsigned int j; + for (j = 0; j < data.num_threads; ++j) { + if (j != data.num_threads - 1) { + if (((data.new[j] = (u_char *)malloc(sizeof(u_char) * (data.size_thread + 1))) == NULL) || + (lseek(data.fd, 0, SEEK_CUR) != j * data.size_thread) || + (read(data.fd, data.new[j], data.size_thread) != data.size_thread)) + err(1, "%s", argv[2]); + } else { + if (((data.new[j] = (u_char *)malloc(sizeof(u_char) * (data.newsize - (j * data.size_thread) + 1))) == NULL) || + (lseek(data.fd, 0, SEEK_CUR) != j * data.size_thread) || + (read(data.fd, data.new[j], data.newsize - (j * data.size_thread)) != data.newsize - (j * data.size_thread))) + err(1, "here %s", argv[2]); + } + } + + if ((close(data.fd) == -1)) + err(1, "%s", argv[2]); + + /* Create the patch file */ + if ((data.pf = fopen("temp_patch", "w")) == NULL) + err(1, "%s", "temp_patch"); + + /* Header is + 0 8 "BSDIFF40" + 8 8 length of bzip2ed ctrl block + 16 8 length of bzip2ed diff block + 24 8 length of new file */ + /* File is + 0 32 Header + 32 ?? Bzip2ed ctrl block + ?? ?? Bzip2ed diff block + ?? ?? Bzip2ed extra block */ + memcpy(data.header, "SSDIFF40", 8); + + offtout(data.newsize, data.header + 8); + if (fwrite(data.header, 16, 1, data.pf) != 1) + err(1, "fwrite(%s)", "temp_patch"); + + /* Compute the differences, writing ctrl as we go */ + data.pfbz2 = data.pf; + //if ((data.pfbz2 = BZ2_bzWriteOpen(&data.bz2err, data.pf, 9, 0, 0)) == NULL) + //errx(1, "BZ2_bzWriteOpen, bz2err = %d", data.bz2err); + + + //BZ2_bzWriteClose(&data.bz2err, data.pfbz2, 0, NULL, NULL); + //if (data.bz2err != BZ_OK) + //errx(1, "BZ2_bzWriteClose, bz2err = %d", data.bz2err); + + int ret = Function(offset_oldscore); +#ifdef TIME_LIMIT_CHECK + if (ret != 0) { + printf("bsdiff fails to create delta with offset score %d\n", offset_oldscore); + printf("Old: [%s] -> New: [%s]\n", argv[1], argv[2]); + } +#endif + /* Seek to the beginning, write the header, and close the file */ + if (fseeko(data.pf, 0, SEEK_SET)) + err(1, "fseeko"); + + if (fwrite(data.header, 16, 1, data.pf) != 1) + err(1, "fwrite(%s)", "temp_patch"); + if (fclose(data.pf)) + err(1, "fclose"); + /* Free the memory we used */ + free(data.I); + free(data.old); + free(data.new); + + return ret; +} + +void* Function(int offset_oldscore) +{ + unsigned int thread_num = 0; + off_t end; + off_t scan=0; + end = data.newsize; + int t1 = 0, t2 = 0; + get_time_stamp(); //total time capturing + t1 = atoi(ts1); + +#ifdef PATCH_FILE_FORMAT_MOD + u_char* db; + u_char* eb; + off_t dblen; + off_t eblen; +#endif + + off_t pos; + off_t len; + off_t lastscan; + off_t lastpos; + off_t lastoffset; + off_t oldscore; + off_t scsc; + off_t s; + off_t Sf; + off_t lenf; + off_t Sb; + off_t lenb; + off_t overlap; + off_t Ss; + off_t lens; + off_t i; + + len = 0; + lastscan = 0; + lastpos = 0; + lastoffset = 0; + while (scan < end) { + oldscore = 0; + for (scsc = scan += len; scan < end; scan++) { + len = search(data.I, data.old, data.oldsize, data.new[thread_num] + scan, end - scan, + len, data.oldsize, &pos); // Passing parameter as len instead of 0 for ramdisk.img etc taking long time + + for (; scsc < scan + len; scsc++) + if ((scsc + lastoffset < data.oldsize) && + (data.old[scsc + lastoffset] == data.new[thread_num][scsc])) + oldscore++; +#ifdef TIME_LIMIT_CHECK + outer_count++; + if (outer_count > 1000) { + outer_count = 0; + get_time_stamp(); //total time capturing + t2 = atoi(ts1); + //printf("\ntime diff = %d\n", (t2 - t1)); + if ((t2 - t1) > TIME_LIMIT_CHECK) + return 1; + } +#endif + if (((len == oldscore) && (len != 0)) || + (len > oldscore + offset_oldscore)) + break; + + if ((scan + lastoffset < data.oldsize) && + (data.old[scan + lastoffset] == data.new[thread_num][scan])) + oldscore--; + }; + if ((len != oldscore) || (scan == end)) { + s = 0; + Sf = 0; + lenf = 0; + for (i = 0; (lastscan + i < scan) && (lastpos + i < data.oldsize); ) { + if (data.old[lastpos + i] == data.new[thread_num][lastscan + i]) + s++; + i++; + if (s * 2 - i > Sf * 2 - lenf) { + Sf = s; + lenf = i; + }; + }; + + lenb = 0; + if (scan < end) { + s = 0; + Sb = 0; + for (i = 1; (scan >= lastscan + i) && (pos >= i); i++) { + if (data.old[pos - i] == data.new[thread_num][scan - i]) + s++; + if (s * 2 - i > Sb * 2 - lenb) { + Sb = s; + lenb = i; + }; + }; + }; + + if (lastscan + lenf > scan - lenb) { + overlap = (lastscan + lenf) - (scan - lenb); + s = 0; + Ss = 0; + lens = 0; + for (i = 0; i < overlap; i++) { + if (data.new[thread_num][lastscan + lenf - overlap + i] == + data.old[lastpos + lenf - overlap + i]) + s++; + if (data.new[thread_num][scan - lenb + i] == + data.old[pos - lenb + i]) + s--; + if (s > Ss) { + Ss = s; + lens = i + 1; + }; + }; + + lenf += lens - overlap; + lenb -= lens; + }; + + if (((db = malloc(lenf + 1)) == NULL) || + ((eb = malloc((scan - lenb) - (lastscan + lenf) + 1)) == NULL)) + err(1, NULL); + + for (i = 0; i < lenf; i++) + db[i] = data.new[thread_num][lastscan + i] - data.old[lastpos + i]; + for (i = 0; i < (scan - lenb) - (lastscan + lenf); i++) + eb[i] = data.new[thread_num][lastscan + lenf + i]; + dblen = lenf; + eblen = (scan - lenb) - (lastscan + lenf); + offtout(lenf, data.buf2); + offtout((scan - lenb) - (lastscan + lenf), data.buf2 + 8); + offtout(lastpos, data.buf2 + 16); + offtout((data.size_thread * thread_num) + lastscan, data.buf2 + 24); + fwrite(data.buf2, 1, 32, data.pf); + //if (data.bz2err != BZ_OK) + //errx(1, "fwrite, bz2err = %d", data.bz2err); + + fwrite(db, 1, dblen, data.pf); + //if (data.bz2err != BZ_OK) + //errx(1, "fwrite, bz2err = %d", data.bz2err); + + fwrite(eb, 1, eblen, data.pf); + //if (data.bz2err != BZ_OK) + //errx(1, "fwrite, bz2err = %d", data.bz2err); + + free(db); + free(eb); + + lastscan = scan - lenb; + lastpos = pos - lenb; + lastoffset = pos - scan; + }; + }; + return 0; +} + +const char *kCantReadMessage = "Can not read input file"; +const char *kCantWriteMessage = "Can not write output file"; +const char *kCantAllocateMessage = "Can not allocate memory"; +const char *kDataErrorMessage = "Data error"; + +static void *SzAlloc(void *p, size_t size) +{ + p = p; + return MyAlloc(size); +} +static void SzFree(void *p, void *address) +{ + p = p; + MyFree(address); +} +static ISzAlloc g_Alloc = { SzAlloc, SzFree }; + +void PrintHelp(char *buffer) +{ + strcat(buffer, "\nLZMA Utility " MY_VERSION_COPYRIGHT_DATE "\n" + "\nUsage: lzma inputFile outputFile\n" + " e: encode file\n" + " d: decode file\n"); +} + +int PrintError(char *buffer, const char *message) +{ + strcat(buffer, "\nError: "); + strcat(buffer, message); + strcat(buffer, "\n"); + return 1; +} + +int PrintErrorNumber(char *buffer, SRes val) +{ + sprintf(buffer + strlen(buffer), "\nError code: %x\n", (unsigned)val); + return 1; +} + +int PrintUserError(char *buffer) +{ + return PrintError(buffer, "Incorrect command"); +} + +#define IN_BUF_SIZE (1 << 16) +#define OUT_BUF_SIZE (1 << 16) + + +static SRes Encode(ISeqOutStream *outStream, ISeqInStream *inStream, UInt64 fileSize, char *rs) +{ + CLzmaEncHandle enc; + SRes res; + CLzmaEncProps props; + + rs = rs; + + enc = LzmaEnc_Create(&g_Alloc); + if (enc == 0) + return SZ_ERROR_MEM; + + LzmaEncProps_Init(&props); + res = LzmaEnc_SetProps(enc, &props); + + if (res == SZ_OK) { + Byte header[LZMA_PROPS_SIZE + 8]; + size_t headerSize = LZMA_PROPS_SIZE; + int i; + + res = LzmaEnc_WriteProperties(enc, header, &headerSize); + for (i = 0; i < 8; i++) + header[headerSize++] = (Byte)(fileSize >> (8 * i)); + if (outStream->Write(outStream, header, headerSize) != headerSize) + res = SZ_ERROR_WRITE; + else { + if (res == SZ_OK) + res = LzmaEnc_Encode(enc, outStream, inStream, NULL, &g_Alloc, &g_Alloc); + } + } + LzmaEnc_Destroy(enc, &g_Alloc, &g_Alloc); + return res; +} + +int main2(int numArgs, const char *args[], char *rs) +{ + CFileSeqInStream inStream; + CFileOutStream outStream; + char c; + int res; + int encodeMode; + Bool useOutFile = False; + + FileSeqInStream_CreateVTable(&inStream); + File_Construct(&inStream.file); + + FileOutStream_CreateVTable(&outStream); + File_Construct(&outStream.file); + + encodeMode = 1; + + size_t t4 = sizeof(UInt32); + size_t t8 = sizeof(UInt64); + if (t4 != 4 || t8 != 8) + return PrintError(rs, "Incorrect UInt32 or UInt64"); + + if (InFile_Open(&inStream.file, "temp_patch") != 0) + return PrintError(rs, "Can not open input file"); + + + if (OutFile_Open(&outStream.file, args[3]) != 0) + return PrintError(rs, "Can not open output file"); + + + UInt64 fileSize; + File_GetLength(&inStream.file, &fileSize); + res = Encode(&outStream.s, &inStream.s, fileSize, rs); + + File_Close(&outStream.file); + File_Close(&inStream.file); + + if (res != SZ_OK) { + if (res == SZ_ERROR_MEM) + return PrintError(rs, kCantAllocateMessage); + else if (res == SZ_ERROR_DATA) + return PrintError(rs, kDataErrorMessage); + else if (res == SZ_ERROR_WRITE) + return PrintError(rs, kCantWriteMessage); + else if (res == SZ_ERROR_READ) + return PrintError(rs, kCantReadMessage); + return PrintErrorNumber(rs, res); + } + return 0; +} + +int MY_CDECL main(int numArgs, const char *args[]) +{ + char rs[800] = { 0 }; + if (numArgs != 4) + errx(1,"ss_bsdiff Version 5.0\nUsage: ss_bsdiff oldfile newfile patchfile\n"); + + int ret = create_patch(numArgs, args, 8); +#ifdef TIME_LIMIT_CHECK + if (ret != 0) { + printf("Trying with offset score 2\n"); + ret = create_patch(numArgs, args, 2); + } + if (ret != 0) { + printf("Trying with offset score 0\n"); + ret = create_patch(numArgs, args, 0); + } + if (ret != 0) + err(1, "bsdiff fails to create delta within timelimit"); +#endif + int res = main2(numArgs, args, rs); + remove("temp_patch"); + fputs(rs, stdout); + return res; +} diff --git a/bsdiff/ss_bspatch.c b/bsdiff/ss_bspatch.c new file mode 100755 index 0000000..104e24a --- /dev/null +++ b/bsdiff/ss_bspatch.c @@ -0,0 +1,411 @@ +/*- + * Copyright 2003-2005 Colin Percival + * Copyright 2012 Matthew Endsley + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Modifications are made in reimplementing suffix sort array generation + * and how the data is read and written to.Iterative part replaced the + * recursive implementation to avoid buffer overflow problems + */ +#define _CRT_SECURE_NO_WARNINGS +#define CONST_MEMORY_USAGE 16384 +#define PATCH_FILE_FORMAT_MOD +#define BSDIFF_HEADER "BSDIFF40" +#define SSDIFF_HEADER "SSDIFF40" +#define MULTI_THREADING +#include +#include +#include +#include + +#include +#include +#include + +#include +#include <7zFile.h> +#include <7zVersion.h> +#include +#include + +const char *kCantReadMessage = "Can not read input file"; +const char *kCantWriteMessage = "Can not write output file"; +const char *kCantAllocateMessage = "Can not allocate memory"; +const char *kDataErrorMessage = "Data error"; + +static void *SzAlloc(void *p, size_t size) +{ + p = p; + return MyAlloc(size); +} + +static void SzFree(void *p, void *address) +{ + p = p; + MyFree(address); +} +static ISzAlloc g_Alloc = { SzAlloc, SzFree }; + +static off_t offtin(u_char *buf) +{ + off_t y; + + y = buf[7] & 0x7F; + y = y * 256; + y += buf[6]; + y = y * 256; + y += buf[5]; + y = y * 256; + y += buf[4]; + y = y * 256; + y += buf[3]; + y = y * 256; + y += buf[2]; + y = y * 256; + y += buf[1]; + y = y * 256; + y += buf[0]; + + if (buf[7] & 0x80) + y = -y; + + return y; +} + +void PrintHelp(char *buffer) +{ + strcat(buffer, "\nLZMA Utility " MY_VERSION_COPYRIGHT_DATE "\n" + "\nUsage: lzma inputFile outputFile\n" + " e: encode file\n" + " d: decode file\n"); +} + +int PrintError(char *buffer, const char *message) +{ + strcat(buffer, "\nError: "); + strcat(buffer, message); + strcat(buffer, "\n"); + return 1; +} + +int PrintErrorNumber(char *buffer, SRes val) +{ + sprintf(buffer + strlen(buffer), "\nError code: %x\n", (unsigned)val); + return 1; +} + +int PrintUserError(char *buffer) +{ + return PrintError(buffer, "Incorrect command"); +} + +#define IN_BUF_SIZE (1 << 16) +#define OUT_BUF_SIZE (1 << 16) + +static SRes Decode2(CLzmaDec *state, ISeqOutStream *outStream, ISeqInStream *inStream, + UInt64 *unpackSize,unsigned char *dec_data) +{ + int thereIsSize = (*unpackSize != (UInt64)(Int64) - 1); + UInt64 offset = 0; + Byte inBuf[IN_BUF_SIZE]; + Byte outBuf[OUT_BUF_SIZE]; + size_t inPos = 0, inSize = 0, outPos = 0; + + LzmaDec_Init(state); + + offset = 0; + + for (;;) { + if (inPos == inSize) { + inSize = IN_BUF_SIZE; + RINOK(inStream->Read(inStream, inBuf, &inSize)); + inPos = 0; + } + + SRes res; + SizeT inProcessed = inSize - inPos; + SizeT outProcessed = OUT_BUF_SIZE - outPos; + ELzmaFinishMode finishMode = LZMA_FINISH_ANY; + ELzmaStatus status; + + if (thereIsSize && outProcessed > *unpackSize) { + outProcessed = (SizeT) * unpackSize; + finishMode = LZMA_FINISH_END; + } + + res = LzmaDec_DecodeToBuf(state, outBuf + outPos, &outProcessed, + inBuf + inPos, &inProcessed, finishMode, &status); + inPos += inProcessed; + outPos += outProcessed; + *unpackSize -= outProcessed; + memcpy(dec_data + offset, outBuf, outProcessed); + offset += outProcessed; + + outPos = 0; + + if ((res != SZ_OK) || (thereIsSize && *unpackSize == 0)) + return res; + + if (inProcessed == 0 && outProcessed == 0) { + if (thereIsSize || status != LZMA_STATUS_FINISHED_WITH_MARK) + return SZ_ERROR_DATA; + return res; + } + } +} + +int apply_patch(char *oldfile, char *patch_buffer, unsigned char **dest_buf, ssize_t *dest_size) +{ + int fd = -1, result = 0; + off_t oldsize, newsize; + u_char header[16], buf[8]; + u_char *old = NULL; + off_t oldpos, newpos; + off_t ctrl[4]; /////////////////////////////////////THREAD + off_t total_write; /////////////////////////////////////////THREAD + off_t j; + off_t memory_usage = CONST_MEMORY_USAGE; + off_t match_size; + off_t patch_buffer_offset = 0; + bool flag; + + /* + File format: + 0 8 "BSDIFF40" + 8 8 X + 16 8 Y + 24 8 sizeof(newfile) + 32 X bzip2(control block) + 32+X Y bzip2(diff block) + 32+X+Y ??? bzip2(extra block) + with control block a set of triples (x,y,z) meaning "add x bytes + from oldfile to x bytes from the diff block; copy y bytes from the + extra block; seek forwards in oldfile by z bytes". + */ + // Read header + if (patch_buffer) + memcpy(header, patch_buffer, 16); + else { + printf("%s().%d Corrupt decoded patch buffer\n", __FUNCTION__, __LINE__); + return 1; + } + + /* Check for appropriate magic */ + if (memcmp(header, BSDIFF_HEADER, 8) != 0 && memcmp(header, SSDIFF_HEADER, 8) != 0) { + printf("%s().%d Patch buffer header corrupt\n", __FUNCTION__, __LINE__ ); + return 1; + } + + /* Read lengths from header */ + newsize = offtin(header + 8); + + if ((newsize < 0)) { + printf("%s().%d Patch buffer corrupt\n", __FUNCTION__, __LINE__ ); + return 1; + } + + /* Cset patch_buffer_offset at the right place */ + patch_buffer_offset += 16; + + if (((fd = open(oldfile, O_RDONLY, 0)) < 0) || + ((oldsize = lseek(fd, 0, SEEK_END)) == -1) || + ((old = malloc(memory_usage + 1)) == NULL) || + (lseek(fd, 0, SEEK_SET) != 0)) { + printf("Corruption in old file %s\n", oldfile); + result = 1; + goto Cleanup; + } + + if ((*dest_buf = malloc(newsize + 1)) == NULL) { + printf("Corruption in old file %s\n", oldfile); + result = 1; + goto Cleanup; + } + oldpos = 0; + newpos = 0; + + total_write = 0; + + while (total_write != newsize) { + /* Read control data */ + for (j = 0; j <= 3; j++) { + memcpy(buf, patch_buffer + patch_buffer_offset, 8); + patch_buffer_offset += 8; + ctrl[j] = offtin(buf); + }; + + total_write += (ctrl[0] + ctrl[1]); + newpos = ctrl[3]; + oldpos = ctrl[2]; + + ////////////////////////////////////////////////////////////////////////////////// + flag = true; + match_size = ctrl[0]; + while (flag == true) { + if (match_size <= memory_usage) { + if (pread(fd, old, match_size, oldpos) != match_size) { + printf("Corruption in old file %s\n", oldfile); + result = 1; + goto Cleanup; + } + if (newpos + match_size > newsize) { + printf("%s().%d Corrupt patch\n", __FUNCTION__, __LINE__ ); + result = 1; + goto Cleanup; + } + memcpy((*dest_buf) + newpos, patch_buffer + patch_buffer_offset, match_size); + patch_buffer_offset += match_size; + for (j = 0; j < match_size; j++) { + (*dest_buf)[newpos + j] += old[j]; + } + newpos += match_size; + flag = false; + } else { + if (pread(fd, old, memory_usage, oldpos) != memory_usage) { + printf("%s().%d Corruption in old file %s\n", __FUNCTION__, __LINE__ , oldfile); + result = 1; + goto Cleanup; + } + if (newpos + memory_usage > newsize) { + printf("%s().%d Corrupt patch\n", __FUNCTION__, __LINE__ ); + result = 1; + goto Cleanup; + } + memcpy((*dest_buf) + newpos, patch_buffer + patch_buffer_offset, memory_usage); + patch_buffer_offset += memory_usage; + for (j = 0; j < memory_usage; j++) + (*dest_buf)[newpos + j] += old[j]; + match_size -= memory_usage; + oldpos += memory_usage; + newpos += memory_usage; + } + } + + //////////////////////////////////////////////////////////////////////////////////////// + /* Sanity-check */ + if (newpos + ctrl[1] > newsize) { + printf("%s().%d Corrupt patch\n", __FUNCTION__, __LINE__ ); + result = 1; + goto Cleanup; + } + /* Read extra string */ + memcpy((*dest_buf) + newpos, patch_buffer + patch_buffer_offset, ctrl[1]); + patch_buffer_offset += ctrl[1]; + }; + *dest_size = newsize; +Cleanup: + //close old file + if (fd >= 0) + close(fd); + if (old) + free(old); + return result; +} + +int main2(int numArgs, const char *args[], char *rs) +{ + CFileSeqInStream inStream; + ISeqOutStream outStream; + int res, fd; + int encodeMode; + char * buf_res = NULL; + unsigned char* new_data; + ssize_t new_size; + + FileSeqInStream_CreateVTable(&inStream); + File_Construct(&inStream.file); + + FileOutStream_CreateVTable(&outStream); + //File_Construct(&outStream.file); + + encodeMode = 0; + + size_t t4 = sizeof(UInt32); + size_t t8 = sizeof(UInt64); + if (t4 != 4 || t8 != 8) + return PrintError(rs, "Incorrect UInt32 or UInt64"); + + if (InFile_Open(&inStream.file, args[3]) != 0) + return PrintError(rs, "Can not open input file"); + else if (encodeMode) + PrintUserError(rs); + + if (encodeMode) { + UInt64 fileSize; + File_GetLength(&inStream.file, &fileSize); + //res = Encode(&outStream.s, &inStream.s, fileSize, rs); + } else { + UInt64 unpackSize, i, dest_len; + CLzmaDec state; + unsigned char header[LZMA_PROPS_SIZE + 8]; + RINOK(SeqInStream_Read(&inStream.s, header, sizeof(header))); + unpackSize = 0; + for (i = 0; i < 8; i++) + unpackSize += (UInt64)header[LZMA_PROPS_SIZE + i] << (i * 8); + buf_res = (unsigned char *)malloc(unpackSize); + memset(buf_res, 0x0, unpackSize); + dest_len = unpackSize; + LzmaDec_Construct(&state); + RINOK(LzmaDec_Allocate(&state, header, LZMA_PROPS_SIZE, &g_Alloc)); + res = Decode2(&state, &outStream, &inStream.s, &unpackSize,buf_res); + LzmaDec_Free(&state, &g_Alloc); + File_Close(&inStream.file); + if (apply_patch(args[1], buf_res, &new_data, &new_size) != 0) { + if (new_data){ + free(new_data); + free(buf_res); + } + return 1; + } + if (((fd = open(args[2], O_CREAT | O_TRUNC | O_WRONLY, 0666)) < 0) || + (write(fd, new_data, new_size) != new_size) || (close(fd) == -1)) + err(1,"%s",args[2]); + if (res != SZ_OK) { + free(new_data); + free(buf_res); + if (res == SZ_ERROR_MEM) + return PrintError(rs, kCantAllocateMessage); + else if (res == SZ_ERROR_DATA) + return PrintError(rs, kDataErrorMessage); + else if (res == SZ_ERROR_WRITE) + return PrintError(rs, kCantWriteMessage); + else if (res == SZ_ERROR_READ) + return PrintError(rs, kCantReadMessage); + return PrintErrorNumber(rs, res); + } + free(new_data); + free(buf_res); + return 0; + } +} + +int main(int numArgs, const char *args[]) +{ + char rs[800] = { 0 }; + if (numArgs != 4) + errx(1,"ss_bspatch Version 1.0\nUsage: ss_bspatch oldfile newfile patchfile\n"); + int res = main2(numArgs, args, rs); + fputs(rs, stdout); + return res; +} diff --git a/packaging/libtota.spec b/packaging/libtota.spec new file mode 100755 index 0000000..a798251 --- /dev/null +++ b/packaging/libtota.spec @@ -0,0 +1,79 @@ +Name: libtota +Summary: fota update library +ExclusiveArch: %{arm} +Version: 0.1.0 +Release: 1 +Group: System +License: Apache-2.0 and BSD-2-Clause and BSD-3-Clause and PD +Source0: %{name}-%{version}.tar.gz + +BuildRequires: cmake +BuildRequires: pkgconfig(lib7zip) +BuildRequires: pkgconfig(libdivsufsort) + +%description +Fota update agent which update firmware using delta files + +%package devel +License: Apache-2.0 and BSD-2-Clause and BSD-3-Clause and PD +Summary: libtota library (development) +Requires: libtota = %{version} +Group: Development/Libraries + +%description devel +Description: libfactory library (development) + +%package -n tota-bsdiff +Summary: bsdiff / bspatch tools for TOTA + +%description -n tota-bsdiff +bsdiff / bspatch are tools for building and applying patches to binary files. +This package offers these tools for TOTA. + +%prep +%setup -q + + +%build +export LDFLAGS+="-Wl,--rpath=%{_prefix}/lib -Wl,--as-needed" +mkdir cmake_tmp +cd cmake_tmp +LDFLAGS="$LDFLAGS" cmake .. -DCMAKE_INSTALL_PREFIX=%{_prefix} + +make %{?jobs:-j%jobs} + +%install +cd cmake_tmp +%make_install +#mkdir -p %{buildroot}/usr/lib/ +cp libtota.a %{buildroot}/usr/lib/libtota.a +%post + + +%files +%defattr(-,root,root,-) +%license LICENSE.Apache-2.0 +%license LICENSE.BSD-2-Clause +%license LICENSE.BSD-3-Clause +#%{_libdir}/libtota.a +#%manifest fota.manifest + +%files devel +%defattr(-,root,root,-) +%{_libdir}/libtota.a +%{_libdir}/pkgconfig/tota.pc +%{_includedir}/fota_common.h +%{_includedir}/fota_log.h +%{_includedir}/fota_tar.h +%{_includedir}/SS_Common.h +%{_includedir}/SS_Engine_Errors.h +%{_includedir}/SS_Engine_Update.h +%{_includedir}/SS_FSUpdate.h +%{_includedir}/SS_ImageUpdate.h +%{_includedir}/SS_MultiProcessUpdate.h +%{_includedir}/SS_Nand.h +%{_includedir}/SS_UPI.h + +%files -n tota-bsdiff +%{_bindir}/ss_bsdiff +%{_bindir}/ss_bspatch diff --git a/ss_engine/SS_Common.c b/ss_engine/SS_Common.c new file mode 100755 index 0000000..876fb21 --- /dev/null +++ b/ss_engine/SS_Common.c @@ -0,0 +1,117 @@ +/* + * libtota + * + * Copyright (c) 2017 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 +#include +#include +#include +#include +#include +#include +#include + +#include "SS_ImageUpdate.h" +#include "SS_Engine_Errors.h" +#include "SS_Common.h" + +#include "ua.h" +#include "fota_tar.h" +#include "fota_common.h" + +void SS_Progress(void *pbUserData, SS_UINT32 uPercent) +{ + LOG("Progress before\n"); + LOGL(LOG_SSENGINE, "Progress.. (%u %%)\n", uPercent); + LOG("Progress after:\n"); + ((ua_data_t *) pbUserData)->ui_progress(pbUserData, uPercent); +} + +/* Prints a string like the C printf() function */ +SS_UINT32 SS_Trace(void *pUser, const char *aFormat, ...) +{ +#if 0 + LOGL(LOG_REDBEND, aFormat); +#else + char temp[4096]; + va_list list; + + va_start(list, aFormat); + vsprintf(temp, aFormat, list); + va_end(list); + + LOGL(LOG_SSENGINE, "%s", temp); +#endif + return S_SS_SUCCESS; +} + +long SS_FSTrace(void *pbUserData, const unsigned short *aFormat, ...) +{ + va_list list; + + va_start(list, aFormat); + vprintf((const char *)aFormat, list); + va_end(list); + + return S_SS_SUCCESS; +} + +long SS_ResetTimerA(void) +{ + //LOG("%s \n", __func__); + + return S_SS_SUCCESS; +} + +long SS_GetDelta(void *pbUserData, unsigned char *pbBuffer, SS_UINT32 dwStartAddressOffset, SS_UINT32 dwSize) +{ + int ret = S_SS_SUCCESS; + int readCount = 0; + FILE *fp; + long itemOffset = 0; + + ua_data_t *ua_data = (ua_data_t *) pbUserData; + ua_part_info_t *ua_partition = ua_data->parti_info; + ua_update_data_t *ua_update_data = ua_data->update_data; + + LOGL(LOG_SSENGINE, "SS_GetDelta offset 0x%x(%u), size 0x%x(%u)\n", + dwStartAddressOffset, dwStartAddressOffset, dwSize, dwSize); + + itemOffset = tar_get_item_offset(ua_update_data->ua_delta_path, ua_partition->ua_subject_name); + if (itemOffset < 0) { + return E_SS_OPENFILE_ONLYR; + } + + fp = fopen(ua_update_data->ua_delta_path, "r"); + if (!fp) { + LOGL(LOG_SSENGINE, "open file %s failed.\n", ua_update_data->ua_delta_path); + return E_SS_OPENFILE_ONLYR; + } + + if (fseek(fp, itemOffset + dwStartAddressOffset, 0) == -1) + ret = E_SS_READ_ERROR; + else { + readCount = fread(pbBuffer, 1, dwSize, fp); + if (readCount != dwSize) { + LOGL(LOG_SSENGINE, "error in read size\n"); + ret = E_SS_READ_ERROR; + } + } + fclose(fp); + + return ret; +} diff --git a/ss_engine/SS_Common.h b/ss_engine/SS_Common.h new file mode 100755 index 0000000..35a8eff --- /dev/null +++ b/ss_engine/SS_Common.h @@ -0,0 +1,64 @@ +/* + * libtota + * + * Copyright (c) 2017 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 "SS_MultiProcessUpdate.h" +#include "SS_FSUpdate.h" +#include "SS_ImageUpdate.h" +#include "SS_Engine_Errors.h" +#include "SS_Engine_Update.h" + +#include "ua.h" + +#define MAX_PATH 256 + +#define NAND_BLOCK_BITS 18 // 0x40000 +#define NAND_PAGE_BITS 12 // 0x1000 + +#if defined(PART_W) || defined(PART_WB1) || defined(PART_W3G) || defined(PART_TINY) \ + || defined(PART_HIGGS) || defined(PART_KIRAN) || defined(PART_KIRANLTE) || defined(PART_ORBIS) +#define UA_RAM_SIZE 64*1024*1024 // it sould be same 0x4000000 in xml. +#else +#define UA_RAM_SIZE 512*1024*1024 // it sould be same 0x20000000 in xml. +#endif + +#define FS_ID_MAX_LEN 4 +#define DP_HEADER_SIZE 36 +#define DP_START_OFFSET 0 +#define BACKUPBUFFER_NUM 4 + +/*******[ Multiprocess API sample implementation ]******/ +#define _NOEXEC_ +#define SAMPLE_PROCS_NUM (0) +#define EXTRA_ARGS (3) +#define HANDLE_RUN_PROC "handle_run_process" + +struct status_header_page { + unsigned char preamble[5]; // state·Î »ç¿ëÇÒ ¼ö ÀÖÀ½, FOTA, SBLB, ZIMG, PLRM, MDEM, DONE, FAILµî + unsigned long format; //ÃÖÁ¾ return °ª, ¾Æ·¡ °¢ image¿¡ ´ëÇÑ engineÀÇ return°ªÀ» DM error table·Î º¯°æµÈ °ª + unsigned long reserved; // Not used + unsigned long flexStatus; // Not used + unsigned long apfwStatus; //zImage update¿¡ ´ëÇÑ engineÀÇ return °ª + unsigned long apffsStatus; //platform update¿¡ ´ëÇÑengineÀÇreturn °ª + unsigned long apuaStatus; //sbl update¿¡ ´ëÇÑengineÀÇreturn °ª + unsigned long bpfwStatus; //modem update¿¡ ´ëÇÑengineÀÇreturn °ª + unsigned long bpuaStatus; +}; + +void SS_unicode_to_char(const char *src, char *dest); diff --git a/ss_engine/SS_Engine_Errors.h b/ss_engine/SS_Engine_Errors.h new file mode 100755 index 0000000..f116c14 --- /dev/null +++ b/ss_engine/SS_Engine_Errors.h @@ -0,0 +1,116 @@ +/* + * libtota + * + * Copyright (c) 2017 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. + */ + +/* + SS_Engine Errors + + 0xAxx Series define INPUT/UA Errors + + 0xBxx Series define Delta Errors + + 0xCxx Series define System Errors + + 0xDxx Series define Engine Errors + + 0xExx Series define any Other Errors + +*/ + +#ifndef __SS_Engine_ERRORS__ +#define __SS_Engine_ERRORS__ + +#define S_SS_SUCCESS (0) /*! Success Code */ +#define E_SS_FAILURE (1) /*! Failure Code */ + +/* INPUT Processing errors */ + +/* invocation errors */ +#define E_SS_BAD_PARAMS (0xA00) /**< error in a run parameter */ +#define E_SS_FSINVALIDNODEPARAMS (0xA01) /* Failed to Parse the Params to verify NODE */ +#define E_SS_FSBADNODES (0xA02) /* Failed to verify NODE for FS */ +#define E_SS_FSBADDELTA (0xA03) /* Delta File does NOT contain required information on FS */ +#define E_SS_FSBADATTRIBUTES (0xA04) /* Failed to parse attribute data */ +#define E_SS_FSFAILEDTOOPENPATCHINFO (0xA05) /*Failed to open patch list file having details of PATCH info */ +#define E_SS_FSFAILEDTOPARSEDELTACNT (0xA06) /* Failed to parse the PATCH count information */ +#define E_SS_FSFAILEDTOPARSEDELTAINFO (0xA07) /* Failed to parse the Delta patch information */ + +/* update package errors */ +#define E_SS_PKG_TOO_LONG (0xA08) /**< expected length error */ +#define E_SS_PKG_CORRUPTED (0xA09) /**< structural error */ +#define E_SS_SOURCE_CORRUPTED (0xA10) /**< signature error */ + +/*Delta Errors*/ + +#define E_SS_FSBADPATCH (0xB00) /*File Patch Does NOT match the signature */ +#define E_SS_FSSHA_MISMATCH (0xB01) /*Could NOT produce expected Target SHA for file */ +#define E_SS_IMGBADDELTA (0xB02) /* Failed to parse attribute data */ +#define E_SS_IMGBADPATCH (0xB03) /*Image Patch Does NOT match the signature */ +#define E_SS_IMGSHA_MISMATCH (0xB04) /*Could NOT produce expected Target SHA for Image */ +#define E_SS_SHAPRASE_FAILED (0xB05) /*Could NOT Parse SHA */ + +/* SYSTEM errors */ + +/* Resources errors */ +#define E_SS_NOT_ENOUGH_RAM (0xC00) /**< given RAM is not enough */ +#define E_SS_BAD_RAM (0xC01) /**< does not behave as RAM */ +#define E_SS_MALLOC_ERROR (0xC02) /**< memory allocation failure */ + +/* Image update Error codes */ +#define E_SS_WRITE_ERROR (0xC03) /**< flash writing failure */ +#define E_SS_ERASE_ERROR (0xC04) /**< flash erasing failure */ +#define E_SS_READ_ERROR (0xC05) /**< flash reading failure */ + +/*File System Error codes */ +#define E_SS_OPENFILE_ONLYR (0xC06) /**< file does not exist */ +#define E_SS_OPENFILE_WRITE (0xC07) /**< RO or no access rights */ +#define E_SS_DELETEFILE_NOFILE (0xC08) /**< file does not exist */ +#define E_SS_DELETEFILE (0xC09) /**< no access rights */ +#define E_SS_RESIZEFILE (0xC10) /**< cannot resize file */ +#define E_SS_READFILE_SIZE (0xC11) /**< cannot read specified size*/ +#define E_SS_CLOSEFILE_ERROR (0xC12) /**< cannot close file handle */ +#define E_SS_FAILED_CREATING_SYMBOLIC_LINK (0xC13) /**< Failed creating symbolic link */ +#define E_SS_CANNOT_CREATE_DIRECTORY (0xC14) /**< Failed creating directory */ +#define E_SS_FSMEMORYERROR (0xC15) /* Failed to allocate Memory */ +#define E_SS_FILENAMELENERROR (0xC16) /* Failed to serve filename length */ + +/*Engine errors */ + +#define E_SS_NOT_ENOUGH_RAM_FOR_OPERATION2 (0xD00) /**< There is not enough RAM to run with operation=2 (Dry update) */ +#define E_SS_DELTA_FILE_TOO_LONG (0xD01) /**< Delta file too long - curropted */ +#define E_SS_ERROR_IN_DELETES_SIG (0xD02) /**< Mismatch between deletes sig and delta deletes buffers signature */ +#define E_SS_DELTA_IS_CORRUPT (0xD03) /**< Delta file is corrupt: signature mismatch between delta header signature and calculated signature */ +#define E_SS_SOURCE_FILE_SIG_MISMATCH (0xD04) /**< File signature does not match signature */ +#define E_SS_TARGET_SIG_MISMATCH (0xD05) /**< Signature for the target buffer does not match the one stored in the delta file */ +#define E_SS_INVALID_BACKUP (0xD06) /**< Too many dirty buffers */ +#define E_SS_UPI_VERSION_MISMATCH (0xD07) /**< UPI version mismatch between UPI and delta */ +#define E_SS_PARTITION_NAME_NOT_FOUND (0xD08) /**< Partition name is different in delta and in UPI data */ +#define E_SS_NO_SPACE_LEFT (0xD09) /**< There is not enough flash to update or install the files */ +#define E_SS_INVALID_DP_HEADER (0xD10) /**< Deployment Package header is invalid */ +#define E_SS_INVALID_DP_WRONG_SIGNATURE (0xD11) /**< Deployment Package signature is invalid */ +#define E_SS_FSFAILEDTOBACKUPPATCHINFO (0xD12) /* Failed to create backup file to write Delta patch info data */ +#define E_SS_FSUPDATEFAILED (0xD13) /*FS Failed during UPGRADE */ +#define E_SS_FSSRCBACKUPFAILED (0xD14) /*Failed to backup FS */ +#define E_SS_FSSRCCURRUPTED (0xD15) /*Could NOT update FS as SRC seems to be corrupted */ +#define E_SS_IMGUPDATEFAILED (0xD16) /*IMG Failed during UPGRADE */ +#define E_SS_IMGSRCBACKUPFAILED (0xD17) /*Failed to backup IMG */ +#define E_SS_IMGRECOVERYWRITEFAILED (0xD18) /*Failed to write patched Recovery IMG */ +#define E_SS_IMGSRCCURRUPTED (0xD19) /*Could NOT update IMG as SRC seems to be corrupted */ +#define E_SS_IMGFLASHWRITEFAIL (0xD20) /*Failed to write Patched IMG data to flash */ +#define E_SS_PATCHFILE_DEL_ERROR (0xD21) /*Failed to Clear/Del Patched SRC file */ + +#endif diff --git a/ss_engine/SS_Engine_Update.h b/ss_engine/SS_Engine_Update.h new file mode 100755 index 0000000..72d6f8e --- /dev/null +++ b/ss_engine/SS_Engine_Update.h @@ -0,0 +1,551 @@ +/* + * libtota + * + * Copyright (c) 2017 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. + */ + +/*! + ******************************************************************************* + * \file SS_vRM_Update.h + * + * \brief UPI Update API + ******************************************************************************* + */ +#ifndef _SS_Engine_UPDATE_H +#define _SS_Engine_UPDATE_H +/** + * Partition type + */ +typedef enum { + PT_FOTA, //!< Image + PT_FS //!< File system +} PartitionType; + +/** + * In-place update + */ +typedef enum { + UT_SELF_UPDATE = 0, //!< Don't update in place + UT_NO_SELF_UPDATE, //!< Update in place + UT_PRIVATE, //!< For internal usage +} UpdateType; + +typedef unsigned int SS_UINT32; + +typedef enum { + FT_REGULAR_FILE, + FT_SYMBOLIC_LINK, + FT_FOLDER, + FT_MISSING +} enumFileType; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Partition data + */ + typedef struct tagCustomerPartitionData { +/** + * Partition name. Maximum 256 characters. Must match exactly the name used in + * the UPG. + */ + const char *partition_name; + +/** + * Partition flash address. Address must be sector aligned. Relevant only for + * R/O partitions of any type; for R/W FS updates, set to 0. + */ + SS_UINT32 rom_start_address; + +/** + * Mount point or drive letter containing the partition. Maximum size is 256 + * characters. Relevent only for R/W FS updates; otherwise set to 0.

+ */ + const char *mount_point; + +/** + * Source path (input) partition if the update will not be done in place. + * Maximum 25 characters. For Image updates, set to 0. + */ + const char *strSourcePath; + +/** + * Target path (output) partition if the update will not be done in place. + * Maximum 25 characters. For Image updates, set to 0. + */ + const char *strTargetPath; + +/** + * Internal use; leave null. + */ + const void *priv; + +/** + * Partition type, a \ref PartitionType value. + */ + PartitionType partition_type; + + } CustomerPartitionData; + +/** + * Device data + */ + typedef struct tag_Engine_DeviceData { +/** + * UPI Mode. One of: + * \li 0: scout and update. Verify that the update is applicable to the device + * by comparing protocol versions and then install (or continue to install after + * an interruption) the update. + * \li 1: Scout only. Verify that the update is applicable to the device by + * comparing protocol versions. + * \li 2: Dry-run. Verify that the update is applicable to the device by + * comparing protocol versions and then "run" the update without actually + * changing any files on the device. This verification mode checks that files + * can be uncompressed and updates can be applied. It does not verify the + * results after the updates are applied. + * \li 3: Update only. Install (or continue to install after an interruption) + * the update without verification. Applying an update will fail + * catastrophically if performed on an incompatible firmware. This mode cannot + * be chosen if the update was created with an "optional file". + * \li 6: Verify post-install: Verify the file contents of the updated firmware + * (post-installation). This mode applies only to FS updates. Does not verify + * attributes or empty folders. + */ + SS_UINT32 ui32Operation; + +/** + * Pre-allocated RAM space. + */ + unsigned char *pRam; + +/** + * Size of pRam in bytes. + */ + SS_UINT32 ui32RamSize; + +/** + * Number of backup sectors listed in pBufferBlocks. + */ + SS_UINT32 ui32NumberOfBuffers; + +/** + * List of backup buffer sector addresses. Addresses must be + * sector-aligned.If the update consists only of Image update, pBufferBlocks + * is used instead of pTempPath. Otherwise pBufferBlocks should be set to 0 + * and pTempPath (see below) will be used to allocate the backup as a file. + */ + SS_UINT32 *pBufferBlocks; + +/** + * Number of partitions listed in pFirstPartitionData. + */ + SS_UINT32 ui32NumberOfPartitions; + +/** + * List of partition data structures, a list of \ref CustomerPartitionData + * values. + */ + CustomerPartitionData *pFirstPartitionData; + +/** + * Path to temporary storage. If the update contains FS updates, pTempPath is + * used instead of pBufferBlocks. If the update consists only of Image updates, + * set pTempPath to 0. The path size is limited to 256 characters. + * The path must be under the mount point of a partition containing a R/W file system.

+ * The maximum file size will not exceed the sector size x number of backup + * sectors. + */ + char *pTempPath; + +/** + * Whether or not there is a UPI self-update in the update, an + * \ref UpdateType value. + */ + UpdateType enmUpdateType; + +/** + * List of customer-defined installer types. For an Image only update, use a + * single element array with the value set to 0.

+ * + * For the list of installer types, see the SWM Center documentation. + */ + SS_UINT32 *pComponentInstallerTypes; + +/** + * Number of installer types in pComponentInstallerTypes. + */ + SS_UINT32 ui32ComponentInstallerTypesNum; + +/** + * Update flags, if any. Used by the SWM Center.

+ * + * For non SWM Center update, set to 0xFFFFFFFF + * For the list of update flags, see the SWM Center documentation. + */ + SS_UINT32 ui32ComponentUpdateFlags; + +/** + * Update number within the DP. + */ + SS_UINT32 ui32OrdinalToUpdate; + +/** + * Deprecated. + */ + char *pDeltaPath; + +/** + * Additional data to pass to APIs, if any. Set to null if not used. + */ + void *pbUserData; + } Engine_DeviceData; + +/** + ******************************************************************************* + * Validate the DP. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_CheckDPStructure(void *pbUserData); + +/** + ******************************************************************************* + * Get the number of updates in a DP that match the \a installer_type and + * \a component_flags values. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param num_deltas (out) Number of updates + * \param installer_types List of installer types; for an Image only + * update, this is a one element array with a value + * of 0 + * \param installer_types_num Size of \a installer_types + * \param component_flags Update flags, if any. Used by the SWM Center. + * For the list of component flags, see the SWM + * Center documentation. + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_GetNumberOfDeltas(void *pbUserData, SS_UINT32 * num_deltas, SS_UINT32 * installer_types, + SS_UINT32 installer_types_num, SS_UINT32 component_flags); + +/** + ******************************************************************************* + * Get the offset in bytes of the specified update in the DP. Consider only + * updates that match the \a installer_type and \a component_flags values. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param delta_ordinal Update number + * \param offset (out) Bytes from start of DP + * \param size (out) Size of update, in bytes + * \param installer_types List of installer types; for an Image only + * update, this is a one element array with a value + * of 0 + * \param installer_types_num Size of \a installer_types + * \param component_flags Update flags, if any. Used by the SWM Center. + * For the list of component flags, see the SWM + * Center documentation. + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_GetSignedDeltaOffset(void *pbUserData, SS_UINT32 delta_ordinal, SS_UINT32 * offset, SS_UINT32 * size, + SS_UINT32 * installer_types, SS_UINT32 installer_types_num, SS_UINT32 component_flags); + +/** + ******************************************************************************* + * Get the offset in bytes of the specified update in the DP. Consider only + * updates that match the \a installer_type and \a component_flags values. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param delta_ordinal Update number + * \param offset (out) Bytes from start of DP + * \param size (out) Size of update, in bytes + * \param installer_types List of installer types; for an Image only + * update, this is a one element array with a value + * of 0 + * \param installer_types_num Size of \a installer_types + * \param component_flags Update flags, if any. Used by the SWM Center. + * For the list of component flags, see the SWM + * Center documentation. + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_GetUnsignedDeltaOffset(void *pbUserData, SS_UINT32 delta_ordinal, SS_UINT32 * offset, SS_UINT32 * size, + SS_UINT32 * installer_types, SS_UINT32 installer_types_num, + SS_UINT32 component_flags); + +/** + ******************************************************************************* + * Get signed and unsigned update offsets in DP. YEHUDA signed/unsigned are not + * described in the IG, so must be defined here. + * + * \param pbUserData Optional data-structure, if required + * \param signed_delta_offset (out) Signed update offset in DP + * \param delta_offset (out) Unsigned update offset in DP + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_GetRBDeltaOffset(void *pbUserData, SS_UINT32 signed_delta_offset, SS_UINT32 * delta_offset); + +/** + ******************************************************************************* + * Install an update.

+ * + * The update is packaged within a DP, which may contain multiple updates as + * well as other components. You must call this function once for each + * update.

+ * + * Depending on the UPI mode set, this function will scout, scout and update, + * update, perform a dry-run, or verify the update post-update. + * + * \param pDeviceData The device data, a \ref vRM_DeviceData value + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_Engine_Update(Engine_DeviceData * pDeviceData); + +/** + ******************************************************************************* + * Get the highest minimum required RAM of all updates that match the + * \a installer_type and \a component_flags values in \a pDeviceData.
+ * The returned amount of memory must be pre allocated and passed to + * \ref SS_vRM_Update through \ref vRM_DeviceData + * + * \param ui32pRamUse (out) + * \param pDeviceData The device data, a \ref vRM_DeviceData value + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_Engine_GetDpRamUse(SS_UINT32 * ui32pRamUse, Engine_DeviceData * pDeviceData); + +/** + ******************************************************************************* + * Get protocol version of an update in the DP.

+ * + * This returns the version of the first update that matches the + * \a installer_type and \a component_flags values. The returned protocol + * version must match the UPI protocol version for the update to proceed. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param pbyRAM (out) Buffer to contain part of the update from + * which the protocol version is extracted + * \param dwRAMSize Size of \a pbyRAM; minimum 0x100 bytes + * \param installer_types List of installer types; for an Image only + * update, this is a one element array with a value + * of 0 + * \param installer_types_num Size of \a installer_types + * \param component_flags Update flags, if any. Used by the SWM Center. + * For the list of component flags, see the SWM + * Center documentation. + * \param dpProtocolVersion (out) Buffer to contain DP protocol version + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_GetDPProtocolVersion(void *pbUserData, void *pbyRAM, SS_UINT32 dwRAMSize, + SS_UINT32 * installer_types, SS_UINT32 installer_types_num, SS_UINT32 component_flags, + SS_UINT32 * dpProtocolVersion); + +/** + ******************************************************************************* + * Get scout protocol version of an update in the DP.

+ * + * This returns the version of the first update that matches the + * \a installer_type and \a component_flags values. The returned scout protocol + * version must match the UPI scout protocol version for the update to proceed. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param pbyRAM (out) Buffer to contain part of the update + * from which the protocol version is extracted + * \param dwRAMSize Size of \a pbyRAM; minimum 0x100 bytes + * \param installer_types List of installer types; for an Image only + * update, this is a one element array with a + * value of 0 + * \param installer_types_num Size of \a installer_types + * \param component_flags Update flags, if any. Used by the SWM Center. + * For the list of component flags, see the SWM + * Center documentation. + * \param dpScoutProtocolVersion (out) Buffer to contain DP scout protocol + * version + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_GetDPScoutProtocolVersion(void *pbUserData, void *pbyRAM, SS_UINT32 dwRAMSize, + SS_UINT32 * installer_types, SS_UINT32 installer_types_num, + SS_UINT32 component_flags, SS_UINT32 * dpScoutProtocolVersion); + +/** + ******************************************************************************* + * Get UPI version.

+ * + * This is not the protocol version number but the vRapid Mobile version number. + * + * \param pbVersion (out) Buffer to contain version + * + * \return S_SS_SUCCESS + ******************************************************************************* + */ + long SS_GetUPIVersion(unsigned char *pbVersion); + +/** + ******************************************************************************* + * Get UPI protocol version.

+ * + * Do not perform the update if this version does not match the DP protocol + * version returned from \ref SS_GetDPProtocolVersion. + * + * \return Protocol version, without periods (.) and with the revision number + * replaced by 0. For example, if the protocol version is 5.0.14.33, + * this returns 50140. + ******************************************************************************* + */ + SS_UINT32 SS_GetUPIProtocolVersion(void); + +/** + ******************************************************************************* + * Get UPI scout protocol version.

+ * + * Do not perform the update if this version does not match the DP protocol + * version returned from \ref SS_GetDPScoutProtocolVersion. + * + * \return Scout protocol version, without periods (.) and with the revision + * number replaced by 0. For example, if the scout protocol version is + * 5.0.14.33, this returns 50140. + ******************************************************************************* + */ + SS_UINT32 SS_GetUPIScoutProtocolVersion(void); + +/** + ******************************************************************************* + * Get protocol version of an update.

+ * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param pbyRAM Pre-allocated RAM space + * \param dwRAMSize Size of \a pbyRAM, in bytes + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + + SS_UINT32 SS_GetDeltaProtocolVersion(void *pbUserData, void *pbyRAM, SS_UINT32 dwRAMSize); +/** + ******************************************************************************* + * Get scout protocol version of an update.

+ * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param pbyRAM Pre-allocated RAM space + * \param dwRAMSize Size of \a pbyRAM, in bytes + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + + SS_UINT32 SS_GetDeltaScoutProtocolVersion(void *pbUserData, void *pbyRAM, SS_UINT32 dwRAMSize); /* User data passed to all porting routines, pointer for the ram to use, size of the ram */ + +/** + ******************************************************************************* + * Reset the watchdog timer.

+ * + * This is a Porting Layer function that must be implemented. + * + * Called periodically to ensure that the bootstrap doesn't believe the boot to + * be stalled during an update. + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_ResetTimerA(void); + +/** + ******************************************************************************* + * Display progress information to the end-user.

+ * + * This is a Porting Layer function that must be implemented. + * + * Actually, you can do whatever you want with the progress information. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param uPercent Update percentage + * + * \return None + ******************************************************************************* + */ + void SS_Progress(void *pbUserData, SS_UINT32 uPercent); + +/** + ******************************************************************************* + * Print a debug message formatted using a printf-like string.

+ * + * This is a Porting Layer function that must be implemented. + * + * Supported tags: + * \li %x: Hex number + * \li %0x: Hex number with leading zeroes + * \li %u: Unsigned decimal + * \li %s: Null-terminated string + * \li %d: Signed decimal integer + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param aFormat Printf-like format string + * \param ... Items to insert in \a aFormat + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + SS_UINT32 SS_Trace(void *pbUserData, const char *aFormat, ...); + +/** + ******************************************************************************* + * Get update from DP.

+ * + * This is a Porting Layer function that must be implemented. + * A default implementation of this function is provided. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param pbBuffer (out) Buffer to store the update + * \param dwStartAddressOffset Update offset in DP + * \param dwSize Size of \a pbBuffer + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_GetDelta(void *pbUserData, unsigned char *pbBuffer, SS_UINT32 dwStartAddressOffset, SS_UINT32 dwSize); + +#ifdef __cplusplus +} +#endif +#endif // _SS_VRM_UPDATE_H diff --git a/ss_engine/SS_FSUpdate.h b/ss_engine/SS_FSUpdate.h new file mode 100755 index 0000000..7b5d1a6 --- /dev/null +++ b/ss_engine/SS_FSUpdate.h @@ -0,0 +1,349 @@ +/* + * libtota + * + * Copyright (c) 2017 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. + */ + +/*! + ******************************************************************************* + * \file SS_FileSystemUpdate.h + * + * \brief UPI FS Update API + ******************************************************************************* + */ +#ifndef _SS_FILESYSTEM_UPDATE_H_ +#define _SS_FILESYSTEM_UPDATE_H_ + +#include "SS_Engine_Update.h" +#include "SS_Engine_Errors.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/*! + * File access modes + */ + typedef enum tag_RW_TYPE { + ONLY_R, //!< Read-only + ONLY_W, //!< Write-only + BOTH_RW //!< Read-write + } E_RW_TYPE; + +/*! + ******************************************************************************* + * Copy file.

+ * + * Must create the path to the new file as required. Must overwrite any contents + * in the old file, if any. Must check if the source file is a symbolic link. + * If it is, instead create a new symbolic link using \ref SS_Link. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param strFromPath Path to old file + * \param strToPath Path to new file + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + + long SS_CopyFile(void *pbUserData, const char *strFromPath, const char *strToPath); + +/*! + ******************************************************************************* + * Move (rename) file.

+ * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param strFromPath Path to old file location + * \param strToPath Path to new file location + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + + long SS_MoveFile(void *pbUserData, const char *strFromPath, const char *strToPath); + +/*! + ******************************************************************************* + * Delete file.

+ * + * Must return success if the file is deleted or didn't exist. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param strPath Path to file + * + * \return S_SS_SUCCESS on success, E_SS_DELETEFILE if the file cannot be + * deleted, or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + + long SS_DeleteFile(void *pbUserData, const char *strPath); + +/*! + ******************************************************************************* + * Delete folder.

+ * + * Must return success if the folder is now deleted or didn't exist. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param strPath Path to folder + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + + long SS_DeleteFolder(void *pbUserData, const char *strPath); + +/*! + ******************************************************************************* + * Create folder.

+ * + * Must return success if the folder is created or already existsed. It is + * recommended that the new folder's attributes are a copy of its parent's + * attributes. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param strPath Path to folder + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + + long SS_CreateFolder(void *pbUserData, const char *strPath); + +/*! + ******************************************************************************* + * Open file.

+ * + * Must create the the file (and the path to the file) if it doesn't exist. Must + * open in binary mode. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param strPath Path to file + * \param wFlag Read/write mode, an \ref E_RW_TYPE value + * \param pwHandle (out) File handle + * + * \return S_SS_SUCCESS on success, E_SS_OPENFILE_ONLYR if attempting to open a + * non-existant file in R/O mode, E_SS_OPENFILE_WRITE if there is an + * error opening a file for writing, or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + + long SS_OpenFile(void *pbUserData, const char *strPath, E_RW_TYPE wFlag, long *pwHandle); + +/*! + ******************************************************************************* + * Set file size. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param wHandle File handle + * \param dwSize New file size + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + + long SS_ResizeFile(void *pbUserData, long wHandle, SS_UINT32 dwSize); + +/*! + ******************************************************************************* + * Close file. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param wHandle File handle + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + + long SS_CloseFile(void *pbUserData, long wHandle); + +/*! + ******************************************************************************* + * Write data to a specified position within a file.

+ * + * Must return success if the block is written or at least resides in + * non-volatile memory. Use \ref SS_SyncFile to commit the file to storage. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param wHandle File handle + * \param dwPosition Position within the file to which to write + * \param pbBuffer Data to write + * \param dwSize Size of \a pbBuffer + * + * \return S_SS_SUCCESS on success, or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + + long SS_WriteFile(void *pbUserData, long wHandle, SS_UINT32 dwPosition, unsigned char *pbBuffer, SS_UINT32 dwSize); + +/*! + ******************************************************************************* + * Commit file to storage.

+ * + * Generally called after \ref SS_WriteFile. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param wHandle File handle + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_SyncFile(void *pbUserData, long wHandle); + +/*! + ******************************************************************************* + * Read data from a specified position within a file. + * If fewer bytes than requested are available in the specified position, this + * function should read up to the end of file and return S_SS_SUCCESS. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param wHandle File handle + * \param dwPosition Position within the file from which to read + * \param pbBuffer Buffer to contain data + * \param dwSize Size of data to read + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + + long SS_ReadFile(void *pbUserData, long wHandle, SS_UINT32 dwPosition, unsigned char *pbBuffer, SS_UINT32 dwSize); + +/*! + ******************************************************************************* + * Get file size. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param wHandle File handle + * + * \return File size, -1 if file not found, or < -1 on error + ******************************************************************************* + */ + long SS_GetFileSize(void *pbUserData, long wHandle); + +/*! + ******************************************************************************* + * Get free space of a mounted file system. + * + * \param pbUserData Optional opaque data-structure to pass to + * IPL functions + * \param path Name of any directory within the mounted + * file system + * \param available_flash_size (out) Available space + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_GetAvailableFreeSpace(void *pbUserData, const char *path, SS_UINT32 * available_flash_size); + +/*! + ******************************************************************************* + * Remove symbolic link.

+ * + * Must return an error if the file does not exist or is not a symbolic link.

+ * + * If your platform does not support symbolic links, you do not need to + * implement this. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param pLinkName Link + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_Unlink(void *pbUserData, char *pLinkName); + +/*! + ******************************************************************************* + * Create symbolic link.

+ * + * Must create the path to the link as required. If a file already exists at the + * named location, must return success if the file is a symbolic link or an + * error if the file is a regular file. The non-existance of the target of the + * link must NOT cause an error.

+ * + * If your platform does not support symbolic links, you do not need to + * implement this. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param pLinkName Path to the link file to create + * \param pReferenceFileName Path to which to point the link + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_Link(void *pbUserData, char *pLinkName, char *pReferenceFileName); + +/*! + ******************************************************************************* + * Set file attributes.

+ * + * The file attributes token (\a ui8pAttribs) is defined at generation time. + * If attributes are not defined explicitly, they are given the following, + * OS-dependent values: + * \li Windows: _redbend_ro_ for R/O files, _redbend_rw_ for R/W files + * \li Linux: _redbend_oooooo:xxxx:yyyy indicating the file mode, uid, and gid + * (uid and gid use capitalized hex digits as required) + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param ui16pFilePath File path + * \param ui32AttribSize Size of \a ui8pAttribs + * \param ui8pAttribs Attributes to set + * + * \return S_SS_SUCCESS on success or < 0 on error + ******************************************************************************* + */ + + long SS_SetFileAttributes(const char *ui16pFilePath, + const SS_UINT32 ui32AttribSize, const unsigned char *ui8pAttribs); + +/*! + ******************************************************************************* + * Print status and debug information. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param aFormat A NULL-terminated printf-like string with support for + * the following tags: + * \li %x: Hex number + * \li %0x: Hex number with leading zeros + * \li %u: Unsigned decimal + * \li %s: NULL-terminated string + * \param ... Strings to insert in \a aFormat + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + SS_UINT32 SS_Trace(void *pbUserData, const char *aFormat, ... + ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/ss_engine/SS_ImageUpdate.c b/ss_engine/SS_ImageUpdate.c new file mode 100755 index 0000000..25ceb60 --- /dev/null +++ b/ss_engine/SS_ImageUpdate.c @@ -0,0 +1,34 @@ +/* + * libtota + * + * Copyright (c) 2017 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "SS_Common.h" +#include "fota_common.h" + +//EMMC, MTD?? +//Write to block +//IF used diff --git a/ss_engine/SS_ImageUpdate.h b/ss_engine/SS_ImageUpdate.h new file mode 100755 index 0000000..2923b71 --- /dev/null +++ b/ss_engine/SS_ImageUpdate.h @@ -0,0 +1,186 @@ +/* + * libtota + * + * Copyright (c) 2017 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. + */ + +/*! + ******************************************************************************* + * \file SS_ImageUpdate.h + * + * \brief UPI Image Update API + ******************************************************************************* + */ +#ifndef __SS_IMAGEUPDATE_H__ +#define __SS_IMAGEUPDATE_H__ + +#include "SS_Engine_Update.h" +#include "SS_Engine_Errors.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +/** + ******************************************************************************* + * Get data from temporary storage. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param pbBuffer (out) Buffer to store the data + * \param dwBlockAddress Location of data + * \param dwSize Size of data + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_ReadBackupBlock(void *pbUserData, unsigned char *pbBuffer, SS_UINT32 dwBlockAddress, SS_UINT32 dwSize); + +/** + ******************************************************************************* + * Erase sector from temporary storage.

+ * + * A default implementation is included. You must use the default implementation + * if both of the following are true: + * \li InstantFailSafe is set to false + * \li Only Image updates are used OR the backup is stored in flash and not on + * the file system + *

The address must be aligned to the sector size. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param dwStartAddress Sector address to erase + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_EraseBackupBlock(void *pbUserData, SS_UINT32 dwStartAddress); + +/** + ******************************************************************************* + * Write complete sector to temporary storage.

+ * + * Must overwrite entire sector. To write part of a sector, see + * \ref SS_WriteBackupPartOfBlock. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param dwBlockStartAddress Address to which to write + * \param pbBuffer Data to write + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_WriteBackupBlock(void *pbUserData, SS_UINT32 dwBlockStartAddress, unsigned char *pbBuffer); + +/** + ******************************************************************************* + * Write data to temporary storage.

+ * + * Used to write part of a sector that has already been written to by + * \ref SS_WriteBackupBlock. Must NOT overwrite entire sector.

+ * + * A default implementation is included. You must use the default implementation + * if both of the following are true: + * \li InstantFailSafe is set to false + * \li Only Image updates are used OR the backup is stored in flash and not on + * the file system + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param dwStartAddress Address to which to write + * \param dwSize Size of \a pbBuffer + * \param pbBuffer Data to write + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_WriteBackupPartOfBlock(void *pbUserData, + SS_UINT32 dwStartAddress, SS_UINT32 dwSize, unsigned char *pbBuffer); + +/** + ******************************************************************************* + * Write sector to flash.

+ * + * Must overwrite a complete sector. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param dwBlockAddress Address to which to write + * \param pbBuffer Data to write + * + * \return S_SS_SUCCESS on success, E_SS_WRITE_ERROR if there is a write error, + * or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_WriteBlock(void *pbUserData, SS_UINT32 dwBlockAddress, unsigned char *pbBuffer); + +/** + ******************************************************************************* + * Read data from flash.

+ * + * See \ref SS_ReadImageNewKey. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param pbBuffer (out) Buffer to store data + * \param dwStartAddress Address from which to read + * \param dwSize Size of data to read (must be less than a sector); 0 + * must immediately return success + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_ReadImage(void *pbUserData, unsigned char *pbBuffer, /* pointer to user buffer */ + SS_UINT32 dwStartAddress, /* memory address to read from */ + SS_UINT32 dwSize); /* number of bytes to copy */ + +/** + ******************************************************************************* + * Read data from flash.

+ * + * As opposed to \ref SS_ReadImage, this function is intended to read already + * updated data and is used for encrypted images. If no part of the image is + * encrypted, simply call \ref SS_ReadImage. + * + * Used by the UPI when resuming an interrupted update. + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param pbBuffer (out) Buffer to store data + * \param dwStartAddress Address from which to read + * \param dwSize Size of data to read (must be less than a sector); 0 + * must immediately return success + * + * \return S_SS_SUCCESS on success or an \ref SS_vRM_Errors.h error code + ******************************************************************************* + */ + long SS_ReadImageNewKey(void *pbUserData, unsigned char *pbBuffer, SS_UINT32 dwStartAddress, SS_UINT32 dwSize); + +/** + ******************************************************************************* + * Get sector size.

+ * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * + * \return Sector size, in bytes + ******************************************************************************* + */ + long SS_GetBlockSize(void *pbUserData); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __SS_UPDATE_IMAGE_H__ */ diff --git a/ss_engine/SS_MultiProcessUpdate.h b/ss_engine/SS_MultiProcessUpdate.h new file mode 100755 index 0000000..7b85830 --- /dev/null +++ b/ss_engine/SS_MultiProcessUpdate.h @@ -0,0 +1,174 @@ +/* + * libtota + * + * Copyright (c) 2017 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. + */ + +/*! + ******************************************************************************* + * \file SS_MultiProcessUpdate.h + * + * \brief UPI MultiProcess API + * + * Multi-process updates can work on platforms that support multi-processing. + * To use the UPI with multi-process support you should implement the following + * Porting Layer functions: + * SS_Malloc + * SS_Free + * SS_WaitForProcess + * SS_RunProcess + * SS_GetMaxNumProcess + * SS_GetMaxProcRamSize + * + * The function SS_RunProcess must run a sub-process (optionally the same + * executable running the main process) that is also integrated with the UPI. + * This process must call the UPI API function SS_HandleProcessRequest. + *

+ * The initial call to SS_vRM_Update does not change. vRapid Mobile calls + * SS_GetMaxNumProcess to see if multi-processing is supported and to see how + * many processes are supported. If SS_GetMaxNumProcess returns a value greater + * than zero, the UPI will run parts of the update in sub-processes. + *

+ * The UPI creates a sub-process by calling SS_RunProcess with the arguments + * for the request. The new process must call SS_HandleProcessRequest to + * handle the request. The UPI manages processes using SS_WaitForProcess. + * Each sub-process allocates and frees memories using SS_Malloc and SS_Free. + *

+ * Notes: + * \li In the function SS_RunProcess it is recommended to flush output streams + * (like stdout or log files) before creating the child process to prevent + * buffer duplication in the child process. + * \li SS_HandleProcessRequests starts a UPI operation that requires all + * standard and common IPL functions in SS_vRM_FileSystemUpdate.h and + * SS_vRM_Update.h + * \li If your implementation of these functions requires any preparations, + * such as opening the update file for SS_GetDelta or opening a log file + * for SS_Trace, these preparations must be done before calling + * SS_HandleProcessRequests. + * \li To distinguish between processes when running in Multi-Process Update mode, + * it is recommended that you add the Process ID (i.e. getpid()) to the log + * output in SS_Trace. Alternatively, create a log file for each sub-process + * named log_file.[pid] + * \li If your implementation uses the optional opaque data structure ('user' + * in the functions below), you may also have to pass it to sub-processes. + * You must implement this functionality. + ******************************************************************************* + */ +#ifndef _REDBEND_MULTIPROCESS_UPDATE_H_ +#define _REDBEND_MULTIPROCESS_UPDATE_H_ + +#include "SS_Engine_Update.h" +#include "SS_Engine_Errors.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + ******************************************************************************* + * Allocate a memory block. + * + * \param size Memory block size, in blocks + * + * \return A pointer to the memory block on success, NULL on failure + ******************************************************************************* + */ + void *SS_Malloc(SS_UINT32 size); + +/** + ******************************************************************************* + * Free a memory block. + * + * \param pMemBlock Pointer to the memory block + ******************************************************************************* + */ + void SS_Free(void *pMemBlock); + +/** + ******************************************************************************* + * Wait for a process to complete. + * + * \param handle Process handle. If NULL, wait for any process to + * complete. + * \param process_exit_code Exit code of the completed process + * + * \return Handle of the completed process or NULL on error + ******************************************************************************* + */ + void *SS_WaitForProcess(const void *handle, SS_UINT32 * process_exit_code); + +/** + ******************************************************************************* + * Create a new process. + * + * The new process must call \ref SS_HandleProcessRequest with the same + * arguments. + * + * \param user Optional opaque data-structure passed in during + * initialization + * \param argc Number of \a argv arguments + * \param argv Arguments to pass to the new process + * + * \return Handle to the new process or NULL on error + ******************************************************************************* + */ + void *SS_RunProcess(void *user, int argc, char *argv[]); + +/** + ******************************************************************************* + * Get the maximum number of processes that are allowed to run. + * + * \param user Optional opaque data-structure passed in during + * initialization + * + * \return The number of allowed processes or 0 if multiple processes are not + * allowed + ******************************************************************************* + */ + SS_UINT32 SS_GetMaxNumProcess(void *user); +/** + ******************************************************************************* + * Get the maximum available memory for processes to use. + * + * \param user Optional opaque data-structure passed in during + * initialization + * + * \return The memory amount available for processes or 0 if there is no available memory + ******************************************************************************* + */ + unsigned long SS_GetMaxProcRamSize(void *user); + +/** + ******************************************************************************* + * Initialize a new process and handle the request sent from an external + * process. + * + * Called from the main function of the new process with the parameters passed to + * \ref SS_RunProcess. + * + * \param user Optional opaque data-structure passed in during + * initialization + * \param argc Number of \a argv arguments + * \param argv Arguments to pass to the new process + * + * \return S_SS_SUCCESS on success or an error code + ******************************************************************************* + */ + long SS_HandleProcessRequest(void *user, int argc, char *argv[]); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/ss_engine/SS_Nand.c b/ss_engine/SS_Nand.c new file mode 100755 index 0000000..21d1e65 --- /dev/null +++ b/ss_engine/SS_Nand.c @@ -0,0 +1,123 @@ +/* + * libtota + * + * Copyright (c) 2017 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "SS_Nand.h" +#include "SS_ImageUpdate.h" +#include "SS_Engine_Errors.h" +#include "SS_Common.h" +#include "ua.h" +#include "fota_common.h" + +static int local_pid; +static int local_offset; + +int nand_init(void *pbUserData, char *block_file, int offset) +{ + + local_pid = open(block_file, O_RDWR, 0777); + if (local_pid < 0) + return E_SS_FAILURE; + + local_offset = offset; + return S_SS_SUCCESS; +} + +void nand_finalize() +{ + close(local_pid); +} + +unsigned long nand_block_size() +{ + return SS_GetBlockSize(NULL); +} + +unsigned long nand_pages_size() +{ + return 1 << NAND_PAGE_BITS; +} + +unsigned long nand_image_size(char *block_file) +{ + int fid; + struct stat st; + fid = open(block_file, O_RDWR, 0777); + if (fid < 0) + return 0; + if (fstat(fid, &st) < 0) { + close(fid); + return 0; + } + close(fid); + return st.st_size; +} + +int nand_write(void *pbUserData, unsigned char *pbBuffer, + unsigned long dwBlockAddress) +{ + int ret; + ret = lseek(local_pid, dwBlockAddress + local_offset, SEEK_SET); + LOG("nand_write lseek =%x\n", ret); + ret = write(local_pid, pbBuffer, nand_block_size()); + + LOG("nand_write dwStartAddress local_offset =%x\n", local_offset); + LOG("nand_write dwStartAddress dwBlockAddress =%lx\n", + dwBlockAddress); + + return (ret >= 0) ? S_SS_SUCCESS : E_SS_WRITE_ERROR; +} + +#define LOGICAL_SECTOR 512 + +int nand_read(void *pbUserData, unsigned char *pbBuffer, + unsigned long dwStartAddress, unsigned long dwSize) +{ + int align, align2, ret; + char *buf; + align = + dwStartAddress - (dwStartAddress / LOGICAL_SECTOR) * LOGICAL_SECTOR; + align2 = + (LOGICAL_SECTOR - + (dwSize + dwStartAddress) % LOGICAL_SECTOR) % LOGICAL_SECTOR; + + ret = lseek(local_pid, local_offset + (dwStartAddress - align), SEEK_SET); + LOG("nand_read lseek =%x\n", ret); + buf = malloc(align + align2 + dwSize); + if (buf == NULL) + return E_SS_MALLOC_ERROR; + ret = read(local_pid, buf, align + align2 + dwSize); + if (ret > 0) + memcpy(pbBuffer, buf + align, dwSize); + free(buf); + + return (ret > 0) ? S_SS_SUCCESS : E_SS_READ_ERROR; + +} +*/ diff --git a/ss_engine/SS_Nand.h b/ss_engine/SS_Nand.h new file mode 100755 index 0000000..6e37c4b --- /dev/null +++ b/ss_engine/SS_Nand.h @@ -0,0 +1,31 @@ +/* + * libtota + * + * Copyright (c) 2017 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 SS_NAND_H +#define SS_NAND_H + +extern unsigned long nand_image_size(char *block_file); +extern unsigned long nand_block_size(void); +extern unsigned long nand_pages_size(void); +extern int nand_init(void *pbUserData,char *block_node,int offset); +extern void nand_finalize(void); +extern int nand_write(void *pbUserData,unsigned char *pbBuffer, unsigned long dwBlockAddress); +extern int nand_read(void *pbUserData,unsigned char *pbBuffer,unsigned long dwStartAddress,unsigned long dwSize); + +#endif //SS_NAND_H +*/ diff --git a/ss_engine/SS_UPI.c b/ss_engine/SS_UPI.c new file mode 100755 index 0000000..0dd61a0 --- /dev/null +++ b/ss_engine/SS_UPI.c @@ -0,0 +1,1987 @@ +/* + * libtota + * + * Copyright (c) 2017 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. + */ + +/*HEADER */ + +/* + +Function Prototypes Mandatory + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ua.h" +#include "SS_Common.h" +#include "fota_common.h" +#include "SS_UPI.h" +#include "ss_patchdelta.h" +#include "SS_Engine_Errors.h" +#include "SS_FSUpdate.h" + +int gtotalFSCnt = 0; +int FS_UpgradeState = E_SS_FAILURE; +int gvalid_session = 0; //used as fail-safe in case device is turmed off or battery removed during update +fs_list *fs_headptr_main = NULL; +fs_list *headptr_list[UA_PARTI_MAX]; +tar_Data_t *tar_cfg_data = NULL; + +#ifdef MEM_PROFILING +/* + Description: + Create script file , set it executable, execute script in child process + Only works if valid delta.tar is present for upgrade + Summary: + If MEM_PROFILING is activated, + we can see the result of memory profiling after delta upgrade + in file defined by macro - SS_MEMORY_PROFILING_SCRIPT +*/ +int mem_profiling_start = 0; +int SS_Do_Memory_Profiling() +{ + int ret = -1; + pid_t pid; + char memory_usage_script[1024] = "#!/bin/bash\nlog_file=$1\npid=$2\nmaxmem=0\nwhile [[ -d \"/proc/${pid}\" ]]; do\n\ + mem=`cat /proc/${pid}/smaps | grep Pss | grep -v Swap|awk '{print $2}'|awk '{s+=$1} END {print s}'`\n\ + if [[ ${mem} -gt ${maxmem} ]]; then\n maxmem=${mem}\n\ + echo -e \"Memory usage till now is: ${maxmem} KB.\" >> $log_file\n fi\n sleep 0.01\ndone\n\ + echo -e \"Max was : ${maxmem} KB.\" >> $log_file\n"; + char cmd[1024] = { 0, }; + + //Open a file and write the script contents in it + FILE *fp = fopen(SS_MEMORY_PROFILING_SCRIPT, "w+"); + fwrite(memory_usage_script, strlen(memory_usage_script), 1, fp); + fclose(fp); + //make the file executable - Octal 495 is 757 decimal + if (chmod(SS_MEMORY_PROFILING_SCRIPT, 495) < 0) { + LOGE("Error in chmod(%s, 495) - %d (%s)\n", SS_MEMORY_PROFILING_SCRIPT, errno, strerror(errno)); + return E_SS_FAILURE; + } + //calling mem_use.sh + //Usage : + pid = getpid(); + snprintf(cmd, sizeof(cmd) - 1, "%s %s %d", SS_MEMORY_PROFILING_SCRIPT, SS_MEMORY_USAGE_LOG, pid); + ret = _system_cmd_nowait(cmd); + sleep(1); + LOG("ret for memory profiling cmd is %d\n", ret); + if (ret == 0) { + mem_profiling_start = 1; + return S_SS_SUCCESS; + } else { + LOGE("Could not start Memory Profiling\n"); + return E_SS_FAILURE; + } +} +#endif + +#ifdef TIME_PROFILING +static char ts1[256]; +static double ts2; +double fast_tar_get_item_size_time = 0.0; +double SS_LoadFile_time = 0.0; +double SS_FSBuildNodes_time = 0.0; + +static void get_time_stamp1(void) +{ + struct timeval tv; + int sec, msec; + + gettimeofday(&tv, NULL); + sec = (int)tv.tv_sec; + msec = (int)(tv.tv_usec / 1000); + snprintf(ts1, 256, "%06d.%03d", sec % 100000, msec); +} + +static double get_time_stamp2(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + ts2 = (double)tv.tv_sec + (double)(tv.tv_usec / 1000000.0); + return ts2; +} +#endif + +long SS_GetUPIVersion(unsigned char *ver_str) +{ + if (ver_str) { + strncpy((char *)ver_str, SS_TOTA_VERSION, MAX_PATH); +#ifdef MEM_PROFILING + if (!mem_profiling_start) + if (!(S_SS_SUCCESS == SS_Do_Memory_Profiling())) + LOGE("Unable to start Memory_Profiling"); +#endif + return S_SS_SUCCESS;//wgid: 2456 + } else + return E_SS_FAILURE; +} + +int SS_CalculateFileSha(char *filename, int filesize, FileInfo * file) +{ + + FILE *fp = NULL; + int ulResult = S_SS_SUCCESS; + int chunk = 20*1024*1024; + + fp = fopen(filename, "rb"); + if (fp == NULL) { + LOGE("failed to open \"%s\": %s\n", filename, strerror(errno)); + ulResult = E_SS_FAILURE; + goto Cleanup; + } + + file->data = SS_Malloc(chunk); + if (!file->data) { + LOGE("failed to allocate memory for \"%s\": %s\n", filename, strerror(errno)); + ulResult = E_SS_FAILURE; + goto Cleanup; + } + + ssize_t bytes_read = 0; + sha1_ctx_t sha_ctx; + sha1_init(&sha_ctx); + + while( filesize > 0){ + if(filesize < chunk){ + bytes_read = fread(file->data, 1, filesize, fp); + if (bytes_read != filesize) { + LOGE("short read of \"%s\" (%ld bytes of %ld)\n", filename, (long)bytes_read, (long)file->size); + ulResult = E_SS_FAILURE; + goto Cleanup; + } + sha1_update(&sha_ctx, file->data, filesize); + break; + } + else { + bytes_read = fread(file->data, 1, chunk, fp); + if (bytes_read != chunk) { + LOGE("short read of \"%s\" (%ld bytes of %ld)\n", filename, (long)bytes_read, (long)file->size); + ulResult = E_SS_FAILURE; + goto Cleanup; + } + sha1_update(&sha_ctx, file->data, chunk); + filesize -= chunk; + } + } + + sha1_final(&sha_ctx, (uint32_t *) &file->sha1); + +Cleanup: + if(fp) + fclose(fp); + if(file->data) + SS_Free(file->data); + return ulResult; +} + +int SS_verify_DELTA_image(char *filename) +{ + + FileInfo file; + FILE *fp = NULL; + char line[SS_TOKEN_MAXLINE_LEN] = { '\0' }; + char * delta_size = NULL; + char *signature = NULL; + char * sha1trg = NULL; + uint8_t target_sha1[SHA_DIGEST_SIZE] = { 0, }; + char cmd[512] = { 0, }; + int udelta_size = 0; + int ulResult = S_SS_SUCCESS; + + if (stat(filename, &file.st) != 0) { + LOGE("failed to stat \"%s\": %s\n", filename, strerror(errno)); + return -1; + } + + snprintf(cmd, sizeof(cmd) - 1, "grep -o -P '%s' --binary-files=text system/%s > %s", + SS_IMAGE_MAGIC_KEY,filename, SS_IMAGE_MAGIC_KEY_VAL); + + ulResult = _system_cmd_wait(cmd); + if (ulResult != S_SS_SUCCESS) { + LOGE("Grep extraction for [%s] failed, code [%d]\n", cmd, ulResult); + ulResult = E_SS_FAILURE; + goto Cleanup; + } + + fp = fopen(SS_IMAGE_MAGIC_KEY_VAL, "r"); + if (!fp) { + LOGE("Grep extraction for [%s] failed, code [%d]\n", cmd, ulResult); + ulResult = E_SS_FAILURE; + goto Cleanup; + } + + if (fgets(line, SS_TOKEN_MAXLINE_LEN, fp) == NULL){ + ulResult = E_SS_FAILURE; + goto Cleanup; + } + fclose(fp); + fp = NULL; + + signature = strtok(line, SS_TOEKN_COLON); + delta_size = strtok(NULL, SS_TOEKN_COLON); + sha1trg = strtok(NULL, SS_TOEKN_COLON); + + if (signature && sha1trg && delta_size) { + udelta_size = atoi(delta_size); + LOGL(LOG_SSENGINE, "delta_size %d sha1trg %s\n", udelta_size, sha1trg); + } + else { + LOGE("Could not parse signature [%s]\n", line); + ulResult = E_SS_FAILURE; + goto Cleanup; + } + + if (ParseSha1(sha1trg, target_sha1) != 0) { + LOGE("failed to parse tgt-sha1 \"%s\"\n", sha1trg); + ulResult = E_SS_FAILURE; + goto Cleanup; + } + + ulResult = SS_CalculateFileSha(filename, udelta_size, &file); + if (ulResult != S_SS_SUCCESS) + goto Cleanup; + + if(memcmp(file.sha1, target_sha1, SHA_DIGEST_SIZE) != 0){ + LOGE("SHA mismatch -[%s] actual [%x] target [%x]\n", filename, file.sha1, target_sha1); + SS_SetUpgradeState(E_SS_FSBADDELTA); + ulResult = E_SS_FAILURE; + goto Cleanup; + } + else + LOGL(LOG_SSENGINE, "DELTA verified %s \n", sha1trg); + +Cleanup: + if(fp) + fclose(fp); + if(file_exist(SS_IMAGE_MAGIC_KEY_VAL, 0)) + SS_DeleteFile(NULL, SS_IMAGE_MAGIC_KEY_VAL); + return ulResult; +} + + +int SS_GetProgressResolution(int ultotalFSCnt) +{ + if (ultotalFSCnt < DISPLAYRESOLUTION_SIZE) + return 1; + else + return (ultotalFSCnt / DISPLAYRESOLUTION_SIZE); +} + +void SS_SetUpgradeState(int Val) +{ + LOGE("FAILED to upgrade Cause:[0x%x]\n", Val); + FS_UpgradeState = Val; + return; +} + +int SS_GetUpgradeState() +{ + return FS_UpgradeState; +} + +//Check SS function if available +int file_exist(char *filename, int type) +{ + struct stat buf; + int ret = 0; + + ret = lstat(filename, &buf); + if (ret < 0) { + ret = stat(filename, &buf); + } + return (ret >= 0) ? (1) : (0); +} + +int SS_rename(const char *old_file_name, const char *new_file_name) +{ + if (link(old_file_name, new_file_name) < 0) { + if (errno == EEXIST) { + if (unlink(new_file_name) < 0 || link(old_file_name, new_file_name) < 0) + return -1; + } else + return -1; + } + if (unlink(old_file_name) < 0) { + if (unlink(new_file_name) == 0) + return -1; + } + return 0; +} + +int SS_rename1(const char *old_file_name, const char *new_file_name) +{ + + int result = E_SS_FAILURE; + char *temp_name = NULL; + temp_name = (char *)SS_Malloc(strlen(new_file_name) + 10); + if (temp_name == NULL) + return E_SS_FAILURE; + strcpy(temp_name, new_file_name); + strcat(temp_name, ".temp"); + result = rename(new_file_name, temp_name); + if (result != 0) + goto Cleanup; + result = rename(old_file_name, new_file_name); + if (result != 0) + goto Cleanup; + result = unlink(temp_name); + Cleanup: + if (temp_name) + SS_Free(temp_name); + return result; +} + +/*! + ********************************************************************************* + * SS_FSVerifyNode + ********************************************************************************* + * + * @brief + * This is to verify nodes being added to global control structure for diff and delete cases. + * gvalid_session global is used to check if nodes are already verified. + * + * + * @param + * + * @return returns S_SS_SUCCESS and + * returns E_SS_FAILURE in case any error occurs + * + ********************************************************************************* + */ +int SS_FSVerifyNode(const char *ubDeltaPath, const char *path, const char *patchname, const char *sha1src, int type, + char *patchpath_name, int *data_size, int *data_offset) +{ + char patch[MAX_FILE_PATH] = { 0 }; + FileInfo source_file; + uint8_t source_sha1[SHA_DIGEST_SIZE]; + + if (gvalid_session) { + if ((type == DIFFS || type == DELETES || type == MOVES) && !file_exist((char *)path, type)) { + LOGE("failed to verifyNodes [does not exist], Path : [%s] Type[%d]\n", path, type); + SS_SetUpgradeState(E_SS_FSBADNODES); + return E_SS_FAILURE; + } + snprintf(patch, MAX_FILE_PATH, "%s%s%s", patchpath_name, "/", patchname); + if ((type == DIFFS /*||type == NEWFILES */ )) // allowing size 0 also for folders + { + if (tar_get_item_size_from_struct(&tar_cfg_data, patchname, data_size, data_offset) != S_SS_SUCCESS) { + LOGE("failed to get item size from struct, Patch : [%s]\n", patchname); + SS_SetUpgradeState(E_SS_FSBADNODES); + return E_SS_FAILURE; + } + if (*data_size < 0) { + LOGE("failed to verifyNodes [delta absent], Patch : [%s] Type[%d]\n", patch, type); + SS_SetUpgradeState(E_SS_FSBADNODES); + return E_SS_FAILURE; + } + } + //For other types (NEWs, SYMs, Folders), SHA check not required + if ((type == DIFFS || type == MOVES /* ||type == DELETES */ ) && (strcmp(sha1src, SS_NULLENTRY) != 0)) { + if (ParseSha1(sha1src, source_sha1) != 0) { + LOGE("Failed to parse Src-sha1 \"%s\"\n", sha1src); + return E_SS_FAILURE; + } + if (SS_LoadFile(path, &source_file) == 0) { + if (memcmp(source_file.sha1, source_sha1, SHA_DIGEST_SIZE) != 0) { + SS_Free(source_file.data); + LOGE("SS_FSVerifyNode - SHA mismatch with SRC - PATH [%s]\n", path); + SS_SetUpgradeState(E_SS_FSSRCCURRUPTED); // Define other error + return E_SS_FAILURE; + } + } + SS_Free(source_file.data); + } + } else { + LOGL(LOG_SSENGINE, "FS partition Already verified - Filling only size and offset \n"); + snprintf(patch, MAX_FILE_PATH, "%s%s%s", patchpath_name, "/", patchname); + if ((type == DIFFS /*||type == NEWFILES */ )) // allowing size 0 also for folders + { + if (tar_get_item_size_from_struct(&tar_cfg_data, patchname, data_size, data_offset) != S_SS_SUCCESS) { + LOGE("failed to get item size from struct, Patch : [%s] \n", patchname); + SS_SetUpgradeState(E_SS_FSBADNODES); + return E_SS_FAILURE; + } + if (*data_size < 0) { + LOGE("failed to verifyNodes [delta absent], Patch : [%s] Type[%d]\n", patch, type); + SS_SetUpgradeState(E_SS_FSBADNODES); + return E_SS_FAILURE; + } + } + } + return S_SS_SUCCESS; +} + +/*! + ********************************************************************************* + * SS_AppendNode + ********************************************************************************* + * + * @brief + * This is to append node to the global control structure for delta files. + * + * + * @param + * + * @return returns S_SS_SUCCESS and + * returns E_SS_FAILURE in case any error occurs + * + ********************************************************************************* + */ +int SS_AppendNode(const char *ubDeltaPath, fs_params ** headparam, fs_params ** tailparam, const char *old_path, + const char *new_path, const char *patchname, const char *sha1src, const char *sha1trg, int type, + char *patchpath_name) +{ + fs_params *newnode = NULL; + int data_size = 0; + int data_offset = 0; + + if (!ubDeltaPath || !old_path || !new_path || !patchname || !sha1src || !sha1trg || !patchpath_name) { + LOGE("Bad Nodes, NULL params passed for Appending Nodes \n"); + SS_SetUpgradeState(E_SS_FSINVALIDNODEPARAMS); + return E_SS_FAILURE; + } + if ((E_SS_FAILURE == + SS_FSVerifyNode(ubDeltaPath, old_path, patchname, sha1src, type, patchpath_name, &data_size, &data_offset))) { + LOGE("Bad Nodes, Failed to pass verification - [Delta Path - %s][OldPath - %s] [NewPath - %s] \n", ubDeltaPath, + old_path, new_path); + return E_SS_FAILURE; + } + newnode = (fs_params *) SS_Malloc(sizeof(fs_params)); + if (!newnode) + return E_SS_FAILURE; + strncpy(newnode->file_old_path, old_path, SS_MAX_NAMELENSUPPORTED);//wgid: 29483 + strncpy(newnode->file_new_path, new_path, SS_MAX_NAMELENSUPPORTED);//wgid: 29482 + strncpy(newnode->patch_name, patchname, SS_MAX_NAMELENSUPPORTED);//wgid: 28033 + strncpy(newnode->sha1src, sha1src, sizeof(newnode->sha1src) -1);//wgid: 25282 + strncpy(newnode->sha1trg, sha1trg, sizeof(newnode->sha1trg) - 1);//wgid: 25283 + newnode->type = type; + newnode->data_size = data_size; + newnode->data_offset = data_offset; + newnode->nextnode = NULL; + + //LOG("%s %s %d %s %s \n",newnode->file_path,newnode->patch_name,newnode->type, newnode->sha1src, newnode->sha1trg); + + if (*headparam == NULL) { + *headparam = newnode; + *tailparam = newnode; + } else { + (*tailparam)->nextnode = newnode; + (*tailparam) = (*tailparam)->nextnode; + } + return S_SS_SUCCESS; + +} + +void SS_UpdateUIProgress(ua_dataSS_t * ua_dataSS, int ulTotalFsCnt, int ulDone) +{ + + static int ss_count = 1; + int res_val = SS_GetProgressResolution(ulTotalFsCnt); + if (!ua_dataSS) { + LOGE("Error ua_dataSS\n"); + return; + } +//LOG("\nvalues are ss_count[%d] total_file_cnt[%d]",ss_count,ulTotalFsCnt); + if (ulDone == 1) { + if (ua_dataSS->ui_progress) + ua_dataSS->ui_progress(ua_dataSS, 100); + ss_count = 1; + } else if (ss_count < ulTotalFsCnt) { + + if (ss_count % res_val == 0 /* && ss_iterator <= 2 */ ) //Max 50 times display + { + double data = (double)ss_count / (double)ulTotalFsCnt; + if (ua_dataSS->ui_progress) + ua_dataSS->ui_progress(ua_dataSS, data * 100); + } + ss_count++; + } else { + if (ua_dataSS->ui_progress) + ua_dataSS->ui_progress(ua_dataSS, 100); + ss_count = 1; + } + +} + +/*! + ********************************************************************************* + * SS_FSClearNodes + ********************************************************************************* + * + * @brief + * This is to clear the global control structure for delta files. + * + * + * @param + * + * @return + * + ********************************************************************************* + */ +void SS_FSClearNodes(int idx) +{ + fs_params *local_temp = NULL; + fs_params *local_next = NULL; + LOGL(LOG_SSENGINE, "Free Nodes idx=%d \n", idx); + if (headptr_list[idx]) { + if (headptr_list[idx]->del_ref) { + local_temp = headptr_list[idx]->del_ref; + while (local_temp) { + local_next = local_temp->nextnode; + SS_Free(local_temp); + local_temp = local_next; + } + } + if (headptr_list[idx]->dif_ref) { + local_temp = headptr_list[idx]->dif_ref; + while (local_temp) { + local_next = local_temp->nextnode; + SS_Free(local_temp); + local_temp = local_next; + } + } + if (headptr_list[idx]->move_ref) { + local_temp = headptr_list[idx]->move_ref; + while (local_temp) { + local_next = local_temp->nextnode; + SS_Free(local_temp); + local_temp = local_next; + } + } + if (headptr_list[idx]->new_ref) { + local_temp = headptr_list[idx]->new_ref; + while (local_temp) { + local_next = local_temp->nextnode; + SS_Free(local_temp); + local_temp = local_next; + } + } + if (headptr_list[idx]->sym_difref) { + local_temp = headptr_list[idx]->sym_difref; + while (local_temp) { + local_next = local_temp->nextnode; + SS_Free(local_temp); + local_temp = local_next; + } + } + if (headptr_list[idx]->sym_newref) { + local_temp = headptr_list[idx]->sym_newref; + while (local_temp) { + local_next = local_temp->nextnode; + SS_Free(local_temp); + local_temp = local_next; + } + } + SS_Free(headptr_list[idx]); + headptr_list[idx] = NULL; + } +} + +/*! + ********************************************************************************* + * SS_FSGetDeltaCount + ********************************************************************************* + * + * @brief + * This is to get the delta count for diffs , deletes etc. + * + * + * @param + * + * @return returns structure with delta count info + * NULL in case of error + * + ********************************************************************************* + */ + +struct details *SS_FSGetDeltaCount(char *ubDeltaPath, char *ubDeltaInfoFile) +{ + int size = 0, bckupsize = 0, ret = 1; + char *token = NULL; + char *FileData = NULL; + int data_size = -1; + char *line = NULL; + struct details *refer_copy = NULL; + FILE *filename_bkup = NULL; + + if (!(ubDeltaPath && ubDeltaInfoFile)) { + LOGE("failed to Parse DELTA count information: \n"); + SS_SetUpgradeState(E_SS_BAD_PARAMS); + return NULL; + } + refer_copy = (struct details *)SS_Malloc(sizeof(struct details)); + + if (refer_copy == NULL) { + LOGE("failed to allocate memory\n"); + return NULL; + } + + LOGL(LOG_SSENGINE, "Delta File Info =%s\n", ubDeltaInfoFile); + + size = tar_get_item_size(ubDeltaPath, ubDeltaInfoFile); + if (size < 0) { + LOGE("failed to Access DELTA info file DPath:[%s] File: [%s]\n", ubDeltaPath, ubDeltaInfoFile); + SS_SetUpgradeState(E_SS_FSBADDELTA); + ret = 0; + goto cleanup; + } + + FileData = SS_Malloc(size + 1); + if (FileData == NULL) { + LOGE("Failed to Allocate Memory\n"); + SS_SetUpgradeState(E_SS_MALLOC_ERROR); + ret = 0; + goto cleanup; + } + + data_size = tar_get_cfg_data(ubDeltaPath, ubDeltaInfoFile, FileData, size); + if (data_size <= 0) { // == 0 is NOT okay?? + LOGE("Failed to read cfg data from Delta\n"); + SS_SetUpgradeState(E_SS_FSBADDELTA); + ret = 0; + goto cleanup; + } + filename_bkup = fopen(SS_PATCHLIST_BKUPLOC, "wb+"); + if (filename_bkup == NULL) { + LOGE("Failed to create BACKUP file Error:[%s]\n", strerror(errno)); + SS_SetUpgradeState(E_SS_FSFAILEDTOBACKUPPATCHINFO); + ret = 0; + goto cleanup; + } + + bckupsize = fwrite(FileData, 1, data_size, filename_bkup); //RECHECK SIZE 1 + if (bckupsize <= 0) { + SS_SetUpgradeState(E_SS_FSFAILEDTOBACKUPPATCHINFO); + ret = 0; + goto cleanup; + } + LOGL(LOG_SSENGINE, " Size [%d] DataSize [%d] BakUpSize [%d]\n", size, data_size, bckupsize); + + line = strstr(FileData, SS_FSCOUNT_MAGIC_KEY); + if (line) { + LOGL(LOG_SSENGINE, "SS_FSGetDeltaCount() last line %s \n", line); + + token = strtok(&line[SS_FSCOUNT_MAGIG_KEYLEN], SS_TOKEN_SPACE); + if (token) + refer_copy->diffs = atoi(token); + else { + SS_SetUpgradeState(E_SS_FSFAILEDTOPARSEDELTACNT); + ret = 0; + goto cleanup; + } + token = strtok(NULL, SS_TOKEN_SPACE); + if (token) + refer_copy->moves = atoi(token); + else { + SS_SetUpgradeState(E_SS_FSFAILEDTOPARSEDELTACNT); + ret = 0; + goto cleanup; + } + + token = strtok(NULL, SS_TOKEN_SPACE); + if (token) + refer_copy->news = atoi(token); + else { + SS_SetUpgradeState(E_SS_FSFAILEDTOPARSEDELTACNT); + ret = 0; + goto cleanup; + } + + token = strtok(NULL, SS_TOKEN_SPACE); + if (token) + refer_copy->deletes = atoi(token); + else { + SS_SetUpgradeState(E_SS_FSFAILEDTOPARSEDELTACNT); + ret = 0; + goto cleanup; + } + + token = strtok(NULL, SS_TOKEN_SPACE); + if (token) + refer_copy->symdiffs = atoi(token); + else { + SS_SetUpgradeState(E_SS_FSFAILEDTOPARSEDELTACNT); + ret = 0; + goto cleanup; + } + + token = strtok(NULL, SS_TOKEN_SPACE); + if (token) + refer_copy->symnews = atoi(token); + else { + SS_SetUpgradeState(E_SS_FSFAILEDTOPARSEDELTACNT); + ret = 0; + goto cleanup; + } + + gtotalFSCnt = + refer_copy->diffs + refer_copy->moves + refer_copy->news + refer_copy->deletes + refer_copy->symdiffs + + refer_copy->symnews; + LOG("SS_FSGetDeltaCount() total no of file %d\n", gtotalFSCnt); + + } else { + SS_SetUpgradeState(E_SS_FSFAILEDTOPARSEDELTACNT); + LOG("SS_FSGetDeltaCount() Failed to read last line\n"); + } + if (gtotalFSCnt < 0) { + ret = 0; + goto cleanup; + } + + cleanup: + if (ret) { + SS_Free(FileData); + if (filename_bkup) + fclose(filename_bkup); + return refer_copy; + } else { + SS_Free(FileData); + SS_Free(refer_copy); + if (filename_bkup) + fclose(filename_bkup); + return NULL; + } + +} + +/*! + ********************************************************************************* + * SS_FSBuildNodes + ********************************************************************************* + * + * @brief + * This is used to build the gobal control structure for diffs, deletes etc. + * For all the entries in config file (which has info) the information is parsed to the global control struct + * + * + * @param + * + * @return returns fs_list structure filled with details of all the files to be diff-ed ,deleted etc. + * NULL in case of error + * + ********************************************************************************* + */ +fs_list *SS_FSBuildNodes(ua_dataSS_t * ua_dataSS) +{ + FILE *fp = NULL; + char line[SS_TOKEN_MAXLINE_LEN] = { '\0' }; + char string_na[] = "NA"; + char *patch_name = NULL; + char *source_name = NULL; + char *target_name = NULL; + char *sha1src = NULL; + char *sha1trg = NULL; + char *change_type = NULL; + char *file_type = NULL; + uint32_t ulPatchCount = 0, del_type = DELETES; + fs_params *fs_diffhead = NULL; + fs_params *fs_difftail = NULL; + fs_params *fs_movehead = NULL; + fs_params *fs_movetail = NULL; + fs_params *fs_newhead = NULL; + fs_params *fs_delhead = NULL; + fs_params *fs_deltail = NULL; + fs_params *fs_symlinkdiffhead = NULL; + fs_params *fs_symlinkdifftail = NULL; + fs_params *fs_symlinknewhead = NULL; + fs_params *fs_symlinknewtail = NULL; + + struct details *local = NULL; + fs_list *fs_head_node = NULL; + int i = 0, retval = 0; + if (!ua_dataSS) { + LOGE("Bad structure ua_dataSS\n"); + SS_SetUpgradeState(E_SS_BAD_PARAMS); + return NULL; + } + LOGL(LOG_SSENGINE, " Build Nodes Entry \n"); + if (tar_cfg_data == NULL) + tar_cfg_data = tar_build_cfg_table(ua_dataSS->update_data->ua_delta_path); + if (!tar_cfg_data) { + LOGE(" tar_build_cfg_table Failed \n"); + SS_SetUpgradeState(E_SS_BAD_PARAMS); + return NULL; + } + local = SS_FSGetDeltaCount(ua_dataSS->update_data->ua_delta_path, ua_dataSS->update_delta->ua_patch_info); + if (local == NULL) { + LOGE(" Build Nodes Failed \n"); + if (tar_cfg_data) + tar_free_cfg_table(&tar_cfg_data); + SS_SetUpgradeState(E_SS_BAD_PARAMS); + return NULL; + } + + fp = fopen(SS_PATCHLIST_BKUPLOC, "r"); + if (!fp) { + SS_SetUpgradeState(E_SS_FSFAILEDTOOPENPATCHINFO); + if (tar_cfg_data) + tar_free_cfg_table(&tar_cfg_data); + SS_Free(local); + return NULL; + } + + ulPatchCount = local->diffs + local->deletes + local->news + local->moves + local->symdiffs + local->symnews; + LOG("Total FS count [%d].\n", ulPatchCount); +/* +************************************************************************ +Parsing logic implemented for patchlist +************************************************************************ +Sample entries in patchlist as below : +:: +************************************************************************ +DIFF:REG:system/bin/vi:system/bin/vi:2f2f3dc6d3ee06af0080ac7975f22941660f2480:78b2d44af32d854c70f1cb7431a60c2682a320cc:diff1_vi.delta +DIFF:TPK:system/usr/packages/removable/com.samsung.calculator.tpk:system/usr/packages/removable/com.samsung.calculator.tpk: + 96fc1bcde30d501ba65ef0038e05da46d255a7b3:fa1d5d9daa4097ac302b69244297f508577c3a01:diff1598_com.samsung.calculator.tpk.delta/ +MOVE:REG:system/etc/smack/accesses.d/heremaps-engine-devel:system/usr/apps/com.samsung.contacts/res/temp:da39a3ee5e6b4b0d3255bfef95601890afd80709 +DEL:REG:system/usr/ug/res/images/ug-phone/contacts/favorites_icon_remove.PNG:38ad8be378506d19b1c769d46be262cf100f6c59 +DEL:SYM:system/usr/apps/com.samsung.message-lite/lib/libmsg-common.so +SYM:DIFF:system/usr/lib/sync-agent/kies-private/libplugin-na-mobex.so.0:system/usr/lib/sync-agent/kies-private/libplugin-na-mobex.so.0: + libplugin-na-mobex.so.0.3.57 +SYM:NEW:system/lib/firmware/vbc_eq:/opt/system/vbc_eq +*********************************************************************** +*/ + if (local && ((local->diffs) > 0 || (local->moves > 0))) { + LOGL(LOG_SSENGINE, "%ss [%d] %ss [%d]\n", SS_STRING_DIFF, local->diffs, SS_STRING_MOVE, local->moves); + for (i = 0; i < (local->diffs + local->moves); i++) { + if (fgets(line, SS_TOKEN_MAXLINE_LEN, fp) == NULL) { + SS_SetUpgradeState(E_SS_FSFAILEDTOPARSEDELTAINFO); + break; + } + //LOGL(LOG_SSENGINE, "DIFF LINE:[%d] [%s] \n",i+1,line); + + change_type = strtok(line, SS_TOEKN_COLON); + file_type = strtok(NULL, SS_TOEKN_COLON); + + if (strcmp(change_type, SS_STRING_MOVE) == 0) { // && strcmp(file_type,"TPK") == 0){ + source_name = strtok(NULL, SS_TOEKN_COLON); + target_name = strtok(NULL, SS_TOEKN_COLON); + sha1src = strtok(NULL, SS_TOKEN_NEWLINE); + //LOGL(LOG_SSENGINE, "%s Index [%d]\n", SS_STRING_MOVE, i); + + if (!source_name || !target_name || !sha1src) { + SS_SetUpgradeState(E_SS_FSFAILEDTOPARSEDELTAINFO); + //LOGE("Failed to extract Patch Info Type:DELETES \n"); + LOGE("Failed to parse DIFFS - LINE:[%d] [%s] \n", i + 1, line); + goto CleanUp; + } + retval = + SS_AppendNode(ua_dataSS->update_data->ua_delta_path, &fs_movehead, &fs_movetail, source_name, + target_name, string_na, sha1src, string_na, MOVES, + ua_dataSS->update_delta->ua_patch_path); + if (retval == E_SS_FAILURE) // ONLY test purpose, should enable this + goto CleanUp; + SS_UpdateUIProgress(ua_dataSS, ulPatchCount, 0); + } else if (strcmp(change_type, SS_STRING_DIFF) == 0) { // && strcmp(file_type,"TPK") == 0){ + source_name = strtok(NULL, SS_TOEKN_COLON); + target_name = strtok(NULL, SS_TOEKN_COLON); + sha1src = strtok(NULL, SS_TOEKN_COLON); + sha1trg = strtok(NULL, SS_TOEKN_COLON); + patch_name = strtok(NULL, SS_TOKEN_NEWLINE); + //LOGL(LOG_SSENGINE, "%s Index [%d]\n", SS_STRING_DIFF, i); + + if (strlen(patch_name) <= SS_MAX_NAMELENSUPPORTED) { + retval = + SS_AppendNode(ua_dataSS->update_data->ua_delta_path, &fs_diffhead, &fs_difftail, + source_name, target_name, patch_name, sha1src, sha1trg, DIFFS, + ua_dataSS->update_delta->ua_patch_path); + if (retval == E_SS_FAILURE) // ONLY test purpose, should enable this + goto CleanUp; + } else { + SS_SetUpgradeState(E_SS_FILENAMELENERROR); + LOGE("File Name length Limitation Error File:[%s]\n", patch_name); + goto CleanUp; + } + SS_UpdateUIProgress(ua_dataSS, ulPatchCount, 0); + } else { + SS_SetUpgradeState(E_SS_FSFAILEDTOPARSEDELTAINFO); + LOGE("Patch Name format Error File\n"); + goto CleanUp; + } + } + } + if (local && (local->news) > 0) { //check if new files archive is present in the delta + char new_patch_path[MAX_FILE_PATH] = { 0, }; + snprintf(new_patch_path, MAX_FILE_PATH, "%s%s", ua_dataSS->parti_info->ua_subject_name, SS_COMPRESSED_FILE); + if (tar_get_item_size(ua_dataSS->update_data->ua_delta_path, new_patch_path) <= 0) { + SS_SetUpgradeState(E_SS_FSFAILEDTOPARSEDELTAINFO); + LOGE("New files not present in Patch\n"); + goto CleanUp; + } + } + if (local && (local->deletes) > 0) { //this is to group to delete list + LOGL(LOG_SSENGINE, "%ss [%d]\n", SS_STRING_DEL, local->deletes); + for (i = 0; i < (local->deletes); i++) { + if (fgets(line, SS_TOKEN_MAXLINE_LEN, fp) == NULL) { + SS_SetUpgradeState(E_SS_FSFAILEDTOPARSEDELTAINFO); + break; + } + + change_type = strtok(line, SS_TOEKN_COLON); + file_type = strtok(NULL, SS_TOEKN_COLON); + + if (strcmp(file_type, SS_STRING_REG) == 0) { + source_name = strtok(NULL, SS_TOEKN_COLON); + sha1src = strtok(NULL, SS_TOKEN_NEWLINE); + del_type = DELETES; + } else if (strcmp(file_type, SS_STRING_SYM) == 0) { + source_name = strtok(NULL, SS_TOKEN_NEWLINE); + sha1src = string_na; + del_type = DELETES; + } else if (strcmp(file_type, SS_STRING_END) == 0) { + source_name = strtok(NULL, SS_TOKEN_NEWLINE); + sha1src = string_na; + del_type = DELETE_END; + } + else { + LOGE("Failed to parse DELETES - LINE:[%d] [%s] \n", i + 1, line); + goto CleanUp; + } + + if (!source_name || !sha1src) { + SS_SetUpgradeState(E_SS_FSFAILEDTOPARSEDELTAINFO); + //LOGE("Failed to extract Patch Info Type:DELETES \n"); + LOGE("Failed to parse DELETES - LINE:[%d] [%s] \n", i + 1, line); + goto CleanUp; + } + //LOGL(LOG_SSENGINE, "%s Index [%d]\n", SS_STRING_DEL, i); + retval = + SS_AppendNode(ua_dataSS->update_data->ua_delta_path, &fs_delhead, &fs_deltail, source_name, + string_na, string_na, sha1src, string_na, del_type, + ua_dataSS->update_delta->ua_patch_path); + if (retval == E_SS_FAILURE) // ONLY test purpose, should enable this + goto CleanUp; + SS_UpdateUIProgress(ua_dataSS, ulPatchCount, 0); + + } + } //For symlink files + + if (local && (local->symdiffs) > 0) { + LOGL(LOG_SSENGINE, "%s %ss [%d]\n", SS_STRING_SYM, SS_STRING_DIFF, local->symdiffs); + for (i = 0; i < (local->symdiffs); i++) { //get the count from below function + if (fgets(line, SS_TOKEN_MAXLINE_LEN, fp) == NULL) { + SS_SetUpgradeState(E_SS_FSFAILEDTOPARSEDELTAINFO); + break; + } + //LOGL(LOG_SSENGINE, "SYMDIFF LINE:[%d] [%s] \n",i+1,line); + + change_type = strtok(line, SS_TOEKN_COLON); + file_type = strtok(NULL, SS_TOEKN_COLON); + + if (strcmp(change_type, SS_STRING_SYM) == 0 && strcmp(file_type, SS_STRING_DIFF) == 0) { // && strcmp(file_type,"TPK") == 0){ + source_name = strtok(NULL, SS_TOEKN_COLON); + target_name = strtok(NULL, SS_TOEKN_COLON); + patch_name = strtok(NULL, SS_TOKEN_NEWLINE); + //LOGL(LOG_SSENGINE, "%s %s Index [%d]\n", SS_STRING_SYM, SS_STRING_DIFF, i); + + if (!source_name || !target_name || !patch_name) { + SS_SetUpgradeState(E_SS_FSFAILEDTOPARSEDELTAINFO); + //LOGE("Failed to extract Patch Info Type:DELETES \n"); + LOGE("Failed to parse SymDiffs - LINE:[%d] [%s] \n", i + 1, line); + goto CleanUp; + } + retval = + SS_AppendNode(ua_dataSS->update_data->ua_delta_path, &fs_symlinkdiffhead, &fs_symlinkdifftail, + source_name, target_name, patch_name, string_na, string_na, SYMDIFFS, + ua_dataSS->update_delta->ua_patch_path); + if (retval == E_SS_FAILURE) // ONLY test purpose, should enable this + goto CleanUp; + SS_UpdateUIProgress(ua_dataSS, ulPatchCount, 0); + } + } + } + if (local && (local->symnews) > 0) { + LOGL(LOG_SSENGINE, "%s %ss [%d]n", SS_STRING_SYM, SS_STRING_NEW, local->symnews); + for (i = 0; i < (local->symnews); i++) { + if (fgets(line, SS_TOKEN_MAXLINE_LEN, fp) == NULL) { + SS_SetUpgradeState(E_SS_FSFAILEDTOPARSEDELTAINFO); + break; + } + //LOGL(LOG_SSENGINE, "SYMNEWS LINE:[%d] [%s] \n",i+1,line); + + change_type = strtok(line, SS_TOEKN_COLON); + file_type = strtok(NULL, SS_TOEKN_COLON); + + if (strcmp(change_type, SS_STRING_SYM) == 0 && strcmp(file_type, SS_STRING_NEW) == 0) { + source_name = strtok(NULL, SS_TOEKN_COLON); + patch_name = strtok(NULL, SS_TOKEN_NEWLINE); + //LOGL(LOG_SSENGINE, "%s %s Index [%d]\n", SS_STRING_SYM, SS_STRING_NEW, i); + + if (!source_name || !patch_name) { + SS_SetUpgradeState(E_SS_FSFAILEDTOPARSEDELTAINFO); + //LOGE("Failed to extract Patch Info Type:DELETES \n"); + LOGE("Failed to parse SymNews - LINE:[%d] [%s] \n", i + 1, line); + goto CleanUp; + } + retval = + SS_AppendNode(ua_dataSS->update_data->ua_delta_path, &fs_symlinknewhead, &fs_symlinknewtail, + source_name, string_na, patch_name, string_na, string_na, SYMNEWFILES, + ua_dataSS->update_delta->ua_patch_path); + if (retval == E_SS_FAILURE) // ONLY test purpose, should enable this + goto CleanUp; + SS_UpdateUIProgress(ua_dataSS, ulPatchCount, 0); + } + } + } + + fs_head_node = (fs_list *) SS_Malloc(sizeof(fs_list)); + if (!fs_head_node) { + SS_SetUpgradeState(E_SS_MALLOC_ERROR); + goto CleanUp; + } + fs_head_node->dif_ref = fs_diffhead; + fs_head_node->move_ref = fs_movehead; + fs_head_node->new_ref = fs_newhead; + fs_head_node->del_ref = fs_delhead; + fs_head_node->sym_difref = fs_symlinkdiffhead; + fs_head_node->sym_newref = fs_symlinknewhead; + fs_head_node->ulPatchCount = ulPatchCount; + + SS_UpdateUIProgress(ua_dataSS, ulPatchCount, 1); + + CleanUp: + fclose(fp); + SS_Free(local); + unlink(SS_PATCHLIST_BKUPLOC); + if (retval == E_SS_FAILURE) + if (tar_cfg_data) + tar_free_cfg_table(&tar_cfg_data); + return fs_head_node; +} + +void SS_GetPartition_LocDetails(ua_dataSS_t * ua_dataSS, int part_idx) +{ + LOGL(LOG_SSENGINE, "PART NAME: [%s] \n", ua_dataSS->parti_info->ua_parti_name); + snprintf(ua_dataSS->update_delta->ua_patch_path, MAX_FILE_PATH, "%s", ua_dataSS->parti_info->ua_subject_name); + snprintf(ua_dataSS->update_delta->ua_patch_info, MAX_FILE_PATH, "%s%s%s", ua_dataSS->parti_info->ua_subject_name, + ua_dataSS->parti_info->ua_parti_name, SS_PATCHLISTFORMAT); + snprintf(ua_dataSS->update_delta->ua_attrib_path, MAX_FILE_PATH, "%s%s%s", ua_dataSS->parti_info->ua_subject_name, + ua_dataSS->parti_info->ua_parti_name, SS_PATCH_ATTR_FORMAT); + LOGL(LOG_SSENGINE, "PatchPath[%s] PatchInfo [%s] Attributes [%s]\n", ua_dataSS->update_delta->ua_patch_path, + ua_dataSS->update_delta->ua_patch_info, ua_dataSS->update_delta->ua_attrib_path); + + return; +} + +//Support functions//Change Struct format details (Can include total file count also???)/*! +/* +******************************************************************************** * +SS_FSSetAttributes + ********************************************************************************* + * *@brief + * This is used to set the file attributes at the end of application of patches in FS + * + * *@param + * + *@return returns S_SS_SUCCESS + * E_SS_FAILURE in case of error + * + ********************************************************************************* + */ +int SS_FSSetAttributes(ua_dataSS_t * ua_dataSS) +{ + char *pline = NULL; + char *psaveptr = NULL; + char *pfilePath = NULL; + char *pfiletype = NULL; + char *attributSize = NULL; + char *pattribs = NULL; + int ulAttribSize = 0; + int result = S_SS_SUCCESS; + + if (!(ua_dataSS && ua_dataSS->update_delta && ua_dataSS->update_data->ua_delta_path)) { + LOGE("Bad params for SS_FSSetAttributes\n"); + SS_SetUpgradeState(E_SS_BAD_PARAMS); + return E_SS_FAILURE; + } + LOGL(LOG_SSENGINE, "ATTRIB PATH: [%s] \n", ua_dataSS->update_delta->ua_attrib_path); + + int item_size = tar_get_item_size(ua_dataSS->update_data->ua_delta_path, ua_dataSS->update_delta->ua_attrib_path); + + if (item_size <= 0) { + LOGL(LOG_SSENGINE, "No Attributes to SET\n"); + return S_SS_SUCCESS; // Delta with ONLY deletes + } + char *item_data = SS_Malloc(item_size + 1); + if (item_data == NULL) { + SS_SetUpgradeState(E_SS_MALLOC_ERROR); + return E_SS_FAILURE; + } + int read_data = + tar_get_cfg_data(ua_dataSS->update_data->ua_delta_path, ua_dataSS->update_delta->ua_attrib_path, item_data, + item_size); + if (read_data <= 0) { + SS_SetUpgradeState(E_SS_FSBADDELTA); + if (item_data != NULL) + SS_Free(item_data); + return E_SS_FAILURE; + } + pline = strtok_r(item_data, "\n", &psaveptr); + if (pline == NULL) { + LOGL(LOG_SSENGINE, "No Attributes to SET as no lines in file\n"); + if (item_data != NULL) + SS_Free(item_data); + return E_SS_FAILURE; + } + + while (pline) { + pfilePath = strtok(pline, "\""); + + if (pfilePath && strcmp(pfilePath, SS_FWSLASH) == 0) { + LOGE("\n skip root: it is RO"); + pline = strtok_r(NULL, SS_TOKEN_NEWLINE, &psaveptr); + continue; + } + + pfiletype = strtok(NULL, SS_TOKEN_SPACE); + attributSize = strtok(NULL, SS_TOKEN_SPACE); + pattribs = strtok(NULL, SS_TOKEN_NEWLINE); + if (pattribs && pfilePath && pfiletype) { + ulAttribSize = strlen(pattribs); + //LOG("\nSS_SetFileAttributes [%s][%s][%d][%s]",pfilePath,pfiletype,ulAttribSize, pattribs ); + //LOG("SS_SetFileAttributes [%s]\n", pfilePath); + + result = SS_SetFileAttributes(pfilePath, ulAttribSize, (const unsigned char *)pattribs); + if (result != S_SS_SUCCESS) { + LOGE("\n Failed to set Attributes %s", pfilePath); + SS_SetUpgradeState(E_SS_FSBADATTRIBUTES); + if (item_data) + SS_Free(item_data); + return E_SS_FAILURE; + } + } else { + LOGE("\n Failed to Parse Attributes - LINE %s", pline); + SS_SetUpgradeState(E_SS_FSBADATTRIBUTES); + if (item_data) + SS_Free(item_data); + return E_SS_FAILURE; + } + pline = strtok_r(NULL, SS_TOKEN_NEWLINE, &psaveptr); + } + SS_Free(item_data); + + return S_SS_SUCCESS; +} + +/*! + ********************************************************************************* + * SS_FSUpdateFile + ********************************************************************************* + * + * @brief + * This is used to update individual files on case basis + * + * + * @param + * + * @return returns S_SS_SUCCESS + * E_SS_FAILURE in case of error + * + ********************************************************************************* + */ +int SS_FSUpdateFile(int ubFileType, ua_dataSS_t * ua_dataSS, int ulPatchCount, fs_params * pFsNode, + const char *patch_path) +{ + int ulFileIndex = 1; + char ubPatch[SS_MAX_FILE_PATH] = { + 0 + }; + int ulReadCnt = 0; + int ulResult = S_SS_SUCCESS; + + if (!patch_path) { + LOGE("Bad patch_path name\n"); + return E_SS_FAILURE; + } + switch (ubFileType) { + case DIFFS: + { + tar_open(ua_dataSS->update_data->ua_delta_path); +#ifdef TIME_PROFILING + get_time_stamp1(); //total time capturing + t1 = atoi(ts1); +#endif + while (pFsNode) { + LOGL(LOG_SSENGINE, "DIFFS update Index: [%d] \n", ulFileIndex++); + snprintf(ubPatch, SS_MAX_FILE_PATH, "%s%s", patch_path, pFsNode->patch_name); + //LOGL(LOG_SSENGINE, "DIFF list --- [File Name %s]\n [Patch Name %s]",pFsNode->file_path, ubPatch); + if (pFsNode->data_size > 0) { + ulReadCnt = + fast_tar_extract_file(ua_dataSS->update_data->ua_delta_path, ubPatch, SS_PATCHFILE_SOURCE, + pFsNode->data_size, pFsNode->data_offset); + if (ulReadCnt < 0) { + ulResult = E_SS_FAILURE; + tar_close(); + SS_SetUpgradeState(E_SS_FSUPDATEFAILED); + LOGE("Delta Read Failed\n"); + break; + } + //LOGL(LOG_SSENGINE,"Updating [Item - %s]and size is[%d] Read Count[%d]\n",ubPatch, pFsNode->data_size, ulReadCnt); + + if (ulReadCnt > 0) + ulResult = + SS_UpdateDeltaFS(pFsNode->file_old_path, pFsNode->file_new_path, pFsNode->sha1src, + pFsNode->sha1trg, pFsNode->data_size); + if (ulResult != S_SS_SUCCESS) { + LOGE("FS update Failed Result : [%d], [Item - %s]and size is[%d] Read Count[%d]\n", ulResult, + ubPatch, pFsNode->data_size, ulReadCnt); + tar_close(); + SS_SetUpgradeState(E_SS_FSUPDATEFAILED); + break; + } + SS_UpdateUIProgress(ua_dataSS, ulPatchCount, 0); + pFsNode = pFsNode->nextnode; + } +#ifdef SUPPORT_CONTAINER + else if (pFsNode->data_size == 0) { //need to add logic to identify archive update + ulResult = tar_extract_folder(ua_dataSS->update_data->ua_delta_path, ubPatch, + SS_ARCHIVE_DELTA_FOLDER); + if (ulResult != S_SS_SUCCESS) { + LOGE("extraction failed for [%s] result [%d]\n", ubPatch, ulResult); + SS_SetUpgradeState(E_SS_DELTA_IS_CORRUPT); + break; + } + //Copy exe's for zip , unzip and find operations - code to be removed after ramdisk upgrade + ulResult = (int)SS_CopyFile(NULL, SS_ZIP_SOURCE, SS_ZIP_TARGET); + if (ulResult != S_SS_SUCCESS) { + LOGE("failed to copy unzip [%d]\n", ulResult); + SS_SetUpgradeState(E_SS_WRITE_ERROR); + break; + } + ulResult = (int)SS_CopyFile(NULL, SS_UNZIP_SOURCE, SS_UNZIP_TARGET); + if (ulResult != S_SS_SUCCESS) { + LOGE("failed to copy unzip [%d]\n", ulResult); + SS_SetUpgradeState(E_SS_WRITE_ERROR); + break; + } + ulResult = (int)SS_CopyFile(NULL, SS_FIND_CMD_SOURCE, SS_FIND_CMD_TARGET); + if (ulResult != S_SS_SUCCESS) { + LOGE("failed to copy find [%d]\n", ulResult); + SS_SetUpgradeState(E_SS_WRITE_ERROR); + break; + } + ulResult = + SS_UpdateArchive(ua_dataSS, pFsNode->file_old_path, pFsNode->file_new_path, pFsNode->sha1src, + pFsNode->sha1trg); + if (ulResult != S_SS_SUCCESS) { + LOGE("FS update Failed, Unable to extact archive\n"); + break; + } + + SS_UpdateUIProgress(ua_dataSS, ulPatchCount, 0); + pFsNode = pFsNode->nextnode; + } +#endif + else { + ulResult = E_SS_FAILURE; + tar_close(); + LOGE("size is invalid\n"); + SS_SetUpgradeState(E_SS_FSUPDATEFAILED); + break; + } + } +#ifdef TIME_PROFILING + get_time_stamp1(); //total time capturing + t2 = atoi(ts1); + LOG("Shirsh time for DIFFS - [%d] \n", (t2 - t1)); +#endif +#ifdef SUPPORT_CONTAINER + SS_DeleteFile(NULL, SS_ZIP_TARGET); + SS_DeleteFile(NULL, SS_UNZIP_TARGET); +#endif + tar_close(); + } + break; + case MOVES: + { +#ifdef TIME_PROFILING + get_time_stamp1(); //total time capturing + t1 = atoi(ts1); +#endif + while (pFsNode) { + LOGL(LOG_SSENGINE, "MOVES update Index: [%d] \n", ulFileIndex++); + ulResult = SS_MoveFile(NULL, pFsNode->file_old_path, pFsNode->file_new_path); + if (ulResult != S_SS_SUCCESS) { + LOGE("Move Failed for [%s] to [%s]\n", pFsNode->file_old_path, pFsNode->file_new_path); + SS_SetUpgradeState(ulResult); + break; + } + SS_UpdateUIProgress(ua_dataSS, ulPatchCount, 0); + pFsNode = pFsNode->nextnode; + } +#ifdef TIME_PROFILING + get_time_stamp1(); //total time capturing + t2 = atoi(ts1); + LOG("Shirsh time for DELETES - [%d] \n", (t2 - t1)); +#endif + } + break; + case DELETES: + { + int ulFiletype = 0; +#ifdef TIME_PROFILING + get_time_stamp1(); //total time capturing + t1 = atoi(ts1); +#endif + while (pFsNode) { + if (pFsNode->type == DELETES) { + LOGL(LOG_SSENGINE, "DELETES update Index: [%d] \n", ulFileIndex++); + SS_GetFileType(NULL, pFsNode->file_old_path, (enumFileType *) & ulFiletype); + if (ulFiletype == 2) //FT_FOLDER + ulResult = SS_DeleteFolder(NULL, pFsNode->file_old_path); + else + ulResult = SS_DeleteFile(NULL, pFsNode->file_old_path); + if (ulResult != S_SS_SUCCESS) { + LOGE("Delete Failed\n"); + SS_SetUpgradeState(ulResult); + break; + } + } + SS_UpdateUIProgress(ua_dataSS, ulPatchCount, 0); + pFsNode = pFsNode->nextnode; + } +#ifdef TIME_PROFILING + get_time_stamp1(); //total time capturing + t2 = atoi(ts1); + LOG("Shirsh time for DELETES - [%d] \n", (t2 - t1)); +#endif + } + break; + case DELETE_END: + { + int ulFiletype = 0; +#ifdef TIME_PROFILING + get_time_stamp1(); //total time capturing + t1 = atoi(ts1); +#endif + while (pFsNode) { + if (pFsNode->type == DELETE_END) { + LOGL(LOG_SSENGINE, "DELETES update Index: [%d] \n", ulFileIndex++); + SS_GetFileType(NULL, pFsNode->file_old_path, (enumFileType *) & ulFiletype); + if (ulFiletype == 2) //FT_FOLDER + ulResult = SS_DeleteFolder(NULL, pFsNode->file_old_path); + else + ulResult = SS_DeleteFile(NULL, pFsNode->file_old_path); + if (ulResult != S_SS_SUCCESS) { + LOGE("Delete Failed\n"); + SS_SetUpgradeState(ulResult); + break; + } + } + pFsNode = pFsNode->nextnode; + } +#ifdef TIME_PROFILING + get_time_stamp1(); //total time capturing + t2 = atoi(ts1); + LOG("Shirsh time for DELETES - [%d] \n", (t2 - t1)); +#endif + } + break; + + case NEWFILES: + { +#ifdef TIME_PROFILING + get_time_stamp1(); //total time capturing + t1 = atoi(ts1); +#endif + LOGL(LOG_SSENGINE, "Starting New file upgrade for [%s]\n", patch_path); + if (tar_extract_file(ua_dataSS->update_data->ua_delta_path, (char *)patch_path, SS_NEW_COMPRESSED_FILE) >= + 0) + if (_7zdecompress(SS_NEW_COMPRESSED_FILE) == 0) + LOGL(LOG_SSENGINE, "7zip extracted successfully %s\n", ua_dataSS->parti_info->ua_parti_name); + else + LOGL(LOG_SSENGINE, "7zip extraction error for %s\n", ua_dataSS->parti_info->ua_parti_name); + else + LOGL(LOG_SSENGINE, "tar extraction error for %s\n", ua_dataSS->parti_info->ua_parti_name); + SS_DeleteFile(NULL, SS_NEW_COMPRESSED_FILE); +#ifdef TIME_PROFILING + get_time_stamp1(); //total time capturing + t2 = atoi(ts1); + LOG("Shirsh time for NEWFILES - [%d] \n", (t2 - t1)); +#endif + } + break; + case SYMDIFFS: + { +#ifdef TIME_PROFILING + get_time_stamp1(); //total time capturing + t1 = atoi(ts1); +#endif + while (pFsNode) { + LOGL(LOG_SSENGINE, "SYMDIFFS update Index: [%d] \n", ulFileIndex++); + //LOG("Sym Diff file paths: [Linkname - %s] [reference file name- %s][]\n", pFsNode->file_path,pFsNode->patch_name); + ulResult = SS_Unlink(NULL, pFsNode->file_old_path); + if (ulResult == S_SS_SUCCESS) + ulResult = SS_Link(NULL, pFsNode->file_new_path, pFsNode->patch_name); + else { + LOGE("Unlink Failed\n"); + SS_SetUpgradeState(E_SS_FSUPDATEFAILED); + break; + } + SS_UpdateUIProgress(ua_dataSS, ulPatchCount, 0); + pFsNode = pFsNode->nextnode; + } +#ifdef TIME_PROFILING + get_time_stamp1(); //total time capturing + t2 = atoi(ts1); + LOG("Shirsh time for SYMDIFFS - [%d] \n", (t2 - t1)); +#endif + } + break; + case SYMNEWFILES: + { + fs_params *head_node; + int retry_count = 0, do_retry = 0; +#ifdef TIME_PROFILING + get_time_stamp1(); //total time capturing + t1 = atoi(ts1); +#endif + SYMLINK_CREATE: + head_node = pFsNode; + while (head_node) { + LOGL(LOG_SSENGINE, "SYMNEWS update Index: [%d] \n", ulFileIndex++); + snprintf(ubPatch, SS_MAX_FILE_PATH, "%s%s%s", patch_path, "/", head_node->patch_name); + LOGL(LOG_SSENGINE, "Sym New file paths: [Linkname - %s] [reference file name- %s][]\n", + head_node->file_old_path, head_node->patch_name); + ulResult = SS_Link(NULL, head_node->file_old_path, head_node->patch_name); + if (ulResult == E_SS_FAILURE) { + LOGE("Link Failed\n"); + SS_SetUpgradeState(E_SS_FSUPDATEFAILED); + break; + } else if (ulResult == ENOENT) //to handle cases where new symlink points to a new symlink yet to be created + { + do_retry = 1; //we will retry the failed symlinks with error 2 (no file or dir) again after this cycle + //SS_UpdateUIProgress(ua_dataSS,ulPatchCount); + head_node = head_node->nextnode; + continue; + + } + SS_UpdateUIProgress(ua_dataSS, ulPatchCount, 0); + head_node = head_node->nextnode; + } + if (do_retry && (retry_count < 4)) { + retry_count++; + ulFileIndex = 0; + do_retry = 0; + goto SYMLINK_CREATE; + } else if (do_retry && (retry_count >= 4)) //retry to be done maximum 4 times + { + LOGE("Link Failed after %d retrys\n", retry_count); + //SS_SetUpgradeState(E_SS_FSUPDATEFAILED); + break; + } +#ifdef TIME_PROFILING + get_time_stamp1(); //total time capturing + t2 = atoi(ts1); + LOG("Shirsh time for SYMNEWS - [%d] \n", (t2 - t1)); +#endif + } + break; + default: + break; + } + return ulResult; +} + +#ifdef MEM_PROFILING +extern int max_mem; +extern int cur_mem; +#endif +/*! + ********************************************************************************* + * SS_FSUpdatemain + ********************************************************************************* + * + * @brief + * This is the API exposed from the engine to update FS. + * + * + * @param + * + * @return returns S_SS_SUCCESS + * E_SS_FAILURE in case of error + * + ********************************************************************************* + */ + +int SS_FSUpdatemain(ua_dataSS_t * ua_dataSS, int part_idx) +{ + int ulResult = S_SS_SUCCESS; + fs_list *head_ptr_node = NULL; + char new_patch_path[SS_MAX_FILE_PATH] = { + 0 + }; + + if (!ua_dataSS) + return E_SS_BAD_PARAMS; // Set error ?? + head_ptr_node = headptr_list[part_idx]; + + if (!head_ptr_node) { //in case of power failure, try rebilding nodes again + SS_FSVerifyPartition(ua_dataSS, part_idx); + head_ptr_node = headptr_list[part_idx]; + } + + if (!head_ptr_node) + return E_SS_FSBADNODES; + + SS_GetPartition_LocDetails(ua_dataSS, part_idx); + + LOGL(LOG_SSENGINE, "FS Update Entry PartIndex: [%d]\n", part_idx); + + if (head_ptr_node->del_ref == NULL) { + LOGL(LOG_SSENGINE, "No DEL header\n"); + } else if (ulResult == S_SS_SUCCESS) { + ulResult = + SS_FSUpdateFile(DELETES, ua_dataSS, head_ptr_node->ulPatchCount, head_ptr_node->del_ref, + ua_dataSS->update_delta->ua_patch_path); + } + + if (head_ptr_node->dif_ref == NULL) { + LOGL(LOG_SSENGINE, "No DIFF header\n"); + } else if (ulResult == S_SS_SUCCESS) { + ulResult = + SS_FSUpdateFile(DIFFS, ua_dataSS, head_ptr_node->ulPatchCount, head_ptr_node->dif_ref, + ua_dataSS->update_delta->ua_patch_path); + } + + if (head_ptr_node->move_ref == NULL) { + LOGL(LOG_SSENGINE, "No MOVE header\n"); + } else if (ulResult == S_SS_SUCCESS) { + ulResult = + SS_FSUpdateFile(MOVES, ua_dataSS, head_ptr_node->ulPatchCount, head_ptr_node->move_ref, + ua_dataSS->update_delta->ua_patch_path); + } + + if (head_ptr_node->del_ref == NULL) { + LOGL(LOG_SSENGINE, "No DEL header\n"); + } else if (ulResult == S_SS_SUCCESS) { + ulResult = + SS_FSUpdateFile(DELETE_END, ua_dataSS, head_ptr_node->ulPatchCount, head_ptr_node->del_ref, + ua_dataSS->update_delta->ua_patch_path); + } + + if (ulResult == S_SS_SUCCESS) { + //new file extraction start + snprintf(new_patch_path, SS_MAX_FILE_PATH, "%s%s", ua_dataSS->parti_info->ua_subject_name, SS_COMPRESSED_FILE); // subject name wil have fw slash as part of cfg file + LOGL(LOG_SSENGINE, "File path created to extract new files : [%s]\n", new_patch_path); + ulResult = + SS_FSUpdateFile(NEWFILES, ua_dataSS, head_ptr_node->ulPatchCount, head_ptr_node->new_ref, new_patch_path); + //new file extraction end + } + + if (head_ptr_node->sym_difref == NULL) { + LOGL(LOG_SSENGINE, "No SYMDIFF header\n"); + } else if (ulResult == S_SS_SUCCESS) { + ulResult = + SS_FSUpdateFile(SYMDIFFS, ua_dataSS, head_ptr_node->ulPatchCount, head_ptr_node->sym_difref, + ua_dataSS->update_delta->ua_patch_path); + } + + if (head_ptr_node->sym_newref == NULL) { + LOGL(LOG_SSENGINE, "No SYMNEW header\n"); + } else if (ulResult == S_SS_SUCCESS) { + ulResult = + SS_FSUpdateFile(SYMNEWFILES, ua_dataSS, head_ptr_node->ulPatchCount, head_ptr_node->sym_newref, + ua_dataSS->update_delta->ua_patch_path); + } + + if (ulResult == S_SS_SUCCESS) + ulResult = SS_FSSetAttributes(ua_dataSS); + sync(); + sleep(1); + SS_FSClearNodes(part_idx); + + if (ulResult == S_SS_SUCCESS) + SS_UpdateUIProgress(ua_dataSS, 0, 1); //fix WGID : 51963, When all updates are done to FS , patchcount is not needed, passing 1 to 3rd arg is enough + + LOGL(LOG_SSENGINE, "FS update Complete PartIndex: [%d]\n", part_idx); +#ifdef MEM_PROFILING + LOGL(LOG_SSENGINE, "Stats are : Cur Max : [%d] Global Max : [%d]\n", cur_mem, max_mem); +#endif + if (ulResult == S_SS_SUCCESS) + return ulResult; + else + return SS_GetUpgradeState(); +} + +/*! + ********************************************************************************* + * SS_FSUpdatemain + ********************************************************************************* + * + * @brief + * This is the API exposed from the engine to update FS. + * FS entry function for updating FS partition. Should be invoked only after verification of the partition + * + * + * @param Requires common data structure having all details & Partition Index. + * (Used for getting right NODES information that built during verification) + * (Configuration, Delta info, Partition Info, UI link , Kind of operation.(Verify or Updates)) + * + * @return returns S_SS_SUCCESS + * E_SS_FAILURE in case of error + * + ********************************************************************************* + */ +size_t SS_FSAvailiableFreeSpace(char *block_name) +{ + + struct mntent *ent; + FILE *aFile; + struct statfs sb; + aFile = setmntent("/proc/mounts", "r"); + if (aFile == NULL) { + LOGE("setmntent error"); + return E_SS_FAILURE; + } + while (NULL != (ent = getmntent(aFile))) { + if (strcmp(ent->mnt_fsname, block_name) == 0) { + if (statfs(ent->mnt_dir, &sb) == 0) { + LOGL(LOG_SSENGINE, "Total free space = %d, blocks free = %d\n", sb.f_bsize * sb.f_bavail, sb.f_bfree); + } + } + } + endmntent(aFile); + return sb.f_bsize * sb.f_bavail; +} + +int SS_FSVerifyPartition(ua_dataSS_t * ua_dataSS, int part_idx) +{ + int ulResult = S_SS_SUCCESS; + size_t free_space = 0; + if (!ua_dataSS) { + LOGE("Wrong Param for fs verification\n"); + SS_SetUpgradeState(E_SS_BAD_PARAMS); + return E_SS_FAILURE; + } + + LOGL(LOG_SSENGINE, "FS max free mem reqired : [%d]\n", ua_dataSS->update_cfg->soure_img_size); + free_space = SS_FSAvailiableFreeSpace(ua_dataSS->parti_info->ua_blk_name); + if (free_space != E_SS_FAILURE) { + //Source img size is max single file size for a file system under upgrade, which is updated in CFG file by UPG + if ((free_space) < (ua_dataSS->update_cfg->soure_img_size + ua_dataSS->update_cfg->soure_img_size / 10)) { + LOGE("Not enough free space [%d] for max size [%d]\n", free_space, + (ua_dataSS->update_cfg->soure_img_size + ua_dataSS->update_cfg->soure_img_size / 10)); + //SS_SetUpgradeState(E_SS_FSMEMORYERROR); + //return E_SS_FAILURE; + } else + LOGL(LOG_SSENGINE, "Enough space for Partition [%s]\n", ua_dataSS->parti_info->ua_parti_name); + } + + SS_GetAvailableFreeSpace(NULL, SS_COMMON_WORKSPACE, &free_space); + //Checking for 2 times the file size free space , as delta can be worst case size of file. + if ((free_space) < (2 * ua_dataSS->update_cfg->soure_img_size)) { + LOGE("Not enough free space [%d] for max size [%d]\n", free_space, (2 * ua_dataSS->update_cfg->soure_img_size)); + SS_SetUpgradeState(E_SS_FSMEMORYERROR); + return E_SS_FAILURE; + } +#ifdef MEM_PROFILING + if (!mem_profiling_start) + if (!(S_SS_SUCCESS == SS_Do_Memory_Profiling())) + return E_SS_FAILURE; +#endif + SS_GetPartition_LocDetails(ua_dataSS, part_idx); + LOGL(LOG_SSENGINE, "FS Verification Start PartIndex:[%d]\n", part_idx); + if (ua_dataSS->ua_operation == UI_OP_SCOUT) + gvalid_session = TRUE; // (shd b true if called during verification) + headptr_list[part_idx] = SS_FSBuildNodes(ua_dataSS); +#ifdef TIME_PROFILING + LOGL(LOG_SSENGINE, "fast_tar_get_item_size_time :[%lf]\n", fast_tar_get_item_size_time); + LOGL(LOG_SSENGINE, "SS_LoadFile_time :[%lf]\n", SS_LoadFile_time); + LOGL(LOG_SSENGINE, "SS_FSBuildNodes_time :[%lf]\n", SS_FSBuildNodes_time); +#endif + if (!headptr_list[part_idx]) { + LOGE("FS Verification Failed PartIndex: [%d]\n", part_idx); + SS_FSClearNodes(part_idx); + ulResult = E_SS_FAILURE; + } + + if (ulResult == S_SS_SUCCESS) + return ulResult; + else + return SS_GetUpgradeState(); +} + +//Should check if space is available???? +int SS_BackupSource(const char *source_filename) +{ + int ret = E_SS_FAILURE; + + if (source_filename) { + ret = (int)SS_CopyFile(NULL, source_filename, SS_BACKUP_SOURCE); + if (ret != S_SS_SUCCESS) { + LOGE("failed to back up source file Error [%d]\n", ret); + SS_SetUpgradeState(E_SS_FSSRCBACKUPFAILED); + } + } + return ret; +} + +int SS_BackupSourceClear(void) +{ + int ret = E_SS_FAILURE; + ret = (int)SS_DeleteFile(NULL, SS_BACKUP_SOURCE); + if (ret != S_SS_SUCCESS) { + LOGE("failed to delete BACKUP file\n"); + SS_SetUpgradeState(E_SS_FSSRCBACKUPFAILED); + } + return ret; +} + +int SS_PatchSourceClear(void) +{ + int ret = E_SS_FAILURE; + ret = (int)SS_DeleteFile(NULL, SS_PATCHFILE_SOURCE); + if (ret != S_SS_SUCCESS) { + LOGE("failed to delete PATCHFILE file\n"); + SS_SetUpgradeState(E_SS_PATCHFILE_DEL_ERROR); + } + return ret; +} +int SS_IMGVerifyFullImage(ua_dataSS_t * ua_dataSS) +{ + int read_cnt = 0, patch_size = 0; + FileInfo source_file; + uint8_t target_sha1[SHA_DIGEST_SIZE]; + int ulResult = S_SS_SUCCESS; + + if (!(ua_dataSS && ua_dataSS->update_cfg && ua_dataSS->parti_info && ua_dataSS->parti_info->ua_blk_name + && !(ua_dataSS->update_cfg->skip_verify == 1))) { + LOGE("Bad structure or members\n"); + SS_SetUpgradeState(E_SS_BAD_PARAMS); + return E_SS_FAILURE; + } + + LOGL(LOG_SSENGINE, "FULL IMG Verification Entry BlkName:[%s]\n", ua_dataSS->parti_info->ua_blk_name); + + if (ua_dataSS->update_data && ua_dataSS->parti_info && ua_dataSS->update_data->ua_delta_path + && ua_dataSS->parti_info->ua_subject_name) + patch_size = tar_get_item_size(ua_dataSS->update_data->ua_delta_path, ua_dataSS->parti_info->ua_subject_name); + else { + LOGE("Bad structure members in ua_dataSS\n"); + SS_SetUpgradeState(E_SS_BAD_PARAMS); + ulResult = E_SS_FAILURE; + goto Cleanup; + } + + if (ua_dataSS->update_cfg && ua_dataSS->update_cfg->soure_img_size && ua_dataSS->update_cfg->target_sha1) + LOGL(LOG_SSENGINE, "\nParams -image size [%d] sha1 [%s]\n", + ua_dataSS->update_cfg->soure_img_size, ua_dataSS->update_cfg->target_sha1); + else { + LOGE("Bad structure member update_cfg in ua_dataSS\n"); + SS_SetUpgradeState(E_SS_BAD_PARAMS); + ulResult = E_SS_FAILURE; + goto Cleanup; + } + + if (ParseSha1(ua_dataSS->update_cfg->target_sha1, target_sha1) != 0) { + LOGE("failed to parse tgt-sha1 \"%s\"\n", ua_dataSS->update_cfg->target_sha1); + SS_SetUpgradeState(E_SS_IMGBADDELTA); + ulResult = E_SS_FAILURE; + goto Cleanup; + } + + if ((patch_size) > 0) + read_cnt = + tar_extract_file(ua_dataSS->update_data->ua_delta_path, ua_dataSS->parti_info->ua_subject_name, + SS_PATCHFILE_SOURCE); + + if (read_cnt <= 0) { + LOGL(LOG_SSENGINE, "Failed to read delta\n"); + SS_SetUpgradeState(E_SS_IMGBADDELTA); + ulResult = E_SS_FAILURE; + goto Cleanup; + } + if (SS_LoadFile(SS_PATCHFILE_SOURCE, &source_file) == 0) { + if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_SIZE) == 0) { + LOGL(LOG_SSENGINE, "Patch Can be applied\n"); + SS_Free(source_file.data); + ulResult = S_SS_SUCCESS; + } + else{ + LOGL(LOG_SSENGINE, "Patch Cannot be applied\n"); + SS_Free(source_file.data); + SS_SetUpgradeState(E_SS_IMGBADDELTA); + ulResult = E_SS_FAILURE; + goto Cleanup; + } + } + else{ + LOGL(LOG_SSENGINE, "Failed to LoadFile\n"); + SS_SetUpgradeState(E_SS_IMGBADDELTA); + ulResult = E_SS_FAILURE; + } + +Cleanup: + if(file_exist(SS_PATCHFILE_SOURCE, 0)) + SS_DeleteFile(NULL, SS_PATCHFILE_SOURCE); + return ulResult; +} + +int SS_IMGVerfiyPartition(ua_dataSS_t * ua_dataSS) +{ + FileInfo source_file; + int ulResult = S_SS_SUCCESS; + uint8_t source_sha1[SHA_DIGEST_SIZE]; + uint8_t target_sha1[SHA_DIGEST_SIZE]; + size_t free_space = 0; + + if (!(ua_dataSS && ua_dataSS->update_cfg && ua_dataSS->parti_info && ua_dataSS->parti_info->ua_blk_name)) { + LOGE("Bad structure or members\n"); + SS_SetUpgradeState(E_SS_BAD_PARAMS); + return E_SS_FAILURE; + } + + //We verify twice the image size for BACKUP source, not on Partition. As Patch will be created on RAM + SS_GetAvailableFreeSpace(NULL, SS_COMMON_WORKSPACE, &free_space); + if ((free_space) < (2 * ua_dataSS->update_cfg->target_img_size)) { + LOGE("Not enough free space [%d] for twice max size [%d]\n", free_space, + (2 * ua_dataSS->update_cfg->target_img_size)); + SS_SetUpgradeState(E_SS_FSMEMORYERROR); + return E_SS_FAILURE; + } + + if (ParseSha1(ua_dataSS->update_cfg->soure_sha1, source_sha1) != 0) { + LOGE("failed to parse Src-sha1 \"%s\"\n", ua_dataSS->update_cfg->soure_sha1); + SS_SetUpgradeState(E_SS_SHAPRASE_FAILED); + return E_SS_FAILURE; + } + // corner case, Parsing sha can fail if update.cfg is wrong/manually edited + if (ParseSha1(ua_dataSS->update_cfg->target_sha1, target_sha1) != 0) { + LOGE("failed to parse Target-sha1 \"%s\"\n", ua_dataSS->update_cfg->target_sha1); + SS_SetUpgradeState(E_SS_SHAPRASE_FAILED); + return E_SS_FAILURE; + } + + source_file.size = ua_dataSS->update_cfg->soure_img_size; + source_file.data = NULL; + if (SS_LoadPartition(ua_dataSS->parti_info->ua_blk_name, &source_file) == 0) { + if (memcmp(source_file.sha1, source_sha1, SHA_DIGEST_SIZE) == 0) { + LOGL(LOG_SSENGINE, "SS_IMGVerfiyPartition - SHA matches with source [%s] \n", + ua_dataSS->parti_info->ua_blk_name); + } else // Need not compare with Target sha as once upgraded, it should NOT verify same partition again. + { + SS_SetUpgradeState(E_SS_IMGSRCCURRUPTED); + ulResult = E_SS_FAILURE; + } + } + SS_Free(source_file.data); + if (ulResult == S_SS_SUCCESS) { + if (ua_dataSS->ui_progress) + ua_dataSS->ui_progress(ua_dataSS, 100); + return ulResult; + } else + return SS_GetUpgradeState(); +} + +/*! + ********************************************************************************* + * SS_IMGUpdatemain + ********************************************************************************* + * + * @brief + * This is the API exposed from the engine to update Images(FULL and DELTA images) + * + * + * @param + * + * @return returns S_SS_SUCCESS + * E_SS_FAILURE in case of error + * + ********************************************************************************* + */ + +int SS_IMGUpdatemain(ua_dataSS_t * ua_dataSS, int update_type) //SS_FSUpdatePartition +{ + int read_cnt = 0, patch_size; + int ulResult = S_SS_SUCCESS; + + //sprintf(Blk_name, "%s%s%s","EMMC",":", ua_dataSS->parti_info->ua_blk_name); + //LOGL(LOG_SSENGINE, "IMG Upgrade Entry BlkName:[%s]\n",Blk_name); + if (!ua_dataSS) { + LOGE("Bad structure ua_dataSS\n"); + SS_SetUpgradeState(E_SS_BAD_PARAMS); + return E_SS_FAILURE; + } + LOGL(LOG_SSENGINE, "IMG Upgrade Entry BlkName:[%s]\n", ua_dataSS->parti_info->ua_blk_name); + + if (ua_dataSS->update_data && ua_dataSS->parti_info && ua_dataSS->update_data->ua_delta_path + && ua_dataSS->parti_info->ua_subject_name) + patch_size = tar_get_item_size(ua_dataSS->update_data->ua_delta_path, ua_dataSS->parti_info->ua_subject_name); + else { + LOGE("Bad structure members in ua_dataSS\n"); + SS_SetUpgradeState(E_SS_BAD_PARAMS); + return E_SS_FAILURE; + } + if (ua_dataSS->update_cfg && ua_dataSS->update_cfg->soure_img_size && ua_dataSS->update_cfg->target_sha1) + LOGL(LOG_SSENGINE, "\n SS_IMGUpdatemain Params -source size [%d] sha1 [%s]", + ua_dataSS->update_cfg->soure_img_size, ua_dataSS->update_cfg->target_sha1); + else { + LOGE("Bad structure member update_cfg in ua_dataSS\n"); + SS_SetUpgradeState(E_SS_BAD_PARAMS); + return E_SS_FAILURE; + } + + if ((patch_size) > 0) + read_cnt = + tar_extract_file(ua_dataSS->update_data->ua_delta_path, ua_dataSS->parti_info->ua_subject_name, + SS_PATCHFILE_SOURCE); + + if (read_cnt <= 0) { + ulResult = E_SS_FAILURE; + SS_SetUpgradeState(E_SS_IMGBADDELTA); + return E_SS_FAILURE; //ulResult; + } + if (ua_dataSS->ui_progress) + ua_dataSS->ui_progress(ua_dataSS, 40); + + if (update_type == FULL_IMG && ua_dataSS->update_data->ua_temp_path) + ulResult = SS_MoveFile(ua_dataSS, SS_PATCHFILE_SOURCE, ua_dataSS->update_data->ua_temp_path); + else if ((ua_dataSS->update_cfg->update_type == DELTA_IMG && ua_dataSS->write_data_to_blkdev) + || ua_dataSS->update_cfg->update_type == EXTRA) { + + FILE *fp = NULL; + char buf[14] = { 0, }; //to store zImage-delta magic keyword + fp = fopen(SS_PATCHFILE_SOURCE, "r"); + fread(buf, 1, 13, fp); //error check not required as any delta corruption will be caught in SS_UpdateDeltaIMG + fclose(fp); + + if (strcmp(buf, SS_KERNEL_MAGIC) == 0) + ulResult = SS_UpdateDeltaKernel(ua_dataSS, ua_dataSS->write_data_to_blkdev); + else + ulResult = SS_UpdateDeltaIMG(ua_dataSS, ua_dataSS->write_data_to_blkdev); + } + else { + LOGE("Update type is INVALID - Exit \n"); + ulResult = E_SS_FAILURE; + SS_SetUpgradeState(E_SS_BAD_PARAMS); + return E_SS_FAILURE; + } + + if (ulResult == S_SS_SUCCESS) { + if (ua_dataSS->ui_progress) + ua_dataSS->ui_progress(ua_dataSS, 100); + return ulResult; + } else + return SS_GetUpgradeState(); +} diff --git a/ss_engine/SS_UPI.h b/ss_engine/SS_UPI.h new file mode 100755 index 0000000..8d3fabd --- /dev/null +++ b/ss_engine/SS_UPI.h @@ -0,0 +1,86 @@ +/* + * libtota + * + * Copyright (c) 2017 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 _SS_UPI_H_ +#define _SS_UPI_H_ + +#define DISPLAYRESOLUTION_SIZE 50 + +struct details { + int diffs; + int moves; + int news; + int deletes; + int symdiffs; + int symnews; +}; + +enum DEL_TYPE { DIFFS, MOVES, NEWFILES, DELETES, SYMDIFFS, SYMNEWFILES, DELETE_END }; +struct fs_params { // Use Macros + char file_old_path[512]; + char file_new_path[512]; + char patch_name[256]; + char sha1src[64]; + char sha1trg[64]; + int data_size; + int data_offset; + int type; //0 is for diff and 1 is for verbatim + struct fs_params *nextnode; +}; +typedef struct fs_params fs_params; + +struct fs_list { + fs_params *dif_ref; + fs_params *move_ref; + fs_params *new_ref; + fs_params *del_ref; + fs_params *sym_difref; + fs_params *sym_newref; + int ulPatchCount; +}; +typedef struct fs_list fs_list; +struct details *get_fs_details(char *filename); + +int SS_AppendNode(const char *ubDeltaPath, fs_params ** headparam, fs_params ** tailparam, const char *old_path, + const char *new_path, const char *patchname, const char *sha1src, const char *sha1trg, int type, + char *patchpath_name); +extern int SS_IMGUpdatemain(ua_dataSS_t * ua_dataSS, int update_type); +extern int SS_IMGVerfiyPartition(ua_dataSS_t * ua_dataSS); +extern int SS_FSUpdatemain(ua_dataSS_t * ua_dataSS, int part_idx); +extern int SS_FSVerifyPartition(ua_dataSS_t * ua_dataSS, int part_idx); +extern int SS_UpdateDeltaIMG(ua_dataSS_t * ua_dataSS, int (*write_to_blkdev) (char *, int, int, char *)); +extern int SS_UpdateDeltaKernel(ua_dataSS_t * ua_dataSS, int (*write_to_blkdev) (char *, int, int, char *)); + +//extra functions +extern void *SS_Malloc(unsigned int size); +extern int tar_get_item_size_from_struct(tar_Data_t **, const char *, int *, int *); +extern void SS_Free(void *pMemBlock); +extern int tar_get_item_size(char *tar, char *item); +extern int tar_get_cfg_data(char *tar, char *item, char *buf, int buflen); +extern tar_Data_t *tar_build_cfg_table(char *tar); +extern int tar_open(char *tar); +extern int fast_tar_extract_file(char *tar, char *item, char *pathname, int size, int offset); +extern int tar_close(); +extern int SS_UpdateDeltaFS(const char *source_filename, const char *target_filename, + const char *source_sha1_str, const char *target_sha1_str, int patch_data_size); +extern int tar_extract_file(char *tar, char *item, char *pathname); +extern int _7zdecompress(char *path); +extern void tar_free_cfg_table(tar_Data_t ** delta_tar); +extern long SS_GetFileType(void *pbUserData, char *pLinkName, enumFileType * fileType); + +#endif //_SS_UPI_H_ diff --git a/ss_engine/fota_common.h b/ss_engine/fota_common.h new file mode 100755 index 0000000..fa690b6 --- /dev/null +++ b/ss_engine/fota_common.h @@ -0,0 +1,142 @@ +/* + * libtota + * + * Copyright (c) 2017 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 __FOTA_COMMON_H__ +#define __FOTA_COMMON_H__ + +#include +#include "fota_log.h" + +typedef signed char s8; +typedef unsigned char u8; + +typedef signed short s16; +typedef unsigned short u16; + +typedef signed int s32; +typedef unsigned int u32; +typedef u32 size_t; + +typedef signed long sl32; +typedef unsigned long ul32; + +typedef signed long long s64; +typedef unsigned long long u64; + +#ifndef TIME_PROFILING + //#define TIME_PROFILING +#endif +#ifndef MEM_PROFILING + //#define MEM_PROFILING +#endif +#ifndef SUPPORT_CONTAINER + //#define SUPPORT_CONTAINER +#endif + +#define UNUSED(x) (void)(x) + +#define SS_TOTA_VERSION "1.0.18" +#define BSDIFF "BSDIFF40" +#define IMGDIFF "IMGDIFF2" +#define SECTOR_SIZE 512 +#define SS_KERNEL_DELTA_HEADER 128 + +#define SS_COMMON_WORKSPACE "/system/opt/usr/data/fota" +#define SS_KERNEL_UNPACK_SCRIPT "unpack.sh" +#define SS_KERN_UNPK_SCRIPT_PATH SS_COMMON_WORKSPACE "/" SS_KERNEL_UNPACK_SCRIPT +#define SS_BACKUP_SOURCE SS_COMMON_WORKSPACE "/saved.file" //How to make sure there is SPACE +#define SS_PATCHFILE_SOURCE SS_COMMON_WORKSPACE "/patchfile.file" //define in common place +#define SS_PATCHLIST_BKUPLOC SS_COMMON_WORKSPACE "/patchlist.txt" +#define SS_NEW_COMPRESSED_FILE SS_COMMON_WORKSPACE "/system.7z" +#define SS_KERNEL_WORKSPACE SS_COMMON_WORKSPACE "/kernel-work" +#define SS_GZIP_TARGET SS_KERNEL_WORKSPACE "/gzip" +#define SS_STAT_TARGET SS_KERNEL_WORKSPACE "/stat" +#define SS_DD_TARGET SS_KERNEL_WORKSPACE "/dd" + +#define SS_GZIP_SOURCE "system/bin/gzip" +#define SS_STAT_SOURCE "system/usr/bin/stat" +#define SS_DD_SOURCE "system/bin/dd" + +#define SS_KERNEL_MAGIC "UnpackdzImage" +#define SS_KERNEL_NAME "zImage" +#define SS_KERNEL_TARGET_NAME "dzImage_final" +#define SS_KERNEL_UNPACK_DIR SS_KERNEL_NAME "_unpacked" +#define SS_PATCHLISTFOLDER "/p" +#define SS_NEWPATCHFOLDER "/n" +#define SS_PATCHLISTFORMAT ".txt" +#define SS_PATCH_ATTR_FORMAT "_attr.txt" +#define SS_FSCOUNT_MAGIC_KEY "PaTcHCoUnT:" +#define SS_FSCOUNT_MAGIG_KEYLEN (11) //length of SS_FSCOUNT_MAGIC_KEY +#define SS_IMAGE_MAGIC_KEY "TiZSiG@tOtA_00_:.{0,64}" +#define SS_IMAGE_MAGIC_KEY_VAL SS_COMMON_WORKSPACE "/delta_sha.txt" + +#define SS_TOKEN_SPACE " " +#define SS_TOKEN_NEWLINE "\n" +#define SS_TOEKN_COLON ":" +#define SS_CHAR_FWSLASH '/' +#define SS_FWSLASH "/" +#define SS_NULLENTRY "0" +#define SS_MAX_NAMELENSUPPORTED (200) //(Tar supports 256, But extra space is used for PartitionName, .delta, /p, so restricting filename max to 200) +#define SS_MAX_FILE_PATH (512) +#define SS_TOKEN_MAXLINE_LEN (1024) +#define SS_COMPRESSED_FILE "system.7z" + +#define SS_STRING_DIFF "DIFF" +#define SS_STRING_MOVE "MOVE" +#define SS_STRING_DEL "DEL" +#define SS_STRING_SYM "SYM" +#define SS_STRING_NEW "NEW" +#define SS_STRING_REG "REG" +#define SS_STRING_TPK "TPK" +#define SS_STRING_ZIP "ZIP" +#define SS_STRING_END "END" + +#ifdef SUPPORT_CONTAINER +#define SS_ZIP_SOURCE "system/usr/bin/zip" +#define SS_ZIP_TARGET "system/opt/data/fota/zip" +#define SS_UNZIP_SOURCE "system/usr/bin/unzip" +#define SS_UNZIP_TARGET "system/opt/data/fota/unzip" +#define SS_FIND_CMD_SOURCE "system/usr/bin/find" +#define SS_FIND_CMD_TARGET "system/opt/data/fota/find" +#define SS_ZIP_COMMAND "/opt/data/fota/zip" +#define SS_UNZIP_COMMAND "/opt/data/fota/unzip" +#define SS_FIND_COMMAND "/opt/data/fota/find" +#define SS_SEPARATOR_TOKEN "|" +#define SS_NEWLINE_TOKEN "\n" +#define SS_FW_SLASH_TOKEN '/' +#define SS_CONTAINER_INFO_FILE "PATCH.txt" +#define SS_CONTAINER_WORKSPACE SS_COMMON_WORKSPACE "/archive" +#define SS_ARCHIVE_WORK_FOLDER SS_CONTAINER_WORKSPACE "/tpk" +#define SS_ARCHIVE_DELTA_FOLDER SS_CONTAINER_WORKSPACE "/delta" +#define SS_ARCHIVE_UNPACK_FOLDER SS_CONTAINER_WORKSPACE "/unpack" +#endif + +#ifdef MEM_PROFILING +#define SS_MEMORY_USAGE_LOG SS_COMMON_WORKSPACE "/log_memory" +#define SS_MEMORY_PROFILING_SCRIPT SS_COMMON_WORKSPACE "/mem_use.sh" +#endif + +struct tar_Data { + int itemSize; + int itemOffset; + int itemName[256]; + struct tar_Data *nextnode; +}; +typedef struct tar_Data tar_Data_t; + +#endif /* __FOTA_COMMON_H__ */ diff --git a/ss_engine/fota_log.h b/ss_engine/fota_log.h new file mode 100755 index 0000000..9b4999a --- /dev/null +++ b/ss_engine/fota_log.h @@ -0,0 +1,76 @@ +/* + * libtota + * + * Copyright (c) 2017 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 __FOTA_LOG_H__ +#define __FOTA_LOG_H__ + +#include + +/* + * DEBUGGING FEATURE + */ + +extern unsigned int __log_level__; +extern FILE *__log_out_file__; +extern int log_printf(FILE* log_fp, char* format_str, ...); + +#define LOG_INFO (1<<8) +#define LOG_ENGINE (1<<7) +#define LOG_FUNCS (1<<6) +#define LOG_GUI (1<<5) +#define LOG_DEBUG (1<<4) +#define LOG_FILE (1<<3) +#define LOG_FLASH (1<<2) + +#define LOG_SSENGINE LOG_ENGINE +//#define LOG_REDBEND LOG_ENGINE + +//#define DEBUG_STDOUT +#define DEBUG_FILE + +#ifdef DEBUG_STDOUT +#define LOGE(s, args...) printf("UA/ERROR(%s) " s, __func__, ##args) // Error log +#define LOGL(mask, s, args...) do{if((mask) & __log_level__) printf("UA/(%s): " s,__func__, ##args);}while(0) +#define LOG(s, args...) LOGL(LOG_DEBUG, s, ##args) + +#elif defined(DEBUG_FILE) +#define LOGE(s, args...) (void)log_printf(__log_out_file__, "UA/ERROR(%s) " s, __func__, ##args) +#define LOGL(mask, s, args...) do{if((mask) & __log_level__) (void)log_printf(__log_out_file__, "UA/(%s): " s ,__func__, ##args);}while(0) +#define LOG(s, args...) LOGL(LOG_DEBUG, s, ##args) + +#elif defined(DEBUG_STDOUT_FILE) // debug printf +#define LOGE(s, args...) do {\ + printf("UA/ERROR(%s) " s, __func__, ##args);\ + (void)log_printf(__log_out_file__, "UA/ERROR(%s) " s, __func__, ##args);\ + }while(0) +#define LOGL(mask, s, args...) do{ \ + if((mask) & __log_level__){\ + printf("UA/(%s): " s ,__func__, ##args);\ + (void)log_printf(__log_out_file__, "UA/(%s): " s,__func__, ##args);\ + }\ + }while(0) +#define LOG(s, args...) LOGL(LOG_DEBUG, s, ##args) + +#else +#define LOGE(s, args...) +#define LOGL(mask, s, args...) +#define LOG(s, args...) + +#endif + +#endif /* __FOTA_LOG_H__ */ diff --git a/ss_engine/fota_tar.c b/ss_engine/fota_tar.c new file mode 100755 index 0000000..b8bda64 --- /dev/null +++ b/ss_engine/fota_tar.c @@ -0,0 +1,1114 @@ +/* + * libtota + * + * Copyright (c) 2017 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 +#include +#include +#include +#include +#include +#include +#include "SS_Engine_Update.h" +#include "SS_Engine_Errors.h" +#include "fota_common.h" +#include "fota_tar.h" +#include "ua.h" + +/* tar Header Block, from POSIX 1003.1-1990. for reference */ +#if 0 + /* POSIX header. */ + +struct posix_header { /* byte offset */ + char name[100]; /* 0 */ + char mode[8]; /* 100 */ + char uid[8]; /* 108 */ + char gid[8]; /* 116 */ + char size[12]; /* 124 */ + char mtime[12]; /* 136 */ + char chksum[8]; /* 148 */ + char typeflag; /* 156 */ + char linkname[100]; /* 157 */ + char magic[6]; /* 257 */ + char version[2]; /* 263 */ + char uname[32]; /* 265 */ + char gname[32]; /* 297 */ + char devmajor[8]; /* 329 */ + char devminor[8]; /* 337 */ + char prefix[155]; /* 345 */ + /* 500 */ +}; +#endif + +#define MAX_ITEM_SIZE 0x0FFFFFFF +#define TAR_ITEM_SIZE_POSITION 124 +#define TAR_SIZE_OF_ITEM_SIZE 8 +#define TAR_SIZE_OF_HEADER 12 +#define TAR_BLOCK_SIZE 512 +#define TAR_ITEM_NAME_SIZE 100 +#define TAR_LONG_NAME_SIZE 256 +#define TAR_ITEM_TYPE_FLAG_POS 156 + + /*** The byte that indicates whether the prefix is present or not */ +#define PREFIX_INDICATOR_BYTE 345 +#define PREFIX_LEN 155 + +/** the rest heavily based on (ie mostly) untgz.c from zlib **/ + +/* Values used in typeflag field. */ + +#define REGTYPE '0' /* regular file */ +#define AREGTYPE '\0' /* regular file */ +#define LNKTYPE '1' /* link */ +#define SYMTYPE '2' /* reserved */ +#define CHRTYPE '3' /* character special */ +#define BLKTYPE '4' /* block special */ +#define DIRTYPE '5' /* directory */ +#define FIFOTYPE '6' /* FIFO special */ +#define CONTTYPE '7' /* reserved, for compatibility with gnu tar, + treat as regular file, where it represents + a regular file, but saved contiguously on disk */ + +/* GNU tar extensions */ + +#define GNUTYPE_DUMPDIR 'D' /* file names from dumped directory */ +#define GNUTYPE_LONGLINK 'K' /* long link name */ +#define GNUTYPE_LONGNAME 'L' /* long file name */ +#define GNUTYPE_MULTIVOL 'M' /* continuation of file from another volume */ +#define GNUTYPE_NAMES 'N' /* file name that does not fit into main hdr */ +#define GNUTYPE_SPARSE 'S' /* sparse file */ +#define GNUTYPE_VOLHDR 'V' /* tape/volume header */ + +extern void *SS_Malloc(SS_UINT32 size); + +int gTarFd = -1; // Currenlty this logic supports only one tar file + +/* Parse an octal number, ignoring leading and trailing nonsense. */ +static int parseoct(const char *p, size_t n) +{ + int i = 0; + + while (*p < '0' || *p > '7') { + ++p; + --n; + } + while (*p >= '0' && *p <= '7' && n > 0) { + i *= 8; + i += *p - '0'; + ++p; + --n; + } + return (i); +} + +/* Verify the tar checksum. */ +static int verify_checksum(const char *p) +{ + int n, u = 0; + for (n = 0; n < 512; ++n) { + if (n < 148 || n > 155) + /* Standard tar checksum adds unsigned bytes. */ + u += ((unsigned char *)p)[n]; + else + u += 0x20; + + } + return (u == parseoct(p + 148, 8)); +} + +static int is_end_of_archive(const char *p) +{ + int n; + for (n = 511; n >= 0; --n) + if (p[n] != '\0') + return (0); + return (1); +} + +void create_dir(char *pathname, int mode) +{ + char *p; + int r; + + /* Strip trailing '/' */ + if (pathname[strlen(pathname) - 1] == '/') + pathname[strlen(pathname) - 1] = '\0'; + + /* Try creating the directory. */ + r = mkdir(pathname, mode); + + if (r != 0) { + /* On failure, try creating parent directory. */ + p = strrchr(pathname, '/'); + if (p != NULL) { + *p = '\0'; + create_dir(pathname, 0755); + *p = '/'; + r = mkdir(pathname, mode); + } + } + if (r != 0) { + if (r != EEXIST && r != -1) + LOG("Could not create directory [%s] Error[%d]\n", pathname, r); + } +} + +/* Create a file, including parent directory as necessary. */ +static FILE *create_file(char *pathname, int mode) +{ + FILE *f; + f = fopen(pathname, "w+"); + if (f == NULL) { + /* Try creating parent dir and then creating file. */ + char *p = strrchr(pathname, '/'); + if (p != NULL) { + *p = '\0'; + create_dir(pathname, 0755); + *p = '/'; + f = fopen(pathname, "w+"); + } + } + return (f); +} + +/*----------------------------------------------------------------------------- + tar_get_item_offset + ----------------------------------------------------------------------------*/ +int tar_get_item_offset(char *tar, char *item) +{ + int ret = -1; + int fd = -1; + char header[TAR_BLOCK_SIZE] = { 0, }; + char name[TAR_ITEM_NAME_SIZE + 1] = { 0, }; + char size_oct[TAR_SIZE_OF_HEADER] = { 0, }; + int size_dec = 0; + int blknum = 0; + off_t pos = 0; + off_t tar_len = 0; + ssize_t rdcnt = 0; + + //check if gTarFd was opened by tar_open during SS_FSUpdateFile then use it + if (gTarFd > 0) + fd = gTarFd; + if (fd < 0) { + fd = open(tar, O_RDONLY); + if (fd < 0) { + LOGE("can't open file(%s).\n", tar); + return -1; + } + } + + tar_len = lseek(fd, 0, SEEK_END); + if (tar_len < 0) { + LOGL(LOG_SSENGINE, "can't read tar_len (%s).\n", tar); + goto Cleanup; + } + pos = lseek(fd, 0, SEEK_SET); + if (pos < 0) { + LOGL(LOG_SSENGINE, "can't read pos (%s).\n", tar); + goto Cleanup; + } + while (pos < tar_len) { + /* read file header */ + rdcnt = read(fd, header, sizeof(header)); + if (rdcnt <= 0) { + LOG("read failed. (rdcnt=%d)\n", rdcnt); + break; + } + + /* get file name and file size */ + memcpy(name, header, sizeof(name) - 1);//wgid: 24572 + memcpy(size_oct, header + TAR_ITEM_SIZE_POSITION, sizeof(size_oct)); + size_dec = strtoul(size_oct, NULL, TAR_SIZE_OF_ITEM_SIZE); + if (size_dec > MAX_ITEM_SIZE) { + LOG("size too big. (size_dec=0x%08X)\n", size_dec); + break; + } + + /* check if the file is what we are looking for */ + if (strncmp(name, item, TAR_ITEM_NAME_SIZE) == 0) { + ret = (int)lseek(fd, 0, SEEK_CUR); + break; + } + + /* move file pointer to next file header */ + blknum = size_dec / TAR_BLOCK_SIZE; + if (size_dec % TAR_BLOCK_SIZE) + blknum++; + + pos = lseek(fd, (off_t) (blknum * TAR_BLOCK_SIZE), SEEK_CUR); + if (pos < 0) { + LOGE("can't read next block (%s).\n", tar); + close(fd); + return -1; + } + } + + Cleanup: + if (gTarFd < 0) + close(fd); + + return ret; +} + +/*----------------------------------------------------------------------------- + tar_get_item_size + ----------------------------------------------------------------------------*/ +int tar_get_item_size(char *tar, char *item) +{ + int ret = -1; + int fd = -1; + char header[TAR_BLOCK_SIZE] = { 0, }; + char uExtendedName[MAX_FILE_PATH] = { 0, }; + char size_oct[TAR_SIZE_OF_HEADER] = { 0, }; + unsigned long size_dec = 0; + int blknum = 0; + off_t pos = 0; + off_t tar_len = 0; + ssize_t rdcnt = 0; + LOGL(LOG_SSENGINE, "Tar file Looking for (%s)\n", item); + fd = open(tar, O_RDONLY); + if (fd < 0) { + LOG("can't open file(%s).\n", tar); + return -1; + } + + tar_len = lseek(fd, 0, SEEK_END); + if (tar_len < 0) { + LOGL(LOG_SSENGINE, "can't read tar_len (%s).\n", tar); + goto Cleanup; + } + pos = lseek(fd, 0, SEEK_SET); + if (pos < 0) { + LOGL(LOG_SSENGINE, "can't read pos (%s).\n", tar); + goto Cleanup; + } + + while (pos < tar_len) { + /* read file header */ + rdcnt = read(fd, header, sizeof(header)); + if (rdcnt <= 0) { + LOG("read failed. (rdcnt=%d)\n", rdcnt); + break; + } + + /* get file name and file size */ + if (header[TAR_ITEM_TYPE_FLAG_POS] == GNUTYPE_LONGNAME || header[TAR_ITEM_TYPE_FLAG_POS] == GNUTYPE_LONGLINK) { + //rdcnt = read(fd, header, sizeof(header)); + memset(uExtendedName, 0, sizeof(uExtendedName)); + rdcnt = read(fd, uExtendedName, sizeof(uExtendedName)); + if (rdcnt <= 0) { + LOG("read failed. (rdcnt=%d)\n", rdcnt); + break; + } + rdcnt = read(fd, header, sizeof(header)); + if (rdcnt <= 0) { + LOG("read failed. (rdcnt=%d)\n", rdcnt); + break; + } + } else { + memset(uExtendedName, 0, sizeof(uExtendedName)); + memcpy(uExtendedName, header, TAR_ITEM_NAME_SIZE); + } + //memcpy(name, header, sizeof(name)); + memcpy(size_oct, header + TAR_ITEM_SIZE_POSITION, sizeof(size_oct)); + size_dec = strtoul(size_oct, NULL, TAR_SIZE_OF_ITEM_SIZE); + if (size_dec > MAX_ITEM_SIZE) { + LOG("size too big. (size_dec=0x%08X)\n", (unsigned int)size_dec); + break; + } + + /* check if the file is what we are looking for */ + if (strcmp(uExtendedName, item) == 0) { + ret = (int)size_dec; + if ((ret == 0) && (header[TAR_ITEM_TYPE_FLAG_POS] == DIRTYPE)) + ret = tar_get_folder_size(tar, item); + break; + } + /* move file pointer to next file header */ + //LOGL(LOG_SSENGINE,"Item in Tar (%s)\n", uExtendedName); + blknum = size_dec / TAR_BLOCK_SIZE; + if (size_dec % TAR_BLOCK_SIZE) + blknum++; + + pos = lseek(fd, (off_t) (blknum * TAR_BLOCK_SIZE), SEEK_CUR); + if (pos < 0) { + LOGL(LOG_SSENGINE, "can't read next block (%s).\n", tar); + close(fd); + return -1; + } + } + + Cleanup: + close(fd); + + return ret; +} + +/*----------------------------------------------------------------------------- + tar_get_item_tye. (Dir/file/link etc) + ----------------------------------------------------------------------------*/ + +char tar_get_item_type(char *tar, char *item) +{ + char ret = '0'; + int fd = -1; + char header[TAR_BLOCK_SIZE] = { 0, }; + char name[TAR_ITEM_NAME_SIZE + 1] = { 0, }; + char size_oct[TAR_SIZE_OF_HEADER] = { 0, }; + unsigned long size_dec = 0; + int blknum = 0; + off_t pos = 0; + off_t tar_len = 0; + ssize_t rdcnt = 0; + //LOG("Tar file Looking for (%s)\n", item); + fd = open(tar, O_RDONLY); + if (fd < 0) { + LOG("can't open file(%s).\n", tar); + return -1; + } + + tar_len = lseek(fd, 0, SEEK_END); + if (tar_len < 0) { + LOGL(LOG_SSENGINE, "can't read tar_len (%s).\n", tar); + goto Cleanup; + } + pos = lseek(fd, 0, SEEK_SET); + if (pos < 0) { + LOGL(LOG_SSENGINE, "can't read pos (%s).\n", tar); + goto Cleanup; + } + + while (pos < tar_len) { + /* read file header */ + rdcnt = read(fd, header, sizeof(header)); + if (rdcnt <= 0) { + LOG("read failed. (rdcnt=%d)\n", rdcnt); + ret = -1; + break; + } + + /* get file name and file size */ + memcpy(name, header, sizeof(name) - 1);//wgid: 24573 + memcpy(size_oct, header + TAR_ITEM_SIZE_POSITION, sizeof(size_oct)); + size_dec = strtoul(size_oct, NULL, TAR_SIZE_OF_ITEM_SIZE); + if (size_dec > MAX_ITEM_SIZE) { + LOG("size too big. (size_dec=0x%08X)\n", (unsigned int)size_dec); + ret = -1; + break; + } + + /* check if the file is what we are looking for */ + if (strncmp(name, item, TAR_ITEM_NAME_SIZE) == 0) { + ret = header[TAR_ITEM_TYPE_FLAG_POS]; + break; + } + + /* move file pointer to next file header */ + blknum = size_dec / TAR_BLOCK_SIZE; + if (size_dec % TAR_BLOCK_SIZE) + blknum++; + + pos = lseek(fd, (off_t) (blknum * TAR_BLOCK_SIZE), SEEK_CUR); + if (pos < 0) { + LOGL(LOG_SSENGINE, "can't read next block (%s).\n", tar); + close(fd); + return -1; + } + } + + Cleanup: + close(fd); + + return ret; +} + +/*----------------------------------------------------------------------------- + tar_get_cfg_data + ----------------------------------------------------------------------------*/ +int tar_get_cfg_data(char *tar, char *item, char *buf, int buflen) +{ + int fd = -1; + int data_size = -1; + int data_offset = -1; + off_t pos = 0; + ssize_t rdcnt = 0; + + data_size = tar_get_item_size(tar, item); + if (data_size <= 0) { + return -1; + } + + if (data_size > buflen) { + data_size = buflen; + } + + data_offset = tar_get_item_offset(tar, item); + if (data_offset < 0) { + return -1; + } + + fd = open(tar, O_RDONLY); + if (fd < 0) { + LOG("can't open file(%s).\n", tar); + return -1; + } + + pos = lseek(fd, data_offset, SEEK_SET); + if (pos < 0) { + LOG("lseek fail (%s offset %d).\n", tar, data_offset); + close(fd); + return -1; + } + + rdcnt = read(fd, buf, data_size); + if (rdcnt != (ssize_t) data_size) { + LOG("read fail(%s from %s).\n", item, tar); + close(fd); + return -1; + } + + close(fd); + + return rdcnt; +} + +tar_Data_t *tar_build_cfg_table(char *tar) +{ + + int fd = -1; + int ret = 0; + char header[TAR_BLOCK_SIZE] = { 0, }; + char uExtendedName[MAX_FILE_PATH] = { 0, }; + char size_oct[TAR_SIZE_OF_HEADER] = { 0, }; + unsigned long size_dec = 0; + int blknum = 0; + off_t pos = 0; + off_t tar_len = 0; + ssize_t rdcnt = 0; + + int itemSize; + int itemOffset; + tar_Data_t *headparam = NULL, *tailparam = NULL, *newnode = NULL; + tar_Data_t *local_temp = NULL; + tar_Data_t *local_next = NULL; + if (!tar) { + LOGE("Bad param tar\n"); + return NULL; + } + //check if gTarFd was opened by tar_open during SS_FSUpdateFile then use it + if (gTarFd > 0) + fd = gTarFd; + if (fd < 0) { + fd = open(tar, O_RDONLY); + if (fd < 0) { + LOG("can't open file(%s).\n", tar); + return NULL; + } + } + tar_len = lseek(fd, 0, SEEK_END); + if (tar_len < 0) { + LOGL(LOG_SSENGINE, "can't read tar_len (%s).\n", tar); + goto Cleanup; + } + pos = lseek(fd, 0, SEEK_SET); + if (pos < 0) { + LOGL(LOG_SSENGINE, "can't read pos (%s).\n", tar); + goto Cleanup; + } + while (pos < tar_len) { + /* read file header */ + rdcnt = read(fd, header, sizeof(header)); + if (rdcnt <= 0) { + LOG("read failed. (rdcnt=%d)\n", rdcnt); + ret = -1; + break; + } + /* get file name and file size */ + //memcpy(name, header, sizeof(name)); + if (header[TAR_ITEM_TYPE_FLAG_POS] == GNUTYPE_LONGNAME || header[TAR_ITEM_TYPE_FLAG_POS] == GNUTYPE_LONGLINK) { + //rdcnt = read(fd, header, sizeof(header)); + memset(uExtendedName, 0, sizeof(uExtendedName)); + rdcnt = read(fd, uExtendedName, sizeof(uExtendedName)); + if (rdcnt <= 0) { + LOG("read failed. (rdcnt=%d)\n", rdcnt); + ret = -1; + break; + } + rdcnt = read(fd, header, sizeof(header)); + if (rdcnt <= 0) { + LOG("read failed. (rdcnt=%d)\n", rdcnt); + ret = -1; + break; + } + } else { + memset(uExtendedName, 0, sizeof(uExtendedName)); + memcpy(uExtendedName, header, TAR_ITEM_NAME_SIZE); + } + memcpy(size_oct, header + TAR_ITEM_SIZE_POSITION, sizeof(size_oct)); + size_dec = strtoul(size_oct, NULL, TAR_SIZE_OF_ITEM_SIZE); + if (size_dec > MAX_ITEM_SIZE) { + LOG("size too big. (size_dec=0x%08X)\n", (unsigned int)size_dec); + ret = -1; + break; + } + //fix WGID : 51254 , size_dec comparison is not required + if ((strstr(uExtendedName, "/diff") != NULL)) //add only delta files from rootfs and csc, hardcoding shd b removed.. + { + + /* check if the file is what we are looking for */ + //strncpy(itemName, name,100); + itemSize = (int)size_dec; + itemOffset = (int)lseek(fd, 0, SEEK_CUR); + newnode = (tar_Data_t *) SS_Malloc(sizeof(tar_Data_t)); + if (!newnode) { + ret = -1; + break; + } + strncpy((char *)newnode->itemName, uExtendedName, sizeof(newnode->itemName)); + newnode->itemOffset = itemOffset; + newnode->itemSize = itemSize; + newnode->nextnode = NULL; + if (headparam == NULL) { + headparam = newnode; + tailparam = newnode; + } else { + (tailparam)->nextnode = newnode; + (tailparam) = (tailparam)->nextnode; + } + } + + /* move file pointer to next file header */ + blknum = size_dec / TAR_BLOCK_SIZE; + if (size_dec % TAR_BLOCK_SIZE) + blknum++; + + pos = lseek(fd, (off_t) (blknum * TAR_BLOCK_SIZE), SEEK_CUR); + if (pos < 0) { + LOGL(LOG_SSENGINE, "can't read next block (%s).\n", tar); + ret = -1; + break; + } + } + Cleanup: + //if gTarFd was opened by tar_open during SS_FSUpdateFile we do not close it + if (gTarFd < 0) + close(fd); + if (ret != -1) + return headparam; + else { + if (newnode) + SS_Free(newnode); + if (headparam) { + local_temp = headparam; + while (local_temp) { + local_next = local_temp->nextnode; + SS_Free(local_temp); + local_temp = local_next; + } + } + return NULL; + } +} + +void tar_free_cfg_table(tar_Data_t ** delta_tar) +{ + tar_Data_t *local_temp = NULL; + tar_Data_t *local_next = NULL; + LOGL(LOG_SSENGINE, "Free TAR CFG TABLE\n"); + if (*delta_tar) { + local_temp = *delta_tar; + while (local_temp) { + local_next = local_temp->nextnode; + //LOGL(LOG_SSENGINE,"freeing [%s]\n",local_temp->itemName); + SS_Free(local_temp); + local_temp = local_next; + } + } +} + +void deleteNode(tar_Data_t * head, tar_Data_t * n) +{ + tar_Data_t *prev = head; + if (head == n) { + if (head->nextnode == NULL) { + LOG("There is only one node. The list can't be made empty "); + return; + } + strncpy((char *)head->itemName, (const char *)head->nextnode->itemName, TAR_ITEM_NAME_SIZE); //head->itemName = head->nextnode->itemName; + head->itemSize = head->nextnode->itemSize; + head->itemOffset = head->nextnode->itemOffset; + n = head->nextnode; + head->nextnode = head->nextnode->nextnode; + SS_Free(n); + return; + } + while (prev->nextnode != NULL && prev->nextnode != n) + prev = prev->nextnode; + if (prev->nextnode == NULL) { + LOG("\n Given node is not present in Linked List"); + return; + } + prev->nextnode = prev->nextnode->nextnode; + SS_Free(n); + return; +} + +int tar_get_item_size_from_struct(tar_Data_t ** delta_tar, const char *patchname, int *data_size, int *data_offset) +{ + tar_Data_t *head = *delta_tar; + tar_Data_t *base = *delta_tar; + if (head == NULL) + return 1; + else { + //LOG("fast_tar_get_item_size- looking for [%s] [%s]\n",patchname,head->itemName); + while (1) { + if (strstr((const char *)head->itemName, patchname) != 0) { + //LOG("fast_tar_get_item_size found [%s] in [%s]\n",patchname, head->itemName); + *data_size = head->itemSize; + *data_offset = head->itemOffset; + deleteNode(base, head); + return 0; + + } else if (head->nextnode != NULL) { + head = head->nextnode; + //LOG("fast_tar_get_item_size current node [%s] \n",head->itemName); + } else { + LOGE("fast_tar_get_item_size FAILED TO GET [%s] in [%s]\n", patchname, (char *)head->itemName); + break; + } + } + return 1; + } +} + +tar_Data_t *tar_cfg_clear_nodes(tar_Data_t * head) +{ + tar_Data_t *local_temp = NULL; + while (head) { + LOGL(LOG_SSENGINE, "tar_cfg_delete_node [%s]", (char *)head->itemName); + local_temp = head->nextnode; + SS_Free(head); + head = local_temp; + } + return 0; +} + +int tar_open(char *tar) +{ + if (gTarFd) + close(gTarFd); + gTarFd = open(tar, O_RDONLY); + if (gTarFd < 0) { + LOG("can't open TAR file(%s).\n", tar); + return -1; + } + return 0; +} + +int tar_close() +{ + if (gTarFd) + close(gTarFd); + gTarFd = -1; + return 0; +} + +int tar_get_folder_size(char *tar, char *item) +{ + int ret = -1; + int fd = -1; + char header[TAR_BLOCK_SIZE] = { 0, }; + char name[TAR_LONG_NAME_SIZE + 1] = { 0, }; + char *lastfolder = NULL; + int folderpathlen = 0; + char size_oct[TAR_SIZE_OF_HEADER] = { 0, }; + unsigned long size_dec = 0; + int blknum = 0; + off_t pos = 0; + off_t tar_len = 0; + ssize_t rdcnt = 0; + LOG("Tar folder Looking for (%s)\n", item); + fd = open(tar, O_RDONLY); + if (fd < 0) { + LOG("can't open file(%s).\n", tar); + return -1; + } + + tar_len = lseek(fd, 0, SEEK_END); + if (tar_len < 0) { + LOGL(LOG_SSENGINE, "can't read tar_len (%s).\n", tar); + goto Cleanup; + } + pos = lseek(fd, 0, SEEK_SET); + if (pos < 0) { + LOGL(LOG_SSENGINE, "can't read pos (%s).\n", tar); + goto Cleanup; + } + + while (pos < tar_len) { + /* read file header */ + rdcnt = read(fd, header, sizeof(header)); + if (rdcnt <= 0) { + LOG("read failed. (rdcnt=%d)\n", rdcnt); + ret = -1; + break; + } + + /* get file name and file size */ + memcpy(name, header, sizeof(name) - 1);//wgid: 24574 + memcpy(size_oct, header + TAR_ITEM_SIZE_POSITION, sizeof(size_oct)); + size_dec = strtoul(size_oct, NULL, TAR_SIZE_OF_ITEM_SIZE); + if (size_dec > MAX_ITEM_SIZE) { + LOG("size too big. (size_dec=0x%08X)\n", (unsigned int)size_dec); + ret = -1; + break; + } + + /* check if the file is what we are looking for */ + //Get until folder name + + lastfolder = strrchr(name, '/'); + if (lastfolder) + folderpathlen = strlen(name) - strlen(lastfolder); + + if (strncmp(name, item, folderpathlen) == 0) { + ret += (int)size_dec; + //LOG("Tar Files under folder [%s]\n", name); + //break; + } + + /* move file pointer to next file header */ + blknum = size_dec / TAR_BLOCK_SIZE; + if (size_dec % TAR_BLOCK_SIZE) + blknum++; + + pos = lseek(fd, (off_t) (blknum * TAR_BLOCK_SIZE), SEEK_CUR); + if (pos < 0) { + LOGL(LOG_SSENGINE, "can't read next block (%s).\n", tar); + close(fd); + return -1; + } + } + + Cleanup: + close(fd); + LOG("ret=%d\n", ret); + + return ret; //Should return +1?? or Ignore?? +} + +/*Extract Specific Folder from tar, Taken from Untar.c */ +int tar_extract_folder(char *tar, char *item, char *path) +{ + char buff[MAX_FILE_PATH]; + FILE *f = NULL; + size_t bytes_read; + int filesize; + int data_offset = -1; + int fd = -1; + char name[512] = { 0, }; + int folderpathlen = 0; + char dirPath[512] = { 0 }; + int getheader = 1; // Asuming initial header is TAR header + char fullname[512] = { 0 }; + LOG("Extracting Folder from %s %s to %s\n", tar, item, path); + + data_offset = tar_get_item_offset(tar, item); + if (data_offset < 0) { + LOGE("data offset for [%s] is [%d]\n", item, data_offset); + return -1; + } + + fd = open(tar, O_RDONLY); + if (fd < 0) { + LOGE("can't open file(%s).\n", tar); + return -1; + } + + folderpathlen = strlen(item); + + for (;;) { + bytes_read = read(fd, buff, sizeof(buff)); + if (bytes_read < 512) { + LOGE("Short read on %s: expected 512, got %d\n", tar, bytes_read); + close(fd); + return -1; + } + if (is_end_of_archive(buff)) { + close(fd); + LOG("End of %s\n", tar); //Can stop at end of folder. + return S_SS_SUCCESS; + } + if (!verify_checksum(buff)) { + close(fd); + LOGE("Checksum failure\n"); + return -1; + } + filesize = parseoct(buff + 124, 12); + if (getheader == 2) { + getheader = 1; + //LOG(" Working on LONG FILE NAME CASE [%s]\n", fullname); + } else { + memset(fullname, 0, sizeof(fullname)); + strncpy(fullname, buff, 100); + //LOG(" Working on Normal FILE NAME CASE [%s]\n", fullname); + } + + switch (buff[156]) { + case '1': + LOG(" Ignoring hardlink %s\n", fullname); + break; + case '2': + + //LOG(" Creating symlink %s\n", buff); + if (strncmp(fullname, item, folderpathlen) == 0) { + //LOG("Printing Buffer \n"); + //for(i=157; buff[i] !='\0' ;i++) + //{ + //LOG("%c", buff[i]) ; + //} + //LOG("\nEnd buffer\n"); + memset(name, 0, sizeof(name)); + strncpy(name, buff + 157, 100); //157, target link name will be present + memset(dirPath, 0, sizeof(dirPath)); + sprintf(dirPath, "%s/%s", path, fullname + folderpathlen); + LOG(" Creating Symlink [%s][%s]\n", name, dirPath); + symlink(name, dirPath); // use ss_link + } + break; + case '3': + LOG(" Ignoring character device %s\n", fullname); + break; + case '4': + LOG(" Ignoring block device %s\n", fullname); + break; + case '5': + //break;//delete + //LOG(" Dir [%s] Item [%s] Length [%d]\n", fullname, item, folderpathlen); + if (strncmp(fullname, item, folderpathlen) == 0) { + //LOG(" Extracting dir %s\n", fullname); + memset(dirPath, 0, sizeof(dirPath)); + sprintf(dirPath, "%s/%s", path, fullname + folderpathlen); + create_dir(dirPath, parseoct(fullname + 100, 8)); + } + + filesize = 0; + break; + case '6': + LOG(" Ignoring FIFO %s\n", fullname); + break; + case GNUTYPE_LONGLINK: + case GNUTYPE_LONGNAME: + { + getheader = 2; + memset(fullname, 0, sizeof(fullname)); + bytes_read = read(fd, fullname, sizeof(fullname)); + if (bytes_read < 512) { + LOGE("Short read on %s: expected 512, got %d\n", tar, bytes_read); + close(fd); + return -1; + } + filesize = 0; + //LOG("Entered LONG FILE NAME CASE new NAME is [%s]\n", fullname); + break; + } + + default: + //break; + //LOG(" File [%s] Item [%s] Length [%d]\n", fullname, item, folderpathlen); + if (strncmp(fullname, item, folderpathlen) == 0) { + if (buff[PREFIX_INDICATOR_BYTE] != 0) { + memset(name, 0, sizeof(name)); + memset(dirPath, 0, sizeof(dirPath)); + strncpy(name, buff, 100); + strcat(name, buff + PREFIX_INDICATOR_BYTE); + sprintf(dirPath, "%s/%s", path, name + folderpathlen); + LOG(" File Name is longer than 100 bytes -Remaining Str [%s]\n Full Str[%s]", dirPath); + } else { + //LOG(" Extracting file %s\n", fullname); + memset(dirPath, 0, sizeof(dirPath)); + sprintf(dirPath, "%s/%s", path, fullname + folderpathlen); + f = create_file(dirPath, parseoct(fullname + 100, 8)); + } + } + + break; + } + + while (filesize > 0) { + bytes_read = read(fd, buff, sizeof(buff)); + if (bytes_read < 512) { + LOGE("Short read on %s: Expected 512, got %d\n", tar, bytes_read); + close(fd); + if(f){ + fclose(f);//wgid: 16892 + f = NULL; + } + return -1; + } + if (filesize < 512) + bytes_read = filesize; + if (f != NULL) { + if (fwrite(buff, 1, bytes_read, f) + != bytes_read) { + LOG("Failed write\n"); + fclose(f); + f = NULL; + close(fd);//wgid: 59268 + return -1;//wgid: 16892 + } + } + filesize -= bytes_read; + } + if (f != NULL) { + fclose(f); + f = NULL; + } + + } + close(fd); + return S_SS_SUCCESS; +} + +int fast_tar_extract_file(char *tar, char *item, char *pathname, int size, int offset) +{ + int fd = -1; + int data_size = size; + int data_offset = offset; + off_t pos = 0; + ssize_t rdcnt = 0; + ssize_t writeCount = 0; + char *buf = NULL; + int fd2; + + if (gTarFd > 0) + fd = gTarFd; + if (fd < 0) { + fd = open(tar, O_RDONLY); + if (fd < 0) { + LOG("can't open file(%s).\n", tar); + return -1; + } + } + pos = lseek(fd, data_offset, SEEK_SET); + if (pos < 0) { + LOG("lseek fail (%s offset %d).\n", tar, data_offset); + close(fd); + return -1; + } + buf = SS_Malloc(data_size + 1); + if (buf == NULL) { + close(fd); + LOGE("Failed to Allocate Memory\n"); + return -1; + } + rdcnt = read(fd, buf, data_size); + if (rdcnt != (ssize_t) data_size) { + LOG(" rdcnt read fail(%s from %s).\n", item, tar); + SS_Free(buf); + close(fd); + return -1; + } + fd2 = open(pathname, O_CREAT | O_WRONLY, 0777); // Directory where file is required should be created already. + if (fd2 < 0) { + LOG("can't open file(%s).\n", pathname); + SS_Free(buf); + close(fd); + return -1; + } + writeCount = write(fd2, buf, rdcnt); + if (writeCount != rdcnt) { + LOG("writeCount write fail(%s from %s).\n", item, tar); + LOG("Oh dear, something went wrong with read()! %s\n", strerror(errno)); + close(fd); + close(fd2); + SS_Free(buf); + return -1; + } + SS_Free(buf); + if (gTarFd < 0) + close(fd); + fsync(fd2); + close(fd2); + return rdcnt; // or jus return success? +} + +int tar_extract_file(char *tar, char *item, char *pathname) +{ + int fd = -1; + int data_size = -1; + int data_offset = -1; + off_t pos = 0; + ssize_t rdcnt = 0; + ssize_t writeCount = 0; + char *buf = NULL; + int fd2; + data_size = tar_get_item_size(tar, item); + data_offset = tar_get_item_offset(tar, item); + + if (data_size <= 0 || data_offset < 0) { + LOGE("Error Not a file , size is [%d], offset [%d] for item [%s]\n", data_size, data_offset, item); + return -1; + } else + LOGL(LOG_SSENGINE, "extracting file [%s] size [%d]\n", item, data_size); + fd = open(tar, O_RDONLY); + if (fd < 0) { + LOG("can't open file(%s).\n", tar); + return -1; + } + pos = lseek(fd, data_offset, SEEK_SET); + if (pos < 0) { + LOG("lseek fail (%s offset %d).\n", tar, data_offset); + close(fd); + return -1; + } + buf = SS_Malloc(data_size + 1); + if (buf == NULL) { + close(fd); + LOGE("Failed to Allocate Memory\n"); + return -1; + } + rdcnt = read(fd, buf, data_size); + if (rdcnt != (ssize_t) data_size) { + LOG(" rdcnt read fail(%s from %s).\n", item, tar); + SS_Free(buf); + close(fd); + return -1; + } + fd2 = open(pathname, O_CREAT | O_WRONLY, 0777); // Directory where file is required should be created already. + if (fd2 < 0) { + LOG("can't open file(%s).\n", pathname); + SS_Free(buf); + close(fd); + return -1; + } + writeCount = write(fd2, buf, rdcnt); + if (writeCount != rdcnt) { + LOG("writeCount write fail(%s from %s).\n", item, tar); + LOG("Oh dear, something went wrong with read()! %s\n", strerror(errno)); + close(fd); + close(fd2); + SS_Free(buf); + return -1; + } + SS_Free(buf); + close(fd); + fsync(fd2); + close(fd2); + return rdcnt; // or jus return success? +} diff --git a/ss_engine/fota_tar.h b/ss_engine/fota_tar.h new file mode 100755 index 0000000..e6a528f --- /dev/null +++ b/ss_engine/fota_tar.h @@ -0,0 +1,31 @@ +/* + * libtota + * + * Copyright (c) 2017 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 _FOTA_TAR_H_ +#define _FOTA_TAR_H_ + +extern void SS_Free(void *pMemBlock); +extern int tar_get_item_offset(char *tar, char *item); + +extern int tar_get_item_size(char *tar, char *item); + +extern int tar_get_cfg_data(char *tar, char *item, char *buf, int buflen); + +extern int tar_get_folder_size(char *tar, char *item); + +#endif /* _FOTA_TAR_H_ */ diff --git a/ss_engine/ua.h b/ss_engine/ua.h new file mode 100755 index 0000000..7d1e73c --- /dev/null +++ b/ss_engine/ua.h @@ -0,0 +1,125 @@ +/* + * libtota + * + * Copyright (c) 2017 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 __UA_H__ +#define __UA_H__ + +#include + +#define MAX_FILE_PATH 512 +#define MAX_PART_NAME 32 + +/* + * FOTA Adaptaion header + */ + +#define ERROR -1 // 0XFFFFFFFF + +#define INVALID 0 +#define VALID 1 +#define TRUE 1 +#define FALSE 0 +#define OK 0 +#define SUCCESS 0 +#define FAIL 1 +#define RESTORING 2 +#define UA_PARTI_MAX 20 //currently supporting max 20 partitions +#define DEFAULT_DELTA_NAME "delta.tar" +#define UPDATTE_CFG_FILE "update.cfg" + +#define UI_OP_SCOUT_UPDATE 0 +#define UI_OP_SCOUT 1 +#define UI_OP_UPDATE 3 +//SS_ENGINE && BSDIFF +#define SS_RECOVERYRAMDISK + + +typedef enum { + FULL_IMG, + DELTA_IMG, + DELTA_FS, + EXTRA +} UA_DATA_FORMAT; + +typedef enum { + UA_MODE_SCOUT_UPDATE, + UA_MODE_SCOUT, + UA_MODE_VERIFYTARGET, + UA_MODE_UPDATE, + UA_MODE_SUPPLYIMFOM = 200 +} UA_OPERATION_MODE; + +typedef struct _ua_update_data_t { + unsigned int exist_check; + unsigned int verify_check; + unsigned int update_check; + unsigned int weight; // the sum of weight should be 100 + unsigned int weight_offset; // start offset + unsigned int data_size; // byte + char *ua_delta_path; // it will be allocated to copy delta file path, need to free memory + char *ua_temp_path; // it will be allocated to copy delta file path, need to free memory +} ua_update_data_t; + +typedef struct _ua_update_cfg_t { + unsigned int update_type; + unsigned int part_idx; + int skip_verify; + int skip_update; + int soure_img_size; //TOTA + int target_img_size; + char *soure_sha1; + char *target_sha1; +} ua_update_cfg_t; + +typedef struct _ua_part_info_t { + char *ua_parti_name; + char *ua_subject_name; + char *ua_blk_name; + int ua_blk_offset; +} ua_part_info_t; + +// User data structure +typedef struct _ua_data_t { // partition operations + ua_part_info_t *parti_info; + ua_update_cfg_t *update_cfg; + ua_update_data_t *update_data; + unsigned long ua_operation; + + int (*ua_op_read_block) (void *, unsigned char *, unsigned long, unsigned long); + int (*ua_op_write_block) (void *, unsigned char *, unsigned long); + void (*ui_progress) (void *, unsigned long); +} ua_data_t; + +typedef struct _ua_delta_info_t { + char ua_patch_path[MAX_FILE_PATH]; + char ua_patch_info[MAX_FILE_PATH]; + char ua_delta_path[MAX_FILE_PATH]; + char ua_attrib_path[MAX_FILE_PATH]; +} ua_delta_info_t; + +typedef struct _ua_dataSS_t { // partition operations + ua_part_info_t *parti_info; + ua_update_cfg_t *update_cfg; + ua_update_data_t *update_data; + ua_delta_info_t *update_delta; + unsigned long ua_operation; + void (*ui_progress) (void *, unsigned long); + int (*write_data_to_blkdev) (char *, int, int, char *); +} ua_dataSS_t; + +#endif diff --git a/ss_patch/sha1.c b/ss_patch/sha1.c new file mode 100755 index 0000000..91ef661 --- /dev/null +++ b/ss_patch/sha1.c @@ -0,0 +1,405 @@ +/* + * sha1.c + * + * an implementation of the Secure Hash Algorithm v.1 (SHA-1), + * specified in FIPS 180-1 + * + * David A. McGrew + * Cisco Systems, Inc. + */ + +/* + * + * Copyright (c) 2001-2006, Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * Neither the name of the Cisco Systems, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + + +#include "sha1.h" + + + +/* SN == Rotate left N bits */ +#define S1(X) ((X << 1) | (X >> 31)) +#define S5(X) ((X << 5) | (X >> 27)) +#define S30(X) ((X << 30) | (X >> 2)) + +#define f0(B,C,D) ((B & C) | (~B & D)) +#define f1(B,C,D) (B ^ C ^ D) +#define f2(B,C,D) ((B & C) | (B & D) | (C & D)) +#define f3(B,C,D) (B ^ C ^ D) + + + +/* + * nota bene: the variable K0 appears in the curses library, so we + * give longer names to these variables to avoid spurious warnings + * on systems that uses curses + */ + +uint32_t SHA_K0 = 0x5A827999; /* Kt for 0 <= t <= 19 */ +uint32_t SHA_K1 = 0x6ED9EBA1; /* Kt for 20 <= t <= 39 */ +uint32_t SHA_K2 = 0x8F1BBCDC; /* Kt for 40 <= t <= 59 */ +uint32_t SHA_K3 = 0xCA62C1D6; /* Kt for 60 <= t <= 79 */ + +void +sha1(const uint8_t *msg, int octets_in_msg, uint32_t hash_value[5]) { + sha1_ctx_t ctx; + + sha1_init(&ctx); + sha1_update(&ctx, msg, octets_in_msg); + sha1_final(&ctx, hash_value); + +} + +/* + * sha1_core(M, H) computes the core compression function, where M is + * the next part of the message (in network byte order) and H is the + * intermediate state { H0, H1, ...} (in host byte order) + * + * this function does not do any of the padding required in the + * complete SHA1 function + * + * this function is used in the SEAL 3.0 key setup routines + * (crypto/cipher/seal.c) + */ + +void +sha1_core(const uint32_t M[16], uint32_t hash_value[5]) { + uint32_t H0; + uint32_t H1; + uint32_t H2; + uint32_t H3; + uint32_t H4; + uint32_t W[80]; + uint32_t A, B, C, D, E, TEMP; + int t; + + /* copy hash_value into H0, H1, H2, H3, H4 */ + H0 = hash_value[0]; + H1 = hash_value[1]; + H2 = hash_value[2]; + H3 = hash_value[3]; + H4 = hash_value[4]; + + /* copy/xor message into array */ + + W[0] = be32_to_cpu(M[0]); + W[1] = be32_to_cpu(M[1]); + W[2] = be32_to_cpu(M[2]); + W[3] = be32_to_cpu(M[3]); + W[4] = be32_to_cpu(M[4]); + W[5] = be32_to_cpu(M[5]); + W[6] = be32_to_cpu(M[6]); + W[7] = be32_to_cpu(M[7]); + W[8] = be32_to_cpu(M[8]); + W[9] = be32_to_cpu(M[9]); + W[10] = be32_to_cpu(M[10]); + W[11] = be32_to_cpu(M[11]); + W[12] = be32_to_cpu(M[12]); + W[13] = be32_to_cpu(M[13]); + W[14] = be32_to_cpu(M[14]); + W[15] = be32_to_cpu(M[15]); + TEMP = W[13] ^ W[8] ^ W[2] ^ W[0]; W[16] = S1(TEMP); + TEMP = W[14] ^ W[9] ^ W[3] ^ W[1]; W[17] = S1(TEMP); + TEMP = W[15] ^ W[10] ^ W[4] ^ W[2]; W[18] = S1(TEMP); + TEMP = W[16] ^ W[11] ^ W[5] ^ W[3]; W[19] = S1(TEMP); + TEMP = W[17] ^ W[12] ^ W[6] ^ W[4]; W[20] = S1(TEMP); + TEMP = W[18] ^ W[13] ^ W[7] ^ W[5]; W[21] = S1(TEMP); + TEMP = W[19] ^ W[14] ^ W[8] ^ W[6]; W[22] = S1(TEMP); + TEMP = W[20] ^ W[15] ^ W[9] ^ W[7]; W[23] = S1(TEMP); + TEMP = W[21] ^ W[16] ^ W[10] ^ W[8]; W[24] = S1(TEMP); + TEMP = W[22] ^ W[17] ^ W[11] ^ W[9]; W[25] = S1(TEMP); + TEMP = W[23] ^ W[18] ^ W[12] ^ W[10]; W[26] = S1(TEMP); + TEMP = W[24] ^ W[19] ^ W[13] ^ W[11]; W[27] = S1(TEMP); + TEMP = W[25] ^ W[20] ^ W[14] ^ W[12]; W[28] = S1(TEMP); + TEMP = W[26] ^ W[21] ^ W[15] ^ W[13]; W[29] = S1(TEMP); + TEMP = W[27] ^ W[22] ^ W[16] ^ W[14]; W[30] = S1(TEMP); + TEMP = W[28] ^ W[23] ^ W[17] ^ W[15]; W[31] = S1(TEMP); + + /* process the remainder of the array */ + for (t=32; t < 80; t++) { + TEMP = W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]; + W[t] = S1(TEMP); + } + + A = H0; B = H1; C = H2; D = H3; E = H4; + + for (t=0; t < 20; t++) { + TEMP = S5(A) + f0(B,C,D) + E + W[t] + SHA_K0; + E = D; D = C; C = S30(B); B = A; A = TEMP; + } + for ( ; t < 40; t++) { + TEMP = S5(A) + f1(B,C,D) + E + W[t] + SHA_K1; + E = D; D = C; C = S30(B); B = A; A = TEMP; + } + for ( ; t < 60; t++) { + TEMP = S5(A) + f2(B,C,D) + E + W[t] + SHA_K2; + E = D; D = C; C = S30(B); B = A; A = TEMP; + } + for ( ; t < 80; t++) { + TEMP = S5(A) + f3(B,C,D) + E + W[t] + SHA_K3; + E = D; D = C; C = S30(B); B = A; A = TEMP; + } + + hash_value[0] = H0 + A; + hash_value[1] = H1 + B; + hash_value[2] = H2 + C; + hash_value[3] = H3 + D; + hash_value[4] = H4 + E; + + return; +} + +void +sha1_init(sha1_ctx_t *ctx) { + + /* initialize state vector */ + ctx->H[0] = 0x67452301; + ctx->H[1] = 0xefcdab89; + ctx->H[2] = 0x98badcfe; + ctx->H[3] = 0x10325476; + ctx->H[4] = 0xc3d2e1f0; + + /* indicate that message buffer is empty */ + ctx->octets_in_buffer = 0; + + /* reset message bit-count to zero */ + ctx->num_bits_in_msg = 0; + +} + +void +sha1_update(sha1_ctx_t *ctx, const uint8_t *msg, int octets_in_msg) { + int i; + uint8_t *buf = (uint8_t *)ctx->M; + + /* update message bit-count */ + ctx->num_bits_in_msg += octets_in_msg * 8; + + /* loop over 16-word blocks of M */ + while (octets_in_msg > 0) { + + if (octets_in_msg + ctx->octets_in_buffer >= 64) { + + /* + * copy words of M into msg buffer until that buffer is full, + * converting them into host byte order as needed + */ + octets_in_msg -= (64 - ctx->octets_in_buffer); + for (i=ctx->octets_in_buffer; i < 64; i++) + buf[i] = *msg++; + ctx->octets_in_buffer = 0; + + /* process a whole block */ + + //debug_print(mod_sha1, "(update) running sha1_core()", NULL); + + sha1_core(ctx->M, ctx->H); + + } else { + + //debug_print(mod_sha1, "(update) not running sha1_core()", NULL); + + for (i=ctx->octets_in_buffer; + i < (ctx->octets_in_buffer + octets_in_msg); i++) + buf[i] = *msg++; + ctx->octets_in_buffer += octets_in_msg; + octets_in_msg = 0; + } + + } + +} + +/* + * sha1_final(ctx, output) computes the result for ctx and copies it + * into the twenty octets located at *output + */ + +void +sha1_final(sha1_ctx_t *ctx, uint32_t *output) { + uint32_t A, B, C, D, E, TEMP; + uint32_t W[80]; + int i, t; + + /* + * process the remaining octets_in_buffer, padding and terminating as + * necessary + */ + { + int tail = ctx->octets_in_buffer % 4; + + /* copy/xor message into array */ + for (i=0; i < (ctx->octets_in_buffer+3)/4; i++) + W[i] = be32_to_cpu(ctx->M[i]); + + /* set the high bit of the octet immediately following the message */ + switch (tail) { + case (3): + W[i-1] = (be32_to_cpu(ctx->M[i-1]) & 0xffffff00) | 0x80; + W[i] = 0x0; + break; + case (2): + W[i-1] = (be32_to_cpu(ctx->M[i-1]) & 0xffff0000) | 0x8000; + W[i] = 0x0; + break; + case (1): + W[i-1] = (be32_to_cpu(ctx->M[i-1]) & 0xff000000) | 0x800000; + W[i] = 0x0; + break; + case (0): + W[i] = 0x80000000; + break; + } + + /* zeroize remaining words */ + for (i++ ; i < 15; i++) + W[i] = 0x0; + + /* + * if there is room at the end of the word array, then set the + * last word to the bit-length of the message; otherwise, set that + * word to zero and then we need to do one more run of the + * compression algo. + */ + if (ctx->octets_in_buffer < 56) + W[15] = ctx->num_bits_in_msg; + else if (ctx->octets_in_buffer < 60) + W[15] = 0x0; + + /* process the word array */ + for (t=16; t < 80; t++) { + TEMP = W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]; + W[t] = S1(TEMP); + } + + A = ctx->H[0]; + B = ctx->H[1]; + C = ctx->H[2]; + D = ctx->H[3]; + E = ctx->H[4]; + + for (t=0; t < 20; t++) { + TEMP = S5(A) + f0(B,C,D) + E + W[t] + SHA_K0; + E = D; D = C; C = S30(B); B = A; A = TEMP; + } + for ( ; t < 40; t++) { + TEMP = S5(A) + f1(B,C,D) + E + W[t] + SHA_K1; + E = D; D = C; C = S30(B); B = A; A = TEMP; + } + for ( ; t < 60; t++) { + TEMP = S5(A) + f2(B,C,D) + E + W[t] + SHA_K2; + E = D; D = C; C = S30(B); B = A; A = TEMP; + } + for ( ; t < 80; t++) { + TEMP = S5(A) + f3(B,C,D) + E + W[t] + SHA_K3; + E = D; D = C; C = S30(B); B = A; A = TEMP; + } + + ctx->H[0] += A; + ctx->H[1] += B; + ctx->H[2] += C; + ctx->H[3] += D; + ctx->H[4] += E; + + } + + //debug_print(mod_sha1, "(final) running sha1_core()", NULL); + + if (ctx->octets_in_buffer >= 56) { + + + //debug_print(mod_sha1, "(final) running sha1_core() again", NULL); + + /* we need to do one final run of the compression algo */ + + /* + * set initial part of word array to zeros, and set the + * final part to the number of bits in the message + */ + for (i=0; i < 15; i++) + W[i] = 0x0; + W[15] = ctx->num_bits_in_msg; + + /* process the word array */ + for (t=16; t < 80; t++) { + TEMP = W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]; + W[t] = S1(TEMP); + } + + A = ctx->H[0]; + B = ctx->H[1]; + C = ctx->H[2]; + D = ctx->H[3]; + E = ctx->H[4]; + + for (t=0; t < 20; t++) { + TEMP = S5(A) + f0(B,C,D) + E + W[t] + SHA_K0; + E = D; D = C; C = S30(B); B = A; A = TEMP; + } + for ( ; t < 40; t++) { + TEMP = S5(A) + f1(B,C,D) + E + W[t] + SHA_K1; + E = D; D = C; C = S30(B); B = A; A = TEMP; + } + for ( ; t < 60; t++) { + TEMP = S5(A) + f2(B,C,D) + E + W[t] + SHA_K2; + E = D; D = C; C = S30(B); B = A; A = TEMP; + } + for ( ; t < 80; t++) { + TEMP = S5(A) + f3(B,C,D) + E + W[t] + SHA_K3; + E = D; D = C; C = S30(B); B = A; A = TEMP; + } + + ctx->H[0] += A; + ctx->H[1] += B; + ctx->H[2] += C; + ctx->H[3] += D; + ctx->H[4] += E; + } + + /* copy result into output buffer */ + output[0] = be32_to_cpu(ctx->H[0]); + output[1] = be32_to_cpu(ctx->H[1]); + output[2] = be32_to_cpu(ctx->H[2]); + output[3] = be32_to_cpu(ctx->H[3]); + output[4] = be32_to_cpu(ctx->H[4]); + + /* indicate that message buffer in context is empty */ + ctx->octets_in_buffer = 0; + + return; +} + + + diff --git a/ss_patch/sha1.h b/ss_patch/sha1.h new file mode 100755 index 0000000..755628a --- /dev/null +++ b/ss_patch/sha1.h @@ -0,0 +1,114 @@ +/* + * sha1.h + * + * interface to the Secure Hash Algorithm v.1 (SHA-1), specified in + * FIPS 180-1 + * + * David A. McGrew + * Cisco Systems, Inc. + */ + +/* + * + * Copyright (c) 2001-2006, Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * Neither the name of the Cisco Systems, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef SHA1_H +#define SHA1_H +#define SHA_DIGEST_SIZE 20 +#define be32_to_cpu(x) (uint32_t) ( \ + (((uint32_t)(x) & 0xff000000u) >> 24) | \ + (((uint32_t)(x) & 0x00ff0000u) >> 8) | \ + (((uint32_t)(x) & 0x0000ff00u) << 8) | \ + (((uint32_t)(x) & 0x000000ffu) << 24)) + + +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +typedef struct { + uint32_t H[5]; /* state vector */ + uint32_t M[16]; /* message buffer */ + int octets_in_buffer; /* octets of message in buffer */ + uint32_t num_bits_in_msg; /* total number of bits in message */ +} sha1_ctx_t; + +/* + * sha1(&ctx, msg, len, output) hashes the len octets starting at msg + * into the SHA1 context, then writes the result to the 20 octets at + * output + * + */ + +void +sha1(const uint8_t *message, int octets_in_msg, uint32_t output[5]); + +/* + * sha1_init(&ctx) initializes the SHA1 context ctx + * + * sha1_update(&ctx, msg, len) hashes the len octets starting at msg + * into the SHA1 context + * + * sha1_final(&ctx, output) performs the final processing of the SHA1 + * context and writes the result to the 20 octets at output + * + */ + +void +sha1_init(sha1_ctx_t *ctx); + +void +sha1_update(sha1_ctx_t *ctx, const uint8_t *M, int octets_in_msg); + +void +sha1_final(sha1_ctx_t *ctx, uint32_t output[5]); + +/* + * The sha1_core function is INTERNAL to SHA-1, but it is declared + * here because it is also used by the cipher SEAL 3.0 in its key + * setup algorithm. + */ + +/* + * sha1_core(M, H) computes the core sha1 compression function, where M is + * the next part of the message and H is the intermediate state {H0, + * H1, ...} + * + * this function does not do any of the padding required in the + * complete sha1 function + */ + +void +sha1_core(const uint32_t M[16], uint32_t hash_value[5]); + +#endif /* SHA1_H */ diff --git a/ss_patch/ss_bspatch.c b/ss_patch/ss_bspatch.c new file mode 100755 index 0000000..be75265 --- /dev/null +++ b/ss_patch/ss_bspatch.c @@ -0,0 +1,368 @@ +/*- + * Copyright 2003-2005 Colin Percival + * Copyright 2012 Matthew Endsley + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Modifications are made in reimplementing suffix sort array generation + * and how the data is read and written to.Iterative part replaced the + * recursive implementation to avoid buffer overflow problems + */ +//#define ZLIB_MOD //not stable yet. +//#define MAX_MATCH_SIZE // define ( MAX_MATCH_SIZE or CONST_MEMORY_USAGE ) or ( none of them ) +#define CONST_MEMORY_USAGE (64*1024) //tests show smallest time when using 64 kb +#define PATCH_FILE_FORMAT_MOD +#define BSDIFF_HEADER "BSDIFF40" +#define SSDIFF_HEADER "SSDIFF40" +//#define MULTI_THREADING +#include +#include +#include +#include +#include "ss_patchdelta.h" +#include "fota_common.h" +#include "sha1.h" +#include "SS_Engine_Errors.h" + +#include +#include +#include + +#include +#include <7zFile.h> +#include <7zVersion.h> +#include +#include + +static void *SzAlloc(void *p, size_t size) +{ + p = p; + return MyAlloc(size); +} + +static void SzFree(void *p, void *address) +{ + p = p; + MyFree(address); +} +static ISzAlloc g_Alloc = { SzAlloc, SzFree }; + +static off_t offtin(u_char *buf) +{ + off_t y; + + y = buf[7] & 0x7F; + y = y * 256; + y += buf[6]; + y = y * 256; + y += buf[5]; + y = y * 256; + y += buf[4]; + y = y * 256; + y += buf[3]; + y = y * 256; + y += buf[2]; + y = y * 256; + y += buf[1]; + y = y * 256; + y += buf[0]; + + if (buf[7] & 0x80) + y = -y; + + return y; +} + +#define IN_BUF_SIZE (1 << 16) +#define OUT_BUF_SIZE (1 << 16) + +static SRes Decode2(CLzmaDec *state, ISeqOutStream *outStream, ISeqInStream *inStream, + UInt64 *unpackSize,unsigned char *dec_data) +{ + int thereIsSize = (*unpackSize != (UInt64)(Int64) - 1); + UInt64 offset = 0; + Byte inBuf[IN_BUF_SIZE]; + Byte outBuf[OUT_BUF_SIZE]; + size_t inPos = 0, inSize = 0, outPos = 0; + + LzmaDec_Init(state); + + offset = 0; + + for (;;) { + if (inPos == inSize) { + inSize = IN_BUF_SIZE; + RINOK(inStream->Read(inStream, inBuf, &inSize)); + inPos = 0; + } + + SRes res; + SizeT inProcessed = inSize - inPos; + SizeT outProcessed = OUT_BUF_SIZE - outPos; + ELzmaFinishMode finishMode = LZMA_FINISH_ANY; + ELzmaStatus status; + + if (thereIsSize && outProcessed > *unpackSize) { + outProcessed = (SizeT) * unpackSize; + finishMode = LZMA_FINISH_END; + } + + res = LzmaDec_DecodeToBuf(state, outBuf + outPos, &outProcessed, + inBuf + inPos, &inProcessed, finishMode, &status); + inPos += inProcessed; + outPos += outProcessed; + *unpackSize -= outProcessed; + memcpy(dec_data + offset, outBuf, outProcessed); + offset += outProcessed; + + outPos = 0; + + if ((res != SZ_OK) || (thereIsSize && *unpackSize == 0)) + return res; + + if (inProcessed == 0 && outProcessed == 0) { + if (thereIsSize || status != LZMA_STATUS_FINISHED_WITH_MARK) + return SZ_ERROR_DATA; + return res; + } + } +} + +int apply_patch(char *oldfile, char *patch_buffer, unsigned char **dest_buf, ssize_t *dest_size) +{ + int fd = -1, result = 0; + off_t oldsize, newsize; + u_char header[16], buf[8]; + u_char *old = NULL; + off_t oldpos, newpos; + off_t ctrl[4]; /////////////////////////////////////THREAD + off_t total_write; /////////////////////////////////////////THREAD + off_t j; + off_t memory_usage = CONST_MEMORY_USAGE; + off_t match_size; + off_t patch_buffer_offset = 0; + bool flag; + + /* + File format: + 0 8 "BSDIFF40" + 8 8 X + 16 8 Y + 24 8 sizeof(newfile) + 32 X bzip2(control block) + 32+X Y bzip2(diff block) + 32+X+Y ??? bzip2(extra block) + with control block a set of triples (x,y,z) meaning "add x bytes + from oldfile to x bytes from the diff block; copy y bytes from the + extra block; seek forwards in oldfile by z bytes". + */ + // Read header + if (patch_buffer) + memcpy(header, patch_buffer, 16); + else { + printf("%s().%d Corrupt decoded patch buffer\n", __FUNCTION__, __LINE__); + return 1; + } + + /* Check for appropriate magic */ + if (memcmp(header, BSDIFF_HEADER, 8) != 0 && memcmp(header, SSDIFF_HEADER, 8) != 0) { + printf("%s().%d Patch buffer header corrupt\n", __FUNCTION__, __LINE__ ); + return 1; + } + + /* Read lengths from header */ + newsize = offtin(header + 8); + + if ((newsize < 0)) { + printf("%s().%d Patch buffer corrupt\n", __FUNCTION__, __LINE__ ); + return 1; + } + + /* Cset patch_buffer_offset at the right place */ + patch_buffer_offset += 16; + + if (((fd = open(oldfile, O_RDONLY, 0)) < 0) || + ((oldsize = lseek(fd, 0, SEEK_END)) == -1) || + ((old = malloc(memory_usage + 1)) == NULL) || + (lseek(fd, 0, SEEK_SET) != 0)) { + printf("Corruption in old file %s\n", oldfile); + result = 1; + goto Cleanup; + } + + if ((*dest_buf = malloc(newsize + 1)) == NULL) { + printf("Corruption in old file %s\n", oldfile); + result = 1; + goto Cleanup; + } + oldpos = 0; + newpos = 0; + + total_write = 0; + + while (total_write != newsize) { + /* Read control data */ + for (j = 0; j <= 3; j++) { + memcpy(buf, patch_buffer + patch_buffer_offset, 8); + patch_buffer_offset += 8; + ctrl[j] = offtin(buf); + }; + + total_write += (ctrl[0] + ctrl[1]); + newpos = ctrl[3]; + oldpos = ctrl[2]; + + ////////////////////////////////////////////////////////////////////////////////// + flag = true; + match_size = ctrl[0]; + while (flag == true) { + if (match_size <= memory_usage) { + if (pread(fd, old, match_size, oldpos) != match_size) { + printf("Corruption in old file %s\n", oldfile); + result = 1; + goto Cleanup; + } + if (newpos + match_size > newsize) { + printf("%s().%d Corrupt patch\n", __FUNCTION__, __LINE__ ); + result = 1; + goto Cleanup; + } + memcpy((*dest_buf) + newpos, patch_buffer + patch_buffer_offset, match_size); + patch_buffer_offset += match_size; + for (j = 0; j < match_size; j++) { + (*dest_buf)[newpos + j] += old[j]; + } + newpos += match_size; + flag = false; + } else { + if (pread(fd, old, memory_usage, oldpos) != memory_usage) { + printf("%s().%d Corruption in old file %s\n", __FUNCTION__, __LINE__ , oldfile); + result = 1; + goto Cleanup; + } + if (newpos + memory_usage > newsize) { + printf("%s().%d Corrupt patch\n", __FUNCTION__, __LINE__ ); + result = 1; + goto Cleanup; + } + memcpy((*dest_buf) + newpos, patch_buffer + patch_buffer_offset, memory_usage); + patch_buffer_offset += memory_usage; + for (j = 0; j < memory_usage; j++) + (*dest_buf)[newpos + j] += old[j]; + match_size -= memory_usage; + oldpos += memory_usage; + newpos += memory_usage; + } + } + + //////////////////////////////////////////////////////////////////////////////////////// + /* Sanity-check */ + if (newpos + ctrl[1] > newsize) { + printf("%s().%d Corrupt patch\n", __FUNCTION__, __LINE__ ); + result = 1; + goto Cleanup; + } + /* Read extra string */ + memcpy((*dest_buf) + newpos, patch_buffer + patch_buffer_offset, ctrl[1]); + patch_buffer_offset += ctrl[1]; + }; + *dest_size = newsize; +Cleanup: + //close old file + if (fd >= 0) + close(fd); + if (old) + free(old); + return result; +} + +int SS_ApplyBsdiff(char *oldfile, char *newfile, char *patch, SinkFn sink, void *token, sha1_ctx_t * ctx1) +{ + + UInt64 unpackSize = 0; + CFileSeqInStream inStream; + ISeqOutStream outStream; + char *buf_res = NULL; + unsigned char *new_data = NULL; + ssize_t new_size = 0; + int result = E_SS_FAILURE; + + FileSeqInStream_CreateVTable(&inStream); + File_Construct(&inStream.file); + FileOutStream_CreateVTable((CFileOutStream *) & outStream); + + if (InFile_Open(&inStream.file, patch) != 0) + return E_SS_FAILURE; + + UInt64 i; + CLzmaDec state; + unsigned char header[LZMA_PROPS_SIZE + 8]; + + RINOK(SeqInStream_Read(&inStream.s, header, sizeof(header))); + + unpackSize = 0; + for (i = 0; i < 8; i++) + unpackSize += (UInt64) header[LZMA_PROPS_SIZE + i] << (i * 8); + + LzmaDec_Construct(&state); + RINOK(LzmaDec_Allocate(&state, header, LZMA_PROPS_SIZE, &g_Alloc)); + + //decompress the patch file into buf_res + buf_res = (char *)SS_Malloc(unpackSize); + if (!buf_res) { + LOGE("Bad memory allocation\n"); + goto Cleanup; + } + result = Decode2(&state, &outStream, &inStream.s, &unpackSize, (unsigned char *)buf_res); + + LzmaDec_Free(&state, &g_Alloc); + File_Close(&inStream.file); + + if (result != S_SS_SUCCESS) { + LOGE("Error decompression failed with code : [%d]\n", result); + goto Cleanup; + } + //apply patch using buffer decoded by Decode2 + result = apply_patch(oldfile, buf_res, &new_data, &new_size); + if (result != S_SS_SUCCESS) { + goto Cleanup; + } + + result = (sink(new_data, new_size, token) < new_size) ? E_SS_FAILURE : S_SS_SUCCESS; + if (result != S_SS_SUCCESS) { + LOGE("short write of output: %d (%s)\n", errno, strerror(errno)); + goto Cleanup; + } + + if (ctx1) { + sha1_update(ctx1, new_data, new_size); + } + Cleanup: + if (new_data) + SS_Free(new_data); + if (buf_res) + SS_Free(buf_res); + File_Close(&inStream.file);//wgid: 27007 + return result; + +} diff --git a/ss_patch/ss_patchdelta.c b/ss_patch/ss_patchdelta.c new file mode 100755 index 0000000..5c4f275 --- /dev/null +++ b/ss_patch/ss_patchdelta.c @@ -0,0 +1,1230 @@ +/* + * libtota + * + * Copyright (c) 2017 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ua.h" +#include "sha1.h" +#include "ss_patchdelta.h" +#include "fota_common.h" +#include "SS_Engine_Errors.h" + +extern void *SS_Malloc(unsigned int size); + +typedef struct { + unsigned char *buffer; + ssize_t size; + ssize_t pos; +} MemorySinkInfo; + +ssize_t ss_memorySink(unsigned char *data, ssize_t len, void *token) +{ + MemorySinkInfo *msi = (MemorySinkInfo *) token; + if (msi->size - msi->pos < len) { + return -1; + } + memcpy(msi->buffer + msi->pos, data, len); + msi->pos += len; + return len; +} + +ssize_t ss_fileSink(unsigned char *data, ssize_t len, void *token) +{ + int ss_fd = *(int *)token; + ssize_t done = 0; + ssize_t wrote; + while (done < (ssize_t) len) { + wrote = write(ss_fd, data + done, len - done); + if (wrote <= 0) { + if (errno == EINTR || errno == EAGAIN) + continue; // try again + LOGE("error writing %d bytes: %s\n", (int)(len - done), strerror(errno)); + return done; + } + done += wrote; + } + return done; +} + +// Take a string 'str' of 40 hex digits and parse it into the 20 +// byte array 'digest'. 'str' may contain only the digest or be of +// the form ":". Return 0 on success, -1 on any +// error. +int ParseSha1(const char *str, uint8_t * digest) +{ + int i; + const char *ps = str; + uint8_t *pd = digest; + for (i = 0; i < SHA_DIGEST_SIZE * 2; ++i, ++ps) { + int digit; + if (*ps >= '0' && *ps <= '9') { + digit = *ps - '0'; + } else if (*ps >= 'a' && *ps <= 'f') { + digit = *ps - 'a' + 10; + } else if (*ps >= 'A' && *ps <= 'F') { + digit = *ps - 'A' + 10; + } else { + return -1; + } + if (i % 2 == 0) { + *pd = digit << 4; + } else { + *pd |= digit; + ++pd; + } + } + if (*ps != '\0') + return -1; + return 0; +} + +//Function to find the start of gzipped part in compressed kernel +int getOffset(char *zimage_path) +{ + char gzip_header[] = { 31, -117, 8 }; //header value for gzip which needs to be checked + char buf[4] = { 0, }; + int offset = 0;//wgid:14074 + + FILE *f = fopen(zimage_path, "r"); + if(!f){ + LOGE("Fopen failed for path %s\n", zimage_path); + SS_SetUpgradeState(E_SS_OPENFILE_ONLYR); + return -1; + } + fseek(f, 0, SEEK_SET); + while (fread(buf, 1, 3, f) > 0) { + if (gzip_header[0] == buf[0] && gzip_header[1] == buf[1] && gzip_header[2] == buf[2]) { + LOGL(LOG_SSENGINE, "match for %d %d %d found at %d\n", buf[0], buf[1], buf[2], ftell(f) - 3); + break; + } else { + fseek(f, -2, SEEK_CUR); + } + } + offset = ftell(f) - 3; + fclose(f); + return offset; +} + +int SS_LoadPartition(const char *filename, FileInfo * file) +{ + size_t read = 0; + FILE *dev = NULL; + int i; + + dev = fopen(filename, "rb"); + if (dev == NULL) { + LOGE("failed to open partition \"%s\": %s\n", filename, strerror(errno)); + return -1; + } + + sha1_ctx_t sha_ctx; + sha1_init(&sha_ctx); + + file->data = SS_Malloc(file->size); + if (file->data) { + read = fread(file->data, 1, file->size, dev); + LOGL(LOG_SSENGINE, "Partition size read %d\n", read); + sha1_update(&sha_ctx, file->data, read); + file->size = read; + } + + const uint8_t sha_final[SHA_DIGEST_SIZE] = { 0, }; + sha1_final(&sha_ctx, (uint32_t *) & sha_final); + for (i = 0; i < SHA_DIGEST_SIZE; ++i) { + file->sha1[i] = sha_final[i]; + } + //LOGL(LOG_SSENGINE, "Final SHA of Source (%s)\n", sha_final); + + file->st.st_mode = 0644; + file->st.st_uid = 0; + file->st.st_gid = 0; + fclose(dev); + return 0; +} + +//extern int write_data_to_blkdev(char* dev_name, int blk_start, int blk_cnt, char* data); + +int SS_LoadFile(const char *filename, FileInfo * file) +{ + + file->data = NULL; + //LOGL(LOG_SSENGINE,"SS_LoadFile --- [File name %s]\n",filename); + + if (stat(filename, &file->st) != 0) { + LOGE("failed to stat \"%s\": %s\n", filename, strerror(errno)); + return -1; + } + + file->size = file->st.st_size; + file->data = SS_Malloc(file->size); + if (!file->data) { + LOGE("failed to allocate memory for \"%s\": %s\n", filename, strerror(errno)); + return -1; + } + + FILE *f = fopen(filename, "rb"); + if (f == NULL) { + LOGE("failed to open \"%s\": %s\n", filename, strerror(errno)); + SS_Free(file->data); + file->data = NULL; + return -1; + } + + ssize_t bytes_read = fread(file->data, 1, file->size, f); + if (bytes_read != file->size) { + LOGE("short read of \"%s\" (%ld bytes of %ld)\n", filename, (long)bytes_read, (long)file->size); + SS_Free(file->data); + file->data = NULL; + fclose(f); + return -1; + } + fclose(f); + //LOGL(LOG_SSENGINE,"SS_LoadFile --- [bytes_read %d]\n",bytes_read); + sha1(file->data, file->size, (uint32_t *) file->sha1); + return 0; +} + +extern int gvalid_session; +extern void create_dir(char *pathname, int mode); +#ifdef SUPPORT_CONTAINER +//unzip source archive , apply patch from extracted delta folder and repack the source archive +int SS_UpdateArchive(ua_dataSS_t * ua_dataSS, const char *source_filename, const char *target_filename, + const char *source_sha1_str, const char *target_sha1_str) +{ + FILE *fp; + char *line = NULL, *token = NULL, *source_file = NULL, *new_file = NULL, *dir_to_create = NULL, *patch_file = NULL; + char patch_path_full[MAX_FILE_PATH] = { 0, }; //absolute path for patches + char source_path_full[MAX_FILE_PATH] = { 0, }; //absolute path for uncompressed source files + char target_path_full[MAX_FILE_PATH] = { 0, }; + char patchlist[MAX_FILE_PATH] = { 0, }; + char cmd[2 * MAX_FILE_PATH] = { 0, }; + size_t len = 0, read = 0; + int result = S_SS_SUCCESS; + uint8_t target_sha1[SHA_DIGEST_SIZE]; + uint8_t source_sha1[SHA_DIGEST_SIZE] = { 0, }; + FileInfo source_data = { 0, }; + int backupsrc = -1; + SinkFn sink = NULL; + void *tok = NULL; + int output = -1; + char *outname = NULL; + + if (ParseSha1(target_sha1_str, target_sha1) != 0) { + LOGE("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str); + return E_SS_FAILURE; + } + + if (0 == gvalid_session) { + if (ParseSha1(source_sha1_str, source_sha1) != 0) { + LOGE("failed to parse tgt-sha1 \"%s\"\n", source_sha1_str); + return E_SS_FAILURE; + } + if (SS_LoadFile(source_filename, &source_data) == 0) { + if (memcmp(source_data.sha1, source_sha1, SHA_DIGEST_SIZE) == 0) { + LOGL(LOG_SSENGINE, "SS_UpdateDeltaFS - Patch Can be applied\n"); + if (source_data.data) + SS_Free(source_data.data); + } else if (memcmp(source_data.sha1, target_sha1, SHA_DIGEST_SIZE) == 0) { + LOGL(LOG_SSENGINE, "SS_UpdateDeltaFS - Patch Already applied\n"); + if (source_data.data) + SS_Free(source_data.data); + return S_SS_SUCCESS; + } else { + //Check for backup file SHA + SS_Free(source_data.data); + source_data.data = NULL; + LOGL(LOG_SSENGINE, "SS_UpdateDeltaFS - Source was currupted, Try loading from backup source\n"); + if (SS_LoadFile(SS_BACKUP_SOURCE, &source_data) == 0) { + if (memcmp(source_data.sha1, source_sha1, SHA_DIGEST_SIZE) == 0) { + if (SS_CopyFile(NULL, SS_BACKUP_SOURCE, source_filename) != 0) { + LOGE("copy of backup to \"%s\" failed: %s\n", source_filename, strerror(errno)); + SS_SetUpgradeState(E_SS_FSUPDATEFAILED); + if (source_data.data) + SS_Free(source_data.data); + return E_SS_FAILURE; + } + LOGL(LOG_SSENGINE, + "SS_UpdateDeltaFS - Patch Can be applied from using backup file as source\n"); + } else { + SS_SetUpgradeState(E_SS_FSSRCCURRUPTED); + if (source_data.data) + SS_Free(source_data.data); + return E_SS_FAILURE; + } + } else { + SS_SetUpgradeState(E_SS_FSSRCCURRUPTED); + if (source_data.data) + SS_Free(source_data.data); + return E_SS_FAILURE; + } + } + } else { + LOGL(LOG_SSENGINE, "SS_UpdateDeltaFS - Source was deleted, Try loading from backup source\n"); + if (SS_LoadFile(SS_BACKUP_SOURCE, &source_data) == 0) { + if (memcmp(source_data.sha1, source_sha1, SHA_DIGEST_SIZE) == 0) { + if (SS_CopyFile(NULL, SS_BACKUP_SOURCE, source_filename) != 0) { + LOGE("copy of backup to \"%s\" failed: %s\n", source_filename, strerror(errno)); + SS_SetUpgradeState(E_SS_FSUPDATEFAILED); + if (source_data.data) + SS_Free(source_data.data); + return E_SS_FAILURE; + } + LOGL(LOG_SSENGINE, "SS_UpdateDeltaFS - Patch Can be applied from using backup file as source\n"); + } else { + SS_SetUpgradeState(E_SS_FSSRCCURRUPTED); + if (source_data.data) + SS_Free(source_data.data); + return E_SS_FAILURE; + } + } else { + SS_SetUpgradeState(E_SS_FSSRCCURRUPTED); + if (source_data.data) + SS_Free(source_data.data); + return E_SS_FAILURE; + } + } + } +#ifndef ENHANCED_BSDIFF + backupsrc = SS_BackupSource(source_filename); + if (backupsrc != 0) { + LOGE("failed to Backup source File:[%s] \n", source_filename); + SS_SetUpgradeState(E_SS_FSSRCBACKUPFAILED); + return E_SS_FAILURE; + } +#endif + //create workspace for processing container upgrade + SS_CreateFolder(NULL, SS_ARCHIVE_WORK_FOLDER); + SS_CreateFolder(NULL, SS_ARCHIVE_UNPACK_FOLDER); + + //unpack the source container to the unpack workspace + snprintf(cmd, sizeof(cmd) - 1, "%s -qo %s -d %s", SS_UNZIP_COMMAND, source_filename, SS_ARCHIVE_UNPACK_FOLDER); + result = _system_cmd_wait(cmd); + if (result != S_SS_SUCCESS) { + LOGE("zip extraction for [%s] failed, code [%d]\n", cmd, result); + return E_SS_FAILURE; + } + //extract unpack scipt from delta.tar to process containers later + if (tar_get_item_size(ua_dataSS->update_data->ua_delta_path, SS_KERNEL_UNPACK_SCRIPT) > 0) + if (tar_extract_file(ua_dataSS->update_data->ua_delta_path, SS_KERNEL_UNPACK_SCRIPT, SS_KERN_UNPK_SCRIPT_PATH) > + 0) + LOGL(LOG_SSENGINE, "Extracted %s successfully\n", SS_KERNEL_UNPACK_SCRIPT); + else { + LOGE("Error in fn tar_extract_file for item %s", SS_KERNEL_UNPACK_SCRIPT); + SS_SetUpgradeState(E_SS_DELTA_IS_CORRUPT); + result = E_SS_FAILURE; + } + //move new tpk extracted in the delta folder to the work folder + new_file = strrchr(target_filename, '/'); + snprintf(source_path_full, sizeof(source_path_full) - 1, "%s/%s", SS_ARCHIVE_DELTA_FOLDER, new_file + 1); + snprintf(target_path_full, sizeof(target_path_full) - 1, "%s/%s", SS_ARCHIVE_WORK_FOLDER, new_file + 1); + + result = rename(source_path_full, target_path_full); + if (result != 0) { + LOGE("fatal error in moving %s to %s\n", source_path_full, target_path_full); + return E_SS_FAILURE; + } + snprintf(cmd, sizeof(cmd) - 1, "%s -qo %s -d %s", SS_UNZIP_COMMAND, target_path_full, SS_ARCHIVE_WORK_FOLDER); + result = _system_cmd_wait(cmd); + if (result != S_SS_SUCCESS) { + LOGE("zip extraction for [%s] failed, code [%d]\n", cmd, result); + return E_SS_FAILURE; + } else + LOGL(LOG_SSENGINE, "Thin zip extracted successfully\n"); + // open the patch list and start iterating through the changes and the same files + snprintf(patchlist, MAX_FILE_PATH, "%s/%s", SS_ARCHIVE_DELTA_FOLDER, SS_CONTAINER_INFO_FILE); + fp = fopen(patchlist, "r"); + if (!fp) { + LOGE("file open error [%s]\n", patchlist); + return E_SS_FAILURE; + } + + while ((read = getline(&line, &len, fp)) != -1) { + + switch (line[0]) { // '-' == Delete File, 's' == same File, 'c' == Changed File + case 's': //for same files case, just extract from old tpk to the work folder, update new tpk in the end + token = strtok(line, SS_SEPARATOR_TOKEN); + + source_file = strtok(NULL, SS_NEWLINE_TOKEN); + + snprintf(source_path_full, sizeof(source_path_full) - 1, "%s/%s", SS_ARCHIVE_UNPACK_FOLDER, source_file); + snprintf(target_path_full, sizeof(target_path_full) - 1, "%s/%s", SS_ARCHIVE_WORK_FOLDER, source_file); + LOGL(LOG_SSENGINE, "copy %s\n", source_file); + result = SS_MoveFile(NULL, source_path_full, target_path_full); + if (result != S_SS_SUCCESS) { + LOGE("fatal error [%d]\n", errno); + goto Cleanup; + } + break; + case 'c': + token = strtok(line, SS_SEPARATOR_TOKEN); + + source_file = strtok(NULL, SS_SEPARATOR_TOKEN); + patch_file = strtok(NULL, SS_NEWLINE_TOKEN); + + snprintf(source_path_full, sizeof(source_path_full) - 1, "%s/%s", SS_ARCHIVE_UNPACK_FOLDER, source_file); + snprintf(target_path_full, sizeof(target_path_full) - 1, "%s/%s", SS_ARCHIVE_WORK_FOLDER, source_file); + LOGL(LOG_SSENGINE, "copy %s\n", source_file); + result = SS_MoveFile(NULL, source_path_full, target_path_full); + if (result != S_SS_SUCCESS) { + LOGE("fatal error [%d]\n", errno); + } + + snprintf(patch_path_full, sizeof(patch_path_full) - 1, "%s/%s", SS_ARCHIVE_DELTA_FOLDER, patch_file); + snprintf(source_path_full, sizeof(source_path_full) - 1, "%s/%s", SS_ARCHIVE_WORK_FOLDER, source_file); + + { + // We write the decoded output to ".patch". + //allocate some extra space to allow for concatinating ".patch" with the name + outname = (char *)SS_Malloc(strlen(source_path_full) + 10); + if (outname == NULL) + goto Cleanup; + strcpy(outname, source_path_full); + strcat(outname, ".patch"); + + output = open(outname, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU); + if (output < 0) { + LOGE("failed to open output file %s: %s\n", outname, strerror(errno)); + SS_Free(outname); + goto Cleanup; + } + sink = ss_fileSink; + tok = &output; + } + result = SS_ApplyBsdiff((char *)source_path_full, outname, patch_path_full, sink, tok, NULL); + LOGL(LOG_SSENGINE, "GenerateTarget Output is %d and result is %d\n", output, result); + if (output >= 0) { + fsync(output); + close(output); + } + + if (result != 0) { + LOGE("applying patch failed %s\n", source_path_full); + if (outname != NULL) { + unlink(outname); + } + goto Cleanup; + } + result = rename(outname, source_path_full); + if (result != 0) { + LOGE("fatal error %s\n", source_path_full); + goto Cleanup; + } else { + LOGL(LOG_SSENGINE, "Successfully applied patch for [%s]\n", source_path_full); + } + break; + default: + break; + } + } + + new_file = strrchr(target_filename, '/'); + snprintf(cmd, sizeof(cmd) - 1, "%s -p %s %s /opt/data/fota", SS_KERN_UNPK_SCRIPT_PATH, SS_ARCHIVE_WORK_FOLDER, + new_file + 1); + int ret = _system_cmd_wait(cmd); + LOGL(LOG_SSENGINE, "ret for %s is %d\n", cmd, ret); + + //Apply diff between intermediate new tpk and the new tpk which contains central dir changes only + snprintf(patch_path_full, sizeof(patch_path_full) - 1, "%s/New_%s.delta", SS_ARCHIVE_DELTA_FOLDER, new_file + 1); + snprintf(source_path_full, sizeof(source_path_full) - 1, "%s/%s", SS_ARCHIVE_WORK_FOLDER, new_file + 1); + + { + // We write the decoded output to ".patch". + //allocate some extra space to allow for concatinating ".patch" with the name + outname = (char *)SS_Malloc(strlen(source_path_full) + 10); + if (outname == NULL) + return E_SS_FAILURE; + strcpy(outname, source_path_full); + strcat(outname, ".patch"); + + output = open(outname, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU); + if (output < 0) { + LOGE("failed to open output file %s: %s\n", outname, strerror(errno)); + SS_Free(outname); + goto Cleanup; + } + sink = ss_fileSink; + tok = &output; + } + result = SS_ApplyBsdiff((char *)source_path_full, outname, patch_path_full, sink, tok, NULL); + LOGL(LOG_SSENGINE, "GenerateTarget Output is %d and result is %d\n", output, result); + if (output >= 0) { + fsync(output); + close(output); + } + + if (result != 0) { + LOGE("applying patch failed %s\n", source_path_full); + if (outname != NULL) { + unlink(outname); + } + goto Cleanup; + } + + if (SS_LoadFile(outname, &source_data) == 0) + result = memcmp(source_data.sha1, target_sha1, SHA_DIGEST_SIZE); + if (result != S_SS_SUCCESS) { + LOGE("patch did not produce expected sha1 \n"); + SS_SetUpgradeState(E_SS_IMGSHA_MISMATCH); + goto Cleanup; + } + + result = rename(outname, source_path_full); + if (result != 0) { + LOGE("fatal error %s\n", source_path_full); + goto Cleanup; + } + //Delete old file and copy patched archive, cant use rename as partitions may be different + unlink(source_filename); + if (result != 0) { + LOGE("failed to unlink [%s] code [%d]\n", source_filename, errno); + goto Cleanup; + } + result = (int)SS_CopyFile(NULL, source_path_full, target_filename); + if (result != S_SS_SUCCESS) { + LOGE("failed to copy file [%s] result [%d]\n", source_path_full, result); + goto Cleanup; + } + + Cleanup: + fclose(fp); + if (line) + SS_Free(line); + if (outname) + SS_Free(outname); + SS_DeleteFile(NULL, SS_KERN_UNPK_SCRIPT_PATH); + SS_DeleteFile(NULL, SS_FIND_CMD_TARGET); + SS_DeleteFolder(NULL, SS_CONTAINER_WORKSPACE); + return result; +} +#endif +/*! + ********************************************************************************* + * SS_UpdateDeltaFS + ********************************************************************************* + * + * @brief + * This is used to apply patch for a file during delta FS upgrade + * + * + * @param + * + * @return 0 - in case of success + * 1 - in case of error during patch application + * + ********************************************************************************* + */ + +int SS_UpdateDeltaFS(const char *source_filename, const char *target_filename, + const char *source_sha1_str, const char *target_sha1_str, int patch_data_size) +{ + uint8_t target_sha1[SHA_DIGEST_SIZE]; + sha1_ctx_t ctx1; + int output; + int retry = 1; + int use_backup = 0; + char *outname = NULL; + int backupsrc = -1; + int result = 0; + FileInfo source_file; + uint8_t source_sha1[SHA_DIGEST_SIZE] = { 0, }; + + if (ParseSha1(target_sha1_str, target_sha1) != 0) { + LOGE("failed to parse tgt-sha1 \"%s\"\n", target_sha1_str); + return E_SS_FAILURE; + } + + /* + if battery removed in between update gvalid_session becomes 0 + need to check file integrity in that case + */ + if (0 == gvalid_session) { + if (ParseSha1(source_sha1_str, source_sha1) != 0) { + LOGE("failed to parse tgt-sha1 \"%s\"\n", source_sha1_str); + return E_SS_FAILURE; + } + if (SS_LoadFile(source_filename, &source_file) == 0) { + if (memcmp(source_file.sha1, source_sha1, SHA_DIGEST_SIZE) == 0) { + LOGL(LOG_SSENGINE, "SS_UpdateDeltaFS - Patch Can be applied\n"); + if (source_file.data) + SS_Free(source_file.data); + } else if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_SIZE) == 0) { + LOGL(LOG_SSENGINE, "SS_UpdateDeltaFS - Patch Already applied\n"); + if (source_file.data) + SS_Free(source_file.data); + return S_SS_SUCCESS; + } else { + //Check for backup file SHA + SS_Free(source_file.data); + source_file.data = NULL; + LOGL(LOG_SSENGINE, "SS_UpdateDeltaFS - Source was currupted, Try loading from backup source\n"); + if (SS_LoadFile(SS_BACKUP_SOURCE, &source_file) == 0) { + if (memcmp(source_file.sha1, source_sha1, SHA_DIGEST_SIZE) == 0) { + if (SS_CopyFile(NULL, SS_BACKUP_SOURCE, source_filename) != S_SS_SUCCESS) { + LOGE("copy of backup to \"%s\" failed: %s\n", source_filename, strerror(errno)); + SS_SetUpgradeState(E_SS_FSUPDATEFAILED); + if (source_file.data) + SS_Free(source_file.data); + return E_SS_FAILURE; + } + LOGL(LOG_SSENGINE, + "SS_UpdateDeltaFS - Patch Can be applied from using backup file as source\n"); + } else { + SS_SetUpgradeState(E_SS_FSSRCCURRUPTED); + if (source_file.data) + SS_Free(source_file.data); + return E_SS_FAILURE; + } + } else { + SS_SetUpgradeState(E_SS_FSSRCCURRUPTED); + if (source_file.data) + SS_Free(source_file.data); + return E_SS_FAILURE; + } + } + } else { + LOGL(LOG_SSENGINE, "SS_UpdateDeltaFS - Source was deleted, Try loading from backup source\n"); + if (SS_LoadFile(SS_BACKUP_SOURCE, &source_file) == 0) { + if (memcmp(source_file.sha1, source_sha1, SHA_DIGEST_SIZE) == 0) { + use_backup = 1; + LOGL(LOG_SSENGINE, "SS_UpdateDeltaFS - Patch Can be applied from using backup file as source\n"); + } else { + SS_SetUpgradeState(E_SS_FSSRCCURRUPTED); + if (source_file.data) + SS_Free(source_file.data); + return E_SS_FAILURE; + } + } else { + SS_SetUpgradeState(E_SS_FSSRCCURRUPTED); + if (source_file.data) + SS_Free(source_file.data); + return E_SS_FAILURE; + } + } + } + //Now proceed wit patch application since patch can be applied + do { + int enough_space = 0; + size_t free_space; + char *tok; + + if (retry > 0) { + if (use_backup) { + tok = strrchr(source_filename, '/'); + *tok = '\0'; + } + SS_GetAvailableFreeSpace(NULL, source_filename, &free_space); + enough_space = (free_space > (256 << 10)) && // 256k (two-block) minimum + (free_space > (patch_data_size * 3 / 2)); // 50% margin of error + if (use_backup) + *tok = '/'; + } + + if (!use_backup) { +#ifndef ENHANCED_BSDIFF + backupsrc = SS_BackupSource(source_filename); + if (backupsrc != 0) { + LOGE("failed to Backup source File:[%s] \n", source_filename); + SS_SetUpgradeState(E_SS_FSSRCBACKUPFAILED); + return E_SS_FAILURE; + } +#endif + } + if (!enough_space) { + LOGL(LOG_SSENGINE, "For %s: free space %ld bytes; enough %d\n", source_filename, (long)free_space, + enough_space); + retry = 0; + use_backup = 1; + unlink(source_filename); + } + //LOGL(LOG_SSENGINE, "For %s: target %ld bytes; free space %ld bytes; enough %d\n", + // source_filename, (long)patch_data_size, (long)free_space, enough_space); + //LOGL(LOG_SSENGINE,"Generate Target Space availabitiy [%d]\n", enough_space); + + SinkFn sink = NULL; + void *token = NULL; + output = -1; + outname = NULL; + + { + // We write the decoded output to ".patch". + //allocate some extra space to allow for concatinating ".patch" with the name + outname = (char *)SS_Malloc(strlen(target_filename) + 10); + if (outname == NULL) + return -1; + strcpy(outname, target_filename); + strcat(outname, ".patch"); + + output = open(outname, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU); + if (output < 0) { + if (errno == 2) { + char *dir_path = strrchr(outname, '/'); + *dir_path = '\0'; + // need to create directory as the target may be different from source + LOGL(LOG_SSENGINE, "need to create directory [%s]\n", outname); + create_dir(outname, 0755); + *dir_path = '/'; + output = open(outname, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU); + if (output < 0) { + LOGE("failed to open output file %s: %s\n", outname, strerror(errno)); + SS_Free(outname); + return E_SS_FAILURE; + } + } + } + sink = ss_fileSink; + token = &output; + } + sha1_init(&ctx1); + if (use_backup) + result = SS_ApplyBsdiff(SS_BACKUP_SOURCE, outname, SS_PATCHFILE_SOURCE, sink, token, &ctx1); + else + result = SS_ApplyBsdiff((char *)source_filename, outname, SS_PATCHFILE_SOURCE, sink, token, &ctx1); + if (output >= 0) { + fsync(output); + close(output); + } + + if (result != 0) { + if (retry == 0) { + LOGE("applying patch failed result : [%d]\n", result); + SS_Free(outname);//wgid: 20739 + SS_SetUpgradeState(E_SS_FSUPDATEFAILED); + return E_SS_FAILURE; + } else { + LOGE("applying patch failed; retrying\n"); + SS_Free(outname);//wgid: 20739 + } + if (outname != NULL) { + unlink(outname); + } + } else { + // succeeded; no need to retry + break; + } + } while (retry-- > 0); + const uint8_t current_target_sha1[SHA_DIGEST_SIZE] = { 0, }; + sha1_final(&ctx1, (uint32_t *) & current_target_sha1); + if (memcmp(current_target_sha1, target_sha1, SHA_DIGEST_SIZE) != 0) { + LOGE("patch did not produce expected sha1\n"); + SS_SetUpgradeState(E_SS_FSSHA_MISMATCH); + if (outname != NULL) { + SS_Free(outname); + } + return E_SS_FAILURE; + } + // Finally, rename the .patch file to replace the target file. +#ifdef ENHANCED_BSDIFF + if (SS_rename1(outname, target_filename) != 0) { + LOGE("rename of .patch to \"%s\" failed: %s\n", target_filename, strerror(errno)); + SS_SetUpgradeState(E_SS_FSUPDATEFAILED); + if (outname != NULL) { + SS_Free(outname); + } + return E_SS_FAILURE; + } +#else + if (rename(outname, target_filename) != 0) { + LOGE("rename of .patch to \"%s\" failed: %s\n", target_filename, strerror(errno)); + SS_SetUpgradeState(E_SS_FSUPDATEFAILED); + if (outname != NULL) { + SS_Free(outname); + } + return E_SS_FAILURE; + } + //remove source file if target is not same + if (strcmp(source_filename, target_filename) != 0) + unlink(source_filename); + SS_BackupSourceClear(); +#endif + SS_PatchSourceClear(); + SS_Free(outname); + + return result; +} + +/*! + ********************************************************************************* + * SS_UpdateDeltaKernel + ********************************************************************************* + * + * @brief + * This is used to apply patch for kernel delta during delta Image upgrade + * + * + * @param + * + * @return 0 - in case of success + * 1 - in case of error during patch application + * + ********************************************************************************* + */ + +int SS_UpdateDeltaKernel(ua_dataSS_t * ua_dataSS, int (*write_to_blkdev) (char *, int, int, char *)) +{ + uint8_t target_sha1[SHA_DIGEST_SIZE]; + uint8_t source_sha1[SHA_DIGEST_SIZE]; + FileInfo source_file; + int result = S_SS_SUCCESS; + int blk_cnt, read_count = 0; + int blk_start = 0; + int backupsrc = -1; + int use_backup_img = -1; + FILE *fp = NULL, *wp = NULL, *kp = NULL; + int i = 0, j = 0, file_len = 0; + char *magic = NULL, *file_name = NULL, *buf = NULL, a = '0'; + char cmd[1024] = { 0, }; + char source_filename[MAX_FILE_PATH] = { 0, }; + char part_filename[MAX_FILE_PATH] = { 0, }; + int file_num = 0; + SinkFn sink = NULL; + void *tok = NULL; + int output = -1; + char *outname = NULL; + //Kernel Parts are created on unpacking kernel which is then used to apply delta + char *kernel_parts[] = { "decompression_code", + "piggy.gz", + "padding_piggy", + "piggy_trailer" + }; + + if (ParseSha1(ua_dataSS->update_cfg->target_sha1, target_sha1) != 0) { + LOGE("failed to parse tgt-sha1 \"%s\"\n", ua_dataSS->update_cfg->target_sha1); + return E_SS_FAILURE; + } + + source_file.size = ua_dataSS->update_cfg->soure_img_size; + source_file.data = NULL; + if (0 == gvalid_session) { + + if (ParseSha1(ua_dataSS->update_cfg->soure_sha1, source_sha1) != 0) { + LOGE("failed to parse Src-sha1 \"%s\"\n", ua_dataSS->update_cfg->soure_sha1); + return E_SS_FAILURE; + } + + if (SS_LoadPartition(ua_dataSS->parti_info->ua_blk_name, &source_file) == 0) { + if (memcmp(source_file.sha1, source_sha1, SHA_DIGEST_SIZE) == 0) { + LOGL(LOG_SSENGINE, "SS_UpdateDeltaIMG - Patch Can be applied\n"); + + } else if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_SIZE) == 0) { + LOGL(LOG_SSENGINE, "SS_UpdateDeltaIMG - Patch Already applied\n"); + SS_Free(source_file.data); + return S_SS_SUCCESS; + } else { + SS_Free(source_file.data); + source_file.data = NULL; + LOGL(LOG_SSENGINE, "SS_UpdateDeltaIMG - Source was currupted, Try loading from backup source\n"); + if (SS_LoadPartition(SS_BACKUP_SOURCE, &source_file) == 0) { + if (memcmp(source_file.sha1, source_sha1, SHA_DIGEST_SIZE) == 0) { + use_backup_img = 1; + + LOGL(LOG_SSENGINE, + "SS_UpdateDeltaIMG - Patch Can be applied from using backup file as source\n"); + } else { + SS_SetUpgradeState(E_SS_IMGSRCCURRUPTED); + SS_Free(source_file.data); + return E_SS_FAILURE; + } + } + } + } + } else { //in case of kernel delta need to copy kernel data from blk to buffer + if (SS_LoadPartition(ua_dataSS->parti_info->ua_blk_name, &source_file) != 0) { + SS_SetUpgradeState(E_SS_IMGSRCCURRUPTED); + LOGE("Fatal Error : Kernel block is corrupted\n"); + return E_SS_FAILURE; + } + } + if (use_backup_img == -1) { + backupsrc = SS_BackupSource(ua_dataSS->parti_info->ua_blk_name); + if (backupsrc != 0) { + LOGE("failed to Backup source File:[%s] \n", ua_dataSS->parti_info->ua_blk_name); + SS_SetUpgradeState(E_SS_IMGSRCBACKUPFAILED); + return E_SS_FAILURE; + } + } + //Cleanup workspace and copy helper executables to it before proceeding + SS_DeleteFolder(NULL, SS_KERNEL_WORKSPACE); + create_dir(SS_KERNEL_WORKSPACE, 0755); + SS_CopyFile(NULL, SS_GZIP_SOURCE, SS_GZIP_TARGET); + SS_CopyFile(NULL, SS_STAT_SOURCE, SS_STAT_TARGET); + SS_CopyFile(NULL, SS_DD_SOURCE, SS_DD_TARGET); + + if (tar_get_item_size(ua_dataSS->update_data->ua_delta_path, SS_KERNEL_UNPACK_SCRIPT) > 0) + if (tar_extract_file(ua_dataSS->update_data->ua_delta_path, SS_KERNEL_UNPACK_SCRIPT, SS_KERN_UNPK_SCRIPT_PATH) > + 0) + LOGL(LOG_SSENGINE, "Extracted %s successfully\n", SS_KERNEL_UNPACK_SCRIPT); + else { + LOGE("Error in fn tar_extract_file for item %s", SS_KERNEL_UNPACK_SCRIPT); + SS_SetUpgradeState(E_SS_DELTA_IS_CORRUPT); + result = E_SS_FAILURE; + goto Cleanup; + } else { + LOGE("Error size is not positive for item %s", SS_KERNEL_UNPACK_SCRIPT); + SS_SetUpgradeState(E_SS_DELTA_IS_CORRUPT); + result = E_SS_FAILURE; + goto Cleanup; + } + //Now write the kernel data to the workplace and start applying patch + snprintf(source_filename, sizeof(source_filename) - 1, "%s/%s", SS_KERNEL_WORKSPACE, SS_KERNEL_NAME); + fp = fopen(source_filename, "w"); + if (!fp) { + LOGE("file open error [%s] code [%d]\n", source_filename, errno); + SS_Free(source_file.data); + result = E_SS_FAILURE; + goto Cleanup; + } + //write source kernel data to workspace + read_count = fwrite(source_file.data, 1, source_file.size, fp); + if (read_count != source_file.size) { + LOGE("file write error read_count = %d for [%s]\n", read_count, source_filename); + SS_Free(source_file.data); + result = E_SS_FAILURE; + goto Cleanup; + } + SS_Free(source_file.data); + fclose(fp); + fp = NULL;//wgid: 59313 + + //Unpack source kernel + int offset = getOffset(source_filename); + if(offset < 0){ + LOGE("Failed to get offset\n"); + result = E_SS_FAILURE; + goto Cleanup; + } + snprintf(cmd, sizeof(cmd) - 1, "%s -u %s %s %d", SS_KERN_UNPK_SCRIPT_PATH, SS_KERNEL_WORKSPACE, SS_KERNEL_NAME, + offset); + int ret = _system_cmd_wait(cmd); + LOGL(LOG_SSENGINE, "ret for %s is %d\n", cmd, ret); + + //open delta file, extract kernel delta parts and apply patch to previously unpacked kernel + fp = fopen(SS_PATCHFILE_SOURCE, "r"); + if (fp == NULL) { + LOGE("Failed to open kernel delta patch\n"); + result = E_SS_FAILURE; + goto Cleanup; + } + //read kernel delta header for delta names and size + buf = SS_Malloc(SS_KERNEL_DELTA_HEADER); + if(!buf){//wgid: 13099 + LOGE("Failed to allocate memory\n"); + result = E_SS_MALLOC_ERROR; + goto Cleanup; + } + fread(buf, 1, SS_KERNEL_DELTA_HEADER, fp); + magic = strtok(buf, ":"); + file_num = atoi(strtok(NULL, ":")); + + //adjust offset to start of data section before proceeding + fseek(fp, SS_KERNEL_DELTA_HEADER, SEEK_SET); + + while (file_num-- > 0) { + file_name = strtok(NULL, ":"); + file_len = atoi(strtok(NULL, ":")); + j = file_len; + snprintf(source_filename, sizeof(source_filename) - 1, "%s/%s_unpacked/%s", SS_KERNEL_WORKSPACE, SS_KERNEL_NAME, + file_name); + snprintf(part_filename, sizeof(part_filename) - 1, "%s/%s", SS_KERNEL_WORKSPACE, file_name); + wp = fopen(part_filename, "w"); + while (j-- > 0) { + a = fgetc(fp); + fputc(a, wp); + } + fclose(wp); + + //apply bspatch to the unpacked kernel parts + + { + // We write the decoded output to ".patch". + //allocate some extra space to allow for concatinating ".patch" with the name + outname = (char *)SS_Malloc(strlen(source_filename) + 10); + if (outname == NULL) { + SS_SetUpgradeState(E_SS_MALLOC_ERROR); + result = E_SS_FAILURE; + goto Cleanup; + } + strcpy(outname, source_filename); + strcat(outname, ".patch"); + + output = open(outname, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU); + if (output < 0) { + LOGE("failed to open output file %s: %s\n", outname, strerror(errno)); + SS_Free(outname); + result = E_SS_FAILURE; + goto Cleanup; + } + sink = ss_fileSink; + tok = &output; + } + result = SS_ApplyBsdiff(source_filename, outname, part_filename, sink, tok, NULL); + LOGL(LOG_SSENGINE, "GenerateTarget Output is %d and result is %d\n", output, result); + if (output >= 0) { + fsync(output); + close(output); + } + + if (result != S_SS_SUCCESS) { + LOGE("applying patch failed %s\n", source_filename); + if (outname != NULL) { + unlink(outname); + } + goto Cleanup; + } + result = rename(outname, source_filename); + if (result != S_SS_SUCCESS) { + LOGE("fatal error %s\n", source_filename); + goto Cleanup; + } + if (strcmp(file_name, "piggy") == 0) { + snprintf(cmd, sizeof(cmd) - 1, + "%s/gzip -n -9 -c %s/%s/%s > %s/%s/%s.gz", + SS_KERNEL_WORKSPACE, SS_KERNEL_WORKSPACE, SS_KERNEL_UNPACK_DIR, file_name, + SS_KERNEL_WORKSPACE, SS_KERNEL_UNPACK_DIR, file_name); + result = _system_cmd_wait(cmd); + LOGL(LOG_SSENGINE, "ret for %s = %d\n", cmd, result); + unlink(source_filename); + } + unlink(part_filename); + } + //open new kernel file and append kernel parts to it in + snprintf(source_filename, sizeof(source_filename) - 1, "%s/%s", SS_KERNEL_WORKSPACE, SS_KERNEL_TARGET_NAME); + kp = fopen(source_filename, "w"); + for (i = 0; i < 4; i++) { + snprintf(part_filename, sizeof(part_filename) - 1, "%s/%s/%s", SS_KERNEL_WORKSPACE, SS_KERNEL_UNPACK_DIR, + kernel_parts[i]); + wp = fopen(part_filename, "r"); + fseek(wp, SEEK_SET, SEEK_END); + j = ftell(wp); + fseek(wp, SEEK_SET, SEEK_SET); + while (j-- > 0) { + a = fgetc(wp); + if(a != EOF)//wgid: 4428 + fputc(a, kp); + else + break; + } + fclose(wp); + unlink(part_filename); + } + fclose(fp); + fp = NULL; + fclose(kp); + + if (SS_LoadFile(source_filename, &source_file) == 0) + result = memcmp(source_file.sha1, target_sha1, SHA_DIGEST_SIZE); + if (result != S_SS_SUCCESS) { + LOGE("patch did not produce expected sha1 \n"); + SS_SetUpgradeState(E_SS_IMGSHA_MISMATCH); + goto Cleanup; + } + //Considering EMMC partition by deafult + + blk_cnt = ((ua_dataSS->update_cfg->target_img_size - 1) / SECTOR_SIZE) + 1; + result = write_to_blkdev((char *)ua_dataSS->parti_info->ua_blk_name, blk_start, blk_cnt, (char *)source_file.data); + if (result != S_SS_SUCCESS) { + LOGE("write of patched data to %s failed\n", ua_dataSS->parti_info->ua_blk_name); // All returns should go to CLEAN UP. + SS_SetUpgradeState(E_SS_IMGFLASHWRITEFAIL); + goto Cleanup; + } + + Cleanup: + SS_BackupSourceClear(); + SS_PatchSourceClear(); + SS_DeleteFile(NULL, SS_KERN_UNPK_SCRIPT_PATH); + SS_DeleteFolder(NULL, SS_KERNEL_WORKSPACE); + SS_Free(buf); + SS_Free(outname);//wgid: 20740 + if (result == S_SS_SUCCESS) + LOGL(LOG_SSENGINE, "************* SS_UpdateDeltaKernel SUCCESS *****************\n"); + else{ + LOGL(LOG_SSENGINE, "************* SS_UpdateDeltaKernel FAILED *****************\n"); + if(fp) + fclose(fp);//wgid:14711 + } + return result; + +} + +/*! + ********************************************************************************* + * SS_UpdateDeltaIMG + ********************************************************************************* + * + * @brief + * This is used to apply patch for an image during delta Image upgrade + * + * + * @param + * + * @return 0 - in case of success + * 1 - in case of error during patch application + * + ********************************************************************************* + */ + +int SS_UpdateDeltaIMG(ua_dataSS_t * ua_dataSS, int (*write_to_blkdev) (char *, int, int, char *)) +{ + uint8_t target_sha1[SHA_DIGEST_SIZE]; + uint8_t source_sha1[SHA_DIGEST_SIZE]; + const uint8_t current_target_sha1[SHA_DIGEST_SIZE]; + FileInfo source_file; + sha1_ctx_t ctx1; + MemorySinkInfo msi; + int result = S_SS_SUCCESS; + int blk_cnt; + int blk_start = 0; + int backupsrc = -1; + int use_backup_img = -1; + int fd = -1; + if (ParseSha1(ua_dataSS->update_cfg->target_sha1, target_sha1) != 0) { + LOGE("failed to parse tgt-sha1 \"%s\"\n", ua_dataSS->update_cfg->target_sha1); + return E_SS_FAILURE; + } + + source_file.size = ua_dataSS->update_cfg->soure_img_size; + source_file.data = NULL; + if (0 == gvalid_session) { + + if (ParseSha1(ua_dataSS->update_cfg->soure_sha1, source_sha1) != 0) { + LOGE("failed to parse Src-sha1 \"%s\"\n", ua_dataSS->update_cfg->soure_sha1); + return E_SS_FAILURE; + } + + if (SS_LoadPartition(ua_dataSS->parti_info->ua_blk_name, &source_file) == 0) { + if (memcmp(source_file.sha1, source_sha1, SHA_DIGEST_SIZE) == 0) { + LOGL(LOG_SSENGINE, "SS_UpdateDeltaIMG - Patch Can be applied\n"); + SS_Free(source_file.data); + } else if (memcmp(source_file.sha1, target_sha1, SHA_DIGEST_SIZE) == 0) { + LOGL(LOG_SSENGINE, "SS_UpdateDeltaIMG - Patch Already applied\n"); + SS_Free(source_file.data); + return S_SS_SUCCESS; + } else { + SS_Free(source_file.data); + source_file.data = NULL; + LOGL(LOG_SSENGINE, "SS_UpdateDeltaIMG - Source was currupted, Try loading from backup source\n"); + if (SS_LoadPartition(SS_BACKUP_SOURCE, &source_file) == 0) { + if (memcmp(source_file.sha1, source_sha1, SHA_DIGEST_SIZE) == 0) { + use_backup_img = 1; + SS_Free(source_file.data); + LOGL(LOG_SSENGINE, + "SS_UpdateDeltaIMG - Patch Can be applied from using backup file as source\n"); + } else { + SS_SetUpgradeState(E_SS_IMGSRCCURRUPTED); + SS_Free(source_file.data); + return E_SS_FAILURE; + } + } + } + } + } + if (use_backup_img == -1) { + backupsrc = SS_BackupSource(ua_dataSS->parti_info->ua_blk_name); + if (backupsrc != 0) { + LOGE("failed to Backup source File:[%s] \n", ua_dataSS->parti_info->ua_blk_name); + SS_SetUpgradeState(E_SS_IMGSRCBACKUPFAILED); + return E_SS_FAILURE; + } + } + SinkFn sink = NULL; + void *token = NULL; + + blk_cnt = ((ua_dataSS->update_cfg->target_img_size - 1) / SECTOR_SIZE) + 1; + + msi.buffer = SS_Malloc(blk_cnt * SECTOR_SIZE); + if (msi.buffer == NULL) { + LOGE("failed to alloc %ld bytes for output\n", (long)ua_dataSS->update_cfg->target_img_size); + SS_SetUpgradeState(E_SS_MALLOC_ERROR); + return E_SS_FAILURE; + } + msi.pos = 0; + msi.size = ua_dataSS->update_cfg->target_img_size; + sink = ss_memorySink; + token = &msi; + + sha1_init(&ctx1); + //if souce was corrupted, use backup to apply diff + if (use_backup_img == -1) + result = + SS_ApplyBsdiff((char *)ua_dataSS->parti_info->ua_blk_name, NULL, SS_PATCHFILE_SOURCE, sink, token, &ctx1); + else + result = SS_ApplyBsdiff(SS_BACKUP_SOURCE, NULL, SS_PATCHFILE_SOURCE, sink, token, &ctx1); + if (result != S_SS_SUCCESS) { + LOGE("failed to SS_ApplyBsdiff\n"); + SS_SetUpgradeState(E_SS_IMGRECOVERYWRITEFAILED); + goto Cleanup; + } + + sha1_final(&ctx1, (uint32_t *) & current_target_sha1); + result = memcmp(current_target_sha1, target_sha1, SHA_DIGEST_SIZE); + if (result != S_SS_SUCCESS) { + LOGE("patch did not produce expected sha1 \n"); + SS_SetUpgradeState(E_SS_IMGSHA_MISMATCH); + goto Cleanup; + } + //Considering EMMC partition by deafult + + if (ua_dataSS->update_cfg->update_type == DELTA_IMG) { + blk_cnt = ((ua_dataSS->update_cfg->target_img_size - 1) / SECTOR_SIZE) + 1; + result = write_to_blkdev((char *)ua_dataSS->parti_info->ua_blk_name, blk_start, blk_cnt, (char *)msi.buffer); + if (result != S_SS_SUCCESS) { + LOGE("write of patched data to %s failed\n", ua_dataSS->parti_info->ua_blk_name); // All returns should go to CLEAN UP. + SS_SetUpgradeState(E_SS_IMGFLASHWRITEFAIL); + goto Cleanup; + } + } else if (ua_dataSS->update_cfg->update_type == EXTRA && ua_dataSS->update_data->ua_temp_path) { + fd = open(ua_dataSS->update_data->ua_temp_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU); + if (fd < 0) { + LOGE("failed to open %s for write: %s\n", ua_dataSS->update_data->ua_temp_path, strerror(errno)); + SS_SetUpgradeState(E_SS_IMGRECOVERYWRITEFAILED); + result = E_SS_FAILURE; + goto Cleanup; + } + result = SS_WriteFile(NULL, fd, 0, msi.buffer, msi.size); + if (result != S_SS_SUCCESS) { + LOGE("failed to write\n"); + SS_SetUpgradeState(E_SS_IMGRECOVERYWRITEFAILED); + goto Cleanup; + } + fsync(fd); + } + else { + SS_SetUpgradeState(E_SS_IMGUPDATEFAILED); + result = E_SS_FAILURE; + LOGE("failed to apply patch - Invalid Update type params \n"); + } + + Cleanup: + SS_BackupSourceClear(); + SS_PatchSourceClear(); + if (msi.buffer) + SS_Free(msi.buffer); + if (fd >= 0) + close(fd); + if (result == S_SS_SUCCESS) + LOGL(LOG_SSENGINE, "************* SS_UpdateDeltaIMG SUCCESS *****************\n"); + return result; + +} diff --git a/ss_patch/ss_patchdelta.h b/ss_patch/ss_patchdelta.h new file mode 100755 index 0000000..db54078 --- /dev/null +++ b/ss_patch/ss_patchdelta.h @@ -0,0 +1,79 @@ +/* + * libtota + * + * Copyright (c) 2017 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 _SS_PATCHDELTA_H +#define _SS_PATCHDELTA_H + +#include +#include "sha1.h" +#include "unistd.h" +#include "fcntl.h" +#include "errno.h" +#include "SS_Engine_Update.h" + +//#define ENHANCED_BSDIFF +#define SS_UPDATE_FS 0 +#define SS_UPDATE_IMG 1 +//#define SHA_DIGEST_SIZE 20 +typedef struct { + int type; + ssize_t size; + char *data; +} Value; +typedef struct _Patch { + uint8_t sha1[SHA_DIGEST_SIZE]; + const char *patch_filename; +} Patch; + +typedef struct _FileInfo { + unsigned char sha1[20]; //SHA_DIGEST_SIZE 20 + unsigned char *data; + int size; + struct stat st; +} FileInfo; + +typedef ssize_t(*SinkFn) (unsigned char *, ssize_t, void *); + +int ParseSha1(const char *str, uint8_t * digest); + +void ShowBSDiffLicense(); +int ApplyBSDiffPatch(const unsigned char *old_data, ssize_t old_size, + const Value * patch, ssize_t patch_offset, SinkFn sink, void *token, sha1_ctx_t * ctx1); +int ApplyBSDiffPatchMem(const unsigned char *old_data, ssize_t old_size, + const Value * patch, ssize_t patch_offset, unsigned char **new_data, ssize_t * new_size); +//int ApplyOptimizedBSDiffPatch(const unsigned char* old_data,void* token, +// const Value* patch, SinkFn sink,sha1_ctx_t* ctx1); + +int SS_LoadPartition(const char *filename, FileInfo * file); +int SS_LoadFile(const char *filename, FileInfo * file); +extern void SS_SetUpgradeState(int Val); +extern long SS_GetAvailableFreeSpace(void *pbUserData, const char *partition_name, SS_UINT32 * available_flash_size); +extern int SS_BackupSource(const char *source_filename); +extern int SS_ApplyBsdiff(char *oldfile, char *newfile, char *patch, SinkFn sink, void *token, sha1_ctx_t * ctx1); +extern int SS_BackupSourceClear(); +extern int SS_PatchSourceClear(); +extern long SS_WriteFile(void *pbUserData, + long wHandle, SS_UINT32 dwPosition, unsigned char *pbBuffer, SS_UINT32 dwSize); +extern void SS_Free(void * pMemBlock); +extern long SS_CopyFile(void * pbUserData, const char * strFromPath, const char * strToPath); +extern long SS_DeleteFolder(void * pbUserData, const char * strPath); +extern long SS_DeleteFile(void * pbUserData, const char * strPath); +extern int tar_get_item_size(char * tar, char * item); +extern int tar_extract_file(char * tar, char * item, char * pathname); +extern int _system_cmd_wait(const char * command); +#endif diff --git a/tota.pc.in b/tota.pc.in new file mode 100755 index 0000000..02eaba9 --- /dev/null +++ b/tota.pc.in @@ -0,0 +1,13 @@ +# Package Information for pkg-config + +prefix=@PREFIX@ +exec_prefix=@EXEC_PREFIX@ +libdir=@LIBDIR@ +includedir=@INCLUDEDIR@ + +Name: libtota +Description: fota function library +Version: @VERSION@ +Requires: +Libs: -L${libdir} +Cflags: -I${includedir} -- 2.7.4 From ccd5750c9319f59c7f1cd9247e195e338e0f4df9 Mon Sep 17 00:00:00 2001 From: Sunmin Lee Date: Thu, 13 Jul 2017 10:14:02 +0900 Subject: [PATCH 03/13] Separate common part of bspatch to shared code Separate same functions (apply_patch, Decode2) to common file so it can be used as shared code. Change-Id: Ie22f8aaf120f723925be027a8694a3f9e1f905c3 Signed-off-by: Sunmin Lee --- CMakeLists.txt | 2 + bsdiff/CMakeLists.txt | 7 +- bsdiff/ss_bsdiff.c | 2 +- bsdiff/ss_bspatch.c | 252 +------------------------------------- bsdiff/ss_bspatch_common.c | 292 +++++++++++++++++++++++++++++++++++++++++++++ bsdiff/ss_bspatch_common.h | 17 +++ packaging/libtota.spec | 4 +- ss_patch/ss_bspatch.c | 257 +-------------------------------------- 8 files changed, 325 insertions(+), 508 deletions(-) create mode 100755 bsdiff/ss_bspatch_common.c create mode 100644 bsdiff/ss_bspatch_common.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 814ee22..e63ad0c 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ SET(SRCS ss_engine/fota_tar.c ss_patch/ss_bspatch.c ss_patch/ss_patchdelta.c + bsdiff/ss_bspatch_common.c ) SET(HEADERS ss_engine/fota_common.h @@ -33,6 +34,7 @@ SET(LIBNAME "tota") INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/ss_engine) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/ss_patch) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/bsdiff) INCLUDE(FindPkgConfig) pkg_check_modules(packages REQUIRED diff --git a/bsdiff/CMakeLists.txt b/bsdiff/CMakeLists.txt index cbdd658..a4cdc59 100755 --- a/bsdiff/CMakeLists.txt +++ b/bsdiff/CMakeLists.txt @@ -2,7 +2,12 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.6) PROJECT(ss_bsdiff C) SET(ss_bsdiff_SRCS ss_bsdiff.c) -SET(ss_bspatch_SRCS ss_bspatch.c) +SET(ss_bspatch_SRCS + ss_bspatch_common.c + ss_bspatch.c +) + +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/bsdiff) INCLUDE(FindPkgConfig) pkg_check_modules(${PROJECT_NAME}_pkgs REQUIRED lib7zip libdivsufsort) diff --git a/bsdiff/ss_bsdiff.c b/bsdiff/ss_bsdiff.c index cea3385..94ce98a 100755 --- a/bsdiff/ss_bsdiff.c +++ b/bsdiff/ss_bsdiff.c @@ -74,7 +74,7 @@ void get_time_stamp(void) #endif #ifdef SUFSORT_MOD //supporting only 32 bit divsufsort for now. -#include "divsufsort.h" +#include #endif #define MIN(x,y) (((x)<(y)) ? (x) : (y)) diff --git a/bsdiff/ss_bspatch.c b/bsdiff/ss_bspatch.c index 104e24a..be9ef1c 100755 --- a/bsdiff/ss_bspatch.c +++ b/bsdiff/ss_bspatch.c @@ -29,12 +29,6 @@ * recursive implementation to avoid buffer overflow problems */ #define _CRT_SECURE_NO_WARNINGS -#define CONST_MEMORY_USAGE 16384 -#define PATCH_FILE_FORMAT_MOD -#define BSDIFF_HEADER "BSDIFF40" -#define SSDIFF_HEADER "SSDIFF40" -#define MULTI_THREADING -#include #include #include #include @@ -49,50 +43,13 @@ #include #include +#include "ss_bspatch_common.h" + const char *kCantReadMessage = "Can not read input file"; const char *kCantWriteMessage = "Can not write output file"; const char *kCantAllocateMessage = "Can not allocate memory"; const char *kDataErrorMessage = "Data error"; -static void *SzAlloc(void *p, size_t size) -{ - p = p; - return MyAlloc(size); -} - -static void SzFree(void *p, void *address) -{ - p = p; - MyFree(address); -} -static ISzAlloc g_Alloc = { SzAlloc, SzFree }; - -static off_t offtin(u_char *buf) -{ - off_t y; - - y = buf[7] & 0x7F; - y = y * 256; - y += buf[6]; - y = y * 256; - y += buf[5]; - y = y * 256; - y += buf[4]; - y = y * 256; - y += buf[3]; - y = y * 256; - y += buf[2]; - y = y * 256; - y += buf[1]; - y = y * 256; - y += buf[0]; - - if (buf[7] & 0x80) - y = -y; - - return y; -} - void PrintHelp(char *buffer) { strcat(buffer, "\nLZMA Utility " MY_VERSION_COPYRIGHT_DATE "\n" @@ -120,209 +77,6 @@ int PrintUserError(char *buffer) return PrintError(buffer, "Incorrect command"); } -#define IN_BUF_SIZE (1 << 16) -#define OUT_BUF_SIZE (1 << 16) - -static SRes Decode2(CLzmaDec *state, ISeqOutStream *outStream, ISeqInStream *inStream, - UInt64 *unpackSize,unsigned char *dec_data) -{ - int thereIsSize = (*unpackSize != (UInt64)(Int64) - 1); - UInt64 offset = 0; - Byte inBuf[IN_BUF_SIZE]; - Byte outBuf[OUT_BUF_SIZE]; - size_t inPos = 0, inSize = 0, outPos = 0; - - LzmaDec_Init(state); - - offset = 0; - - for (;;) { - if (inPos == inSize) { - inSize = IN_BUF_SIZE; - RINOK(inStream->Read(inStream, inBuf, &inSize)); - inPos = 0; - } - - SRes res; - SizeT inProcessed = inSize - inPos; - SizeT outProcessed = OUT_BUF_SIZE - outPos; - ELzmaFinishMode finishMode = LZMA_FINISH_ANY; - ELzmaStatus status; - - if (thereIsSize && outProcessed > *unpackSize) { - outProcessed = (SizeT) * unpackSize; - finishMode = LZMA_FINISH_END; - } - - res = LzmaDec_DecodeToBuf(state, outBuf + outPos, &outProcessed, - inBuf + inPos, &inProcessed, finishMode, &status); - inPos += inProcessed; - outPos += outProcessed; - *unpackSize -= outProcessed; - memcpy(dec_data + offset, outBuf, outProcessed); - offset += outProcessed; - - outPos = 0; - - if ((res != SZ_OK) || (thereIsSize && *unpackSize == 0)) - return res; - - if (inProcessed == 0 && outProcessed == 0) { - if (thereIsSize || status != LZMA_STATUS_FINISHED_WITH_MARK) - return SZ_ERROR_DATA; - return res; - } - } -} - -int apply_patch(char *oldfile, char *patch_buffer, unsigned char **dest_buf, ssize_t *dest_size) -{ - int fd = -1, result = 0; - off_t oldsize, newsize; - u_char header[16], buf[8]; - u_char *old = NULL; - off_t oldpos, newpos; - off_t ctrl[4]; /////////////////////////////////////THREAD - off_t total_write; /////////////////////////////////////////THREAD - off_t j; - off_t memory_usage = CONST_MEMORY_USAGE; - off_t match_size; - off_t patch_buffer_offset = 0; - bool flag; - - /* - File format: - 0 8 "BSDIFF40" - 8 8 X - 16 8 Y - 24 8 sizeof(newfile) - 32 X bzip2(control block) - 32+X Y bzip2(diff block) - 32+X+Y ??? bzip2(extra block) - with control block a set of triples (x,y,z) meaning "add x bytes - from oldfile to x bytes from the diff block; copy y bytes from the - extra block; seek forwards in oldfile by z bytes". - */ - // Read header - if (patch_buffer) - memcpy(header, patch_buffer, 16); - else { - printf("%s().%d Corrupt decoded patch buffer\n", __FUNCTION__, __LINE__); - return 1; - } - - /* Check for appropriate magic */ - if (memcmp(header, BSDIFF_HEADER, 8) != 0 && memcmp(header, SSDIFF_HEADER, 8) != 0) { - printf("%s().%d Patch buffer header corrupt\n", __FUNCTION__, __LINE__ ); - return 1; - } - - /* Read lengths from header */ - newsize = offtin(header + 8); - - if ((newsize < 0)) { - printf("%s().%d Patch buffer corrupt\n", __FUNCTION__, __LINE__ ); - return 1; - } - - /* Cset patch_buffer_offset at the right place */ - patch_buffer_offset += 16; - - if (((fd = open(oldfile, O_RDONLY, 0)) < 0) || - ((oldsize = lseek(fd, 0, SEEK_END)) == -1) || - ((old = malloc(memory_usage + 1)) == NULL) || - (lseek(fd, 0, SEEK_SET) != 0)) { - printf("Corruption in old file %s\n", oldfile); - result = 1; - goto Cleanup; - } - - if ((*dest_buf = malloc(newsize + 1)) == NULL) { - printf("Corruption in old file %s\n", oldfile); - result = 1; - goto Cleanup; - } - oldpos = 0; - newpos = 0; - - total_write = 0; - - while (total_write != newsize) { - /* Read control data */ - for (j = 0; j <= 3; j++) { - memcpy(buf, patch_buffer + patch_buffer_offset, 8); - patch_buffer_offset += 8; - ctrl[j] = offtin(buf); - }; - - total_write += (ctrl[0] + ctrl[1]); - newpos = ctrl[3]; - oldpos = ctrl[2]; - - ////////////////////////////////////////////////////////////////////////////////// - flag = true; - match_size = ctrl[0]; - while (flag == true) { - if (match_size <= memory_usage) { - if (pread(fd, old, match_size, oldpos) != match_size) { - printf("Corruption in old file %s\n", oldfile); - result = 1; - goto Cleanup; - } - if (newpos + match_size > newsize) { - printf("%s().%d Corrupt patch\n", __FUNCTION__, __LINE__ ); - result = 1; - goto Cleanup; - } - memcpy((*dest_buf) + newpos, patch_buffer + patch_buffer_offset, match_size); - patch_buffer_offset += match_size; - for (j = 0; j < match_size; j++) { - (*dest_buf)[newpos + j] += old[j]; - } - newpos += match_size; - flag = false; - } else { - if (pread(fd, old, memory_usage, oldpos) != memory_usage) { - printf("%s().%d Corruption in old file %s\n", __FUNCTION__, __LINE__ , oldfile); - result = 1; - goto Cleanup; - } - if (newpos + memory_usage > newsize) { - printf("%s().%d Corrupt patch\n", __FUNCTION__, __LINE__ ); - result = 1; - goto Cleanup; - } - memcpy((*dest_buf) + newpos, patch_buffer + patch_buffer_offset, memory_usage); - patch_buffer_offset += memory_usage; - for (j = 0; j < memory_usage; j++) - (*dest_buf)[newpos + j] += old[j]; - match_size -= memory_usage; - oldpos += memory_usage; - newpos += memory_usage; - } - } - - //////////////////////////////////////////////////////////////////////////////////////// - /* Sanity-check */ - if (newpos + ctrl[1] > newsize) { - printf("%s().%d Corrupt patch\n", __FUNCTION__, __LINE__ ); - result = 1; - goto Cleanup; - } - /* Read extra string */ - memcpy((*dest_buf) + newpos, patch_buffer + patch_buffer_offset, ctrl[1]); - patch_buffer_offset += ctrl[1]; - }; - *dest_size = newsize; -Cleanup: - //close old file - if (fd >= 0) - close(fd); - if (old) - free(old); - return result; -} - int main2(int numArgs, const char *args[], char *rs) { CFileSeqInStream inStream; @@ -368,7 +122,7 @@ int main2(int numArgs, const char *args[], char *rs) dest_len = unpackSize; LzmaDec_Construct(&state); RINOK(LzmaDec_Allocate(&state, header, LZMA_PROPS_SIZE, &g_Alloc)); - res = Decode2(&state, &outStream, &inStream.s, &unpackSize,buf_res); + res = Decode2(&state, &outStream, &inStream.s, &unpackSize, buf_res); LzmaDec_Free(&state, &g_Alloc); File_Close(&inStream.file); if (apply_patch(args[1], buf_res, &new_data, &new_size) != 0) { diff --git a/bsdiff/ss_bspatch_common.c b/bsdiff/ss_bspatch_common.c new file mode 100755 index 0000000..baad6bf --- /dev/null +++ b/bsdiff/ss_bspatch_common.c @@ -0,0 +1,292 @@ +/*- + * Copyright 2003-2005 Colin Percival + * Copyright 2012 Matthew Endsley + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted providing that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Modifications are made in reimplementing suffix sort array generation + * and how the data is read and written to.Iterative part replaced the + * recursive implementation to avoid buffer overflow problems + */ +//#define ZLIB_MOD //not stable yet. +//#define MAX_MATCH_SIZE // define ( MAX_MATCH_SIZE or CONST_MEMORY_USAGE ) or ( none of them ) +#define CONST_MEMORY_USAGE (64*1024) //tests show smallest time when using 64 kb +#define PATCH_FILE_FORMAT_MOD +#define BSDIFF_HEADER "BSDIFF40" +#define SSDIFF_HEADER "SSDIFF40" +//#define MULTI_THREADING +#include +#include +#include + +#include +#include +#include + +#include +#include <7zFile.h> +#include <7zVersion.h> +#include +#include + +static void *SzAlloc(void *p, size_t size) +{ + p = p; + return MyAlloc(size); +} + +static void SzFree(void *p, void *address) +{ + p = p; + MyFree(address); +} +ISzAlloc g_Alloc = { SzAlloc, SzFree }; + +static off_t offtin(u_char *buf) +{ + off_t y; + + y = buf[7] & 0x7F; + y = y * 256; + y += buf[6]; + y = y * 256; + y += buf[5]; + y = y * 256; + y += buf[4]; + y = y * 256; + y += buf[3]; + y = y * 256; + y += buf[2]; + y = y * 256; + y += buf[1]; + y = y * 256; + y += buf[0]; + + if (buf[7] & 0x80) + y = -y; + + return y; +} + +#define IN_BUF_SIZE (1 << 16) +#define OUT_BUF_SIZE (1 << 16) + +SRes Decode2(CLzmaDec *state, ISeqOutStream *outStream, ISeqInStream *inStream, + UInt64 *unpackSize, unsigned char *dec_data) +{ + int thereIsSize = (*unpackSize != (UInt64)(Int64) - 1); + UInt64 offset = 0; + Byte inBuf[IN_BUF_SIZE]; + Byte outBuf[OUT_BUF_SIZE]; + size_t inPos = 0, inSize = 0, outPos = 0; + + LzmaDec_Init(state); + + offset = 0; + + for (;;) { + if (inPos == inSize) { + inSize = IN_BUF_SIZE; + RINOK(inStream->Read(inStream, inBuf, &inSize)); + inPos = 0; + } + + SRes res; + SizeT inProcessed = inSize - inPos; + SizeT outProcessed = OUT_BUF_SIZE - outPos; + ELzmaFinishMode finishMode = LZMA_FINISH_ANY; + ELzmaStatus status; + + if (thereIsSize && outProcessed > *unpackSize) { + outProcessed = (SizeT) * unpackSize; + finishMode = LZMA_FINISH_END; + } + + res = LzmaDec_DecodeToBuf(state, outBuf + outPos, &outProcessed, + inBuf + inPos, &inProcessed, finishMode, &status); + inPos += inProcessed; + outPos += outProcessed; + *unpackSize -= outProcessed; + memcpy(dec_data + offset, outBuf, outProcessed); + offset += outProcessed; + + outPos = 0; + + if ((res != SZ_OK) || (thereIsSize && *unpackSize == 0)) + return res; + + if (inProcessed == 0 && outProcessed == 0) { + if (thereIsSize || status != LZMA_STATUS_FINISHED_WITH_MARK) + return SZ_ERROR_DATA; + return res; + } + } +} + +int apply_patch(char *oldfile, char *patch_buffer, unsigned char **dest_buf, ssize_t *dest_size) +{ + int fd = -1, result = 0; + off_t oldsize, newsize; + u_char header[16], buf[8]; + u_char *old = NULL; + off_t oldpos, newpos; + off_t ctrl[4]; /////////////////////////////////////THREAD + off_t total_write; /////////////////////////////////////////THREAD + off_t j; + off_t memory_usage = CONST_MEMORY_USAGE; + off_t match_size; + off_t patch_buffer_offset = 0; + bool flag; + + /* + File format: + 0 8 "BSDIFF40" + 8 8 X + 16 8 Y + 24 8 sizeof(newfile) + 32 X bzip2(control block) + 32+X Y bzip2(diff block) + 32+X+Y ??? bzip2(extra block) + with control block a set of triples (x,y,z) meaning "add x bytes + from oldfile to x bytes from the diff block; copy y bytes from the + extra block; seek forwards in oldfile by z bytes". + */ + // Read header + if (patch_buffer) + memcpy(header, patch_buffer, 16); + else { + printf("%s().%d Corrupt decoded patch buffer\n", __FUNCTION__, __LINE__); + return 1; + } + + /* Check for appropriate magic */ + if (memcmp(header, BSDIFF_HEADER, 8) != 0 && memcmp(header, SSDIFF_HEADER, 8) != 0) { + printf("%s().%d Patch buffer header corrupt\n", __FUNCTION__, __LINE__ ); + return 1; + } + + /* Read lengths from header */ + newsize = offtin(header + 8); + + if ((newsize < 0)) { + printf("%s().%d Patch buffer corrupt\n", __FUNCTION__, __LINE__ ); + return 1; + } + + /* Cset patch_buffer_offset at the right place */ + patch_buffer_offset += 16; + + if (((fd = open(oldfile, O_RDONLY, 0)) < 0) || + ((oldsize = lseek(fd, 0, SEEK_END)) == -1) || + ((old = malloc(memory_usage + 1)) == NULL) || + (lseek(fd, 0, SEEK_SET) != 0)) { + printf("Corruption in old file %s\n", oldfile); + result = 1; + goto Cleanup; + } + + if ((*dest_buf = malloc(newsize + 1)) == NULL) { + printf("Corruption in old file %s\n", oldfile); + result = 1; + goto Cleanup; + } + oldpos = 0; + newpos = 0; + + total_write = 0; + + while (total_write != newsize) { + /* Read control data */ + for (j = 0; j <= 3; j++) { + memcpy(buf, patch_buffer + patch_buffer_offset, 8); + patch_buffer_offset += 8; + ctrl[j] = offtin(buf); + }; + + total_write += (ctrl[0] + ctrl[1]); + newpos = ctrl[3]; + oldpos = ctrl[2]; + + ////////////////////////////////////////////////////////////////////////////////// + flag = true; + match_size = ctrl[0]; + while (flag == true) { + if (match_size <= memory_usage) { + if (pread(fd, old, match_size, oldpos) != match_size) { + printf("Corruption in old file %s\n", oldfile); + result = 1; + goto Cleanup; + } + if (newpos + match_size > newsize) { + printf("%s().%d Corrupt patch\n", __FUNCTION__, __LINE__ ); + result = 1; + goto Cleanup; + } + memcpy((*dest_buf) + newpos, patch_buffer + patch_buffer_offset, match_size); + patch_buffer_offset += match_size; + for (j = 0; j < match_size; j++) { + (*dest_buf)[newpos + j] += old[j]; + } + newpos += match_size; + flag = false; + } else { + if (pread(fd, old, memory_usage, oldpos) != memory_usage) { + printf("%s().%d Corruption in old file %s\n", __FUNCTION__, __LINE__ , oldfile); + result = 1; + goto Cleanup; + } + if (newpos + memory_usage > newsize) { + printf("%s().%d Corrupt patch\n", __FUNCTION__, __LINE__ ); + result = 1; + goto Cleanup; + } + memcpy((*dest_buf) + newpos, patch_buffer + patch_buffer_offset, memory_usage); + patch_buffer_offset += memory_usage; + for (j = 0; j < memory_usage; j++) + (*dest_buf)[newpos + j] += old[j]; + match_size -= memory_usage; + oldpos += memory_usage; + newpos += memory_usage; + } + } + + //////////////////////////////////////////////////////////////////////////////////////// + /* Sanity-check */ + if (newpos + ctrl[1] > newsize) { + printf("%s().%d Corrupt patch\n", __FUNCTION__, __LINE__ ); + result = 1; + goto Cleanup; + } + /* Read extra string */ + memcpy((*dest_buf) + newpos, patch_buffer + patch_buffer_offset, ctrl[1]); + patch_buffer_offset += ctrl[1]; + }; + *dest_size = newsize; +Cleanup: + //close old file + if (fd >= 0) + close(fd); + if (old) + free(old); + return result; +} diff --git a/bsdiff/ss_bspatch_common.h b/bsdiff/ss_bspatch_common.h new file mode 100644 index 0000000..bd59396 --- /dev/null +++ b/bsdiff/ss_bspatch_common.h @@ -0,0 +1,17 @@ +#ifndef _SS_BSPATCH_COMMON_H +#define _SS_BSPATCH_COMMON_H 1 + +#include +#include <7zFile.h> +#include <7zVersion.h> +#include +#include + +extern ISzAlloc g_Alloc; + +SRes Decode2(CLzmaDec *state, ISeqOutStream *outStream, ISeqInStream *inStream, + UInt64 *unpackSize, unsigned char *dec_data); + +int apply_patch(char *oldfile, char *patch_buffer, unsigned char **dest_buf, ssize_t *dest_size); + +#endif /* _SS_BSPATCH_COMMON_H */ diff --git a/packaging/libtota.spec b/packaging/libtota.spec index a798251..fb98203 100755 --- a/packaging/libtota.spec +++ b/packaging/libtota.spec @@ -1,8 +1,8 @@ Name: libtota Summary: fota update library ExclusiveArch: %{arm} -Version: 0.1.0 -Release: 1 +Version: 0.1.1 +Release: 2 Group: System License: Apache-2.0 and BSD-2-Clause and BSD-3-Clause and PD Source0: %{name}-%{version}.tar.gz diff --git a/ss_patch/ss_bspatch.c b/ss_patch/ss_bspatch.c index be75265..bf61e29 100755 --- a/ss_patch/ss_bspatch.c +++ b/ss_patch/ss_bspatch.c @@ -28,273 +28,20 @@ * and how the data is read and written to.Iterative part replaced the * recursive implementation to avoid buffer overflow problems */ -//#define ZLIB_MOD //not stable yet. -//#define MAX_MATCH_SIZE // define ( MAX_MATCH_SIZE or CONST_MEMORY_USAGE ) or ( none of them ) -#define CONST_MEMORY_USAGE (64*1024) //tests show smallest time when using 64 kb -#define PATCH_FILE_FORMAT_MOD -#define BSDIFF_HEADER "BSDIFF40" -#define SSDIFF_HEADER "SSDIFF40" -//#define MULTI_THREADING -#include +#include #include -#include -#include #include "ss_patchdelta.h" #include "fota_common.h" #include "sha1.h" #include "SS_Engine_Errors.h" -#include -#include -#include - +#include "ss_bspatch_common.h" #include #include <7zFile.h> #include <7zVersion.h> #include #include -static void *SzAlloc(void *p, size_t size) -{ - p = p; - return MyAlloc(size); -} - -static void SzFree(void *p, void *address) -{ - p = p; - MyFree(address); -} -static ISzAlloc g_Alloc = { SzAlloc, SzFree }; - -static off_t offtin(u_char *buf) -{ - off_t y; - - y = buf[7] & 0x7F; - y = y * 256; - y += buf[6]; - y = y * 256; - y += buf[5]; - y = y * 256; - y += buf[4]; - y = y * 256; - y += buf[3]; - y = y * 256; - y += buf[2]; - y = y * 256; - y += buf[1]; - y = y * 256; - y += buf[0]; - - if (buf[7] & 0x80) - y = -y; - - return y; -} - -#define IN_BUF_SIZE (1 << 16) -#define OUT_BUF_SIZE (1 << 16) - -static SRes Decode2(CLzmaDec *state, ISeqOutStream *outStream, ISeqInStream *inStream, - UInt64 *unpackSize,unsigned char *dec_data) -{ - int thereIsSize = (*unpackSize != (UInt64)(Int64) - 1); - UInt64 offset = 0; - Byte inBuf[IN_BUF_SIZE]; - Byte outBuf[OUT_BUF_SIZE]; - size_t inPos = 0, inSize = 0, outPos = 0; - - LzmaDec_Init(state); - - offset = 0; - - for (;;) { - if (inPos == inSize) { - inSize = IN_BUF_SIZE; - RINOK(inStream->Read(inStream, inBuf, &inSize)); - inPos = 0; - } - - SRes res; - SizeT inProcessed = inSize - inPos; - SizeT outProcessed = OUT_BUF_SIZE - outPos; - ELzmaFinishMode finishMode = LZMA_FINISH_ANY; - ELzmaStatus status; - - if (thereIsSize && outProcessed > *unpackSize) { - outProcessed = (SizeT) * unpackSize; - finishMode = LZMA_FINISH_END; - } - - res = LzmaDec_DecodeToBuf(state, outBuf + outPos, &outProcessed, - inBuf + inPos, &inProcessed, finishMode, &status); - inPos += inProcessed; - outPos += outProcessed; - *unpackSize -= outProcessed; - memcpy(dec_data + offset, outBuf, outProcessed); - offset += outProcessed; - - outPos = 0; - - if ((res != SZ_OK) || (thereIsSize && *unpackSize == 0)) - return res; - - if (inProcessed == 0 && outProcessed == 0) { - if (thereIsSize || status != LZMA_STATUS_FINISHED_WITH_MARK) - return SZ_ERROR_DATA; - return res; - } - } -} - -int apply_patch(char *oldfile, char *patch_buffer, unsigned char **dest_buf, ssize_t *dest_size) -{ - int fd = -1, result = 0; - off_t oldsize, newsize; - u_char header[16], buf[8]; - u_char *old = NULL; - off_t oldpos, newpos; - off_t ctrl[4]; /////////////////////////////////////THREAD - off_t total_write; /////////////////////////////////////////THREAD - off_t j; - off_t memory_usage = CONST_MEMORY_USAGE; - off_t match_size; - off_t patch_buffer_offset = 0; - bool flag; - - /* - File format: - 0 8 "BSDIFF40" - 8 8 X - 16 8 Y - 24 8 sizeof(newfile) - 32 X bzip2(control block) - 32+X Y bzip2(diff block) - 32+X+Y ??? bzip2(extra block) - with control block a set of triples (x,y,z) meaning "add x bytes - from oldfile to x bytes from the diff block; copy y bytes from the - extra block; seek forwards in oldfile by z bytes". - */ - // Read header - if (patch_buffer) - memcpy(header, patch_buffer, 16); - else { - printf("%s().%d Corrupt decoded patch buffer\n", __FUNCTION__, __LINE__); - return 1; - } - - /* Check for appropriate magic */ - if (memcmp(header, BSDIFF_HEADER, 8) != 0 && memcmp(header, SSDIFF_HEADER, 8) != 0) { - printf("%s().%d Patch buffer header corrupt\n", __FUNCTION__, __LINE__ ); - return 1; - } - - /* Read lengths from header */ - newsize = offtin(header + 8); - - if ((newsize < 0)) { - printf("%s().%d Patch buffer corrupt\n", __FUNCTION__, __LINE__ ); - return 1; - } - - /* Cset patch_buffer_offset at the right place */ - patch_buffer_offset += 16; - - if (((fd = open(oldfile, O_RDONLY, 0)) < 0) || - ((oldsize = lseek(fd, 0, SEEK_END)) == -1) || - ((old = malloc(memory_usage + 1)) == NULL) || - (lseek(fd, 0, SEEK_SET) != 0)) { - printf("Corruption in old file %s\n", oldfile); - result = 1; - goto Cleanup; - } - - if ((*dest_buf = malloc(newsize + 1)) == NULL) { - printf("Corruption in old file %s\n", oldfile); - result = 1; - goto Cleanup; - } - oldpos = 0; - newpos = 0; - - total_write = 0; - - while (total_write != newsize) { - /* Read control data */ - for (j = 0; j <= 3; j++) { - memcpy(buf, patch_buffer + patch_buffer_offset, 8); - patch_buffer_offset += 8; - ctrl[j] = offtin(buf); - }; - - total_write += (ctrl[0] + ctrl[1]); - newpos = ctrl[3]; - oldpos = ctrl[2]; - - ////////////////////////////////////////////////////////////////////////////////// - flag = true; - match_size = ctrl[0]; - while (flag == true) { - if (match_size <= memory_usage) { - if (pread(fd, old, match_size, oldpos) != match_size) { - printf("Corruption in old file %s\n", oldfile); - result = 1; - goto Cleanup; - } - if (newpos + match_size > newsize) { - printf("%s().%d Corrupt patch\n", __FUNCTION__, __LINE__ ); - result = 1; - goto Cleanup; - } - memcpy((*dest_buf) + newpos, patch_buffer + patch_buffer_offset, match_size); - patch_buffer_offset += match_size; - for (j = 0; j < match_size; j++) { - (*dest_buf)[newpos + j] += old[j]; - } - newpos += match_size; - flag = false; - } else { - if (pread(fd, old, memory_usage, oldpos) != memory_usage) { - printf("%s().%d Corruption in old file %s\n", __FUNCTION__, __LINE__ , oldfile); - result = 1; - goto Cleanup; - } - if (newpos + memory_usage > newsize) { - printf("%s().%d Corrupt patch\n", __FUNCTION__, __LINE__ ); - result = 1; - goto Cleanup; - } - memcpy((*dest_buf) + newpos, patch_buffer + patch_buffer_offset, memory_usage); - patch_buffer_offset += memory_usage; - for (j = 0; j < memory_usage; j++) - (*dest_buf)[newpos + j] += old[j]; - match_size -= memory_usage; - oldpos += memory_usage; - newpos += memory_usage; - } - } - - //////////////////////////////////////////////////////////////////////////////////////// - /* Sanity-check */ - if (newpos + ctrl[1] > newsize) { - printf("%s().%d Corrupt patch\n", __FUNCTION__, __LINE__ ); - result = 1; - goto Cleanup; - } - /* Read extra string */ - memcpy((*dest_buf) + newpos, patch_buffer + patch_buffer_offset, ctrl[1]); - patch_buffer_offset += ctrl[1]; - }; - *dest_size = newsize; -Cleanup: - //close old file - if (fd >= 0) - close(fd); - if (old) - free(old); - return result; -} int SS_ApplyBsdiff(char *oldfile, char *newfile, char *patch, SinkFn sink, void *token, sha1_ctx_t * ctx1) { -- 2.7.4 From b2b7ab296071d67213b32bd20156ca0e12e60a81 Mon Sep 17 00:00:00 2001 From: Sunmin Lee Date: Thu, 18 May 2017 16:17:42 +0900 Subject: [PATCH 04/13] Provide shared library also From this patch, libtota can be used as shared library. Change-Id: I593d946cbcc37e17eb8637f6feb5a91ad4256a0e Signed-off-by: Sunmin Lee --- CMakeLists.txt | 10 ++++++++-- packaging/libtota.spec | 13 ++++++++----- tota.pc.in | 2 +- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e63ad0c..cbc521f 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +45,7 @@ FOREACH(flag ${packages_CFLAGS}) SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") ENDFOREACH(flag) -SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fvisibility=hidden -Wall") +SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -Wall") SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -g ") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}") @@ -63,14 +63,20 @@ SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") CONFIGURE_FILE(tota.pc.in tota.pc @ONLY) +ADD_LIBRARY(${LIBNAME}-shared SHARED ${SRCS}) +TARGET_LINK_LIBRARIES(${LIBNAME}-shared ${packages_LDFLAGS}) +SET_TARGET_PROPERTIES(${LIBNAME}-shared PROPERTIES VERSION ${VERSION}) +SET_TARGET_PROPERTIES(${LIBNAME}-shared PROPERTIES OUTPUT_NAME ${LIBNAME}) +INSTALL(TARGETS ${LIBNAME}-shared DESTINATION ${LIB_INSTALL_DIR}) + ADD_LIBRARY(${LIBNAME} STATIC ${SRCS}) TARGET_LINK_LIBRARIES(${LIBNAME} ${packages_LDFLAGS}) +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/libtota.a DESTINATION ${LIB_INSTALL_DIR}) #CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/modulename-api.pc.in ${CMAKE_CURRENT_BINARY_DIR}/modulename-api.pc @ONLY) #INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/modulename-api.pc DESTINATION lib/pkgconfig) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/tota.pc DESTINATION lib/pkgconfig) FOREACH(hfile ${HEADERS}) INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${hfile} DESTINATION include) ENDFOREACH(hfile) -INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/libtota.a DESTINATION /usr/lib) ADD_SUBDIRECTORY(bsdiff) diff --git a/packaging/libtota.spec b/packaging/libtota.spec index fb98203..8dad2cd 100755 --- a/packaging/libtota.spec +++ b/packaging/libtota.spec @@ -1,8 +1,8 @@ Name: libtota Summary: fota update library ExclusiveArch: %{arm} -Version: 0.1.1 -Release: 2 +Version: 0.1.2 +Release: 3 Group: System License: Apache-2.0 and BSD-2-Clause and BSD-3-Clause and PD Source0: %{name}-%{version}.tar.gz @@ -38,7 +38,9 @@ This package offers these tools for TOTA. export LDFLAGS+="-Wl,--rpath=%{_prefix}/lib -Wl,--as-needed" mkdir cmake_tmp cd cmake_tmp -LDFLAGS="$LDFLAGS" cmake .. -DCMAKE_INSTALL_PREFIX=%{_prefix} +LDFLAGS="$LDFLAGS" +%cmake .. \ + -DCMAKE_INSTALL_PREFIX=%{_prefix} make %{?jobs:-j%jobs} @@ -46,7 +48,7 @@ make %{?jobs:-j%jobs} cd cmake_tmp %make_install #mkdir -p %{buildroot}/usr/lib/ -cp libtota.a %{buildroot}/usr/lib/libtota.a +cp libtota.a %{buildroot}%{_libdir}/libtota.a %post @@ -55,11 +57,12 @@ cp libtota.a %{buildroot}/usr/lib/libtota.a %license LICENSE.Apache-2.0 %license LICENSE.BSD-2-Clause %license LICENSE.BSD-3-Clause -#%{_libdir}/libtota.a +%{_libdir}/libtota.so.* #%manifest fota.manifest %files devel %defattr(-,root,root,-) +%{_libdir}/libtota.so %{_libdir}/libtota.a %{_libdir}/pkgconfig/tota.pc %{_includedir}/fota_common.h diff --git a/tota.pc.in b/tota.pc.in index 02eaba9..c58d859 100755 --- a/tota.pc.in +++ b/tota.pc.in @@ -9,5 +9,5 @@ Name: libtota Description: fota function library Version: @VERSION@ Requires: -Libs: -L${libdir} +Libs: -L${libdir} -ltota Cflags: -I${includedir} -- 2.7.4 From 547502045b1d305885903c09d457a386d07f40e5 Mon Sep 17 00:00:00 2001 From: Sunmin Lee Date: Fri, 19 May 2017 15:59:43 +0900 Subject: [PATCH 05/13] Migrate SS_FSUpdate.c from tota-ua For ease of managing, get SS_FSUpdate.c from tota-ua. Change-Id: Id1928040f00527abee2a26bd278722d00c74d923 Signed-off-by: Sunmin Lee --- CMakeLists.txt | 2 + packaging/libtota.spec | 5 +- ss_engine/SS_FSUpdate.c | 1244 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1249 insertions(+), 2 deletions(-) create mode 100755 ss_engine/SS_FSUpdate.c diff --git a/CMakeLists.txt b/CMakeLists.txt index cbc521f..771bac8 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ SET(SRCS ss_engine/SS_Common.c ss_patch/sha1.c ss_engine/SS_UPI.c + ss_engine/SS_FSUpdate.c ss_engine/fota_tar.c ss_patch/ss_bspatch.c ss_patch/ss_patchdelta.c @@ -38,6 +39,7 @@ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/bsdiff) INCLUDE(FindPkgConfig) pkg_check_modules(packages REQUIRED + libsmack lib7zip ) diff --git a/packaging/libtota.spec b/packaging/libtota.spec index 8dad2cd..08e418c 100755 --- a/packaging/libtota.spec +++ b/packaging/libtota.spec @@ -1,13 +1,14 @@ Name: libtota Summary: fota update library ExclusiveArch: %{arm} -Version: 0.1.2 -Release: 3 +Version: 0.1.3 +Release: 4 Group: System License: Apache-2.0 and BSD-2-Clause and BSD-3-Clause and PD Source0: %{name}-%{version}.tar.gz BuildRequires: cmake +BuildRequires: pkgconfig(libsmack) BuildRequires: pkgconfig(lib7zip) BuildRequires: pkgconfig(libdivsufsort) diff --git a/ss_engine/SS_FSUpdate.c b/ss_engine/SS_FSUpdate.c new file mode 100755 index 0000000..cb03e4c --- /dev/null +++ b/ss_engine/SS_FSUpdate.c @@ -0,0 +1,1244 @@ +/* + * tota-ua + * + * Copyright (c) 2017 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. + */ + +#define _XOPEN_SOURCE 500 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "SS_Common.h" + +#include "fota_common.h" +#include "ua.h" + +/************************************************************ + * common functions + ************************************************************/ +void SS_create_dir(char *pathname, int mode) +{ + char *p; + int r; + + /* Strip trailing '/' */ + if (pathname[strlen(pathname) - 1] == '/') + pathname[strlen(pathname) - 1] = '\0'; + + /* Try creating the directory. */ + r = mkdir(pathname, mode); + + if (r != 0) { + /* On failure, try creating parent directory. */ + p = strrchr(pathname, '/'); + if (p != NULL) { + *p = '\0'; + SS_create_dir(pathname, 0755); + *p = '/'; + r = mkdir(pathname, mode); + } + } + if (r != 0) { + if (r != EEXIST && r != -1) + LOG("Could not create directory [%s] Error[%d]\n", pathname, r); + } +} + + +void SS_unicode_to_char(const char *src, char *dest) +{ + if (src == NULL) { + return; + } + + strcpy(dest, src); +} + +void SS_char_to_unicode(const char *src, char *dest) +{ + if (src == NULL) { + return; + } + + strcpy(dest, src); +} + +long SS_recursive_folder_creater(const char *path, const mode_t mode) +{ + int ret = 0; + int offset = 0; + char temppath[MAX_PATH] = { '\0' }; + + LOGL(LOG_SSENGINE, "path: %s\n", path); + + if ((offset = strlen(path)) == 0) // For counting back until the '/' delimiter + return -1; //if from some reason we got to the end return error!!!. + + while (path[offset] != '/') // get to the next '/' place + offset--; + + strncpy(temppath, path, offset); // copy one depth below till and without the char '/' + LOGL(LOG_SSENGINE, " temppath: %s\n", temppath); + ret = mkdir(temppath, mode); + LOGL(LOG_SSENGINE, " mkdir result: %d errno: %d\n", ret, errno); + + if (ret == 0 || ((ret == -1) && (errno == EEXIST))) { + return 0; //meaning the depth creation is success. + } else if ((ret == -1) && (errno == ENOENT)) { + if ((ret = SS_recursive_folder_creater(temppath, mode)) == 0); + ret = mkdir(temppath, mode); + return ret; + } else { + return -1; + } +} + +long +SS_CopyFile(void *pbUserData, + const char *strFromPath, const char *strToPath) +{ + int fd1, fd2; + int readCount = 0, writeCount = 0; + char buf[1 << 15]; // copy 32KB wise + int ret = 0; + + char path1[MAX_PATH] = { '\0' }; + char path2[MAX_PATH] = { '\0' }; + + if (!strFromPath || !strToPath) { + LOGE("NULL file name find. Abort.\n"); + return -1; + } + + SS_unicode_to_char((const char *)strFromPath, (char *)path1); + SS_unicode_to_char((const char *)strToPath, (char *)path2); + + //LOGL(LOG_SSENGINE, "%s -> %s \n", path1, path2); + + fd1 = open(path1, O_RDONLY); + if (fd1 < 0) + return E_SS_OPENFILE_ONLYR; + ret = SS_OpenFile(pbUserData, strToPath, ONLY_W, (long *)&fd2); + if (ret != S_SS_SUCCESS || fd2 < 0) { + close(fd1); + LOGE(" SS_OpenFile fail leaved path1:%s | path2:%s\n", path1, path1); + return E_SS_WRITE_ERROR; + } + + while ((readCount = read(fd1, buf, sizeof(buf))) > 0) { + writeCount = write(fd2, buf, readCount); + if (writeCount != readCount) { + LOGE(" read %d, but write %d, abort.\n", readCount, + writeCount); + ret = E_SS_WRITE_ERROR; + break; + } + } + + close(fd1); + fsync(fd2); + close(fd2); + //LOGL(LOG_INFO, " leaved path1:%s | path2:%s\n", path1, path2); + + return ret; +} + +long SS_DeleteFile(void *pbUserData, const char *strPath) +{ + char path[MAX_PATH] = { '\0' }; + int ret = 0; + + SS_unicode_to_char((const char *)strPath, (char *)path); + //LOGL(LOG_SSENGINE, "%s\n", path); + ret = unlink(path); + if (ret == 0) + return S_SS_SUCCESS; + + LOGE("failed to delete path [%s] unlink value: %d, errno: %d\n", path, ret, errno); + if (ret < 0 && errno == ENOENT) + return S_SS_SUCCESS; + return E_SS_DELETEFILE; +} + +int SS_unlink_cbf(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) +{ + int rv = remove(fpath);//returns zero on success and -1 on failure + + if (rv){ + perror(fpath); + LOGE("path : %s, remove value: %d, errno: %d\n",fpath, rv, errno); + } + return rv; +} +long SS_DeleteFolder(void *pbUserData, const char *strPath) +{ + //runs till either tree is exhausted or when unlink_cbf returns non zero value. + return ((long)nftw(strPath, SS_unlink_cbf, 64, FTW_DEPTH | FTW_PHYS) == 0) ? S_SS_SUCCESS : E_SS_FAILURE; + +} + +long SS_DeleteFolderEmpty(void *pbUserData, const char *strPath) +{ + + int ret = 0; + char path[MAX_PATH] = { '\0' }; + + SS_unicode_to_char((const char *)strPath, (char *)path); + //LOGL(LOG_REDBEND, "%s\n", path); + ret = rmdir(path); + if ((ret == 0) + || ((ret < 0) && ((errno == ENOENT) || (errno == ENOTEMPTY)))) { + LOGL(LOG_SSENGINE, "rmdir value: %d, errno: %d\n", ret, errno); + return S_SS_SUCCESS; + } + LOGE("rmdir value: %d, errno: %d\n", ret, errno); + return E_SS_FAILURE; +} + +long SS_CreateFolder(void *pbUserData, const char *strPath) +{ + mode_t mode = 0; + int ret = 0; + char path[MAX_PATH] = { '\0' }; + + SS_unicode_to_char((const char *)strPath, (char *)path); + mode = S_IRUSR /*Read by owner */ | + S_IWUSR /*Write by owner */ | + S_IXUSR /*Execute by owner */ | + S_IRGRP /*Read by group */ | + S_IWGRP /*Write by group */ | + S_IXGRP /*Execute by group */ | + S_IROTH /*Read by others */ | + S_IWOTH /*Write by others */ | + S_IXOTH /*Execute by others */ ; + + LOGL(LOG_SSENGINE, "%s, mode:0x%x\n", path, mode); + + ret = mkdir(path, mode); + + if (ret == 0 || ((ret == -1) && (errno == EEXIST))) { + return S_SS_SUCCESS; + } else if ((ret == -1) && (errno == ENOENT)) //maybe multi directory problem + { + //do//Recursive Function + //{ + ret = SS_recursive_folder_creater(path, mode); + if (ret == S_SS_SUCCESS) { + ret = mkdir(path, mode); //After creating all the depth Directories we try to create the Original one again. + if (ret == 0) + return S_SS_SUCCESS; + else + return E_SS_FAILURE; + } else { + return E_SS_FAILURE; + } + //} + //while(ret != 0); + + } else { + return E_SS_FAILURE; + } +} + +mode_t SS_get_mode(E_RW_TYPE wFlag) +{ + switch (wFlag) { + case ONLY_R: + //LOGL(LOG_SSENGINE, " RDONLY \n"); + return O_RDONLY; + case ONLY_W: + //LOGL(LOG_SSENGINE, " WRONLY \n"); + return O_WRONLY | O_CREAT; + case BOTH_RW: + //LOGL(LOG_SSENGINE, " RDWR \n"); + return O_RDWR | O_CREAT; + default: + //LOGL(LOG_SSENGINE, " Unknown \n"); + return 0; + } +} + +long +SS_OpenFile(void *pbUserData, + const char *strPath, E_RW_TYPE wFlag, long *pwHandle) +{ + mode_t mode; + char path[MAX_PATH] = { '\0' }; + + SS_unicode_to_char((const char *)strPath, (char *)path); + + mode = SS_get_mode(wFlag); + //LOGL(LOG_SSENGINE, "Path:%s wFlag:%d Mode:%d\n", path, wFlag, mode); + + if(mode & O_CREAT) { + //LOGL(LOG_SSENGINE, " open() S_IRWXU\n"); + *pwHandle = open(path, mode, S_IRWXU); + } else { + //LOGL(LOG_SSENGINE, " open() %d\n", mode); + *pwHandle = open(path, mode); + } + if (*pwHandle == -1) { + *pwHandle = 0; + LOGE(" First open() with error %d\n", errno); + if (wFlag == ONLY_R) { + LOGE(" error in ONLY_R return E_SS_OPENFILE_ONLYR\n"); + return E_SS_OPENFILE_ONLYR; + } + + //if we need to open the file for write or read/write then we need to create the folder (in case it does not exist) + if ((wFlag != ONLY_R) && (errno == ENOENT)) { + char dir[MAX_PATH] = { '\0' }; + char dirShort[MAX_PATH] = { '\0' }; + int i = 0; + //copy the full file path to directory path variable + while (path[i] != '\0') { + dir[i] = path[i]; + i++; + } + LOGL(LOG_SSENGINE, " copy dir[]=%s\n", dir); + //search for the last '/' char + while (dir[i--] != '/') ; + dir[i + 1] = '\0'; + + SS_char_to_unicode((const char *)dir, + (char *)dirShort); + + if (SS_CreateFolder(pbUserData, dirShort)) { + LOGE(" Fail create folder, Leave SS_OpenFile\n"); + return E_SS_OPENFILE_WRITE; + } + + *pwHandle = open(path, mode); + if (*pwHandle == -1) { + *pwHandle = 0; + LOGE(" After successful creating folder, fail open() with error %d\n", errno); + return E_SS_OPENFILE_WRITE; + } + } else { + LOG(" fail open() *pwHandle:%ld\n", *pwHandle); + return E_SS_OPENFILE_WRITE; + } + } + + //LOGL(LOG_SSENGINE, " Successful open() *pwHandle:%ld\n", *pwHandle); + + return S_SS_SUCCESS; +} + +long SS_ResizeFile(void *pbUserData, long wHandle, SS_UINT32 dwSize) +{ + int ret = 0; + + LOGL(LOG_SSENGINE, "handle %ld, dwSize %d\n", wHandle, errno); + + if (wHandle) + ret = ftruncate(wHandle, dwSize); + else + ret = E_SS_RESIZEFILE; + + LOGL(LOG_SSENGINE, "ret %d handle %ld %d\n", ret, wHandle, errno); + + return ret; +} + +long SS_CloseFile(void *pbUserData, long wHandle) +{ + int ret = 0; + LOGL(LOG_SSENGINE, "wHandle = %ld\n", wHandle); + + if (wHandle) { + ret = fsync(wHandle); + if (ret < 0) { + LOG(" fsync Failed with return value: %d\n", ret); + return E_SS_WRITE_ERROR; + } + LOG(" fsync after write: %d\n", ret); + ret = close(wHandle); + } + + if (ret == 0) + return S_SS_SUCCESS; + + return E_SS_CLOSEFILE_ERROR; +} + +long +SS_WriteFile(void *pbUserData, + long wHandle, + SS_UINT32 dwPosition, + unsigned char *pbBuffer, SS_UINT32 dwSize) +{ + int ret = 0; + + LOGL(LOG_SSENGINE, "Handle:%ld , Pos:%u , Size: %u\n", wHandle, + dwPosition, dwSize); + + ret = lseek(wHandle, dwPosition, SEEK_SET); + if (ret < 0) { + LOGE(" lseek failed with return value: %d\n", ret); + LOGL(LOG_SSENGINE, "lseek errno=%d\n", errno); + return E_SS_WRITE_ERROR; + } + + ret = write(wHandle, pbBuffer, dwSize); + if (ret < 0 || ret != dwSize) { + LOGE(" Failed with return value: %d\n", ret); + LOGL(LOG_SSENGINE, "ret=%d, dwSize=%u write errno=%d\n", + ret, (unsigned int)dwSize, errno); + return E_SS_WRITE_ERROR; + } + LOGL(LOG_SSENGINE, "leave Bytes Write: %d\n", ret); + + return S_SS_SUCCESS; +} + +long SS_MoveFile(void *pbUserData, const char *strFromPath, + const char *strToPath) +{ + int ret = 0; + struct stat sbuf; + char path1[MAX_PATH] = { '\0' }; + char path2[MAX_PATH] = { '\0' }; + + if (!strFromPath || !strToPath) { + return -1; //should never happen + } + + SS_unicode_to_char(strFromPath, (char *)path1); + SS_unicode_to_char(strToPath, (char *)path2); + + LOGL(LOG_INFO, "entered path1:%s | path2:%s\n", path1, path2); + ret = stat(path1, &sbuf); + if (ret < 0) { + LOGE("stat failed with return value: %d errno: %d\n", ret, errno); + return E_SS_FAILURE; + } + ret = rename(path1, path2); + if (ret < 0) { + LOGL(LOG_INFO, "rename fail with code [%d], try to create dir if errno is 2\n", errno); + if (errno == 2) { + char * file_name = strrchr(path2,'/'); + *file_name = '\0'; + SS_create_dir(path2,0755); + *file_name = '/'; + ret = rename(path1, path2); + if (ret < 0) { + LOGE("Move failed, error code [%d]",errno); + return E_SS_WRITE_ERROR; + } + } + else if (errno == 18){ //EXDEV 18 /* Cross-device link */ + //Moving file across partitions if mount point is different (Extremely rare) + ret = (int)SS_CopyFile(NULL, path1, path2); + if (ret != S_SS_SUCCESS) { + LOGE("failed to copy file [%s] result [%d]\n", path1, ret); + return E_SS_WRITE_ERROR; + } + ret = unlink(path1); + if (ret != 0) { + LOGE("failed to unlink [%s] code [%d]\n", path1, errno); + return E_SS_WRITE_ERROR; + } + } + else{ + LOGE("Move failed, error code [%d]",errno); + return E_SS_WRITE_ERROR; + } + } + LOGL(LOG_INFO, "leaved path1:%s | path2:%s\n", path1, path2); + return S_SS_SUCCESS; +} +long SS_SyncFile(void *pbUserData, long wHandle) +{ + return (-1 == fsync(wHandle)) ? E_SS_FAILURE : S_SS_SUCCESS; +} + +long +SS_ReadFile(void *pbUserData, + long wHandle, + SS_UINT32 dwPosition, + unsigned char *pbBuffer, SS_UINT32 dwSize) +{ + int ret = 0; + +#if 0 + LOG(" %s: Handle:%ld , Pos:%u , Size: %u", __func__, wHandle, + dwPosition, dwSize); +#endif + ret = lseek(wHandle, dwPosition, SEEK_SET); + if (ret < 0) { + LOGE("Handle:%ld , Pos:%u , Size: %u", wHandle, dwPosition, + dwSize); + LOGE("lseek failed with return value: %d\n", ret); + return E_SS_READ_ERROR; + } + ret = read(wHandle, pbBuffer, dwSize); + if (ret < 0) { + LOGE("Handle:%ld , Pos:%u , Size: %u", wHandle, dwPosition, + dwSize); + LOGE("read failed with return value: %d\n", ret); + return E_SS_READ_ERROR; + } + + if (ret != dwSize && ((ret + dwPosition) != (unsigned long)SS_GetFileSize(pbUserData, wHandle))) + return E_SS_READ_ERROR; + +#if 0 + LOGL(LOG_DEBUG, "Bytes Read: %d\n", ret); +#endif + return S_SS_SUCCESS; +} + +long SS_GetFileSize(void *pbUserData, long wHandle) +{ + int ret = 0; + + + ret = lseek(wHandle, 0, SEEK_END); + + if (ret == -1) { + LOGE(" lseek errno: %d\n", errno); + return E_SS_READFILE_SIZE; + } + LOGL(LOG_SSENGINE, "handle=%d Returning Size = %ld(0x%x)\n", + (int)wHandle, (long int)ret, ret); + + return ret; +} + +long SS_Unlink(void *pbUserData, char *pLinkName) +{ + int ret = 0; + char path[MAX_PATH] = { '\0' }; + //enumFileType fileType = FT_REGULAR_FILE; + + + SS_unicode_to_char((const char *)pLinkName, (char *)path); + + ret = unlink(path); + if (ret < 0 && errno != ENOENT) { + LOGE("unlink failed with return value: %d\n", ret); + return E_SS_FAILURE; + } + LOGL(LOG_SSENGINE, "unlink with return value: %d\n", ret); + + return S_SS_SUCCESS; +} + +long +SS_VerifyLinkReference(void *pbUserData, + char *pLinkName, + char *pReferenceFileName) +{ + int ret = 0; + char path[MAX_PATH] = { '\0' }; + char linkedpath[MAX_PATH] = { '\0' }; + char refpath[MAX_PATH] = { '\0' }; + + + SS_unicode_to_char((const char *)pLinkName, (char *)path); + SS_unicode_to_char((const char *)pReferenceFileName, (char *)refpath); + + ret = readlink(path, linkedpath, MAX_PATH); + if (ret < 0) { + LOGE("readlink failed with return value: %d\n", ret); + return E_SS_FAILURE; + } + + if ((memcmp(&linkedpath, &refpath, ret)) != 0) { + LOGE("not same linked path\n"); + return E_SS_FAILURE; + } + LOGL(LOG_SSENGINE, "same linked path\n"); + + return S_SS_SUCCESS; +} + +long +SS_Link(void *pbUserData, char *pLinkName, char *pReferenceFileName) +{ + int ret = 0; + char sympath[MAX_PATH] = { '\0' }; + char refpath[MAX_PATH] = { '\0' }; + //enumFileType fileType = FT_SYMBOLIC_LINK; + struct stat sbuf; + + SS_unicode_to_char((const char *)pLinkName, (char *)sympath); + SS_unicode_to_char((const char *)pReferenceFileName, (char *)refpath); + + ret = symlink(refpath, sympath); + if (ret != 0) { + LOGE(" symlink failed with return value: %d, errno: %d\n", ret,errno); + + if (errno == EEXIST) { + ret = lstat(sympath, &sbuf); + LOGL(LOG_SSENGINE, "symlink LSTAT with return value: %d\n", ret); + if (ret >= 0) { + if (S_ISREG(sbuf.st_mode)) { + LOGL(LOG_SSENGINE, " stat->st_mode = regular file, To be deleted and create a LINK \n"); + SS_DeleteFile(pbUserData,sympath); + SS_Link(pbUserData, pLinkName, pReferenceFileName); + } + } + if (SS_VerifyLinkReference(pbUserData, pLinkName + , pReferenceFileName) == S_SS_SUCCESS) { + return S_SS_SUCCESS; + } + else + return E_SS_FAILURE; + } + else if(errno == ENOENT )//to handle cases where new symlink points to a new symlink yet to be created + return errno; + else + return E_SS_FAILURE; + } + //LOGL(LOG_SSENGINE, "symlink with return value: %d\n", ret); + + return S_SS_SUCCESS; +} + +long SS_GetFileType(void *pbUserData, + char *pLinkName, enumFileType * fileType) +{ + int ret = 0; + char path[MAX_PATH] = { '\0' }; + struct stat sbuf; + + //LOGL(LOG_SSENGINE, "\n"); + SS_unicode_to_char((const char *)pLinkName, (char *)path); + + ret = lstat(path, &sbuf); + if (ret < 0) { + ret = stat(path, &sbuf); + if (ret < 0) { + LOGE("stat failed with return value: %d errno: %d\n", + ret, errno); + *fileType = FT_MISSING; + return S_SS_SUCCESS; + } + } + //LOGL(LOG_SSENGINE, " sbuf.st_mode: %d\n", sbuf.st_mode); + //LOGL(LOG_SSENGINE, " S_ISREG(sbuf.st_mode): %d\n", S_ISREG(sbuf.st_mode)); + //LOGL(LOG_SSENGINE, " S_ISLNK(sbuf.st_mode): %d\n", S_ISLNK(sbuf.st_mode)); + + if (S_ISLNK(sbuf.st_mode)) { + //LOGL(LOG_SSENGINE, " stat->st_mode = symbolic link file\n"); + *fileType = FT_SYMBOLIC_LINK; + return S_SS_SUCCESS; + } + + if (S_ISREG(sbuf.st_mode)) { + //LOGL(LOG_SSENGINE, " stat->st_mode = regular file\n"); + *fileType = FT_REGULAR_FILE; + return S_SS_SUCCESS; + } + + if (S_ISDIR(sbuf.st_mode)) { + //LOGL(LOG_SSENGINE, " stat->st_mode = regular file\n"); + *fileType = FT_FOLDER; + return S_SS_SUCCESS; + } + LOGE("failed to lstat, err : %d\n", ret); + return E_SS_FAILURE; +} + +char SS_a2ch(int value) +{ + char set_value = 0; + + LOGL(LOG_SSENGINE, "%d\n", value); + + switch (value) { + case '1': + set_value = 0x01; + break; + case '2': + set_value = 0x02; + break; + case '3': + set_value = 0x03; + break; + case '4': + set_value = 0x04; + break; + case '5': + set_value = 0x05; + break; + case '6': + set_value = 0x06; + break; + case '7': + set_value = 0x07; + break; + case '8': + set_value = 0x08; + break; + case '9': + set_value = 0x09; + break; + case '0': + set_value = 0x00; + break; + default: + LOGL(LOG_SSENGINE, "Wrong attribute value: %d\n", value); + + } + LOGL(LOG_SSENGINE, "SS_a2ch : %c\n", set_value); + + return set_value; +} + +void SS_chtoa(int value, char *str) +{ + char *pStr = str; + + LOGL(LOG_SSENGINE, "%d\n", value); + + switch (value) { + case 1: + *pStr = '1'; + break; + case 2: + *pStr = '2'; + break; + case 3: + *pStr = '3'; + break; + case 4: + *pStr = '4'; + break; + case 5: + *pStr = '5'; + break; + case 6: + *pStr = '6'; + break; + case 7: + *pStr = '7'; + break; + case 8: + *pStr = '8'; + break; + case 9: + *pStr = '9'; + break; + case 0: + *pStr = '0'; + break; + default: + LOGL(LOG_SSENGINE, "Wrong attribute value: %d\n", value); + } +} + +/*! + ******************************************************************************* + * Set file attributes.

+ * + * The file attributes token (\a ui8pAttribs) is defined at generation time. + * If attributes are not defined explicitly, they are given the following, + * OS-dependent values: + * \li Windows: _redbend_ro_ for R/O files, _redbend_rw_ for R/W files + * \li Linux: _redbend_oooooo:xxxx:yyyy indicating the file mode, uid, and gid + * (uid and gid use capitalized hex digits as required) + * + * \param pbUserData Optional opaque data-structure to pass to IPL + * functions + * \param ui16pFilePath File path + * \param ui32AttribSize Size of \a ui8pAttribs + * \param ui8pAttribs Attributes to set + * + * \return S_SS_SUCCESS on success or < 0 on error + ******************************************************************************* + */ + +long SS_SetFileAttributes(const char *ui16pFilePath, + const SS_UINT32 ui32AttribSize, + const unsigned char *ui8pAttribs) +{ + static char tmpAttribs[512]; + static char tmpSmackAttribs[512]; + char *tp; + char *endstr; + char *smack_value, *psmack; + uid_t setUserID = 0; + gid_t setGroupID = 0; + mode_t setFileMode = 0; + const char attrDelim[2] = ":"; + + char setFilePath[MAX_PATH] = { '\0' }; + struct stat sbuf; + int ret = 0; + char *smack_attr_pos = NULL; +#if defined(FEATURE_SUPPORT_CAPABILITY) + int has_cap = 0; + char cap_raw[100]; + int cap_len; + /*ACL */ + int has_acl = 0; + char acl_raw[256]; + int acl_len; + +#endif + if (NULL == ui16pFilePath) { + LOGL(LOG_SSENGINE, "ui16pFilePath NULL [error]\n"); + return E_SS_BAD_PARAMS; + } else if (NULL == ui8pAttribs) { + LOGL(LOG_SSENGINE, "ui8pAttribs NULL [error]\n"); + return E_SS_BAD_PARAMS; + } else if (0 == ui32AttribSize) { + LOGL(LOG_SSENGINE, "ui32AttribSize 0\n"); + return S_SS_SUCCESS; + } + + SS_unicode_to_char((const char *)ui16pFilePath, (char *)setFilePath); + + ret = lstat(setFilePath, &sbuf); + if (ret < 0) { + ret = stat(setFilePath, &sbuf); + if (ret < 0) { + LOGL(LOG_SSENGINE, " stat failed with return value: %d\n", ret); + return E_SS_FAILURE; + } else { + if (S_ISLNK(sbuf.st_mode)) { + LOGL(LOG_SSENGINE, " stat->st_mode = symbolic link file\n"); +// return S_RB_SUCCESS; // sybolic link should be set mode. + } + if (S_ISREG(sbuf.st_mode)) { + LOGL(LOG_SSENGINE, " stat->st_mode = regular file\n"); + } + if (S_ISDIR(sbuf.st_mode)) { + LOGL(LOG_SSENGINE, " stat->st_mode = directory\n"); + } + } + + LOGL(LOG_SSENGINE, "ui16pFilePath = %s\n", setFilePath); + LOGL(LOG_SSENGINE, "ui32AttribSize = %u\n", ui32AttribSize); + LOGL(LOG_SSENGINE, "ui8pAttribs = %s\n", ui8pAttribs); + + } + memset(tmpAttribs, 0x0, sizeof(tmpAttribs)); + memcpy(tmpAttribs, ui8pAttribs, (size_t) ui32AttribSize-1); + smack_attr_pos = tmpAttribs; + tp = strtok(tmpAttribs, attrDelim); + + // Get FileMode + if (tp != NULL) { + smack_attr_pos += strlen(tp); + smack_attr_pos++; + setFileMode = strtol(tp, &endstr, 8); + tp = strtok(NULL, attrDelim); + } + // Get UserID + if (tp != NULL) { + smack_attr_pos += strlen(tp); + smack_attr_pos++; + setUserID = (uid_t) strtol(tp, &endstr, 10); + tp = strtok(NULL, attrDelim); + } + // Get GroupID + if (tp != NULL) { + smack_attr_pos += strlen(tp); + smack_attr_pos++; + setGroupID = (gid_t) strtol(tp, &endstr, 10); + } +#if defined(FEATURE_SUPPORT_CAPABILITY) + // Get Capability + has_cap = 0; + if (*smack_attr_pos != '\0') { + char *cap_mark = "capability=0x"; + int cap_mark_len = strlen(cap_mark); + psmack = strstr(smack_attr_pos, cap_mark); + if(psmack) { + int cap_hex_len; + int i; + char ch1, ch2; + int raw1, raw2; + + tp = strstr(psmack, ":"); + smack_attr_pos = tp + 1; + memset(tmpSmackAttribs, 0x0, sizeof(tmpSmackAttribs)); + memcpy(tmpSmackAttribs, psmack+cap_mark_len, + (int)tp - (int)psmack - cap_mark_len); + + // convert hexadecimal into raw data + cap_hex_len = strlen(tmpSmackAttribs); + cap_len = cap_hex_len/2; + memset(cap_raw, 0x00, sizeof(cap_raw)); + for (i=0; i= '0')&&(ch1 <= '9')) raw1 = ch1 - '0'; + else if ((ch1 >= 'a')&&(ch1 <= 'f')) raw1 = ch1 - 'a' + 10; + else if ((ch1 >= 'A')&&(ch1 <= 'F')) raw1 = ch1 - 'A' + 10; + else raw1 = 0; + if ((ch2 >= '0')&&(ch2 <= '9')) raw2 = ch2 - '0'; + else if ((ch2 >= 'a')&&(ch2 <= 'f')) raw2 = ch2 - 'a' + 10; + else if ((ch2 >= 'A')&&(ch2 <= 'F')) raw2 = ch2 - 'A' + 10; + else raw2 = 0; + + cap_raw[i] = raw1*16 + raw2; + } + LOGL(LOG_SSENGINE, "[Cap] %s (cap_len=%d)\n", tmpSmackAttribs, cap_len); + has_cap = 1; + } + + } + // Get ACL + has_acl = 0; + if (*smack_attr_pos != '\0') { + char *acl_mark = "acl_access=0x"; + int acl_mark_len = strlen(acl_mark); + psmack = strstr(smack_attr_pos, acl_mark); + if(psmack) { + int acl_hex_len; + int i; + char ch1, ch2; + int raw1, raw2; + + tp = strstr(psmack, ":"); + smack_attr_pos = tp + 1; + memset(tmpSmackAttribs, 0x0, sizeof(tmpSmackAttribs)); + memcpy(tmpSmackAttribs, psmack+acl_mark_len, + (int)tp - (int)psmack - acl_mark_len); + + // convert hexadecimal into raw data + acl_hex_len = strlen(tmpSmackAttribs); + acl_len = acl_hex_len/2; + memset(acl_raw, 0x00, sizeof(acl_raw)); + for (i=0; i= '0')&&(ch1 <= '9')) raw1 = ch1 - '0'; + else if ((ch1 >= 'a')&&(ch1 <= 'f')) raw1 = ch1 - 'a' + 10; + else if ((ch1 >= 'A')&&(ch1 <= 'F')) raw1 = ch1 - 'A' + 10; + else raw1 = 0; + if ((ch2 >= '0')&&(ch2 <= '9')) raw2 = ch2 - '0'; + else if ((ch2 >= 'a')&&(ch2 <= 'f')) raw2 = ch2 - 'a' + 10; + else if ((ch2 >= 'A')&&(ch2 <= 'F')) raw2 = ch2 - 'A' + 10; + else raw2 = 0; + + acl_raw[i] = raw1*16 + raw2; + } + LOG("[ACL] %s (acl_len=%d)\n", tmpSmackAttribs, acl_len); + has_acl = 1; + } + + } + +#endif + // Get Smack value -> Set Smack value + if (*smack_attr_pos != '\0') { + smack_lsetlabel(setFilePath, NULL, SMACK_LABEL_ACCESS); + smack_lsetlabel(setFilePath, NULL, SMACK_LABEL_EXEC); + smack_lsetlabel(setFilePath, NULL, SMACK_LABEL_MMAP); + smack_lsetlabel(setFilePath, NULL, SMACK_LABEL_TRANSMUTE); + + psmack = strstr(smack_attr_pos, "access=\""); + if(psmack) { + memset(tmpSmackAttribs, 0x0, sizeof(tmpSmackAttribs)); + memcpy(tmpSmackAttribs, psmack, strlen(psmack)); + smack_value = strtok(tmpSmackAttribs, "\""); + if (smack_value) { + smack_value = strtok(NULL, "\""); + //LOGL(LOG_SSENGINE, "[SMACK_LABEL_ACCESS] smack_value=%s\n", smack_value); + if(smack_value) { + ret = smack_lsetlabel(setFilePath, smack_value, SMACK_LABEL_ACCESS); + if (ret < 0) + LOGL(LOG_SSENGINE, "smack_lsetlabel() failed\n"); + } + } + } + psmack = strstr(smack_attr_pos, "execute=\""); + if(psmack) { + memset(tmpSmackAttribs, 0x0, sizeof(tmpSmackAttribs)); + memcpy(tmpSmackAttribs, psmack, strlen(psmack)); + smack_value = strtok(tmpSmackAttribs, "\""); + if (smack_value) { + smack_value = strtok(NULL, "\""); + //LOGL(LOG_SSENGINE, "[SMACK_LABEL_EXEC] smack_value=%s\n", smack_value); + if(smack_value) { + ret = smack_lsetlabel(setFilePath, smack_value, SMACK_LABEL_EXEC); + if (ret < 0) + LOGL(LOG_SSENGINE, "smack_lsetlabel() failed\n"); + } + } + } + psmack = strstr(smack_attr_pos, "mmap=\""); + if(psmack) { + memset(tmpSmackAttribs, 0x0, sizeof(tmpSmackAttribs)); + memcpy(tmpSmackAttribs, psmack, strlen(psmack)); + smack_value = strtok(tmpSmackAttribs, "\""); + if (smack_value) { + smack_value = strtok(NULL, "\""); + //LOGL(LOG_SSENGINE, "[SMACK_LABEL_MMAP] smack_value=%s\n", smack_value); + if(smack_value) { + ret = smack_lsetlabel(setFilePath, smack_value, SMACK_LABEL_MMAP); + if (ret < 0) + LOGL(LOG_SSENGINE, "smack_lsetlabel() failed\n"); + } + } + } + psmack = strstr(smack_attr_pos, "transmute=\""); + if(psmack) { + memset(tmpSmackAttribs, 0x0, sizeof(tmpSmackAttribs)); + memcpy(tmpSmackAttribs, psmack, strlen(psmack)); + smack_value = strtok(tmpSmackAttribs, "\""); + if (smack_value) { + smack_value = strtok(NULL, "\""); + //LOGL(LOG_SSENGINE, "[SMACK_LABEL_TRANSMUTE] smack_value=%s\n", smack_value); + if(smack_value) { + if (strcasecmp(smack_value, "TRUE")==0) { + ret = smack_lsetlabel(setFilePath, "1", SMACK_LABEL_TRANSMUTE); + } else { + ret = smack_lsetlabel(setFilePath, "0", SMACK_LABEL_TRANSMUTE); + } + if (ret < 0) + LOGL(LOG_SSENGINE, "smack_lsetlabel() failed\n"); + } + } + } + } + + // Set UserID,GroupID + if (lchown(setFilePath, setUserID, setGroupID)) { + // debug start + LOGL(LOG_SSENGINE, "%s chown error\n", __func__); + LOGL(LOG_SSENGINE, "%s setUserID = %d\n", __func__, setUserID); + LOGL(LOG_SSENGINE, "%s setGroupID = %d\n", __func__, setGroupID); + LOGL(LOG_SSENGINE, "%s chown errno = %d\n", __func__, errno); + // debug end + + return E_SS_FAILURE; + } + + // mode is always 0777 at symlink file. It doesn't need to call chmod(). + if (S_ISLNK(sbuf.st_mode)) { + LOGL(LOG_SSENGINE, " stat->st_mode = symbolic link file\n"); + return S_SS_SUCCESS; + } + + if (chmod(setFilePath, setFileMode)) { + LOGL(LOG_SSENGINE, "%s chmod error\n", __func__); + return E_SS_FAILURE; + } +#if defined(FEATURE_SUPPORT_CAPABILITY) + if (has_cap) { + if (setxattr(setFilePath, "security.capability", (void*)cap_raw, cap_len, 0) < 0) { + LOGL(LOG_SSENGINE, "cap setxattr() failed: %s\n", strerror(errno)); + } + } + + if (has_acl) { + if (setxattr(setFilePath, "system.posix_acl_access", (void*)acl_raw, acl_len, 0) < 0) { + LOGL(LOG_SSENGINE, "Acl setxattr() failed: %s\n", strerror(errno)); + } + //LOG("Acl setxattr() :")asfd + } +#endif + + //LOGL(LOG_SSENGINE, "%s SUCCESS\n", __func__); + + return S_SS_SUCCESS; +} + + +long SS_CompareFileAttributes(void *pbUserData, + char *pFilePath, + unsigned char *pAdditionalAttribs, + unsigned long iAddiInfoSize) +{ + return S_SS_SUCCESS; +} + + + +#define MAX_INT 0xefffffff + +/* vrm functions */ +long +SS_GetAvailableFreeSpace(void *pbUserData, const char *partition_name, + SS_UINT32 *available_flash_size) +{ +// *available_flash_size = MAX_INT; +/* +#define IDENT_SBL "sbl" +#define IDENT_PLATFORM "platform" +#define IDENT_BOOT "boot" +*/ + int result = 0; + char path[MAX_PATH] = { '\0' }; + + SS_unicode_to_char(partition_name, (char *)path); + //LOGL(LOG_SSENGINE, "Enter %s path=%s\n", __func__, path); + struct statfs vfs; + + //LOGL(LOG_SSENGINE, "path=%s\n", path); + result = statfs(path, &vfs); + if (result < 0 ) + { + LOGE("failed to fstatfs, err : %d errno: %d\n", result, errno); + return -1; + } + + *available_flash_size = vfs.f_bsize * vfs.f_bavail; + if(*available_flash_size == 0) + { + *available_flash_size = 0x80000; //Same as Legecy RB + LOGE("available_flash_size=%u(vfs.f_bsize=%d vfs.f_bavail=%d\n", + (unsigned int)*available_flash_size, (int)vfs.f_bsize, (int)vfs.f_bavail); + return 0; // Same as Legecy RB + } + return 0; +} + +#ifdef HEAP_PROFILING + int max_mem; + int cur_mem; +#endif +/*******[ Multiprocess API sample implementation ]******/ +void *SS_Malloc(SS_UINT32 size) +{ + void *p = malloc(size); + + if (p) + memset(p, 0, size); +#ifdef HEAP_PROFILING + cur_mem += size; + if(cur_mem > max_mem ){ + max_mem = cur_mem; + LOGL(LOG_SSENGINE,"New chunk [%d] assigned making heap [%d]\n",size,cur_mem); + } +#endif + + return p; +} + +void SS_Free(void *pMemBlock) +{ +#ifdef HEAP_PROFILING + cur_mem -= malloc_usable_size(pMemBlock); + LOGL(LOG_SSENGINE,"Old chunk [%d] removed making heap [%d]\n",malloc_usable_size(pMemBlock),cur_mem); +#endif + if(pMemBlock) + free(pMemBlock); +} + +SS_UINT32 SS_GetMaxNumProcess(void *user) +{ + return SAMPLE_PROCS_NUM; +} + +void* SS_WaitForProcess(const void *handle, SS_UINT32* process_exit_code) +{ + pid_t pid; + *process_exit_code = 0; + + // processes + if (handle) + #ifdef _NOEXEC_ + pid = waitpid((pid_t)handle, (int *)process_exit_code, 0); + #else + pid = wait((int*)process_exit_code); + #endif + + else + pid = wait((int*)process_exit_code); + + if (pid < 0) + return NULL; + + if (!WIFEXITED(*process_exit_code)) + { + *process_exit_code = (char)WTERMSIG(*process_exit_code); + LOG("Wait Error\n"); + } + else + *process_exit_code = (char)WEXITSTATUS(*process_exit_code); + + return (void*)pid; +} +unsigned long SS_GetMaxProcRamSize(void *user) +{ + return UA_RAM_SIZE; +} + +void* SS_RunProcess(void *pbUserData, int argc, char* argv[]) +{ + pid_t child_pid; + + #ifdef _NOEXEC_ + child_pid = fork(); + #else + child_pid = vfork(); + #endif + + if (child_pid == -1) { + LOG("Fork failed.\n"); + return (void *)-1; + } + + // This is the child + if (child_pid == 0) + { + #ifdef _NOEXEC_ + #ifdef _TIZEN_REDBEND//bota + SS_HandleProcessRequest(pbUserData, argc, argv); + #endif + LOGL(LOG_SSENGINE, "SS_RunProcess was called - SS_HandleProcessRequest\n"); + exit(0); + #else + + char **params = NULL; + int i; + + params = (char **)SS_Malloc((argc+EXTRA_ARGS) *sizeof(char*)); + if (!params) + { + LOG("params allocation failed\n"); + return NULL; + } + // prepare child data, as it's forbidden to change data after vfork + params[0] = strdup(((sample_userdata*)user)->exec_path); + params[1] = strdup("handle_run_process"); + params[2] = strdup(((sample_userdata*)user)->delta_path); + + for (i=0; i < argc; i++) + params[i+EXTRA_ARGS] = strdup(argv[i]); + + // no need to free allocated memory - execv takes care of it + execv(params[0], (char**)params); + _exit(-1); // if we're here, execv has failed + #endif + } + else + { + return (void *)child_pid; + } +} + + + -- 2.7.4 From fcc4f494be81ce72e86bd38f53e48de0a1916108 Mon Sep 17 00:00:00 2001 From: Sunmin Lee Date: Fri, 14 Jul 2017 11:59:09 +0900 Subject: [PATCH 06/13] Arrange code directories Because ss_patch/ codes also used to build libtota library, it looks better to move them into single source directory: ss_engine. Change-Id: I9c06b69eb7e774514c4e258ca7f6f50d86ba39a7 Signed-off-by: Sunmin Lee --- CMakeLists.txt | 7 +++---- packaging/libtota.spec | 4 ++-- ss_patch/ss_bspatch.c => ss_engine/SS_ApplyPatch.c | 2 +- ss_patch/ss_patchdelta.c => ss_engine/SS_PatchDelta.c | 2 +- ss_patch/ss_patchdelta.h => ss_engine/SS_PatchDelta.h | 0 ss_engine/SS_UPI.c | 2 +- {ss_patch => ss_engine}/sha1.c | 0 {ss_patch => ss_engine}/sha1.h | 0 8 files changed, 8 insertions(+), 9 deletions(-) rename ss_patch/ss_bspatch.c => ss_engine/SS_ApplyPatch.c (99%) rename ss_patch/ss_patchdelta.c => ss_engine/SS_PatchDelta.c (99%) rename ss_patch/ss_patchdelta.h => ss_engine/SS_PatchDelta.h (100%) rename {ss_patch => ss_engine}/sha1.c (100%) rename {ss_patch => ss_engine}/sha1.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 771bac8..f1979a2 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,12 +3,12 @@ PROJECT(tota C) SET(SRCS ss_engine/SS_Common.c - ss_patch/sha1.c + ss_engine/sha1.c ss_engine/SS_UPI.c ss_engine/SS_FSUpdate.c ss_engine/fota_tar.c - ss_patch/ss_bspatch.c - ss_patch/ss_patchdelta.c + ss_engine/SS_ApplyPatch.c + ss_engine/SS_PatchDelta.c bsdiff/ss_bspatch_common.c ) SET(HEADERS @@ -34,7 +34,6 @@ SET(LIBNAME "tota") #INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/ss_engine) -INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/ss_patch) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/bsdiff) INCLUDE(FindPkgConfig) diff --git a/packaging/libtota.spec b/packaging/libtota.spec index 08e418c..39f2909 100755 --- a/packaging/libtota.spec +++ b/packaging/libtota.spec @@ -1,8 +1,8 @@ Name: libtota Summary: fota update library ExclusiveArch: %{arm} -Version: 0.1.3 -Release: 4 +Version: 0.1.4 +Release: 5 Group: System License: Apache-2.0 and BSD-2-Clause and BSD-3-Clause and PD Source0: %{name}-%{version}.tar.gz diff --git a/ss_patch/ss_bspatch.c b/ss_engine/SS_ApplyPatch.c similarity index 99% rename from ss_patch/ss_bspatch.c rename to ss_engine/SS_ApplyPatch.c index bf61e29..7ec6975 100755 --- a/ss_patch/ss_bspatch.c +++ b/ss_engine/SS_ApplyPatch.c @@ -30,7 +30,7 @@ */ #include #include -#include "ss_patchdelta.h" +#include "SS_PatchDelta.h" #include "fota_common.h" #include "sha1.h" #include "SS_Engine_Errors.h" diff --git a/ss_patch/ss_patchdelta.c b/ss_engine/SS_PatchDelta.c similarity index 99% rename from ss_patch/ss_patchdelta.c rename to ss_engine/SS_PatchDelta.c index 5c4f275..fd00053 100755 --- a/ss_patch/ss_patchdelta.c +++ b/ss_engine/SS_PatchDelta.c @@ -28,7 +28,7 @@ #include #include "ua.h" #include "sha1.h" -#include "ss_patchdelta.h" +#include "SS_PatchDelta.h" #include "fota_common.h" #include "SS_Engine_Errors.h" diff --git a/ss_patch/ss_patchdelta.h b/ss_engine/SS_PatchDelta.h similarity index 100% rename from ss_patch/ss_patchdelta.h rename to ss_engine/SS_PatchDelta.h diff --git a/ss_engine/SS_UPI.c b/ss_engine/SS_UPI.c index 0dd61a0..6a48e54 100755 --- a/ss_engine/SS_UPI.c +++ b/ss_engine/SS_UPI.c @@ -36,7 +36,7 @@ Function Prototypes Mandatory #include "SS_Common.h" #include "fota_common.h" #include "SS_UPI.h" -#include "ss_patchdelta.h" +#include "SS_PatchDelta.h" #include "SS_Engine_Errors.h" #include "SS_FSUpdate.h" diff --git a/ss_patch/sha1.c b/ss_engine/sha1.c similarity index 100% rename from ss_patch/sha1.c rename to ss_engine/sha1.c diff --git a/ss_patch/sha1.h b/ss_engine/sha1.h similarity index 100% rename from ss_patch/sha1.h rename to ss_engine/sha1.h -- 2.7.4 From bfe9cd94b4432ffbacd103d2c7325e92e9237158 Mon Sep 17 00:00:00 2001 From: Sunmin Lee Date: Tue, 23 May 2017 11:56:12 +0900 Subject: [PATCH 07/13] Integrate some common codes from tota-ua There were duplicated codes in libtota and tota-ua. It would be better to integrate in one place and manage them. Change-Id: I58f813d3b6ff9b28d3f032a88cb6eef6e1688f78 Signed-off-by: Sunmin Lee --- CMakeLists.txt | 2 ++ packaging/libtota.spec | 4 +-- ss_engine/SS_FSUpdate.h | 20 +++++++++++ ss_engine/fota_common.h | 3 ++ ss_engine/fota_log.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++++ ss_engine/fota_log.h | 2 +- ss_engine/fota_tar.h | 9 +++-- 7 files changed, 127 insertions(+), 8 deletions(-) create mode 100755 ss_engine/fota_log.c diff --git a/CMakeLists.txt b/CMakeLists.txt index f1979a2..6a0f93e 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,8 @@ SET(SRCS ss_engine/fota_tar.c ss_engine/SS_ApplyPatch.c ss_engine/SS_PatchDelta.c + ss_engine/fota_log.c + ss_engine/fota_tar.c bsdiff/ss_bspatch_common.c ) SET(HEADERS diff --git a/packaging/libtota.spec b/packaging/libtota.spec index 39f2909..b9e7a58 100755 --- a/packaging/libtota.spec +++ b/packaging/libtota.spec @@ -1,8 +1,8 @@ Name: libtota Summary: fota update library ExclusiveArch: %{arm} -Version: 0.1.4 -Release: 5 +Version: 0.2.0 +Release: 1 Group: System License: Apache-2.0 and BSD-2-Clause and BSD-3-Clause and PD Source0: %{name}-%{version}.tar.gz diff --git a/ss_engine/SS_FSUpdate.h b/ss_engine/SS_FSUpdate.h index 7b5d1a6..b221582 100755 --- a/ss_engine/SS_FSUpdate.h +++ b/ss_engine/SS_FSUpdate.h @@ -343,6 +343,26 @@ extern "C" { SS_UINT32 SS_Trace(void *pbUserData, const char *aFormat, ... ); +/** + ******************************************************************************* + * Allocate a memory block. + * + * \param size Memory block size, in blocks + * + * \return A pointer to the memory block on success, NULL on failure + ******************************************************************************* + */ + void *SS_Malloc(SS_UINT32 size); + +/** + ******************************************************************************* + * Free a memory block. + * + * \param pMemBlock Pointer to the memory block + ******************************************************************************* + */ + void SS_Free(void *pMemBlock); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/ss_engine/fota_common.h b/ss_engine/fota_common.h index fa690b6..8424a45 100755 --- a/ss_engine/fota_common.h +++ b/ss_engine/fota_common.h @@ -41,6 +41,9 @@ typedef unsigned long long u64; #ifndef TIME_PROFILING //#define TIME_PROFILING #endif +#ifndef HEAP_PROFILING + //#define HEAP_PROFILING; +#endif #ifndef MEM_PROFILING //#define MEM_PROFILING #endif diff --git a/ss_engine/fota_log.c b/ss_engine/fota_log.c new file mode 100755 index 0000000..7d7c44e --- /dev/null +++ b/ss_engine/fota_log.c @@ -0,0 +1,95 @@ +/* + * libtota + * + * Copyright (c) 2017 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 +#include + +long curr_offset = 0; +long next_offset = 0; +long cut_offset = 0; +long max_logfile_size = (2*1024*1024); + +/*----------------------------------------------------------------------------- + log_printf + ----------------------------------------------------------------------------*/ +int log_printf(FILE* log_fp, char* format_str, ...) +{ + int ret = 0; + char log_str[4096]; + int len; + int wlen; + va_list list; + + va_start(list, format_str); + vsprintf(log_str, format_str, list); + va_end(list); + + len = strlen(log_str); + next_offset = curr_offset + len; + + if (next_offset <= max_logfile_size) { + wlen = len; + if (fwrite(log_str, 1, wlen, log_fp) != wlen) { + ret = -1; + goto exit; + } + curr_offset = next_offset; + if (curr_offset == max_logfile_size) { + rewind(log_fp); + curr_offset = 0; + } + } else { + cut_offset = max_logfile_size - curr_offset; + wlen = cut_offset; + if (fwrite(log_str, 1, wlen, log_fp) != wlen) { + ret = -1; + goto exit; + } + rewind(log_fp); + wlen = next_offset - max_logfile_size; + if (fwrite(log_str+cut_offset, 1, wlen, log_fp) != wlen) { + ret = -1; + goto exit; + } + curr_offset = next_offset - max_logfile_size; + } + +exit: + return ret; +} + +/*----------------------------------------------------------------------------- + truncate_log_file + ----------------------------------------------------------------------------*/ +void truncate_log_file(char *log_path, int size_kb) +{ + FILE *log_fp; + + if (size_kb == 0) { + log_fp = fopen(log_path, "w"); + if (log_fp == NULL) { + perror("file open error\n"); + } else { + fclose(log_fp); + } + } + + sync(); +} + + diff --git a/ss_engine/fota_log.h b/ss_engine/fota_log.h index 9b4999a..e03beed 100755 --- a/ss_engine/fota_log.h +++ b/ss_engine/fota_log.h @@ -28,6 +28,7 @@ extern unsigned int __log_level__; extern FILE *__log_out_file__; extern int log_printf(FILE* log_fp, char* format_str, ...); +extern void truncate_log_file(char *log_path, int size_kb); #define LOG_INFO (1<<8) #define LOG_ENGINE (1<<7) @@ -38,7 +39,6 @@ extern int log_printf(FILE* log_fp, char* format_str, ...); #define LOG_FLASH (1<<2) #define LOG_SSENGINE LOG_ENGINE -//#define LOG_REDBEND LOG_ENGINE //#define DEBUG_STDOUT #define DEBUG_FILE diff --git a/ss_engine/fota_tar.h b/ss_engine/fota_tar.h index e6a528f..6d141ef 100755 --- a/ss_engine/fota_tar.h +++ b/ss_engine/fota_tar.h @@ -19,13 +19,12 @@ #ifndef _FOTA_TAR_H_ #define _FOTA_TAR_H_ -extern void SS_Free(void *pMemBlock); -extern int tar_get_item_offset(char *tar, char *item); +int tar_get_item_offset(char *tar, char *item); -extern int tar_get_item_size(char *tar, char *item); +int tar_get_item_size(char *tar, char *item); -extern int tar_get_cfg_data(char *tar, char *item, char *buf, int buflen); +int tar_get_cfg_data(char *tar, char *item, char *buf, int buflen); -extern int tar_get_folder_size(char *tar, char *item); +int tar_get_folder_size(char *tar, char *item); #endif /* _FOTA_TAR_H_ */ -- 2.7.4 From 546adc9c121d6b54383b051e43c0bb5cf00a7084 Mon Sep 17 00:00:00 2001 From: Sunmin Lee Date: Wed, 24 May 2017 11:40:22 +0900 Subject: [PATCH 08/13] Remove UPG directory The UPG includes scripts which are used to make delta. Because there is another repository "tota-upg", it doesn't need to be duplicated at both repository. Change-Id: I059c3f149dbb441a322c41978407a66a41cf440e Signed-off-by: Sunmin Lee --- UPG/CreatePatch.py | 1137 ------------------------------------------------ UPG/dzImagescript.sh | 255 ----------- UPG/ss_bsdiff | Bin 212699 -> 0 bytes UPG/ss_bspatch | Bin 180101 -> 0 bytes UPG/unpack.sh | 236 ---------- packaging/libtota.spec | 4 +- 6 files changed, 2 insertions(+), 1630 deletions(-) delete mode 100755 UPG/CreatePatch.py delete mode 100755 UPG/dzImagescript.sh delete mode 100755 UPG/ss_bsdiff delete mode 100755 UPG/ss_bspatch delete mode 100755 UPG/unpack.sh diff --git a/UPG/CreatePatch.py b/UPG/CreatePatch.py deleted file mode 100755 index f45f3e3..0000000 --- a/UPG/CreatePatch.py +++ /dev/null @@ -1,1137 +0,0 @@ -#!/usr/bin/python - -import sys - - -if sys.hexversion < 0x02040000: - print >> sys.stderr, "Python 2.4 or newer is required." - sys.exit(1) - -import sys -import os -import filecmp -import shutil -import subprocess -import re -import ntpath -import zipfile -import datetime -import hashlib -import operator -import locale -import errno -import logging -import glob -import apt - -''' -Diff two folders and create delta using SS_BSDIFF -Will maintain same format of script that will be generated when we use diffutil - -1. Create a list of files in each Base folders, -2. These files will fall into one these below categories: - 1) Only in OLD - Should be deleted - 2) Only in NEW - Should be added or renamed accordingly - 3) File exists in both directories but contents are different - Create Diff. - 4) File name is same but TYPE can change (File to Folder, Folder to Link etc.) - 5) Duplicates in the list of Deletes and News - 6) Close matching diffs even though name changes across directories. (for matching extension) - 7) Clearing empty directories after Moves or diffs under Rename. - -Current Case -1. Given two folders, from list of REMOVED and NEW files find if there -is version change and create diff between them - -TODO -Want to extend the same script for entire DIFF generation and replace TOTAlib.sh file -Catching errors at all stages. SHOULD exit & return error in case of failure -''' - -def global_paths(): - global DIFF_UTIL - global ZIPUTIL - global NEW_FILES_PATH - global NEW_FILES_FOLDER - global NEW_FILES_ZIP_NAME - global SYMLINK_TYPE - global ATTR_DOC_EXT - global SYMLINK_DOC_NAME - global DIFF_PREFIX - global DIFF_SUFFIX - global SUPPORT_RENAME - global NEW_PREFIX - global DIFFPATCH_UTIL - global SUPPORT_CONTAINERS - global FULL_IMG - global DELTA_IMG - global DELTA_FS - global EXTRA - global COMMON_BIN_PATH - global MEM_REQ - global EMPTY - -COMMON_BIN_PATH = "../../common/bin/" -DIFF_UTIL = "./ss_bsdiff" -DIFFPATCH_UTIL = "./ss_bspatch" -ZIPUTIL = "p7zip " -#ZIPUTIL = "7z a system.7z " -NEW_FILES_PATH = "system" -NEW_FILES_FOLDER = "system" -NEW_FILES_ZIP_NAME = "system.7z" -SYMLINK_TYPE = "SYM" -ATTR_DOC_EXT = "_attr.txt" -SYMLINK_DOC_NAME = "_sym.txt" -PART_DOC_EXT = ".txt" -DIFF_PREFIX = "diff" -DIFF_SUFFIX = ".delta" -NEW_PREFIX = 'new' -FULL_IMG = "FULL_IMG" -DELTA_IMG = "DELTA_IMG" -DELTA_FS = "DELTA_FS" -EXTRA = "EXTRA" -LOGFILE = "Delta.log" -EMPTY = "" -MEM_REQ = 0 - -SUPPORT_RENAME = "TRUE" #Use appropriate name -SUPPORT_CONTAINERS = "FALSE" -SUPPORT_DZIMAGE = "TRUE" - -TEST_MODE = "FALSE" - -def main(): - logging.basicConfig(filename=LOGFILE, level=logging.DEBUG) - global AttributeFile - global GenerateDiffAttr - try: - - if len(sys.argv) < 5: - sys.exit('Usage: CreatePatch.py UPDATE_TYPE PARTNAME OLDBASE NEWBASE OUTFOLDER') - UPDATE_TYPE = sys.argv[1] - PART_NAME = sys.argv[2] # lets make this also optional - - BASE_OLD = sys.argv[3] - BASE_NEW = sys.argv[4] - OUT_DIR = sys.argv[5] - ATTR_OLD = EMPTY - ATTR_NEW = EMPTY - UPDATE_CFG_PATH = EMPTY - GenerateDiffAttr = "FALSE" - if UPDATE_TYPE == DELTA_FS: - #instead of arguments check it in outdirectory ? - if len(sys.argv) == 9: - ATTR_OLD = sys.argv[6] - ATTR_NEW = sys.argv[7] - UPDATE_CFG_PATH = '../'+sys.argv[8] - GenerateDiffAttr = "TRUE" - - elif UPDATE_TYPE == DELTA_IMG or UPDATE_TYPE == FULL_IMG: - if len(sys.argv) == 7: - #Use path in better way - UPDATE_CFG_PATH = '../'+sys.argv[6] - - global DIFF_UTIL - global DIFFPATCH_UTIL - if not (os.path.isfile(DIFF_UTIL) and os.access(DIFF_UTIL, os.X_OK)): - DIFF_UTIL = COMMON_BIN_PATH+DIFF_UTIL - DIFFPATCH_UTIL = COMMON_BIN_PATH+DIFFPATCH_UTIL - if not (os.path.isfile(DIFF_UTIL) and os.access(DIFF_UTIL, os.X_OK)): - print >> sys.stderr, "Diff Util Does NOT exist -- ABORT" - logging.info ('Diff Util Does NOT exist -- ABORT') - sys.exit(1) - - start = datetime.datetime.now().time() - logging.info('*************** ENTERED PYTHON SCRIPT *****************') - logging.info('Arguments Passed: [UpdateType - %s][Part Name - %s] [BaseOld - %s] [BaseNew - %s] \n [OUTPUTDir - %s] [BASE ATTR - %s] [TARGET ATTR - %s]'% (UPDATE_TYPE, PART_NAME, BASE_OLD, BASE_NEW, OUT_DIR, ATTR_OLD, ATTR_NEW)) - - ensure_dir_exists(OUT_DIR) - if GenerateDiffAttr == "TRUE": - if not (os.path.isfile(ATTR_OLD) and os.path.isfile(ATTR_NEW)): - print >> sys.stderr, "Attributes missing -- ABORT" - sys.exit(1) - - - # Should check if APT is supported on other linux flavours - cache = apt.Cache() - if cache['p7zip'].is_installed and cache['attr'].is_installed and cache['tar'].is_installed: - logging.info ('Basic utils installed') - - if UPDATE_TYPE == FULL_IMG: - SS_mk_full_img(BASE_OLD, BASE_NEW, OUT_DIR, PART_NAME, UPDATE_CFG_PATH) - elif UPDATE_TYPE == DELTA_IMG: - SS_mk_delta_img(BASE_OLD, BASE_NEW, OUT_DIR, PART_NAME, UPDATE_CFG_PATH) - elif UPDATE_TYPE == DELTA_FS: - AttributeFile = ATTR_NEW - ATTR_FILE = OUT_DIR+'/'+PART_NAME+ATTR_DOC_EXT - Diff_AttrFiles(ATTR_OLD, ATTR_NEW, ATTR_FILE) - Old_files, Old_dirs = Get_Files(BASE_OLD) - New_files, New_dirs = Get_Files(BASE_NEW) - SS_Generate_Delta(PART_NAME, BASE_OLD, Old_files, Old_dirs, BASE_NEW, New_files, New_dirs, OUT_DIR, ATTR_FILE) - - if not UPDATE_CFG_PATH == EMPTY: - SS_update_cfg(PART_NAME, UPDATE_CFG_PATH) - - - elif UPDATE_TYPE == EXTRA: - print('UPDATE_TYPE ---- EXTRA') - else: - print('UPDATE_TYPE ---- UNKNOWN FORMAT') - - if GenerateDiffAttr == "TRUE": - os.remove(ATTR_OLD) - os.remove(ATTR_NEW) - end = datetime.datetime.now().time() - - logging.info('Max Memory requried to upgrade [%s] is [%d]' % (PART_NAME, MEM_REQ)) - logging.info('*************** DONE WITH PYTHON SCRIPT ***************') - logging.info('Time start [%s] - Time end [%s]' % (start, end)) - print('Done with [%s][%d] ---- Time start [%s] - Time end [%s]' % (PART_NAME, MEM_REQ, start, end)) - except: - logging.error('Usage: {} '.format(os.path.basename(sys.argv[0]))) - raise - - -def SS_update_cfg(DELTA_BIN, UPDATE_CFG_PATH): - f = open(UPDATE_CFG_PATH, 'r') - lines = f.readlines() - f.close() - f = open(UPDATE_CFG_PATH, 'w') - for line in lines: - ConfigItems = line.split() - if ConfigItems[0] == DELTA_BIN: - DELTA = ConfigItems[1] - logging.info ('Updating %s config' % DELTA_BIN) - line = line.rstrip('\n') - Value = MEM_REQ - line = line.replace(line, line+'\t'+str(Value)+'\n') - f.write(line) - else: - f.write(line) - f.close() - -def SS_mk_delta_img(BASE_OLD, BASE_NEW, OUT_DIR, DELTA_BIN, UPDATE_CFG_PATH): - #for sizes - - ZIMAGE_SCRIPT = COMMON_BIN_PATH+'./dzImagescript.sh' - ZIMAGE_OLD = BASE_OLD+'_unpacked' - ZIMAGE_NEW = BASE_NEW+'_unpacked' - DZIMAGE_HEADER = 'UnpackdzImage' - DZIMAGE_SEP = ':' - - oldsize_d= os.path.getsize(BASE_OLD) - newsize_d= os.path.getsize(BASE_NEW) - SHA_BIN_DEST= hash_file(BASE_NEW) - SHA_BIN_BASE=hash_file(BASE_OLD) - - #incase UPDATE CFG is empty - DELTA = DELTA_BIN - SS_UpdateSize(BASE_OLD, BASE_NEW) - #Should throw error if PART NAME NOT found?? - if not UPDATE_CFG_PATH == EMPTY: - f = open(UPDATE_CFG_PATH, 'r') - lines = f.readlines() - f.close() - f = open(UPDATE_CFG_PATH, 'w') - for line in lines: - ConfigItems = line.split() - if ConfigItems[0] == DELTA_BIN: - logging.info ('Updating %s config' % DELTA_BIN) - DELTA = ConfigItems[1] - line = line.rstrip('\n') - line = line.replace(line, line+'\t'+str(oldsize_d)+'\t\t'+str(newsize_d)+'\t\t'+str(SHA_BIN_BASE)+'\t\t'+str(SHA_BIN_DEST)+'\n') - f.write(line) - else: - f.write(line) - f.close() - - #Any validation checks required? - if (DELTA_BIN == "zImage" or DELTA_BIN == "dzImage" or DELTA_BIN == "KERNEL" or DELTA_BIN == "BOOT") and SUPPORT_DZIMAGE == "TRUE": - - #Unpack Old and New Images for creating delta - subprocess.call([ZIMAGE_SCRIPT, '-u', BASE_OLD]) - subprocess.call([ZIMAGE_SCRIPT, '-u', BASE_NEW]) - - DeltaFiles = [] - Old_files, Old_dirs = Get_Files(ZIMAGE_OLD) - New_files, New_dirs = Get_Files(ZIMAGE_NEW) - - patchLoc = '%s/%s_temp' % (OUT_DIR, DELTA_BIN) - ensure_dir_exists(patchLoc) - - for elt in New_files: - if elt in Old_files: - src_file = ZIMAGE_OLD+'/'+elt - dst_file = ZIMAGE_NEW+'/'+elt - if not filecmp.cmp(src_file, dst_file): - patch = '%s/%s' % (patchLoc,elt) - DeltaFiles.append(patch) - subprocess.call([DIFF_UTIL,src_file,dst_file,patch]) - logging.info('Make dz Image %s <--> %s ==> %s %s' % (src_file, dst_file , DELTA_BIN, patch)) - - #Append all delta files to make image.delta - - #HEADER FORMAT MAGICNAME:FILECOUNT:[FILENAME:FILESIZE:][FILECONTENT/S] - HeaderStr = DZIMAGE_HEADER+DZIMAGE_SEP+'%d' % len(DeltaFiles) - HeaderStr = HeaderStr+DZIMAGE_SEP - - with open(OUT_DIR+'/'+DELTA, 'w') as DeltaFile: - for fname in DeltaFiles: - DeltaSize = os.path.getsize(fname) - HeaderStr = HeaderStr+path_leaf(fname)+DZIMAGE_SEP+'%d' % DeltaSize - HeaderStr = HeaderStr+DZIMAGE_SEP - #Using 128 bytes as max Header. - logging.info('zImage Header - %s' % HeaderStr.ljust(128,'0')) - DeltaFile.write(HeaderStr.ljust(128,'0')) - for fname in DeltaFiles: - with open(fname) as infile: - DeltaFile.write(infile.read()) - infile.close() - - DeltaFile.close() - shutil.rmtree(patchLoc) - shutil.rmtree(ZIMAGE_OLD) - shutil.rmtree(ZIMAGE_NEW) - #Do we need to incorprate Max memory required for backup?? - - else: - patchLoc = '%s/%s' % (OUT_DIR, DELTA) - subprocess.call([DIFF_UTIL,BASE_OLD,BASE_NEW,patchLoc]) - logging.info('Make Delta Image %s <--> %s ==> %s %s' % (BASE_OLD, BASE_NEW , DELTA_BIN, patchLoc)) - - - -def SS_mk_full_img(BASE_OLD, BASE_NEW, OUT_DIR, DELTA_BIN ,UPDATE_CFG_PATH): - logging.info('Make Full Image %s <--> %s ==> %s' % (BASE_OLD, BASE_NEW ,DELTA_BIN)) - oldsize_d= os.path.getsize(BASE_OLD) - newsize_d= os.path.getsize(BASE_NEW) - SHA_BIN_DEST= hash_file(BASE_NEW) - SHA_BIN_BASE=hash_file(BASE_OLD) - #echo -e "\t${oldsize_d}\t\t${newsize_d}\t\t${SHA_BIN_BASE}\t\t${SHA_BIN_DEST}" >> ${DATA_DIR}/update_new.cfg - SS_UpdateSize(BASE_OLD, BASE_NEW) - - if not UPDATE_CFG_PATH == EMPTY: - f = open(UPDATE_CFG_PATH, 'r') - lines = f.readlines() - f.close() - f = open(UPDATE_CFG_PATH, 'w') - for line in lines: - ConfigItems = line.split() - if ConfigItems[0] == DELTA_BIN: - logging.info ('Updating %s config' % DELTA_BIN) - DELTA = ConfigItems[1] - line = line.rstrip('\n') - line = line.replace(line, line+'\t'+str(oldsize_d)+'\t\t'+str(newsize_d)+'\t\t'+str(SHA_BIN_BASE)+'\t\t'+str(SHA_BIN_DEST)+'\n') - f.write(line) - else: - f.write(line) - f.close() - -def zipdir(path, zip): - for root, dirs, files in os.walk(path): - for file in files: - zip.write(os.path.join(root, file)) - -def ensure_dir_exists(path): - if not os.path.exists(path): - os.makedirs(path) - #shutil.rmtree(path) - #os.makedirs(path) - - -def path_leaf(path): - head, tail = ntpath.split(path) #This is for windows?? Recheck - return tail - -def path_head(path): - head, tail = ntpath.split(path) - return head - -def difflines(list1, list2): - c = set(list1).union(set(list2)) - d = set(list1).intersection(set(list2)) - return list(c-d) - -#Creating Diff between OLD and NEW attribute files v12 -def Diff_AttrFiles(ATTR_OLD, ATTR_NEW, ATTR_FILE): - if GenerateDiffAttr == "FALSE": - return - with open(ATTR_OLD, 'r') as f_old: - lines1 = set(f_old.read().splitlines()) - - with open(ATTR_NEW, 'r') as f_new: - lines2 = set(f_new.read().splitlines()) - - lines = difflines(lines2, lines1) - with open(ATTR_FILE, 'w+') as file_out: - for line in lines: - if line not in lines1: - logging.info('Diff_AttrFiles - %s' % line) - file_out.write(line+'\n') - - f_new.close() - f_old.close() - file_out.close() - - - -def Update_Attr(RequestedPath, Type, File_Attibutes, Sym_Attibutes): - #Full File Path should MATCH - if GenerateDiffAttr == "FALSE": - return - FilePath = '"/'+RequestedPath+'"' - #print ('FilePath - %s'% (FilePath)) - with open(AttributeFile) as f: - for line in f: - if FilePath in line: - if Type == SYMLINK_TYPE: - Sym_Attibutes.append(line) - else: - File_Attibutes.append(line) - - -'''This function returns the SHA-1 hash of the file passed into it''' -def hash_file(filename): - - # make a hash object - h = hashlib.sha1() - - # open file for reading in binary mode - with open(filename,'rb') as file: - # loop till the end of the file - chunk = 0 - while chunk != b'': - # read only 1024 bytes at a time - chunk = file.read(1024*1024) - h.update(chunk) - - # return the hex representation of digest - return h.hexdigest() - -def find_dupes_dir(BASE_OLD, BASE_NEW): - dups = {} - fdupes = {} - print('Finding Duplicates in - %s %s' % (BASE_OLD, BASE_NEW)) - logging.info('Finding Duplicates in - %s %s' % (BASE_OLD, BASE_NEW)) - for rootbase, subdirsB, fileListB in os.walk(BASE_OLD): - #print('Scanning %s...' % rootbase) - for filename in fileListB: - path = os.path.join(rootbase, filename) - if os.path.islink(path): - continue - # Calculate hash - file_hash = hash_file(path) - dups[file_hash] = path - - for roottarget, subdirsT, fileListT in os.walk(BASE_NEW): - #print('Scanning %s...' % roottarget) - for filename in fileListT: - # Get the path to the file - path = os.path.join(roottarget, filename) - if os.path.islink(path): - continue - # Calculate hash - file_hash = hash_file(path) - # Add or append the file path - if file_hash in dups: - BaseStr = dups.get(file_hash) - Baseloc = path.find('/') - TarLoc = BaseStr.find('/') - if not path[Baseloc:] == BaseStr[TarLoc:]: - logging.info('Dupes - %s ==> %s' % (path[Baseloc:], BaseStr[TarLoc:])) - fdupes[path] = BaseStr - logging.info('Total Duplicate files %d' % (len(fdupes))) - return fdupes - - -def find_dupes_list(BASE_OLD, BASE_NEW, fileListB, fileListT): - dups = {} - fdupes = {} - print('Finding Duplicates in - %s %s' % (BASE_OLD, BASE_NEW)) - - for filename in fileListB: - Src_File = BASE_OLD+'/'+filename - if os.path.islink(Src_File) or os.path.isdir(Src_File): - continue - # Calculate hash - file_hash = hash_file(Src_File) - dups[file_hash] = Src_File - - - for filename in fileListT: - Dest_File = BASE_NEW+'/'+filename - if os.path.islink(Dest_File) or os.path.isdir(Dest_File): - continue - # Calculate hash - file_hash = hash_file(Dest_File) - if file_hash in dups: - BaseStr = dups.get(file_hash) - Baseloc = BaseStr.find('/') - if not BaseStr[Baseloc:] == filename: - #print('Dupes - %s ==> %s' % (BaseStr[Baseloc:], filename)) - fdupes[BaseStr] = filename - - logging.info('Total Duplicate files %d' % (len(fdupes))) - return fdupes - -def SS_UpdateSize(src_file, dst_file): - global MEM_REQ - oldsize_d= os.path.getsize(src_file) - newsize_d= os.path.getsize(dst_file) - if oldsize_d >= newsize_d: - Max = newsize_d - else: - Max = oldsize_d - if MEM_REQ < Max: - MEM_REQ = Max - - - -def SS_Generate_Delta(PART_NAME, BASE_OLD, Old_files, Old_dirs, BASE_NEW, New_files, New_dirs, OUT_DIR, ATTR_FILE): - print('Going from %d files to %d files' % (len(Old_files), len(New_files))) - logging.info('Going from %d files to %d files' % (len(Old_files), len(New_files))) - - # First let's fill up these categories - files_new = [] - files_removed = [] - Dir_removed = [] - Dir_Added = [] - files_changed = [] - files_unchanged = [] - files_renamed = [] - File_Attibutes = [] - Sym_Attibutes = [] - - files_Del_List = {} - files_New_List = {} - MyDict_Patches = {} - - - - PWD = os.getcwd() - - # Generate NEW List - for elt in New_files: - if elt not in Old_files: - files_new.append(elt) - logging.info('New files %s' % elt) - - # Generate Delete List - for elt in Old_files: - if elt not in New_files: - # Cant we just append it here only if this is NOT a directory???? so that we have list of removed files ONLY. including directories - files_removed.append(elt) - logging.info('Old files %s' % elt) - - - for elt in Old_dirs: - #print('List of Old Dirs %s' % elt) - if elt not in New_dirs: - Dir_removed.append(elt) - #print('Old Dirs %s' % elt+'/') - - for elt in New_dirs: - if elt not in Old_dirs: - Dir_Added.append(elt) - #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - # What files have changed contents but not name/path? - for elt in New_files: - if elt in Old_files: - #Both are symbolic linkes and they differ - src_file = BASE_OLD+'/'+elt - dst_file = BASE_NEW+'/'+elt - #print('Files Changed - %s -%s' % (src_file,dst_file)) - if os.path.islink(src_file) and os.path.islink(dst_file): - if not os.readlink(src_file) == os.readlink(dst_file): - files_changed.append(elt) - #print('%d Sym link files changed' % len(files_changed)) - logging.info('Sym links Changed - %s' % elt) - else: - files_unchanged.append(elt) - #Both are Normal files and they differ. (Is file returns true in case of symlink also, so additional check to find either of the file is symlink) - elif (not os.path.islink(src_file) or os.path.islink(dst_file)) and os.path.isfile(src_file) and os.path.isfile(dst_file): - if not filecmp.cmp(src_file, dst_file): - files_changed.append(elt) - #print('%d Normal files changed' % len(files_changed)) - #print('Files Changed - %s' % elt) - else: - files_unchanged.append(elt) - #File types differ between BASE and TARGET - else: - logging.info('Files are of diff types but same names Src- %s Des- %s' % (src_file, dst_file)) - #Both file types have changed and they differ - #Case 1: First Delete the OLD entry file type (Be it anything) - #Processing and updating partition txt file will be done under REMOVED case and NEW files case accordingly, we just make an entry here - files_removed.append(elt) - files_new.append(elt) - - - - #Currently if Version or number is the first character of the file, then we are NOT making any diffs. - if SUPPORT_RENAME == "TRUE": - for elt in files_removed: - if os.path.isfile(BASE_OLD+'/'+elt): - FileName = path_leaf(elt) - entries = re.split('[0-9]' , FileName) - #Gives the STRING part of NAME. if name starts with version then later part wil b string - #print('Entires under removed list after split - %s %s - %s' % (FileName, entries[0], elt)) - #If version is starting at the begining of the string?? shd we hav additional check for such cases?? - if len(entries[0]) > 0: - files_Del_List.update({entries[0]: elt}) - - for elt in files_new: - if os.path.isfile(BASE_NEW+'/'+elt): - FileName = path_leaf(elt) - entries = re.split('[0-9]' , FileName) - #print('Entires under NEWfiles list after split - %s %s - %s' % (FileName, entries[0], elt)) - if len(entries[0]) > 0: - files_New_List.update({entries[0]: elt}) - - for key, value in files_Del_List.iteritems(): - #print('Key value pair -%s -%s' % (key, value)) - if key in files_New_List: - # this file is the same name in both! - src_file = BASE_OLD+'/'+value - dst_file = BASE_NEW+'/'+files_New_List[key] - olddirpath = path_head(files_New_List[key]) - newdirpath = path_head(value) - if os.path.islink(src_file) or os.path.islink(dst_file): - logging.debug('Cannot diff as one of them is Symlink') - else: - #Pick the best diff of same type and diff names - files_renamed.append([files_New_List[key], value]) - files_removed.remove(value) - files_new.remove(files_New_List[key]) - - ''' - Patch Section - Partition.txt contains Protocol for UPI - Types Supported: DIFFS, MOVES, NEWS, DELETES, SYMDIFFS, SYMNEWS. - ''' - Sym_Diff_Cnt = 0 - Sym_New_Cnt = 0; - Del_Cnt = 0 - New_Cnt = 0 - Diff_Cnt = 0 - Move_Cnt = 0 - - SymLinkDoc = OUT_DIR+'/'+PART_NAME+SYMLINK_DOC_NAME - Partition_Doc = open(OUT_DIR+'/'+PART_NAME+'.txt','w') - Partition_Doc_SymLinks = open(SymLinkDoc,'w') - - print("writing diff'ed changed files...") - for elt in files_changed: - dst_file = BASE_NEW+'/'+elt - src_file = BASE_OLD+'/'+elt - #Both files are symbolic links and they differ - if os.path.islink(dst_file) and os.path.islink(src_file): - #Both are symlinks and they differ - logging.debug(' File Changed is Link %s ' % dst_file) - patch = os.readlink(dst_file) - Sym_Diff_Cnt = Sym_Diff_Cnt + 1 - Partition_Doc_SymLinks.write('SYM:DIFF:%s:%s:%s\n' % (elt, elt, patch)) - Update_Attr(elt, "SYM", File_Attibutes, Sym_Attibutes) - #Both are NORMAL files and they differ - elif (not os.path.islink(src_file) or os.path.islink(dst_file)) and os.path.isfile(dst_file) and os.path.isfile(src_file): - #Both are files and they differ - Diff_Cnt = Diff_Cnt + 1 - patchName = (DIFF_PREFIX+'%d_%s'+DIFF_SUFFIX) % (Diff_Cnt, path_leaf(elt)) - patchLoc = '%s/%s' % (OUT_DIR, patchName) - logging.debug(' File Differ %s %s' % (src_file, dst_file)) - SS_UpdateSize(src_file, dst_file) - if SUPPORT_CONTAINERS == "TRUE": - if src_file.endswith('.zip') and dst_file.endswith('.zip'): - FORMAT = "ZIP" - Partition_Doc.write('DIFF:ZIP:%s:%s:%s:%s:%s/\n' % (elt, elt, hash_file(src_file), hash_file(dst_file), patchName)) - compute_containerdelta(src_file, dst_file, FORMAT, OUT_DIR+'/'+patchName, Partition_Doc) - elif src_file.endswith('.tpk') and dst_file.endswith('.tpk'): - FORMAT = "TPK" - Partition_Doc.write('DIFF:TPK:%s:%s:%s:%s:%s/\n' % (elt, elt, hash_file(src_file), hash_file(dst_file), patchName)) - compute_containerdelta(src_file, dst_file, FORMAT, OUT_DIR+'/'+patchName, Partition_Doc) - else: - FORMAT = "REG" - Partition_Doc.write('DIFF:REG:%s:%s:%s:%s:%s\n' % (elt, elt, hash_file(src_file), hash_file(dst_file), patchName)) - subprocess.call([DIFF_UTIL,src_file,dst_file,patchLoc]) - else: - FORMAT = "REG" - Partition_Doc.write('DIFF:REG:%s:%s:%s:%s:%s\n' % (elt, elt, hash_file(src_file), hash_file(dst_file), patchName)) - subprocess.call([DIFF_UTIL,src_file,dst_file,patchLoc]) - Update_Attr(elt, "FILE", File_Attibutes, Sym_Attibutes) - #Both differ but they are of diff types - else: - #Processing and updating partition txt file will be done under REMOVED case and NEW files case accordingly, we just make an entry here - files_removed.append(elt) - files_new.append(elt) - - fdupes = find_dupes_list(BASE_OLD, BASE_NEW, files_removed, files_new) - for oldpath, newpath in fdupes.iteritems(): - logging.info('Dupes %s -> %s' % (oldpath, newpath)) - - for elt in files_removed: - src_file = BASE_OLD+'/'+elt - if src_file in fdupes.keys(): - dst_file = BASE_NEW+'/'+ fdupes[src_file] - logging.debug(' File Moved %s ==> %s' % (src_file, dst_file)) - Move_Cnt = Move_Cnt + 1 - Partition_Doc.write('MOVE:REG:%s:%s:%s\n' % (elt, fdupes[src_file], hash_file(src_file))) - #directories should b taken care?? +++++++++++++++++++++++++++++++++++++ PARENT DIREC if not present etc - files_removed.remove(elt) - files_new.remove(fdupes[src_file]) - - #Should be placed after removing duplicates, else they will be filtered here. - # loop shd b for all NEW files, rather than for all delete files ?? - DelList = files_removed[:] - NewList = files_new[:] - for new_file in NewList: - if os.path.islink(BASE_NEW+'/'+new_file): - continue - elif os.path.isdir(BASE_NEW+'/'+new_file): - continue - else:# os.path.isfile(BASE_NEW+'/'+new_file): - DirPathNew = path_head(new_file) - FileNameNew = path_leaf(new_file) - DiffSize = 0 - winning_patch_sz = os.path.getsize(BASE_NEW+'/'+new_file) - winning_file = '' - for del_file in DelList: - #print '+++++' - if os.path.islink(BASE_OLD+'/'+del_file): - continue - elif os.path.isdir(BASE_OLD+'/'+del_file): - continue - else: #if os.path.isfile(BASE_OLD+'/'+del_file): - FileNameOld = path_leaf(del_file) - if (FileNameOld.startswith(FileNameNew[:len(FileNameNew)/2]) and (os.path.splitext(FileNameNew)[1] == os.path.splitext(del_file)[1])): - #winning_patch_sz = 0.9 * os.path.getsize(BASE_NEW+'/'+new_file) - #logging.debug('I can compute diff between %s %s' % (del_file, new_file)) - DiffSize = measure_two_filediffs(BASE_OLD+'/'+del_file, BASE_NEW+'/'+new_file) - if (DiffSize < 0.8 * winning_patch_sz): - winning_patch_sz = DiffSize - winning_file = del_file - if len(winning_file) > 0: - logging.debug('Best Pick -%s ==> %s [%d]' % (winning_file, new_file, DiffSize)) - files_renamed.append([new_file, winning_file]) - DelList.remove(winning_file) - files_removed.remove(winning_file) - files_new.remove(new_file) - - #********************** SHOULD CHECK THIS LOGIC *********************** - - if SUPPORT_RENAME == "TRUE": - for elt in files_renamed: - src_file = BASE_OLD+'/'+elt[1] - dst_file = BASE_NEW+'/'+elt[0] - Diff_Cnt = Diff_Cnt + 1 - patchName = (DIFF_PREFIX+'%d_%s'+DIFF_SUFFIX) % (Diff_Cnt, path_leaf(elt[1])) - #patchName = (DIFF_PREFIX+'_%s'+DIFF_SUFFIX) % (path_leaf(elt[0])) - patchLoc = '%s/%s' % (OUT_DIR, patchName) - logging.debug(' File Renamed %s ==> %s' % (src_file, dst_file)) - # Should be careful of renaming files?? - # Should we consider measure_two_filediffs ?? so that patch size is NOT greater than actual file? - # What if folder path has numerics?? - - if os.path.isdir(src_file) or os.path.isdir(dst_file): - #This case never occurs?? - Partition_Doc.write('"%s" and "%s" renamed 0 0\n' % (elt[0], elt[1])) - Update_Attr(elt[0], "FILE", File_Attibutes, Sym_Attibutes) - else: #Make sure these files are PROPER and they shd NOT be symlinks - if filecmp.cmp(src_file, dst_file): - Move_Cnt = Move_Cnt + 1 - Diff_Cnt = Diff_Cnt - 1 - Partition_Doc.write('MOVE:REG:%s:%s:%s\n' % (elt[1], elt[0], hash_file(src_file))) - elif SUPPORT_CONTAINERS == "TRUE": - if src_file.endswith('.zip') and dst_file.endswith('.zip'): - FORMAT = "ZIP" - Partition_Doc.write('DIFF:ZIP:%s:%s:%s:%s:%s/\n' % (elt[1], elt[0], hash_file(src_file), hash_file(dst_file), patchName)) - compute_containerdelta(src_file, dst_file, FORMAT, OUT_DIR+'/'+patchName, Partition_Doc) - elif src_file.endswith('.tpk') and dst_file.endswith('.tpk'): - FORMAT = "TPK" - Partition_Doc.write('DIFF:TPK:%s:%s:%s:%s:%s/\n' % (elt[1], elt[0], hash_file(src_file), hash_file(dst_file), patchName)) - compute_containerdelta(src_file, dst_file, FORMAT, OUT_DIR+'/'+patchName, Partition_Doc) - else: - FORMAT = "REG" - Partition_Doc.write('DIFF:REG:%s:%s:%s:%s:%s\n' % (elt[1], elt[0], hash_file(src_file), hash_file(dst_file), patchName)) - subprocess.call([DIFF_UTIL,src_file,dst_file,patchLoc]) - else: - FORMAT = "REG" - Partition_Doc.write('DIFF:REG:%s:%s:%s:%s:%s\n' % (elt[1], elt[0], hash_file(src_file), hash_file(dst_file), patchName)) - subprocess.call([DIFF_UTIL,src_file,dst_file,patchLoc]) - - SS_UpdateSize(src_file, dst_file) - Update_Attr(elt[0], "FILE", File_Attibutes, Sym_Attibutes) - - - for elt in files_removed: - #if files are part of patches after renaming, we shd remove them as part of removed. - src_file = BASE_OLD+'/'+elt - if os.path.islink(src_file): - Partition_Doc.write('DEL:SYM:%s\n' % (elt)) - elif os.path.isdir(src_file): - Partition_Doc.write('DEL:DIR:%s\n' % (elt)) - else: - Partition_Doc.write('DEL:REG:%s:%s\n' % (elt, hash_file(src_file))) - logging.debug(' File Deleted %s' % src_file) - Del_Cnt = Del_Cnt + 1 - - for elt in Dir_removed: - #if Dir is empty, add it to the removed list. - src_file = BASE_OLD+'/'+elt - #Irrespective of weather files are MOVED or DIFF'ed, we can delete the folders. This action can be performed at the end. - #It covers symlinks also, as NEW symlinks cannot point to NON existant folders of TARGET (NEW binary) - if os.path.isdir(src_file): - Partition_Doc.write('DEL:END:%s\n' % (elt)) - Del_Cnt = Del_Cnt + 1 - logging.debug(' Dir Deleted- %s' % src_file) - - - for elt in files_new: - dst_file = BASE_NEW+'/'+elt - newfiles_dest_path = 'system/' - ensure_dir_exists(newfiles_dest_path) - if os.path.islink(dst_file): - patch = os.readlink(dst_file) - logging.debug(' File New Links %s' % elt) - Partition_Doc_SymLinks.write('SYM:NEW:%s:%s\n' % (elt, patch)) - #What if this is only a new sym link and folder already exists??? Should recheck - destpath = newfiles_dest_path + elt - if not os.path.exists(path_head(destpath)): - os.makedirs(path_head(destpath)) - logging.info('New SymLink - Adding missing Dir') - #Update_Attr(elt, "SYM", File_Attibutes, Sym_Attibutes) - Sym_New_Cnt = Sym_New_Cnt + 1 - elif os.path.isdir(dst_file): # We create just empty directory here - destpath = newfiles_dest_path + elt - if not os.path.exists(destpath): - os.makedirs(destpath) - logging.debug(' File New Dir %s' % destpath) - New_Cnt = New_Cnt + 1 - else: - New_Cnt = New_Cnt + 1 - #newfiles_dest_path = OUT_DIR + '/system/' - destpath = newfiles_dest_path + elt - destdir = os.path.dirname(destpath) - logging.debug('New files - %s ==> %s' % (dst_file, destdir)) - - if not os.path.isdir(destdir): - try: - os.makedirs(destdir) - except: - logging.critical('Error in NEW files DIR entry -%s' % destdir) - raise - - try: - shutil.copy2(dst_file, destpath) - logging.debug('New files copied from- %s to- %s' % (dst_file, destpath)) - except: - logging.critical('Error in NEW files entry -%s -%s' % (dst_file, destpath)) - raise - - for elt in Dir_Added: - newfiles_dest_path = 'system/' - ensure_dir_exists(newfiles_dest_path) - destpath = newfiles_dest_path + elt - if not os.path.exists(destpath): - os.makedirs(destpath) - logging.debug(' DirList New Dir %s' % destpath) - New_Cnt = New_Cnt + 1 - - #Base directory should be system - print 'Compressing New files' - if (New_Cnt > 0): - WorkingDir = os.getcwd() - os.chdir(os.getcwd()+"/"+NEW_FILES_PATH) - logging.info('Curr Working Dir - %s' % os.getcwd()) - os.system(ZIPUTIL+NEW_FILES_PATH+" >> " + LOGFILE) - shutil.move(NEW_FILES_ZIP_NAME, WorkingDir+"/"+OUT_DIR) - #New file size?? cos, we extract system.7z from delta.tar and then proceed with decompression - SS_UpdateSize(WorkingDir+"/"+OUT_DIR+"/"+NEW_FILES_ZIP_NAME, WorkingDir+"/"+OUT_DIR+"/"+NEW_FILES_ZIP_NAME) - os.chdir(WorkingDir) - shutil.rmtree(NEW_FILES_PATH) - # use 7z a system.7z ./* - - #logging.info('%d Dir to be removed' % len(Dir_removed)) - logging.info('%d files unchanged' % len(files_unchanged)) - logging.info('%d files files_renamed' % len(files_renamed)) - logging.info('%d files NEW' % len(files_new)) - logging.info('%d File attr' % len(File_Attibutes)) - logging.info('%d Sym attr' % len(Sym_Attibutes)) - logging.info('PaTcHCoUnT:Diffs-%d Moves-%d News-%d Delets-%d SymDiffs-%d SymNews-%d\n' % (Diff_Cnt, Move_Cnt, New_Cnt, Del_Cnt, Sym_Diff_Cnt, Sym_New_Cnt)) - print('PaTcHCoUnT:Diffs-%d Moves-%d News-%d Delets-%d SymDiffs-%d SymNews-%d\n' % (Diff_Cnt, Move_Cnt, New_Cnt, Del_Cnt, Sym_Diff_Cnt, Sym_New_Cnt)) - - #There could be duplicates, TODO, can check before adding.. - ATTR_FILE = open(ATTR_FILE,'a+') - for elt in File_Attibutes: - ATTR_FILE.write(elt) - for elt in Sym_Attibutes: - ATTR_FILE.write(elt) - - ATTR_FILE.close() - - Partition_Doc_SymLinks.close() - Partition_Read_SymLinks = open(SymLinkDoc,'r+') - Partition_Doc.write(Partition_Read_SymLinks.read()) - Partition_Doc.write('PaTcHCoUnT:%d %d %d %d %d %d\n' % (Diff_Cnt, Move_Cnt, New_Cnt, Del_Cnt, Sym_Diff_Cnt, Sym_New_Cnt)) - Partition_Doc_SymLinks.close() - Partition_Doc.close() - os.remove(SymLinkDoc) - - -def Apply_Container_Delta(a_apk, b_apk, new_apk, a_folder, g_output_dir): - - #CONTROL NAMES, AND PRINTS AND ERROR CASES... SHOULD NOT PROCEED. - print 'ApplyContainerDelta - ', b_apk, a_folder, g_output_dir - shutil.copy2(g_output_dir+'/'+b_apk, g_output_dir+'/temp') - temp_apk = '../'+g_output_dir+'/'+b_apk - Patch = 'Patch_'+b_apk - ensure_dir_exists(Patch) - shutil.copy2(g_output_dir+'/'+b_apk, Patch+'/'+b_apk) - - #Size issue on Device side?? shd check this - subprocess.call(['unzip','-q', Patch+'/'+b_apk, '-d', Patch]) - with open(g_output_dir+'/PATCH.txt', 'r') as f_new: - lines = set(f_new.read().splitlines()) - for line in lines: - #print('Action ==> %s' % line) - #Action, Path, Patch = line.split('|') - Items = line.split('|') - Action = Items[0] - Path = Items[1] - ActualPath = a_folder+'/'+Path - PatchPath = Patch+'/'+Path - SrcPath = g_output_dir+'/'+path_leaf(Path) - #print('Action ==> %s Path ==> %s ' % (Action, Path)) - if line[0] == 'c': - patchName = g_output_dir+'/'+Items[2] - #print('Apply Patch: ActualPath %s SrcPath %s PatchLoc %s ' % (PatchPath, ActualPath, patchName)) - subprocess.call([DIFFPATCH_UTIL,ActualPath,ActualPath,patchName]) - WorkingDir = os.getcwd() - os.chdir(WorkingDir+"/"+"temp_a") - subprocess.call(['cp', '--parents', Path, '../'+Patch]) - os.chdir(WorkingDir) - elif line[0] == 's': - WorkingDir = os.getcwd() - os.chdir(WorkingDir+"/"+"temp_a") - subprocess.call(['cp', '--parents', Path, '../'+Patch]) - os.chdir(WorkingDir) - else: - print('Unknown Error') - #print('Touch all files and set common attributes for DIFF generation') - WorkingDir = os.getcwd() - os.chdir(WorkingDir+"/"+Patch) - - CONTAINER_DATE = '200011111111.11' - CONTAINER_MODE = '0755' - subprocess.call(['find', '.', '-type', 'l', '-exec', 'rm', '-rf', '{}', ';']) - subprocess.call(['find', '.', '-exec', 'touch', '-t', CONTAINER_DATE, '{}', ';']) - subprocess.call(['chmod', '-R', CONTAINER_MODE, '../'+Patch]) - - print 'Update Intermediate Archive' - #subprocess.call(['zip','-ryX', b_apk, '*']) - subprocess.call(['zip','-ryX', b_apk] + glob.glob('*')) - os.chdir(WorkingDir) - #print('Apply Path completed - Now create diff for this and place in patch folder') - #print os.getcwd() - print('Patch Applied, Create Final Diff - %s %s' % (g_output_dir+'/'+b_apk,new_apk)) - patchName = ('New'+'_%s'+DIFF_SUFFIX) % (b_apk) - patchLoc = '%s/%s' % (g_output_dir, patchName) - - subprocess.call([DIFF_UTIL, Patch+'/'+b_apk ,new_apk,patchLoc]) - - #Only on HOST... for testing - if TEST_MODE == 'TRUE': - UpgradedName = '%s_Upgraded' % (b_apk) - subprocess.call([DIFFPATCH_UTIL,Patch+'/'+b_apk,UpgradedName,patchLoc]) - - #This is file only with NEWS and empty diffs and same files. - if TEST_MODE == 'FALSE': - os.remove(g_output_dir+'/'+b_apk) - os.rename(g_output_dir+'/temp', g_output_dir+'/'+b_apk) - shutil.rmtree(Patch) - -def IsSymlink(info): - return (info.external_attr >> 16) == 0120777 - - -def compute_containerdelta(src_file, dst_file, FORMAT, patchName, Partition_Doc): - - a_apk = src_file - b_apk = dst_file - a_folder = 'temp_a' - b_folder = 'temp_b' - - g_output_dir = patchName - - logging.info('Uncompressing Containers... [%s][%s]' % (src_file, dst_file)) - logging.info('Out Dir -%s' %(g_output_dir)) - ensure_dir_exists(a_folder) - zipf = zipfile.ZipFile(a_apk, 'r'); - zipf.extractall(a_folder) - zipf.close() - - ensure_dir_exists(b_folder) - zipf = zipfile.ZipFile(b_apk, 'r'); - zipf.extractall(b_folder) - - - symlinks = [] - for info in zipf.infolist(): - basefilename = info.filename[7:] - if IsSymlink(info): - symlinks.append(info.filename) - os.remove(b_folder+'/'+info.filename) - zipf.close() - - a_files, a_dirs = Get_Files(a_folder) - b_files, b_dirs = Get_Files(b_folder) - - logging.info('Going from %d files %d files' % (len(a_files), len(b_files))) - - # First let's fill up these categories - C_files_new = [] - C_files_removed = [] - C_files_changed = [] - C_files_unchanged = [] - - # What files appear in B but not in A? - for elt in b_files: - if elt not in a_files: - #if not elt.endswith('.so'): - C_files_new.append(elt) - - # What files appear in A but not in B? - for elt in a_files: - if elt not in b_files: - C_files_removed.append(elt) - - # What files have changed contents but not name/path? - for elt in b_files: - if elt in a_files: - if os.path.islink(a_folder+'/'+elt) or os.path.islink(b_folder+'/'+elt): - print 'links - skip' - elif not filecmp.cmp(a_folder+'/'+elt, b_folder+'/'+elt): - C_files_changed.append(elt) - else: - C_files_unchanged.append(elt) - - - print('%d new files' % len(C_files_new)) - print('%d removed files' % len(C_files_removed)) - print('%d files changed' % len(C_files_changed)) - print('%d files unchanged' % len(C_files_unchanged)) - - # temp dir where we're assembling the patch - ensure_dir_exists(g_output_dir) - - unique_fileid = 0 - toc = open(g_output_dir+'/PATCH.txt','w') - print("writing diff'ed changed files...") - - for elt in C_files_changed: - dst_file = b_folder+'/'+elt - src_file = a_folder+'/'+elt - patchName = (DIFF_PREFIX+'%d_%s'+DIFF_SUFFIX) % (unique_fileid, path_leaf(elt)) - patchLoc = '%s/%s' % (g_output_dir, patchName) - #print('src - %s dest -%s patch -%s' % (src_file ,dst_file,patchLoc)) - subprocess.call([DIFF_UTIL,src_file ,dst_file,patchLoc]) - toc.write('c%d|%s|%s\n' % (unique_fileid, elt, patchName)) - unique_fileid = unique_fileid + 1 - - for elt in C_files_unchanged: - dst_file = b_folder+'/'+elt - src_file = a_folder+'/'+elt - #print('Same Files src - %s dest -%s' % (src_file ,dst_file)) - toc.write('s%d|%s\n' % (unique_fileid, elt)) - unique_fileid = unique_fileid + 1 - - #Create NEW TPK with empty data for below files and NEW files - shutil.copy2(b_apk, g_output_dir) - - #May b for host?? - #temp_apk = '../'+g_output_dir+'/'+b_apk - temp_apk = '../'+g_output_dir+'/'+path_leaf(b_apk) - - for elt in C_files_changed: - dst_file = b_folder+'/'+elt - #print dst_file - open(dst_file, 'w').close() - - for elt in C_files_unchanged: - dst_file = b_folder+'/'+elt - open(dst_file, 'w').close() - - WorkingDir = os.getcwd() - os.chdir(WorkingDir+"/"+b_folder) - - #for elt in files_changed: - # subprocess.call(['zip', temp_apk, elt]) # confirm ZIP options, extra fields etc.. jus zip it, shd do all at once.. else time taking - - #for elt in files_unchanged: - # subprocess.call(['zip', temp_apk, elt]) - - subprocess.call(['zip','-ryq', temp_apk, '*']) - os.chdir(WorkingDir) - toc.close() - - Apply_Container_Delta(path_leaf(a_apk), path_leaf(b_apk), b_apk, a_folder, g_output_dir) - shutil.rmtree(a_folder) - shutil.rmtree(b_folder) - -def NewFiles(src, dest): - print src,dest - subprocess.call(['cp','-rp', src,dest]) - #try: - #shutil.copytree(src, dest) - #except OSError as e: - # If the error was caused because the source wasn't a directory - #if e.errno == errno.ENOTDIR: - #shutil.copy2(src, dest) - #else: - #print('Directory not copied. Error: %s' % e) - -def measure_two_filediffs(src, dst): - patchLoc = 'temp.patch' - subprocess.call([DIFF_UTIL,src,dst,patchLoc]) - result_size = os.path.getsize(patchLoc) - os.remove(patchLoc) - return result_size - -def Get_Files(path): - all_files = [] - all_dirs = [] - - for root, directories, filenames in os.walk(path, topdown=False, followlinks=False): - for directory in directories: - #DirName = os.path.join(root+'/',directory) - DirName = os.path.join(root,directory) - if os.path.islink(DirName): - logging.debug('This is symlink pointing to dir -%s' % DirName) - all_files.append(os.path.relpath(DirName, path)) - elif not os.listdir(DirName): - #print('*****Empty Directory******* -%s', DirName) - #This should NOT be appended ??? Empty dir shd b considered - all_dirs.append(os.path.relpath(DirName, path)) - else: - all_dirs.append(os.path.relpath(DirName, path)) - for filename in filenames: - FileName = os.path.join(root,filename) - all_files.append(os.path.relpath(FileName, path)) - - all_files.sort() - all_dirs.sort() - return all_files, all_dirs - - -USAGE_DOCSTRING = """ - Generate Delta using BASEOLD AND BASE NEW - Attributes is optional -""" - -def Usage(docstring): - print docstring.rstrip("\n") - print COMMON_DOCSTRING - - - -if __name__ == '__main__': - main() - diff --git a/UPG/dzImagescript.sh b/UPG/dzImagescript.sh deleted file mode 100755 index f96bc4f..0000000 --- a/UPG/dzImagescript.sh +++ /dev/null @@ -1,255 +0,0 @@ -#!/bin/bash - -pname="${0##*/}" -args=("$@") -cur_dir="$(pwd)" - -# file names: -decompression_code="decompression_code" -piggy_gz_piggy_trailer="piggy.gz+piggy_trailer" -piggy="piggy" -piggy_gz="piggy.gz" -padding_piggy="padding_piggy" -piggy_trailer="piggy_trailer" -ramfs_gz_part3="initramfs.cpio+part3" -ramfs_cpio_gz="initramfs.cpio.gz" -padding3="padding3" -part3="part3" -kernel_img="kernel.img" -ramfs_cpio="initramfs.cpio" -ramfs_dir="initramfs" -sizes="sizes" -ramfs_part3="ramfs+part3" -ramfs_list="initramfs_list" -cpio_t="cpio-t" - - -cpio="cpio_set0" - -# We dup2 stderr to 3 so an error path is always available (even -# during commands where stderr is redirected to /dev/null). If option -# -v is set, we dup2 sterr to 9 also so commands (and some of their -# results if redirected to &9) are printed also. -exec 9>/dev/null # kill diagnostic ouput (will be >&2 if -v) -exec 3>&2 # an always open error channel - -# -########### Start of functions -# - -# Emit an error message and abort -fatal(){ - # Syntax: fatal - # Output error message, then abort - echo >&3 - echo >&3 "$pname: $*" - kill $$ - exit 1 -} - -# Execute a command, displaying the command if -v: -cmd(){ - # Syntax: cmd - # Execute , echo command line if -v - echo >&9 "$*" - "$@" -} - -# Execute a required command, displaying the command if -v, abort on -# error: -rqd(){ - # Syntax: cmd - # Execute , echo commandline if -v, abort on error - cmd "$@" || fatal "$* failed." -} - -findByteSequence(){ - # Syntax: findByteSequence [] - # Returns: position (offset) on stdout, empty string if nothing found - file="$1" - local opt - if [ "$2" = "lzma" ]; then - srch=$'\x5d....\xff\xff\xff\xff\xff' - opt= - else - srch="${2:-$'\x1f\x8b\x08'}" # Default: search for gzip header - opt="-F" - fi - pos=$(LC_ALL=C grep $opt -a --byte-offset -m 1 --only-matching -e "$srch" -- "$file") - echo ${pos%%:*} -} - -getFileSize(){ - # Syntax: getFileSize - # Returns size of the file on stdout. - # Aborts if file doesn't exist. - rqd stat -c %s "$1" -} -checkNUL(){ - # Syntax: checkNUL file offset - # Returns true (0) if byte there is 0x0. - [ "$(rqd 2>/dev/null dd if="$1" skip=$2 bs=1 count=1)" = $'\0' ] -} - -gunzipWithTrailer(){ - # Syntax gunzipWithTrailer - # - # : the input file - # , , : - # The output files. For the gzipped part, both the - # compressed and the uncompressed output is generated, so we have - # 4 output files. - local file="$1" - local gz_result="$2.gz" - local result="$2" - local padding="$3" - local trailer="$4" - local tmpfile="/tmp/gunzipWithTrailer.$$.gz" - local original_size=$(getFileSize "$file") - local d=$(( (original_size+1) / 2)) - local direction fini at_min=0 - local results_at_min=() - local size=$d - local at_min= - echo "Separating gzipped part from trailer in "$file"" - echo -n "Trying size: $size" - while :; do - rqd dd if="$file" of="$tmpfile" bs=$size count=1 2>/dev/null - cmd gunzip >/dev/null 2>&1 -c "$tmpfile" - res=$? - if [ "$d" -eq 1 ]; then - : $((at_min++)) - results_at_min[$size]=1 - [ "$at_min" -gt 3 ] && break - fi - d=$(((d+1)/2)) - case $res in - # 1: too small - 1) size=$((size+d)); direction="↑";; - # 2: trailing garbage - 2) size=$((size-d)); direction="↓";; - # OK - 0) break;; - *) fatal "gunzip returned $res while checking "$file"";; - esac - echo -n " $size" - done - if [ "$at_min" -gt 3 ]; then - echo -e "\ngunzip result is oscillating between 'too small' and 'too large' at size: ${!results_at_min[*]}" - echo -n "Trying lower nearby values: " - fini= - for ((d=1; d < 30; d++)); do - : $((size--)) - echo -n " $size" - rqd dd if="$file" of="$tmpfile" bs=$size count=1 2>/dev/null - if cmd gunzip >/dev/null 2>&1 -c "$tmpfile"; then - echo -n " - OK" - fini=1 - break - fi - done - [ -z "$fini" ] && fatal 'oscillating gunzip result, giving up.' - fi - # We've found the end of the gzipped part. This is not the real - # end since gzip allows for some trailing padding to be appended - # before it barfs. First, go back until we find a non-null - # character: - echo -ne "\npadding check (may take some time): " - real_end=$((size-1)) - while checkNUL "$file" $real_end; do - : $((real_end--)) - done - # Second, try if gunzip still succeeds. If not, add trailing - # null(s) until it succeeds: - while :; do - rqd dd if="$file" of="$tmpfile" bs=$real_end count=1 2>/dev/null - gunzip >/dev/null 2>&1 -c "$tmpfile" - case $? in - # 1: too small - 1) : $((real_end++));; - *) break;; - esac - done - real_next_start=$size - # Now, skip NULs forward until we reach a non-null byte. This is - # considered as being the start of the next part. - while checkNUL "$file" $real_next_start; do - : $((real_next_start++)) - done - echo $((real_next_start - real_end)) - echo - rm "$tmpfile" - # Using the numbers we got so far, create the output files which - # reflect the parts we've found so far: - rqd dd 2>&9 if="$file" of="$gz_result" bs=$real_end count=1 - rqd dd 2>&9 if="$file" of="$padding" skip=$real_end bs=1 count=$((real_next_start - real_end)) - rqd dd 2>&9 if="$file" of="$trailer" bs=$real_next_start skip=1 - rqd gunzip -c "$gz_result" > "$result" -} - - -unpack()( - [ -d "$unpacked" ] && echo "\ -Warning: there is aready an unpacking directory. If you have files added on -your own there, the repacking result may not reflect the result of the -current unpacking process." - rqd mkdir -p "$unpacked" - rqd cd "$unpacked" - sizes="$unpacked/sizes" - echo "# Unpacking sizes" > "$sizes" - - piggy_start=$(findByteSequence "$cur_dir/$zImage") - if [ -z "$piggy_start" ]; then - fatal "Can't find a gzip header in file '$zImage'" - fi - - rqd dd 2>&9 if="$cur_dir/$zImage" bs="$piggy_start" count=1 of="$decompression_code" - rqd dd 2>&9 if="$cur_dir/$zImage" bs="$piggy_start" skip=1 of="$piggy_gz_piggy_trailer" - - gunzipWithTrailer "$piggy_gz_piggy_trailer" \ - "$piggy" "$padding_piggy" "$piggy_trailer" - - echo - sudo rm -rf "piggy.gz" "piggy.gz+piggy_trailer" "sizes" - echo "Success." - echo "The unpacked files and the initramfs directory are in "$unpacked"" -) - -#### start of main program -while getopts xv12345sgrpuhtz-: argv; do - case $argv in - p|u|z|1|2|3|4|5|t|r|g) eval opt_$argv=1;; - v) exec 9>&2; opt_v=1;; - s) cpio="cpio";; - x) set -x;; - -) if [ "$OPTARG" = "version" ]; then - echo "$pname $version" - exit 0 - else - echo "Wrong Usage, use -u to unpack" - fi;; - h|-) echo "Wrong Usage, use -u to unpack";; - *) fatal "Illegal option";; - esac -done -shift $((OPTIND-1)) -zImage="${1:-zImage}" -unpacked="$cur_dir/${zImage}_unpacked" -packing="$cur_dir/${zImage}_packing" -shift -if [ -n "$*" ]; then - fatal "Excess arguments: '$*'" -fi - -if [ -n "$opt_u" ]; then - [ -f "$zImage" ] || fatal "file '$zImage': not found" - unpack -fi -if [ -z "$opt_u" ]; then - echo >&2 "$pname: Need at least -u option." - echo >&2 "$pname: Type '$pname --help' for usage info." - exit 1 -fi - -exit - diff --git a/UPG/ss_bsdiff b/UPG/ss_bsdiff deleted file mode 100755 index d1293cd30d4fb106cff93e499a1530c939830ebb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 212699 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⁢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 diff --git a/UPG/unpack.sh b/UPG/unpack.sh deleted file mode 100755 index 869881d..0000000 --- a/UPG/unpack.sh +++ /dev/null @@ -1,236 +0,0 @@ -#!/bin/bash - -pname="${0##*/}" -args=("$@") -cur_dir="$(pwd)" - -# file names: -decompression_code="decompression_code" -piggy_gz_piggy_trailer="piggy.gz+piggy_trailer" -piggy="piggy" -piggy_gz="piggy.gz" -padding_piggy="padding_piggy" -piggy_trailer="piggy_trailer" -padding3="padding3" -sizes="sizes" - -# We dup2 stderr to 3 so an error path is always available (even -# during commands where stderr is redirected to /dev/null). If option -# -v is set, we dup2 sterr to 9 also so commands (and some of their -# results if redirected to &9) are printed also. -exec 9>/dev/null # kill diagnostic ouput (will be >&2 if -v) -exec 3>&2 # an always open error channel - -# -########### Start of functions -# - -# Emit an error message and abort -fatal(){ - # Syntax: fatal - # Output error message, then abort - echo >&3 - echo >&3 "$pname: $*" - kill $$ - exit 1 -} - -# Execute a command, displaying the command if -v: -cmd(){ - # Syntax: cmd - # Execute , echo command line if -v - echo >>"$workspace/log_file" "$*" - "$@" -} - -# Execute a required command, displaying the command if -v, abort on -# error: -rqd(){ - # Syntax: cmd - # Execute , echo commandline if -v, abort on error - cmd "$@" || fatal "$* failed." -} - -checkNUL(){ - # Syntax: checkNUL file offset - # Returns true (0) if byte there is 0x0. - [ "$(rqd 2>>"$workspace/log_file" "$workspace/dd" if="$1" skip=$2 bs=1 count=1)" = $'\0' ] -} - -gunzipWithTrailer(){ - # Syntax gunzipWithTrailer - # - # : the input file - # , , : - # The output files. For the gzipped part, both the - # compressed and the uncompressed output is generated, so we have - # 4 output files. - local file="$1" - local gz_result="$2.gz" - local result="$2" - local padding="$3" - local trailer="$4" - local tmpfile="/tmp/gunzipWithTrailer.$$.gz" - local original_size=$("$workspace/stat" -c %s "$unpacked/$file") 2>>"$workspace/log_file" - echo "Original size is $original_size" >> "$workspace/log_file" - local d=$(( (original_size+1) / 2)) - local direction fini at_min=0 - local results_at_min=() - local size=$d - local at_min= - rm -rf /tmp/test_file - echo "Separating gzipped part from trailer in "$unpacked/$file"" >> "$workspace/log_file" - echo -n "Trying size: $size" >> "$workspace/log_file" - while :; do - rqd "$workspace/dd" if="$unpacked/$file" of="$tmpfile" bs=$size count=1 2>>"$workspace/log_file" - cmd "$workspace/gzip" >/tmp/test_file 2>>"$workspace/log_file" -d -c "$tmpfile" - res=$? - echo "result for gunzip is $res" >>"$workspace/log_file" - if [ "$d" -eq 1 ]; then - : $((at_min++)) - results_at_min[$size]=1 - [ "$at_min" -gt 3 ] && break - fi - d=$(((d+1)/2)) - case $res in - # 1: too small - 1) echo "In case 1" >> "$workspace/log_file" - size=$((size+d)); direction="↑";; - # 2: trailing garbage - 2) echo "In case 2" >> "$workspace/log_file" - size=$((size-d)); direction="↓";; - # OK - 0) echo "Breaking" >> "$workspace/log_file" - break;; - *) echo "In case *" >> "$workspace/log_file" - fatal "gunzip returned $res while checking "$unpacked/$file"";; - esac - echo -n " $size" >> "$workspace/log_file" - done - if [ "$at_min" -gt 3 ]; then - echo -e "\ngunzip result is oscillating between 'too small' and 'too large' at size: ${!results_at_min[*]}" >> "$workspace/log_file" - echo -n "Trying lower nearby values: " >> "$workspace/log_file" - fini= - for ((d=1; d < 30; d++)); do - : $((size--)) - echo -n " $size" >> "$workspace/log_file" - rqd "$workspace/dd" if="$unpacked/$file" of="$tmpfile" bs=$size count=1 2>/dev/null - if cmd "$workspace/gzip" >/dev/null 2>&1 -d -c "$tmpfile"; then - echo -n " - OK" >> "$workspace/log_file" - fini=1 - break - fi - done - [ -z "$fini" ] && fatal 'oscillating gunzip result, giving up.' - fi - # We've found the end of the gzipped part. This is not the real - # end since gzip allows for some trailing padding to be appended - # before it barfs. First, go back until we find a non-null - # character: - echo -ne "\npadding check (may take some time): " >> "$workspace/log_file" - real_end=$((size-1)) - while checkNUL "$unpacked/$file" $real_end; do - : $((real_end--)) - done - echo "Found real end at $real_end" >> "$workspace/log_file" - # Second, try if gunzip still succeeds. If not, add trailing - # null(s) until it succeeds: - while :; do - rqd "$workspace/dd" if="$unpacked/$file" of="$tmpfile" bs=$real_end count=1 2>>"$workspace/log_file" - "$workspace/gzip" >/tmp/test_file2 2>>"$workspace/log_file" -d -c "$tmpfile" - case $? in - # 1: too small - 1) echo "In case 1" >> "$workspace/log_file" - : $((real_end++));; - *) echo "Case other $?" >> "$workspace/log_file" - break;; - esac - done - echo "Done with add trailing null(s) until it succeeds" >> "$workspace/log_file" - real_next_start=$size - # Now, skip NULs forward until we reach a non-null byte. This is - # considered as being the start of the next part. - while checkNUL "$unpacked/$file" $real_next_start; do - : $((real_next_start++)) - done - echo $((real_next_start - real_end)) >> "$workspace/log_file" - echo >> "$workspace/log_file" - rm "$tmpfile" - # Using the numbers we got so far, create the output files which - # reflect the parts we've found so far: - rqd "$workspace/dd" if="$unpacked/$file" of="$unpacked/$gz_result" bs=$real_end count=1 - rqd "$workspace/dd" if="$unpacked/$file" of="$unpacked/$padding" skip=$real_end bs=1 count=$((real_next_start - real_end)) - rqd "$workspace/dd" if="$unpacked/$file" of="$unpacked/$trailer" bs=$real_next_start skip=1 - rqd "$workspace/gzip" -c -d "$unpacked/$gz_result" > "$unpacked/$result" -} - -unpack()( - [ -d "$unpacked" ] && echo "\ -Warning: there is aready an unpacking directory. If you have files added on -your own there, the repacking result may not reflect the result of the -current unpacking process." - rqd mkdir -p "$unpacked" - rqd cd "$unpacked" - sizes="$unpacked/sizes" - echo "# Unpacking sizes" > "$sizes" - log_file="$unpacked/log_file" - #piggy_start=$1 - if [ -z "$piggy_start" ]; then - fatal "Can't find a gzip header in file '$zImage'" >> "$workspace/log_file" - fatal "Can't find a gzip header in file '$zImage'" - else - echo "start is $piggy_start" >> "$sizes" - fi - - rqd "$workspace/dd" if="$zImage" bs="$piggy_start" count=1 of="$unpacked/$decompression_code" - rqd "$workspace/dd" if="$zImage" bs="$piggy_start" skip=1 of="$piggy_gz_piggy_trailer" - - gunzipWithTrailer "$piggy_gz_piggy_trailer" \ - "$piggy" "$padding_piggy" "$piggy_trailer" - - echo - echo "Success." -) - -#### start of main program -while getopts xv12345sgrpuhtz-: argv; do - case $argv in - p|z|1|2|3|4|5|t|r|g) eval opt_$argv=1;; - u) opt_u=1 - workspace=$2 - zImage="$2/$3" - piggy_start=$4 - unpacked="${zImage}_unpacked" - packing="${zImage}_packing";; - -) if [ "$OPTARG" = "version" ]; then - echo "$pname $version" - exit 0 - else - echo "Wrong Usage, use -u to unpack" - fi;; - h|-) echo "Wrong Usage, use -u to unpack";; - *) fatal "Illegal option";; - esac -done -if [ -n "$opt_u" ]; then - [ -f "$zImage" ] || fatal "file '$zImage': not found" - unpack -fi -if [ -n "$opt_p" ]; then - work_dir=$2 - tgt_file=$3 - cmd_dir=$4 - rqd cd "$work_dir" - #remove all links before proceeding with zip processing - "$cmd_dir/find" . -type l -exec rm {} \; - "$cmd_dir/find" . -exec touch -t 200011111111.11 {} \; - "$cmd_dir/find" . -exec chmod 0755 {} \; - "$cmd_dir/zip" -ryX "$tgt_file" * -fi -if [ -z "$opt_u$opt_p" ]; then - echo >&2 "$pname: Need -u or -p option to work" - echo >&2 "$pname: Type '$pname --help' for usage info." - exit 1 -fi - -exit diff --git a/packaging/libtota.spec b/packaging/libtota.spec index b9e7a58..a873d50 100755 --- a/packaging/libtota.spec +++ b/packaging/libtota.spec @@ -1,8 +1,8 @@ Name: libtota Summary: fota update library ExclusiveArch: %{arm} -Version: 0.2.0 -Release: 1 +Version: 0.2.1 +Release: 2 Group: System License: Apache-2.0 and BSD-2-Clause and BSD-3-Clause and PD Source0: %{name}-%{version}.tar.gz -- 2.7.4 From 1c119e5e4772acb3cb3c72369cc3b5ea9d79f7c0 Mon Sep 17 00:00:00 2001 From: Sunmin Lee Date: Wed, 24 May 2017 15:17:24 +0900 Subject: [PATCH 09/13] Patch for x86 Resolve issues of building libtota in x86, x86_64 architecture. Change-Id: I70331c9d9246ca72d57e6d3928bf03ee9a65ae94 Signed-off-by: Sunmin Lee --- CMakeLists.txt | 2 +- packaging/libtota.spec | 5 ++--- ss_engine/fota_common.h | 3 +++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a0f93e..fe4c89d 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,7 +77,7 @@ TARGET_LINK_LIBRARIES(${LIBNAME} ${packages_LDFLAGS}) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/libtota.a DESTINATION ${LIB_INSTALL_DIR}) #CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/modulename-api.pc.in ${CMAKE_CURRENT_BINARY_DIR}/modulename-api.pc @ONLY) #INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/modulename-api.pc DESTINATION lib/pkgconfig) -INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/tota.pc DESTINATION lib/pkgconfig) +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/tota.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) FOREACH(hfile ${HEADERS}) INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${hfile} DESTINATION include) ENDFOREACH(hfile) diff --git a/packaging/libtota.spec b/packaging/libtota.spec index a873d50..5f4c488 100755 --- a/packaging/libtota.spec +++ b/packaging/libtota.spec @@ -1,8 +1,7 @@ Name: libtota Summary: fota update library -ExclusiveArch: %{arm} -Version: 0.2.1 -Release: 2 +Version: 0.2.2 +Release: 3 Group: System License: Apache-2.0 and BSD-2-Clause and BSD-3-Clause and PD Source0: %{name}-%{version}.tar.gz diff --git a/ss_engine/fota_common.h b/ss_engine/fota_common.h index 8424a45..f9beb06 100755 --- a/ss_engine/fota_common.h +++ b/ss_engine/fota_common.h @@ -30,7 +30,10 @@ typedef unsigned short u16; typedef signed int s32; typedef unsigned int u32; +#ifndef __size_t /* typedef check for x86 env: stddef.h */ +#define __size_t typedef u32 size_t; +#endif /* __size_t */ typedef signed long sl32; typedef unsigned long ul32; -- 2.7.4 From 16f7977131d07ce6a9cd27c4a54722cbb1bf1541 Mon Sep 17 00:00:00 2001 From: Sunmin Lee Date: Tue, 4 Jul 2017 10:38:05 +0900 Subject: [PATCH 10/13] Enhacement internal log system - Considering log-size management for efficent storage usage Change-Id: I762288c5ffb51717a3d5ee4b9b2b6d3e1aebf26f Signed-off-by: Sunmin Lee --- ss_engine/fota_common.h | 2 + ss_engine/fota_log.c | 126 ++++++++++++++++++++++++++++++++++++++++++++---- ss_engine/fota_log.h | 2 +- 3 files changed, 119 insertions(+), 11 deletions(-) diff --git a/ss_engine/fota_common.h b/ss_engine/fota_common.h index f9beb06..91af6fa 100755 --- a/ss_engine/fota_common.h +++ b/ss_engine/fota_common.h @@ -41,6 +41,8 @@ typedef unsigned long ul32; typedef signed long long s64; typedef unsigned long long u64; +#define MAX_FILE_PATH 512 + #ifndef TIME_PROFILING //#define TIME_PROFILING #endif diff --git a/ss_engine/fota_log.c b/ss_engine/fota_log.c index 7d7c44e..8421eac 100755 --- a/ss_engine/fota_log.c +++ b/ss_engine/fota_log.c @@ -17,12 +17,113 @@ */ #include +#include #include +#include +#include +#include +#include +#include +#include + +#define LOG_SIZE_OPT_PATH "/opt/data/recovery/.ua_log_size" +#define DEF_MAX_LOG_SIZE (2*1024*1024) +#define MAX_FILE_PATH 512 long curr_offset = 0; long next_offset = 0; -long cut_offset = 0; -long max_logfile_size = (2*1024*1024); +long backup_offset = 0; +long max_logfile_size = DEF_MAX_LOG_SIZE; + +/*----------------------------------------------------------------------------- + __check_existence + ----------------------------------------------------------------------------*/ +static long __check_existence(const char *file_path) +{ + struct stat statbuf; + char filename[MAX_FILE_PATH]; + + if (strncpy(filename, file_path, strlen(file_path) + 1) == NULL) { + return 0; + } + if (stat(filename, &statbuf)) { + if (ENOENT == errno) { + return 0; + } + } + return statbuf.st_size; +} + +/*----------------------------------------------------------------------------- + __read_from_file + ----------------------------------------------------------------------------*/ +static int __read_from_file(const char *path, char *buf, size_t size) +{ + int fd; + ssize_t count; + + if (!path) + return -1; + + if (size == 0) { + return 0; + } + + fd = open(path, O_RDONLY, 0); + if (fd == -1) { + return -1; + } + + count = read(fd, buf, size); + if (count > 0) { + count = (count < (ssize_t)size) ? count : ((ssize_t)size - 1); + while (count > 0 && buf[count - 1] == '\n') + count--; + buf[count] = '\0'; + } else { + buf[0] = '\0'; + } + + close(fd); + + return (int)count; +} + +/*----------------------------------------------------------------------------- + get_opt_logfile_size + ----------------------------------------------------------------------------*/ +static int get_opt_logfile_size(void) +{ + /* + if status file does not exist, status = UP_START_NONE. + if status file exist, read status from file. + */ + char buf[256]; + + if (__check_existence(LOG_SIZE_OPT_PATH) == 0) { + return -1; + } + + if (__read_from_file(LOG_SIZE_OPT_PATH, buf, sizeof(buf)) < 0) { + return -1; + } + + return atoi(buf); +} + +/*----------------------------------------------------------------------------- + set_max_logfile_size + ----------------------------------------------------------------------------*/ +void set_max_logfile_size(void) +{ + int size = get_opt_logfile_size(); + + if (size <= 0) { + size = DEF_MAX_LOG_SIZE; + } + + max_logfile_size = size; +} /*----------------------------------------------------------------------------- log_printf @@ -31,8 +132,8 @@ int log_printf(FILE* log_fp, char* format_str, ...) { int ret = 0; char log_str[4096]; + char backup_ch; int len; - int wlen; va_list list; va_start(list, format_str); @@ -43,8 +144,7 @@ int log_printf(FILE* log_fp, char* format_str, ...) next_offset = curr_offset + len; if (next_offset <= max_logfile_size) { - wlen = len; - if (fwrite(log_str, 1, wlen, log_fp) != wlen) { + if (fprintf(log_fp, "%s", log_str) < 0) { ret = -1; goto exit; } @@ -54,15 +154,16 @@ int log_printf(FILE* log_fp, char* format_str, ...) curr_offset = 0; } } else { - cut_offset = max_logfile_size - curr_offset; - wlen = cut_offset; - if (fwrite(log_str, 1, wlen, log_fp) != wlen) { + backup_offset = max_logfile_size - curr_offset; + backup_ch = log_str[backup_offset]; + log_str[backup_offset] = 0x00; + if (fprintf(log_fp, "%s", log_str) < 0) { ret = -1; goto exit; } rewind(log_fp); - wlen = next_offset - max_logfile_size; - if (fwrite(log_str+cut_offset, 1, wlen, log_fp) != wlen) { + log_str[backup_offset] = backup_ch; + if (fprintf(log_fp, "%s", log_str+backup_offset) < 0) { ret = -1; goto exit; } @@ -80,6 +181,11 @@ void truncate_log_file(char *log_path, int size_kb) { FILE *log_fp; + if (max_logfile_size != DEF_MAX_LOG_SIZE) { + /* This means someone wants to see log file, so not truncate. */ + return; + } + if (size_kb == 0) { log_fp = fopen(log_path, "w"); if (log_fp == NULL) { diff --git a/ss_engine/fota_log.h b/ss_engine/fota_log.h index e03beed..6c9aca5 100755 --- a/ss_engine/fota_log.h +++ b/ss_engine/fota_log.h @@ -29,6 +29,7 @@ extern unsigned int __log_level__; extern FILE *__log_out_file__; extern int log_printf(FILE* log_fp, char* format_str, ...); extern void truncate_log_file(char *log_path, int size_kb); +extern void set_max_logfile_size(void); #define LOG_INFO (1<<8) #define LOG_ENGINE (1<<7) @@ -37,7 +38,6 @@ extern void truncate_log_file(char *log_path, int size_kb); #define LOG_DEBUG (1<<4) #define LOG_FILE (1<<3) #define LOG_FLASH (1<<2) - #define LOG_SSENGINE LOG_ENGINE //#define DEBUG_STDOUT -- 2.7.4 From 4a0e65a2c10bdd8291a7929bc11d36e40ebe9c90 Mon Sep 17 00:00:00 2001 From: Kunhoon Baik Date: Fri, 14 Jul 2017 18:09:24 +0900 Subject: [PATCH 11/13] Providing local build script for server developer Change-Id: I87422c50d3e11b9d5a065f8583b2f27b3aa50c45 --- local_build.sh | 5 +++++ 1 file changed, 5 insertions(+) create mode 100755 local_build.sh diff --git a/local_build.sh b/local_build.sh new file mode 100755 index 0000000..3f3d42a --- /dev/null +++ b/local_build.sh @@ -0,0 +1,5 @@ +mkdir -p local_build +cd local_build +cmake ../bsdiff +make install +cd .. -- 2.7.4 From be318ce89f88a5012ceece5e5893f2269467cba6 Mon Sep 17 00:00:00 2001 From: Sunmin Lee Date: Mon, 24 Jul 2017 10:44:18 +0900 Subject: [PATCH 12/13] Make SS_FSUpdate feature valid After get the SS_FSUpdate from tota-ua, the feature which is determined in build time doesn't work any longer. To make it valid, use flag variable instead of preprocessor defined value. Change-Id: I7d2531dde17798f1b0c21612c83600f5f409f22f Signed-off-by: Sunmin Lee --- ss_engine/SS_Common.c | 2 +- ss_engine/SS_FSUpdate.c | 199 ++++++++++++++++++++------------------ ss_engine/SS_FSUpdate.h | 22 ++++- ss_engine/SS_MultiProcessUpdate.h | 4 +- 4 files changed, 127 insertions(+), 100 deletions(-) diff --git a/ss_engine/SS_Common.c b/ss_engine/SS_Common.c index 876fb21..adbc49b 100755 --- a/ss_engine/SS_Common.c +++ b/ss_engine/SS_Common.c @@ -45,7 +45,7 @@ void SS_Progress(void *pbUserData, SS_UINT32 uPercent) SS_UINT32 SS_Trace(void *pUser, const char *aFormat, ...) { #if 0 - LOGL(LOG_REDBEND, aFormat); + LOGL(LOG_SSENGINE, aFormat); #else char temp[4096]; va_list list; diff --git a/ss_engine/SS_FSUpdate.c b/ss_engine/SS_FSUpdate.c index cb03e4c..7dd75db 100755 --- a/ss_engine/SS_FSUpdate.c +++ b/ss_engine/SS_FSUpdate.c @@ -37,6 +37,8 @@ #include "fota_common.h" #include "ua.h" +static int feature_support_capability; + /************************************************************ * common functions ************************************************************/ @@ -208,7 +210,7 @@ long SS_DeleteFolderEmpty(void *pbUserData, const char *strPath) char path[MAX_PATH] = { '\0' }; SS_unicode_to_char((const char *)strPath, (char *)path); - //LOGL(LOG_REDBEND, "%s\n", path); + //LOGL(LOG_SSENGINE, "%s\n", path); ret = rmdir(path); if ((ret == 0) || ((ret < 0) && ((errno == ENOENT) || (errno == ENOTEMPTY)))) { @@ -761,8 +763,8 @@ void SS_chtoa(int value, char *str) * The file attributes token (\a ui8pAttribs) is defined at generation time. * If attributes are not defined explicitly, they are given the following, * OS-dependent values: - * \li Windows: _redbend_ro_ for R/O files, _redbend_rw_ for R/W files - * \li Linux: _redbend_oooooo:xxxx:yyyy indicating the file mode, uid, and gid + * \li Windows: _foo_ro_ for R/O files, _foo_rw_ for R/W files + * \li Linux: _foo_oooooo:xxxx:yyyy indicating the file mode, uid, and gid * (uid and gid use capitalized hex digits as required) * * \param pbUserData Optional opaque data-structure to pass to IPL @@ -793,7 +795,6 @@ long SS_SetFileAttributes(const char *ui16pFilePath, struct stat sbuf; int ret = 0; char *smack_attr_pos = NULL; -#if defined(FEATURE_SUPPORT_CAPABILITY) int has_cap = 0; char cap_raw[100]; int cap_len; @@ -802,7 +803,6 @@ long SS_SetFileAttributes(const char *ui16pFilePath, char acl_raw[256]; int acl_len; -#endif if (NULL == ui16pFilePath) { LOGL(LOG_SSENGINE, "ui16pFilePath NULL [error]\n"); return E_SS_BAD_PARAMS; @@ -865,91 +865,92 @@ long SS_SetFileAttributes(const char *ui16pFilePath, smack_attr_pos++; setGroupID = (gid_t) strtol(tp, &endstr, 10); } -#if defined(FEATURE_SUPPORT_CAPABILITY) - // Get Capability - has_cap = 0; - if (*smack_attr_pos != '\0') { - char *cap_mark = "capability=0x"; - int cap_mark_len = strlen(cap_mark); - psmack = strstr(smack_attr_pos, cap_mark); - if(psmack) { - int cap_hex_len; - int i; - char ch1, ch2; - int raw1, raw2; - tp = strstr(psmack, ":"); - smack_attr_pos = tp + 1; - memset(tmpSmackAttribs, 0x0, sizeof(tmpSmackAttribs)); - memcpy(tmpSmackAttribs, psmack+cap_mark_len, - (int)tp - (int)psmack - cap_mark_len); - - // convert hexadecimal into raw data - cap_hex_len = strlen(tmpSmackAttribs); - cap_len = cap_hex_len/2; - memset(cap_raw, 0x00, sizeof(cap_raw)); - for (i=0; i= '0')&&(ch1 <= '9')) raw1 = ch1 - '0'; - else if ((ch1 >= 'a')&&(ch1 <= 'f')) raw1 = ch1 - 'a' + 10; - else if ((ch1 >= 'A')&&(ch1 <= 'F')) raw1 = ch1 - 'A' + 10; - else raw1 = 0; - if ((ch2 >= '0')&&(ch2 <= '9')) raw2 = ch2 - '0'; - else if ((ch2 >= 'a')&&(ch2 <= 'f')) raw2 = ch2 - 'a' + 10; - else if ((ch2 >= 'A')&&(ch2 <= 'F')) raw2 = ch2 - 'A' + 10; - else raw2 = 0; - - cap_raw[i] = raw1*16 + raw2; + if (feature_support_capability) { + // Get Capability + has_cap = 0; + if (*smack_attr_pos != '\0') { + char *cap_mark = "capability=0x"; + int cap_mark_len = strlen(cap_mark); + psmack = strstr(smack_attr_pos, cap_mark); + if(psmack) { + int cap_hex_len; + int i; + char ch1, ch2; + int raw1, raw2; + + tp = strstr(psmack, ":"); + smack_attr_pos = tp + 1; + memset(tmpSmackAttribs, 0x0, sizeof(tmpSmackAttribs)); + memcpy(tmpSmackAttribs, psmack+cap_mark_len, + (int)tp - (int)psmack - cap_mark_len); + + // convert hexadecimal into raw data + cap_hex_len = strlen(tmpSmackAttribs); + cap_len = cap_hex_len/2; + memset(cap_raw, 0x00, sizeof(cap_raw)); + for (i=0; i= '0')&&(ch1 <= '9')) raw1 = ch1 - '0'; + else if ((ch1 >= 'a')&&(ch1 <= 'f')) raw1 = ch1 - 'a' + 10; + else if ((ch1 >= 'A')&&(ch1 <= 'F')) raw1 = ch1 - 'A' + 10; + else raw1 = 0; + if ((ch2 >= '0')&&(ch2 <= '9')) raw2 = ch2 - '0'; + else if ((ch2 >= 'a')&&(ch2 <= 'f')) raw2 = ch2 - 'a' + 10; + else if ((ch2 >= 'A')&&(ch2 <= 'F')) raw2 = ch2 - 'A' + 10; + else raw2 = 0; + + cap_raw[i] = raw1*16 + raw2; + } + LOGL(LOG_SSENGINE, "[Cap] %s (cap_len=%d)\n", tmpSmackAttribs, cap_len); + has_cap = 1; } - LOGL(LOG_SSENGINE, "[Cap] %s (cap_len=%d)\n", tmpSmackAttribs, cap_len); - has_cap = 1; - } - } - // Get ACL - has_acl = 0; - if (*smack_attr_pos != '\0') { - char *acl_mark = "acl_access=0x"; - int acl_mark_len = strlen(acl_mark); - psmack = strstr(smack_attr_pos, acl_mark); - if(psmack) { - int acl_hex_len; - int i; - char ch1, ch2; - int raw1, raw2; - - tp = strstr(psmack, ":"); - smack_attr_pos = tp + 1; - memset(tmpSmackAttribs, 0x0, sizeof(tmpSmackAttribs)); - memcpy(tmpSmackAttribs, psmack+acl_mark_len, - (int)tp - (int)psmack - acl_mark_len); - - // convert hexadecimal into raw data - acl_hex_len = strlen(tmpSmackAttribs); - acl_len = acl_hex_len/2; - memset(acl_raw, 0x00, sizeof(acl_raw)); - for (i=0; i= '0')&&(ch1 <= '9')) raw1 = ch1 - '0'; - else if ((ch1 >= 'a')&&(ch1 <= 'f')) raw1 = ch1 - 'a' + 10; - else if ((ch1 >= 'A')&&(ch1 <= 'F')) raw1 = ch1 - 'A' + 10; - else raw1 = 0; - if ((ch2 >= '0')&&(ch2 <= '9')) raw2 = ch2 - '0'; - else if ((ch2 >= 'a')&&(ch2 <= 'f')) raw2 = ch2 - 'a' + 10; - else if ((ch2 >= 'A')&&(ch2 <= 'F')) raw2 = ch2 - 'A' + 10; - else raw2 = 0; - - acl_raw[i] = raw1*16 + raw2; - } - LOG("[ACL] %s (acl_len=%d)\n", tmpSmackAttribs, acl_len); - has_acl = 1; } + // Get ACL + has_acl = 0; + if (*smack_attr_pos != '\0') { + char *acl_mark = "acl_access=0x"; + int acl_mark_len = strlen(acl_mark); + psmack = strstr(smack_attr_pos, acl_mark); + if(psmack) { + int acl_hex_len; + int i; + char ch1, ch2; + int raw1, raw2; + + tp = strstr(psmack, ":"); + smack_attr_pos = tp + 1; + memset(tmpSmackAttribs, 0x0, sizeof(tmpSmackAttribs)); + memcpy(tmpSmackAttribs, psmack+acl_mark_len, + (int)tp - (int)psmack - acl_mark_len); + + // convert hexadecimal into raw data + acl_hex_len = strlen(tmpSmackAttribs); + acl_len = acl_hex_len/2; + memset(acl_raw, 0x00, sizeof(acl_raw)); + for (i=0; i= '0')&&(ch1 <= '9')) raw1 = ch1 - '0'; + else if ((ch1 >= 'a')&&(ch1 <= 'f')) raw1 = ch1 - 'a' + 10; + else if ((ch1 >= 'A')&&(ch1 <= 'F')) raw1 = ch1 - 'A' + 10; + else raw1 = 0; + if ((ch2 >= '0')&&(ch2 <= '9')) raw2 = ch2 - '0'; + else if ((ch2 >= 'a')&&(ch2 <= 'f')) raw2 = ch2 - 'a' + 10; + else if ((ch2 >= 'A')&&(ch2 <= 'F')) raw2 = ch2 - 'A' + 10; + else raw2 = 0; + + acl_raw[i] = raw1*16 + raw2; + } + LOG("[ACL] %s (acl_len=%d)\n", tmpSmackAttribs, acl_len); + has_acl = 1; + } + } } -#endif // Get Smack value -> Set Smack value if (*smack_attr_pos != '\0') { smack_lsetlabel(setFilePath, NULL, SMACK_LABEL_ACCESS); @@ -1045,20 +1046,21 @@ long SS_SetFileAttributes(const char *ui16pFilePath, LOGL(LOG_SSENGINE, "%s chmod error\n", __func__); return E_SS_FAILURE; } -#if defined(FEATURE_SUPPORT_CAPABILITY) - if (has_cap) { - if (setxattr(setFilePath, "security.capability", (void*)cap_raw, cap_len, 0) < 0) { - LOGL(LOG_SSENGINE, "cap setxattr() failed: %s\n", strerror(errno)); + + if (feature_support_capability) { + if (has_cap) { + if (setxattr(setFilePath, "security.capability", (void*)cap_raw, cap_len, 0) < 0) { + LOGL(LOG_SSENGINE, "cap setxattr() failed: %s\n", strerror(errno)); + } } - } - if (has_acl) { - if (setxattr(setFilePath, "system.posix_acl_access", (void*)acl_raw, acl_len, 0) < 0) { - LOGL(LOG_SSENGINE, "Acl setxattr() failed: %s\n", strerror(errno)); + if (has_acl) { + if (setxattr(setFilePath, "system.posix_acl_access", (void*)acl_raw, acl_len, 0) < 0) { + LOGL(LOG_SSENGINE, "Acl setxattr() failed: %s\n", strerror(errno)); + } + //LOG("Acl setxattr() :")asfd } - //LOG("Acl setxattr() :")asfd } -#endif //LOGL(LOG_SSENGINE, "%s SUCCESS\n", __func__); @@ -1205,7 +1207,7 @@ void* SS_RunProcess(void *pbUserData, int argc, char* argv[]) if (child_pid == 0) { #ifdef _NOEXEC_ - #ifdef _TIZEN_REDBEND//bota + #ifdef _TIZEN_SSENGINE//bota SS_HandleProcessRequest(pbUserData, argc, argv); #endif LOGL(LOG_SSENGINE, "SS_RunProcess was called - SS_HandleProcessRequest\n"); @@ -1240,5 +1242,12 @@ void* SS_RunProcess(void *pbUserData, int argc, char* argv[]) } } +int SS_get_feature_support_capability(void) +{ + return feature_support_capability; +} - +void SS_set_feature_support_capability(int val) +{ + feature_support_capability = val; +} diff --git a/ss_engine/SS_FSUpdate.h b/ss_engine/SS_FSUpdate.h index b221582..3bc4e88 100755 --- a/ss_engine/SS_FSUpdate.h +++ b/ss_engine/SS_FSUpdate.h @@ -306,8 +306,8 @@ extern "C" { * The file attributes token (\a ui8pAttribs) is defined at generation time. * If attributes are not defined explicitly, they are given the following, * OS-dependent values: - * \li Windows: _redbend_ro_ for R/O files, _redbend_rw_ for R/W files - * \li Linux: _redbend_oooooo:xxxx:yyyy indicating the file mode, uid, and gid + * \li Windows: _foo_ro_ for R/O files, _foo_rw_ for R/W files + * \li Linux: _foo_oooooo:xxxx:yyyy indicating the file mode, uid, and gid * (uid and gid use capitalized hex digits as required) * * \param pbUserData Optional opaque data-structure to pass to IPL @@ -363,6 +363,24 @@ extern "C" { */ void SS_Free(void *pMemBlock); +/** + ******************************************************************************* + * Get capability feature flag value + * + * \return The value of feature_support_capability + ******************************************************************************* + */ + int SS_get_feature_support_capability(void); + +/** + ******************************************************************************* + * Set capability feature flag + * + * \param val The value to set feature_support_capability + ******************************************************************************* + */ + void SS_set_feature_support_capability(int val); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/ss_engine/SS_MultiProcessUpdate.h b/ss_engine/SS_MultiProcessUpdate.h index 7b85830..73a0d75 100755 --- a/ss_engine/SS_MultiProcessUpdate.h +++ b/ss_engine/SS_MultiProcessUpdate.h @@ -66,8 +66,8 @@ * You must implement this functionality. ******************************************************************************* */ -#ifndef _REDBEND_MULTIPROCESS_UPDATE_H_ -#define _REDBEND_MULTIPROCESS_UPDATE_H_ +#ifndef _SSENGINE_MULTIPROCESS_UPDATE_H_ +#define _SSENGINE_MULTIPROCESS_UPDATE_H_ #include "SS_Engine_Update.h" #include "SS_Engine_Errors.h" -- 2.7.4 From b8405008cc351f6f87d3fea08a4290607f2bd01b Mon Sep 17 00:00:00 2001 From: Sunmin Lee Date: Mon, 11 Sep 2017 18:46:20 +0900 Subject: [PATCH 13/13] Fix build warnings Change-Id: I3701c06d65674f862f5171c011bc41df5d74cecc Signed-off-by: Sunmin Lee --- bsdiff/ss_bsdiff.c | 32 +++++++++++++------------------- bsdiff/ss_bspatch.c | 12 ++++++------ bsdiff/ss_bspatch_common.c | 2 +- bsdiff/ss_bspatch_common.h | 2 +- packaging/libtota.spec | 4 ++-- ss_engine/SS_ApplyPatch.c | 7 ++++--- ss_engine/SS_FSUpdate.c | 3 ++- ss_engine/SS_PatchDelta.c | 5 ++++- ss_engine/SS_UPI.c | 32 ++++++++++++++++++-------------- ss_engine/fota_tar.c | 6 +++++- 10 files changed, 56 insertions(+), 49 deletions(-) diff --git a/bsdiff/ss_bsdiff.c b/bsdiff/ss_bsdiff.c index 94ce98a..102f8ad 100755 --- a/bsdiff/ss_bsdiff.c +++ b/bsdiff/ss_bsdiff.c @@ -108,7 +108,7 @@ struct data_thread struct data_thread data; -void* Function(int); +int Function(int); #endif @@ -135,18 +135,16 @@ static off_t search(saidx_t *I, u_char *old, off_t oldsize, } } - if (en - st < 2) { - x = matchlen(old + I[st], oldsize - I[st], new, newsize); - y = matchlen(old + I[en], oldsize - I[en], new, newsize); + x = matchlen(old + I[st], oldsize - I[st], new, newsize); + y = matchlen(old + I[en], oldsize - I[en], new, newsize); - if (x > y) { - *pos = I[st]; - return x; - } else { - *pos = I[en]; - return y; - } - }; + if (x > y) { + *pos = I[st]; + return x; + } else { + *pos = I[en]; + return y; + } } static void offtout(off_t x, u_char *buf) @@ -185,7 +183,7 @@ static void offtout(off_t x, u_char *buf) buf[7] |= 0x80; } -int create_patch(int argc, char *argv[], int offset_oldscore) +int create_patch(int argc, const char *argv[], int offset_oldscore) { data.num_threads = MULTI_THREADING; data.new = (u_char **)malloc(sizeof(u_char *)*data.num_threads); @@ -284,7 +282,7 @@ int create_patch(int argc, char *argv[], int offset_oldscore) return ret; } -void* Function(int offset_oldscore) +int Function(int offset_oldscore) { unsigned int thread_num = 0; off_t end; @@ -318,6 +316,7 @@ void* Function(int offset_oldscore) off_t lens; off_t i; + pos = 0; len = 0; lastscan = 0; lastpos = 0; @@ -524,10 +523,7 @@ int main2(int numArgs, const char *args[], char *rs) { CFileSeqInStream inStream; CFileOutStream outStream; - char c; int res; - int encodeMode; - Bool useOutFile = False; FileSeqInStream_CreateVTable(&inStream); File_Construct(&inStream.file); @@ -535,8 +531,6 @@ int main2(int numArgs, const char *args[], char *rs) FileOutStream_CreateVTable(&outStream); File_Construct(&outStream.file); - encodeMode = 1; - size_t t4 = sizeof(UInt32); size_t t8 = sizeof(UInt64); if (t4 != 4 || t8 != 8) diff --git a/bsdiff/ss_bspatch.c b/bsdiff/ss_bspatch.c index be9ef1c..2c33a09 100755 --- a/bsdiff/ss_bspatch.c +++ b/bsdiff/ss_bspatch.c @@ -80,11 +80,11 @@ int PrintUserError(char *buffer) int main2(int numArgs, const char *args[], char *rs) { CFileSeqInStream inStream; - ISeqOutStream outStream; + CFileOutStream outStream; int res, fd; int encodeMode; - char * buf_res = NULL; - unsigned char* new_data; + unsigned char *buf_res = NULL; + unsigned char *new_data; ssize_t new_size; FileSeqInStream_CreateVTable(&inStream); @@ -110,7 +110,7 @@ int main2(int numArgs, const char *args[], char *rs) File_GetLength(&inStream.file, &fileSize); //res = Encode(&outStream.s, &inStream.s, fileSize, rs); } else { - UInt64 unpackSize, i, dest_len; + UInt64 unpackSize, i; CLzmaDec state; unsigned char header[LZMA_PROPS_SIZE + 8]; RINOK(SeqInStream_Read(&inStream.s, header, sizeof(header))); @@ -119,10 +119,9 @@ int main2(int numArgs, const char *args[], char *rs) unpackSize += (UInt64)header[LZMA_PROPS_SIZE + i] << (i * 8); buf_res = (unsigned char *)malloc(unpackSize); memset(buf_res, 0x0, unpackSize); - dest_len = unpackSize; LzmaDec_Construct(&state); RINOK(LzmaDec_Allocate(&state, header, LZMA_PROPS_SIZE, &g_Alloc)); - res = Decode2(&state, &outStream, &inStream.s, &unpackSize, buf_res); + res = Decode2(&state, &outStream.s, &inStream.s, &unpackSize, buf_res); LzmaDec_Free(&state, &g_Alloc); File_Close(&inStream.file); if (apply_patch(args[1], buf_res, &new_data, &new_size) != 0) { @@ -152,6 +151,7 @@ int main2(int numArgs, const char *args[], char *rs) free(buf_res); return 0; } + return 1; } int main(int numArgs, const char *args[]) diff --git a/bsdiff/ss_bspatch_common.c b/bsdiff/ss_bspatch_common.c index baad6bf..ffb5a7e 100755 --- a/bsdiff/ss_bspatch_common.c +++ b/bsdiff/ss_bspatch_common.c @@ -143,7 +143,7 @@ SRes Decode2(CLzmaDec *state, ISeqOutStream *outStream, ISeqInStream *inStream, } } -int apply_patch(char *oldfile, char *patch_buffer, unsigned char **dest_buf, ssize_t *dest_size) +int apply_patch(const char *oldfile, unsigned char *patch_buffer, unsigned char **dest_buf, ssize_t *dest_size) { int fd = -1, result = 0; off_t oldsize, newsize; diff --git a/bsdiff/ss_bspatch_common.h b/bsdiff/ss_bspatch_common.h index bd59396..3075ea2 100644 --- a/bsdiff/ss_bspatch_common.h +++ b/bsdiff/ss_bspatch_common.h @@ -12,6 +12,6 @@ extern ISzAlloc g_Alloc; SRes Decode2(CLzmaDec *state, ISeqOutStream *outStream, ISeqInStream *inStream, UInt64 *unpackSize, unsigned char *dec_data); -int apply_patch(char *oldfile, char *patch_buffer, unsigned char **dest_buf, ssize_t *dest_size); +int apply_patch(const char *oldfile, unsigned char *patch_buffer, unsigned char **dest_buf, ssize_t *dest_size); #endif /* _SS_BSPATCH_COMMON_H */ diff --git a/packaging/libtota.spec b/packaging/libtota.spec index 5f4c488..5bec19f 100755 --- a/packaging/libtota.spec +++ b/packaging/libtota.spec @@ -1,7 +1,7 @@ Name: libtota Summary: fota update library -Version: 0.2.2 -Release: 3 +Version: 0.2.3 +Release: 4 Group: System License: Apache-2.0 and BSD-2-Clause and BSD-3-Clause and PD Source0: %{name}-%{version}.tar.gz diff --git a/ss_engine/SS_ApplyPatch.c b/ss_engine/SS_ApplyPatch.c index 7ec6975..58ce8af 100755 --- a/ss_engine/SS_ApplyPatch.c +++ b/ss_engine/SS_ApplyPatch.c @@ -34,6 +34,7 @@ #include "fota_common.h" #include "sha1.h" #include "SS_Engine_Errors.h" +#include "SS_FSUpdate.h" #include "ss_bspatch_common.h" #include @@ -49,7 +50,7 @@ int SS_ApplyBsdiff(char *oldfile, char *newfile, char *patch, SinkFn sink, void UInt64 unpackSize = 0; CFileSeqInStream inStream; ISeqOutStream outStream; - char *buf_res = NULL; + unsigned char *buf_res = NULL; unsigned char *new_data = NULL; ssize_t new_size = 0; int result = E_SS_FAILURE; @@ -75,12 +76,12 @@ int SS_ApplyBsdiff(char *oldfile, char *newfile, char *patch, SinkFn sink, void RINOK(LzmaDec_Allocate(&state, header, LZMA_PROPS_SIZE, &g_Alloc)); //decompress the patch file into buf_res - buf_res = (char *)SS_Malloc(unpackSize); + buf_res = (unsigned char *)SS_Malloc(unpackSize); if (!buf_res) { LOGE("Bad memory allocation\n"); goto Cleanup; } - result = Decode2(&state, &outStream, &inStream.s, &unpackSize, (unsigned char *)buf_res); + result = Decode2(&state, &outStream, &inStream.s, &unpackSize, buf_res); LzmaDec_Free(&state, &g_Alloc); File_Close(&inStream.file); diff --git a/ss_engine/SS_FSUpdate.c b/ss_engine/SS_FSUpdate.c index 7dd75db..23b602f 100755 --- a/ss_engine/SS_FSUpdate.c +++ b/ss_engine/SS_FSUpdate.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -111,7 +112,7 @@ long SS_recursive_folder_creater(const char *path, const mode_t mode) if (ret == 0 || ((ret == -1) && (errno == EEXIST))) { return 0; //meaning the depth creation is success. } else if ((ret == -1) && (errno == ENOENT)) { - if ((ret = SS_recursive_folder_creater(temppath, mode)) == 0); + if ((ret = SS_recursive_folder_creater(temppath, mode)) == 0) ret = mkdir(temppath, mode); return ret; } else { diff --git a/ss_engine/SS_PatchDelta.c b/ss_engine/SS_PatchDelta.c index fd00053..0146afa 100755 --- a/ss_engine/SS_PatchDelta.c +++ b/ss_engine/SS_PatchDelta.c @@ -934,8 +934,11 @@ int SS_UpdateDeltaKernel(ua_dataSS_t * ua_dataSS, int (*write_to_blkdev) (char * result = E_SS_MALLOC_ERROR; goto Cleanup; } - fread(buf, 1, SS_KERNEL_DELTA_HEADER, fp); + ssize_t bytes_read = fread(buf, 1, SS_KERNEL_DELTA_HEADER, fp); + if (bytes_read != SS_KERNEL_DELTA_HEADER) + LOGL(LOG_SSENGINE, "short read of \"%s\" (%ld bytes of %ld)\n", SS_PATCHFILE_SOURCE, (long)bytes_read, (long)SS_KERNEL_DELTA_HEADER); magic = strtok(buf, ":"); + LOGL(LOG_SSENGINE, "magic: %s\n", magic); file_num = atoi(strtok(NULL, ":")); //adjust offset to start of data section before proceeding diff --git a/ss_engine/SS_UPI.c b/ss_engine/SS_UPI.c index 6a48e54..2a9dbff 100755 --- a/ss_engine/SS_UPI.c +++ b/ss_engine/SS_UPI.c @@ -123,6 +123,19 @@ static double get_time_stamp2(void) } #endif +//Check SS function if available +int file_exist(char *filename, int type) +{ + struct stat buf; + int ret = 0; + + ret = lstat(filename, &buf); + if (ret < 0) { + ret = stat(filename, &buf); + } + return (ret >= 0) ? (1) : (0); +} + long SS_GetUPIVersion(unsigned char *ver_str) { if (ver_str) { @@ -300,19 +313,6 @@ int SS_GetUpgradeState() return FS_UpgradeState; } -//Check SS function if available -int file_exist(char *filename, int type) -{ - struct stat buf; - int ret = 0; - - ret = lstat(filename, &buf); - if (ret < 0) { - ret = stat(filename, &buf); - } - return (ret >= 0) ? (1) : (0); -} - int SS_rename(const char *old_file_name, const char *new_file_name) { if (link(old_file_name, new_file_name) < 0) { @@ -1156,6 +1156,7 @@ int SS_FSSetAttributes(ua_dataSS_t * ua_dataSS) pfiletype = strtok(NULL, SS_TOKEN_SPACE); attributSize = strtok(NULL, SS_TOKEN_SPACE); pattribs = strtok(NULL, SS_TOKEN_NEWLINE); + LOG("\nSS_FSSetAttributes [%s][%s][%s]", pfiletype, attributSize, pattribs); if (pattribs && pfilePath && pfiletype) { ulAttribSize = strlen(pattribs); //LOG("\nSS_SetFileAttributes [%s][%s][%d][%s]",pfilePath,pfiletype,ulAttribSize, pattribs ); @@ -1962,8 +1963,11 @@ int SS_IMGUpdatemain(ua_dataSS_t * ua_dataSS, int update_type) //SS_FSUpdatePar FILE *fp = NULL; char buf[14] = { 0, }; //to store zImage-delta magic keyword + ssize_t bytes_read; fp = fopen(SS_PATCHFILE_SOURCE, "r"); - fread(buf, 1, 13, fp); //error check not required as any delta corruption will be caught in SS_UpdateDeltaIMG + bytes_read = fread(buf, 1, 13, fp); //error check not required as any delta corruption will be caught in SS_UpdateDeltaIMG + if (bytes_read != 13) + LOGL(LOG_SSENGINE, "short read of \"%s\" (%ld bytes of %ld)\n", SS_PATCHFILE_SOURCE, (long)bytes_read, (long)13); fclose(fp); if (strcmp(buf, SS_KERNEL_MAGIC) == 0) diff --git a/ss_engine/fota_tar.c b/ss_engine/fota_tar.c index b8bda64..8f8226e 100755 --- a/ss_engine/fota_tar.c +++ b/ss_engine/fota_tar.c @@ -25,6 +25,7 @@ #include #include "SS_Engine_Update.h" #include "SS_Engine_Errors.h" +#include "SS_FSUpdate.h" #include "fota_common.h" #include "fota_tar.h" #include "ua.h" @@ -826,6 +827,7 @@ int tar_extract_folder(char *tar, char *item, char *path) char dirPath[512] = { 0 }; int getheader = 1; // Asuming initial header is TAR header char fullname[512] = { 0 }; + int ret; LOG("Extracting Folder from %s %s to %s\n", tar, item, path); data_offset = tar_get_item_offset(tar, item); @@ -888,7 +890,9 @@ int tar_extract_folder(char *tar, char *item, char *path) memset(dirPath, 0, sizeof(dirPath)); sprintf(dirPath, "%s/%s", path, fullname + folderpathlen); LOG(" Creating Symlink [%s][%s]\n", name, dirPath); - symlink(name, dirPath); // use ss_link + ret = symlink(name, dirPath); // use ss_link + if (ret < 0) + LOG("Error with symlink: %d\n", errno); } break; case '3': -- 2.7.4