socket-util: Avoid using SO_ERROR.
[openvswitch] / python / ovs / socket_util.py
1 # Copyright (c) 2010, 2012 Nicira, Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at:
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import errno
16 import os
17 import select
18 import socket
19 import sys
20
21 import ovs.fatal_signal
22 import ovs.poller
23 import ovs.vlog
24
25 vlog = ovs.vlog.Vlog("socket_util")
26
27
28 def make_unix_socket(style, nonblock, bind_path, connect_path):
29     """Creates a Unix domain socket in the given 'style' (either
30     socket.SOCK_DGRAM or socket.SOCK_STREAM) that is bound to 'bind_path' (if
31     'bind_path' is not None) and connected to 'connect_path' (if 'connect_path'
32     is not None).  If 'nonblock' is true, the socket is made non-blocking.
33
34     Returns (error, socket): on success 'error' is 0 and 'socket' is a new
35     socket object, on failure 'error' is a positive errno value and 'socket' is
36     None."""
37
38     try:
39         sock = socket.socket(socket.AF_UNIX, style)
40     except socket.error, e:
41         return get_exception_errno(e), None
42
43     try:
44         if nonblock:
45             set_nonblocking(sock)
46         if bind_path is not None:
47             # Delete bind_path but ignore ENOENT.
48             try:
49                 os.unlink(bind_path)
50             except OSError, e:
51                 if e.errno != errno.ENOENT:
52                     return e.errno, None
53
54             ovs.fatal_signal.add_file_to_unlink(bind_path)
55             sock.bind(bind_path)
56
57             try:
58                 if sys.hexversion >= 0x02060000:
59                     os.fchmod(sock.fileno(), 0700)
60                 else:
61                     os.chmod("/dev/fd/%d" % sock.fileno(), 0700)
62             except OSError, e:
63                 pass
64         if connect_path is not None:
65             try:
66                 sock.connect(connect_path)
67             except socket.error, e:
68                 if get_exception_errno(e) != errno.EINPROGRESS:
69                     raise
70         return 0, sock
71     except socket.error, e:
72         sock.close()
73         if bind_path is not None:
74             ovs.fatal_signal.unlink_file_now(bind_path)
75         return get_exception_errno(e), None
76
77
78 def check_connection_completion(sock):
79     p = ovs.poller.SelectPoll()
80     p.register(sock, ovs.poller.POLLOUT)
81     pfds = p.poll(0)
82     if len(pfds) == 1:
83         revents = pfds[0][1]
84         if revents & ovs.poller.POLLERR:
85             try:
86                 # The following should raise an exception.
87                 socket.send("\0", socket.MSG_DONTWAIT)
88
89                 # (Here's where we end up if it didn't.)
90                 # XXX rate-limit
91                 vlog.err("poll return POLLERR but send succeeded")
92                 return errno.EPROTO
93             except socket.error, e:
94                 return get_exception_errno(e)
95         else:
96             return 0
97     else:
98         return errno.EAGAIN
99
100
101 def inet_parse_active(target, default_port):
102     address = target.split(":")
103     host_name = address[0]
104     if not host_name:
105         raise ValueError("%s: bad peer name format" % target)
106     if len(address) >= 2:
107         port = int(address[1])
108     elif default_port:
109         port = default_port
110     else:
111         raise ValueError("%s: port number must be specified" % target)
112     return (host_name, port)
113
114
115 def inet_open_active(style, target, default_port, dscp):
116     address = inet_parse_active(target, default_port)
117     try:
118         sock = socket.socket(socket.AF_INET, style, 0)
119     except socket.error, e:
120         return get_exception_errno(e), None
121
122     try:
123         set_nonblocking(sock)
124         set_dscp(sock, dscp)
125         try:
126             sock.connect(address)
127         except socket.error, e:
128             if get_exception_errno(e) != errno.EINPROGRESS:
129                 raise
130         return 0, sock
131     except socket.error, e:
132         sock.close()
133         return get_exception_errno(e), None
134
135
136 def get_socket_error(sock):
137     """Returns the errno value associated with 'socket' (0 if no error) and
138     resets the socket's error status."""
139     return sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
140
141
142 def get_exception_errno(e):
143     """A lot of methods on Python socket objects raise socket.error, but that
144     exception is documented as having two completely different forms of
145     arguments: either a string or a (errno, string) tuple.  We only want the
146     errno."""
147     if type(e.args) == tuple:
148         return e.args[0]
149     else:
150         return errno.EPROTO
151
152
153 null_fd = -1
154
155
156 def get_null_fd():
157     """Returns a readable and writable fd for /dev/null, if successful,
158     otherwise a negative errno value.  The caller must not close the returned
159     fd (because the same fd will be handed out to subsequent callers)."""
160     global null_fd
161     if null_fd < 0:
162         try:
163             null_fd = os.open("/dev/null", os.O_RDWR)
164         except OSError, e:
165             vlog.err("could not open /dev/null: %s" % os.strerror(e.errno))
166             return -e.errno
167     return null_fd
168
169
170 def write_fully(fd, buf):
171     """Returns an (error, bytes_written) tuple where 'error' is 0 on success,
172     otherwise a positive errno value, and 'bytes_written' is the number of
173     bytes that were written before the error occurred.  'error' is 0 if and
174     only if 'bytes_written' is len(buf)."""
175     bytes_written = 0
176     if len(buf) == 0:
177         return 0, 0
178     while True:
179         try:
180             retval = os.write(fd, buf)
181             assert retval >= 0
182             if retval == len(buf):
183                 return 0, bytes_written + len(buf)
184             elif retval == 0:
185                 vlog.warn("write returned 0")
186                 return errno.EPROTO, bytes_written
187             else:
188                 bytes_written += retval
189                 buf = buf[:retval]
190         except OSError, e:
191             return e.errno, bytes_written
192
193
194 def set_nonblocking(sock):
195     try:
196         sock.setblocking(0)
197     except socket.error, e:
198         vlog.err("could not set nonblocking mode on socket: %s"
199                  % os.strerror(get_exception_errno(e)))
200
201
202 def set_dscp(sock, dscp):
203     if dscp > 63:
204         raise ValueError("Invalid dscp %d" % dscp)
205     val = dscp << 2
206     sock.setsockopt(socket.IPPROTO_IP, socket.IP_TOS, val)