#!/usr/bin/env python
# _*_ coding: utf-8 _*_
# Copyright (C) 2008 The Android Open Source Project
#
# 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.

#===========================================================================
#							EDIT HISTORY FOR MODULE
#This section contains comments describing changees made to the module.
#Notice that changes are listed in reverse chronological order.
#WHEN		 WHO		WHAT,WHERE,WHY
#----------	-----		----------------------------------------------------
#11/02/2018	Ramos		add file head to record all change history
#11/20/2018	Ramos		delete hardware link files before overwrite update, make sure files is same with the target Version evenif 
#                               hardware link file is changed link between the two version
#03/15/2019 chongyu     add ignore char and block devices feature
#============================================================================*/

"""
Given a target-files zipfile, produces an OTA package that installs
that build.  An incremental OTA is produced if -i is given, otherwise
a full OTA is produced.
Usage:  ota_from_target_files [flags] input_target_files output_ota_package
  -b  (--board_config)  <file>
      Deprecated.
  -k (--package_key) <key> Key to use to sign the package (default is
      the value of default_system_dev_certificate from the input
      target-files's META/misc_info.txt, or
      "build/target/product/security/testkey" if that value is not
      specified).
      For incremental OTAs, the default value is based on the source
      target-file, not the target build.
  -i  (--incremental_from)  <file>
      Generate an incremental OTA using the given target-files zip as
      the starting build.
  -w  (--wipe_user_data)
      Generate an OTA package that will wipe the user data partition
      when installed.
  -n  (--no_prereq)
      Omit the timestamp prereq check normally included at the top of
      the build scripts (used for developer OTA packages which
      legitimately need to go back and forth).
  -e  (--extra_script)  <file>
      Insert the contents of file at the end of the update script.
  -a  (--aslr_mode)  <on|off>
      Specify whether to turn on ASLR for the package (on by default).
  -d  (--device_type) <type>
      Specify mmc or mtd type device. mtd by default
  -f  (--fota) <fota>
      Specify if fota upgrade is used or not. not used by default
  -t  (--typefota) <key>
      FOTA TYPE  a all, m modem etc, l linux etc,o boot etc,mo modem and boot etc,lo linux and boot etc
  -u  (--multi fota)
      Specify if open the multi fota function
"""
import sys
if sys.hexversion < 0x02040000:
  print >> sys.stderr, "Python 2.4 or newer is required."
  sys.exit(1)
print >> sys.stdout ,sys.path[1]
import copy
import errno
import os
import re
import subprocess
import tempfile
import time
import zipfile
from stat import *
try:
  from hashlib import sha1 as sha1
except ImportError:
  from sha import sha as sha1
import common
import edify_generator
import ql_global 
import multi_fota 

#[chongyu] 2019-03-15 : add ignore char and block devices feature
from ubi_reader.modules.ubifs.output import ignore_files

global qlg
#add by harden.xu
qlg = ql_global.QL_GLOBAL()

type_ubi = "ubi"
type_zip = "zip"
typefota_system = ["a", "ml", "lm", "lo", "ol", "l"]
typefota_modem = ["a", "ml", "lm", "mo", "om", "m"]
typefota_boot = ["a", "ol", "lo", "mo", "om", "o"]
df_typefota = {"modem" : typefota_modem, "system" : typefota_system, "boot" : typefota_boot}

quectel_project_version_file = "quectel-project-version"
in_path_info = {"source" : "v1", "target" : "v2", "modem" : "firmware", "boot" : "BOOTABLE_IMAGES"}
out_path_info = {"system" : "SYSTEM", "modem" : "SYSTEM/firmware", "boot" : "BOOTABLE_IMAGES", "recovery" : "RECOVERY", "ota" : "OTA/bin"}
in_file_info = {"system" : ".*sysfs.ubi", "zip" : "targetfiles.zip", "modem" : "NON-HLOS.ubi", "boot" : ".*boot.img", "recovery" : ".*recovery.*.ubi"}
out_file_info = {"boot" : "boot.img", "updater_bin" : "/usr/bin/updater", "applypatch" : "applypatch", "recovery" : "recovery.img", "update" : "update.zip", "recovery_bin" : "/usr/bin/recovery", "project_file" : "/etc/quectel-project-version", "updater_config" : "/quectel-updater-config", "modem_version_file" : "/firmware/image/quectel-modem-version", "anti_rollback_file" : "/quectel-anti-rollback-version"}
df_info = {"in_path" : in_path_info, "out_path" : out_path_info, "in_file" : in_file_info, "out_file" : out_file_info}
pl = 'quectel_project_filelist.txt'

target_zip = source_zip = input_zip = None
matched_boot = matched_modem = matched_system = matched_recovery = None
modem_in_system_flag = False
pkg_info = None
OPTIONS = common.OPTIONS
OPTIONS.package_key = None
OPTIONS.incremental_source = None
OPTIONS.require_verbatim = set()
OPTIONS.prohibit_verbatim = set(("system/build.prop",))
OPTIONS.patch_threshold = 0.95
OPTIONS.wipe_user_data = False
OPTIONS.omit_prereq = False
OPTIONS.extra_script = None
OPTIONS.aslr_mode = False
OPTIONS.worker_threads = 3
#OPTIONS.device_type = 'MTD'
OPTIONS.file_type = type_zip
OPTIONS.fota = 0
OPTIONS.multi_fota = 0
OPTIONS.full_images = False


def MostPopularKey(d, default):
  """Given a dict, return the key corresponding to the largest
  value.  Returns 'default' if the dict is empty."""
  x = [(v, k) for (k, v) in d.iteritems()]
  if not x: return default
  x.sort()
  return x[-1][1]
def IsSymlink(info):
  """Return true if the zipfile.ZipInfo object passed in represents a
  symlink."""
  return oct(info.external_attr >> 16)[0:3] == "012"
def IsRegular(info):
  """Return true if the zipfile.ZipInfo object passed in represents a
  symlink."""
  return oct(info.external_attr >> 28) == "010"
"""
2016/07/08[becking]Add this code to find files from a directory
def FindFile(filename, value):
    try:
        dirlist = os.listdir(filename)
        for x in dirlist:
            tmp = os.path.join(filename,x)
            if os.path.isdir(tmp):      #tmpΪ·
                FindFile(tmp, value)
            elif os.path.isfile(tmp):  #tmpΪ·Ȼжϳ
                if value in x:      #ifҪ·аvaluetmpx
                    print(tmp)
    except Exception as e:
        print(e)
"""
s = []
def ReadInfoFile(name):
  with open(os.path.abspath(name), 'r') as f: 
     s = f.readlines()
     print("Read file [%s] :" % name) 
     for line in s:
       line = line.strip()
       if line.startswith("SYSTEM/"):
         line = line.split('SYSTEM/')
         print "\t",line[1]
  return s
""" 
s = []
def ReadInfoFile(name):  #
 
  with open(os.path.abspath(name), 'r') as f: 
     s = f.read()
     print('open for read...') 
     print(s) 
     for line in s.split("\n"):
       line = line.strip()
       if line.startswith("SYSTEM/"):
         s = line.split('SYSTEM/')
         print "%%%%[becking]",line
         #s.append(line)
  print "!!!becking",os.path.abspath('quectel_fullota_filelist.txt')
  return s
""" 
class Item:
  """Items represent the metadata (user, group, mode) of files and
  directories in the system image."""
  ITEMS = {}
  def __init__(self, name, dir=False):
    self.name = name
    self.uid = None
    self.gid = None
    self.mode = None
    self.dir = dir
    if name:
      self.parent = Item.Get(os.path.dirname(name), dir=True)
      self.parent.children.append(self)
    else:
      self.parent = None
    if dir:
      self.children = []
  def Dump(self, indent=0):
    if self.uid is not None:
      print "%s%s %d %d %o" % ("  "*indent, self.name, self.uid, self.gid, self.mode)
    else:
      print "%s%s %s %s %s" % ("  "*indent, self.name, self.uid, self.gid, self.mode)
    if self.dir:
      print "%s%s" % ("  "*indent, self.descendants)
      print "%s%s" % ("  "*indent, self.best_subtree)
      for i in self.children:
        i.Dump(indent=indent+1)
  @classmethod
  def Get(cls, name, dir=False):
    if name not in cls.ITEMS:
      cls.ITEMS[name] = Item(name, dir=dir)
    return cls.ITEMS[name]
  @classmethod
  def GetMetadata(cls, input_zip):
    try:
      # See if the target_files contains a record of what the uid,
      # gid, and mode is supposed to be.
      output = input_zip.read("META/filesystem_config.txt")
    except KeyError:
      # Run the external 'fs_config' program to determine the desired
      # uid, gid, and mode for every Item object.  Note this uses the
      # one in the client now, which might not be the same as the one
      # used when this target_files was built.
      p = common.Run(["fs_config"], stdin=subprocess.PIPE,
                     stdout=subprocess.PIPE, stderr=subprocess.PIPE)
      suffix = { False: "", True: "/" }
      input = "".join(["%s%s\n" % (i.name, suffix[i.dir])
                       for i in cls.ITEMS.itervalues() if i.name])
      output, error = p.communicate(input)
      assert not error
    for line in output.split("\n"):
      if not line: continue
      name, uid, gid, mode = line.split()
      i = cls.ITEMS.get(name, None)
      if i is not None:
        i.uid = int(uid)
        i.gid = int(gid)
        i.mode = int(mode, 8)
        if i.dir:
          i.children.sort(key=lambda i: i.name)
    # set metadata for the files generated by this script.
    i = cls.ITEMS.get("system/recovery-from-boot.p", None)
    if i: i.uid, i.gid, i.mode = 0, 0, 0644
    i = cls.ITEMS.get("system/etc/install-recovery.sh", None)
    if i: i.uid, i.gid, i.mode = 0, 0, 0544
  def CountChildMetadata(self):
    """Count up the (uid, gid, mode) tuples for all children and
    determine the best strategy for using set_perm_recursive and
    set_perm to correctly chown/chmod all the files to their desired
    values.  Recursively calls itself for all descendants.
    Returns a dict of {(uid, gid, dmode, fmode): count} counting up
    all descendants of this node.  (dmode or fmode may be None.)  Also
    sets the best_subtree of each directory Item to the (uid, gid,
    dmode, fmode) tuple that will match the most descendants of that
    Item.
    """
    assert self.dir
    d = self.descendants = {(self.uid, self.gid, self.mode, None): 1}
    for i in self.children:
      if i.dir:
        for k, v in i.CountChildMetadata().iteritems():
          d[k] = d.get(k, 0) + v
      else:
        k = (i.uid, i.gid, None, i.mode)
        d[k] = d.get(k, 0) + 1
    # Find the (uid, gid, dmode, fmode) tuple that matches the most
    # descendants.
    # First, find the (uid, gid) pair that matches the most
    # descendants.
    ug = {}
    for (uid, gid, _, _), count in d.iteritems():
      ug[(uid, gid)] = ug.get((uid, gid), 0) + count
    ug = MostPopularKey(ug, (0, 0))
    # Now find the dmode and fmode that match the most descendants
    # with that (uid, gid), and choose those.
    best_dmode = (0, 0755)
    best_fmode = (0, 0644)
    for k, count in d.iteritems():
      if k[:2] != ug: continue
      if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
      if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
    self.best_subtree = ug + (best_dmode[1], best_fmode[1])
    return d
  def SetPermissions(self, script):
    """Append set_perm/set_perm_recursive commands to 'script' to
    set all permissions, users, and groups for the tree of files
    rooted at 'self'."""
    self.CountChildMetadata()
    def recurse(item, current):
      # current is the (uid, gid, dmode, fmode) tuple that the current
      # item (and all its children) have already been set to.  We only
      # need to issue set_perm/set_perm_recursive commands if we're
      # supposed to be something different.
      # During recovery, usrdata partition is mounted at /data. For setting
      # permissions for files in usrdata we need to make sure the path starts
      # with /data
      if item.name.startswith('userdata'):
          path_on_device = item.name[4:]
      else:
          path_on_device = item.name
      if item.dir:
        if current != item.best_subtree:
          script.SetPermissionsRecursive("/"+path_on_device, *item.best_subtree)
          current = item.best_subtree
        if item.uid != current[0] or item.gid != current[1] or \
           item.mode != current[2]:
          script.SetPermissions("/"+path_on_device, item.uid, item.gid, item.mode)
        for i in item.children:
          recurse(i, current)
      else:
        if item.uid != current[0] or item.gid != current[1] or \
               item.mode != current[3]:
          script.SetPermissions("/"+path_on_device, item.uid, item.gid, item.mode)
    recurse(self, (-1, -1, -1, -1))
def CopySystemFiles(input_zip, output_zip=None, input_data = None,
                    substitute=None):
  """Copies files underneath system/ in the input zip to the output
  zip.  Populates the Item class with their metadata, and returns a
  list of symlinks as well as a list of files that will be retouched.
  output_zip may be None, in which case the copy is skipped (but the
  other side effects still happen).  substitute is an optional dict
  of {output filename: contents} to be output instead of certain input
  files.
  """
  symlinks = []
  retouch_files = []
  if OPTIONS.file_type == type_zip:
    for info in input_zip.infolist():
      if info.filename.startswith("SYSTEM/"):
        basefilename = info.filename[7:]
        #print "[becking]CopySystemFiles basefilename", basefilename
        if IsSymlink(info):
          #Ramos.zhang-20190628 add if just update modem(no system),just append /SYSTEM/firmware files
          if (OPTIONS.typefota =='m' or OPTIONS.typefota =='mo'):
            if basefilename[0:8] == "firmware":
              symlinks.append((input_zip.read(info.filename),
                           "/system/" + basefilename))            
          else:
            symlinks.append((input_zip.read(info.filename),
                           "/system/" + basefilename))
          #print "[becking]CopySystemFiles basefilename", basefilename
        else:
          #print "[becking]CopySystemFiles basefilename", basefilename
          info2 = copy.copy(info)
          fn = info2.filename = "system/" + basefilename
          if substitute and fn in substitute and substitute[fn] is None:
            continue
          if output_zip is not None:
            if substitute and fn in substitute:
              data = substitute[fn]
            else:
              data = input_zip.read(info.filename)
            if info.filename.startswith("SYSTEM/lib/") and IsRegular(info):
              retouch_files.append(("/system/" + basefilename,
                                    common.sha1(data).hexdigest()))
            if fn.endswith("/"):
              #zip does not play nice with empty folders. Create dummy file to make sure folder is saved in archive
              info_dummy = copy.copy(info2)
              info_dummy.filename = info_dummy.filename + "__emptyfile__"
              output_zip.writestr(info_dummy,data)
            output_zip.writestr(info2, data)
          if fn.endswith("/"):
            Item.Get(fn[:-1], dir=True)
          else:
            Item.Get(fn, dir=False)
  else:
    #ubi type
    for k, v in input_data.iteritems():
      if k.startswith("system/") and oct(v.mode)[0:3] == "012":
        symlinks.append((v.data, "/" + k))
  symlinks.sort()
  return (symlinks, retouch_files)
def CopyUserdataFiles(input_zip, output_zip=None,
                    substitute=None):
  """Copies files underneath DATA/ in the input zip to the output
  zip.  Populates the Item class with their metadata, and returns a
  list of symlinks as well as a list of files that will be retouched.
  output_zip may be None, in which case the copy is skipped (but the
  other side effects still happen).  substitute is an optional dict
  of {output filename: contents} to be output instead of certain input
  files.
  """
  symlinks = []
  retouch_files = []
  for info in input_zip.infolist():
    if info.filename.startswith("DATA/"):
      basefilename = info.filename[5:]
      if IsSymlink(info):
        symlinks.append((input_zip.read(info.filename),
                         "/data/" + basefilename))
      else:
        info2 = copy.copy(info)
        fn = info2.filename = "userdata/" + basefilename
        if substitute and fn in substitute and substitute[fn] is None:
          continue
        if output_zip is not None:
          if substitute and fn in substitute:
            data = substitute[fn]
          else:
            data = input_zip.read(info.filename)
          if info.filename.startswith("DATA/lib/") and IsRegular(info):
            retouch_files.append(("/data/" + basefilename,
                                  common.sha1(data).hexdigest()))
          if fn.endswith("/"):
            #zip does not play nice with empty folders. Create dummy file to make sure folder is saved in archive
            info_dummy = copy.copy(info2)
            info_dummy.filename = info_dummy.filename + "__emptyfile__"
            output_zip.writestr(info_dummy,data)
          output_zip.writestr(info2, data)
        if fn.endswith("/"):
          Item.Get(fn[:-1], dir=True)
        else:
          Item.Get(fn, dir=False)
  symlinks.sort()
  return (symlinks, retouch_files)
