| OLD | NEW |
| 1 # Copyright 2013 The LUCI Authors. All rights reserved. | 1 # Copyright 2013 The LUCI Authors. All rights reserved. |
| 2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
| 3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
| 4 | 4 |
| 5 """Provides functions: get_native_path_case(), isabs() and safe_join(). | 5 """Provides functions: get_native_path_case(), isabs() and safe_join(). |
| 6 | 6 |
| 7 This module assumes that filesystem is not changing while current process | 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. | 8 is running and thus it caches results of functions that depend on FS state. |
| 9 """ | 9 """ |
| 10 | 10 |
| (...skipping 228 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 239 # its group or that the current ACL permits this. Otherwise it will silently | 239 # its group or that the current ACL permits this. Otherwise it will silently |
| 240 # fail. | 240 # fail. |
| 241 win32security.SetFileSecurity( | 241 win32security.SetFileSecurity( |
| 242 fs.extend(path), win32security.DACL_SECURITY_INFORMATION, sd) | 242 fs.extend(path), win32security.DACL_SECURITY_INFORMATION, sd) |
| 243 # It's important to also look for the read only bit after, as it's possible | 243 # It's important to also look for the read only bit after, as it's possible |
| 244 # the set_read_only() call to remove the read only bit had silently failed | 244 # the set_read_only() call to remove the read only bit had silently failed |
| 245 # because there was no DACL for the user. | 245 # because there was no DACL for the user. |
| 246 if not (os.stat(path).st_mode & stat.S_IWUSR): | 246 if not (os.stat(path).st_mode & stat.S_IWUSR): |
| 247 os.chmod(path, 0777) | 247 os.chmod(path, 0777) |
| 248 | 248 |
| 249 |
| 249 def isabs(path): | 250 def isabs(path): |
| 250 """Accepts X: as an absolute path, unlike python's os.path.isabs().""" | 251 """Accepts X: as an absolute path, unlike python's os.path.isabs().""" |
| 251 return os.path.isabs(path) or len(path) == 2 and path[1] == ':' | 252 return os.path.isabs(path) or len(path) == 2 and path[1] == ':' |
| 252 | 253 |
| 253 | 254 |
| 254 def find_item_native_case(root, item): | 255 def find_item_native_case(root, item): |
| 255 """Gets the native path case of a single item based at root_path.""" | 256 """Gets the native path case of a single item based at root_path.""" |
| 256 if item == '..': | 257 if item == '..': |
| 257 return item | 258 return item |
| 258 | 259 |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 300 # The path does not exist. Try to recurse and reconstruct the path. | 301 # The path does not exist. Try to recurse and reconstruct the path. |
| 301 base = os.path.dirname(p) | 302 base = os.path.dirname(p) |
| 302 rest = os.path.basename(p) | 303 rest = os.path.basename(p) |
| 303 return os.path.join(get_native_path_case(base), rest) | 304 return os.path.join(get_native_path_case(base), rest) |
| 304 raise | 305 raise |
| 305 # Always upper case the first letter since GetLongPathName() will return the | 306 # Always upper case the first letter since GetLongPathName() will return the |
| 306 # drive letter in the case it was given. | 307 # drive letter in the case it was given. |
| 307 return out[0].upper() + out[1:] + suffix | 308 return out[0].upper() + out[1:] + suffix |
| 308 | 309 |
| 309 | 310 |
| 310 def enum_processes_win(): | 311 def get_process_token(): |
| 312 """Get the current process token.""" |
| 313 TOKEN_ALL_ACCESS = 0xF01FF |
| 314 token = ctypes.wintypes.HANDLE() |
| 315 if not OpenProcessToken( |
| 316 GetCurrentProcess(), TOKEN_ALL_ACCESS, ctypes.byref(token)): |
| 317 # pylint: disable=undefined-variable |
| 318 raise WindowsError('Couldn\'t get process token') |
| 319 return token |
| 320 |
| 321 |
| 322 def get_luid(name): |
| 323 """Returns the LUID for a privilege.""" |
| 324 luid = LUID() |
| 325 if not LookupPrivilegeValue(None, unicode(name), ctypes.byref(luid)): |
| 326 # pylint: disable=undefined-variable |
| 327 raise WindowsError('Couldn\'t lookup privilege value') |
| 328 return luid |
| 329 |
| 330 |
| 331 def enable_privilege(name): |
| 332 """Enables the privilege for the current process token. |
| 333 |
| 334 Returns: |
| 335 - True if the assignment is successful. |
| 336 """ |
| 337 SE_PRIVILEGE_ENABLED = 2 |
| 338 ERROR_NOT_ALL_ASSIGNED = 1300 |
| 339 |
| 340 size = ctypes.sizeof(TOKEN_PRIVILEGES) + ctypes.sizeof(LUID_AND_ATTRIBUTES) |
| 341 buf = ctypes.create_string_buffer(size) |
| 342 tp = ctypes.cast(buf, ctypes.POINTER(TOKEN_PRIVILEGES)).contents |
| 343 tp.count = 1 |
| 344 tp.get_array()[0].LUID = get_luid(name) |
| 345 tp.get_array()[0].Attributes = SE_PRIVILEGE_ENABLED |
| 346 token = get_process_token() |
| 347 try: |
| 348 if not AdjustTokenPrivileges(token, False, tp, 0, None, None): |
| 349 # pylint: disable=undefined-variable |
| 350 raise WindowsError('Error in AdjustTokenPrivileges') |
| 351 finally: |
| 352 ctypes.windll.kernel32.CloseHandle(token) |
| 353 return ctypes.windll.kernel32.GetLastError() != ERROR_NOT_ALL_ASSIGNED |
| 354 |
| 355 |
| 356 def enable_symlink(): |
| 357 """Enable SeCreateSymbolicLinkPrivilege for the current token. |
| 358 |
| 359 Returns: |
| 360 - True if symlink support is enabled. |
| 361 |
| 362 Thanks Microsoft. This is appreciated. |
| 363 """ |
| 364 return enable_privilege(u'SeCreateSymbolicLinkPrivilege') |
| 365 |
| 366 |
| 367 def kill_children_processes(root): |
| 368 """Try to kill all children processes indistriminately and prints updates to |
| 369 stderr. |
| 370 |
| 371 Returns: |
| 372 True if at least one child process was found. |
| 373 """ |
| 374 processes = _get_children_processes_win(root) |
| 375 if not processes: |
| 376 return False |
| 377 sys.stderr.write('Enumerating processes:\n') |
| 378 for _, proc in sorted(processes.iteritems()): |
| 379 sys.stderr.write( |
| 380 '- pid %d; Handles: %d; Exe: %s; Cmd: %s\n' % ( |
| 381 proc.ProcessId, |
| 382 proc.HandleCount, |
| 383 proc.ExecutablePath, |
| 384 proc.CommandLine)) |
| 385 sys.stderr.write('Terminating %d processes:\n' % len(processes)) |
| 386 for pid in sorted(processes): |
| 387 try: |
| 388 # Killing is asynchronous. |
| 389 os.kill(pid, 9) |
| 390 sys.stderr.write('- %d killed\n' % pid) |
| 391 except OSError: |
| 392 sys.stderr.write('- failed to kill %s\n' % pid) |
| 393 return True |
| 394 |
| 395 |
| 396 ## Windows private code. |
| 397 |
| 398 |
| 399 def _enum_processes_win(): |
| 311 """Returns all processes on the system that are accessible to this process. | 400 """Returns all processes on the system that are accessible to this process. |
| 312 | 401 |
| 313 Returns: | 402 Returns: |
| 314 Win32_Process COM objects. See | 403 Win32_Process COM objects. See |
| 315 http://msdn.microsoft.com/library/aa394372.aspx for more details. | 404 http://msdn.microsoft.com/library/aa394372.aspx for more details. |
| 316 """ | 405 """ |
| 317 import win32com.client # pylint: disable=F0401 | 406 import win32com.client # pylint: disable=F0401 |
| 318 wmi_service = win32com.client.Dispatch('WbemScripting.SWbemLocator') | 407 wmi_service = win32com.client.Dispatch('WbemScripting.SWbemLocator') |
| 319 wbem = wmi_service.ConnectServer('.', 'root\\cimv2') | 408 wbem = wmi_service.ConnectServer('.', 'root\\cimv2') |
| 320 return [proc for proc in wbem.ExecQuery('SELECT * FROM Win32_Process')] | 409 return [proc for proc in wbem.ExecQuery('SELECT * FROM Win32_Process')] |
| 321 | 410 |
| 322 | 411 |
| 323 def filter_processes_dir_win(processes, root_dir): | 412 def _filter_processes_dir_win(processes, root_dir): |
| 324 """Returns all processes which has their main executable located inside | 413 """Returns all processes which has their main executable located inside |
| 325 root_dir. | 414 root_dir. |
| 326 """ | 415 """ |
| 327 def normalize_path(filename): | 416 def normalize_path(filename): |
| 328 try: | 417 try: |
| 329 return GetLongPathName(unicode(filename)).lower() | 418 return GetLongPathName(unicode(filename)).lower() |
| 330 except: # pylint: disable=W0702 | 419 except: # pylint: disable=W0702 |
| 331 return unicode(filename).lower() | 420 return unicode(filename).lower() |
| 332 | 421 |
| 333 root_dir = normalize_path(root_dir) | 422 root_dir = normalize_path(root_dir) |
| (...skipping 11 matching lines...) Expand all Loading... |
| 345 return None | 434 return None |
| 346 | 435 |
| 347 long_names = ((process_name(proc), proc) for proc in processes) | 436 long_names = ((process_name(proc), proc) for proc in processes) |
| 348 | 437 |
| 349 return [ | 438 return [ |
| 350 proc for name, proc in long_names | 439 proc for name, proc in long_names |
| 351 if name is not None and name.startswith(root_dir) | 440 if name is not None and name.startswith(root_dir) |
| 352 ] | 441 ] |
| 353 | 442 |
| 354 | 443 |
| 355 def filter_processes_tree_win(processes): | 444 def _filter_processes_tree_win(processes): |
| 356 """Returns all the processes under the current process.""" | 445 """Returns all the processes under the current process.""" |
| 357 # Convert to dict. | 446 # Convert to dict. |
| 358 processes = dict((p.ProcessId, p) for p in processes) | 447 processes = dict((p.ProcessId, p) for p in processes) |
| 359 root_pid = os.getpid() | 448 root_pid = os.getpid() |
| 360 out = {root_pid: processes[root_pid]} | 449 out = {root_pid: processes[root_pid]} |
| 361 while True: | 450 while True: |
| 362 found = set() | 451 found = set() |
| 363 for pid in out: | 452 for pid in out: |
| 364 found.update( | 453 found.update( |
| 365 p.ProcessId for p in processes.itervalues() | 454 p.ProcessId for p in processes.itervalues() |
| 366 if p.ParentProcessId == pid) | 455 if p.ParentProcessId == pid) |
| 367 found -= set(out) | 456 found -= set(out) |
| 368 if not found: | 457 if not found: |
| 369 break | 458 break |
| 370 out.update((p, processes[p]) for p in found) | 459 out.update((p, processes[p]) for p in found) |
| 371 return out.values() | 460 return out.values() |
| 372 | 461 |
| 373 | 462 |
| 374 def get_process_token(): | 463 def _get_children_processes_win(root): |
| 375 """Get the current process token.""" | 464 """Returns a list of processes. |
| 376 TOKEN_ALL_ACCESS = 0xF01FF | |
| 377 token = ctypes.wintypes.HANDLE() | |
| 378 if not OpenProcessToken( | |
| 379 GetCurrentProcess(), TOKEN_ALL_ACCESS, ctypes.byref(token)): | |
| 380 # pylint: disable=undefined-variable | |
| 381 raise WindowsError('Couldn\'t get process token') | |
| 382 return token | |
| 383 | 465 |
| 466 Enumerates both: |
| 467 - all child processes from this process. |
| 468 - processes where the main executable in inside 'root'. The reason is that |
| 469 the ancestry may be broken so stray grand-children processes could be |
| 470 undetected by the first technique. |
| 384 | 471 |
| 385 def get_luid(name): | 472 This technique is not fool-proof but gets mostly there. |
| 386 """Returns the LUID for a privilege.""" | |
| 387 luid = LUID() | |
| 388 if not LookupPrivilegeValue(None, unicode(name), ctypes.byref(luid)): | |
| 389 # pylint: disable=undefined-variable | |
| 390 raise WindowsError('Couldn\'t lookup privilege value') | |
| 391 return luid | |
| 392 | |
| 393 | |
| 394 def enable_privilege(name): | |
| 395 """Enables the privilege for the current process token. | |
| 396 | |
| 397 Returns: | |
| 398 - True if the assignment is successful. | |
| 399 """ | 473 """ |
| 400 SE_PRIVILEGE_ENABLED = 2 | 474 processes = _enum_processes_win() |
| 401 ERROR_NOT_ALL_ASSIGNED = 1300 | 475 tree_processes = _filter_processes_tree_win(processes) |
| 402 | 476 dir_processes = _filter_processes_dir_win(processes, root) |
| 403 size = ctypes.sizeof(TOKEN_PRIVILEGES) + ctypes.sizeof(LUID_AND_ATTRIBUTES) | 477 # Convert to dict to remove duplicates. |
| 404 buf = ctypes.create_string_buffer(size) | 478 processes = dict((p.ProcessId, p) for p in tree_processes) |
| 405 tp = ctypes.cast(buf, ctypes.POINTER(TOKEN_PRIVILEGES)).contents | 479 processes.update((p.ProcessId, p) for p in dir_processes) |
| 406 tp.count = 1 | 480 processes.pop(os.getpid()) |
| 407 tp.get_array()[0].LUID = get_luid(name) | 481 return processes |
| 408 tp.get_array()[0].Attributes = SE_PRIVILEGE_ENABLED | |
| 409 token = get_process_token() | |
| 410 try: | |
| 411 if not AdjustTokenPrivileges(token, False, tp, 0, None, None): | |
| 412 # pylint: disable=undefined-variable | |
| 413 raise WindowsError('Error in AdjustTokenPrivileges') | |
| 414 finally: | |
| 415 ctypes.windll.kernel32.CloseHandle(token) | |
| 416 return ctypes.windll.kernel32.GetLastError() != ERROR_NOT_ALL_ASSIGNED | |
| 417 | |
| 418 | |
| 419 def enable_symlink(): | |
| 420 """Enable SeCreateSymbolicLinkPrivilege for the current token. | |
| 421 | |
| 422 Returns: | |
| 423 - True if symlink support is enabled. | |
| 424 | |
| 425 Thanks Microsoft. This is appreciated. | |
| 426 """ | |
| 427 return enable_privilege(u'SeCreateSymbolicLinkPrivilege') | |
| 428 | 482 |
| 429 | 483 |
| 430 elif sys.platform == 'darwin': | 484 elif sys.platform == 'darwin': |
| 431 | 485 |
| 432 | 486 |
| 433 # On non-windows, keep the stdlib behavior. | 487 # On non-windows, keep the stdlib behavior. |
| 434 isabs = os.path.isabs | 488 isabs = os.path.isabs |
| 435 | 489 |
| 436 | 490 |
| 437 def _native_case(p): | |
| 438 """Gets the native path case. Warning: this function resolves symlinks.""" | |
| 439 try: | |
| 440 rel_ref, _ = Carbon.File.FSPathMakeRef(p.encode('utf-8')) | |
| 441 # The OSX underlying code uses NFD but python strings are in NFC. This | |
| 442 # will cause issues with os.listdir() for example. Since the dtrace log | |
| 443 # *is* in NFC, normalize it here. | |
| 444 out = unicodedata.normalize( | |
| 445 'NFC', rel_ref.FSRefMakePath().decode('utf-8')) | |
| 446 if p.endswith(os.path.sep) and not out.endswith(os.path.sep): | |
| 447 return out + os.path.sep | |
| 448 return out | |
| 449 except MacOS.Error, e: | |
| 450 if e.args[0] in (-43, -120): | |
| 451 # The path does not exist. Try to recurse and reconstruct the path. | |
| 452 # -43 means file not found. | |
| 453 # -120 means directory not found. | |
| 454 base = os.path.dirname(p) | |
| 455 rest = os.path.basename(p) | |
| 456 return os.path.join(_native_case(base), rest) | |
| 457 raise OSError( | |
| 458 e.args[0], 'Failed to get native path for %s' % p, p, e.args[1]) | |
| 459 | |
| 460 | |
| 461 def _split_at_symlink_native(base_path, rest): | |
| 462 """Returns the native path for a symlink.""" | |
| 463 base, symlink, rest = split_at_symlink(base_path, rest) | |
| 464 if symlink: | |
| 465 if not base_path: | |
| 466 base_path = base | |
| 467 else: | |
| 468 base_path = safe_join(base_path, base) | |
| 469 symlink = find_item_native_case(base_path, symlink) | |
| 470 return base, symlink, rest | |
| 471 | |
| 472 | |
| 473 def find_item_native_case(root_path, item): | 491 def find_item_native_case(root_path, item): |
| 474 """Gets the native path case of a single item based at root_path. | 492 """Gets the native path case of a single item based at root_path. |
| 475 | 493 |
| 476 There is no API to get the native path case of symlinks on OSX. So it | 494 There is no API to get the native path case of symlinks on OSX. So it |
| 477 needs to be done the slow way. | 495 needs to be done the slow way. |
| 478 """ | 496 """ |
| 479 if item == '..': | 497 if item == '..': |
| 480 return item | 498 return item |
| 481 | 499 |
| 482 item = item.lower() | 500 item = item.lower() |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 530 # Make sure no symlink was resolved. | 548 # Make sure no symlink was resolved. |
| 531 assert base.lower() == path.lower(), (base, path) | 549 assert base.lower() == path.lower(), (base, path) |
| 532 logging.debug('get_native_path_case(%s) = %s' % (path, base)) | 550 logging.debug('get_native_path_case(%s) = %s' % (path, base)) |
| 533 return base | 551 return base |
| 534 | 552 |
| 535 | 553 |
| 536 def enable_symlink(): | 554 def enable_symlink(): |
| 537 return True | 555 return True |
| 538 | 556 |
| 539 | 557 |
| 558 ## OSX private code. |
| 559 |
| 560 |
| 561 def _native_case(p): |
| 562 """Gets the native path case. Warning: this function resolves symlinks.""" |
| 563 try: |
| 564 rel_ref, _ = Carbon.File.FSPathMakeRef(p.encode('utf-8')) |
| 565 # The OSX underlying code uses NFD but python strings are in NFC. This |
| 566 # will cause issues with os.listdir() for example. Since the dtrace log |
| 567 # *is* in NFC, normalize it here. |
| 568 out = unicodedata.normalize( |
| 569 'NFC', rel_ref.FSRefMakePath().decode('utf-8')) |
| 570 if p.endswith(os.path.sep) and not out.endswith(os.path.sep): |
| 571 return out + os.path.sep |
| 572 return out |
| 573 except MacOS.Error, e: |
| 574 if e.args[0] in (-43, -120): |
| 575 # The path does not exist. Try to recurse and reconstruct the path. |
| 576 # -43 means file not found. |
| 577 # -120 means directory not found. |
| 578 base = os.path.dirname(p) |
| 579 rest = os.path.basename(p) |
| 580 return os.path.join(_native_case(base), rest) |
| 581 raise OSError( |
| 582 e.args[0], 'Failed to get native path for %s' % p, p, e.args[1]) |
| 583 |
| 584 |
| 585 def _split_at_symlink_native(base_path, rest): |
| 586 """Returns the native path for a symlink.""" |
| 587 base, symlink, rest = split_at_symlink(base_path, rest) |
| 588 if symlink: |
| 589 if not base_path: |
| 590 base_path = base |
| 591 else: |
| 592 base_path = safe_join(base_path, base) |
| 593 symlink = find_item_native_case(base_path, symlink) |
| 594 return base, symlink, rest |
| 595 |
| 596 |
| 540 else: # OSes other than Windows and OSX. | 597 else: # OSes other than Windows and OSX. |
| 541 | 598 |
| 542 | 599 |
| 543 # On non-windows, keep the stdlib behavior. | 600 # On non-windows, keep the stdlib behavior. |
| 544 isabs = os.path.isabs | 601 isabs = os.path.isabs |
| 545 | 602 |
| 546 | 603 |
| 547 def find_item_native_case(root, item): | 604 def find_item_native_case(root, item): |
| 548 """Gets the native path case of a single item based at root_path.""" | 605 """Gets the native path case of a single item based at root_path.""" |
| 549 if item == '..': | 606 if item == '..': |
| (...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 645 logging.debug( | 702 logging.debug( |
| 646 'split_at_symlink(%s, %s) -> (%s, %s, %s)' % | 703 'split_at_symlink(%s, %s) -> (%s, %s, %s)' % |
| 647 (base_dir, relfile, base, symlink, rest)) | 704 (base_dir, relfile, base, symlink, rest)) |
| 648 return base, symlink, rest | 705 return base, symlink, rest |
| 649 if index == len(relfile): | 706 if index == len(relfile): |
| 650 break | 707 break |
| 651 index += 1 | 708 index += 1 |
| 652 return relfile, None, None | 709 return relfile, None, None |
| 653 | 710 |
| 654 | 711 |
| 712 def kill_children_processes(root): |
| 713 """Not yet implemented on posix.""" |
| 714 # pylint: disable=unused-argument |
| 715 return False |
| 716 |
| 717 |
| 655 def relpath(path, root): | 718 def relpath(path, root): |
| 656 """os.path.relpath() that keeps trailing os.path.sep.""" | 719 """os.path.relpath() that keeps trailing os.path.sep.""" |
| 657 out = os.path.relpath(path, root) | 720 out = os.path.relpath(path, root) |
| 658 if path.endswith(os.path.sep): | 721 if path.endswith(os.path.sep): |
| 659 out += os.path.sep | 722 out += os.path.sep |
| 660 return out | 723 return out |
| 661 | 724 |
| 662 | 725 |
| 663 def safe_relpath(filepath, basepath): | 726 def safe_relpath(filepath, basepath): |
| 664 """Do not throw on Windows when filepath and basepath are on different drives. | 727 """Do not throw on Windows when filepath and basepath are on different drives. |
| (...skipping 21 matching lines...) Expand all Loading... |
| 686 """posix.relpath() that keeps trailing slash. | 749 """posix.relpath() that keeps trailing slash. |
| 687 | 750 |
| 688 It is different from relpath() since it can be used on Windows. | 751 It is different from relpath() since it can be used on Windows. |
| 689 """ | 752 """ |
| 690 out = posixpath.relpath(path, root) | 753 out = posixpath.relpath(path, root) |
| 691 if path.endswith('/'): | 754 if path.endswith('/'): |
| 692 out += '/' | 755 out += '/' |
| 693 return out | 756 return out |
| 694 | 757 |
| 695 | 758 |
| 696 def cleanup_path(x): | |
| 697 """Cleans up a relative path. Converts any os.path.sep to '/' on Windows.""" | |
| 698 if x: | |
| 699 x = x.rstrip(os.path.sep).replace(os.path.sep, '/') | |
| 700 if x == '.': | |
| 701 x = '' | |
| 702 if x: | |
| 703 x += '/' | |
| 704 return x | |
| 705 | |
| 706 | |
| 707 def is_url(path): | 759 def is_url(path): |
| 708 """Returns True if it looks like an HTTP url instead of a file path.""" | 760 """Returns True if it looks like an HTTP url instead of a file path.""" |
| 709 return bool(re.match(r'^https?://.+$', path)) | 761 return bool(re.match(r'^https?://.+$', path)) |
| 710 | 762 |
| 711 | 763 |
| 712 def path_starts_with(prefix, path): | 764 def path_starts_with(prefix, path): |
| 713 """Returns true if the components of the path |prefix| are the same as the | 765 """Returns true if the components of the path |prefix| are the same as the |
| 714 initial components of |path| (or all of the components of |path|). The paths | 766 initial components of |path| (or all of the components of |path|). The paths |
| 715 must be absolute. | 767 must be absolute. |
| 716 """ | 768 """ |
| (...skipping 400 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1117 # The same path may be listed multiple times. | 1169 # The same path may be listed multiple times. |
| 1118 for path in sorted(set(path for _, path, _ in errors)): | 1170 for path in sorted(set(path for _, path, _ in errors)): |
| 1119 sys.stderr.write('- %s\n' % path) | 1171 sys.stderr.write('- %s\n' % path) |
| 1120 | 1172 |
| 1121 # If soft retries fail on Linux, there's nothing better we can do. | 1173 # If soft retries fail on Linux, there's nothing better we can do. |
| 1122 if sys.platform != 'win32': | 1174 if sys.platform != 'win32': |
| 1123 raise errors[0][2][0], errors[0][2][1], errors[0][2][2] | 1175 raise errors[0][2][0], errors[0][2][1], errors[0][2][2] |
| 1124 | 1176 |
| 1125 # The soft way was not good enough. Try the hard way. | 1177 # The soft way was not good enough. Try the hard way. |
| 1126 for i in xrange(max_tries): | 1178 for i in xrange(max_tries): |
| 1127 if not _kill_children_processes_win(root): | 1179 if not kill_children_processes(root): |
| 1128 break | 1180 break |
| 1129 if i != max_tries - 1: | 1181 if i != max_tries - 1: |
| 1130 time.sleep((i+1)*2) | 1182 time.sleep((i+1)*2) |
| 1131 else: | 1183 else: |
| 1132 processes = _get_children_processes_win(root) | 1184 processes = _get_children_processes_win(root) |
| 1133 if processes: | 1185 if processes: |
| 1134 sys.stderr.write('Failed to terminate processes.\n') | 1186 sys.stderr.write('Failed to terminate processes.\n') |
| 1135 raise errors[0][2][0], errors[0][2][1], errors[0][2][2] | 1187 raise errors[0][2][0], errors[0][2][1], errors[0][2][2] |
| 1136 | 1188 |
| 1137 # Now that annoying processes in root are evicted, try again. | 1189 # Now that annoying processes in root are evicted, try again. |
| 1138 errors = [] | 1190 errors = [] |
| 1139 fs.rmtree(root, onerror=lambda *args: errors.append(args)) | 1191 fs.rmtree(root, onerror=lambda *args: errors.append(args)) |
| 1140 if errors and fs.exists(root): | 1192 if errors and fs.exists(root): |
| 1141 # There's no hope: the directory was tried to be removed 4 times. Give up | 1193 # There's no hope: the directory was tried to be removed 4 times. Give up |
| 1142 # and raise an exception. | 1194 # and raise an exception. |
| 1143 sys.stderr.write( | 1195 sys.stderr.write( |
| 1144 'Failed to delete %s. The following files remain:\n' % root) | 1196 'Failed to delete %s. The following files remain:\n' % root) |
| 1145 # The same path may be listed multiple times. | 1197 # The same path may be listed multiple times. |
| 1146 for path in sorted(set(path for _, path, _ in errors)): | 1198 for path in sorted(set(path for _, path, _ in errors)): |
| 1147 sys.stderr.write('- %s\n' % path) | 1199 sys.stderr.write('- %s\n' % path) |
| 1148 raise errors[0][2][0], errors[0][2][1], errors[0][2][2] | 1200 raise errors[0][2][0], errors[0][2][1], errors[0][2][2] |
| 1149 return False | 1201 return False |
| 1150 | 1202 |
| 1151 | 1203 |
| 1152 ## Private code. | 1204 ## Private code. |
| 1153 | |
| 1154 | |
| 1155 def _kill_children_processes_win(root): | |
| 1156 """Try to kill all children processes indistriminately and prints updates to | |
| 1157 stderr. | |
| 1158 | |
| 1159 Returns: | |
| 1160 True if at least one child process was found. | |
| 1161 """ | |
| 1162 processes = _get_children_processes_win(root) | |
| 1163 if not processes: | |
| 1164 return False | |
| 1165 sys.stderr.write('Enumerating processes:\n') | |
| 1166 for _, proc in sorted(processes.iteritems()): | |
| 1167 sys.stderr.write( | |
| 1168 '- pid %d; Handles: %d; Exe: %s; Cmd: %s\n' % ( | |
| 1169 proc.ProcessId, | |
| 1170 proc.HandleCount, | |
| 1171 proc.ExecutablePath, | |
| 1172 proc.CommandLine)) | |
| 1173 sys.stderr.write('Terminating %d processes:\n' % len(processes)) | |
| 1174 for pid in sorted(processes): | |
| 1175 try: | |
| 1176 # Killing is asynchronous. | |
| 1177 os.kill(pid, 9) | |
| 1178 sys.stderr.write('- %d killed\n' % pid) | |
| 1179 except OSError: | |
| 1180 sys.stderr.write('- failed to kill %s\n' % pid) | |
| 1181 return True | |
| 1182 | |
| 1183 | |
| 1184 def _get_children_processes_win(root): | |
| 1185 """Returns a list of processes. | |
| 1186 | |
| 1187 Enumerates both: | |
| 1188 - all child processes from this process. | |
| 1189 - processes where the main executable in inside 'root'. The reason is that | |
| 1190 the ancestry may be broken so stray grand-children processes could be | |
| 1191 undetected by the first technique. | |
| 1192 | |
| 1193 This technique is not fool-proof but gets mostly there. | |
| 1194 """ | |
| 1195 processes = enum_processes_win() | |
| 1196 tree_processes = filter_processes_tree_win(processes) | |
| 1197 dir_processes = filter_processes_dir_win(processes, root) | |
| 1198 # Convert to dict to remove duplicates. | |
| 1199 processes = dict((p.ProcessId, p) for p in tree_processes) | |
| 1200 processes.update((p.ProcessId, p) for p in dir_processes) | |
| 1201 processes.pop(os.getpid()) | |
| 1202 return processes | |
| OLD | NEW |