diff options
Diffstat (limited to 'django')
| -rw-r--r-- | django/core/cache/backends/filebased.py | 10 | ||||
| -rw-r--r-- | django/core/files/storage/filesystem.py | 13 | ||||
| -rw-r--r-- | django/utils/_os.py | 58 |
3 files changed, 65 insertions, 16 deletions
diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py index 215fefbcc0..32a231d125 100644 --- a/django/core/cache/backends/filebased.py +++ b/django/core/cache/backends/filebased.py @@ -10,6 +10,7 @@ import zlib from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache from django.core.files import locks from django.core.files.move import file_move_safe +from django.utils._os import safe_makedirs from django.utils.crypto import md5 @@ -114,13 +115,10 @@ class FileBasedCache(BaseCache): self._delete(fname) def _createdir(self): - # Set the umask because os.makedirs() doesn't apply the "mode" argument + # Workaround because os.makedirs() doesn't apply the "mode" argument # to intermediate-level directories. - old_umask = os.umask(0o077) - try: - os.makedirs(self._dir, 0o700, exist_ok=True) - finally: - os.umask(old_umask) + # https://github.com/python/cpython/issues/86533 + safe_makedirs(self._dir, mode=0o700, exist_ok=True) def _key_to_file(self, key, version=None): """ diff --git a/django/core/files/storage/filesystem.py b/django/core/files/storage/filesystem.py index 85fc4eff9f..90a7adde8e 100644 --- a/django/core/files/storage/filesystem.py +++ b/django/core/files/storage/filesystem.py @@ -6,7 +6,7 @@ from django.conf import settings from django.core.files import File, locks from django.core.files.move import file_move_safe from django.core.signals import setting_changed -from django.utils._os import safe_join +from django.utils._os import safe_join, safe_makedirs from django.utils.deconstruct import deconstructible from django.utils.encoding import filepath_to_uri from django.utils.functional import cached_property @@ -74,15 +74,10 @@ class FileSystemStorage(Storage, StorageSettingsMixin): directory = os.path.dirname(full_path) try: if self.directory_permissions_mode is not None: - # Set the umask because os.makedirs() doesn't apply the "mode" + # Workaround because os.makedirs() doesn't apply the "mode" # argument to intermediate-level directories. - old_umask = os.umask(0o777 & ~self.directory_permissions_mode) - try: - os.makedirs( - directory, self.directory_permissions_mode, exist_ok=True - ) - finally: - os.umask(old_umask) + # https://github.com/python/cpython/issues/86533 + safe_makedirs(directory, self.directory_permissions_mode, exist_ok=True) else: os.makedirs(directory, exist_ok=True) except FileExistsError: diff --git a/django/utils/_os.py b/django/utils/_os.py index f85fe09248..8054fadf4c 100644 --- a/django/utils/_os.py +++ b/django/utils/_os.py @@ -1,11 +1,67 @@ import os import tempfile -from os.path import abspath, dirname, join, normcase, sep +from os.path import abspath, curdir, dirname, join, normcase, sep from pathlib import Path from django.core.exceptions import SuspiciousFileOperation +# Copied verbatim (minus `os.path` fixes) from: +# https://github.com/python/cpython/pull/23901. +# Python versions >= PY315 may include this fix, so periodic checks are needed +# to remove this vendored copy of `makedirs` once solved upstream. +def makedirs(name, mode=0o777, exist_ok=False, *, parent_mode=None): + """makedirs(name [, mode=0o777][, exist_ok=False][, parent_mode=None]) + + Super-mkdir; create a leaf directory and all intermediate ones. Works like + mkdir, except that any intermediate path segment (not just the rightmost) + will be created if it does not exist. If the target directory already + exists, raise an OSError if exist_ok is False. Otherwise no exception is + raised. If parent_mode is not None, it will be used as the mode for any + newly-created, intermediate-level directories. Otherwise, intermediate + directories are created with the default permissions (respecting umask). + This is recursive. + + """ + head, tail = os.path.split(name) + if not tail: + head, tail = os.path.split(head) + if head and tail and not os.path.exists(head): + try: + if parent_mode is not None: + makedirs( + head, mode=parent_mode, exist_ok=exist_ok, parent_mode=parent_mode + ) + else: + makedirs(head, exist_ok=exist_ok) + except FileExistsError: + # Defeats race condition when another thread created the path + pass + cdir = curdir + if isinstance(tail, bytes): + cdir = bytes(curdir, "ASCII") + if tail == cdir: # xxx/newdir/. exists if xxx/newdir exists + return + try: + os.mkdir(name, mode) + # PY315: The call to `chmod()` is not in the CPython proposed code. + # Apply `chmod()` after `mkdir()` to enforce the exact requested + # permissions, since the kernel masks the mode argument with the + # process umask. This guarantees consistent directory permissions + # without mutating global umask state. + os.chmod(name, mode) + except OSError: + # Cannot rely on checking for EEXIST, since the operating system + # could give priority to other errors like EACCES or EROFS + if not exist_ok or not os.path.isdir(name): + raise + + +def safe_makedirs(name, mode=0o777, exist_ok=False): + """Create directories recursively with explicit `mode` on each level.""" + makedirs(name=name, mode=mode, exist_ok=exist_ok, parent_mode=mode) + + def safe_join(base, *paths): """ Join one or more path components to the base path component intelligently. |
