Added --archive-url, --archive-file and --archive-extract. This adds an
[0publish.git] / unpack.py
blob80a1118b5b88c1754ece562713d415c16205a8da
1 # This is a copy of the new, unreleased, version in 0launch. Once the new
2 # version is out, we can drop this copy.
4 import os
5 import shutil
6 import traceback
7 from tempfile import mkdtemp, mkstemp
8 import sha
9 import re
10 from logging import debug, info, warn
11 from zeroinstall import SafeException
13 _recent_gnu_tar = None
14 def recent_gnu_tar():
15 global _recent_gnu_tar
16 if _recent_gnu_tar is None:
17 _recent_gnu_tar = False
18 version = os.popen('tar --version 2>&1').next()
19 if '(GNU tar)' in version:
20 try:
21 version = version.split(')', 1)[1].strip()
22 assert version
23 version = map(int, version.split('.'))
24 _recent_gnu_tar = version > [1, 13, 92]
25 except:
26 warn("Failed to extract GNU tar version number")
27 debug("Recent GNU tar = %s", _recent_gnu_tar)
28 return _recent_gnu_tar
30 def unpack_archive(url, data, destdir, extract = None):
31 """Unpack stream 'data' into directory 'destdir'. If extract is given, extract just
32 that sub-directory from the archive. Works out the format from the name."""
33 url = url.lower()
34 if url.endswith('.tar.bz2'):
35 extract_tar(data, destdir, extract, '--bzip2')
36 elif url.endswith('.rpm'):
37 extract_rpm(data, destdir, extract)
38 elif url.endswith('.tar.gz') or url.endswith('.tgz'):
39 extract_tar(data, destdir, extract, '-z')
40 else:
41 raise SafeException('Unknown extension on "%s"; I only know .tgz, .tar.bz2 and .rpm' % url)
43 def extract_rpm(stream, destdir, extract = None):
44 if extract:
45 raise SafeException('Sorry, but the "extract" attribute is not yet supported for RPMs')
46 fd, cpiopath = mkstemp('-rpm-tmp')
47 try:
48 child = os.fork()
49 if child == 0:
50 try:
51 try:
52 os.dup2(stream.fileno(), 0)
53 os.dup2(fd, 1)
54 os.execlp('rpm2cpio', 'rpm2cpio', '-')
55 except:
56 traceback.print_exc()
57 finally:
58 os._exit(1)
59 id, status = os.waitpid(child, 0)
60 assert id == child
61 if status != 0:
62 raise SafeException("rpm2cpio failed; can't unpack RPM archive; exit code %d" % status)
63 os.close(fd)
64 fd = None
65 args = ['cpio', '-mid', '--quiet']
66 _extract(file(cpiopath), destdir, args)
67 # Set the mtime of every directory under 'tmp' to 0, since cpio doesn't
68 # preserve directory mtimes.
69 os.path.walk(destdir, lambda arg, dirname, names: os.utime(dirname, (0, 0)), None)
70 finally:
71 if fd is not None:
72 os.close(fd)
73 os.unlink(cpiopath)
75 def extract_tar(stream, destdir, extract, decompress):
76 if extract:
77 # Limit the characters we accept, to avoid sending dodgy
78 # strings to tar
79 if not re.match('^[a-zA-Z0-9][- _a-zA-Z0-9.]*$', extract):
80 raise SafeException('Illegal character in extract attribute')
82 if recent_gnu_tar():
83 args = ['tar', decompress, '-x', '--no-same-owner', '--no-same-permissions']
84 else:
85 args = ['tar', decompress, '-xf', '-']
87 if extract:
88 args.append(extract)
90 _extract(stream, destdir, args)
92 def _extract(stream, destdir, command):
93 """Run execvp('command') inside destdir in a child process, with a
94 rewound stream as stdin."""
95 child = os.fork()
96 if child == 0:
97 try:
98 try:
99 os.chdir(destdir)
100 stream.seek(0)
101 os.dup2(stream.fileno(), 0)
102 os.execvp(command[0], command)
103 except:
104 traceback.print_exc()
105 finally:
106 os._exit(1)
107 id, status = os.waitpid(child, 0)
108 assert id == child
109 if status != 0:
110 raise SafeException('Failed to extract archive; exit code %d' % status)