def SignOutput(temp_zip_name, output_zip_name):
  key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
  pw = key_passwords[OPTIONS.package_key]
  common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
                  whole_file=True)
def AppendAssertions(script, input_zip):
  print "Skip assertions"
def MakeRecoveryPatch(output_zip, recovery_img, boot_img):
  """Generate a binary patch that creates the recovery image starting
  with the boot image.  (Most of the space in these images is just the
  kernel, which is identical for the two, so the resulting patch
  should be efficient.)  Add it to the output zip, along with a shell
  script that is run from init.rc on first boot to actually do the
  patching and install the new recovery image.
  recovery_img and boot_img should be File objects for the
  corresponding images.  info should be the dictionary returned by
  common.LoadInfoDict() on the input target_files.
  Returns an Item for the shell script, which must be made
  executable.
  """
  d = common.Difference(recovery_img, boot_img)
  _, _, patch = d.ComputePatch()
  common.ZipWriteStr(output_zip, "recovery/recovery-from-boot.p", patch)
  Item.Get("system/recovery-from-boot.p", dir=False)
  boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
  recovery_type, recovery_device = common.GetTypeAndDevice("/recovery", OPTIONS.info_dict)
  sh = """#!/system/bin/sh
if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
  log -t recovery "Installing new recovery image"
  applypatch %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s %(recovery_type)s:%(recovery_device)s %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p
else
  log -t recovery "Recovery image already installed"
fi
""" % { 'boot_size': boot_img.size,
        'boot_sha1': boot_img.sha1,
        'recovery_size': recovery_img.size,
        'recovery_sha1': recovery_img.sha1,
        'boot_type': boot_type,
        'boot_device': boot_device,
        'recovery_type': recovery_type,
        'recovery_device': recovery_device,
        }
  common.ZipWriteStr(output_zip, "recovery/etc/install-recovery.sh", sh)
  return Item.Get("system/etc/install-recovery.sh", dir=False)
def WriteFullOTAPackage(input_zip, output_zip, fota):
  # TODO: how to determine this?  We don't know what version it will
  # be installed on top of.  For now, we expect the API just won't
  # change very often.
  print "[becking]WriteFullOTAPackage..."
  script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
  script.Print("".join(os.popen("./update_gen.sh -v").readlines()))
  
  metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),
              "pre-device": GetBuildProp("ro.product.device", input_zip),
              "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip),
              }
  device_specific = common.DeviceSpecificParams(
      input_zip=input_zip,
      input_version=OPTIONS.info_dict["recovery_api_version"],
      output_zip=output_zip,
      script=script,
      input_tmp=OPTIONS.input_tmp,
      metadata=metadata,
      info_dict=OPTIONS.info_dict,
      type=OPTIONS.device_type,
      file_type=OPTIONS.file_type,
      fota=OPTIONS.fota)
  device_specific.FullOTA_Assertions()
  script.ShowProgress(0.5, 0)
  if OPTIONS.wipe_user_data:
    script.FormatPartition("/data")
  script.FormatPartition("/system")
  script.Mount("/system")
  #dont want to format userdata partition
  #script.FormatPartition("/data")
  script.Mount("/data")
  #script.UnpackPackageDir("recovery", "/system")
  script.UnpackPackageDir("system", "/system")
  script.UnpackPackageDir("userdata", "/data")
  (symlinks, retouch_files) = CopySystemFiles(input_zip, output_zip)
  script.MakeSymlinks(symlinks)
  if OPTIONS.aslr_mode:
    script.RetouchBinaries(retouch_files)
  else:
    pass
    #script.UndoRetouchBinaries(retouch_files)
  
  (userdata_symlinks, userdata_retouch_files) = CopyUserdataFiles(input_zip, output_zip)
  script.RetouchBinaries(userdata_retouch_files)
  script.MakeSymlinks(userdata_symlinks)
  
  boot_img = common.GetBootableImage("boot.img", "boot.img",
                                     OPTIONS.input_tmp, "BOOT")
  recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
                                         OPTIONS.input_tmp, "RECOVERY")
  #Item.GetMetadata(input_zip)
  #Item.Get("system").SetPermissions(script)
  #Item.Get("userdata").SetPermissions(script)
  
  common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
  common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
  
  #Write dsp diff/mbn images to zip file
  dsp_image_names = ["dsp1.diff", "dsp2.diff", "dsp3.diff", "dsp1.mbn", "dsp2.mbn", "dsp3.mbn"]
  for dsp_image_name in dsp_image_names:
    dsp_image_path = os.path.join(OPTIONS.input_tmp, dsp_image_name)
    if os.path.exists(dsp_image_path):
      print dsp_image_name + " image found. Writing to zip"
      dsp_file = common.File.FromLocalFile(dsp_image_name, dsp_image_path)
      common.ZipWriteStr(output_zip, dsp_image_name, dsp_file.data)
  #if os.path.exists
  script.ShowProgress(0.2, 0)
  script.ShowProgress(0.2, 10)
  script.WriteRawImage("/boot", "boot.img")
  script.ShowProgress(0.1, 0)
  device_specific.FullOTA_InstallEnd()
  if OPTIONS.extra_script is not None:
    script.AppendExtra(OPTIONS.extra_script)
  script.UnmountAll()
  global pkg_info
  script.AddToZip(input_zip, output_zip, fota, pkg_info)
  WriteMetadata(metadata, output_zip)
def WriteMetadata(metadata, output_zip):
  common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
                     "".join(["%s=%s\n" % kv
                              for kv in sorted(metadata.iteritems())]))
def LoadSystemFiles(z, tmp, typ):
  """Load all the files from SYSTEM/... in a given target-files
  ZipFile, and return a dict of {filename: File object}."""
  out = {}
  retouch_files = []
  if typ == type_zip and z:
    for info in z.infolist():
      if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
        basefilename = info.filename[7:]
        fn = "system/" + basefilename
        #Ramos.zhang-20190628 add if just update modem(no system),just append /SYSTEM/firmware files
        if (OPTIONS.typefota =='m' or OPTIONS.typefota =='mo'):
          if basefilename[0:8] == "firmware":
            #print "modem file system files=", fn
            data = z.read(info.filename)
            out[fn] = common.File(fn, data, tmp)
        else:
          data = z.read(info.filename)
          out[fn] = common.File(fn, data, tmp)
        if (OPTIONS.typefota !='m' and OPTIONS.typefota !='mo'):
          if info.filename.startswith("SYSTEM/lib/") and IsRegular(info):
            retouch_files.append(("/system/" + basefilename,
			                      out[fn].sha1))
  elif typ == type_ubi:
    for k, v in common.get_str_info(df_info["out_path"]["system"].lower(), tmp).iteritems():
      out["system" + k[len(tmp) + 7:]] = v
  else:
    print "Error: Please check the input files"
    sys.exit(1)
  return (out, retouch_files)
def GetBuildProp(property, z):
  #We dont need the properties from here. Returning dummy value
  return "None"
  """Return the fingerprint of the build of a given target-files
  ZipFile object."""
  bp = z.read("SYSTEM/build.prop")
  if not property:
    return bp
  m = re.search(re.escape(property) + r"=(.*)\n", bp)
  if not m:
    raise common.ExternalError("couldn't find %s in build.prop" % (property,))
  return m.group(1).strip()


def GetValueFromMultiLines(data, key, delimiter=':'):
  lines = data.splitlines()
  for line in lines:
    #print line
    if line.find(key) != -1:
      if delimiter in line:
        return line.split(delimiter, 1)[1].strip()

  return None


def GetPackageTime(data):
  time_str = GetValueFromMultiLines(data, 'Package Time')
  if time_str is not None:
      print "package time:%s" %(time_str)
      return int(time.mktime(time.strptime(time_str, '%Y-%m-%d,%H:%M')))

  return 0

#start add by franklin.zhang -2023/03/30, Find out the platform information in the quectel-project-version file
def GetPackageBranch(zip1):
  project_file_data = zip1.read(df_info["out_path"]["system"] + df_info["out_file"]["project_file"])
  branch_str = GetValueFromMultiLines(project_file_data, 'Branch  Name')
  return branch_str
  
#end add by franklin.zhang -2023/03/30, Find out the platform information in the quectel-project-version file

