Statistics
| Branch: | Revision:

root / env / lib / python2.7 / site-packages / distribute-0.6.19-py2.7.egg / setuptools / sandbox.py @ 1a305335

History | View | Annotate | Download (8.55 KB)

1
import os, sys, __builtin__, tempfile, operator, pkg_resources
2
_os = sys.modules[os.name]
3
try:
4
    _file = file
5
except NameError:
6
    _file = None
7
_open = open
8
from distutils.errors import DistutilsError
9
__all__ = [
10
    "AbstractSandbox", "DirectorySandbox", "SandboxViolation", "run_setup",
11
]
12
def run_setup(setup_script, args):
13
    """Run a distutils setup script, sandboxed in its directory"""
14
    old_dir = os.getcwd()
15
    save_argv = sys.argv[:]
16
    save_path = sys.path[:]
17
    setup_dir = os.path.abspath(os.path.dirname(setup_script))
18
    temp_dir = os.path.join(setup_dir,'temp')
19
    if not os.path.isdir(temp_dir): os.makedirs(temp_dir)
20
    save_tmp = tempfile.tempdir
21
    save_modules = sys.modules.copy()
22
    pr_state = pkg_resources.__getstate__()
23
    try:
24
        tempfile.tempdir = temp_dir
25
        os.chdir(setup_dir)
26
        try:
27
            sys.argv[:] = [setup_script]+list(args)
28
            sys.path.insert(0, setup_dir)
29
            DirectorySandbox(setup_dir).run(
30
                lambda: execfile(
31
                    "setup.py",
32
                    {'__file__':setup_script, '__name__':'__main__'}
33
                )
34
            )
35
        except SystemExit, v:
36
            if v.args and v.args[0]:
37
                raise
38
            # Normal exit, just return
39
    finally:
40
        pkg_resources.__setstate__(pr_state)
41
        sys.modules.update(save_modules)
42
        for key in list(sys.modules):
43
            if key not in save_modules: del sys.modules[key]
44
        os.chdir(old_dir)
45
        sys.path[:] = save_path
46
        sys.argv[:] = save_argv
47
        tempfile.tempdir = save_tmp
48

    
49
class AbstractSandbox:
50
    """Wrap 'os' module and 'open()' builtin for virtualizing setup scripts"""
51

    
52
    _active = False
53

    
54
    def __init__(self):
55
        self._attrs = [
56
            name for name in dir(_os)
57
                if not name.startswith('_') and hasattr(self,name)
58
        ]
59

    
60
    def _copy(self, source):
61
        for name in self._attrs:
62
            setattr(os, name, getattr(source,name))
63

    
64
    def run(self, func):
65
        """Run 'func' under os sandboxing"""
66
        try:
67
            self._copy(self)
68
            if _file:
69
                __builtin__.file = self._file
70
            __builtin__.open = self._open
71
            self._active = True
72
            return func()
73
        finally:
74
            self._active = False
75
            if _file:
76
                __builtin__.file = _file
77
            __builtin__.open = _open
78
            self._copy(_os)
79

    
80

    
81
    def _mk_dual_path_wrapper(name):
82
        original = getattr(_os,name)
83
        def wrap(self,src,dst,*args,**kw):
84
            if self._active:
85
                src,dst = self._remap_pair(name,src,dst,*args,**kw)
86
            return original(src,dst,*args,**kw)
87
        return wrap
88

    
89

    
90
    for name in ["rename", "link", "symlink"]:
91
        if hasattr(_os,name): locals()[name] = _mk_dual_path_wrapper(name)
92

    
93

    
94
    def _mk_single_path_wrapper(name, original=None):
95
        original = original or getattr(_os,name)
96
        def wrap(self,path,*args,**kw):
97
            if self._active:
98
                path = self._remap_input(name,path,*args,**kw)
99
            return original(path,*args,**kw)
100
        return wrap
101

    
102
    if _file:
103
        _file = _mk_single_path_wrapper('file', _file)
104
    _open = _mk_single_path_wrapper('open', _open)
105
    for name in [
106
        "stat", "listdir", "chdir", "open", "chmod", "chown", "mkdir",
107
        "remove", "unlink", "rmdir", "utime", "lchown", "chroot", "lstat",
108
        "startfile", "mkfifo", "mknod", "pathconf", "access"
109
    ]:
110
        if hasattr(_os,name): locals()[name] = _mk_single_path_wrapper(name)
111

    
112

    
113
    def _mk_single_with_return(name):
114
        original = getattr(_os,name)
115
        def wrap(self,path,*args,**kw):
116
            if self._active:
117
                path = self._remap_input(name,path,*args,**kw)
118
                return self._remap_output(name, original(path,*args,**kw))
119
            return original(path,*args,**kw)
120
        return wrap
121

    
122
    for name in ['readlink', 'tempnam']:
123
        if hasattr(_os,name): locals()[name] = _mk_single_with_return(name)
124

    
125
    def _mk_query(name):
126
        original = getattr(_os,name)
127
        def wrap(self,*args,**kw):
128
            retval = original(*args,**kw)
129
            if self._active:
