Ticket #16082: django-file_storage-makedirs-race.patch

File django-file_storage-makedirs-race.patch, 3.8 KB (added by pjdelport, 4 years ago)
  • django/core/files/storage.py

    diff -r e09a8491e49d django/core/files/storage.py
    a b  
    161161    def _save(self, name, content):
    162162        full_path = self.path(name)
    163163
     164        # Create any intermediate directories that do not exist.
     165        # Note that there is a race between os.path.exists and os.makedirs:
     166        # if os.makedirs fails with EEXIST, the directory was created
     167        # concurrently, and we can continue normally.
    164168        directory = os.path.dirname(full_path)
    165169        if not os.path.exists(directory):
    166             os.makedirs(directory)
    167         elif not os.path.isdir(directory):
     170            try:
     171                os.makedirs(directory)
     172            except OSError, e:
     173                if e.errno != errno.EEXIST:
     174                    raise
     175        if not os.path.isdir(directory):
    168176            raise IOError("%s exists and is not a directory." % directory)
    169177
    170178        # There's a potential race condition between get_available_name and
  • tests/regressiontests/file_storage/tests.py

    diff -r e09a8491e49d tests/regressiontests/file_storage/tests.py
    a b  
    11# -*- coding: utf-8 -*-
    22import os
     3import errno
    34import shutil
    45import sys
    56import tempfile
     
    186187
    187188        self.storage.delete(storage_f_name)
    188189
     190    def test_file_save_with_path(self):
     191        """
     192        Saving a pathname should create intermediate directories as necessary.
     193        """
     194        self.assertFalse(self.storage.exists('path/to'))
     195        self.storage.save('path/to/test.file', ContentFile('file saved with path'))
     196
     197        self.assertTrue(self.storage.exists('path/to'))
     198        self.assertEqual(self.storage.open('path/to/test.file').read(), 'file saved with path')
     199
     200        self.assertTrue(os.path.exists(os.path.join(self.temp_dir, 'path', 'to', 'test.file')))
     201
     202        self.storage.delete('path/to/test.file')
     203
    189204    def test_file_path(self):
    190205        """
    191206        File storage returns the full path of a file
     
    282297                         temp_storage.path(mixed_case))
    283298        temp_storage.delete(mixed_case)
    284299
     300    def test_makedirs_race_handling(self):
     301        """
     302        File storage should be robust against directory creation race conditions.
     303        """
     304        # Monkey-patch os.makedirs, to simulate a normal call, a raced call,
     305        # and an error.
     306        def fake_makedirs(path):
     307            if path == os.path.join(self.temp_dir, 'normal'):
     308                os.mkdir(path)
     309            elif path == os.path.join(self.temp_dir, 'raced'):
     310                os.mkdir(path)
     311                raise OSError(errno.EEXIST, 'simulated EEXIST')
     312            elif path == os.path.join(self.temp_dir, 'error'):
     313                raise OSError(errno.EACCES, 'simulated EACCES')
     314            else:
     315                self.fail('unexpected argument %r' % (path,))
     316
     317        real_makedirs = os.makedirs
     318        try:
     319            os.makedirs = fake_makedirs
     320
     321            self.storage.save('normal/test.file', ContentFile('saved normally'))
     322            self.assertEqual(self.storage.open('normal/test.file').read(), 'saved normally')
     323
     324            self.storage.save('raced/test.file', ContentFile('saved with race'))
     325            self.assertEqual(self.storage.open('raced/test.file').read(), 'saved with race')
     326
     327            # Check that OSErrors aside from EEXIST are still raised.
     328            self.assertRaises(OSError,
     329                lambda: self.storage.save('error/test.file', ContentFile('not saved')))
     330        finally:
     331            os.makedirs = real_makedirs
     332
    285333class CustomStorage(FileSystemStorage):
    286334    def get_available_name(self, name):
    287335        """
Back to Top