mirror of
https://github.com/facebook/sapling.git
synced 2024-10-05 06:18:07 +03:00
eden cli: Refactor handler.exe code a bit
Summary: This diff moves the code that manages handler.exe to its own module and abstract the functionality into a base class, even though for now only Windows is implemented. It also calls this new handle in the branch that deletes a non-Eden directory, because it's very convenient to test. Reviewed By: genevievehelsel Differential Revision: D53594227 fbshipit-source-id: 309efab3f89893f745a82fe5acd128ecd702d42e
This commit is contained in:
parent
b1b6d9b430
commit
a23600b759
@ -141,6 +141,7 @@ python_library(
|
||||
"doctor/facebook/lib/fake_vscode_extensions_checker.py",
|
||||
"doctor/problem.py",
|
||||
"doctor/util.py",
|
||||
"file_handler_tools.py",
|
||||
"filesystem.py",
|
||||
"hg_util.py",
|
||||
"logfile.py",
|
||||
|
@ -44,6 +44,7 @@ from facebook.eden.ttypes import MountInfo as ThriftMountInfo, MountState
|
||||
from filelock import BaseFileLock, FileLock
|
||||
|
||||
from . import configinterpolator, configutil, telemetry, util, version
|
||||
from .file_handler_tools import WinFileHandlerReleaser
|
||||
from .util import (
|
||||
FUSE_MOUNT_PROTOCOL_STRING,
|
||||
HealthStatus,
|
||||
@ -862,51 +863,6 @@ Do you want to run `eden mount %s` instead?"""
|
||||
with self.get_thrift_client_legacy(timeout=60) as client:
|
||||
client.unmount(os.fsencode(path))
|
||||
|
||||
def get_handle_path(self) -> Optional[Path]:
|
||||
handle = shutil.which("handle.exe")
|
||||
if handle:
|
||||
return Path(handle)
|
||||
return None
|
||||
|
||||
def check_handle(self, mount: Path) -> None:
|
||||
handle = self.get_handle_path()
|
||||
|
||||
if not handle:
|
||||
return
|
||||
|
||||
print(f"Checking handle.exe for processes using '{mount}'...")
|
||||
print("Press ctrl+c to skip.")
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
[handle, "-nobanner", "-accepteula", mount]
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
print("Handle check interrupted.\n")
|
||||
print("If you want to find out which process is still using the repo, run:")
|
||||
print(f" handle.exe {mount}\n")
|
||||
return
|
||||
parsed = [
|
||||
line.split() for line in output.decode(errors="ignore").splitlines() if line
|
||||
]
|
||||
non_edenfs_process = any(filter(lambda x: x[0].lower() != "edenfs.exe", parsed))
|
||||
|
||||
# When no handle is found in the repo, handle.exe will report `"No
|
||||
# matching handles found."`, which will be 4 words.
|
||||
if not non_edenfs_process or not parsed or len(parsed[0]) == 4:
|
||||
# Nothing other than edenfs.exe is holding handles to files from
|
||||
# the repo, we can proceed with the removal
|
||||
return
|
||||
|
||||
print(
|
||||
"The following processes are still using the repo, please terminate them.\n"
|
||||
)
|
||||
|
||||
for executable, _, pid, _, _type, _, path in parsed:
|
||||
print(f"{executable}({pid}): {path}")
|
||||
|
||||
print()
|
||||
return
|
||||
|
||||
def destroy_mount(
|
||||
self, path: Union[Path, str], preserve_mount_point: bool = False
|
||||
) -> None:
|
||||
@ -1007,22 +963,8 @@ trouble cleaning up leftovers. You will need to manually remove {path}.
|
||||
)
|
||||
|
||||
if used_by_other:
|
||||
if self.get_handle_path():
|
||||
self.check_handle(path)
|
||||
else:
|
||||
print(
|
||||
f"""\
|
||||
It looks like {path} is still in use by another process. If you need help to
|
||||
figure out which process, please try `handle.exe` from sysinternals:
|
||||
|
||||
handle.exe {path}
|
||||
|
||||
"""
|
||||
)
|
||||
print(
|
||||
f"After terminating the processes, please manually delete {path}."
|
||||
)
|
||||
print()
|
||||
winhr = WinFileHandlerReleaser()
|
||||
winhr.try_release(path)
|
||||
|
||||
raise errors[0][1]
|
||||
|
||||
|
106
eden/fs/cli/file_handler_tools.py
Normal file
106
eden/fs/cli/file_handler_tools.py
Normal file
@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
#
|
||||
# This software may be used and distributed according to the terms of the
|
||||
# GNU General Public License version 2.
|
||||
|
||||
# pyre-strict
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
WINDOWS_HANDLE_BIN = "handle.exe"
|
||||
|
||||
|
||||
class FileHandlerReleaser(ABC):
|
||||
@abstractmethod
|
||||
def get_handle_path(self) -> Optional[Path]:
|
||||
"""Returns the path to the handle tool if it exists on the system, such as handle.exe on Windows or lsof on Linux."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def check_handle(self, mount: Path) -> None:
|
||||
"""Displays processes keeping an open handle to files and if possible, offers to terminate them."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def try_release(self, mount: Path) -> None:
|
||||
"""If a handle tool exist, use it to display info to the user with check handle."""
|
||||
pass
|
||||
|
||||
|
||||
class WinFileHandlerReleaser(FileHandlerReleaser):
|
||||
def get_handle_path(self) -> Optional[Path]:
|
||||
if sys.platform != "win32":
|
||||
return None
|
||||
|
||||
handle = shutil.which(WINDOWS_HANDLE_BIN)
|
||||
if handle:
|
||||
return Path(handle)
|
||||
return None
|
||||
|
||||
def check_handle(self, mount: Path) -> None:
|
||||
handle = self.get_handle_path()
|
||||
|
||||
if not handle:
|
||||
return
|
||||
|
||||
print(
|
||||
f"Checking handle.exe for processes using '{mount}'. This can take a while..."
|
||||
)
|
||||
print("Press ctrl+c to skip.")
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
[
|
||||
handle,
|
||||
"-nobanner",
|
||||
"/accepteula",
|
||||
mount,
|
||||
] # / vs - is importart for accepteula, otherwise it won't find handles (??)
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
print("Handle check interrupted.\n")
|
||||
print("If you want to find out which process is still using the repo, run:")
|
||||
print(f" handle.exe {mount}\n")
|
||||
return
|
||||
parsed = [
|
||||
line.split() for line in output.decode(errors="ignore").splitlines() if line
|
||||
]
|
||||
non_edenfs_process = any(filter(lambda x: x[0].lower() != "edenfs.exe", parsed))
|
||||
|
||||
# When no handle is found in the repo, handle.exe will report `"No
|
||||
# matching handles found."`, which will be 4 words.
|
||||
if not non_edenfs_process or not parsed or len(parsed[0]) == 4:
|
||||
# Nothing other than edenfs.exe is holding handles to files from
|
||||
# the repo, we can proceed with the removal
|
||||
return
|
||||
|
||||
print(
|
||||
"The following processes are still using the repo, please terminate them.\n"
|
||||
)
|
||||
|
||||
for executable, _, pid, _, _type, _, path in parsed:
|
||||
print(f"{executable}({pid}): {path}")
|
||||
|
||||
print()
|
||||
return
|
||||
|
||||
def try_release(self, mount: Path) -> None:
|
||||
if self.get_handle_path():
|
||||
self.check_handle(mount)
|
||||
else:
|
||||
print(
|
||||
f"""\
|
||||
It looks like {mount} is still in use by another process. If you need help to
|
||||
figure out which process, please try `handle.exe` from sysinternals:
|
||||
|
||||
handle.exe {mount}
|
||||
|
||||
"""
|
||||
)
|
||||
print(f"After terminating the processes, please manually delete {mount}.")
|
||||
print()
|
@ -78,6 +78,7 @@ from .constants import (
|
||||
SHUTDOWN_EXIT_CODE_REQUESTED_SHUTDOWN,
|
||||
SHUTDOWN_EXIT_CODE_TERMINATED_VIA_SIGKILL,
|
||||
)
|
||||
from .file_handler_tools import WinFileHandlerReleaser
|
||||
from .stats_print import format_size
|
||||
from .subcmd import Subcmd
|
||||
from .util import get_environment_suitable_for_subprocess, print_stderr, ShutdownError
|
||||
@ -1599,8 +1600,15 @@ Do you still want to delete {path}?"""
|
||||
path.rmdir()
|
||||
return 0
|
||||
except Exception as ex:
|
||||
print(f"error: cannot remove contents of {path}: {ex}")
|
||||
return 1
|
||||
if sys.platform != "win32":
|
||||
print(
|
||||
f"Error: cannot remove contents of {path}: {ex}"
|
||||
)
|
||||
return 1
|
||||
else:
|
||||
winhr = WinFileHandlerReleaser()
|
||||
winhr.try_release(path)
|
||||
return 0
|
||||
else:
|
||||
# We can't ask the user what their true intentions are,
|
||||
# so let's fail by default.
|
||||
|
Loading…
Reference in New Issue
Block a user