Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / tools / swarming_client / utils / file_path.py
1 # Copyright 2013 The Swarming Authors. All rights reserved.
2 # Use of this source code is governed under the Apache License, Version 2.0 that
3 # can be found in the LICENSE file.
4
5 """Provides functions: get_native_path_case(), isabs() and safe_join().
6
7 This module assumes that filesystem is not changing while current process
8 is running and thus it caches results of functions that depend on FS state.
9 """
10
11 import ctypes
12 import logging
13 import os
14 import posixpath
15 import re
16 import shlex
17 import shutil
18 import stat
19 import sys
20 import unicodedata
21 import time
22
23 from utils import tools
24
25
26 # Types of action accepted by link_file().
27 HARDLINK, HARDLINK_WITH_FALLBACK, SYMLINK, COPY = range(1, 5)
28
29
30 ## OS-specific imports
31
32 if sys.platform == 'win32':
33   from ctypes.wintypes import create_unicode_buffer
34   from ctypes.wintypes import windll, FormatError  # pylint: disable=E0611
35   from ctypes.wintypes import GetLastError  # pylint: disable=E0611
36 elif sys.platform == 'darwin':
37   import Carbon.File  #  pylint: disable=F0401
38   import MacOS  # pylint: disable=F0401
39
40
41 if sys.platform == 'win32':
42   def QueryDosDevice(drive_letter):
43     """Returns the Windows 'native' path for a DOS drive letter."""
44     assert re.match(r'^[a-zA-Z]:$', drive_letter), drive_letter
45     assert isinstance(drive_letter, unicode)
46     # Guesswork. QueryDosDeviceW never returns the required number of bytes.
47     chars = 1024
48     drive_letter = drive_letter
49     p = create_unicode_buffer(chars)
50     if 0 == windll.kernel32.QueryDosDeviceW(drive_letter, p, chars):
51       err = GetLastError()
52       if err:
53         # pylint: disable=E0602
54         msg = u'QueryDosDevice(%s): %s (%d)' % (
55               drive_letter, FormatError(err), err)
56         raise WindowsError(err, msg.encode('utf-8'))
57     return p.value
58
59
60   def GetShortPathName(long_path):
61     """Returns the Windows short path equivalent for a 'long' path."""
62     assert isinstance(long_path, unicode), repr(long_path)
63     # Adds '\\\\?\\' when given an absolute path so the MAX_PATH (260) limit is
64     # not enforced.
65     if os.path.isabs(long_path) and not long_path.startswith('\\\\?\\'):
66       long_path = '\\\\?\\' + long_path
67     chars = windll.kernel32.GetShortPathNameW(long_path, None, 0)
68     if chars:
69       p = create_unicode_buffer(chars)
70       if windll.kernel32.GetShortPathNameW(long_path, p, chars):
71         return p.value
72
73     err = GetLastError()
74     if err:
75       # pylint: disable=E0602
76       msg = u'GetShortPathName(%s): %s (%d)' % (
77             long_path, FormatError(err), err)
78       raise WindowsError(err, msg.encode('utf-8'))
79
80
81   def GetLongPathName(short_path):
82     """Returns the Windows long path equivalent for a 'short' path."""
83     assert isinstance(short_path, unicode)
84     # Adds '\\\\?\\' when given an absolute path so the MAX_PATH (260) limit is
85     # not enforced.
86     if os.path.isabs(short_path) and not short_path.startswith('\\\\?\\'):
87       short_path = '\\\\?\\' + short_path
88     chars = windll.kernel32.GetLongPathNameW(short_path, None, 0)
89     if chars:
90       p = create_unicode_buffer(chars)
91       if windll.kernel32.GetLongPathNameW(short_path, p, chars):
92         return p.value
93
94     err = GetLastError()
95     if err:
96       # pylint: disable=E0602
97       msg = u'GetLongPathName(%s): %s (%d)' % (
98             short_path, FormatError(err), err)
99       raise WindowsError(err, msg.encode('utf-8'))
100
101
102   class DosDriveMap(object):
103     """Maps \Device\HarddiskVolumeN to N: on Windows."""
104     # Keep one global cache.
105     _MAPPING = {}
106
107     def __init__(self):
108       """Lazy loads the cache."""
109       if not self._MAPPING:
110         # This is related to UNC resolver on windows. Ignore that.
111         self._MAPPING[u'\\Device\\Mup'] = None
112         self._MAPPING[u'\\SystemRoot'] = os.environ[u'SystemRoot']
113
114         for letter in (chr(l) for l in xrange(ord('C'), ord('Z')+1)):
115           try:
116             letter = u'%s:' % letter
117             mapped = QueryDosDevice(letter)
118             if mapped in self._MAPPING:
119               logging.warn(
120                   ('Two drives: \'%s\' and \'%s\', are mapped to the same disk'
121                    '. Drive letters are a user-mode concept and the kernel '
122                    'traces only have NT path, so all accesses will be '
123                    'associated with the first drive letter, independent of the '
124                    'actual letter used by the code') % (
125                      self._MAPPING[mapped], letter))
126             else:
127               self._MAPPING[mapped] = letter
128           except WindowsError:  # pylint: disable=E0602
129             pass
130
131     def to_win32(self, path):
132       """Converts a native NT path to Win32/DOS compatible path."""
133       match = re.match(r'(^\\Device\\[a-zA-Z0-9]+)(\\.*)?$', path)
134       if not match:
135         raise ValueError(
136             'Can\'t convert %s into a Win32 compatible path' % path,
137             path)
138       if not match.group(1) in self._MAPPING:
139         # Unmapped partitions may be accessed by windows for the
140         # fun of it while the test is running. Discard these.
141         return None
142       drive = self._MAPPING[match.group(1)]
143       if not drive or not match.group(2):
144         return drive
145       return drive + match.group(2)
146
147
148   def isabs(path):
149     """Accepts X: as an absolute path, unlike python's os.path.isabs()."""
150     return os.path.isabs(path) or len(path) == 2 and path[1] == ':'
151
152
153   def find_item_native_case(root, item):
154     """Gets the native path case of a single item based at root_path."""
155     if item == '..':
156       return item
157
158     root = get_native_path_case(root)
159     return os.path.basename(get_native_path_case(os.path.join(root, item)))
160
161
162   @tools.profile
163   @tools.cached
164   def get_native_path_case(p):
165     """Returns the native path case for an existing file.
166
167     On Windows, removes any leading '\\?\'.
168     """
169     assert isinstance(p, unicode), repr(p)
170     if not isabs(p):
171       raise ValueError(
172           'get_native_path_case(%r): Require an absolute path' % p, p)
173
174     # Make sure it is normalized to os.path.sep. Do not do it here to keep the
175     # function fast
176     assert '/' not in p, p
177     suffix = ''
178     count = p.count(':')
179     if count > 1:
180       # This means it has an alternate-data stream. There could be 3 ':', since
181       # it could be the $DATA datastream of an ADS. Split the whole ADS suffix
182       # off and add it back afterward. There is no way to know the native path
183       # case of an alternate data stream.
184       items = p.split(':')
185       p = ':'.join(items[0:2])
186       suffix = ''.join(':' + i for i in items[2:])
187
188     # TODO(maruel): Use os.path.normpath?
189     if p.endswith('.\\'):
190       p = p[:-2]
191
192     # Windows used to have an option to turn on case sensitivity on non Win32
193     # subsystem but that's out of scope here and isn't supported anymore.
194     # Go figure why GetShortPathName() is needed.
195     try:
196       out = GetLongPathName(GetShortPathName(p))
197     except OSError, e:
198       if e.args[0] in (2, 3, 5):
199         # The path does not exist. Try to recurse and reconstruct the path.
200         base = os.path.dirname(p)
201         rest = os.path.basename(p)
202         return os.path.join(get_native_path_case(base), rest)
203       raise
204     if out.startswith('\\\\?\\'):
205       out = out[4:]
206     # Always upper case the first letter since GetLongPathName() will return the
207     # drive letter in the case it was given.
208     return out[0].upper() + out[1:] + suffix
209
210
211   def enum_processes_win():
212     """Returns all processes on the system that are accessible to this process.
213
214     Returns:
215       Win32_Process COM objects. See
216       http://msdn.microsoft.com/library/aa394372.aspx for more details.
217     """
218     import win32com.client  # pylint: disable=F0401
219     wmi_service = win32com.client.Dispatch('WbemScripting.SWbemLocator')
220     wbem = wmi_service.ConnectServer('.', 'root\\cimv2')
221     return [proc for proc in wbem.ExecQuery('SELECT * FROM Win32_Process')]
222
223
224   def filter_processes_dir_win(processes, root_dir):
225     """Returns all processes which has their main executable located inside
226     root_dir.
227     """
228     def normalize_path(filename):
229       try:
230         return GetLongPathName(unicode(filename)).lower()
231       except:  # pylint: disable=W0702
232         return unicode(filename).lower()
233
234     root_dir = normalize_path(root_dir)
235
236     def process_name(proc):
237       if proc.ExecutablePath:
238         return normalize_path(proc.ExecutablePath)
239       # proc.ExecutablePath may be empty if the process hasn't finished
240       # initializing, but the command line may be valid.
241       if proc.CommandLine is None:
242         return None
243       parsed_line = shlex.split(proc.CommandLine)
244       if len(parsed_line) >= 1 and os.path.isabs(parsed_line[0]):
245         return normalize_path(parsed_line[0])
246       return None
247
248     long_names = ((process_name(proc), proc) for proc in processes)
249
250     return [
251       proc for name, proc in long_names
252       if name is not None and name.startswith(root_dir)
253     ]
254
255
256   def filter_processes_tree_win(processes):
257     """Returns all the processes under the current process."""
258     # Convert to dict.
259     processes = {p.ProcessId: p for p in processes}
260     root_pid = os.getpid()
261     out = {root_pid: processes[root_pid]}
262     while True:
263       found = set()
264       for pid in out:
265         found.update(
266             p.ProcessId for p in processes.itervalues()
267             if p.ParentProcessId == pid)
268       found -= set(out)
269       if not found:
270         break
271       out.update((p, processes[p]) for p in found)
272     return out.values()
273
274 elif sys.platform == 'darwin':
275
276
277   # On non-windows, keep the stdlib behavior.
278   isabs = os.path.isabs
279
280
281   def _native_case(p):
282     """Gets the native path case. Warning: this function resolves symlinks."""
283     try:
284       rel_ref, _ = Carbon.File.FSPathMakeRef(p.encode('utf-8'))
285       # The OSX underlying code uses NFD but python strings are in NFC. This
286       # will cause issues with os.listdir() for example. Since the dtrace log
287       # *is* in NFC, normalize it here.
288       out = unicodedata.normalize(
289           'NFC', rel_ref.FSRefMakePath().decode('utf-8'))
290       if p.endswith(os.path.sep) and not out.endswith(os.path.sep):
291         return out + os.path.sep
292       return out
293     except MacOS.Error, e:
294       if e.args[0] in (-43, -120):
295         # The path does not exist. Try to recurse and reconstruct the path.
296         # -43 means file not found.
297         # -120 means directory not found.
298         base = os.path.dirname(p)
299         rest = os.path.basename(p)
300         return os.path.join(_native_case(base), rest)
301       raise OSError(
302           e.args[0], 'Failed to get native path for %s' % p, p, e.args[1])
303
304
305   def _split_at_symlink_native(base_path, rest):
306     """Returns the native path for a symlink."""
307     base, symlink, rest = split_at_symlink(base_path, rest)
308     if symlink:
309       if not base_path:
310         base_path = base
311       else:
312         base_path = safe_join(base_path, base)
313       symlink = find_item_native_case(base_path, symlink)
314     return base, symlink, rest
315
316
317   def find_item_native_case(root_path, item):
318     """Gets the native path case of a single item based at root_path.
319
320     There is no API to get the native path case of symlinks on OSX. So it
321     needs to be done the slow way.
322     """
323     if item == '..':
324       return item
325
326     item = item.lower()
327     for element in listdir(root_path):
328       if element.lower() == item:
329         return element
330
331
332   @tools.profile
333   @tools.cached
334   def get_native_path_case(path):
335     """Returns the native path case for an existing file.
336
337     Technically, it's only HFS+ on OSX that is case preserving and
338     insensitive. It's the default setting on HFS+ but can be changed.
339     """
340     assert isinstance(path, unicode), repr(path)
341     if not isabs(path):
342       raise ValueError(
343           'get_native_path_case(%r): Require an absolute path' % path, path)
344     if path.startswith('/dev'):
345       # /dev is not visible from Carbon, causing an exception.
346       return path
347
348     # Starts assuming there is no symlink along the path.
349     resolved = _native_case(path)
350     if path.lower() in (resolved.lower(), resolved.lower() + './'):
351       # This code path is incredibly faster.
352       logging.debug('get_native_path_case(%s) = %s' % (path, resolved))
353       return resolved
354
355     # There was a symlink, process it.
356     base, symlink, rest = _split_at_symlink_native(None, path)
357     if not symlink:
358       # TODO(maruel): This can happen on OSX because we use stale APIs on OSX.
359       # Fixing the APIs usage will likely fix this bug. The bug occurs due to
360       # hardlinked files, where the API may return one file path or the other
361       # depending on how it feels.
362       return base
363     prev = base
364     base = safe_join(_native_case(base), symlink)
365     assert len(base) > len(prev)
366     while rest:
367       prev = base
368       relbase, symlink, rest = _split_at_symlink_native(base, rest)
369       base = safe_join(base, relbase)
370       assert len(base) > len(prev), (prev, base, symlink)
371       if symlink:
372         base = safe_join(base, symlink)
373       assert len(base) > len(prev), (prev, base, symlink)
374     # Make sure no symlink was resolved.
375     assert base.lower() == path.lower(), (base, path)
376     logging.debug('get_native_path_case(%s) = %s' % (path, base))
377     return base
378
379
380 else:  # OSes other than Windows and OSX.
381
382
383   # On non-windows, keep the stdlib behavior.
384   isabs = os.path.isabs
385
386
387   def find_item_native_case(root, item):
388     """Gets the native path case of a single item based at root_path."""
389     if item == '..':
390       return item
391
392     root = get_native_path_case(root)
393     return os.path.basename(get_native_path_case(os.path.join(root, item)))
394
395
396   @tools.profile
397   @tools.cached
398   def get_native_path_case(path):
399     """Returns the native path case for an existing file.
400
401     On OSes other than OSX and Windows, assume the file system is
402     case-sensitive.
403
404     TODO(maruel): This is not strictly true. Implement if necessary.
405     """
406     assert isinstance(path, unicode), repr(path)
407     if not isabs(path):
408       raise ValueError(
409           'get_native_path_case(%r): Require an absolute path' % path, path)
410     # Give up on cygwin, as GetLongPathName() can't be called.
411     # Linux traces tends to not be normalized so use this occasion to normalize
412     # it. This function implementation already normalizes the path on the other
413     # OS so this needs to be done here to be coherent between OSes.
414     out = os.path.normpath(path)
415     if path.endswith(os.path.sep) and not out.endswith(os.path.sep):
416       out = out + os.path.sep
417     # In 99.99% of cases on Linux out == path. Since a return value is cached
418     # forever, reuse (also cached) |path| object. It safes approx 7MB of ram
419     # when isolating Chromium tests. It's important on memory constrained
420     # systems running ARM.
421     return path if out == path else out
422
423
424 if sys.platform != 'win32':  # All non-Windows OSes.
425
426
427   def safe_join(*args):
428     """Joins path elements like os.path.join() but doesn't abort on absolute
429     path.
430
431     os.path.join('foo', '/bar') == '/bar'
432     but safe_join('foo', '/bar') == 'foo/bar'.
433     """
434     out = ''
435     for element in args:
436       if element.startswith(os.path.sep):
437         if out.endswith(os.path.sep):
438           out += element[1:]
439         else:
440           out += element
441       else:
442         if out.endswith(os.path.sep):
443           out += element
444         else:
445           out += os.path.sep + element
446     return out
447
448
449   @tools.profile
450   def split_at_symlink(base_dir, relfile):
451     """Scans each component of relfile and cut the string at the symlink if
452     there is any.
453
454     Returns a tuple (base_path, symlink, rest), with symlink == rest == None if
455     not symlink was found.
456     """
457     if base_dir:
458       assert relfile
459       assert os.path.isabs(base_dir)
460       index = 0
461     else:
462       assert os.path.isabs(relfile)
463       index = 1
464
465     def at_root(rest):
466       if base_dir:
467         return safe_join(base_dir, rest)
468       return rest
469
470     while True:
471       try:
472         index = relfile.index(os.path.sep, index)
473       except ValueError:
474         index = len(relfile)
475       full = at_root(relfile[:index])
476       if os.path.islink(full):
477         # A symlink!
478         base = os.path.dirname(relfile[:index])
479         symlink = os.path.basename(relfile[:index])
480         rest = relfile[index:]
481         logging.debug(
482             'split_at_symlink(%s, %s) -> (%s, %s, %s)' %
483             (base_dir, relfile, base, symlink, rest))
484         return base, symlink, rest
485       if index == len(relfile):
486         break
487       index += 1
488     return relfile, None, None
489
490
491 @tools.profile
492 @tools.cached
493 def listdir(abspath):
494   """Lists a directory given an absolute path to it."""
495   if not isabs(abspath):
496     raise ValueError(
497         'list_dir(%r): Require an absolute path' % abspath, abspath)
498   return os.listdir(abspath)
499
500
501 def relpath(path, root):
502   """os.path.relpath() that keeps trailing os.path.sep."""
503   out = os.path.relpath(path, root)
504   if path.endswith(os.path.sep):
505     out += os.path.sep
506   return out
507
508
509 def safe_relpath(filepath, basepath):
510   """Do not throw on Windows when filepath and basepath are on different drives.
511
512   Different than relpath() above since this one doesn't keep the trailing
513   os.path.sep and it swallows exceptions on Windows and return the original
514   absolute path in the case of different drives.
515   """
516   try:
517     return os.path.relpath(filepath, basepath)
518   except ValueError:
519     assert sys.platform == 'win32'
520     return filepath
521
522
523 def normpath(path):
524   """os.path.normpath() that keeps trailing os.path.sep."""
525   out = os.path.normpath(path)
526   if path.endswith(os.path.sep):
527     out += os.path.sep
528   return out
529
530
531 def posix_relpath(path, root):
532   """posix.relpath() that keeps trailing slash.
533
534   It is different from relpath() since it can be used on Windows.
535   """
536   out = posixpath.relpath(path, root)
537   if path.endswith('/'):
538     out += '/'
539   return out
540
541
542 def cleanup_path(x):
543   """Cleans up a relative path. Converts any os.path.sep to '/' on Windows."""
544   if x:
545     x = x.rstrip(os.path.sep).replace(os.path.sep, '/')
546   if x == '.':
547     x = ''
548   if x:
549     x += '/'
550   return x
551
552
553 def is_url(path):
554   """Returns True if it looks like an HTTP url instead of a file path."""
555   return bool(re.match(r'^https?://.+$', path))
556
557
558 def path_starts_with(prefix, path):
559   """Returns true if the components of the path |prefix| are the same as the
560   initial components of |path| (or all of the components of |path|). The paths
561   must be absolute.
562   """
563   assert os.path.isabs(prefix) and os.path.isabs(path)
564   prefix = os.path.normpath(prefix)
565   path = os.path.normpath(path)
566   assert prefix == get_native_path_case(prefix), prefix
567   assert path == get_native_path_case(path), path
568   prefix = prefix.rstrip(os.path.sep) + os.path.sep
569   path = path.rstrip(os.path.sep) + os.path.sep
570   return path.startswith(prefix)
571
572
573 @tools.profile
574 def fix_native_path_case(root, path):
575   """Ensures that each component of |path| has the proper native case.
576
577   It does so by iterating slowly over the directory elements of |path|. The file
578   must exist.
579   """
580   native_case_path = root
581   for raw_part in path.split(os.sep):
582     if not raw_part or raw_part == '.':
583       break
584
585     part = find_item_native_case(native_case_path, raw_part)
586     if not part:
587       raise OSError(
588           'File %s doesn\'t exist' %
589           os.path.join(native_case_path, raw_part))
590     native_case_path = os.path.join(native_case_path, part)
591
592   return os.path.normpath(native_case_path)
593
594
595 def ensure_command_has_abs_path(command, cwd):
596   """Ensures that an isolate command uses absolute path.
597
598   This is needed since isolate can specify a command relative to 'cwd' and
599   subprocess.call doesn't consider 'cwd' when searching for executable.
600   """
601   if not os.path.isabs(command[0]):
602     command[0] = os.path.abspath(os.path.join(cwd, command[0]))
603
604
605 def is_same_filesystem(path1, path2):
606   """Returns True if both paths are on the same filesystem.
607
608   This is required to enable the use of hardlinks.
609   """
610   assert os.path.isabs(path1), path1
611   assert os.path.isabs(path2), path2
612   if sys.platform == 'win32':
613     # If the drive letter mismatches, assume it's a separate partition.
614     # TODO(maruel): It should look at the underlying drive, a drive letter could
615     # be a mount point to a directory on another drive.
616     assert re.match(r'^[a-zA-Z]\:\\.*', path1), path1
617     assert re.match(r'^[a-zA-Z]\:\\.*', path2), path2
618     if path1[0].lower() != path2[0].lower():
619       return False
620   return os.stat(path1).st_dev == os.stat(path2).st_dev
621
622
623 def get_free_space(path):
624   """Returns the number of free bytes."""
625   if sys.platform == 'win32':
626     free_bytes = ctypes.c_ulonglong(0)
627     ctypes.windll.kernel32.GetDiskFreeSpaceExW(
628         ctypes.c_wchar_p(path), None, None, ctypes.pointer(free_bytes))
629     return free_bytes.value
630   # For OSes other than Windows.
631   f = os.statvfs(path)  # pylint: disable=E1101
632   return f.f_bfree * f.f_frsize
633
634
635 ### Write file functions.
636
637
638 def hardlink(source, link_name):
639   """Hardlinks a file.
640
641   Add support for os.link() on Windows.
642   """
643   if sys.platform == 'win32':
644     if not ctypes.windll.kernel32.CreateHardLinkW(
645         unicode(link_name), unicode(source), 0):
646       raise OSError()
647   else:
648     os.link(source, link_name)
649
650
651 def readable_copy(outfile, infile):
652   """Makes a copy of the file that is readable by everyone."""
653   shutil.copy2(infile, outfile)
654   read_enabled_mode = (os.stat(outfile).st_mode | stat.S_IRUSR |
655                        stat.S_IRGRP | stat.S_IROTH)
656   os.chmod(outfile, read_enabled_mode)
657
658
659 def set_read_only(path, read_only):
660   """Sets or resets the write bit on a file or directory.
661
662   Zaps out access to 'group' and 'others'.
663   """
664   assert isinstance(read_only, bool), read_only
665   mode = os.lstat(path).st_mode
666   # TODO(maruel): Stop removing GO bits.
667   if read_only:
668     mode = mode & 0500
669   else:
670     mode = mode | 0200
671   if hasattr(os, 'lchmod'):
672     os.lchmod(path, mode)  # pylint: disable=E1101
673   else:
674     if stat.S_ISLNK(mode):
675       # Skip symlink without lchmod() support.
676       logging.debug(
677           'Can\'t change %sw bit on symlink %s',
678           '-' if read_only else '+', path)
679       return
680
681     # TODO(maruel): Implement proper DACL modification on Windows.
682     os.chmod(path, mode)
683
684
685 def try_remove(filepath):
686   """Removes a file without crashing even if it doesn't exist."""
687   try:
688     # TODO(maruel): Not do it unless necessary since it slows this function
689     # down.
690     if sys.platform == 'win32':
691       # Deleting a read-only file will fail if it is read-only.
692       set_read_only(filepath, False)
693     else:
694       # Deleting a read-only file will fail if the directory is read-only.
695       set_read_only(os.path.dirname(filepath), False)
696     os.remove(filepath)
697   except OSError:
698     pass
699
700
701 def link_file(outfile, infile, action):
702   """Links a file. The type of link depends on |action|."""
703   if action not in (HARDLINK, HARDLINK_WITH_FALLBACK, SYMLINK, COPY):
704     raise ValueError('Unknown mapping action %s' % action)
705   if not os.path.isfile(infile):
706     raise OSError('%s is missing' % infile)
707   if os.path.isfile(outfile):
708     raise OSError(
709         '%s already exist; insize:%d; outsize:%d' %
710         (outfile, os.stat(infile).st_size, os.stat(outfile).st_size))
711
712   if action == COPY:
713     readable_copy(outfile, infile)
714   elif action == SYMLINK and sys.platform != 'win32':
715     # On windows, symlink are converted to hardlink and fails over to copy.
716     os.symlink(infile, outfile)  # pylint: disable=E1101
717   else:
718     # HARDLINK or HARDLINK_WITH_FALLBACK.
719     try:
720       hardlink(infile, outfile)
721     except OSError as e:
722       if action == HARDLINK:
723         raise OSError('Failed to hardlink %s to %s: %s' % (infile, outfile, e))
724       # Probably a different file system.
725       logging.warning(
726           'Failed to hardlink, failing back to copy %s to %s' % (
727             infile, outfile))
728       readable_copy(outfile, infile)
729
730
731 ### Write directory functions.
732
733
734 def make_tree_read_only(root):
735   """Makes all the files in the directories read only.
736
737   Also makes the directories read only, only if it makes sense on the platform.
738
739   This means no file can be created or deleted.
740   """
741   logging.debug('make_tree_read_only(%s)', root)
742   assert os.path.isabs(root), root
743   for dirpath, dirnames, filenames in os.walk(root, topdown=True):
744     for filename in filenames:
745       set_read_only(os.path.join(dirpath, filename), True)
746     if sys.platform != 'win32':
747       # It must not be done on Windows.
748       for dirname in dirnames:
749         set_read_only(os.path.join(dirpath, dirname), True)
750   if sys.platform != 'win32':
751     set_read_only(root, True)
752
753
754 def make_tree_files_read_only(root):
755   """Makes all the files in the directories read only but not the directories
756   themselves.
757
758   This means files can be created or deleted.
759   """
760   logging.debug('make_tree_files_read_only(%s)', root)
761   assert os.path.isabs(root), root
762   if sys.platform != 'win32':
763     set_read_only(root, False)
764   for dirpath, dirnames, filenames in os.walk(root, topdown=True):
765     for filename in filenames:
766       set_read_only(os.path.join(dirpath, filename), True)
767     if sys.platform != 'win32':
768       # It must not be done on Windows.
769       for dirname in dirnames:
770         set_read_only(os.path.join(dirpath, dirname), False)
771
772
773 def make_tree_writeable(root):
774   """Makes all the files in the directories writeable.
775
776   Also makes the directories writeable, only if it makes sense on the platform.
777
778   It is different from make_tree_deleteable() because it unconditionally affects
779   the files.
780   """
781   logging.debug('make_tree_writeable(%s)', root)
782   assert os.path.isabs(root), root
783   if sys.platform != 'win32':
784     set_read_only(root, False)
785   for dirpath, dirnames, filenames in os.walk(root, topdown=True):
786     for filename in filenames:
787       set_read_only(os.path.join(dirpath, filename), False)
788     if sys.platform != 'win32':
789       # It must not be done on Windows.
790       for dirname in dirnames:
791         set_read_only(os.path.join(dirpath, dirname), False)
792
793
794 def make_tree_deleteable(root):
795   """Changes the appropriate permissions so the files in the directories can be
796   deleted.
797
798   On Windows, the files are modified. On other platforms, modify the directory.
799   It only does the minimum so the files can be deleted safely.
800
801   Warning on Windows: since file permission is modified, the file node is
802   modified. This means that for hard-linked files, every directory entry for the
803   file node has its file permission modified.
804   """
805   logging.debug('make_tree_deleteable(%s)', root)
806   assert os.path.isabs(root), root
807   if sys.platform != 'win32':
808     set_read_only(root, False)
809   for dirpath, dirnames, filenames in os.walk(root, topdown=True):
810     if sys.platform == 'win32':
811       for filename in filenames:
812         set_read_only(os.path.join(dirpath, filename), False)
813     else:
814       for dirname in dirnames:
815         set_read_only(os.path.join(dirpath, dirname), False)
816
817
818 def rmtree(root):
819   """Wrapper around shutil.rmtree() to retry automatically on Windows.
820
821   On Windows, forcibly kills processes that are found to interfere with the
822   deletion.
823
824   Returns:
825     True on normal execution, False if berserk techniques (like killing
826     processes) had to be used.
827   """
828   make_tree_deleteable(root)
829   logging.info('rmtree(%s)', root)
830   if sys.platform != 'win32':
831     shutil.rmtree(root)
832     return True
833
834   # Windows is more 'challenging'. First tries the soft way: tries 3 times to
835   # delete and sleep a bit in between.
836   max_tries = 3
837   for i in xrange(max_tries):
838     # errors is a list of tuple(function, path, excinfo).
839     errors = []
840     shutil.rmtree(root, onerror=lambda *args: errors.append(args))
841     if not errors:
842       return True
843     if i == max_tries - 1:
844       sys.stderr.write(
845           'Failed to delete %s. The following files remain:\n' % root)
846       for _, path, _ in errors:
847         sys.stderr.write('- %s\n' % path)
848     else:
849       delay = (i+1)*2
850       sys.stderr.write(
851           'Failed to delete %s (%d files remaining).\n'
852           '  Maybe the test has a subprocess outliving it.\n'
853           '  Sleeping %d seconds.\n' %
854           (root, len(errors), delay))
855       time.sleep(delay)
856
857   # The soft way was not good enough. Try the hard way. Enumerates both:
858   # - all child processes from this process.
859   # - processes where the main executable in inside 'root'. The reason is that
860   #   the ancestry may be broken so stray grand-children processes could be
861   #   undetected by the first technique.
862   # This technique is not fool-proof but gets mostly there.
863   def get_processes():
864     processes = enum_processes_win()
865     tree_processes = filter_processes_tree_win(processes)
866     dir_processes = filter_processes_dir_win(processes, root)
867     # Convert to dict to remove duplicates.
868     processes = {p.ProcessId: p for p in tree_processes}
869     processes.update((p.ProcessId, p) for p in dir_processes)
870     processes.pop(os.getpid())
871     return processes
872
873   for i in xrange(3):
874     sys.stderr.write('Enumerating processes:\n')
875     processes = get_processes()
876     if not processes:
877       break
878     for _, proc in sorted(processes.iteritems()):
879       sys.stderr.write(
880           '- pid %d; Handles: %d; Exe: %s; Cmd: %s\n' % (
881             proc.ProcessId,
882             proc.HandleCount,
883             proc.ExecutablePath,
884             proc.CommandLine))
885     sys.stderr.write('Terminating %d processes.\n' % len(processes))
886     for pid in sorted(processes):
887       try:
888         # Killing is asynchronous.
889         os.kill(pid, 9)
890         sys.stderr.write('- %d killed\n' % pid)
891       except OSError:
892         sys.stderr.write('- failed to kill %s\n' % pid)
893     if i < 2:
894       time.sleep((i+1)*2)
895   else:
896     processes = get_processes()
897     if processes:
898       sys.stderr.write('Failed to terminate processes.\n')
899       raise errors[0][2][0], errors[0][2][1], errors[0][2][2]
900
901   # Now that annoying processes in root are evicted, try again.
902   errors = []
903   shutil.rmtree(root, onerror=lambda *args: errors.append(args))
904   if errors:
905     # There's no hope.
906     sys.stderr.write(
907         'Failed to delete %s. The following files remain:\n' % root)
908     for _, path, _ in errors:
909       sys.stderr.write('- %s\n' % path)
910     raise errors[0][2][0], errors[0][2][1], errors[0][2][2]
911   return False