1 import os, sys, __builtin__, tempfile, operator, pkg_resources
2 _os = sys.modules[os.name]
6 from distutils.errors import DistutilsError
7 from pkg_resources import working_set
10 "AbstractSandbox", "DirectorySandbox", "SandboxViolation", "run_setup",
42 def run_setup(setup_script, args):
43 """Run a distutils setup script, sandboxed in its directory"""
45 save_argv = sys.argv[:]
46 save_path = sys.path[:]
47 setup_dir = os.path.abspath(os.path.dirname(setup_script))
48 temp_dir = os.path.join(setup_dir,'temp')
49 if not os.path.isdir(temp_dir): os.makedirs(temp_dir)
50 save_tmp = tempfile.tempdir
51 save_modules = sys.modules.copy()
52 pr_state = pkg_resources.__getstate__()
54 tempfile.tempdir = temp_dir; os.chdir(setup_dir)
56 sys.argv[:] = [setup_script]+list(args)
57 sys.path.insert(0, setup_dir)
58 # reset to include setup dir, w/clean callback list
59 working_set.__init__()
60 working_set.callbacks.append(lambda dist:dist.activate())
61 DirectorySandbox(setup_dir).run(
64 {'__file__':setup_script, '__name__':'__main__'}
68 if v.args and v.args[0]:
70 # Normal exit, just return
72 pkg_resources.__setstate__(pr_state)
73 sys.modules.update(save_modules)
74 for key in list(sys.modules):
75 if key not in save_modules: del sys.modules[key]
77 sys.path[:] = save_path
78 sys.argv[:] = save_argv
79 tempfile.tempdir = save_tmp
83 class AbstractSandbox:
84 """Wrap 'os' module and 'open()' builtin for virtualizing setup scripts"""
90 name for name in dir(_os)
91 if not name.startswith('_') and hasattr(self,name)
94 def _copy(self, source):
95 for name in self._attrs:
96 setattr(os, name, getattr(source,name))
99 """Run 'func' under os sandboxing"""
102 __builtin__.file = self._file
103 __builtin__.open = self._open
108 __builtin__.open = _open
109 __builtin__.file = _file
112 def _mk_dual_path_wrapper(name):
113 original = getattr(_os,name)
114 def wrap(self,src,dst,*args,**kw):
116 src,dst = self._remap_pair(name,src,dst,*args,**kw)
117 return original(src,dst,*args,**kw)
120 for name in ["rename", "link", "symlink"]:
121 if hasattr(_os,name): locals()[name] = _mk_dual_path_wrapper(name)
124 def _mk_single_path_wrapper(name, original=None):
125 original = original or getattr(_os,name)
126 def wrap(self,path,*args,**kw):
128 path = self._remap_input(name,path,*args,**kw)
129 return original(path,*args,**kw)
132 _open = _mk_single_path_wrapper('open', _open)
133 _file = _mk_single_path_wrapper('file', _file)
135 "stat", "listdir", "chdir", "open", "chmod", "chown", "mkdir",
136 "remove", "unlink", "rmdir", "utime", "lchown", "chroot", "lstat",
137 "startfile", "mkfifo", "mknod", "pathconf", "access"
139 if hasattr(_os,name): locals()[name] = _mk_single_path_wrapper(name)
141 def _mk_single_with_return(name):
142 original = getattr(_os,name)
143 def wrap(self,path,*args,**kw):
145 path = self._remap_input(name,path,*args,**kw)
146 return self._remap_output(name, original(path,*args,**kw))
147 return original(path,*args,**kw)
150 for name in ['readlink', 'tempnam']:
151 if hasattr(_os,name): locals()[name] = _mk_single_with_return(name)
154 original = getattr(_os,name)
155 def wrap(self,*args,**kw):
156 retval = original(*args,**kw)
158 return self._remap_output(name, retval)
162 for name in ['getcwd', 'tmpnam']:
163 if hasattr(_os,name): locals()[name] = _mk_query(name)
165 def _validate_path(self,path):
166 """Called to remap or validate any path, whether input or output"""
169 def _remap_input(self,operation,path,*args,**kw):
170 """Called for path inputs"""
171 return self._validate_path(path)
173 def _remap_output(self,operation,path):
174 """Called for path outputs"""
175 return self._validate_path(path)
177 def _remap_pair(self,operation,src,dst,*args,**kw):
178 """Called for path pairs like rename, link, and symlink operations"""
180 self._remap_input(operation+'-from',src,*args,**kw),
181 self._remap_input(operation+'-to',dst,*args,**kw)
185 class DirectorySandbox(AbstractSandbox):
186 """Restrict operations to a single subdirectory - pseudo-chroot"""
188 write_ops = dict.fromkeys([
189 "open", "chmod", "chown", "mkdir", "remove", "unlink", "rmdir",
190 "utime", "lchown", "chroot", "mkfifo", "mknod", "tempnam",
193 def __init__(self,sandbox):
194 self._sandbox = os.path.normcase(os.path.realpath(sandbox))
195 self._prefix = os.path.join(self._sandbox,'')
196 AbstractSandbox.__init__(self)
198 def _violation(self, operation, *args, **kw):
199 raise SandboxViolation(operation, args, kw)
201 def _open(self, path, mode='r', *args, **kw):
202 if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):
203 self._violation("open", path, mode, *args, **kw)
204 return _open(path,mode,*args,**kw)
206 def tmpnam(self): self._violation("tmpnam")
209 if hasattr(_os,'devnull') and path==_os.devnull: return True
210 active = self._active
213 realpath = os.path.normcase(os.path.realpath(path))
214 if realpath==self._sandbox or realpath.startswith(self._prefix):
217 self._active = active
219 def _remap_input(self,operation,path,*args,**kw):
220 """Called for path inputs"""
221 if operation in self.write_ops and not self._ok(path):
222 self._violation(operation, os.path.realpath(path), *args, **kw)
225 def _remap_pair(self,operation,src,dst,*args,**kw):
226 """Called for path pairs like rename, link, and symlink operations"""
227 if not self._ok(src) or not self._ok(dst):
228 self._violation(operation, src, dst, *args, **kw)
231 def _file(self, path, mode='r', *args, **kw):
232 if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):
233 self._violation("file", path, mode, *args, **kw)
234 return _file(path,mode,*args,**kw)
236 def open(self, file, flags, mode=0777):
237 """Called for low-level os.open()"""
238 if flags & WRITE_FLAGS and not self._ok(file):
239 self._violation("os.open", file, flags, mode)
240 return _os.open(file,flags,mode)
242 WRITE_FLAGS = reduce(
243 operator.or_, [getattr(_os, a, 0) for a in
244 "O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split()]
247 class SandboxViolation(DistutilsError):
248 """A setup script attempted to modify the filesystem outside the sandbox"""
251 return """SandboxViolation: %s%r %s
253 The package setup script has attempted to modify files on your system
254 that are not within the EasyInstall build area, and has been aborted.
256 This package cannot be safely installed by EasyInstall, and may not
257 support alternate installation locations even if you run its setup
258 script by hand. Please inform the package's author and the EasyInstall
259 maintainers to find out if a fix or workaround is available.""" % self.args