Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(285)

Side by Side Diff: recipe_engine/third_party/setuptools/sandbox.py

Issue 1344583003: Recipe package system. (Closed) Base URL: git@github.com:luci/recipes-py.git@master
Patch Set: Recompiled proto Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 import os
2 import sys
3 import tempfile
4 import operator
5 import functools
6 import itertools
7 import re
8
9 import pkg_resources
10
11 if os.name == "java":
12 import org.python.modules.posix.PosixModule as _os
13 else:
14 _os = sys.modules[os.name]
15 try:
16 _file = file
17 except NameError:
18 _file = None
19 _open = open
20 from distutils.errors import DistutilsError
21 from pkg_resources import working_set
22
23 from setuptools.compat import builtins
24
25 __all__ = [
26 "AbstractSandbox", "DirectorySandbox", "SandboxViolation", "run_setup",
27 ]
28
29 def _execfile(filename, globals, locals=None):
30 """
31 Python 3 implementation of execfile.
32 """
33 mode = 'rb'
34 # Python 2.6 compile requires LF for newlines, so use deprecated
35 # Universal newlines support.
36 if sys.version_info < (2, 7):
37 mode += 'U'
38 with open(filename, mode) as stream:
39 script = stream.read()
40 if locals is None:
41 locals = globals
42 code = compile(script, filename, 'exec')
43 exec(code, globals, locals)
44
45 def run_setup(setup_script, args):
46 """Run a distutils setup script, sandboxed in its directory"""
47 old_dir = os.getcwd()
48 save_argv = sys.argv[:]
49 save_path = sys.path[:]
50 setup_dir = os.path.abspath(os.path.dirname(setup_script))
51 temp_dir = os.path.join(setup_dir,'temp')
52 if not os.path.isdir(temp_dir): os.makedirs(temp_dir)
53 save_tmp = tempfile.tempdir
54 save_modules = sys.modules.copy()
55 pr_state = pkg_resources.__getstate__()
56 try:
57 tempfile.tempdir = temp_dir
58 os.chdir(setup_dir)
59 try:
60 sys.argv[:] = [setup_script]+list(args)
61 sys.path.insert(0, setup_dir)
62 # reset to include setup dir, w/clean callback list
63 working_set.__init__()
64 working_set.callbacks.append(lambda dist:dist.activate())
65 def runner():
66 ns = dict(__file__=setup_script, __name__='__main__')
67 _execfile(setup_script, ns)
68 DirectorySandbox(setup_dir).run(runner)
69 except SystemExit:
70 v = sys.exc_info()[1]
71 if v.args and v.args[0]:
72 raise
73 # Normal exit, just return
74 finally:
75 pkg_resources.__setstate__(pr_state)
76 sys.modules.update(save_modules)
77 # remove any modules imported within the sandbox
78 del_modules = [
79 mod_name for mod_name in sys.modules
80 if mod_name not in save_modules
81 # exclude any encodings modules. See #285
82 and not mod_name.startswith('encodings.')
83 ]
84 list(map(sys.modules.__delitem__, del_modules))
85 os.chdir(old_dir)
86 sys.path[:] = save_path
87 sys.argv[:] = save_argv
88 tempfile.tempdir = save_tmp
89
90
91 class AbstractSandbox:
92 """Wrap 'os' module and 'open()' builtin for virtualizing setup scripts"""
93
94 _active = False
95
96 def __init__(self):
97 self._attrs = [
98 name for name in dir(_os)
99 if not name.startswith('_') and hasattr(self,name)
100 ]
101
102 def _copy(self, source):
103 for name in self._attrs:
104 setattr(os, name, getattr(source,name))
105
106 def run(self, func):
107 """Run 'func' under os sandboxing"""
108 try:
109 self._copy(self)
110 if _file:
111 builtins.file = self._file
112 builtins.open = self._open
113 self._active = True
114 return func()
115 finally:
116 self._active = False
117 if _file:
118 builtins.file = _file
119 builtins.open = _open
120 self._copy(_os)
121
122 def _mk_dual_path_wrapper(name):
123 original = getattr(_os,name)
124 def wrap(self,src,dst,*args,**kw):
125 if self._active:
126 src,dst = self._remap_pair(name,src,dst,*args,**kw)
127 return original(src,dst,*args,**kw)
128 return wrap
129
130 for name in ["rename", "link", "symlink"]:
131 if hasattr(_os,name): locals()[name] = _mk_dual_path_wrapper(name)
132
133 def _mk_single_path_wrapper(name, original=None):
134 original = original or getattr(_os,name)
135 def wrap(self,path,*args,**kw):
136 if self._active:
137 path = self._remap_input(name,path,*args,**kw)
138 return original(path,*args,**kw)
139 return wrap
140
141 if _file:
142 _file = _mk_single_path_wrapper('file', _file)
143 _open = _mk_single_path_wrapper('open', _open)
144 for name in [
145 "stat", "listdir", "chdir", "open", "chmod", "chown", "mkdir",
146 "remove", "unlink", "rmdir", "utime", "lchown", "chroot", "lstat",
147 "startfile", "mkfifo", "mknod", "pathconf", "access"
148 ]:
149 if hasattr(_os,name): locals()[name] = _mk_single_path_wrapper(name)
150
151 def _mk_single_with_return(name):
152 original = getattr(_os,name)
153 def wrap(self,path,*args,**kw):
154 if self._active:
155 path = self._remap_input(name,path,*args,**kw)
156 return self._remap_output(name, original(path,*args,**kw))
157 return original(path,*args,**kw)
158 return wrap
159
160 for name in ['readlink', 'tempnam']:
161 if hasattr(_os,name): locals()[name] = _mk_single_with_return(name)
162
163 def _mk_query(name):
164 original = getattr(_os,name)
165 def wrap(self,*args,**kw):
166 retval = original(*args,**kw)
167 if self._active:
168 return self._remap_output(name, retval)
169 return retval
170 return wrap
171
172 for name in ['getcwd', 'tmpnam']:
173 if hasattr(_os,name): locals()[name] = _mk_query(name)
174
175 def _validate_path(self,path):
176 """Called to remap or validate any path, whether input or output"""
177 return path
178
179 def _remap_input(self,operation,path,*args,**kw):
180 """Called for path inputs"""
181 return self._validate_path(path)
182
183 def _remap_output(self,operation,path):
184 """Called for path outputs"""
185 return self._validate_path(path)
186
187 def _remap_pair(self,operation,src,dst,*args,**kw):
188 """Called for path pairs like rename, link, and symlink operations"""
189 return (
190 self._remap_input(operation+'-from',src,*args,**kw),
191 self._remap_input(operation+'-to',dst,*args,**kw)
192 )
193
194
195 if hasattr(os, 'devnull'):
196 _EXCEPTIONS = [os.devnull,]
197 else:
198 _EXCEPTIONS = []
199
200 try:
201 from win32com.client.gencache import GetGeneratePath
202 _EXCEPTIONS.append(GetGeneratePath())
203 del GetGeneratePath
204 except ImportError:
205 # it appears pywin32 is not installed, so no need to exclude.
206 pass
207
208 class DirectorySandbox(AbstractSandbox):
209 """Restrict operations to a single subdirectory - pseudo-chroot"""
210
211 write_ops = dict.fromkeys([
212 "open", "chmod", "chown", "mkdir", "remove", "unlink", "rmdir",
213 "utime", "lchown", "chroot", "mkfifo", "mknod", "tempnam",
214 ])
215
216 _exception_patterns = [
217 # Allow lib2to3 to attempt to save a pickled grammar object (#121)
218 '.*lib2to3.*\.pickle$',
219 ]
220 "exempt writing to paths that match the pattern"
221
222 def __init__(self, sandbox, exceptions=_EXCEPTIONS):
223 self._sandbox = os.path.normcase(os.path.realpath(sandbox))
224 self._prefix = os.path.join(self._sandbox,'')
225 self._exceptions = [
226 os.path.normcase(os.path.realpath(path))
227 for path in exceptions
228 ]
229 AbstractSandbox.__init__(self)
230
231 def _violation(self, operation, *args, **kw):
232 raise SandboxViolation(operation, args, kw)
233
234 if _file:
235 def _file(self, path, mode='r', *args, **kw):
236 if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):
237 self._violation("file", path, mode, *args, **kw)
238 return _file(path,mode,*args,**kw)
239
240 def _open(self, path, mode='r', *args, **kw):
241 if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):
242 self._violation("open", path, mode, *args, **kw)
243 return _open(path,mode,*args,**kw)
244
245 def tmpnam(self):
246 self._violation("tmpnam")
247
248 def _ok(self, path):
249 active = self._active
250 try:
251 self._active = False
252 realpath = os.path.normcase(os.path.realpath(path))
253 return (
254 self._exempted(realpath)
255 or realpath == self._sandbox
256 or realpath.startswith(self._prefix)
257 )
258 finally:
259 self._active = active
260
261 def _exempted(self, filepath):
262 start_matches = (
263 filepath.startswith(exception)
264 for exception in self._exceptions
265 )
266 pattern_matches = (
267 re.match(pattern, filepath)
268 for pattern in self._exception_patterns
269 )
270 candidates = itertools.chain(start_matches, pattern_matches)
271 return any(candidates)
272
273 def _remap_input(self, operation, path, *args, **kw):
274 """Called for path inputs"""
275 if operation in self.write_ops and not self._ok(path):
276 self._violation(operation, os.path.realpath(path), *args, **kw)
277 return path
278
279 def _remap_pair(self, operation, src, dst, *args, **kw):
280 """Called for path pairs like rename, link, and symlink operations"""
281 if not self._ok(src) or not self._ok(dst):
282 self._violation(operation, src, dst, *args, **kw)
283 return (src,dst)
284
285 def open(self, file, flags, mode=0o777, *args, **kw):
286 """Called for low-level os.open()"""
287 if flags & WRITE_FLAGS and not self._ok(file):
288 self._violation("os.open", file, flags, mode, *args, **kw)
289 return _os.open(file,flags,mode, *args, **kw)
290
291 WRITE_FLAGS = functools.reduce(
292 operator.or_, [getattr(_os, a, 0) for a in
293 "O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split()]
294 )
295
296 class SandboxViolation(DistutilsError):
297 """A setup script attempted to modify the filesystem outside the sandbox"""
298
299 def __str__(self):
300 return """SandboxViolation: %s%r %s
301
302 The package setup script has attempted to modify files on your system
303 that are not within the EasyInstall build area, and has been aborted.
304
305 This package cannot be safely installed by EasyInstall, and may not
306 support alternate installation locations even if you run its setup
307 script by hand. Please inform the package's author and the EasyInstall
308 maintainers to find out if a fix or workaround is available.""" % self.args
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336 #
OLDNEW
« no previous file with comments | « recipe_engine/third_party/setuptools/py31compat.py ('k') | recipe_engine/third_party/setuptools/script.tmpl » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698