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 mkstemp
10 from logging
import debug
, info
, warn
11 from zeroinstall
import SafeException
13 _recent_gnu_tar
= None
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
:
21 version
= version
.split(')', 1)[1].strip()
23 version
= map(int, version
.split('.'))
24 _recent_gnu_tar
= version
> [1, 13, 92]
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 _find_in_path(prog
):
31 for d
in os
.environ
['PATH'].split(':'):
32 path
= os
.path
.join(d
, prog
)
33 if os
.path
.isfile(path
):
36 _pola_run
= _find_in_path('pola-run')
38 info('Found pola-run: %s', _pola_run
)
40 info('pola-run not found; archive extraction will not be sandboxed')
42 def _exec_maybe_sandboxed(writable
, prog
, *args
):
43 """execlp prog, with (only) the 'writable' directory writable if sandboxing is available.
44 If no sandbox is available, run without a sandbox."""
45 prog_path
= _find_in_path(prog
)
47 os
.execlp(prog_path
, prog_path
, *args
)
48 # We have pola-shell :-)
49 pola_args
= ['--prog', prog_path
, '-f', '/']
51 pola_args
+= ['-a', a
]
53 pola_args
+= ['-fw', writable
]
54 os
.execl(_pola_run
, _pola_run
, *pola_args
)
56 def unpack_archive(url
, data
, destdir
, extract
= None):
57 """Unpack stream 'data' into directory 'destdir'. If extract is given, extract just
58 that sub-directory from the archive. Works out the format from the name."""
60 if url
.endswith('.tar.bz2'):
61 extract_tar(data
, destdir
, extract
, '--bzip2')
62 elif url
.endswith('.deb'):
63 extract_deb(data
, destdir
, extract
)
64 elif url
.endswith('.rpm'):
65 extract_rpm(data
, destdir
, extract
)
66 elif url
.endswith('.tar.gz') or url
.endswith('.tgz'):
67 extract_tar(data
, destdir
, extract
, '-z')
69 raise SafeException('Unknown extension on "%s"; I only know .tgz, .tar.bz2 and .rpm' % url
)
71 def extract_deb(stream
, destdir
, extract
= None):
73 raise SafeException('Sorry, but the "extract" attribute is not yet supported for Debs')
76 # ar can't read from stdin, so make a copy...
77 deb_copy_name
= os
.path
.join(destdir
, 'archive.deb')
78 deb_copy
= file(deb_copy_name
, 'w')
79 shutil
.copyfileobj(stream
, deb_copy
)
81 _extract(stream
, destdir
, ('ar', 'x', 'archive.deb', 'data.tar.gz'))
82 os
.unlink(deb_copy_name
)
83 data_name
= os
.path
.join(destdir
, 'data.tar.gz')
84 data_stream
= file(data_name
)
86 _extract(data_stream
, destdir
, ('tar', 'xzf', '-'))
88 def extract_rpm(stream
, destdir
, extract
= None):
90 raise SafeException('Sorry, but the "extract" attribute is not yet supported for RPMs')
91 fd
, cpiopath
= mkstemp('-rpm-tmp')
97 os
.dup2(stream
.fileno(), 0)
99 _exec_maybe_sandboxed(None, 'rpm2cpio', '-')
101 traceback
.print_exc()
104 id, status
= os
.waitpid(child
, 0)
107 raise SafeException("rpm2cpio failed; can't unpack RPM archive; exit code %d" % status
)
110 args
= ['cpio', '-mid', '--quiet']
111 _extract(file(cpiopath
), destdir
, args
)
112 # Set the mtime of every directory under 'tmp' to 0, since cpio doesn't
113 # preserve directory mtimes.
114 os
.path
.walk(destdir
, lambda arg
, dirname
, names
: os
.utime(dirname
, (0, 0)), None)
120 def extract_tar(stream
, destdir
, extract
, decompress
):
122 # Limit the characters we accept, to avoid sending dodgy
124 if not re
.match('^[a-zA-Z0-9][- _a-zA-Z0-9.]*$', extract
):
125 raise SafeException('Illegal character in extract attribute')
128 args
= ['tar', decompress
, '-x', '--no-same-owner', '--no-same-permissions']
130 args
= ['tar', decompress
, '-xf', '-']
135 _extract(stream
, destdir
, args
)
137 def _extract(stream
, destdir
, command
):
138 """Run execvp('command') inside destdir in a child process, with a
139 rewound stream as stdin."""
146 os
.dup2(stream
.fileno(), 0)
147 _exec_maybe_sandboxed(destdir
, *command
)
149 traceback
.print_exc()
152 id, status
= os
.waitpid(child
, 0)
155 raise SafeException('Failed to extract archive; exit code %d' % status
)