Skip to content

Commit

Permalink
Raise exception if trying to create file in read-only directory (Posi…
Browse files Browse the repository at this point in the history
…x only)

- fixed assertRaisesIOError and assertRaisesOSError
- never allow adding of already existing file or directory
- fixes pytest-dev#203
  • Loading branch information
mrbean-bremen committed Jun 14, 2017
1 parent 94f9019 commit 3c1611b
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 11 deletions.
2 changes: 1 addition & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The release versions are PyPi releases.
#### Infrastructure

#### Fixes

* Creating files in read-only directory was possible ([#203](../../issues/203))

## [Version 3.2](https://pypi.python.org/pypi/pyfakefs/3.2)

Expand Down
30 changes: 30 additions & 0 deletions fake_filesystem_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,14 @@ def assertModeEqual(self, expected, actual):
def assertRaisesIOError(self, subtype, expression, *args, **kwargs):
try:
expression(*args, **kwargs)
self.fail('No exception was raised, IOError expected')
except IOError as exc:
self.assertEqual(exc.errno, subtype)

def assertRaisesOSError(self, subtype, expression, *args, **kwargs):
try:
expression(*args, **kwargs)
self.fail('No exception was raised, OSError expected')
except OSError as exc:
if self.is_windows and sys.version_info < (3, 0):
self.assertEqual(exc.winerror, subtype)
Expand Down Expand Up @@ -96,6 +98,17 @@ def testGetEntry(self):
self.fake_dir.AddEntry(self.fake_file)
self.assertEqual(self.fake_file, self.fake_dir.GetEntry('foobar'))

def testGetPath(self):
self.fake_dir.AddEntry(self.fake_file)
self.assertEqual('somedir' + os.sep + 'foobar', self.fake_file.GetPath())

def testGetPathWithFilesystem(self):
filesystem = fake_filesystem.FakeFilesystem(path_separator='/')
fake_dir = filesystem.CreateDirectory('somedir')
self.fake_file.filesystem = filesystem
fake_dir.AddEntry(self.fake_file)
self.assertEqual('/somedir/foobar', self.fake_file.GetPath())

def testRemoveEntry(self):
self.fake_dir.AddEntry(self.fake_file)
self.assertEqual(self.fake_file, self.fake_dir.GetEntry('foobar'))
Expand Down Expand Up @@ -427,6 +440,21 @@ def testCreateDirectoryAlreadyExistsError(self):
self.filesystem.CreateDirectory(path)
self.assertRaisesOSError(errno.EEXIST, self.filesystem.CreateDirectory, path)

def testCreateFileInReadOnlyDirectoryRaisesInPosix(self):
self.filesystem.is_windows_fs = False
dir_path = '/foo/bar'
self.filesystem.CreateDirectory(dir_path, perm_bits=0o555)
file_path = dir_path + '/baz'
self.assertRaisesOSError(errno.EACCES, self.filesystem.CreateFile, file_path)

def testCreateFileInReadOnlyDirectoryPossibleInWindows(self):
self.filesystem.is_windows_fs = True
dir_path = 'C:/foo/bar'
self.filesystem.CreateDirectory(dir_path, perm_bits=0o555)
file_path = dir_path + '/baz'
self.filesystem.CreateFile(file_path)
self.assertTrue(self.filesystem.Exists(file_path))

def testCreateFileInCurrentDirectory(self):
path = 'foo'
contents = 'dummy data'
Expand Down Expand Up @@ -1418,6 +1446,7 @@ def testMkdirRaisesWithDoubleDots(self):

def testMkdirRaisesIfParentIsReadOnly(self):
"""mkdir raises exception if parent is read only."""
self.filesystem.is_windows_fs = False
directory = '/a'
self.os.mkdir(directory)

Expand Down Expand Up @@ -1446,6 +1475,7 @@ def testMakedirsRaisesIfParentIsFile(self):
def testMakedirsRaisesIfAccessDenied(self):
"""makedirs raises exception if access denied."""
directory = '/a'
self.filesystem.is_windows_fs = False
self.os.mkdir(directory)

# Change directory permissions to be read only.
Expand Down
50 changes: 40 additions & 10 deletions pyfakefs/fake_filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ def __init__(self, name, st_mode=stat.S_IFREG | PERM_DEF_FILE,
len(self._byte_contents) if self._byte_contents is not None else 0)
self.filesystem = filesystem
self.epoch = 0
self.parent_dir = None

@property
def byte_contents(self):
Expand Down Expand Up @@ -465,6 +466,18 @@ def GetSize(self):
"""
return self.st_size

def GetPath(self):
"""Return the full path of the current object."""
names = []
obj = self
while obj:
names.insert(0, obj.name)
obj = obj.parent_dir
if self.filesystem:
sep = self.filesystem._path_separator(self.name)
return self.filesystem.NormalizePath(sep.join(names[1:]))
return os.sep.join(names)

def SetSize(self, st_size):
"""Resizes file content, padding with nulls if new size exceeds the old.
Expand Down Expand Up @@ -616,13 +629,29 @@ def ordered_dirs(self):
return [item[0] for item in sorted(
self.byte_contents.items(), key=lambda entry: entry[1].st_ino)]

def _is_windows(self):
return self.filesystem.is_windows_fs if self.filesystem else sys.platform == 'win32'

def AddEntry(self, path_object):
"""Adds a child FakeFile to this directory.
Args:
path_object: FakeFile instance to add as a child of this directory.
path_object: FakeFile instance to add as a child of this directory.
Raises:
OSError: if the directory has no write permission (Posix only)
OSError: if the file or directory to be added already exists
"""
if not self.st_mode & PERM_WRITE and not self._is_windows():
raise OSError(errno.EACCES, 'Permission Denied', self.GetPath())

if path_object.name in self.contents:
raise OSError(errno.EEXIST,
'Object already exists in fake filesystem',
self.GetPath())

self.contents[path_object.name] = path_object
path_object.parent_dir = self
path_object.st_nlink += 1
path_object.st_dev = self.st_dev
if self.filesystem and path_object.st_nlink == 1:
Expand Down Expand Up @@ -1872,15 +1901,22 @@ def CreateDirectory(self, directory_path, perm_bits=PERM_DEF):
path_components = self.GetPathComponents(directory_path)
current_dir = self.root

new_dirs = []
for component in path_components:
directory = self._DirectoryContent(current_dir, component)[1]
if not directory:
new_dir = FakeDirectory(component, perm_bits, filesystem=self)
new_dir = FakeDirectory(component, filesystem=self)
new_dirs.append(new_dir)
current_dir.AddEntry(new_dir)
current_dir = new_dir
else:
current_dir = directory

# set the permission after creating the directories
# to allow directory creation inside a read-only directory
for new_dir in new_dirs:
new_dir.st_mode = stat.S_IFDIR | perm_bits

self.last_ino += 1
current_dir.SetIno(self.last_ino)
return current_dir
Expand Down Expand Up @@ -2197,12 +2233,9 @@ def MakeDirectory(self, dir_name, mode=PERM_DEF):
if self.Exists(dir_name):
raise OSError(errno.EEXIST, 'Fake object already exists', dir_name)
head, tail = self.SplitPath(dir_name)
directory_object = self.GetObject(head)
if not directory_object.st_mode & PERM_WRITE:
raise OSError(errno.EACCES, 'Permission Denied', dir_name)

self.AddObject(
head, FakeDirectory(tail, mode & ~self.umask))
head, FakeDirectory(tail, mode & ~self.umask, filesystem=self))

def MakeDirectories(self, dir_name, mode=PERM_DEF, exist_ok=False):
"""Create a leaf Fake directory and create any non-existent parent dirs.
Expand All @@ -2228,10 +2261,7 @@ def MakeDirectories(self, dir_name, mode=PERM_DEF, exist_ok=False):
current_dir = self.root
for component in path_components:
if component not in current_dir.contents:
if not current_dir.st_mode & PERM_WRITE:
raise OSError(errno.EACCES, 'Permission Denied', dir_name)
else:
break
break
else:
current_dir = current_dir.contents[component]
try:
Expand Down

0 comments on commit 3c1611b

Please sign in to comment.