def GetNewerVersionZip(zip1, zip2):
  try:
    zip1_data = zip1.read(df_info["out_path"]["system"] + df_info["out_file"]["project_file"])
    #print 'zip1_data: \n%s' % (zip1_data)
    zip1_time = GetPackageTime(zip1_data)
  except Exception, e:
    print 'exception: %s' % (str(e))
    zip1_time = 0

  try:
    zip2_data = zip2.read(df_info["out_path"]["system"] + df_info["out_file"]["project_file"])
    #print 'zip2_data: \n%s' % (zip2_data)
    zip2_time = GetPackageTime(zip2_data)
  except Exception, e:
    print 'exception: %s' % (str(e))
    zip2_time = 0

  if zip1_time < zip2_time:
    return zip2
  else:
    return zip1


def GetUpdaterVersion(package_zip):
  updater_ver = 0

  try:
    zip_data = package_zip.read(df_info["out_path"]["recovery"] + df_info["out_file"]["updater_config"])
    ver_str = GetValueFromMultiLines(zip_data, 'updater_version')
    if ver_str is not None:
      if ver_str.isdigit():
        updater_ver = int(ver_str)
      else:
        print 'the value updater_version is not a digit, set to default 0.'

  except Exception, e:
    print 'exception: %s' % (str(e))
    updater_ver = 0

  print 'updater_ver: %d' % (updater_ver)
  return updater_ver


def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip, fota):
  if OPTIONS.file_type == type_zip:
    source_version = OPTIONS.source_info_dict["recovery_api_version"]
    target_version = OPTIONS.target_info_dict["recovery_api_version"]
  else:
    source_version = target_version = 3
    OPTIONS.target_info_dict = None
    OPTIONS.source_info_dict = None

  newer_zip = GetNewerVersionZip(target_zip, source_zip);
  
  ffns = ReadInfoFile('quectel_fullota_filelist.txt')
  nfns = ReadInfoFile('quectel_noota_filelist.txt')
  def check_ufile(name):
    for ffn in ffns:
      if name == ffn.strip():
        return 1
    for nfn in nfns:
      if name == nfn.strip():
        return 2

    #add by harden.xu
    if qlg.is_diff_seg() == True and qlg.is_clade_temp_file(name):
        return 2

    #add for clade2 compress.
    if qlg.is_diff_seg_clade2() == True and qlg.is_clade2_temp_file(name):
        return 2

    return 0
  #print "$$$$$$$$$$becking",ffns,nfns
  """
  for ffn in ffns:
    ffn = ffn.strip()
    #ffn = ffn.split('SYSTEM/')
    print '&&&&!![becking]',ffn.replace("SYSTEM/","system/",7)
  """
  if OPTIONS.file_type == type_zip and source_version == 0:
    print ("WARNING: generating edify script for a source that "
           "can't install it.")
  script = edify_generator.EdifyGenerator(source_version, OPTIONS.target_info_dict)
  script.Print("".join(os.popen("./update_gen.sh -v").readlines()))

  metadata = {"pre-device": GetBuildProp("ro.product.device", source_zip),
                "post-timestamp": GetBuildProp("ro.build.date.utc", target_zip),
                }
  device_specific = common.DeviceSpecificParams(
        source_zip=source_zip,
        source_version=source_version,
        target_zip=target_zip,
        target_version=target_version,
        output_zip=output_zip,
        script=script,
        metadata=metadata,
        info_dict=OPTIONS.info_dict)
  print "Loading target..."
  (target_data, target_retouch_files) = LoadSystemFiles(target_zip, OPTIONS.target_tmp, OPTIONS.file_type)
  print "Loading source..."
  (source_data, source_retouch_files) = LoadSystemFiles(source_zip, OPTIONS.source_tmp, OPTIONS.file_type)
  verbatim_targets = []
  patch_list = []
  diffs = []
  tmp_perm = {}
  largest_source_size = 0
  updating_recovery = 0
  updating_boot = 0
  pflag = True
  global modem_in_system_flag
  

  print "##############Add quectel_project_version file to update.zip package###########"
  #Ramos.zhang-20190628 update modem Only,have no project files, so can't add to update.zip package 
  if (OPTIONS.typefota !='m' and OPTIONS.typefota !='mo'):
    common.ZipWriteStr(output_zip, quectel_project_version_file, source_data.get("system" + df_info["out_file"]["project_file"]).data)

  # /* +/ zac.chen-2023/01/06 JIRA BZSDX24-1264: DX24 multi partition development, adding quertel mode version file */
  if OPTIONS.typefota in typefota_modem:
    file_path = "system" + df_info["out_file"]["modem_version_file"]
    file_name = os.path.basename(file_path)
    print "file_name: %s"%(file_name)
    try:
      common.ZipWriteStr(output_zip, file_name, source_data.get(file_path).data)
      print "source_ Data find the file: %s"%(file_path)
    except Exception as e:
      print "skip: Not source_ Data does not find the file: %s!!!"%(file_path)
  # /* -/ zac.chen-2023/01/06 JIRA BZSDX24-1264: DX24 multi partition development, adding quertel mode version file */

  if 1:
    file_path = "system" + df_info["out_file"]["anti_rollback_file"]
    file_name = os.path.basename(file_path)
    print "file_name: %s"%(file_name)
    try:
      common.ZipWriteStr(output_zip, file_name, target_data.get(file_path).data)
      print "source_ Data find the file: %s"%(file_path)
    except Exception as e:
      print "skip: Not target_data does not find the file: %s!!!"%(file_path)


  def creat_perm(var):
    tmp_perm["/" + var.name] = int(oct(var.mode)[-4:], 8)

  if OPTIONS.typefota != 'o':
    for tk, tv in sorted(target_data.iteritems()):
      if OPTIONS.file_type == type_ubi and OPTIONS.typefota in df_typefota["system"]:# and tv.name.startswith(df_info["out_path"]["system"].lower()):
          if tv.name.startswith("system/firmware/image/mba.mbn"):
            modem_in_system_flag = True
            break
          else:
            continue

  if OPTIONS.typefota != 'o':
    max_size = 0
    for tk, tv in sorted(target_data.iteritems()):
      if oct(tv.mode)[0:3] == "012": #ignore link file
        continue
      if OPTIONS.typefota in df_typefota["system"]:# and tv.name.startswith(df_info["out_path"]["system"].lower()):
        if not pflag:
          tpn = target_data.get("system" + df_info["out_file"]["project_file"]).data.split("\n")
          spn = source_data.get("system" + df_info["out_file"]["project_file"]).data.split("\n")
          if tpn[0] == spn[0]:
            for ln in file(pl,"r"):
              if tpn[0].split(": ")[1] == ln.strip("\n").strip():
                pflag = True
                break
          else:
            print "Error: Not Matched Project!!!"
            sys.exit(1)
        if OPTIONS.typefota not in df_typefota["modem"] and not modem_in_system_flag and tv.name.startswith(df_info["out_path"]["modem"].lower()):
          continue

      elif OPTIONS.typefota in df_typefota["modem"] and not modem_in_system_flag:
        if tv.name.startswith(df_info["out_path"]["modem"].lower()):
          pass
        else:
          continue
      else:
        print "Not Care"
        continue
      if not pflag:
        print ("Error: Undefined Project!!!!")
        sys.exit(1)

      assert tk == tv.name
      ln = len(tk.split("/")[0])
      rtn = check_ufile(tk.split("/")[0].upper() + tk[ln:])
      sv = source_data.get(tk)
      if sv is not None and sv.size > max_size:
        if "modem.b_h" in tv.name or "modem.b_d" in tv.name or "modem.b_d" in tv.name or "clade2uncomp.bin" in tv.name :
            pass
        else:
            max_size = sv.size

      if rtn == 0 :
        #[chongyu] 2019-03-15 :  add ignore char and block devices feature
        if ignore_files.get(tv.name.lower()) is 'ignore':
          print 'ignore file [%s]' % tv.name
        else:
          if sv is None or sv.data is None and tv.data is not None or sv.data is not None and tv.data is None:
            creat_perm(tv)
            verbatim_targets.append((tv.name, tv.size))
            tv.AddToZip(output_zip)
          elif sv.sha1 and tv.sha1 and sv.sha1 != tv.sha1:
            diffs.append(common.Difference(tv, sv))
            creat_perm(tv)
          else:
            pass
            #print "no need upgrade file [ %s ]" % tv.name.encode('utf-8')
      elif rtn == 1:
        creat_perm(tv)
        #if OPTIONS.file_type == type_zip:
        verbatim_targets.append((tv.name, tv.size))
        tv.AddToZip(output_zip)
      elif rtn == 2:
        continue
      else:
        print "Not Care"
        continue
    common.ComputeDifferences(diffs)
    common.ZipWriteStr(output_zip, "max_file", str(max_size))

    for diff in diffs:
      tf, sf, d = diff.GetPatch()
      if d is None or len(d) > tf.size * OPTIONS.patch_threshold:
        # patch is almost as big as the file; don't bother patching
        # add by harden.xu
        if qlg.is_diff_seg() == False and tf.name == "system/firmware/image/modem.%s"%(qlg.src_ext['clade_ext']) \
           or qlg.is_diff_seg_clade2() == False and tf.name == "system/firmware/image/modem.%s"%(qlg.src_ext['clade2_ext']):
          patch_list.append((tf.name, tf, sf, tf.size, hex(0)))
          largest_source_size = max(largest_source_size, sf.size)
          continue
        tf.AddToZip(output_zip)
        verbatim_targets.append((tf.name, tf.size))
        creat_perm(tf)
      else:
        common.ZipWriteStr(output_zip, "patch/" + tf.name + ".p", d)
        patch_list.append((tf.name, tf, sf, tf.size, common.sha1(d).hexdigest()))
        largest_source_size = max(largest_source_size, sf.size)
    if OPTIONS.file_type == type_zip:
      source_fp = GetBuildProp("ro.build.fingerprint", source_zip)
      target_fp = GetBuildProp("ro.build.fingerprint", target_zip)
      metadata["pre-build"] = source_fp
      metadata["post-build"] = target_fp
    script.Mount("/system")
