72 lines
2.3 KiB
Python
72 lines
2.3 KiB
Python
|
|
"""Internal directory binding helpers for disposable publication paths."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import os
|
||
|
|
import stat
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
from .errors import DocForgeError
|
||
|
|
|
||
|
|
|
||
|
|
def open_bound_directory(path: Path) -> int:
|
||
|
|
"""Open one real directory and bind its current inode for later operations."""
|
||
|
|
|
||
|
|
try:
|
||
|
|
path_status = path.lstat()
|
||
|
|
if (
|
||
|
|
stat.S_ISLNK(path_status.st_mode)
|
||
|
|
or not stat.S_ISDIR(path_status.st_mode)
|
||
|
|
or path.resolve(strict=True) != path
|
||
|
|
):
|
||
|
|
raise DocForgeError(
|
||
|
|
"path_escape",
|
||
|
|
"Derived cache root is not a safe real directory",
|
||
|
|
)
|
||
|
|
directory_fd = os.open(path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
|
||
|
|
except FileNotFoundError as error:
|
||
|
|
raise DocForgeError(
|
||
|
|
"missing_index",
|
||
|
|
"Derived cache root does not exist",
|
||
|
|
) from error
|
||
|
|
except OSError as error:
|
||
|
|
raise DocForgeError(
|
||
|
|
"path_escape",
|
||
|
|
"Derived cache root cannot be opened safely",
|
||
|
|
) from error
|
||
|
|
try:
|
||
|
|
opened_status = os.fstat(directory_fd)
|
||
|
|
if opened_status.st_dev != path_status.st_dev or opened_status.st_ino != path_status.st_ino:
|
||
|
|
raise DocForgeError(
|
||
|
|
"path_escape",
|
||
|
|
"Derived cache root changed while opening",
|
||
|
|
)
|
||
|
|
except Exception:
|
||
|
|
os.close(directory_fd)
|
||
|
|
raise
|
||
|
|
return directory_fd
|
||
|
|
|
||
|
|
|
||
|
|
def require_bound_directory(path: Path, directory_fd: int) -> None:
|
||
|
|
"""Require a path to still name the exact opened real directory."""
|
||
|
|
|
||
|
|
try:
|
||
|
|
path_status = path.lstat()
|
||
|
|
opened_status = os.fstat(directory_fd)
|
||
|
|
if (
|
||
|
|
stat.S_ISLNK(path_status.st_mode)
|
||
|
|
or not stat.S_ISDIR(path_status.st_mode)
|
||
|
|
or path.resolve(strict=True) != path
|
||
|
|
or opened_status.st_dev != path_status.st_dev
|
||
|
|
or opened_status.st_ino != path_status.st_ino
|
||
|
|
):
|
||
|
|
raise DocForgeError(
|
||
|
|
"path_escape",
|
||
|
|
"Derived cache root changed during publication",
|
||
|
|
)
|
||
|
|
except FileNotFoundError as error:
|
||
|
|
raise DocForgeError(
|
||
|
|
"path_escape",
|
||
|
|
"Derived cache root disappeared during publication",
|
||
|
|
) from error
|