Yet another fix for concurrent calls to expand.exe.

This time, try doing the work in uniquely named temp directories rather than blocking.

BUG=107291
TEST=Watch for build failures while extracting d3dx9_43.dll, D3DCompiler_43.dll, or xinput1_3.dll.

Review URL: http://codereview.chromium.org/8890072

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@114290 0039d316-1c4b-4281-b951-d872f2087c98


CrOS-Libchrome-Original-Commit: c6f0ea410717920f51d896a5080f834df86307e1
diff --git a/build/extract_from_cab.py b/build/extract_from_cab.py
index d04bcfd..d5410d6 100755
--- a/build/extract_from_cab.py
+++ b/build/extract_from_cab.py
@@ -6,29 +6,11 @@
 """Extracts a single file from a CAB archive."""
 
 import os
+import shutil
 import subprocess
 import sys
 import tempfile
 
-lock_file = os.path.join(tempfile.gettempdir(), 'expand.lock')
-
-
-def acquire_lock():
-  while True:
-    try:
-      fd = os.open(lock_file, os.O_CREAT | os.O_EXCL | os.O_RDWR)
-      return fd
-    except OSError as e:
-      if e.errno != errno.EEXIST:
-        raise
-      print 'Cab extraction could not get exclusive lock. Retrying in 100ms...'
-      time.sleep(0.1)
-
-
-def release_lock(fd):
-  os.close(fd)
-  os.unlink(lock_file)
-
 
 def main():
   if len(sys.argv) != 4:
@@ -37,15 +19,30 @@
 
   [cab_path, archived_file, output_dir] = sys.argv[1:]
 
-  lock_fd = acquire_lock()
+  # Expand.exe does its work in a fixed-named temporary directory created within
+  # the given output directory. This is a problem for concurrent extractions, so
+  # create a unique temp dir within the desired output directory to work around
+  # this limitation.
+  temp_dir = tempfile.mkdtemp(dir=output_dir)
+
   try:
     # Invoke the Windows expand utility to extract the file.
     level = subprocess.call(
-        ['expand', cab_path, '-F:' + archived_file, output_dir])
-    if level != 0:
-      return level
+        ['expand', cab_path, '-F:' + archived_file, temp_dir])
+    if level == 0:
+      # Move the output file into place, preserving expand.exe's behavior of
+      # paving over any preexisting file.
+      output_file = os.path.join(output_dir, archived_file)
+      try:
+        os.remove(output_file)
+      except OSError:
+        pass
+      os.rename(os.path.join(temp_dir, archived_file), output_file)
   finally:
-    release_lock(lock_fd)
+    shutil.rmtree(temp_dir, True)
+
+  if level != 0:
+    return level
 
   # The expand utility preserves the modification date and time of the archived
   # file. Touch the extracted file. This helps build systems that compare the