#Quectel 20160415 mark the next line, fix the dfota upgrade fail. 
#  script.AssertSomeFingerprint(source_fp, target_fp)
  if nfns:
    #print "***********nfns is not none ********"
    for nfn in nfns:
      nfn = nfn.strip()
      #print '>>>>Resolve not upgrade files>>>>[becking]',nfn.replace("SYSTEM/","system/",7)
      if nfn == 'BOOTABLE_IMAGES/boot.img' or nfn == 'BOOTABLE_IMAGES/recovery.img':
        #print ">>>>@Check the boot_img or recovery.img@>>>>[becking]",nfn
        break
  else:
    print "@@@@@@nfns is none@@@@@@@"
    nfns = None
  if ffns:
    #print "*******ffns is not none************"
    for ffn in ffns:
      ffn = ffn.strip()
      #print '>>>>Covering upgrade file parsing>>>>[becking]',ffn.replace("SYSTEM/","system/",7)
      if ffn == 'BOOTABLE_IMAGES/boot.img' or ffn == 'BOOTABLE_IMAGES/recovery.img':
        #print ">>>>Check the target file name>>>>[becking]",tf.name
        break
  else:
    ffns = None
  if nfns is not None and nfn == 'BOOTABLE_IMAGES/boot.img':
    updating_boot = 0
    updating_recovery = 0
    print "*********nfns>>>>boot_img or recoverynot update*****************"
  elif ffns is not None and ffn ==  'BOOTABLE_IMAGES/boot.img':
    updating_boot = 0
    updating_recovery = 0
    print "*********ffns>>>>boot_img or recoverynot update*****************"
  elif OPTIONS.typefota in typefota_boot:
    if OPTIONS.file_type == type_zip:
      common.GetBootableImage(None, "boot.img", OPTIONS.source_tmp, df_info["out_path"]["boot"])
      source_boot = common.get_str_info(df_info["out_path"]["boot"], OPTIONS.source_tmp)[OPTIONS.source_tmp + "/" + df_info["out_path"]["boot"] + "/" + df_info["out_file"]["boot"]]

      common.GetBootableImage(None, "boot.img", OPTIONS.target_tmp, df_info["out_path"]["boot"])
      target_boot = common.get_str_info(df_info["out_path"]["boot"], OPTIONS.target_tmp)[OPTIONS.target_tmp + "/" + df_info["out_path"]["boot"] + "/" + df_info["out_file"]["boot"]]

      updating_boot = (source_boot.sha1 != target_boot.sha1)

      common.GetBootableImage(None, "recovery.img", OPTIONS.source_tmp, df_info["out_path"]["boot"])
      source_recovery = common.get_str_info(df_info["out_path"]["boot"], OPTIONS.source_tmp)[OPTIONS.source_tmp + "/" + df_info["out_path"]["boot"] + "/" + df_info["out_file"]["recovery"]]

      common.GetBootableImage(None, "recovery.img", OPTIONS.target_tmp, df_info["out_path"]["boot"])
      target_recovery = common.get_str_info(df_info["out_path"]["boot"], OPTIONS.target_tmp)[OPTIONS.target_tmp + "/" + df_info["out_path"]["boot"] + "/" + df_info["out_file"]["recovery"]]

      updating_recovery = (source_recovery.sha1 != target_recovery.sha1)
    elif matched_boot and OPTIONS.file_type == type_ubi:
      common.GetBootableImage(None, matched_boot.string, df_info["in_path"]["source"], None)
      source_boot = common.get_str_info(matched_boot.string, df_info["in_path"]["source"])[df_info["in_path"]["source"] + "/" + matched_boot.string]
      
      common.GetBootableImage(None, matched_boot.string, df_info["in_path"]["target"], None)
      target_boot = common.get_str_info(matched_boot.string, df_info["in_path"]["target"])[df_info["in_path"]["target"] + "/" + matched_boot.string]
      updating_boot = (source_boot.sha1 != target_boot.sha1)
           
  # Here's how we divide up the progress bar:
  #  0.1 for verifying the start state (PatchCheck calls)
  #  0.8 for applying patches (ApplyPatch calls)
  #  0.1 for unpacking verbatim files, symlinking, and doing the
  #      device-specific commands.
           
  #AppendAssertions(script, target_zip)
  device_specific.IncrementalOTA_Assertions()
  
  script.USB_Remount()

  if qlg.is_diff_seg() == False:
      script.Print("Check clade decompress modem seg...")
      script.CheckCladeCompress(qlg.src_ext,qlg.dst_ext)

  if qlg.is_diff_seg_clade2() == False:
      script.Print("Check clade2 decompress modem seg...")
      script.CheckClade2Compress(qlg.src_ext,qlg.dst_ext)

  script.Print("Verifying current system...")
           
  script.ShowProgress(0.1, 0)
  total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
  if updating_boot:
    total_verify_size += source_boot.size
  so_far = 0
           
  for fn, tf, sf, size, patch_sha in patch_list:
    script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
    so_far += sf.size
    script.SetProgress(so_far / total_verify_size)
           
  if updating_boot:
    d = common.Difference(target_boot, source_boot)
    _, _, d = d.ComputePatch()
    print "boot target: %d\nboot source: %d\ndiff num: %d" % (
        target_boot.size, source_boot.size, len(d))
           
    common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
           
    if OPTIONS.file_type == type_zip:
      boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
    else:
      boot_type = OPTIONS.device_type
      boot_device = "boot"
           
    script.PatchCheck("%s:%s:%d:%s:%d:%s" %
                      (boot_type, boot_device,
                       source_boot.size, source_boot.sha1,
                       target_boot.size, target_boot.sha1))
    so_far += source_boot.size
 
  """ check multi fota bin """
  script.SetProgress(0.99)
  if int(OPTIONS.multi_fota) == 1:
    with open("/tmp/MULTIFOTA/multifota.bin",'r') as mf:
      data = mf.read()
      script.PatchCheck("/tmp/multifota.bin", common.sha1(data).hexdigest())
    mf.close()

  """ Calculate upgrade percentage """
  script.SetProgress(so_far / total_verify_size)

 # if patch_list or updating_recovery or updating_boot:
 #   script.CacheFreeSpaceCheck(largest_source_size)
           
  device_specific.IncrementalOTA_VerifyEnd()
           
  script.Comment("---- start making changes here ----")
           
  if OPTIONS.wipe_user_data:
    script.Print("Erasing user data...")
    script.FormatPartition("/data")
           
  script.Print("Removing unneeded files...")
  script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
                     ["/"+i for i in sorted(source_data)
                            if i not in target_data])
  
  #Ramos.zhang-20190628 update modem Only, don't delete this files  
  if (OPTIONS.typefota =='m' or OPTIONS.typefota =='mo'):
    script.DeleteFiles(["/system/firmware/image/qdsp6m.qdb"] +
                       ["/system/firmware/image/sdx55/qdsp6m.qdb"] +
                       ["/system/firmware/image/olympic/qdsp6m.qdb"])
  else:
    if (OPTIONS.typefota =='a'):
      script.DeleteFiles(["/system/firmware/image/qdsp6m.qdb"] +
                         ["/system/firmware/image/sdx55/qdsp6m.qdb"] +
                         ["/system/firmware/image/olympic/qdsp6m.qdb"])
    script.DeleteFiles(["/system/recovery.img"] +
                     ["/system/sbin/powerapp"] +
                     ["/system/sbin/sys_reboot"] +
                     ["/system/sbin/sys_shutdown"] +
                     ["/system/usr/bin/gdb"] +
                     ["/system/usr/sbin/tcpdump"])
           
  script.ShowProgress(0.8, 0)
  total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
  if updating_boot:
    total_patch_size += target_boot.size
  so_far = 0
           
  script.Print("Patching system files...")
  deferred_patch_list = []
  for item in patch_list:
    fn, tf, sf, size, _ = item
    if tf.name == "system/build.prop":
      deferred_patch_list.append(item)
      continue

    temp_tf = tf.name[:6].upper() + tf.name[6:]  # transfer system to SYSTEM

    if qlg.is_diff_seg() == False:
      # 把clade临时文件的升级移至压缩前
      if qlg.is_clade_temp_file(temp_tf):
        continue
      # 把tcm_ext文件的升级移至压缩前
      if os.path.basename(temp_tf) == 'modem.' + qlg.dst_ext['tcm_ext']:
        continue

    if qlg.is_diff_seg_clade2() == False:
      # 把clade2临时文件的升级移至压缩前
      if qlg.is_clade2_temp_file(temp_tf):
        continue

    script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
    so_far += tf.size
    script.SetProgress(so_far / total_patch_size)

    if qlg.is_diff_seg() == False:
      #把clade临时文件和tcm_ext的升级移至压缩前
      def applypatch_clade_temp_file(patch_list, so_far, total_patch_size):
        for item in patch_list:
          fn, tf, sf, size, _ = item
          temp_tf = tf.name[:6].upper() + tf.name[6:]  #transfer system to SYSTEM
          if qlg.is_clade_temp_file(temp_tf) or os.path.basename(temp_tf) == 'modem.' + qlg.dst_ext['tcm_ext']:
            script.ApplyPatch("/" + fn, "-", tf.size, tf.sha1, sf.sha1, "patch/" + fn + ".p")
            so_far += tf.size
            script.SetProgress(so_far / total_patch_size)
        return so_far

      if tf.name == "system/firmware/image/modem.%s" % (qlg.src_ext['clade_ext']):
        tcm_ext_fullota_path = "system/firmware/image/modem." + qlg.dst_ext['tcm_ext']
        if tcm_ext_fullota_path in output_zip.namelist():
          print "tcm_ext upgrade by fullota, not patch"
          script.UnpackPackageFile(tcm_ext_fullota_path, "/" + tcm_ext_fullota_path)
        so_far = applypatch_clade_temp_file(patch_list, so_far, total_patch_size)
        script.UnpackPackageFile("system/firmware/image/clade_meta.bin", "/system/firmware/image/clade_meta.bin")
        script.Print("Clade Compress modem seg...")
        script.CladeCompress()

    if qlg.is_diff_seg_clade2() == False:
      # 把clade2临时文件的升级移至压缩前
      def applypatch_clade2_temp_file(patch_list, so_far, total_patch_size):
        for item in patch_list:
          fn, tf, sf, size, _ = item
          temp_tf = tf.name[:6].upper() + tf.name[6:]  #transfer system to SYSTEM
          if qlg.is_clade2_temp_file(temp_tf):
            script.ApplyPatch("/" + fn, "-", tf.size, tf.sha1, sf.sha1, "patch/" + fn + ".p")
            so_far += tf.size
            script.SetProgress(so_far / total_patch_size)
        return so_far

      if tf.name == "system/firmware/image/modem.%s" % (qlg.src_ext['clade2_ext']):
        so_far = applypatch_clade2_temp_file(patch_list, so_far, total_patch_size)
        script.UnpackPackageFile("system/firmware/image/clade_meta.bin", "/system/firmware/image/clade_meta.bin")
        script.Print("Clade2 Compress modem seg...")
        script.Clade2Compress()

  if updating_boot:
    # Produce the boot image by applying a patch to the current
    # contents of the boot partition, and write it back to the
    # partition.
    script.Print("Patching boot image...")
    script.ApplyPatch("%s:%s:%d:%s:%d:%s"
                      % (boot_type, boot_device,
                         source_boot.size, source_boot.sha1,
                         target_boot.size, target_boot.sha1),
                      "-",
                      target_boot.size, target_boot.sha1,
                      source_boot.sha1, "patch/boot.img.p")
    so_far += target_boot.size
    script.SetProgress(so_far / total_patch_size)
    print "boot image changed; including."
  else:    
    print "boot image unchanged; skipping."
           
  if updating_recovery:
    # Is it better to generate recovery as a patch from the current
    # boot image, or from the previous recovery image?  For large
    # updates with significant kernel changes, probably the former.
    # For small updates where the kernel hasn't changed, almost
    # certainly the latter.  We pick the first option.  Future
    # complicated schemes may let us effectively use both.
    #      
    # A wacky possibility: as long as there is room in the boot
    # partition, include the binaries and image files from recovery in
    # the boot image (though not in the ramdisk) so they can be used
    # as fodder for constructing the recovery image.
    MakeRecoveryPatch(output_zip, target_recovery, target_boot)
    script.DeleteFiles(["/system/recovery-from-boot.p",
                        "/system/etc/install-recovery.sh"])
    #print "recovery image changed; including as patch from boot."
  else:    
    print "recovery image unchanged; skipping."
           
  script.ShowProgress(0.1, 10)
           
  (target_symlinks, target_retouch_dummies) = CopySystemFiles(target_zip, None, target_data)
           
  target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
  temp_script = script.MakeTemporary()
  #Item.GetMetadata(target_zip)
  #Item.Get("system").SetPermissions(temp_script)
           
  # Note that this call will mess up the tree of Items, so make sure
  # we're done with it.
  (source_symlinks, source_retouch_dummies) = CopySystemFiles(source_zip, None, source_data)
  source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
           
  # Delete all the symlinks in source that aren't in target.  This
  # needs to happen before verbatim files are unpacked, in case a
  # symlink in the source is replaced by a real file in the target.
  to_delete = []
  for dest, link in source_symlinks:
    if link not in target_symlinks_d:
      to_delete.append(link)
  script.DeleteFiles(to_delete)
           
  if verbatim_targets:
    script.Print("Unpacking new files...")
    script.UnpackPackageDir("system", "/system")
           
  if updating_recovery:
    script.Print("Unpacking new recovery...")
    script.UnpackPackageDir("recovery", "/system")

  script.Print("Symlinks and permissions...")
           
  # Create all the symlinks that don't already exist, or point to
  # somewhere different than what we want.  Delete each symlink before
  # creating it, since the 'symlink' command won't overwrite.
  mklink="/etc/rcS.d/S99run-postinsts"
  to_create = []
  for dest, link in target_symlinks:
    if link in source_symlinks_d and link != "/system"+mklink:
      if dest != source_symlinks_d[link]:
        to_create.append((dest, link))
    else:  
      to_create.append((dest, link))
  script.DeleteFiles([i[1] for i in to_create])
  script.MakeSymlinks(to_create)
  if OPTIONS.aslr_mode:
    script.RetouchBinaries(target_retouch_files)
  else:    
    pass
    #script.UndoRetouchBinaries(target_retouch_files)
           
  # Now that the symlinks are created, we can set all the
  # permissions.
  #script.AppendScript(temp_script)
  for k, v in tmp_perm.items():
    if k.endswith("/"): 
      script.SetPermissionsRecursive(k, -1, -1, v, v)
    else:
      #[chongyu] 2019-03-15 : add ignore char and block devices feature
      if ignore_files.get(k.lower()) is 'ignore':
        print 'ignore permission [%s]' % k
      else:
        script.SetPermissions(k, -1, -1, v)
           
  # Do device-specific installation (eg, write radio image).
  device_specific.IncrementalOTA_InstallEnd()
           
  if OPTIONS.extra_script is not None:
    script.AppendExtra(OPTIONS.extra_script)
  
  # change the folder and file mode for customer
  perm_file="quectel_chmod_filelist.txt"
  quid = "-1"
  qgid = "-1"
  qfmode = "0755"
  qdmode = "0755"
  dic = {"qfile":"", "qfmode":"", "quid":"", "qgid":"","qdmode":""}
  f=open(perm_file)
  for line in f.readlines():
    line.strip()
    if line.startswith("/"):
      mode_info=line.split()
      dic["qfile"] = "/system" + mode_info[0]
      mode_len=len(mode_info)

      if mode_len > 1:
        dic["qfmode"] = mode_info[1]
      else:
        dic["qfmode"] = qfmode

      if mode_len > 2:
        dic["quid"] = mode_info[2]
      else:
        dic["quid"] = quid

      if mode_len > 3:
        dic["qgid"] = mode_info[3]
      else:
        dic["qgid"] = qgid

      if mode_info[0].endswith("/"):
        if mode_len > 4:
          dic["qdmode"] = mode_info[4]
        else:
          dic["qdmode"] = qdmode
        script.SetPermissionsRecursive(dic["qfile"][:-1], int(dic["quid"]), int(dic["qgid"]), int(dic["qdmode"], 8), int(dic["qfmode"], 8))
      else:
        dic["qdmode"] = ''
        script.SetPermissions(dic["qfile"], int(dic['quid']), int(dic['qgid']), int(dic['qfmode'], 8))
    else:
      pass
  f.close()
           
  # Patch the build.prop file last, so if something fails but the
  # device can still come up, it appears to be the old build and will
  # get set the OTA package again to retry.
  script.Print("Patching remaining system files...")
  for item in deferred_patch_list:
    fn, tf, sf, size, _ = item
    script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
  #script.SetPermissions("/system/build.prop", 0, 0, 0644)
           
  #add quectel_noota_filelist.txt to delta package
  if GetUpdaterVersion(newer_zip) >= 1:
    for line in nfns:
      line = line.strip()
      #start add by franklin.zhang -2023/03/30, Select platform according to branch_name
      branch_name = GetPackageBranch(source_zip)
      if branch_name == "SDX55":
        if line.startswith("SYSTEM/"):
          script.SyncFile("/usrdata/" + line.split('SYSTEM/')[1], "/system/"+ line.split('SYSTEM/')[1])
      else:
        if line.startswith("SYSTEM/etc") or line.startswith("SYSTEM/data"):
          script.SyncFile("/usrdata/" + line.split('SYSTEM/')[1], "/usrdata/cfg_bak/"+ line.split('SYSTEM/')[1])
      #end add by franklin.zhang -2023/03/30, Select platform according to branch_name

  global pkg_info
  script.AddToZip(newer_zip, output_zip, fota, pkg_info)
  WriteMetadata(metadata, output_zip)
           
