Skip to content

Commit 92d2772

Browse files
authored
rsync_remote: Fix a problem when receiving read-only directories. (#145)
Before this change, when the source directories hierarchy was read-only, the read-only mode would be preserved at the destination, preventing child directories to be recreated by a normal user (a permission denied error, EACCES would be raised). * execnet/rsync_remote.py (serve_rsync.receive_directory_structure): Bitwise OR to ensure the write bit is set on received directories. * testing/test_rsync.py (TestRSync) <test_read_only_directories>: New test.
1 parent 92d7226 commit 92d2772

File tree

2 files changed

+23
-2
lines changed

2 files changed

+23
-2
lines changed

execnet/rsync_remote.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ def receive_directory_structure(path, relcomponents):
3535
os.makedirs(path)
3636
mode = msg.pop(0)
3737
if mode:
38-
os.chmod(path, mode)
38+
# Ensure directories are writable, otherwise a
39+
# permission denied error (EACCES) would be raised
40+
# when attempting to receive read-only directory
41+
# structures.
42+
os.chmod(path, mode | 0o700)
3943
entrynames = {}
4044
for entryname in msg:
4145
destpath = os.path.join(path, entryname)
@@ -59,7 +63,7 @@ def receive_directory_structure(path, relcomponents):
5963
checksum = md5(f.read()).digest()
6064
f.close()
6165
elif msg_mode and msg_mode != st.st_mode:
62-
os.chmod(path, msg_mode)
66+
os.chmod(path, msg_mode | 0o700)
6367
return
6468
else:
6569
return # already fine

testing/test_rsync.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,23 @@ def test_permissions(self, dirs, gw1, gw2):
157157
mode = destdir.stat().mode
158158
assert mode & 511 == 504
159159

160+
@py.test.mark.skipif("sys.platform == 'win32' or getattr(os, '_name', '') == 'nt'")
161+
def test_read_only_directories(self, dirs, gw1):
162+
source = dirs.source
163+
dest = dirs.dest1
164+
source.ensure("sub", "subsub", dir=True)
165+
source.join("sub").chmod(0o500)
166+
source.join("sub", "subsub").chmod(0o500)
167+
168+
# The destination directories should be created with the write
169+
# permission forced, to avoid raising an EACCES error.
170+
rsync = RSync(source)
171+
rsync.add_target(gw1, dest)
172+
rsync.send()
173+
174+
assert dest.join("sub").stat().mode & 0o700
175+
assert dest.join("sub").join("subsub").stat().mode & 0o700
176+
160177
@needssymlink
161178
def test_symlink_rsync(self, dirs, gw1):
162179
source = dirs.source

0 commit comments

Comments
 (0)