Handle symlinks when extracting zipfiles

python3.11's zipfile implementation does not handle symlinks. This
causes important symlinks in ramdisk to be broken, and later causing a
boo failure.

Test: unzip a target files with symlinks, make sure symlinks are created
Bug: 287896098

Change-Id: Ia7d6ac8ffb03807680a36ff648aa11afafb7f481
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index e29af60..49ef84d 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -34,6 +34,7 @@
 import shutil
 import subprocess
 import sys
+import stat
 import tempfile
 import threading
 import time
@@ -2025,6 +2026,26 @@
     shutil.copyfileobj(in_file, out_file)
 
 
+def UnzipSingleFile(input_zip: zipfile.ZipFile, info: zipfile.ZipInfo, dirname: str):
+  # According to https://stackoverflow.com/questions/434641/how-do-i-set-permissions-attributes-on-a-file-in-a-zip-file-using-pythons-zip/6297838#6297838
+  # higher bits of |external_attr| are unix file permission and types
+  unix_filetype = info.external_attr >> 16
+
+  def CheckMask(a, mask):
+    return (a & mask) == mask
+
+  def IsSymlink(a):
+    return CheckMask(a, stat.S_IFLNK)
+  # python3.11 zipfile implementation doesn't handle symlink correctly
+  if not IsSymlink(unix_filetype):
+    return input_zip.extract(info, dirname)
+  if dirname is None:
+    dirname = os.getcwd()
+  target = os.path.join(dirname, info.filename)
+  os.makedirs(os.path.dirname(target), exist_ok=True)
+  os.symlink(input_zip.read(info).decode(), target)
+
+
 def UnzipToDir(filename, dirname, patterns=None):
   """Unzips the archive to the given directory.
 
@@ -2070,9 +2091,11 @@
       # There isn't any matching files. Don't unzip anything.
       if not filtered:
         return
-      input_zip.extractall(dirname, filtered)
+      for info in filtered:
+        UnzipSingleFile(input_zip, info, dirname)
     else:
-      input_zip.extractall(dirname, entries)
+      for info in entries:
+        UnzipSingleFile(input_zip, info, dirname)
 
 
 def UnzipTemp(filename, patterns=None):