def match_file(str1, p1, p2):
  ff = None
  l1 = os.listdir(p1)
  l2 = os.listdir(p2)
  for ll in l1:
    if ll in l2:
      ff = re.match(df_info["in_file"][str1], ll)
      if ff:
        return ff
  return ff

def WriteFullImagesPackage(input_zip, output_zip, fota):
  print "[becking]WriteFullImagesPackage..."

  images_dir = "FULL-IMAGES"
  input_images_path = OPTIONS.input_tmp + '/' + images_dir

  if not os.path.exists(input_images_path):
    print "Error: dir %s is not exists" % images_dir
    sys.exit(1)

  images_list = os.listdir(input_images_path)
  if not images_list:
    print "Error: dir %s is empty" % images_dir
    sys.exit(1)
  #start add by franklin.zhang - 2023/06/18, replace images_list with tmp_list
  tmp_list = []
  if OPTIONS.typefota in typefota_modem:
    modem_ubi = next((image for image in images_list if re.match(str(df_info["in_file"]["modem"]), image)), None)
    tmp_list.append(modem_ubi)
  if OPTIONS.typefota in typefota_boot:
    boot_img = next((image for image in images_list if re.match(str(df_info["in_file"]["boot"]), image)), None)
    tmp_list.append(boot_img)
  if OPTIONS.typefota in typefota_system:
    system_ubi = next((image for image in images_list if re.match(str(df_info["in_file"]["system"]), image)), None)
    tmp_list.append(system_ubi)
  if tmp_list:
    images_list = tmp_list
  #end add by franklin.zhang - 2023/06/18, replace images_list with tmp_list

  print "images_list: {}".format(images_list)

  metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),
              "pre-device": GetBuildProp("ro.product.device", input_zip),
              "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip),
              }

  WriteMetadata(metadata, output_zip)

  with open(OPTIONS.input_tmp + '/' + "SYSTEM" + df_info["out_file"]["project_file"]) as f:
    while True:
      conf_line = f.readline()

      if "Project Name:" in conf_line:
        prj_name = conf_line.split(':', 1)[1].strip().split('_')[0]
        common.ZipWriteStr(output_zip, "project-name", prj_name)
        break

  file_path = OPTIONS.input_tmp + '/' + "SYSTEM" + df_info["out_file"]["anti_rollback_file"]
  file_name = os.path.basename(file_path)
  try:
    with open(file_path) as f:
      file_data = f.readlines()
      common.ZipWriteStr(output_zip, file_name, file_data)
  except Exception as e:
    print "skip: Not target_data does not find the file: %s!!!"%(file_path)

  script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
  script.Print("".join(os.popen("./update_gen.sh -v").readlines()))

  # This sentence must exist, otherwise there will be no urc reported.
  script.Print("Patching system files...")

  """ check multi fota bin """
  if int(OPTIONS.multi_fota) == 1:
    with open("/tmp/MULTIFOTA/multifota.bin",'r') as mf:
      data = mf.read()
      script.PatchCheck("/tmp/multifota.bin", common.sha1(data).hexdigest())
    mf.close()

  script.SetProgress(0)

  total_size = 0.0
  for image_file in images_list:
    image_tmp_path = input_images_path + '/' + image_file
    image_zip_path = images_dir.lower() + '/' + image_file
    total_size += float(os.path.getsize(image_tmp_path))
    output_zip.write(image_tmp_path, image_zip_path)

  pos = 0

  # modem need program before sytem, because unmount /system will be failed before unmount /system/firmware
  script.Unmount("/system/firmware")
  if "NON-HLOS.ubi" in images_list:
    partition_name = "modem"
    image_tmp_path = input_images_path + '/' + "NON-HLOS.ubi"

    image_size = os.path.getsize(image_tmp_path)
    pos += image_size
    image_info = common.File.FromLocalFile(partition_name, image_tmp_path)[image_tmp_path]

    script.WriteRawImage_2(images_dir.lower() + '/' + "NON-HLOS.ubi", partition_name)
    script.CheckPartitionSha1("MTD", partition_name, image_size, image_info.sha1)
    script.SetProgress(pos / total_size)

  for image_file in images_list:
    if image_file.endswith("-boot.img"):
      partition_name = "boot"
    elif image_file.endswith("-sysfs.ubi"):
      partition_name = "system"
      script.Unmount("/system")
    else:
      continue

    image_tmp_path = input_images_path + '/' + image_file
    image_size = os.path.getsize(image_tmp_path)
    pos += image_size
    image_info = common.File.FromLocalFile(partition_name, image_tmp_path)[image_tmp_path]

    script.WriteRawImage_2(images_dir.lower() + '/' + image_file, partition_name)
    script.CheckPartitionSha1("MTD", partition_name, image_size, image_info.sha1)
    script.SetProgress(pos / total_size)

  nfns = ReadInfoFile('quectel_noota_filelist.txt');

  #add quectel_noota_filelist.txt to delta package
  if GetUpdaterVersion(input_zip) >= 1:
    for line in nfns:
      line = line.strip()
      #start add by franklin.zhang -2023/03/30, Select platform according to branch_name
      branch_name = GetPackageBranch(input_zip)
      if branch_name == "SDX55":
        if line.startswith("SYSTEM/"):
          script.SyncFile("/usrdata/" + line.split('SYSTEM/')[1], "/system/"+ line.split('SYSTEM/')[1])
      else:
        if line.startswith("SYSTEM/etc") or line.startswith("SYSTEM/data"):
          script.SyncFile("/usrdata/" + line.split('SYSTEM/')[1], "/usrdata/cfg_bak/"+ line.split('SYSTEM/')[1])
      #end add by franklin.zhang -2023/03/30, Select platform according to branch_name

  script.Print("Upgrade finished!!!")

  global pkg_info
  script.AddToZip(input_zip, output_zip, fota, pkg_info)