130
                return self._remap_output(name, retval)
131
            return retval
132
        return wrap
133

    
134
    for name in ['getcwd', 'tmpnam']:
135
        if hasattr(_os,name): locals()[name] = _mk_query(name)
136

    
137
    def _validate_path(self,path):
138
        """Called to remap or validate any path, whether input or output"""
139
        return path
140

    
141
    def _remap_input(self,operation,path,*args,**kw):
142
        """Called for path inputs"""
143
        return self._validate_path(path)
144

    
145
    def _remap_output(self,operation,path):
146
        """Called for path outputs"""
147
        return self._validate_path(path)
148

    
149
    def _remap_pair(self,operation,src,dst,*args,**kw):
150
        """Called for path pairs like rename, link, and symlink operations"""
151
        return (
152
            self._remap_input(operation+'-from',src,*args,**kw),
153
            self._remap_input(operation+'-to',dst,*args,**kw)
154
        )
155

    
156

    
157
if hasattr(os, 'devnull'):
158
    _EXCEPTIONS = [os.devnull,]
159
else:
160
    _EXCEPTIONS = []
161

    
162
try:
163
        from win32com.client.gencache import GetGeneratePath
164
        _EXCEPTIONS.append(GetGeneratePath())
165
        del GetGeneratePath
166
except ImportError:
167
        # it appears pywin32 is not installed, so no need to exclude.
168
        pass
169

    
170
class DirectorySandbox(AbstractSandbox):
171
    """Restrict operations to a single subdirectory - pseudo-chroot"""
172

    
173
    write_ops = dict.fromkeys([
174
        "open", "chmod", "chown", "mkdir", "remove", "unlink", "rmdir",
175
        "utime", "lchown", "chroot", "mkfifo", "mknod", "tempnam",
176
    ])
177

    
178
    def __init__(self, sandbox, exceptions=_EXCEPTIONS):
179
        self._sandbox = os.path.normcase(os.path.realpath(sandbox))
180
        self._prefix = os.path.join(self._sandbox,'')
181
        self._exceptions = [os.path.normcase(os.path.realpath(path)) for path in exceptions]
182
        AbstractSandbox.__init__(self)
183

    
184
    def _violation(self, operation, *args, **kw):
185
        raise SandboxViolation(operation, args, kw)
186

    
187
    if _file:
188
        def _file(self, path, mode='r', *args, **kw):
189
            if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):
190
                self._violation("file", path, mode, *args, **kw)
191
            return _file(path,mode,*args,**kw)
192

    
193
    def _open(self, path, mode='r', *args, **kw):
194
        if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):
195
            self._violation("open", path, mode, *args, **kw)
196
        return _open(path,mode,*args,**kw)
197

    
198
    def tmpnam(self):
199
        self._violation("tmpnam")
200

    
201
    def _ok(self,path):
202
        active = self._active
203
        try:
204
            self._active = False
205
            realpath = os.path.normcase(os.path.realpath(path))
206
            if (self._exempted(realpath) or realpath == self._sandbox
207
                or realpath.startswith(self._prefix)):
208
                return True
209
        finally:
210
            self._active = active
211

    
212
    def _exempted(self, filepath):
213
        exception_matches = map(filepath.startswith, self._exceptions)
214
        return True in exception_matches
215

    
216
    def _remap_input(self,operation,path,*args,**kw):
217
        """Called for path inputs"""
218
        if operation in self.write_ops and not self._ok(path):
219
            self._violation(operation, os.path.realpath(path), *args, **kw)
220
        return path
221

    
222
    def _remap_pair(self,operation,src,dst,*args,**kw):
223
        """Called for path pairs like rename, link, and symlink operations"""
224
        if not self._ok(src) or not self._ok(dst):
225
            self._violation(operation, src, dst, *args, **kw)
226
        return (src,dst)
227

    
228
    def open(self, file, flags, mode=0777):
229
        """Called for low-level os.open()"""
230
        if flags & WRITE_FLAGS and not self._ok(file):
231
            self._violation("os.open", file, flags, mode)
232
        return _os.open(file,flags,mode)
233

    
234

    
235
WRITE_FLAGS = reduce(
236
    operator.or_,
237
    [getattr(_os, a, 0) for a in
238
        "O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split()]
239
)
240

    
241

    
242

    
243

    
244
class SandboxViolation(DistutilsError):
245
    """A setup script attempted to modify the filesystem outside the sandbox"""
246

    
247
    def __str__(self):
248
        return """SandboxViolation: %s%r %s
249

250
The package setup script has attempted to modify files on your system
251
that are not within the EasyInstall build area, and has been aborted.
252

253
This package cannot be safely installed by EasyInstall, and may not
254
support alternate installation locations even if you run its setup
255
script by hand.  Please inform the package's author and the EasyInstall
256
maintainers to find out if a fix or workaround is available.""" % self.args
257

    
258

    
259

    
260

    
261

    
262

    
263

    
264

    
265

    
266

    
267

    
268

    
269

    
270

    
271

    
272

    
273

    
274

    
275

    
276

    
277

    
278

    
279

    
280

    
281

    
282

    
283

    
284
#