130 lines
4.0 KiB
Python
130 lines
4.0 KiB
Python
"""Ermittlung von Prozessen, die Dateien unter Windows sperren."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ctypes
|
|
import os
|
|
from ctypes import wintypes
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
__all__ = ["LockingProcess", "find_locking_processes", "is_file_locked"]
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class LockingProcess:
|
|
"""Ein vom Windows Restart Manager gemeldeter Prozess."""
|
|
|
|
pid: int
|
|
name: str
|
|
|
|
|
|
def is_file_locked(path: Path) -> bool:
|
|
"""Prüft eine vorhandene Datei durch exklusives Öffnen ohne Schreibzugriff."""
|
|
if not path.exists() or os.name != "nt":
|
|
return False
|
|
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
|
create_file = kernel32.CreateFileW
|
|
create_file.argtypes = [
|
|
wintypes.LPCWSTR,
|
|
wintypes.DWORD,
|
|
wintypes.DWORD,
|
|
ctypes.c_void_p,
|
|
wintypes.DWORD,
|
|
wintypes.DWORD,
|
|
wintypes.HANDLE,
|
|
]
|
|
create_file.restype = wintypes.HANDLE
|
|
close_handle = kernel32.CloseHandle
|
|
close_handle.argtypes = [wintypes.HANDLE]
|
|
close_handle.restype = wintypes.BOOL
|
|
handle = create_file(
|
|
str(path),
|
|
0x80000000,
|
|
0,
|
|
None,
|
|
3,
|
|
0x80,
|
|
None,
|
|
)
|
|
invalid_handle = ctypes.c_void_p(-1).value
|
|
if handle == invalid_handle:
|
|
return ctypes.get_last_error() in {32, 33}
|
|
close_handle(handle)
|
|
return False
|
|
|
|
|
|
def find_locking_processes(path: Path) -> list[LockingProcess]:
|
|
"""Liefert sperrende Prozesse über die Windows-Restart-Manager-API."""
|
|
if os.name != "nt" or not path.exists():
|
|
return []
|
|
try:
|
|
return _restart_manager_processes(path)
|
|
except (OSError, AttributeError, ctypes.ArgumentError):
|
|
return []
|
|
|
|
|
|
def _restart_manager_processes(path: Path) -> list[LockingProcess]:
|
|
rstrtmgr = ctypes.WinDLL("Rstrtmgr")
|
|
session_handle = wintypes.DWORD()
|
|
session_key = ctypes.create_unicode_buffer(33)
|
|
if rstrtmgr.RmStartSession(ctypes.byref(session_handle), 0, session_key) != 0:
|
|
return []
|
|
try:
|
|
resources = (wintypes.LPCWSTR * 1)(str(path))
|
|
if rstrtmgr.RmRegisterResources(session_handle, 1, resources, 0, None, 0, None) != 0:
|
|
return []
|
|
needed = wintypes.UINT(0)
|
|
count = wintypes.UINT(0)
|
|
reboot_reasons = wintypes.DWORD(0)
|
|
result = rstrtmgr.RmGetList(
|
|
session_handle,
|
|
ctypes.byref(needed),
|
|
ctypes.byref(count),
|
|
None,
|
|
ctypes.byref(reboot_reasons),
|
|
)
|
|
if result not in {0, 234} or needed.value == 0:
|
|
return []
|
|
process_info_type = _rm_process_info_type()
|
|
process_array = (process_info_type * needed.value)()
|
|
count.value = needed.value
|
|
result = rstrtmgr.RmGetList(
|
|
session_handle,
|
|
ctypes.byref(needed),
|
|
ctypes.byref(count),
|
|
process_array,
|
|
ctypes.byref(reboot_reasons),
|
|
)
|
|
if result != 0:
|
|
return []
|
|
return [
|
|
LockingProcess(
|
|
pid=int(item.Process.dwProcessId), name=item.strAppName or f"Prozess {item.Process.dwProcessId}"
|
|
)
|
|
for item in process_array[: count.value]
|
|
]
|
|
finally:
|
|
rstrtmgr.RmEndSession(session_handle)
|
|
|
|
|
|
def _rm_process_info_type() -> type[ctypes.Structure]:
|
|
class FileTime(ctypes.Structure):
|
|
_fields_ = [("dwLowDateTime", wintypes.DWORD), ("dwHighDateTime", wintypes.DWORD)]
|
|
|
|
class UniqueProcess(ctypes.Structure):
|
|
_fields_ = [("dwProcessId", wintypes.DWORD), ("ProcessStartTime", FileTime)]
|
|
|
|
class ProcessInfo(ctypes.Structure):
|
|
_fields_ = [
|
|
("Process", UniqueProcess),
|
|
("strAppName", wintypes.WCHAR * 256),
|
|
("strServiceShortName", wintypes.WCHAR * 64),
|
|
("ApplicationType", wintypes.UINT),
|
|
("AppStatus", wintypes.ULONG),
|
|
("TSSessionId", wintypes.DWORD),
|
|
("bRestartable", wintypes.BOOL),
|
|
]
|
|
|
|
return ProcessInfo
|