21 if sys.hexversion < 0x02040000:
22 print >> sys.stderr, "Python 2.4 or newer is required."
26 Diff two folders and create delta using SS_BSDIFF
27 Will maintain same format of script that will be generated when we use diffutil
29 1. Create a list of files in each Base folders,
30 2. These files will fall into one these below categories:
31 1) Only in OLD - Should be deleted
32 2) Only in NEW - Should be added or renamed accordingly
33 3) File exists in both directories but contents are different - Create Diff.
34 4) File name is same but TYPE can change (File to Folder, Folder to Link etc.)
35 5) Duplicates in the list of Deletes and News
36 6) Close matching diffs even though name changes across directories. (for matching extension)
37 7) Clearing empty directories after Moves or diffs under Rename.
38 8) Supporting Verbatim - Any entry under Verbatim_list.txt will be treated as NEW files instead of patch.
41 1. Given two folders, from list of REMOVED and NEW files find if there
42 is version change and create diff between them
45 Want to extend the same script for entire DIFF generation and replace TOTAlib.sh file
46 Catching errors at all stages. SHOULD exit & return error in case of failure
53 global NEW_FILES_ZIP_NAME
56 global SYMLINK_DOC_NAME
62 global SUPPORT_CONTAINERS
68 global COMMON_BIN_PATH
74 COMMON_BIN_PATH = "../../common/bin/"
75 DIFF_UTIL = "/usr/local/bin/ss_bsdiff"
76 DIFFPATCH_UTIL = "/usr/local/bin/ss_bspatch"
78 ZIPUTIL = "7z -mf=off a system.7z "
79 NEW_FILES_PATH = "run/upgrade-sysroot"
80 NEW_FILES_ZIP_NAME = "system.7z"
82 ATTR_DOC_EXT = "_attr.txt"
83 SYMLINK_DOC_NAME = "_sym.txt"
86 DIFF_SUFFIX = ".delta"
89 DELTA_IMG = "DELTA_IMG"
90 DELTA_IMG_AB = "DELTA_IMG_AB"
95 VERBATIM_LIST = "Verbatim_List.txt"
99 COMPRESSION_LZMA = "lzma"
100 COMPRESSION_BROTLI = "brotli"
102 SUPPORT_RENAME = "TRUE" #Use appropriate name
103 SUPPORT_CONTAINERS = "FALSE"
104 SUPPORT_VERBATIM = "TRUE"
109 logging.basicConfig(filename=LOGFILE, level=logging.DEBUG)
111 global GenerateDiffAttr
114 if len(sys.argv) < 5:
115 sys.exit('Usage: CreatePatch.py UPDATE_TYPE PARTNAME OLDBASE NEWBASE OUTFOLDER')
116 UPDATE_TYPE = sys.argv[1]
117 PART_NAME = sys.argv[2] # lets make this also optional
119 BASE_OLD = sys.argv[3]
120 BASE_NEW = sys.argv[4]
121 OUT_DIR = sys.argv[5]
124 UPDATE_CFG_PATH = EMPTY
125 GenerateDiffAttr = "FALSE"
126 if UPDATE_TYPE == DELTA_FS:
127 #instead of arguments check it in outdirectory ?
128 if len(sys.argv) == 9:
129 ATTR_OLD = sys.argv[6]
130 ATTR_NEW = sys.argv[7]
131 UPDATE_CFG_PATH = '../'+sys.argv[8]
132 GenerateDiffAttr = "TRUE"
134 elif UPDATE_TYPE in [DELTA_IMG, DELTA_IMG_AB, FULL_IMG]:
135 if len(sys.argv) == 7:
136 #Use path in better way
137 UPDATE_CFG_PATH = '../'+sys.argv[6]
140 global DIFFPATCH_UTIL
141 if not (os.path.isfile(DIFF_UTIL) and os.access(DIFF_UTIL, os.X_OK)):
142 DIFF_UTIL = COMMON_BIN_PATH+DIFF_UTIL
143 DIFFPATCH_UTIL = COMMON_BIN_PATH+DIFFPATCH_UTIL
144 if not (os.path.isfile(DIFF_UTIL) and os.access(DIFF_UTIL, os.X_OK)):
145 print >> sys.stderr, "Diff Util Does NOT exist -- ABORT"
146 logging.info ('Diff Util Does NOT exist -- ABORT')
149 start = datetime.datetime.now().time()
150 logging.info('*************** ENTERED PYTHON SCRIPT *****************')
151 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))
153 ensure_dir_exists(OUT_DIR)
154 if GenerateDiffAttr == "TRUE":
155 if not (os.path.isfile(ATTR_OLD) and os.path.isfile(ATTR_NEW)):
156 print >> sys.stderr, "Attributes missing -- ABORT"
160 # Should check if APT is supported on other linux flavours
162 if cache['p7zip'].is_installed and cache['attr'].is_installed and cache['tar'].is_installed:
163 logging.info ('Basic utils installed')
165 print >> sys.stderr, "Basic utils missing -- ABORT"
168 if UPDATE_TYPE == FULL_IMG:
169 SS_mk_full_img(BASE_OLD, BASE_NEW, OUT_DIR, PART_NAME, UPDATE_CFG_PATH)
170 elif UPDATE_TYPE == DELTA_IMG:
171 SS_mk_delta_img(BASE_OLD, BASE_NEW, OUT_DIR, PART_NAME, UPDATE_CFG_PATH, COMPRESSION_LZMA)
172 elif UPDATE_TYPE == DELTA_IMG_AB:
173 SS_mk_delta_img(BASE_OLD, BASE_NEW, OUT_DIR, PART_NAME, UPDATE_CFG_PATH, COMPRESSION_BROTLI)
174 elif UPDATE_TYPE == DELTA_FS:
175 AttributeFile = ATTR_NEW
176 ATTR_FILE = OUT_DIR+'/'+PART_NAME+ATTR_DOC_EXT
177 Diff_AttrFiles(ATTR_OLD, ATTR_NEW, ATTR_FILE)
178 Old_files, Old_dirs = Get_Files(BASE_OLD)
179 New_files, New_dirs = Get_Files(BASE_NEW)
180 SS_Generate_Delta(PART_NAME, BASE_OLD, Old_files, Old_dirs, BASE_NEW, New_files, New_dirs, OUT_DIR, ATTR_FILE)
182 if not UPDATE_CFG_PATH == EMPTY:
183 SS_update_cfg(PART_NAME, UPDATE_CFG_PATH)
186 elif UPDATE_TYPE == EXTRA:
187 print('UPDATE_TYPE ---- EXTRA')
188 elif UPDATE_TYPE == PRE_UA:
189 print('UPDATE_TYPE ---- PRE_UA')
191 print('UPDATE_TYPE ---- UNKNOWN FORMAT')
193 if GenerateDiffAttr == "TRUE":
194 if os.path.exists(ATTR_OLD) and os.path.exists(ATTR_NEW):
197 end = datetime.datetime.now().time()
199 logging.info('Max Memory requried to upgrade [%s] is [%d] for File[%s]' % (PART_NAME, MEM_REQ, MEM_FILE))
200 logging.info('*************** DONE WITH PYTHON SCRIPT ***************')
201 logging.info('Time start [%s] - Time end [%s]' % (start, end))
202 print('Done with [%s][%d]---- Time start [%s] - Time end [%s]' % (PART_NAME, MEM_REQ, start, end))
205 logging.error('Usage: {} <Update_Type> <Part_Name> <OLD_Base> <NEW_Base> <OUT_DIR>'.format(os.path.basename(sys.argv[0])))
209 def SS_update_cfg(DELTA_BIN, UPDATE_CFG_PATH):
210 f = open(UPDATE_CFG_PATH, 'r')
211 lines = f.readlines()
213 f = open(UPDATE_CFG_PATH, 'w')
215 ConfigItems = line.split()
216 if ConfigItems[0] == DELTA_BIN:
217 DELTA = ConfigItems[1]
218 logging.info ('Updating %s config' % DELTA_BIN)
219 line = line.rstrip('\n')
221 line = line.replace(line, line+'\t'+str(Value)+'\n')
227 def SS_mk_delta_img(BASE_OLD, BASE_NEW, OUT_DIR, DELTA_BIN, UPDATE_CFG_PATH, COMPRESSION_METHOD):
230 oldsize_d= os.path.getsize(BASE_OLD)
231 newsize_d= os.path.getsize(BASE_NEW)
232 SHA_BIN_DEST= hash_file(BASE_NEW)
233 SHA_BIN_BASE=hash_file(BASE_OLD)
235 #incase UPDATE CFG is empty
237 SS_UpdateSize(BASE_OLD, BASE_NEW)
238 #Should throw error if PART NAME NOT found??
239 if not UPDATE_CFG_PATH == EMPTY:
240 f = open(UPDATE_CFG_PATH, 'r')
241 lines = f.readlines()
243 f = open(UPDATE_CFG_PATH, 'w')
245 ConfigItems = line.split()
246 if ConfigItems[0] == DELTA_BIN:
247 logging.info ('Updating %s config' % DELTA_BIN)
248 DELTA = ConfigItems[1]
249 line = line.rstrip('\n')
250 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')
256 patchLoc = '%s/%s' % (OUT_DIR, DELTA)
257 logging.info('Make Delta Image %s <--> %s ==> %s %s' % (BASE_OLD, BASE_NEW , DELTA_BIN, patchLoc))
258 subprocess.call([DIFF_UTIL,"-c",COMPRESSION_METHOD,BASE_OLD,BASE_NEW,patchLoc])
262 def SS_mk_full_img(BASE_OLD, BASE_NEW, OUT_DIR, DELTA_BIN ,UPDATE_CFG_PATH):
263 logging.info('Make Full Image %s <--> %s ==> %s' % (BASE_OLD, BASE_NEW ,DELTA_BIN))
264 oldsize_d= os.path.getsize(BASE_OLD)
265 newsize_d= os.path.getsize(BASE_NEW)
266 SHA_BIN_DEST= hash_file(BASE_NEW)
267 SHA_BIN_BASE=hash_file(BASE_OLD)
268 #echo -e "\t${oldsize_d}\t\t${newsize_d}\t\t${SHA_BIN_BASE}\t\t${SHA_BIN_DEST}" >> ${DATA_DIR}/update_new.cfg
269 SS_UpdateSize(BASE_OLD, BASE_NEW)
271 if not UPDATE_CFG_PATH == EMPTY:
272 f = open(UPDATE_CFG_PATH, 'r')
273 lines = f.readlines()
275 f = open(UPDATE_CFG_PATH, 'w')
277 ConfigItems = line.split()
278 if ConfigItems[0] == DELTA_BIN:
279 logging.info ('Updating %s config' % DELTA_BIN)
280 DELTA = ConfigItems[1]
281 line = line.rstrip('\n')
282 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')
288 def zipdir(path, zip):
289 for root, dirs, files in os.walk(path):
291 zip.write(os.path.join(root, file))
293 def ensure_dir_exists(path):
294 if not os.path.exists(path):
301 head, tail = ntpath.split(path) #This is for windows?? Recheck
305 head, tail = ntpath.split(path)
308 def difflines(list1, list2):
309 c = set(list1).union(set(list2))
310 d = set(list1).intersection(set(list2))
313 #Creating Diff between OLD and NEW attribute files v12
314 def Diff_AttrFiles(ATTR_OLD, ATTR_NEW, ATTR_FILE):
315 if GenerateDiffAttr == "FALSE":
317 with open(ATTR_OLD, 'r') as f_old:
318 lines1 = set(f_old.read().splitlines())
320 with open(ATTR_NEW, 'r') as f_new:
321 lines2 = set(f_new.read().splitlines())
323 lines = difflines(lines2, lines1)
324 with open(ATTR_FILE, 'w+') as file_out:
326 if line not in lines1:
327 logging.info('Diff_AttrFiles - %s' % line)
328 file_out.write(line+'\n')
336 def Update_Attr(RequestedPath, Type, File_Attibutes, Sym_Attibutes):
337 #Full File Path should MATCH
338 if GenerateDiffAttr == "FALSE":
340 FilePath = '"/'+RequestedPath+'"'
341 #print ('FilePath - %s'% (FilePath))
342 with open(AttributeFile) as f:
345 if Type == SYMLINK_TYPE:
346 Sym_Attibutes.append(line)
348 File_Attibutes.append(line)
351 '''This function returns the SHA-1 hash of the file passed into it'''
352 def hash_file(filename):
357 # open file for reading in binary mode
358 with open(filename,'rb') as file:
359 # loop till the end of the file
362 # read only 1024 bytes at a time
363 chunk = file.read(1024*1024)
366 # return the hex representation of digest
369 def find_dupes_dir(BASE_OLD, BASE_NEW):
372 print('Finding Duplicates in - %s %s' % (BASE_OLD, BASE_NEW))
373 logging.info('Finding Duplicates in - %s %s' % (BASE_OLD, BASE_NEW))
374 for rootbase, subdirsB, fileListB in os.walk(BASE_OLD):
375 #print('Scanning %s...' % rootbase)
376 for filename in fileListB:
377 path = os.path.join(rootbase, filename)
378 if os.path.islink(path):
381 file_hash = hash_file(path)
382 dups[file_hash] = path
384 for roottarget, subdirsT, fileListT in os.walk(BASE_NEW):
385 #print('Scanning %s...' % roottarget)
386 for filename in fileListT:
387 # Get the path to the file
388 path = os.path.join(roottarget, filename)
389 if os.path.islink(path):
392 file_hash = hash_file(path)
393 # Add or append the file path
394 if file_hash in dups:
395 BaseStr = dups.get(file_hash)
396 Baseloc = path.find('/')
397 TarLoc = BaseStr.find('/')
398 if not path[Baseloc:] == BaseStr[TarLoc:]:
399 logging.info('Dupes - %s ==> %s' % (path[Baseloc:], BaseStr[TarLoc:]))
400 fdupes[path] = BaseStr
401 logging.info('Total Duplicate files %d' % (len(fdupes)))
405 def find_dupes_list(BASE_OLD, BASE_NEW, fileListB, fileListT):
408 print('Finding Duplicates in - %s %s' % (BASE_OLD, BASE_NEW))
410 for filename in fileListB:
411 Src_File = BASE_OLD+'/'+filename
412 if os.path.islink(Src_File) or os.path.isdir(Src_File):
415 file_hash = hash_file(Src_File)
416 dups[file_hash] = Src_File
419 for filename in fileListT:
420 Dest_File = BASE_NEW+'/'+filename
421 if os.path.islink(Dest_File) or os.path.isdir(Dest_File):
424 file_hash = hash_file(Dest_File)
425 if file_hash in dups:
426 BaseStr = dups.get(file_hash)
427 Baseloc = BaseStr.find('/')
428 if not BaseStr[Baseloc:] == filename:
429 #print('Dupes - %s ==> %s' % (BaseStr[Baseloc:], filename))
430 fdupes[BaseStr] = filename
432 logging.info('Total Duplicate files %d' % (len(fdupes)))
435 def SS_UpdateSize(src_file, dst_file):
438 oldsize_d= os.path.getsize(src_file)
439 newsize_d= os.path.getsize(dst_file)
440 if oldsize_d >= newsize_d:
450 def SS_Generate_Delta(PART_NAME, BASE_OLD, Old_files, Old_dirs, BASE_NEW, New_files, New_dirs, OUT_DIR, ATTR_FILE):
451 print('Going from %d files to %d files' % (len(Old_files), len(New_files)))
452 logging.info('Going from %d files to %d files' % (len(Old_files), len(New_files)))
454 # First let's fill up these categories
474 for elt in New_files:
475 if elt not in Old_files:
476 files_new.append(elt)
477 logging.info('New files %s' % elt)
479 # Generate Delete List
480 for elt in Old_files:
481 if elt not in New_files:
482 # Cant we just append it here only if this is NOT a directory???? so that we have list of removed files ONLY. including directories
483 files_removed.append(elt)
484 logging.info('Old files %s' % elt)
488 #print('List of Old Dirs %s' % elt)
489 # Delete END logic goes in hand with UPG, After Diffs and moves, DEL END should be done.
490 if elt not in New_dirs:
491 Dir_removed.append(elt)
492 logging.info('Old Dirs %s' % elt+'/')
495 if elt not in Old_dirs:
496 Dir_Added.append(elt)
497 #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
499 # What files have changed contents but not name/path?
500 for elt in New_files:
502 #Both are symbolic linkes and they differ
503 src_file = BASE_OLD+'/'+elt
504 dst_file = BASE_NEW+'/'+elt
505 #print('Files Changed - %s -%s' % (src_file,dst_file))
506 if os.path.islink(src_file) and os.path.islink(dst_file):
507 if not os.readlink(src_file) == os.readlink(dst_file):
508 files_changed.append(elt)
509 #print('%d Sym link files changed' % len(files_changed))
510 logging.info('Sym links Changed - %s' % elt)
512 files_unchanged.append(elt)
513 #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)
514 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):
515 if not filecmp.cmp(src_file, dst_file):
516 files_changed.append(elt)
517 #print('%d Normal files changed' % len(files_changed))
518 #print('Files Changed - %s' % elt)
520 files_unchanged.append(elt)
521 #File types differ between BASE and TARGET
523 logging.info('Files are of diff types but same names Src- %s Des- %s' % (src_file, dst_file))
524 #Both file types have changed and they differ
525 #Case 1: First Delete the OLD entry file type (Be it anything)
526 #Processing and updating partition txt file will be done under REMOVED case and NEW files case accordingly, we just make an entry here
527 files_removed.append(elt)
528 files_new.append(elt)
531 # HANDLING VERBATIM - Remove from changed list and delete the entries on device first
532 #This script is called partition wise, So, how do u want to handle it? (specialy for delete case?)
534 print("Check for any verbatim under - %s" % VERBATIM_LIST)
535 if SUPPORT_VERBATIM == "TRUE" and os.path.exists(VERBATIM_LIST):
536 with open(VERBATIM_LIST, 'r') as F_News:
537 lines = set(F_News.read().splitlines())
539 if line in files_changed:
540 files_changed.remove(line)
541 files_removed.append(line)
542 if line in files_new:
543 files_new.remove(line)
545 #Currently if Version or number is the first character of the file, then we are NOT making any diffs.
546 if SUPPORT_RENAME == "TRUE":
547 for elt in files_removed:
548 if os.path.isfile(BASE_OLD+'/'+elt):
549 FileName = path_leaf(elt)
550 entries = re.split('[0-9]' , FileName)
551 #Gives the STRING part of NAME. if name starts with version then later part wil b string
552 #print('Entires under removed list after split - %s %s - %s' % (FileName, entries[0], elt))
553 #If version is starting at the begining of the string?? shd we hav additional check for such cases??
554 if len(entries[0]) > 0:
555 files_Del_List.update({entries[0]: elt})
557 for elt in files_new:
558 if os.path.isfile(BASE_NEW+'/'+elt):
559 FileName = path_leaf(elt)
560 entries = re.split('[0-9]' , FileName)
561 #print('Entires under NEWfiles list after split - %s %s - %s' % (FileName, entries[0], elt))
562 if len(entries[0]) > 0:
563 files_New_List.update({entries[0]: elt})
565 for key, value in files_Del_List.iteritems():
566 #print('Key value pair -%s -%s' % (key, value))
567 if key in files_New_List:
568 # this file is the same name in both!
569 src_file = BASE_OLD+'/'+value
570 dst_file = BASE_NEW+'/'+files_New_List[key]
571 olddirpath = path_head(files_New_List[key])
572 newdirpath = path_head(value)
573 if os.path.islink(src_file) or os.path.islink(dst_file):
574 logging.debug('Cannot diff as one of them is Symlink')
575 elif os.path.isdir(src_file) or os.path.isdir(dst_file):
576 logging.debug('Cannot diff as one of them is dir')
578 #Pick the best diff of same type and diff names
579 files_renamed.append([files_New_List[key], value])
580 files_removed.remove(value)
581 files_new.remove(files_New_List[key])
585 Partition.txt contains Protocol for UPI
586 Types Supported: DIFFS, MOVES, NEWS, DELETES, SYMDIFFS, SYMNEWS.
595 SymLinkDoc = OUT_DIR+'/'+PART_NAME+SYMLINK_DOC_NAME
596 Partition_Doc = open(OUT_DIR+'/'+PART_NAME+'.txt','w')
597 Partition_Doc_SymLinks = open(SymLinkDoc,'w')
599 print("writing diff'ed changed files...")
600 for elt in files_changed:
601 dst_file = BASE_NEW+'/'+elt
602 src_file = BASE_OLD+'/'+elt
603 #Both files are symbolic links and they differ
604 if os.path.islink(dst_file) and os.path.islink(src_file):
605 #Both are symlinks and they differ
606 logging.debug(' File Changed is Link %s ' % dst_file)
607 patch = os.readlink(dst_file)
608 Sym_Diff_Cnt = Sym_Diff_Cnt + 1
609 Partition_Doc_SymLinks.write('SYM:DIFF:%s:%s:%s\n' % (elt, elt, patch))
610 Update_Attr(elt, "SYM", File_Attibutes, Sym_Attibutes)
611 #Both are NORMAL files and they differ
612 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):
613 #Both are files and they differ
614 Diff_Cnt = Diff_Cnt + 1
615 patchName = (DIFF_PREFIX+'%d_%s_'+PART_NAME+DIFF_SUFFIX) % (Diff_Cnt, path_leaf(elt))
616 patchLoc = '%s/%s' % (OUT_DIR, patchName)
617 logging.debug(' File Differ %s %s' % (src_file, dst_file))
618 SS_UpdateSize(src_file, dst_file)
621 ret = subprocess.call([DIFF_UTIL,src_file,dst_file,patchLoc])
623 logging.debug('Failed to create diff %d %s %s\n' % (ret, src_file, dst_file))
624 files_new.append(elt)
625 Diff_Cnt = Diff_Cnt - 1
627 Partition_Doc.write('DIFF:REG:%s:%s:%s:%s:%s\n' % (elt, elt, hash_file(src_file), hash_file(dst_file), patchName))
629 Update_Attr(elt, "FILE", File_Attibutes, Sym_Attibutes)
630 #Both differ but they are of diff types
632 #Processing and updating partition txt file will be done under REMOVED case and NEW files case accordingly, we just make an entry here
633 files_removed.append(elt)
634 files_new.append(elt)
636 fdupes = find_dupes_list(BASE_OLD, BASE_NEW, files_removed, files_new)
637 for oldpath, newpath in fdupes.iteritems():
638 logging.info('Dupes %s -> %s' % (oldpath, newpath))
640 for elt in files_removed:
641 src_file = BASE_OLD+'/'+elt
642 #If parent directory is deleted.. & del end not possible. (==> Moves should be done before deletes in ENGINE)
643 if src_file in fdupes.keys():
644 dst_file = BASE_NEW+'/'+ fdupes[src_file]
645 logging.debug(' File Moved %s ==> %s' % (src_file, dst_file))
646 Move_Cnt = Move_Cnt + 1
647 Partition_Doc.write('MOVE:REG:%s:%s:%s\n' % (elt, fdupes[src_file], hash_file(src_file)))
648 files_removed.remove(elt)
649 files_new.remove(fdupes[src_file])
651 #Should be placed after removing duplicates, else they will be filtered here.
652 # loop shd b for all NEW files, rather than for all delete files (Current understanding)
653 # First Step: Sort & Filter out unwanted files
654 # Minimum condition used is,
655 # 1. File name should match 70%
656 # 2. Extensions should be same
657 # 3. File name length shd b greater than 3 char
658 # 4. As we are using sorting on file names, once file name does not match and R_Flag is set to true, we nee not check remaining files. So, will execute break.
659 # 5. Should consider editdistance for RENAME LOGIC ==> TBD
661 Base_DelList = files_removed[:]
662 Base_NewList = files_new[:]
663 DelList = sorted(Base_DelList, key=path_leaf)
664 NewList = sorted(Base_NewList, key=path_leaf)
665 logging.debug('Rename Logic before filter: Delcount -%d NewCount -%d' % (len(DelList), len(NewList)))
669 #Remove unwanted items which we cant make diff with for rename logic
671 if os.path.islink(BASE_OLD+'/'+file):
673 elif os.path.isdir(BASE_OLD+'/'+file):
677 #logging.debug('Sorted del list - %s' % (file))
682 if os.path.islink(BASE_NEW+'/'+file):
684 elif os.path.isdir(BASE_NEW+'/'+file):
686 elif len(path_leaf(file)) <= 3:
687 logging.debug('Ignored for best picks -%s ' % (BASE_NEW+'/'+file))
694 logging.debug('Rename Logic After filter: Delcount -%d NewCount -%d' % (len(DelList), len(NewList)))
696 for new_file in NewList:
698 DirPathNew = path_head(new_file)
699 FileNameNew = path_leaf(new_file)
701 winning_patch_sz = os.path.getsize(BASE_NEW+'/'+new_file)
702 New_fs = winning_patch_sz
705 for del_file in DelList:
706 FileNameOld = path_leaf(del_file)
707 if (FileNameOld.startswith(FileNameNew[:len(FileNameNew)*7/10]) and (os.path.splitext(FileNameNew)[1] == os.path.splitext(del_file)[1])):
708 #winning_patch_sz = 0.9 * os.path.getsize(BASE_NEW+'/'+new_file)
709 #Percentage difference between two file sizes is within 30%, then we consider for diff generation
710 Del_fs = os.path.getsize(BASE_OLD+'/'+del_file)
711 v1 = abs(New_fs-Del_fs)
712 v2 = (New_fs+Del_fs)/2
713 if( v2<=0 or ((v1/v2) * 100) > 30 ):
714 logging.debug('Ignore diff generation New_fs - %d Del_Fs - %d' % (New_fs, Del_fs))
716 logging.debug('I can compute diff between %s %s Del_Fs - %d New_Fs - %d' % (del_file, new_file, Del_fs, New_fs))
718 DiffSize = measure_two_filediffs(BASE_OLD+'/'+del_file, BASE_NEW+'/'+new_file)
719 if (DiffSize < 0.8 * winning_patch_sz):
720 winning_patch_sz = DiffSize
721 winning_file = del_file
722 elif (not FileNameOld.startswith(FileNameNew[:len(FileNameNew)*7/10]) and R_Flag == 'TRUE'):
723 logging.debug('Becuase nex set of files will not have matching name - break @@ %s %s' % (del_file, new_file))
725 if len(winning_file) > 0:
726 logging.debug('Best Pick -%s ==> %s [%d]' % (winning_file, new_file, DiffSize))
727 files_renamed.append([new_file, winning_file])
728 DelList.remove(winning_file)
729 files_removed.remove(winning_file)
730 files_new.remove(new_file)
732 #********************** Files should NOT be deleted for any such renames ***********************
734 if SUPPORT_RENAME == "TRUE":
735 for elt in files_renamed:
736 src_file = BASE_OLD+'/'+elt[1]
737 dst_file = BASE_NEW+'/'+elt[0]
738 Diff_Cnt = Diff_Cnt + 1
739 patchName = (DIFF_PREFIX+'%d_%s_'+PART_NAME+DIFF_SUFFIX) % (Diff_Cnt, path_leaf(elt[1]))
740 #patchName = (DIFF_PREFIX+'_%s'+DIFF_SUFFIX) % (path_leaf(elt[0]))
741 patchLoc = '%s/%s' % (OUT_DIR, patchName)
742 logging.debug(' File Renamed %s ==> %s' % (src_file, dst_file))
743 # Should be careful of renaming files??
744 # Should we consider measure_two_filediffs ?? so that patch size is NOT greater than actual file?
745 # What if folder path has numerics??
747 if os.path.isdir(src_file) or os.path.isdir(dst_file):
748 #This case never occurs??
749 Partition_Doc.write('"%s" and "%s" renamed 0 0\n' % (elt[0], elt[1]))
750 Update_Attr(elt[0], "FILE", File_Attibutes, Sym_Attibutes)
751 else: #Make sure these files are PROPER and they shd NOT be symlinks
752 if filecmp.cmp(src_file, dst_file):
753 Move_Cnt = Move_Cnt + 1
754 Diff_Cnt = Diff_Cnt - 1
755 Partition_Doc.write('MOVE:REG:%s:%s:%s\n' % (elt[1], elt[0], hash_file(src_file)))
758 ret = subprocess.call([DIFF_UTIL,src_file,dst_file,patchLoc])
760 logging.debug('Failed to create diff %d %s %s\n' % (ret, src_file, dst_file))
761 files_new.append(elt)
762 Diff_Cnt = Diff_Cnt - 1
764 Partition_Doc.write('DIFF:REG:%s:%s:%s:%s:%s\n' % (elt[1], elt[0], hash_file(src_file), hash_file(dst_file), patchName))
766 SS_UpdateSize(src_file, dst_file)
767 Update_Attr(elt[0], "FILE", File_Attibutes, Sym_Attibutes)
770 #HANDLING VERBATIM - We Process NEWs and DELETEs for Verbatim list ONLY after processing duplicates & rename functionality.
771 #So that, the rename functionality will NOT create PATCH instead of verbatims.
773 if SUPPORT_VERBATIM == "TRUE" and os.path.exists(VERBATIM_LIST):
774 with open(VERBATIM_LIST, 'r') as F_News:
775 lines = set(F_News.read().splitlines())
777 if not line in files_new:
778 if os.path.exists(BASE_NEW+'/'+line):
779 files_new.append(line)
780 Verbatim_Cnt = Verbatim_Cnt+1
781 logging.debug("Added to list of verbatims -%s" % BASE_NEW+'/'+line)
785 for elt in files_removed:
786 #if files are part of patches after renaming, we shd remove them as part of removed.
787 src_file = BASE_OLD+'/'+elt
788 if os.path.islink(src_file):
789 Partition_Doc.write('DEL:SYM:%s\n' % (elt))
790 elif os.path.isdir(src_file):
791 #If we change to DIR TYPE, then the same token should be modified on UA also and SHA should be accordingly passed.
792 Partition_Doc.write('DEL:REG:%s:NA\n' % (elt))
794 Partition_Doc.write('DEL:REG:%s:%s\n' % (elt, hash_file(src_file)))
795 logging.debug(' File Deleted %s' % src_file)
796 Del_Cnt = Del_Cnt + 1
798 Dir_removed.sort(reverse=True)
799 for elt in Dir_removed:
800 #if Dir is empty, add it to the removed list.
801 src_file = BASE_OLD+'/'+elt
802 #Irrespective of weather files are MOVED or DIFF'ed, we can delete the folders. This action can be performed at the end.
803 #It covers symlinks also, as NEW symlinks cannot point to NON existant folders of TARGET (NEW binary)
804 if os.path.isdir(src_file):
805 Partition_Doc.write('DEL:END:%s\n' % (elt))
806 Del_Cnt = Del_Cnt + 1
807 logging.debug(' Dir Deleted- %s' % src_file)
810 for elt in files_new:
811 dst_file = BASE_NEW+'/'+elt
812 newfiles_dest_path = 'run/upgrade-sysroot/'
813 ensure_dir_exists(newfiles_dest_path)
814 if os.path.islink(dst_file):
815 patch = os.readlink(dst_file)
816 logging.debug(' File New Links %s' % elt)
817 Partition_Doc_SymLinks.write('SYM:NEW:%s:%s\n' % (elt, patch))
818 #What if this is only a new sym link and folder already exists??? Should recheck
819 destpath = newfiles_dest_path + elt
820 if not os.path.exists(path_head(destpath)):
821 os.makedirs(path_head(destpath))
822 logging.info('New SymLink - Adding missing Dir')
823 #Update_Attr(elt, "SYM", File_Attibutes, Sym_Attibutes)
824 Sym_New_Cnt = Sym_New_Cnt + 1
825 elif os.path.isdir(dst_file): # We create just empty directory here
826 destpath = newfiles_dest_path + elt
827 if not os.path.exists(destpath):
828 os.makedirs(destpath)
829 logging.debug(' File New Dir %s' % destpath)
830 New_Cnt = New_Cnt + 1
832 New_Cnt = New_Cnt + 1
833 destpath = newfiles_dest_path + elt
834 destdir = os.path.dirname(destpath)
835 logging.debug('New files - %s ==> %s' % (dst_file, destdir))
837 if not os.path.isdir(destdir):
841 logging.critical('Error in NEW files DIR entry -%s' % destdir)
845 if not stat.S_ISFIFO(os.stat(dst_file).st_mode):
846 shutil.copy2(dst_file, destpath)
847 logging.debug('New files copied from- %s to- %s' % (dst_file, destpath))
849 logging.critical('Error in NEW files entry -%s -%s' % (dst_file, destpath))
852 for elt in Dir_Added:
853 newfiles_dest_path = 'run/upgrade-sysroot/'
854 ensure_dir_exists(newfiles_dest_path)
855 destpath = newfiles_dest_path + elt
856 if not os.path.exists(destpath):
857 os.makedirs(destpath)
858 logging.debug(' DirList New Dir %s' % destpath)
859 New_Cnt = New_Cnt + 1
861 #Base directory should be system
862 print 'Compressing New files'
863 if (New_Cnt > 0 or Sym_New_Cnt > 0):
864 WorkingDir = os.getcwd()
865 os.chdir(os.getcwd()+"/"+NEW_FILES_PATH)
866 logging.info('Curr Working Dir - %s' % os.getcwd())
867 os.system(ZIPUTIL+NEW_FILES_PATH+" >> " + LOGFILE)
868 shutil.move(NEW_FILES_ZIP_NAME, WorkingDir+"/"+OUT_DIR)
869 #New file size?? cos, we extract system.7z from delta.tar and then proceed with decompression
870 SS_UpdateSize(WorkingDir+"/"+OUT_DIR+"/"+NEW_FILES_ZIP_NAME, WorkingDir+"/"+OUT_DIR+"/"+NEW_FILES_ZIP_NAME)
872 shutil.rmtree(NEW_FILES_PATH)
873 # use 7z a system.7z ./*
875 #logging.info('%d Dir to be removed' % len(Dir_removed))
876 logging.info('%d files unchanged' % len(files_unchanged))
877 logging.info('%d files files_renamed' % len(files_renamed))
878 logging.info('%d files NEW' % len(files_new))
879 logging.info('%d File attr' % len(File_Attibutes))
880 logging.info('%d Sym attr' % len(Sym_Attibutes))
881 logging.info('PaTcHCoUnT:Diffs-%d Moves-%d News-%d Delets-%d SymDiffs-%d SymNews-%d Verbatim -%d\n' % (Diff_Cnt, Move_Cnt, New_Cnt, Del_Cnt, Sym_Diff_Cnt, Sym_New_Cnt, Verbatim_Cnt))
882 print('PaTcHCoUnT:Diffs-%d Moves-%d News-%d Delets-%d SymDiffs-%d SymNews-%d Verbatim -%d\n' % (Diff_Cnt, Move_Cnt, New_Cnt, Del_Cnt, Sym_Diff_Cnt, Sym_New_Cnt, Verbatim_Cnt))
884 #There could be duplicates, TODO, can check before adding..
885 ATTR_FILE_D = open(ATTR_FILE,'a+')
886 for elt in File_Attibutes:
887 ATTR_FILE_D.write(elt)
888 for elt in Sym_Attibutes:
889 ATTR_FILE_D.write(elt)
893 Partition_Doc_SymLinks.close()
894 Partition_Read_SymLinks = open(SymLinkDoc,'r+')
895 Partition_Doc.write(Partition_Read_SymLinks.read())
896 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))
897 Partition_Doc_SymLinks.close()
898 Partition_Doc.close()
899 os.remove(SymLinkDoc)
901 if Diff_Cnt + Move_Cnt + New_Cnt+ Del_Cnt + Sym_Diff_Cnt + Sym_New_Cnt + Verbatim_Cnt + os.path.getsize(ATTR_FILE) == 0:
902 print('No Delta Generated for %s - %s' % (PART_NAME,OUT_DIR))
903 logging.info('No Delta Generated for %s' % PART_NAME)
904 shutil.rmtree(OUT_DIR)
907 def Apply_Container_Delta(a_apk, b_apk, new_apk, a_folder, g_output_dir):
909 #CONTROL NAMES, AND PRINTS AND ERROR CASES... SHOULD NOT PROCEED.
910 print 'ApplyContainerDelta - ', b_apk, a_folder, g_output_dir
911 shutil.copy2(g_output_dir+'/'+b_apk, g_output_dir+'/temp')
912 temp_apk = '../'+g_output_dir+'/'+b_apk
913 Patch = 'Patch_'+b_apk
914 ensure_dir_exists(Patch)
915 shutil.copy2(g_output_dir+'/'+b_apk, Patch+'/'+b_apk)
917 #Size issue on Device side?? shd check this
918 subprocess.call(['unzip','-q', Patch+'/'+b_apk, '-d', Patch])
919 with open(g_output_dir+'/PATCH.txt', 'r') as f_new:
920 lines = set(f_new.read().splitlines())
922 #print('Action ==> %s' % line)
923 #Action, Path, Patch = line.split('|')
924 Items = line.split('|')
927 ActualPath = a_folder+'/'+Path
928 PatchPath = Patch+'/'+Path
929 SrcPath = g_output_dir+'/'+path_leaf(Path)
930 #print('Action ==> %s Path ==> %s ' % (Action, Path))
932 patchName = g_output_dir+'/'+Items[2]
933 #print('Apply Patch: ActualPath %s SrcPath %s PatchLoc %s ' % (PatchPath, ActualPath, patchName))
934 subprocess.call([DIFFPATCH_UTIL,ActualPath,ActualPath,patchName])
935 WorkingDir = os.getcwd()
936 os.chdir(WorkingDir+"/"+"temp_a")
937 subprocess.call(['cp', '--parents', Path, '../'+Patch])
940 WorkingDir = os.getcwd()
941 os.chdir(WorkingDir+"/"+"temp_a")
942 subprocess.call(['cp', '--parents', Path, '../'+Patch])
945 print('Apply_Container_Delta - Unknown Error')
946 #print('Touch all files and set common attributes for DIFF generation')
947 WorkingDir = os.getcwd()
948 os.chdir(WorkingDir+"/"+Patch)
950 CONTAINER_DATE = '200011111111.11'
951 CONTAINER_MODE = '0755'
952 subprocess.call(['find', '.', '-type', 'l', '-exec', 'rm', '-rf', '{}', ';'])
953 subprocess.call(['find', '.', '-exec', 'touch', '-t', CONTAINER_DATE, '{}', ';'])
954 subprocess.call(['chmod', '-R', CONTAINER_MODE, '../'+Patch])
956 print 'Update Intermediate Archive'
957 #subprocess.call(['zip','-ryX', b_apk, '*'])
958 subprocess.call(['zip','-ryX', b_apk] + glob.glob('*'))
960 #print('Apply Path completed - Now create diff for this and place in patch folder')
962 print('Patch Applied, Create Final Diff - %s %s' % (g_output_dir+'/'+b_apk,new_apk))
963 patchName = ('New'+'_%s'+DIFF_SUFFIX) % (b_apk)
964 patchLoc = '%s/%s' % (g_output_dir, patchName)
966 subprocess.call([DIFF_UTIL, Patch+'/'+b_apk ,new_apk,patchLoc])
968 #Only on HOST... for testing
969 if TEST_MODE == 'TRUE':
970 UpgradedName = '%s_Upgraded' % (b_apk)
971 subprocess.call([DIFFPATCH_UTIL,Patch+'/'+b_apk,UpgradedName,patchLoc])
973 #This is file only with NEWS and empty diffs and same files.
974 if TEST_MODE == 'FALSE':
975 os.remove(g_output_dir+'/'+b_apk)
976 os.rename(g_output_dir+'/temp', g_output_dir+'/'+b_apk)
980 return (info.external_attr >> 16) == 0120777
982 def NewFiles(src, dest):
984 subprocess.call(['cp','-rp', src,dest])
986 #shutil.copytree(src, dest)
987 #except OSError as e:
988 # If the error was caused because the source wasn't a directory
989 #if e.errno == errno.ENOTDIR:
990 #shutil.copy2(src, dest)
992 #print('Directory not copied. Error: %s' % e)
994 def measure_two_filediffs(src, dst):
995 patchLoc = 'temp.patch'
996 subprocess.call([DIFF_UTIL,src,dst,patchLoc])
997 result_size = os.path.getsize(patchLoc)
1001 def Get_Files(path):
1005 for root, directories, filenames in os.walk(path, topdown=False, followlinks=False):
1006 for directory in directories:
1007 #DirName = os.path.join(root+'/',directory)
1008 DirName = os.path.join(root,directory)
1009 if os.path.islink(DirName):
1010 logging.debug('This is symlink pointing to dir -%s' % DirName)
1011 all_files.append(os.path.relpath(DirName, path))
1012 elif not os.listdir(DirName):
1013 #print('*****Empty Directory******* -%s', DirName)
1014 #This should NOT be appended ??? Empty dir shd b considered
1015 all_dirs.append(os.path.relpath(DirName, path))
1017 all_dirs.append(os.path.relpath(DirName, path))
1018 for filename in filenames:
1019 FileName = os.path.join(root,filename)
1020 all_files.append(os.path.relpath(FileName, path))
1024 return all_files, all_dirs
1027 USAGE_DOCSTRING = """
1028 Generate Delta using BASEOLD AND BASE NEW
1029 Attributes is optional
1030 Usage: CreatePatch.py UPDATE_TYPE PARTNAME OLDBASE NEWBASE OUTFOLDER
1033 def Usage(docstring):
1034 print docstring.rstrip("\n")
1035 print COMMON_DOCSTRING
1039 if __name__ == '__main__':