1 # Copyright (C) 2006, Thomas Leonard
2 # See the README file for details, or visit http://0install.net.
3 # Copied from the injector, as versions before 0.18 don't have it.
8 from tempfile
import mkdtemp
, mkstemp
11 from logging
import debug
, info
, warn
12 from zeroinstall
import SafeException
14 _recent_gnu_tar
= None
16 global _recent_gnu_tar
17 if _recent_gnu_tar
is None:
18 _recent_gnu_tar
= False
19 version
= os
.popen('tar --version 2>&1').next()
20 if '(GNU tar)' in version
:
22 version
= version
.split(')', 1)[1].strip()
24 version
= map(int, version
.split('.'))
25 _recent_gnu_tar
= version
> [1, 13, 92]
27 warn("Failed to extract GNU tar version number")
28 debug("Recent GNU tar = %s", _recent_gnu_tar
)
29 return _recent_gnu_tar
31 def _find_in_path(prog
):
32 for d
in os
.environ
['PATH'].split(':'):
33 path
= os
.path
.join(d
, prog
)
34 if os
.path
.isfile(path
):
37 _pola_run
= _find_in_path('pola-run')
39 info('Found pola-run: %s', _pola_run
)
41 info('pola-run not found; archive extraction will not be sandboxed')
43 def _exec_maybe_sandboxed(writable
, prog
, *args
):
44 """execlp prog, with (only) the 'writable' directory writable if sandboxing is available.
45 If no sandbox is available, run without a sandbox."""
46 prog_path
= _find_in_path(prog
)
48 os
.execlp(prog_path
, prog_path
, *args
)
49 # We have pola-shell :-)
50 pola_args
= ['--prog', prog_path
, '-f', '/']
52 pola_args
+= ['-a', a
]
54 pola_args
+= ['-fw', writable
]
55 os
.execl(_pola_run
, _pola_run
, *pola_args
)
57 def unpack_archive(url
, data
, destdir
, extract
= None):
58 """Unpack stream 'data' into directory 'destdir'. If extract is given, extract just
59 that sub-directory from the archive. Works out the format from the name."""
61 if url
.endswith('.tar.bz2'):
62 extract_tar(data
, destdir
, extract
, '--bzip2')
63 elif url
.endswith('.deb'):
64 extract_deb(data
, destdir
, extract
)
65 elif url
.endswith('.rpm'):
66 extract_rpm(data
, destdir
, extract
)
67 elif url
.endswith('.tar.gz') or url
.endswith('.tgz'):
68 extract_tar(data
, destdir
, extract
, '-z')
70 raise SafeException('Unknown extension on "%s"; I only know .tgz, .tar.bz2 and .rpm' % url
)
72 def extract_deb(stream
, destdir
, extract
= None):
74 raise SafeException('Sorry, but the "extract" attribute is not yet supported for Debs')
77 # ar can't read from stdin, so make a copy...
78 deb_copy_name
= os
.path
.join(destdir
, 'archive.deb')
79 deb_copy
= file(deb_copy_name
, 'w')
80 shutil
.copyfileobj(stream
, deb_copy
)
82 _extract(stream
, destdir
, ('ar', 'x', 'archive.deb', 'data.tar.gz'))
83 os
.unlink(deb_copy_name
)
84 data_name
= os
.path
.join(destdir
, 'data.tar.gz')
85 data_stream
= file(data_name
)
87 _extract(data_stream
, destdir
, ('tar', 'xzf', '-'))
89 def extract_rpm(stream
, destdir
, extract
= None):
91 raise SafeException('Sorry, but the "extract" attribute is not yet supported for RPMs')
92 fd
, cpiopath
= mkstemp('-rpm-tmp')
98 os
.dup2(stream
.fileno(), 0)
100 _exec_maybe_sandboxed(None, 'rpm2cpio', '-')
102 traceback
.print_exc()
105 id, status
= os
.waitpid(child
, 0)
108 raise SafeException("rpm2cpio failed; can't unpack RPM archive; exit code %d" % status
)
111 args
= ['cpio', '-mid', '--quiet']
112 _extract(file(cpiopath
), destdir
, args
)
113 # Set the mtime of every directory under 'tmp' to 0, since cpio doesn't
114 # preserve directory mtimes.
115 os
.path
.walk(destdir
, lambda arg
, dirname
, names
: os
.utime(dirname
, (0, 0)), None)
121 def extract_tar(stream
, destdir
, extract
, decompress
):
123 # Limit the characters we accept, to avoid sending dodgy
125 if not re
.match('^[a-zA-Z0-9][- _a-zA-Z0-9.]*$', extract
):
126 raise SafeException('Illegal character in extract attribute')
129 args
= ['tar', decompress
, '-x', '--no-same-owner', '--no-same-permissions']
131 args
= ['tar', decompress
, '-xf', '-']
136 _extract(stream
, destdir
, args
)
138 def _extract(stream
, destdir
, command
):
139 """Run execvp('command') inside destdir in a child process, with a
140 rewound stream as stdin."""
147 os
.dup2(stream
.fileno(), 0)
148 _exec_maybe_sandboxed(destdir
, *command
)
150 traceback
.print_exc()
153 id, status
= os
.waitpid(child
, 0)
156 raise SafeException('Failed to extract archive; exit code %d' % status
)