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.
5 """Provides functions: get_native_path_case(), isabs() and safe_join().
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.
23 from utils import tools
26 # Types of action accepted by link_file().
27 HARDLINK, HARDLINK_WITH_FALLBACK, SYMLINK, COPY = range(1, 5)
30 ## OS-specific imports
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
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.
48 drive_letter = drive_letter
49 p = create_unicode_buffer(chars)
50 if 0 == windll.kernel32.QueryDosDeviceW(drive_letter, p, chars):
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'))
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
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)
69 p = create_unicode_buffer(chars)
70 if windll.kernel32.GetShortPathNameW(long_path, p, chars):
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'))
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
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)
90 p = create_unicode_buffer(chars)
91 if windll.kernel32.GetLongPathNameW(short_path, p, chars):
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'))
102 class DosDriveMap(object):
103 """Maps \Device\HarddiskVolumeN to N: on Windows."""
104 # Keep one global cache.
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']
114 for letter in (chr(l) for l in xrange(ord('C'), ord('Z')+1)):
116 letter = u'%s:' % letter
117 mapped = QueryDosDevice(letter)
118 if mapped in self._MAPPING:
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))
127 self._MAPPING[mapped] = letter
128 except WindowsError: # pylint: disable=E0602
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)
136 'Can\'t convert %s into a Win32 compatible path' % 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.
142 drive = self._MAPPING[match.group(1)]
143 if not drive or not match.group(2):
145 return drive + match.group(2)
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] == ':'
153 def find_item_native_case(root, item):
154 """Gets the native path case of a single item based at root_path."""
158 root = get_native_path_case(root)
159 return os.path.basename(get_native_path_case(os.path.join(root, item)))
164 def get_native_path_case(p):
165 """Returns the native path case for an existing file.
167 On Windows, removes any leading '\\?\'.
169 assert isinstance(p, unicode), repr(p)
172 'get_native_path_case(%r): Require an absolute path' % p, p)
174 # Make sure it is normalized to os.path.sep. Do not do it here to keep the
176 assert '/' not in p, p
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.
185 p = ':'.join(items[0:2])
186 suffix = ''.join(':' + i for i in items[2:])
188 # TODO(maruel): Use os.path.normpath?
189 if p.endswith('.\\'):
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.
196 out = GetLongPathName(GetShortPathName(p))
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)
204 if out.startswith('\\\\?\\'):
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
211 def enum_processes_win():
212 """Returns all processes on the system that are accessible to this process.
215 Win32_Process COM objects. See
216 http://msdn.microsoft.com/library/aa394372.aspx for more details.
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')]
224 def filter_processes_dir_win(processes, root_dir):
225 """Returns all processes which has their main executable located inside
228 def normalize_path(filename):
230 return GetLongPathName(unicode(filename)).lower()
231 except: # pylint: disable=W0702
232 return unicode(filename).lower()
234 root_dir = normalize_path(root_dir)
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:
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])
248 long_names = ((process_name(proc), proc) for proc in processes)
251 proc for name, proc in long_names
252 if name is not None and name.startswith(root_dir)
256 def filter_processes_tree_win(processes):
257 """Returns all the processes under the current process."""
259 processes = {p.ProcessId: p for p in processes}
260 root_pid = os.getpid()
261 out = {root_pid: processes[root_pid]}
266 p.ProcessId for p in processes.itervalues()
267 if p.ParentProcessId == pid)
271 out.update((p, processes[p]) for p in found)
274 elif sys.platform == 'darwin':
277 # On non-windows, keep the stdlib behavior.
278 isabs = os.path.isabs
282 """Gets the native path case. Warning: this function resolves symlinks."""
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
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)
302 e.args[0], 'Failed to get native path for %s' % p, p, e.args[1])
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)
312 base_path = safe_join(base_path, base)
313 symlink = find_item_native_case(base_path, symlink)
314 return base, symlink, rest
317 def find_item_native_case(root_path, item):
318 """Gets the native path case of a single item based at root_path.
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.
327 for element in listdir(root_path):
328 if element.lower() == item:
334 def get_native_path_case(path):
335 """Returns the native path case for an existing file.
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.
340 assert isinstance(path, unicode), repr(path)
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.
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))
355 # There was a symlink, process it.
356 base, symlink, rest = _split_at_symlink_native(None, path)
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.
364 base = safe_join(_native_case(base), symlink)
365 assert len(base) > len(prev)
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)
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))
380 else: # OSes other than Windows and OSX.
383 # On non-windows, keep the stdlib behavior.
384 isabs = os.path.isabs
387 def find_item_native_case(root, item):
388 """Gets the native path case of a single item based at root_path."""
392 root = get_native_path_case(root)
393 return os.path.basename(get_native_path_case(os.path.join(root, item)))
398 def get_native_path_case(path):
399 """Returns the native path case for an existing file.
401 On OSes other than OSX and Windows, assume the file system is
404 TODO(maruel): This is not strictly true. Implement if necessary.
406 assert isinstance(path, unicode), repr(path)
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
424 if sys.platform != 'win32': # All non-Windows OSes.
427 def safe_join(*args):
428 """Joins path elements like os.path.join() but doesn't abort on absolute
431 os.path.join('foo', '/bar') == '/bar'
432 but safe_join('foo', '/bar') == 'foo/bar'.
436 if element.startswith(os.path.sep):
437 if out.endswith(os.path.sep):
442 if out.endswith(os.path.sep):
445 out += os.path.sep + element
450 def split_at_symlink(base_dir, relfile):
451 """Scans each component of relfile and cut the string at the symlink if
454 Returns a tuple (base_path, symlink, rest), with symlink == rest == None if
455 not symlink was found.
459 assert os.path.isabs(base_dir)
462 assert os.path.isabs(relfile)
467 return safe_join(base_dir, rest)
472 index = relfile.index(os.path.sep, index)
475 full = at_root(relfile[:index])
476 if os.path.islink(full):
478 base = os.path.dirname(relfile[:index])
479 symlink = os.path.basename(relfile[:index])
480 rest = relfile[index:]
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):
488 return relfile, None, None
493 def listdir(abspath):
494 """Lists a directory given an absolute path to it."""
495 if not isabs(abspath):
497 'list_dir(%r): Require an absolute path' % abspath, abspath)
498 return os.listdir(abspath)
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):
509 def safe_relpath(filepath, basepath):
510 """Do not throw on Windows when filepath and basepath are on different drives.
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.
517 return os.path.relpath(filepath, basepath)
519 assert sys.platform == 'win32'
524 """os.path.normpath() that keeps trailing os.path.sep."""
525 out = os.path.normpath(path)
526 if path.endswith(os.path.sep):
531 def posix_relpath(path, root):
532 """posix.relpath() that keeps trailing slash.
534 It is different from relpath() since it can be used on Windows.
536 out = posixpath.relpath(path, root)
537 if path.endswith('/'):
543 """Cleans up a relative path. Converts any os.path.sep to '/' on Windows."""
545 x = x.rstrip(os.path.sep).replace(os.path.sep, '/')
554 """Returns True if it looks like an HTTP url instead of a file path."""
555 return bool(re.match(r'^https?://.+$', path))
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
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)
574 def fix_native_path_case(root, path):
575 """Ensures that each component of |path| has the proper native case.
577 It does so by iterating slowly over the directory elements of |path|. The file
580 native_case_path = root
581 for raw_part in path.split(os.sep):
582 if not raw_part or raw_part == '.':
585 part = find_item_native_case(native_case_path, raw_part)
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)
592 return os.path.normpath(native_case_path)
595 def ensure_command_has_abs_path(command, cwd):
596 """Ensures that an isolate command uses absolute path.
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.
601 if not os.path.isabs(command[0]):
602 command[0] = os.path.abspath(os.path.join(cwd, command[0]))
605 def is_same_filesystem(path1, path2):
606 """Returns True if both paths are on the same filesystem.
608 This is required to enable the use of hardlinks.
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():
620 return os.stat(path1).st_dev == os.stat(path2).st_dev
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
635 ### Write file functions.
638 def hardlink(source, link_name):
641 Add support for os.link() on Windows.
643 if sys.platform == 'win32':
644 if not ctypes.windll.kernel32.CreateHardLinkW(
645 unicode(link_name), unicode(source), 0):
648 os.link(source, link_name)
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)
659 def set_read_only(path, read_only):
660 """Sets or resets the write bit on a file or directory.
662 Zaps out access to 'group' and 'others'.
664 assert isinstance(read_only, bool), read_only
665 mode = os.lstat(path).st_mode
666 # TODO(maruel): Stop removing GO bits.
671 if hasattr(os, 'lchmod'):
672 os.lchmod(path, mode) # pylint: disable=E1101
674 if stat.S_ISLNK(mode):
675 # Skip symlink without lchmod() support.
677 'Can\'t change %sw bit on symlink %s',
678 '-' if read_only else '+', path)
681 # TODO(maruel): Implement proper DACL modification on Windows.
685 def try_remove(filepath):
686 """Removes a file without crashing even if it doesn't exist."""
688 # TODO(maruel): Not do it unless necessary since it slows this function
690 if sys.platform == 'win32':
691 # Deleting a read-only file will fail if it is read-only.
692 set_read_only(filepath, False)
694 # Deleting a read-only file will fail if the directory is read-only.
695 set_read_only(os.path.dirname(filepath), False)
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):
709 '%s already exist; insize:%d; outsize:%d' %
710 (outfile, os.stat(infile).st_size, os.stat(outfile).st_size))
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
718 # HARDLINK or HARDLINK_WITH_FALLBACK.
720 hardlink(infile, outfile)
722 if action == HARDLINK:
723 raise OSError('Failed to hardlink %s to %s: %s' % (infile, outfile, e))
724 # Probably a different file system.
726 'Failed to hardlink, failing back to copy %s to %s' % (
728 readable_copy(outfile, infile)
731 ### Write directory functions.
734 def make_tree_read_only(root):
735 """Makes all the files in the directories read only.
737 Also makes the directories read only, only if it makes sense on the platform.
739 This means no file can be created or deleted.
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)
754 def make_tree_files_read_only(root):
755 """Makes all the files in the directories read only but not the directories
758 This means files can be created or deleted.
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)
773 def make_tree_writeable(root):
774 """Makes all the files in the directories writeable.
776 Also makes the directories writeable, only if it makes sense on the platform.
778 It is different from make_tree_deleteable() because it unconditionally affects
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)
794 def make_tree_deleteable(root):
795 """Changes the appropriate permissions so the files in the directories can be
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.
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.
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)
814 for dirname in dirnames:
815 set_read_only(os.path.join(dirpath, dirname), False)
819 """Wrapper around shutil.rmtree() to retry automatically on Windows.
821 On Windows, forcibly kills processes that are found to interfere with the
825 True on normal execution, False if berserk techniques (like killing
826 processes) had to be used.
828 make_tree_deleteable(root)
829 logging.info('rmtree(%s)', root)
830 if sys.platform != 'win32':
834 # Windows is more 'challenging'. First tries the soft way: tries 3 times to
835 # delete and sleep a bit in between.
837 for i in xrange(max_tries):
838 # errors is a list of tuple(function, path, excinfo).
840 shutil.rmtree(root, onerror=lambda *args: errors.append(args))
843 if i == max_tries - 1:
845 'Failed to delete %s. The following files remain:\n' % root)
846 for _, path, _ in errors:
847 sys.stderr.write('- %s\n' % path)
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))
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.
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())
874 sys.stderr.write('Enumerating processes:\n')
875 processes = get_processes()
878 for _, proc in sorted(processes.iteritems()):
880 '- pid %d; Handles: %d; Exe: %s; Cmd: %s\n' % (
885 sys.stderr.write('Terminating %d processes.\n' % len(processes))
886 for pid in sorted(processes):
888 # Killing is asynchronous.
890 sys.stderr.write('- %d killed\n' % pid)
892 sys.stderr.write('- failed to kill %s\n' % pid)
896 processes = get_processes()
898 sys.stderr.write('Failed to terminate processes.\n')
899 raise errors[0][2][0], errors[0][2][1], errors[0][2][2]
901 # Now that annoying processes in root are evicted, try again.
903 shutil.rmtree(root, onerror=lambda *args: errors.append(args))
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]