Skip to content

Commit

Permalink
Strip common path prefixes from linkname as well as name when extract…
Browse files Browse the repository at this point in the history
…ing a tarfile (#688)

Stripping common path prefixes from the member name breaks hardlink name resolution unless you perform the same operation on the member linknames

LP: #1606700
  • Loading branch information
mhall119 authored and sergiusens committed Aug 3, 2016
1 parent 36c1c72 commit 9b240ef
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 4 deletions.
16 changes: 12 additions & 4 deletions snapcraft/internal/sources.py
Expand Up @@ -296,17 +296,25 @@ def filter_members(tar):
for m in members:
if m.name == common:
continue
if m.name.startswith(common + '/'):
m.name = m.name[len(common + '/'):]
# strip leading '/', './' or '../' as many times as needed
m.name = re.sub(r'^(\.{0,2}/)*', r'', m.name)
self._strip_prefix(common, m)
# We mask all files to be writable to be able to easily
# extract on top.
m.mode = m.mode | 0o200
yield m

tar.extractall(members=filter_members(tar), path=dst)

def _strip_prefix(self, common, member):
if member.name.startswith(common + '/'):
member.name = member.name[len(common + '/'):]
# strip leading '/', './' or '../' as many times as needed
member.name = re.sub(r'^(\.{0,2}/)*', r'', member.name)
# do the same for linkname if this is a hardlink
if member.islnk() and not member.issym():
if member.linkname.startswith(common + '/'):
member.linkname = member.linkname[len(common + '/'):]
member.linkname = re.sub(r'^(\.{0,2}/)*', r'', member.linkname)


class Zip(FileBase):

Expand Down
111 changes: 111 additions & 0 deletions snapcraft/tests/test_plugin_tar_content.py
Expand Up @@ -82,3 +82,114 @@ class Options:

self.assertTrue(
os.path.exists(os.path.join(self.build_prefix)))

def test_common_prefix_stripping(self):
class Options:
source = os.path.join('src', 'test.tar')
destination = 'destdir1'

# create tar file for testing
os.makedirs('src/test_prefix')
file_to_tar = os.path.join('src', 'test_prefix', 'test.txt')
open(file_to_tar, 'w').close()
tar = tarfile.open(os.path.join('src', 'test.tar'), 'w')
tar.add(file_to_tar)
tar.close()

t = TarContentPlugin('tar_content', Options(),
self.project_options)
os.mkdir(t.sourcedir)
t.pull()
t.build()

# the 'test_prefix' part of the path should have been removed
self.assertTrue(
os.path.exists(os.path.join(self.install_prefix, 'destdir1')))
self.assertTrue(
os.path.exists(
os.path.join(self.install_prefix, 'destdir1/test.txt')))

def test_common_prefix_stripping_symlink(self):
class Options:
source = os.path.join('src', 'test.tar')
destination = 'destdir1'

# create tar file for testing
os.makedirs('src/test_prefix')
file_to_tar = os.path.join('src', 'test_prefix', 'test.txt')
open(file_to_tar, 'w').close()

file_to_link = os.path.join('src', 'test_prefix', 'link.txt')
os.symlink("./test.txt", file_to_link)
self.assertTrue(os.path.islink(file_to_link))

def check_for_symlink(tarinfo):
self.assertTrue(tarinfo.issym())
self.assertEqual(file_to_link, tarinfo.name)
self.assertEqual(file_to_tar, os.path.normpath(
os.path.join(
os.path.dirname(file_to_tar), tarinfo.linkname)))
return tarinfo

tar = tarfile.open(os.path.join('src', 'test.tar'), 'w')
tar.add(file_to_tar)
tar.add(file_to_link, filter=check_for_symlink)
tar.close()

t = TarContentPlugin('tar_content', Options(),
self.project_options)
os.mkdir(t.sourcedir)
t.pull()
t.build()

# the 'test_prefix' part of the path should have been removed
self.assertTrue(
os.path.exists(os.path.join(self.install_prefix, 'destdir1')))
self.assertTrue(
os.path.exists(
os.path.join(self.install_prefix, 'destdir1/test.txt')))
self.assertTrue(
os.path.exists(
os.path.join(self.install_prefix, 'destdir1/link.txt')))

def test_common_prefix_stripping_hardlink(self):
class Options:
source = os.path.join('src', 'test.tar')
destination = 'destdir1'

# create tar file for testing
os.makedirs('src/test_prefix')
file_to_tar = os.path.join('src', 'test_prefix', 'test.txt')
open(file_to_tar, 'w').close()

file_to_link = os.path.join('src', 'test_prefix', 'link.txt')
os.link(file_to_tar, file_to_link)
self.assertTrue(os.path.exists(file_to_link))

def check_for_hardlink(tarinfo):
self.assertTrue(tarinfo.islnk())
self.assertFalse(tarinfo.issym())
self.assertEqual(file_to_link, tarinfo.name)
self.assertEqual(file_to_tar, tarinfo.linkname)
return tarinfo

tar = tarfile.open(os.path.join('src', 'test.tar'), 'w')
tar.add(file_to_tar)
tar.add(file_to_link, filter=check_for_hardlink)
tar.close()

t = TarContentPlugin('tar_content', Options(),
self.project_options)
os.mkdir(t.sourcedir)
t.pull()
t.build()

# the 'test_prefix' part of the path should have been removed
self.assertTrue(
os.path.exists(os.path.join(self.install_prefix, 'destdir1')))
self.assertTrue(
os.path.exists(
os.path.join(self.install_prefix, 'destdir1/test.txt')))
self.assertTrue(
os.path.exists(
os.path.join(self.install_prefix, 'destdir1/link.txt')))

0 comments on commit 9b240ef

Please sign in to comment.