-
Notifications
You must be signed in to change notification settings - Fork 308
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
TemporaryDirectory fails to cleanup read-only files (Windows, py<3.8)
On python < 3.8, TemporaryDirectory fails when cleaning up dirs containing read-only files on Windows.
- Loading branch information
Showing
3 changed files
with
86 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import os | ||
import stat | ||
import sys | ||
import tempfile | ||
from functools import partial | ||
from itertools import chain | ||
|
||
|
||
def _ensure_tree_writeable(path: str) -> None: | ||
"""Attempt to ensure that all files in the tree rooted at path are writeable.""" | ||
dirscans = [] | ||
|
||
def fix_mode(path, statfunc): | ||
try: | ||
# paranoia regarding symlink attacks | ||
current_mode = statfunc(follow_symlinks=False).st_mode | ||
if not stat.S_ISLNK(current_mode): | ||
isdir = stat.S_ISDIR(current_mode) | ||
fixed_mode = current_mode | (0o700 if isdir else 0o200) | ||
if current_mode != fixed_mode: | ||
os.chmod(path, fixed_mode) | ||
if isdir: | ||
dirscans.append(os.scandir(path)) | ||
except FileNotFoundError: | ||
pass | ||
|
||
fix_mode(path, partial(os.stat, path)) | ||
for entry in chain.from_iterable(dirscans): | ||
fix_mode(entry.path, entry.stat) | ||
|
||
|
||
class FixedTemporaryDirectory(tempfile.TemporaryDirectory): | ||
"""A version of tempfile.TemporaryDirectory that works if dir contains read-only files. | ||
On python < 3.8 under Windows, if any read-only files are created | ||
in a TemporaryDirectory, TemporaryDirectory will throw an | ||
exception when it tries to remove them on cleanup. See | ||
https://bugs.python.org/issue26660 | ||
This can create issues, e.g., with temporary git repositories since | ||
git creates read-only files in its object store. | ||
""" | ||
|
||
def cleanup(self) -> None: | ||
_ensure_tree_writeable(self.name) | ||
super().cleanup() | ||
|
||
|
||
if sys.version_info >= (3, 8): | ||
TemporaryDirectory = tempfile.TemporaryDirectory | ||
else: | ||
TemporaryDirectory = FixedTemporaryDirectory |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import os | ||
from pathlib import Path | ||
|
||
import pytest | ||
|
||
from lektor.compat import _ensure_tree_writeable | ||
from lektor.compat import FixedTemporaryDirectory | ||
from lektor.compat import TemporaryDirectory | ||
|
||
|
||
def test_ensure_tree_writeable(tmp_path): | ||
topdir = tmp_path / "topdir" | ||
subdir = topdir / "subdir" | ||
regfile = subdir / "regfile" | ||
subdir.mkdir(parents=True) | ||
regfile.touch(mode=0) | ||
subdir.chmod(0) | ||
topdir.chmod(0) | ||
|
||
_ensure_tree_writeable(topdir) | ||
|
||
for p in topdir, subdir, regfile: | ||
assert os.access(p, os.W_OK) | ||
|
||
|
||
@pytest.mark.parametrize("tmpdir_class", [FixedTemporaryDirectory, TemporaryDirectory]) | ||
def test_TemporaryDirectory(tmpdir_class): | ||
with tmpdir_class() as tmpdir: | ||
file = Path(tmpdir, "test-file") | ||
file.touch(mode=0) | ||
os.chmod(tmpdir, 0) | ||
assert not os.path.exists(tmpdir) |