Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 1 | /* |
| 2 | * libwebsockets - small server side websockets and web server implementation |
| 3 | * |
| 4 | * Copyright (C) 2010-2013 Andy Green <andy@warmcat.com> |
| 5 | * |
| 6 | * This library is free software; you can redistribute it and/or |
| 7 | * modify it under the terms of the GNU Lesser General Public |
| 8 | * License as published by the Free Software Foundation: |
| 9 | * version 2.1 of the License. |
| 10 | * |
| 11 | * This library is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 14 | * Lesser General Public License for more details. |
| 15 | * |
| 16 | * You should have received a copy of the GNU Lesser General Public |
| 17 | * License along with this library; if not, write to the Free Software |
| 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
| 19 | * MA 02110-1301 USA |
| 20 | */ |
| 21 | |
| 22 | |
| 23 | #include "private-libwebsockets.h" |
| 24 | |
| 25 | #ifdef WIN32 |
| 26 | #include <tchar.h> |
| 27 | #include <io.h> |
| 28 | #else |
| 29 | #ifdef LWS_BUILTIN_GETIFADDRS |
| 30 | #include <getifaddrs.h> |
| 31 | #else |
| 32 | #include <ifaddrs.h> |
| 33 | #endif |
| 34 | #include <sys/un.h> |
| 35 | #include <sys/socket.h> |
| 36 | #include <netdb.h> |
| 37 | #endif |
| 38 | |
| 39 | #ifdef LWS_OPENSSL_SUPPORT |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 40 | |
| 41 | static void |
| 42 | libwebsockets_decode_ssl_error(void) |
| 43 | { |
| 44 | char buf[256]; |
| 45 | u_long err; |
| 46 | |
| 47 | while ((err = ERR_get_error()) != 0) { |
| 48 | ERR_error_string_n(err, buf, sizeof(buf)); |
| 49 | lwsl_err("*** %s\n", buf); |
| 50 | } |
| 51 | } |
| 52 | #endif |
| 53 | |
| 54 | int |
| 55 | interface_to_sa(const char *ifname, struct sockaddr_in *addr, size_t addrlen) |
| 56 | { |
| 57 | int rc = -1; |
| 58 | #ifdef WIN32 |
| 59 | /* TODO */ |
| 60 | #else |
| 61 | struct ifaddrs *ifr; |
| 62 | struct ifaddrs *ifc; |
| 63 | struct sockaddr_in *sin; |
| 64 | |
| 65 | getifaddrs(&ifr); |
| 66 | for (ifc = ifr; ifc != NULL; ifc = ifc->ifa_next) { |
| 67 | if (strcmp(ifc->ifa_name, ifname)) |
| 68 | continue; |
| 69 | if (ifc->ifa_addr == NULL) |
| 70 | continue; |
| 71 | sin = (struct sockaddr_in *)ifc->ifa_addr; |
| 72 | if (sin->sin_family != AF_INET) |
| 73 | continue; |
| 74 | memcpy(addr, sin, addrlen); |
| 75 | rc = 0; |
| 76 | } |
| 77 | |
| 78 | freeifaddrs(ifr); |
| 79 | #endif |
| 80 | return rc; |
| 81 | } |
| 82 | |
| 83 | struct libwebsocket * |
| 84 | libwebsocket_create_new_server_wsi(struct libwebsocket_context *context) |
| 85 | { |
| 86 | struct libwebsocket *new_wsi; |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 87 | |
| 88 | new_wsi = (struct libwebsocket *)malloc(sizeof(struct libwebsocket)); |
| 89 | if (new_wsi == NULL) { |
| 90 | lwsl_err("Out of memory for new connection\n"); |
| 91 | return NULL; |
| 92 | } |
| 93 | |
| 94 | memset(new_wsi, 0, sizeof(struct libwebsocket)); |
Andy Green | 3182ece | 2013-01-20 17:08:31 +0800 | [diff] [blame] | 95 | #ifndef LWS_NO_EXTENSIONS |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 96 | new_wsi->count_active_extensions = 0; |
Andy Green | 3182ece | 2013-01-20 17:08:31 +0800 | [diff] [blame] | 97 | #endif |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 98 | new_wsi->pending_timeout = NO_PENDING_TIMEOUT; |
| 99 | |
| 100 | /* intialize the instance struct */ |
| 101 | |
| 102 | new_wsi->state = WSI_STATE_HTTP; |
Andy Green | 623a98d | 2013-01-21 11:04:23 +0800 | [diff] [blame] | 103 | new_wsi->u.hdr.name_buffer_pos = 0; |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 104 | new_wsi->mode = LWS_CONNMODE_HTTP_SERVING; |
Andy Green | 224149a | 2013-02-11 21:43:41 +0800 | [diff] [blame] | 105 | new_wsi->hdr_parsing_completed = 0; |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 106 | |
Andy Green | 16ab318 | 2013-02-10 18:02:31 +0800 | [diff] [blame] | 107 | if (lws_allocate_header_table(new_wsi)) { |
| 108 | free(new_wsi); |
| 109 | return NULL; |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 110 | } |
| 111 | |
| 112 | /* |
| 113 | * these can only be set once the protocol is known |
| 114 | * we set an unestablished connection's protocol pointer |
| 115 | * to the start of the supported list, so it can look |
| 116 | * for matching ones during the handshake |
| 117 | */ |
| 118 | new_wsi->protocol = context->protocols; |
| 119 | new_wsi->user_space = NULL; |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 120 | new_wsi->ietf_spec_revision = 0; |
| 121 | |
| 122 | return new_wsi; |
| 123 | } |
| 124 | |
| 125 | int lws_server_socket_service(struct libwebsocket_context *context, |
| 126 | struct libwebsocket *wsi, struct pollfd *pollfd) |
| 127 | { |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 128 | struct libwebsocket *new_wsi; |
| 129 | int accept_fd; |
| 130 | unsigned int clilen; |
| 131 | struct sockaddr_in cli_addr; |
| 132 | int n; |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 133 | ssize_t len; |
Edwin van den Oetelaar | 0794af9 | 2013-01-28 21:53:53 +0800 | [diff] [blame] | 134 | #ifdef LWS_OPENSSL_SUPPORT |
| 135 | int m; |
Andy Green | 23c5f2e | 2013-02-06 15:43:00 +0900 | [diff] [blame] | 136 | #ifndef USE_CYASSL |
Andy Green | 1167dd4 | 2013-01-28 17:45:34 +0800 | [diff] [blame] | 137 | BIO *bio; |
Edwin van den Oetelaar | 0794af9 | 2013-01-28 21:53:53 +0800 | [diff] [blame] | 138 | #endif |
Andy Green | 23c5f2e | 2013-02-06 15:43:00 +0900 | [diff] [blame] | 139 | #endif |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 140 | |
| 141 | switch (wsi->mode) { |
| 142 | |
| 143 | case LWS_CONNMODE_HTTP_SERVING: |
| 144 | |
| 145 | /* handle http headers coming in */ |
| 146 | |
| 147 | /* any incoming data ready? */ |
| 148 | |
| 149 | if (pollfd->revents & POLLIN) { |
| 150 | |
| 151 | #ifdef LWS_OPENSSL_SUPPORT |
| 152 | if (wsi->ssl) |
Andy Green | b5b2319 | 2013-02-11 17:13:32 +0800 | [diff] [blame] | 153 | len = SSL_read(wsi->ssl, |
| 154 | context->service_buffer, |
| 155 | sizeof(context->service_buffer)); |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 156 | else |
| 157 | #endif |
Andy Green | b5b2319 | 2013-02-11 17:13:32 +0800 | [diff] [blame] | 158 | len = recv(pollfd->fd, |
| 159 | context->service_buffer, |
| 160 | sizeof(context->service_buffer), 0); |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 161 | |
| 162 | if (len < 0) { |
| 163 | lwsl_debug("Socket read returned %d\n", len); |
| 164 | if (errno != EINTR && errno != EAGAIN) |
Andy Green | b5b2319 | 2013-02-11 17:13:32 +0800 | [diff] [blame] | 165 | libwebsocket_close_and_free_session( |
| 166 | context, wsi, |
| 167 | LWS_CLOSE_STATUS_NOSTATUS); |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 168 | return 0; |
| 169 | } |
| 170 | if (!len) { |
Andy Green | 224149a | 2013-02-11 21:43:41 +0800 | [diff] [blame] | 171 | lwsl_info("lws_server_skt_srv: read 0 len\n"); |
| 172 | /* lwsl_info(" state=%d\n", wsi->state); */ |
| 173 | if (!wsi->hdr_parsing_completed) |
| 174 | free(wsi->u.hdr.ah); |
Andy Green | b5b2319 | 2013-02-11 17:13:32 +0800 | [diff] [blame] | 175 | libwebsocket_close_and_free_session( |
| 176 | context, wsi, LWS_CLOSE_STATUS_NOSTATUS); |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 177 | return 0; |
| 178 | } |
| 179 | |
Andy Green | b5b2319 | 2013-02-11 17:13:32 +0800 | [diff] [blame] | 180 | n = libwebsocket_read(context, wsi, |
| 181 | context->service_buffer, len); |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 182 | if (n < 0) |
| 183 | /* we closed wsi */ |
| 184 | return 0; |
| 185 | } |
| 186 | |
| 187 | /* this handles POLLOUT for http serving fragments */ |
| 188 | |
| 189 | if (!(pollfd->revents & POLLOUT)) |
| 190 | break; |
| 191 | |
| 192 | /* one shot */ |
| 193 | pollfd->events &= ~POLLOUT; |
Andy Green | b5b2319 | 2013-02-11 17:13:32 +0800 | [diff] [blame] | 194 | |
Andy Green | 54cb346 | 2013-02-14 22:23:54 +0800 | [diff] [blame^] | 195 | if (wsi->state != WSI_STATE_HTTP_ISSUING_FILE) { |
| 196 | n = user_callback_handle_rxflow( |
| 197 | wsi->protocol->callback, |
| 198 | wsi->protocol->owning_server, |
| 199 | wsi, LWS_CALLBACK_HTTP_WRITEABLE, |
| 200 | wsi->user_space, |
| 201 | NULL, |
| 202 | 0); |
| 203 | if (n < 0) |
| 204 | libwebsocket_close_and_free_session( |
| 205 | context, wsi, LWS_CLOSE_STATUS_NOSTATUS); |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 206 | break; |
Andy Green | 54cb346 | 2013-02-14 22:23:54 +0800 | [diff] [blame^] | 207 | } |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 208 | |
Andy Green | b5b2319 | 2013-02-11 17:13:32 +0800 | [diff] [blame] | 209 | /* nonzero for completion or error */ |
| 210 | if (libwebsockets_serve_http_file_fragment(context, wsi)) |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 211 | libwebsocket_close_and_free_session(context, wsi, |
| 212 | LWS_CLOSE_STATUS_NOSTATUS); |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 213 | break; |
| 214 | |
| 215 | case LWS_CONNMODE_SERVER_LISTENER: |
| 216 | |
| 217 | /* pollin means a client has connected to us then */ |
| 218 | |
| 219 | if (!(pollfd->revents & POLLIN)) |
| 220 | break; |
| 221 | |
| 222 | /* listen socket got an unencrypted connection... */ |
| 223 | |
| 224 | clilen = sizeof(cli_addr); |
Andy Green | e000a70 | 2013-01-29 12:37:35 +0800 | [diff] [blame] | 225 | lws_latency_pre(context, wsi); |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 226 | accept_fd = accept(pollfd->fd, (struct sockaddr *)&cli_addr, |
| 227 | &clilen); |
Andy Green | b5b2319 | 2013-02-11 17:13:32 +0800 | [diff] [blame] | 228 | lws_latency(context, wsi, |
| 229 | "unencrypted accept LWS_CONNMODE_SERVER_LISTENER", |
| 230 | accept_fd, accept_fd >= 0); |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 231 | if (accept_fd < 0) { |
Andy Green | e216071 | 2013-01-28 12:19:10 +0800 | [diff] [blame] | 232 | if (errno == EAGAIN || errno == EWOULDBLOCK) { |
| 233 | lwsl_debug("accept asks to try again\n"); |
| 234 | break; |
| 235 | } |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 236 | lwsl_warn("ERROR on accept: %s\n", strerror(errno)); |
| 237 | break; |
| 238 | } |
| 239 | |
Andy Green | a690cd0 | 2013-02-09 12:25:31 +0800 | [diff] [blame] | 240 | lws_set_socket_options(context, accept_fd); |
Andy Green | 6f047ee | 2013-01-28 11:23:52 +0800 | [diff] [blame] | 241 | |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 242 | /* |
| 243 | * look at who we connected to and give user code a chance |
| 244 | * to reject based on client IP. There's no protocol selected |
| 245 | * yet so we issue this to protocols[0] |
| 246 | */ |
| 247 | |
| 248 | if ((context->protocols[0].callback)(context, wsi, |
| 249 | LWS_CALLBACK_FILTER_NETWORK_CONNECTION, |
| 250 | (void *)(long)accept_fd, NULL, 0)) { |
| 251 | lwsl_debug("Callback denied network connection\n"); |
| 252 | compatible_close(accept_fd); |
| 253 | break; |
| 254 | } |
| 255 | |
| 256 | new_wsi = libwebsocket_create_new_server_wsi(context); |
| 257 | if (new_wsi == NULL) { |
| 258 | compatible_close(accept_fd); |
| 259 | break; |
| 260 | } |
| 261 | |
| 262 | new_wsi->sock = accept_fd; |
| 263 | |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 264 | #ifdef LWS_OPENSSL_SUPPORT |
| 265 | new_wsi->ssl = NULL; |
Andy Green | e216071 | 2013-01-28 12:19:10 +0800 | [diff] [blame] | 266 | if (!context->use_ssl) { |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 267 | #endif |
Andy Green | e216071 | 2013-01-28 12:19:10 +0800 | [diff] [blame] | 268 | |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 269 | lwsl_debug("accepted new conn port %u on fd=%d\n", |
| 270 | ntohs(cli_addr.sin_port), accept_fd); |
| 271 | |
Andy Green | e216071 | 2013-01-28 12:19:10 +0800 | [diff] [blame] | 272 | insert_wsi_socket_into_fds(context, new_wsi); |
| 273 | break; |
| 274 | #ifdef LWS_OPENSSL_SUPPORT |
| 275 | } |
| 276 | |
| 277 | new_wsi->ssl = SSL_new(context->ssl_ctx); |
| 278 | if (new_wsi->ssl == NULL) { |
| 279 | lwsl_err("SSL_new failed: %s\n", |
| 280 | ERR_error_string(SSL_get_error( |
| 281 | new_wsi->ssl, 0), NULL)); |
| 282 | libwebsockets_decode_ssl_error(); |
| 283 | free(new_wsi); |
| 284 | compatible_close(accept_fd); |
| 285 | break; |
| 286 | } |
| 287 | |
| 288 | SSL_set_ex_data(new_wsi->ssl, |
| 289 | openssl_websocket_private_data_index, context); |
| 290 | |
| 291 | SSL_set_fd(new_wsi->ssl, accept_fd); |
| 292 | |
Joakim Soderberg | b378ce9 | 2013-02-06 15:29:18 +0900 | [diff] [blame] | 293 | #ifdef USE_CYASSL |
| 294 | CyaSSL_set_using_nonblock(new_wsi->ssl, 1); |
| 295 | #else |
Andy Green | 1167dd4 | 2013-01-28 17:45:34 +0800 | [diff] [blame] | 296 | bio = SSL_get_rbio(new_wsi->ssl); |
| 297 | if (bio) |
| 298 | BIO_set_nbio(bio, 1); /* nonblocking */ |
| 299 | else |
| 300 | lwsl_notice("NULL rbio\n"); |
| 301 | bio = SSL_get_wbio(new_wsi->ssl); |
| 302 | if (bio) |
| 303 | BIO_set_nbio(bio, 1); /* nonblocking */ |
| 304 | else |
| 305 | lwsl_notice("NULL rbio\n"); |
Joakim Soderberg | b378ce9 | 2013-02-06 15:29:18 +0900 | [diff] [blame] | 306 | #endif |
Andy Green | 1167dd4 | 2013-01-28 17:45:34 +0800 | [diff] [blame] | 307 | |
Andy Green | b5b2319 | 2013-02-11 17:13:32 +0800 | [diff] [blame] | 308 | /* |
Andy Green | e216071 | 2013-01-28 12:19:10 +0800 | [diff] [blame] | 309 | * we are not accepted yet, but we need to enter ourselves |
| 310 | * as a live connection. That way we can retry when more |
| 311 | * pieces come if we're not sorted yet |
| 312 | */ |
| 313 | |
| 314 | wsi = new_wsi; |
| 315 | wsi->mode = LWS_CONNMODE_SSL_ACK_PENDING; |
| 316 | insert_wsi_socket_into_fds(context, wsi); |
| 317 | |
Andy Green | ba85a7d | 2013-01-28 17:20:41 +0800 | [diff] [blame] | 318 | libwebsocket_set_timeout(wsi, PENDING_TIMEOUT_SSL_ACCEPT, |
| 319 | AWAITING_TIMEOUT); |
| 320 | |
Andy Green | b5b2319 | 2013-02-11 17:13:32 +0800 | [diff] [blame] | 321 | lwsl_info("inserted SSL accept into fds, trying SSL_accept\n"); |
Andy Green | e216071 | 2013-01-28 12:19:10 +0800 | [diff] [blame] | 322 | |
| 323 | /* fallthru */ |
| 324 | |
| 325 | case LWS_CONNMODE_SSL_ACK_PENDING: |
| 326 | |
| 327 | pollfd->events &= ~POLLOUT; |
| 328 | |
| 329 | /* external POLL support via protocol 0 */ |
| 330 | context->protocols[0].callback(context, wsi, |
| 331 | LWS_CALLBACK_CLEAR_MODE_POLL_FD, |
| 332 | (void *)(long)wsi->sock, NULL, POLLOUT); |
| 333 | |
Andy Green | e000a70 | 2013-01-29 12:37:35 +0800 | [diff] [blame] | 334 | lws_latency_pre(context, wsi); |
Andy Green | e216071 | 2013-01-28 12:19:10 +0800 | [diff] [blame] | 335 | n = SSL_accept(wsi->ssl); |
Andy Green | b5b2319 | 2013-02-11 17:13:32 +0800 | [diff] [blame] | 336 | lws_latency(context, wsi, |
| 337 | "SSL_accept LWS_CONNMODE_SSL_ACK_PENDING\n", n, n == 1); |
Andy Green | e216071 | 2013-01-28 12:19:10 +0800 | [diff] [blame] | 338 | |
| 339 | if (n != 1) { |
| 340 | m = SSL_get_error(wsi->ssl, n); |
Andy Green | b5b2319 | 2013-02-11 17:13:32 +0800 | [diff] [blame] | 341 | lwsl_debug("SSL_accept failed %d / %s\n", |
| 342 | m, ERR_error_string(m, NULL)); |
Andy Green | e216071 | 2013-01-28 12:19:10 +0800 | [diff] [blame] | 343 | |
| 344 | if (m == SSL_ERROR_WANT_READ) { |
Andy Green | b5b2319 | 2013-02-11 17:13:32 +0800 | [diff] [blame] | 345 | context->fds[ |
| 346 | wsi->position_in_fds_table].events |= POLLIN; |
Andy Green | e216071 | 2013-01-28 12:19:10 +0800 | [diff] [blame] | 347 | |
| 348 | /* external POLL support via protocol 0 */ |
| 349 | context->protocols[0].callback(context, wsi, |
| 350 | LWS_CALLBACK_SET_MODE_POLL_FD, |
| 351 | (void *)(long)wsi->sock, NULL, POLLIN); |
| 352 | lwsl_info("SSL_ERROR_WANT_READ\n"); |
| 353 | break; |
| 354 | } |
| 355 | if (m == SSL_ERROR_WANT_WRITE) { |
Andy Green | b5b2319 | 2013-02-11 17:13:32 +0800 | [diff] [blame] | 356 | context->fds[ |
| 357 | wsi->position_in_fds_table].events |= POLLOUT; |
Andy Green | e216071 | 2013-01-28 12:19:10 +0800 | [diff] [blame] | 358 | |
| 359 | /* external POLL support via protocol 0 */ |
| 360 | context->protocols[0].callback(context, wsi, |
| 361 | LWS_CALLBACK_SET_MODE_POLL_FD, |
| 362 | (void *)(long)wsi->sock, NULL, POLLOUT); |
| 363 | break; |
| 364 | } |
| 365 | lwsl_debug("SSL_accept failed skt %u: %s\n", |
| 366 | pollfd->fd, |
| 367 | ERR_error_string(m, NULL)); |
Andy Green | b5b2319 | 2013-02-11 17:13:32 +0800 | [diff] [blame] | 368 | libwebsocket_close_and_free_session(context, wsi, |
| 369 | LWS_CLOSE_STATUS_NOSTATUS); |
Andy Green | e216071 | 2013-01-28 12:19:10 +0800 | [diff] [blame] | 370 | break; |
| 371 | } |
| 372 | |
| 373 | /* OK, we are accepted */ |
| 374 | |
Andy Green | ba85a7d | 2013-01-28 17:20:41 +0800 | [diff] [blame] | 375 | libwebsocket_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); |
| 376 | |
Andy Green | e216071 | 2013-01-28 12:19:10 +0800 | [diff] [blame] | 377 | wsi->mode = LWS_CONNMODE_HTTP_SERVING; |
| 378 | |
Andy Green | 3551709 | 2013-02-11 20:10:56 +0800 | [diff] [blame] | 379 | lwsl_debug("accepted new SSL conn\n"); |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 380 | break; |
Andy Green | e216071 | 2013-01-28 12:19:10 +0800 | [diff] [blame] | 381 | #endif |
| 382 | |
Andy Green | a1ce6be | 2013-01-18 11:43:21 +0800 | [diff] [blame] | 383 | default: |
| 384 | break; |
| 385 | } |
| 386 | return 0; |
| 387 | } |
Joakim Soderberg | 63ff120 | 2013-02-11 17:52:23 +0100 | [diff] [blame] | 388 | |