1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 OS Specific Utilities
19 =====================
20
21 Certain utilities to make the life more easy.
22 """
23 __author__ = u"Andr\xe9 Malo"
24 __docformat__ = "restructuredtext en"
25
26 import datetime as _datetime
27 import errno as _errno
28 import fcntl as _fcntl
29 import os as _os
30 import resource as _resource
31 import socket as _socket
32 import sys as _sys
33 import threading as _threading
34 import warnings as _warnings
35
36 from wtf import Error, WtfWarning
37
38
40 """ The attempt to change identity caused a soft error """
41
43 """ The attempt to change identity caused a hard error """
44
45
48
50 """ Address resolution error """
51
54
57
58
60 """
61 Convert a socket error into an appropriate module exception
62
63 This function needs an already raised ``socket.error``.
64
65 ``raise_socket_error.EAIS`` is a mapping from GAI error numbers to their
66 names (``{int: 'name', ...}``)
67
68 :Parameters:
69 - `timeout`: applied timeout in seconds, used for the TimeoutError
70 description
71
72 :Types:
73 - `timeout`: ``float``
74
75 :Exceptions:
76 - `TimeoutError`: ``socket.timeout``
77 - `AddressError`: address/host resolution error
78 (``socket.gaierror/herror``)
79 - `SSLError`: ``socket.sslerror``
80 - `SocketError`: other socket errors, ``IOError``
81 - `Exception`: unrecognized exceptions
82 """
83 try:
84 raise
85
86 except _socket.timeout:
87 if timeout is not None:
88 raise TimeoutError, "Timed out after %s seconds" % timeout, \
89 _sys.exc_info()[2]
90 raise TimeoutError, "Timed out", _sys.exc_info()[2]
91
92 except _socket.gaierror, e:
93
94 raise AddressError, "Address Information Error: %s (%s)" % \
95 (raise_socket_error.EAIS.get(e[0], e[0]), e[1]), \
96 _sys.exc_info()[2]
97
98 except _socket.herror, e:
99 raise AddressError, "Host Resolution Error %s: %s" % \
100 (e[0], e[1]), _sys.exc_info()[2]
101
102 except _socket.sslerror, e:
103 raise SSLError, "Socket SSL Error: %s" % str(e), _sys.exc_info()[2]
104
105 except _socket.error, e:
106 if len(e.args) == 1:
107 raise SocketError, "Socket Error: %s" % \
108 (e[0],), _sys.exc_info()[2]
109 else:
110 raise SocketError, "Socket Error %s: %s" % \
111 (_errno.errorcode.get(e[0], e[0]), e[1]), _sys.exc_info()[2]
112
113 except IOError, e:
114 raise SocketError, "Socket Error %s: %s" % \
115 (_errno.errorcode.get(e[0], e[0]), str(e)), \
116 _sys.exc_info()[2]
117
118 if 1:
119 raise_socket_error.EAIS = dict((val, var)
120 for var, val in vars(_socket).items() if var.startswith('EAI_')
121 )
122
123
125 """
126 Unlink a filename, but ignore if it does not exist
127
128 :Parameters:
129 - `filename`: The filename to remove
130
131 :Types:
132 - `filename`: ``basestring``
133 """
134 try:
135 _os.unlink(filename)
136 except OSError, e:
137 if e.errno != _errno.ENOENT:
138 raise
139
140
142 """
143 Mark `descriptor` to be closed on exec (or not)
144
145 :Warning: This function is not thread safe (race condition)
146
147 :Parameters:
148 - `descriptor`: An object with ``fileno`` method or an ``int``
149 representing a low level file descriptor
150 - `close`: Mark being closed on exec?
151
152 :Types:
153 - `descriptor`: ``file`` or ``int``
154 - `close`: ``bool``
155
156 :Exceptions:
157 - `IOError`: Something went wrong
158 """
159 try:
160 fileno = descriptor.fileno
161 except AttributeError:
162 fd = descriptor
163 else:
164 fd = fileno()
165
166 old = _fcntl.fcntl(fd, _fcntl.F_GETFD)
167 if close:
168 new = old | _fcntl.FD_CLOEXEC
169 else:
170 new = old & ~_fcntl.FD_CLOEXEC
171 _fcntl.fcntl(fd, _fcntl.F_SETFD, new)
172
173
175 """
176 Ensure that file descriptor fd is >= 3
177
178 This is done by dup(2) calls until it's greater than 2. After success
179 the duped descriptors are closed.
180
181 :Parameters:
182 - `fd`: The file descriptor to process
183
184 :Types:
185 - `fd`: ``int``
186
187 :return: The new file descriptor (>=3)
188 :rtype: ``int``
189
190 :Exceptions:
191 - `OSError`: Duping went wrong
192 """
193 toclose = []
194 try:
195 while fd < 3:
196 toclose.append(fd)
197 fd = _os.dup(fd)
198 finally:
199 for dfd in toclose:
200 try:
201 _os.close(dfd)
202 except OSError:
203 pass
204 return fd
205
206
208 """ Close all file descriptors >= 3 """
209 keep = set(keep)
210 try:
211 flag = _resource.RLIMIT_NOFILE
212 except AttributeError:
213 try:
214 flag = _resource.RLIMIT_OFILE
215 except AttributeError:
216 flag = None
217 if flag is not None:
218 try:
219 maxfiles = _resource.getrlimit(flag)[0]
220 except (_resource.error, ValueError):
221 flag = None
222 if flag is None:
223 maxfiles = 256
224 for fd in xrange(3, maxfiles + 1):
225 if fd in keep:
226 continue
227 try:
228 _os.close(fd)
229 except OSError:
230 pass
231
232
233 try:
234 _myflag = _socket.TCP_NODELAY
235 except AttributeError:
237 """
238 Disable nagle algorithm for a TCP socket
239
240 :Note: This function is a NOOP on this platform (not implemented).
241
242 :Parameters:
243 - `sock`: Socket to process
244 - `peername`: The name of the remote socket, if ``str``, it's a UNIX
245 domain socket and the function does nothing
246
247 :Types:
248 - `sock`: ``socket.socket``
249 - `peername`: ``str`` or ``tuple``
250
251 :return: The socket and the peername again (if the latter was passed
252 as ``None``, it will be set to something useful
253 :rtype: ``tuple``
254
255 :Exceptions:
256 - `socket.error`: The socket was probably not connected. If setting
257 of the option fails, no socket error is thrown though. It's ignored.
258 """
259 if peername is None:
260 peername = sock.getpeername()
261 return sock, peername
262 else:
264 """
265 Disable nagle algorithm for a TCP socket
266
267 :Parameters:
268 - `sock`: Socket to process
269 - `peername`: The name of the remote socket, if ``str``, it's a UNIX
270 domain socket and the function does nothing
271
272 :Types:
273 - `sock`: ``socket.socket``
274 - `peername`: ``str`` or ``tuple``
275
276 :return: The socket and the peername again (if the latter was passed
277 as ``None``, it will be set to something useful
278 :rtype: ``tuple``
279
280 :Exceptions:
281 - `socket.error`: The socket was probably not connected. If setting
282 of the option fails, no socket error is thrown though. It's ignored.
283 """
284 if peername is None:
285 peername = sock.getpeername()
286 if not isinstance(peername, str):
287 try:
288 sock.setsockopt(_socket.IPPROTO_TCP, _flag, 1)
289 except _socket.error:
290 pass
291 return sock, peername
292
293
294 _connect_cache = {}
295 _connect_cache_lock = _threading.Lock()
296 -def connect(spec, timeout=None, nagle_off=True, cache=0,
297 _cache=_connect_cache, _lock=_connect_cache_lock):
298 """
299 Create and connect a socket to a peer
300
301 :Parameters:
302 - `spec`: The peer specification (``(host, port)`` or ``str``)
303 - `timeout`: Timeout in seconds
304 - `nagle_off`: Disable Nagle's algorithm. This option does not
305 apply to UNIX domain sockets.
306
307 :Types:
308 - `spec`: ``tuple`` or ``str``
309 - `timeout`: ``float``
310 - `nagle_off`: ``bool``
311
312 :return: The connected socket or ``None`` if no connectable address
313 could be found
314 :rtype: ``socket.socket``
315
316 :Exceptions:
317 - `SocketError`: socket error (maybe a subclass of `SocketError`)
318 - `NotImplementedError`: UNIX domain sockets are not supported in this
319 platform
320 """
321
322
323 sock = None
324 try:
325 adi = None
326 if cache > 0:
327 _lock.acquire()
328 try:
329 if spec in _cache:
330 adi, stamp = _cache[spec]
331 if stamp < _datetime.datetime.utcnow():
332 del _cache[spec]
333 adi = None
334 finally:
335 _lock.release()
336 if adi is None:
337 if isinstance(spec, str):
338 try:
339 AF_UNIX = _socket.AF_UNIX
340 except AttributeError:
341 raise NotImplementedError(
342 "UNIX domain sockets are not supported"
343 )
344 adi = [(AF_UNIX, _socket.SOCK_STREAM, 0, None, spec)]
345 else:
346 adi = _socket.getaddrinfo(spec[0], spec[1],
347 _socket.AF_UNSPEC, _socket.SOCK_STREAM, 0, 0)
348 if cache > 0:
349 _lock.acquire()
350 try:
351 if spec not in _cache:
352 _cache[spec] = (
353 adi,
354 _datetime.datetime.utcnow()
355 + _datetime.timedelta(seconds=cache),
356 )
357 finally:
358 _lock.release()
359
360 AF_INET6 = getattr(_socket, 'AF_INET6', None)
361 for family, stype, proto, _, addr in adi:
362 if not _socket.has_ipv6 and family == AF_INET6:
363 continue
364
365 sock = _socket.socket(family, stype, proto)
366 sock.settimeout(timeout)
367 retry = True
368 while retry:
369 try:
370 sock.connect(addr)
371 except _socket.timeout:
372 break
373 except _socket.error, e:
374 if e[0] == _errno.EINTR:
375 continue
376 elif e[0] in (_errno.ENETUNREACH, _errno.ECONNREFUSED):
377 break
378 raise
379 retry = False
380 else:
381 if nagle_off:
382 disable_nagle(sock)
383 return sock
384 sock.close()
385 except (_socket.error, IOError):
386 try:
387 raise_socket_error(timeout=timeout)
388 except SocketError:
389 e = _sys.exc_info()
390 try:
391 if sock is not None:
392 sock.close()
393 finally:
394 try:
395 raise e[0], e[1], e[2]
396 finally:
397 del e
398 return None
399
400 del _connect_cache, _connect_cache_lock
401
402
404 """
405 Change identity of the current process
406
407 This only works if the effective user ID of the current process is 0.
408
409 :Parameters:
410 - `user`: User identification, if it is interpretable as ``int``, it's
411 assumed to be a numeric user ID
412 - `group`: Group identification, if it is interpretable as ``int``, it's
413 asummed to be a numeric group ID
414
415 :Types:
416 - `user`: ``str``
417 - `group`: ``str``
418
419 :Exceptions:
420 - `IdentityWarning`: A soft error occured (like not being root)
421 """
422 if _os.geteuid() != 0:
423 _warnings.warn("Not attempting to change identity (not root)",
424 category=IdentityWarning)
425 return
426
427 user, group = str(user), str(group)
428
429
430 import pwd
431 try:
432 try:
433 userid = int(user)
434 except (TypeError, ValueError):
435 userid = pwd.getpwnam(user).pw_uid
436 else:
437 user = pwd.getpwuid(userid).pw_name
438 except KeyError, e:
439 raise IdentityError(
440 "User resolution problem of %r: %s" % (user, str(e))
441 )
442
443
444 import grp
445 try:
446 try:
447 groupid = int(group)
448 except (TypeError, ValueError):
449 groupid = grp.getgrnam(group).gr_gid
450 else:
451 group = grp.getgrgid(groupid).gr_name
452 except KeyError, e:
453 raise IdentityError(
454 "Group resolution problem of %r: %s" % (group, str(e))
455 )
456
457
458
459 _os.setgid(groupid)
460 try:
461 initgroups(user, groupid)
462 except NotImplementedError:
463 _warnings.warn("initgroups(3) is not implemented. You have to run "
464 "without supplemental groups or compile the wtf package "
465 "properly.", category=IdentityWarning)
466 _os.setuid(userid)
467
468
470 """
471 Implement initgroups(3)
472
473 :Parameters:
474 - `username`: The user name
475 - `gid`: The group id
476
477 :Types:
478 - `username`: ``str``
479 - `gid`: ``int``
480
481 :Exceptions:
482 - `OSError`: initgroups() didn't succeed
483 - `NotImplementedError`: initgroups is not implemented
484 (needs c-extension)
485 """
486
487
488 raise NotImplementedError()
489
490
491 from wtf import c_override
492 cimpl = c_override('_wtf_cutil')
493 if cimpl is not None:
494
495 initgroups = cimpl.initgroups
496 del c_override, cimpl
497