diff --git a/.gitignore b/.gitignore index 386c8ce..8b8a8ca 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ __pycache__/ build/ dist/ .pytest_cache/ +.ruff_cache/ .coverage htmlcov/ .venv/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 592bd34..2de9b85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.3.1 - 2026-08-26 + +- Refuse to kill the parent shell (the usual holder of `.` via cwd), matching + the existing "don't kill yourself" guard. +- CI now runs ruff. + ## 0.3.0 - 2026-08-13 - **Linux: hardlink detection.** When a process holds the same file under a diff --git a/README.md b/README.md index f6250ee..119602e 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ No drivers, no elevated service, no kernel tricks. Killing uses `TerminateProces ## Safety - Killing always shows *what* will die and asks first (`--yes` to skip). -- Refuses to touch critical system processes, PID 0–4 / PID 1, and itself. +- Refuses to touch critical system processes, PID 0–4 / PID 1, itself, and its parent shell. - Windows services are refused unless `--force` (stop them properly with `net stop`). - Databases get a "stop the service instead" warning in the tip line. - `--kill` verifies afterwards: success is reported only if the path is actually free. @@ -231,7 +231,7 @@ wholocks -r /mnt/usb ## 安全设计 - 结束进程前必先列出并确认(脚本用 `--yes` 跳过) -- 拒绝结束系统关键进程、PID 0–4 / PID 1、以及 wholocks 自己 +- 拒绝结束系统关键进程、PID 0–4 / PID 1、wholocks 自己、以及启动它的父 shell - Windows 服务需要 `--force` 才会动手(并提示用 `net stop` 更妥当) - 数据库进程会警告「请正常停止服务,强杀可能损坏数据」 - `--kill` 结束后会重新扫描验证,文件真正释放了才报成功 diff --git a/pyproject.toml b/pyproject.toml index e244ddd..f6fb209 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,15 @@ Changelog = "https://github.com/hc-ui/wholocks/blob/main/CHANGELOG.md" wholocks = "wholocks.cli:main" [project.optional-dependencies] -dev = ["pytest>=7"] +dev = ["pytest>=7", "ruff>=0.4"] + +[tool.ruff] +line-length = 100 +target-version = "py39" + +[tool.ruff.lint] +select = ["E", "F", "W"] +ignore = ["E501"] [tool.setuptools.dynamic] version = { attr = "wholocks.__version__" } diff --git a/src/wholocks/__init__.py b/src/wholocks/__init__.py index a6b8cb7..f3ca65d 100644 --- a/src/wholocks/__init__.py +++ b/src/wholocks/__init__.py @@ -17,7 +17,7 @@ find_holders, ) -__version__ = "0.3.0" +__version__ = "0.3.1" __all__ = [ "find_holders", diff --git a/src/wholocks/core.py b/src/wholocks/core.py index c590219..8864d8b 100644 --- a/src/wholocks/core.py +++ b/src/wholocks/core.py @@ -270,6 +270,8 @@ def kill_block_reason(holder: Holder, force: bool = False) -> Optional[str]: return "PID 1 (init/systemd)" if holder.pid == os.getpid(): return "this is wholocks itself" + if holder.pid == os.getppid() and not force: + return "that's the shell wholocks is running in" if holder.app_type == "service" and not force: svc = holder.description or holder.name return ( diff --git a/tests/test_core.py b/tests/test_core.py index f6e064d..0aa042c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -116,6 +116,11 @@ def test_refuses_self(self): h = Holder(pid=os.getpid(), name="python") assert kill_block_reason(h) is not None + def test_refuses_parent_shell(self): + h = Holder(pid=os.getppid(), name="bash") + assert kill_block_reason(h) is not None + assert kill_block_reason(h, force=True) is None + def test_service_requires_force(self): h = Holder(pid=5555, name="svc.exe", app_type="service") assert kill_block_reason(h, force=False) is not None