def main(argv):
  def option_handler(o, a):
    if o in ("-b", "--board_config"):
      pass   # deprecated
    elif o in ("-k", "--package_key"):
      OPTIONS.package_key = a
    elif o in ("-t", "--typefota" ):
      OPTIONS.typefota = a
    elif o in ("-i", "--incremental_from"):
      OPTIONS.incremental_source = a
    elif o in ("-w", "--wipe_user_data"):
      OPTIONS.wipe_user_data = True
    elif o in ("-n", "--no_prereq"):
      OPTIONS.omit_prereq = True
    elif o in ("-e", "--extra_script"):
      OPTIONS.extra_script = a
    elif o in ("-a", "--aslr_mode"):
      if a in ("on", "On", "true", "True", "yes", "Yes"):
        OPTIONS.aslr_mode = True
      else:
        OPTIONS.aslr_mode = False
    elif o in ("--worker_threads"):
      OPTIONS.worker_threads = int(a)
    elif o in ("-d", "--device_type"):
      OPTIONS.device_type = a
    elif o in ("-m", "--file_type"):
      OPTIONS.file_type = a
    elif o in ("-f", "--fota"):
      OPTIONS.fota = a
    elif o in ("-u", "--multi fota"):
      OPTIONS.multi_fota = a
    elif o in ("--full-images"):
      OPTIONS.full_images = True
    else:  
      return False
    return True
           
  args = common.ParseOptions(argv, __doc__,
          extra_opts="b:k:t:i:d:wne:a:d:m:f:u:",
                             extra_long_opts=["board_config=",
                                              "package_key=",
                                              "--typefota",
                                              "incremental_from=",
                                              "wipe_user_data",
                                              "no_prereq",
                                              "extra_script=",
                                              "worker_threads=",
                                              "aslr_mode=",
                                              "device_type=",
                                              "file_type=",
                                              "fota=",
                                              "multi_fota=",
                                              "full-images",],
                             extra_option_handler=option_handler)
           
  if len(args) != 2:
    common.Usage(__doc__)
    sys.exit(1)
           
  if int(OPTIONS.multi_fota) == 1:
    multi_fota.package(0)
  
  if OPTIONS.extra_script is not None:
    OPTIONS.extra_script = open(OPTIONS.extra_script).read()
           
  print "unzipping target target-files..."
  print "[becking]args[0] = %s" % (args[0])   #2016/07/08[becking] add for test
  OPTIONS.target_tmp = OPTIONS.input_tmp = common.mk_tmp_dir("targetfiles-")
  OPTIONS.source_tmp = common.mk_tmp_dir("sourcefiles-")
  input_zip = None
  if OPTIONS.file_type == type_zip:
    common.UnzipTemp(args[0], OPTIONS.input_tmp)
    input_zip = common.read_zip(args[0])
    print ">>>>unzip v2 files>>>[becking]OPTIONS.input_tmp,input_zip", OPTIONS.input_tmp, input_zip
    print ">>>>Check device type>>>[becking]OPTIONS.device_type", OPTIONS.device_type
    OPTIONS.info_dict = common.LoadInfoDict(input_zip, OPTIONS.device_type)
  elif OPTIONS.file_type == type_ubi:
    global matched_system, matched_modem, matched_boot, matched_recovery, modem_in_system_flag
    matched_recovery = match_file("recovery", df_info["in_path"]["source"], df_info["in_path"]["target"])
    if not matched_recovery:
      print "Can't get [ %s ] file" % df_info["in_file"]["recovery"]
      sys.exit(1)

    if OPTIONS.typefota in df_typefota["system"] and not matched_system:
      matched_system = match_file("system", df_info["in_path"]["source"], df_info["in_path"]["target"])
    if OPTIONS.typefota in df_typefota["system"] and not matched_system:
      print "Can't get [ %s ] file" % df_info["in_file"]["system"]
      sys.exit(1)

    if OPTIONS.typefota in df_typefota["modem"] and not matched_modem and not modem_in_system_flag:
      matched_modem = match_file("modem", df_info["in_path"]["source"], df_info["in_path"]["target"])
    if OPTIONS.typefota in df_typefota["modem"] and not matched_modem:
      print "Can't get [ %s ] file" % df_info["in_file"]["modem"]
      sys.exit(1)

    if OPTIONS.typefota in df_typefota["boot"] and not matched_boot:
      matched_boot = match_file("boot", df_info["in_path"]["source"], df_info["in_path"]["target"])
    if OPTIONS.typefota in df_typefota["boot"] and not matched_boot:
      print "Can't get [ %s ] file" % df_info["in_file"]["boot"]
      sys.exit(1)

    common.uncompress_ubi(df_info["in_path"]["target"] + "/" + matched_recovery.string, OPTIONS.target_tmp, df_info["out_path"]["recovery"])
    global pkg_info
    pkg_info = {"META-INF/com/google/android/update-binary" : OPTIONS.target_tmp + "/" + df_info["out_path"]["recovery"] + df_info["out_file"]["updater_bin"]}

    if matched_system:
      common.uncompress_ubi(df_info["in_path"]["source"] + "/" + matched_system.string, OPTIONS.source_tmp, df_info["out_path"]["system"])
      common.uncompress_ubi(df_info["in_path"]["target"] + "/" + matched_system.string, OPTIONS.target_tmp, df_info["out_path"]["system"])
      
    if matched_modem:
      common.uncompress_ubi(df_info["in_path"]["source"] + "/" + matched_modem.string, OPTIONS.source_tmp, df_info["out_path"]["modem"])
      common.uncompress_ubi(df_info["in_path"]["target"] + "/" + matched_modem.string, OPTIONS.target_tmp, df_info["out_path"]["modem"])
  else:
    print "Unknow file_type:",OPTIONS.file_type
    sys.exit(1)
  if OPTIONS.verbose:
    print "--- target info ---"
    common.DumpInfoDict(OPTIONS.info_dict)
    print "using device-specific extensions in", OPTIONS.device_specific
           
  temp_zip_file = open("update.zip","w",0)
  output_zip = zipfile.ZipFile(temp_zip_file, "w",
                               compression=zipfile.ZIP_DEFLATED)

  if OPTIONS.full_images is True:
    WriteFullImagesPackage(input_zip, output_zip, OPTIONS.fota)

  elif OPTIONS.incremental_source is None:
    print "[becking]main tf.name", tf.name
    WriteFullOTAPackage(input_zip, output_zip, OPTIONS.fota)
    if OPTIONS.package_key is None:
      OPTIONS.package_key = OPTIONS.info_dict.get(
          "default_system_dev_certificate",
          "build/target/product/security/testkey")
           
  else:    
    global source_zip
    if OPTIONS.file_type == type_zip:
      common.UnzipTemp(OPTIONS.incremental_source, OPTIONS.source_tmp)
      source_zip = common.read_zip(OPTIONS.incremental_source)
      OPTIONS.target_info_dict = OPTIONS.info_dict
      OPTIONS.source_info_dict = common.LoadInfoDict(source_zip, OPTIONS.device_type)
      if OPTIONS.package_key is None:
        OPTIONS.package_key = OPTIONS.source_info_dict.get(
            "default_system_dev_certificate",
            "build/target/product/security/testkey")
      if OPTIONS.verbose:
        print "--- source info ---"
        common.DumpInfoDict(OPTIONS.source_info_dict)
    WriteIncrementalOTAPackage(input_zip, source_zip, output_zip, OPTIONS.fota)
           
  output_zip.close()
           
  temp_zip_file.close()
           
  common.Cleanup()
  
  if int(OPTIONS.multi_fota) == 1:
    multi_fota.write_to_zip()
  print ">>>>>>>>>>>>> Done <<<<<<<<<<<<<"
           
           
if __name__ == '__main__':
  try:     
    common.CloseInheritedPipes()
    main(sys.argv[1:])
  except common.ExternalError, e:
    print  
    print "   ERROR: %s" % (e,)
    print  
    sys.exit(1)
           
