Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ __pycache__/
build/
dist/
.pytest_cache/
.ruff_cache/
.coverage
htmlcov/
.venv/
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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` 结束后会重新扫描验证,文件真正释放了才报成功
Expand Down
10 changes: 9 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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__" }
Expand Down
2 changes: 1 addition & 1 deletion src/wholocks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
find_holders,
)

__version__ = "0.3.0"
__version__ = "0.3.1"

__all__ = [
"find_holders",
Expand Down
2 changes: 2 additions & 0 deletions src/wholocks/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
5 changes: 5 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading