Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-74623: Avoid untar errors when write-protected files are tarred twice #103174

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 11 additions & 18 deletions Lib/tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2034,38 +2034,31 @@ def addfile(self, tarinfo, fileobj=None):

def extractall(self, path=".", members=None, *, numeric_owner=False):
"""Extract all members from the archive to the current working
directory and set owner, modification time and permissions on
directories afterwards. `path' specifies a different directory
directory and set owner, modification time and permissions
afterwards. `path' specifies a different directory
to extract to. `members' is optional and must be a subset of the
list returned by getmembers(). If `numeric_owner` is True, only
the numbers for user/group names are used and not the names.
"""
directories = []

if members is None:
members = self

for tarinfo in members:
if tarinfo.isdir():
# Extract directories with a safe mode.
directories.append(tarinfo)
tarinfo = copy.copy(tarinfo)
tarinfo.mode = 0o700
# Extract tarfile contents with a safe mode.
tarinfo = copy.copy(tarinfo)
tarinfo.mode = 0o700
# Do not set_attrs directories, as we will do that further down
self.extract(tarinfo, path, set_attrs=not tarinfo.isdir(),
numeric_owner=numeric_owner)

# Reverse sort directories.
directories.sort(key=lambda a: a.name)
directories.reverse()

# Set correct owner, mtime and filemode on directories.
for tarinfo in directories:
dirpath = os.path.join(path, tarinfo.name)
# Set correct owner, mtime and filemode
for tarinfo in sorted (members, key=lambda a: a.name, reverse=True):
member_path = os.path.join(path, tarinfo.name)
try:
self.chown(tarinfo, dirpath, numeric_owner=numeric_owner)
self.utime(tarinfo, dirpath)
self.chmod(tarinfo, dirpath)
self.chown(tarinfo, member_path, numeric_owner=numeric_owner)
self.utime(tarinfo, member_path)
self.chmod(tarinfo, member_path)
except ExtractError as e:
if self.errorlevel > 1:
raise
Expand Down
23 changes: 23 additions & 0 deletions Lib/test/test_tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2976,6 +2976,29 @@ def test_keyword_only(self, mock_geteuid):
tarfl.extract, filename_1, TEMPDIR, False, True)


class ReadonlyArchivedFileTest(TarTest):

def setUp(self):
self.file_name = os.path.join(TEMPDIR, 'read_only_file.txt')
with open(self.file_name, 'w') as outfile:
outfile.write('')

os.chmod(self.file_name, 0o444)

def tearDown(self):
os.remove(self.file_name)

def test_extract_doubly_added_file(self):
# gh-74623: tarring a readonly file twice, then extracting,
# should succeed.
with tarfile.open(tmpname, 'w') as tarfl:
tarfl.add(self.file_name)
tarfl.add(self.file_name)

with tarfile.open(tmpname) as tarfl:
tarfl.extractall()


def setUpModule():
os_helper.unlink(TEMPDIR)
os.makedirs(TEMPDIR)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Avoid untar errors when write-protected files are tarred twice. Patch by
Catherine Devlin.