mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-09 00:00:04 +01:00
adoption: make union for socket and file fds
This lets lws support adopting raw file FDs and raw socket fds. A test plugin creates a FIFO and prints data sent on it, using the lws event loop.
This commit is contained in:
parent
8bb3dffc86
commit
be8d791b5e
20 changed files with 499 additions and 150 deletions
|
@ -1340,6 +1340,10 @@ if (NOT LWS_WITHOUT_TESTAPPS)
|
|||
"plugins/protocol_post_demo.c" "" "")
|
||||
create_plugin(protocol_lws_table_dirlisting
|
||||
"plugins/generic-table/protocol_table_dirlisting.c" "" "")
|
||||
if (NOT WIN32)
|
||||
create_plugin(protocol_lws_raw_test
|
||||
"plugins/protocol_lws_raw_test.c" "" "")
|
||||
endif()
|
||||
|
||||
if (LWS_WITH_SERVER_STATUS)
|
||||
create_plugin(protocol_lws_server_status
|
||||
|
|
|
@ -169,34 +169,34 @@ lws_client_connect_2(struct lws *wsi)
|
|||
freeaddrinfo(result);
|
||||
}
|
||||
|
||||
if (!lws_socket_is_valid(wsi->sock)) {
|
||||
if (!lws_socket_is_valid(wsi->desc.sockfd)) {
|
||||
|
||||
#ifdef LWS_USE_IPV6
|
||||
if (LWS_IPV6_ENABLED(wsi->vhost))
|
||||
wsi->sock = socket(AF_INET6, SOCK_STREAM, 0);
|
||||
wsi->desc.sockfd = socket(AF_INET6, SOCK_STREAM, 0);
|
||||
else
|
||||
#endif
|
||||
wsi->sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
wsi->desc.sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
if (!lws_socket_is_valid(wsi->sock)) {
|
||||
if (!lws_socket_is_valid(wsi->desc.sockfd)) {
|
||||
lwsl_warn("Unable to open socket\n");
|
||||
cce = "unable to open socket";
|
||||
goto oom4;
|
||||
}
|
||||
|
||||
if (lws_plat_set_socket_options(wsi->vhost, wsi->sock)) {
|
||||
if (lws_plat_set_socket_options(wsi->vhost, wsi->desc.sockfd)) {
|
||||
lwsl_err("Failed to set wsi socket options\n");
|
||||
compatible_close(wsi->sock);
|
||||
compatible_close(wsi->desc.sockfd);
|
||||
cce = "set socket opts failed";
|
||||
goto oom4;
|
||||
}
|
||||
|
||||
wsi->mode = LWSCM_WSCL_WAITING_CONNECT;
|
||||
|
||||
lws_libev_accept(wsi, wsi->sock);
|
||||
lws_libuv_accept(wsi, wsi->sock);
|
||||
lws_libev_accept(wsi, wsi->desc);
|
||||
lws_libuv_accept(wsi, wsi->desc);
|
||||
if (insert_wsi_socket_into_fds(context, wsi)) {
|
||||
compatible_close(wsi->sock);
|
||||
compatible_close(wsi->desc.sockfd);
|
||||
cce = "insert wsi failed";
|
||||
goto oom4;
|
||||
}
|
||||
|
@ -217,7 +217,7 @@ lws_client_connect_2(struct lws *wsi)
|
|||
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE,
|
||||
AWAITING_TIMEOUT);
|
||||
|
||||
n = lws_socket_bind(wsi->vhost, wsi->sock, 0, wsi->vhost->iface);
|
||||
n = lws_socket_bind(wsi->vhost, wsi->desc.sockfd, 0, wsi->vhost->iface);
|
||||
if (n < 0) {
|
||||
cce = "unable to bind socket";
|
||||
goto failed;
|
||||
|
@ -235,7 +235,7 @@ lws_client_connect_2(struct lws *wsi)
|
|||
n = sizeof(struct sockaddr);
|
||||
}
|
||||
|
||||
if (connect(wsi->sock, v, n) == -1 || LWS_ERRNO == LWS_EISCONN) {
|
||||
if (connect(wsi->desc.sockfd, v, n) == -1 || LWS_ERRNO == LWS_EISCONN) {
|
||||
if (LWS_ERRNO == LWS_EALREADY ||
|
||||
LWS_ERRNO == LWS_EINPROGRESS ||
|
||||
LWS_ERRNO == LWS_EWOULDBLOCK
|
||||
|
@ -287,7 +287,7 @@ lws_client_connect_2(struct lws *wsi)
|
|||
goto failed;
|
||||
wsi->c_port = wsi->vhost->http_proxy_port;
|
||||
|
||||
n = send(wsi->sock, (char *)pt->serv_buf, plen,
|
||||
n = send(wsi->desc.sockfd, (char *)pt->serv_buf, plen,
|
||||
MSG_NOSIGNAL);
|
||||
if (n < 0) {
|
||||
lwsl_debug("ERROR writing to proxy socket\n");
|
||||
|
@ -317,7 +317,7 @@ lws_client_connect_2(struct lws *wsi)
|
|||
AWAITING_TIMEOUT);
|
||||
|
||||
wsi->mode = LWSCM_WSCL_ISSUE_HANDSHAKE;
|
||||
pfd.fd = wsi->sock;
|
||||
pfd.fd = wsi->desc.sockfd;
|
||||
pfd.events = LWS_POLLIN;
|
||||
pfd.revents = LWS_POLLIN;
|
||||
|
||||
|
@ -411,10 +411,10 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port,
|
|||
|
||||
/* close the connection by hand */
|
||||
|
||||
compatible_close(wsi->sock);
|
||||
compatible_close(wsi->desc.sockfd);
|
||||
remove_wsi_socket_from_fds(wsi);
|
||||
|
||||
wsi->sock = LWS_SOCK_INVALID;
|
||||
wsi->desc.sockfd = LWS_SOCK_INVALID;
|
||||
wsi->state = LWSS_CLIENT_UNCONNECTED;
|
||||
wsi->protocol = NULL;
|
||||
wsi->pending_timeout = NO_PENDING_TIMEOUT;
|
||||
|
@ -581,7 +581,7 @@ lws_client_connect_via_info(struct lws_client_connect_info *i)
|
|||
wsi->context = i->context;
|
||||
/* assert the mode and union status (hdr) clearly */
|
||||
lws_union_transition(wsi, LWSCM_HTTP_CLIENT);
|
||||
wsi->sock = LWS_SOCK_INVALID;
|
||||
wsi->desc.sockfd = LWS_SOCK_INVALID;
|
||||
|
||||
/* 1) fill up the wsi with stuff from the connect_info as far as it
|
||||
* can go. It's because not only is our connection async, we might
|
||||
|
|
|
@ -114,7 +114,7 @@ lws_client_socket_service(struct lws_context *context, struct lws *wsi,
|
|||
return 0;
|
||||
}
|
||||
|
||||
n = recv(wsi->sock, sb, context->pt_serv_buf_size, 0);
|
||||
n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0);
|
||||
if (n < 0) {
|
||||
if (LWS_ERRNO == LWS_EAGAIN) {
|
||||
lwsl_debug("Proxy read returned EAGAIN... retrying\n");
|
||||
|
@ -952,7 +952,7 @@ check_accept:
|
|||
wsi->u.ws.rx_ubuf_alloc = n;
|
||||
lwsl_info("Allocating client RX buffer %d\n", n);
|
||||
|
||||
if (setsockopt(wsi->sock, SOL_SOCKET, SO_SNDBUF, (const char *)&n,
|
||||
if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&n,
|
||||
sizeof n)) {
|
||||
lwsl_warn("Failed to set SNDBUF to %d", n);
|
||||
cce = "HS: SO_SNDBUF failed";
|
||||
|
|
14
lib/libev.c
14
lib/libev.c
|
@ -97,7 +97,7 @@ lws_ev_initloop(struct lws_context *context, struct ev_loop *loop, int tsi)
|
|||
while (vh) {
|
||||
if (vh->lserv_wsi) {
|
||||
vh->lserv_wsi->w_read.context = context;
|
||||
ev_io_init(w_accept, lws_accept_cb, vh->lserv_wsi->sock,
|
||||
ev_io_init(w_accept, lws_accept_cb, vh->lserv_wsi->desc.sockfd,
|
||||
EV_READ);
|
||||
}
|
||||
vh = vh->vhost_next;
|
||||
|
@ -160,19 +160,25 @@ lws_libev_destroyloop(struct lws_context *context, int tsi)
|
|||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
lws_libev_accept(struct lws *new_wsi, int accept_fd)
|
||||
lws_libev_accept(struct lws *new_wsi, lws_sock_file_fd_type desc)
|
||||
{
|
||||
struct lws_context *context = lws_get_context(new_wsi);
|
||||
struct ev_io *r = &new_wsi->w_read.ev_watcher;
|
||||
struct ev_io *w = &new_wsi->w_write.ev_watcher;
|
||||
int fd;
|
||||
|
||||
if (!LWS_LIBEV_ENABLED(context))
|
||||
return;
|
||||
|
||||
if (wsi->mode == LWSCM_RAW_FILEDESC)
|
||||
fd = desc.filefd;
|
||||
else
|
||||
fd = desc.sockfd;
|
||||
|
||||
new_wsi->w_read.context = context;
|
||||
new_wsi->w_write.context = context;
|
||||
ev_io_init(r, lws_accept_cb, accept_fd, EV_READ);
|
||||
ev_io_init(w, lws_accept_cb, accept_fd, EV_WRITE);
|
||||
ev_io_init(r, lws_accept_cb, fd, EV_READ);
|
||||
ev_io_init(w, lws_accept_cb, fd, EV_WRITE);
|
||||
}
|
||||
|
||||
LWS_VISIBLE void
|
||||
|
|
14
lib/libuv.c
14
lib/libuv.c
|
@ -164,10 +164,10 @@ lws_uv_initvhost(struct lws_vhost* vh, struct lws* wsi)
|
|||
|
||||
wsi->w_read.context = vh->context;
|
||||
n = uv_poll_init_socket(pt->io_loop_uv,
|
||||
&wsi->w_read.uv_watcher, wsi->sock);
|
||||
&wsi->w_read.uv_watcher, wsi->desc.sockfd);
|
||||
if (n) {
|
||||
lwsl_err("uv_poll_init failed %d, sockfd=%p\n",
|
||||
n, (void *)(long)wsi->sock);
|
||||
n, (void *)(long)wsi->desc.sockfd);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
@ -318,7 +318,7 @@ lws_libuv_destroyloop(struct lws_context *context, int tsi)
|
|||
}
|
||||
|
||||
void
|
||||
lws_libuv_accept(struct lws *wsi, lws_sockfd_type accept_fd)
|
||||
lws_libuv_accept(struct lws *wsi, lws_sock_file_fd_type desc)
|
||||
{
|
||||
struct lws_context *context = lws_get_context(wsi);
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
|
@ -329,8 +329,12 @@ lws_libuv_accept(struct lws *wsi, lws_sockfd_type accept_fd)
|
|||
lwsl_debug("%s: new wsi %p\n", __func__, wsi);
|
||||
|
||||
wsi->w_read.context = context;
|
||||
|
||||
uv_poll_init_socket(pt->io_loop_uv, &wsi->w_read.uv_watcher, accept_fd);
|
||||
if (wsi->mode == LWSCM_RAW_FILEDESC)
|
||||
uv_poll_init(pt->io_loop_uv, &wsi->w_read.uv_watcher,
|
||||
desc.filefd);
|
||||
else
|
||||
uv_poll_init_socket(pt->io_loop_uv, &wsi->w_read.uv_watcher,
|
||||
desc.sockfd);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -154,6 +154,15 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason)
|
|||
if (!wsi)
|
||||
return;
|
||||
|
||||
if (wsi->mode == LWSCM_RAW_FILEDESC) {
|
||||
remove_wsi_socket_from_fds(wsi);
|
||||
wsi->protocol->callback(wsi,
|
||||
LWS_CALLBACK_RAW_CLOSE_FILE, wsi->user_space, NULL, 0);
|
||||
lws_free_wsi(wsi);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lws_access_log(wsi);
|
||||
#if defined(LWS_WITH_ESP8266)
|
||||
if (wsi->premature_rx)
|
||||
|
@ -222,7 +231,7 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason)
|
|||
#endif
|
||||
|
||||
if (wsi->mode == LWSCM_RAW) {
|
||||
wsi->vhost->protocols->callback(wsi,
|
||||
wsi->protocol->callback(wsi,
|
||||
LWS_CALLBACK_RAW_CLOSE, wsi->user_space, NULL, 0);
|
||||
wsi->socket_is_permanently_unusable = 1;
|
||||
goto just_kill_connection;
|
||||
|
@ -438,17 +447,17 @@ just_kill_connection:
|
|||
#ifdef LWS_OPENSSL_SUPPORT
|
||||
if (lws_is_ssl(wsi) && wsi->ssl)
|
||||
{
|
||||
lwsl_info("%s: shutting down SSL connection: %p (ssl %p, sock %d, state %d)\n", __func__, wsi, wsi->ssl, (int)(long)wsi->sock, wsi->state);
|
||||
lwsl_info("%s: shutting down SSL connection: %p (ssl %p, sock %d, state %d)\n", __func__, wsi, wsi->ssl, (int)(long)wsi->desc.sockfd, wsi->state);
|
||||
n = SSL_shutdown(wsi->ssl);
|
||||
if (n == 0) /* Complete bidirectional SSL shutdown */
|
||||
n = SSL_shutdown(wsi->ssl);
|
||||
n = shutdown(wsi->sock, SHUT_WR);
|
||||
n = shutdown(wsi->desc.sockfd, SHUT_WR);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
lwsl_info("%s: shutting down connection: %p (sock %d, state %d)\n", __func__, wsi, (int)(long)wsi->sock, wsi->state);
|
||||
n = shutdown(wsi->sock, SHUT_WR);
|
||||
lwsl_info("%s: shutting down connection: %p (sock %d, state %d)\n", __func__, wsi, (int)(long)wsi->desc.sockfd, wsi->state);
|
||||
n = shutdown(wsi->desc.sockfd, SHUT_WR);
|
||||
}
|
||||
if (n)
|
||||
lwsl_debug("closing: shutdown (state %d) ret %d\n", wsi->state, LWS_ERRNO);
|
||||
|
@ -470,7 +479,7 @@ just_kill_connection:
|
|||
#endif
|
||||
|
||||
lwsl_info("%s: real just_kill_connection: %p (sockfd %d)\n", __func__,
|
||||
wsi, wsi->sock);
|
||||
wsi, wsi->desc.sockfd);
|
||||
|
||||
#ifdef LWS_WITH_HTTP_PROXY
|
||||
if (wsi->rw) {
|
||||
|
@ -487,11 +496,11 @@ just_kill_connection:
|
|||
lws_remove_from_timeout_list(wsi);
|
||||
|
||||
/* checking return redundant since we anyway close */
|
||||
if (wsi->sock != LWS_SOCK_INVALID)
|
||||
if (wsi->desc.sockfd != LWS_SOCK_INVALID)
|
||||
remove_wsi_socket_from_fds(wsi);
|
||||
|
||||
#if defined(LWS_WITH_ESP8266)
|
||||
espconn_disconnect(wsi->sock);
|
||||
espconn_disconnect(wsi->desc.sockfd);
|
||||
#endif
|
||||
|
||||
wsi->state = LWSS_DEAD_SOCKET;
|
||||
|
@ -606,18 +615,18 @@ lws_close_free_wsi_final(struct lws *wsi)
|
|||
{
|
||||
int n;
|
||||
|
||||
if (!lws_ssl_close(wsi) && lws_socket_is_valid(wsi->sock)) {
|
||||
if (!lws_ssl_close(wsi) && lws_socket_is_valid(wsi->desc.sockfd)) {
|
||||
#if LWS_POSIX
|
||||
//lwsl_err("*** closing sockfd %d\n", wsi->sock);
|
||||
n = compatible_close(wsi->sock);
|
||||
//lwsl_err("*** closing sockfd %d\n", wsi->desc.sockfd);
|
||||
n = compatible_close(wsi->desc.sockfd);
|
||||
if (n)
|
||||
lwsl_debug("closing: close ret %d\n", LWS_ERRNO);
|
||||
|
||||
#else
|
||||
compatible_close(wsi->sock);
|
||||
compatible_close(wsi->desc.sockfd);
|
||||
(void)n;
|
||||
#endif
|
||||
wsi->sock = LWS_SOCK_INVALID;
|
||||
wsi->desc.sockfd = LWS_SOCK_INVALID;
|
||||
}
|
||||
|
||||
/* outermost destroy notification for wsi (user_space still intact) */
|
||||
|
@ -786,7 +795,7 @@ lws_get_peer_simple(struct lws *wsi, char *name, int namelen)
|
|||
}
|
||||
|
||||
olen = len;
|
||||
if (getpeername(wsi->sock, p, &len) < 0 || len > olen) {
|
||||
if (getpeername(wsi->desc.sockfd, p, &len) < 0 || len > olen) {
|
||||
lwsl_warn("getpeername: %s\n", strerror(LWS_ERRNO));
|
||||
return NULL;
|
||||
}
|
||||
|
@ -974,7 +983,7 @@ lws_now_secs(void)
|
|||
LWS_VISIBLE int
|
||||
lws_get_socket_fd(struct lws *wsi)
|
||||
{
|
||||
return wsi->sock;
|
||||
return wsi->desc.sockfd;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1976,7 +1985,7 @@ lws_create_basic_wsi(struct lws_context *context, int tsi)
|
|||
new_wsi->protocol = context->vhost_list->protocols;
|
||||
new_wsi->user_space = NULL;
|
||||
new_wsi->ietf_spec_revision = 0;
|
||||
new_wsi->sock = LWS_SOCK_INVALID;
|
||||
new_wsi->desc.sockfd = LWS_SOCK_INVALID;
|
||||
context->count_wsi_allocated++;
|
||||
|
||||
return new_wsi;
|
||||
|
@ -2024,7 +2033,7 @@ lws_cgi(struct lws *wsi, const char * const *exec_array, int script_uri_path_len
|
|||
// cgi->pipe_fds[n][!!(n == 0)], cgi->pipe_fds[n][!(n == 0)]);
|
||||
|
||||
/* read side is 0, stdin we want the write side, others read */
|
||||
cgi->stdwsi[n]->sock = cgi->pipe_fds[n][!!(n == 0)];
|
||||
cgi->stdwsi[n]->desc.sockfd = cgi->pipe_fds[n][!!(n == 0)];
|
||||
if (fcntl(cgi->pipe_fds[n][!!(n == 0)], F_SETFL, O_NONBLOCK) < 0) {
|
||||
lwsl_err("%s: setting NONBLOCK failed\n", __func__);
|
||||
goto bail2;
|
||||
|
@ -2032,7 +2041,7 @@ lws_cgi(struct lws *wsi, const char * const *exec_array, int script_uri_path_len
|
|||
}
|
||||
|
||||
for (n = 0; n < 3; n++) {
|
||||
lws_libuv_accept(cgi->stdwsi[n], cgi->stdwsi[n]->sock);
|
||||
lws_libuv_accept(cgi->stdwsi[n], cgi->stdwsi[n]->desc);
|
||||
if (insert_wsi_socket_into_fds(wsi->context, cgi->stdwsi[n]))
|
||||
goto bail3;
|
||||
cgi->stdwsi[n]->parent = wsi;
|
||||
|
@ -2045,8 +2054,9 @@ lws_cgi(struct lws *wsi, const char * const *exec_array, int script_uri_path_len
|
|||
lws_change_pollfd(cgi->stdwsi[LWS_STDERR], LWS_POLLOUT, LWS_POLLIN);
|
||||
|
||||
lwsl_debug("%s: fds in %d, out %d, err %d\n", __func__,
|
||||
cgi->stdwsi[LWS_STDIN]->sock, cgi->stdwsi[LWS_STDOUT]->sock,
|
||||
cgi->stdwsi[LWS_STDERR]->sock);
|
||||
cgi->stdwsi[LWS_STDIN]->desc.sockfd,
|
||||
cgi->stdwsi[LWS_STDOUT]->desc.sockfd,
|
||||
cgi->stdwsi[LWS_STDERR]->desc.sockfd);
|
||||
|
||||
lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, timeout_secs);
|
||||
|
||||
|
|
|
@ -1059,12 +1059,20 @@ enum lws_callback_reasons {
|
|||
LWS_CALLBACK_RAW_WRITEABLE = 61,
|
||||
/**< RAW mode connection may be written */
|
||||
LWS_CALLBACK_RAW_ADOPT = 62,
|
||||
/**< RAW mode connection was adopted (equivalent to 'created') */
|
||||
/**< RAW mode connection was adopted (equivalent to 'wsi created') */
|
||||
LWS_CALLBACK_RAW_ADOPT_FILE = 63,
|
||||
/**< RAW mode file was adopted (equivalent to 'wsi created') */
|
||||
LWS_CALLBACK_RAW_RX_FILE = 64,
|
||||
/**< RAW mode file has something to read */
|
||||
LWS_CALLBACK_RAW_WRITEABLE_FILE = 65,
|
||||
/**< RAW mode file is writeable */
|
||||
LWS_CALLBACK_RAW_CLOSE_FILE = 66,
|
||||
/**< RAW mode wsi that adopted a file is closing */
|
||||
|
||||
/****** add new things just above ---^ ******/
|
||||
|
||||
LWS_CALLBACK_USER = 1000,
|
||||
/**< user code can use any including / above without fear of clashes */
|
||||
/**< user code can use any including above without fear of clashes */
|
||||
};
|
||||
|
||||
|
||||
|
@ -3703,23 +3711,37 @@ lws_adopt_socket(struct lws_context *context, lws_sockfd_type accept_fd);
|
|||
*/
|
||||
LWS_VISIBLE LWS_EXTERN struct lws *
|
||||
lws_adopt_socket_vhost(struct lws_vhost *vh, lws_sockfd_type accept_fd);
|
||||
|
||||
typedef enum {
|
||||
LWS_ADOPT_HTTP = 1, /* absent implies RAW */
|
||||
LWS_ADOPT_SOCKET = 2, /* absent implies file descriptor */
|
||||
LWS_ADOPT_ALLOW_SSL = 4 /* if set requires LWS_ADOPT_SOCKET */
|
||||
} lws_adoption_type;
|
||||
|
||||
typedef union {
|
||||
lws_sockfd_type sockfd;
|
||||
lws_filefd_type filefd;
|
||||
} lws_sock_file_fd_type;
|
||||
|
||||
/*
|
||||
* lws_adopt_socket_vhost2() - adopt foreign socket as if listen socket accepted it
|
||||
* for vhost, allow control over defeat SSL and raw transport mode
|
||||
* lws_adopt_descriptor_vhost() - adopt foreign socket or file descriptor
|
||||
* if socket descriptor, should already have been accepted from listen socket
|
||||
*
|
||||
* \param vhost: lws vhost
|
||||
* \param accept_fd: fd of already-accepted socket to adopt
|
||||
* \param allow_ssl: 0 = no SSL even if vhost supports, 1 = SSL if vhost supports
|
||||
* \param raw: 0 = http[s]/wss[s], 1 = raw mode semantics
|
||||
* \param type: OR-ed combinations of lws_adoption_type flags
|
||||
* \param fd: union with either .sockfd or .filefd set
|
||||
* \param vh_prot_name: NULL or vh protocol name to bind raw connection to
|
||||
*
|
||||
* Either returns new wsi bound to accept_fd, or closes accept_fd and
|
||||
* returns NULL, having cleaned up any new wsi pieces.
|
||||
*
|
||||
* LWS adopts the socket in http serving mode, it's ready to accept an upgrade
|
||||
* to ws or just serve http.
|
||||
* If LWS_ADOPT_SOCKET is set, LWS adopts the socket in http serving mode, it's
|
||||
* ready to accept an upgrade to ws or just serve http.
|
||||
*/
|
||||
LWS_VISIBLE struct lws *
|
||||
lws_adopt_socket_vhost2(struct lws_vhost *vh, lws_sockfd_type accept_fd,
|
||||
int allow_ssl, int raw);
|
||||
lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type,
|
||||
lws_sock_file_fd_type fd, const char *vh_prot_name);
|
||||
|
||||
|
||||
/**
|
||||
* lws_adopt_socket_readbuf() - adopt foreign socket and first rx as if listen socket accepted it
|
||||
|
@ -4254,7 +4276,9 @@ struct lws_fop_fd {
|
|||
};
|
||||
#if defined(WIN32) || defined(_WIN32)
|
||||
/* ... */
|
||||
#if !defined(ssize_t)
|
||||
typedef SSIZE_T ssize_t;
|
||||
#endif
|
||||
/* !!! >:-[ */
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef unsigned __int8 uint8_t;
|
||||
|
|
|
@ -30,9 +30,9 @@ lws_send_pipe_choked(struct lws *wsi)
|
|||
return 1;
|
||||
|
||||
FD_ZERO(&writefds);
|
||||
FD_SET(wsi->sock, &writefds);
|
||||
FD_SET(wsi->desc.sockfd, &writefds);
|
||||
|
||||
if (select(wsi->sock + 1, NULL, &writefds, NULL, &tv) < 1)
|
||||
if (select(wsi->desc.sockfd + 1, NULL, &writefds, NULL, &tv) < 1)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -72,7 +72,7 @@ lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len)
|
|||
//lwsl_notice("%s: wsi %p: len %d\n", __func__, wsi, len);
|
||||
|
||||
wsi->pending_send_completion++;
|
||||
espconn_send(wsi->sock, buf, len);
|
||||
espconn_send(wsi->desc.sockfd, buf, len);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
@ -247,7 +247,7 @@ esp8266_create_tcp_listen_socket(struct lws_vhost *vh)
|
|||
const char *
|
||||
lws_plat_get_peer_simple(struct lws *wsi, char *name, int namelen)
|
||||
{
|
||||
unsigned char *p = wsi->sock->proto.tcp->remote_ip;
|
||||
unsigned char *p = wsi->desc.sockfd->proto.tcp->remote_ip;
|
||||
|
||||
lws_snprintf(name, namelen, "%u.%u.%u.%u", p[0], p[1], p[2], p[3]);
|
||||
|
||||
|
@ -493,7 +493,7 @@ esp8266_tcp_stream_accept(lws_sockfd_type fd, struct lws *wsi)
|
|||
fd->reverse = wsi;
|
||||
|
||||
for (n = 0; n < wsi->context->max_fds ; n++)
|
||||
if (wsi->context->connpool[n] == wsi->sock)
|
||||
if (wsi->context->connpool[n] == wsi->desc.sockfd)
|
||||
wsi->position_in_fds_table = n;
|
||||
}
|
||||
|
||||
|
@ -555,7 +555,7 @@ lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi)
|
|||
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
|
||||
|
||||
context->connpool[wsi->position_in_fds_table + context->max_fds] = (lws_sockfd_type)wsi;
|
||||
wsi->sock->reverse = wsi;
|
||||
wsi->desc.sockfd->reverse = wsi;
|
||||
pt->fds_count++;
|
||||
}
|
||||
|
||||
|
@ -567,13 +567,13 @@ lws_plat_delete_socket_from_fds(struct lws_context *context,
|
|||
int n;
|
||||
|
||||
for (n = 0; n < wsi->context->max_fds; n++)
|
||||
if (wsi->context->connpool[n] == wsi->sock) {
|
||||
if (wsi->context->connpool[n] == wsi->desc.sockfd) {
|
||||
wsi->context->connpool[n] = NULL;
|
||||
wsi->context->connpool[n + wsi->context->max_fds] = NULL;
|
||||
lwsl_notice(" freed connpool %d\n", n);
|
||||
}
|
||||
|
||||
wsi->sock->reverse = NULL;
|
||||
wsi->desc.sockfd->reverse = NULL;
|
||||
pt->fds_count--;
|
||||
}
|
||||
|
||||
|
@ -596,17 +596,17 @@ lws_plat_change_pollfd(struct lws_context *context,
|
|||
lwsl_notice("replaying buffered rx: wsi %p\n", wsi);
|
||||
p = wsi->premature_rx;
|
||||
wsi->premature_rx = NULL;
|
||||
esp8266_cb_rx(wsi->sock,
|
||||
esp8266_cb_rx(wsi->desc.sockfd,
|
||||
(char *)p + wsi->prem_rx_pos,
|
||||
wsi->prem_rx_size - wsi->prem_rx_pos);
|
||||
wsi->prem_rx_size = 0;
|
||||
wsi->prem_rx_pos = 0;
|
||||
lws_free(p);
|
||||
}
|
||||
if (espconn_recv_unhold(wsi->sock) < 0)
|
||||
if (espconn_recv_unhold(wsi->desc.sockfd) < 0)
|
||||
return -1;
|
||||
} else
|
||||
if (espconn_recv_hold(wsi->sock) < 0)
|
||||
if (espconn_recv_hold(wsi->desc.sockfd) < 0)
|
||||
return -1;
|
||||
|
||||
if (!(pfd->events & LWS_POLLOUT))
|
||||
|
|
|
@ -29,7 +29,7 @@ lws_send_pipe_choked(struct lws *wsi)
|
|||
if (wsi->trunc_len)
|
||||
return 1;
|
||||
|
||||
fds.fd = wsi->sock;
|
||||
fds.fd = wsi->desc.sockfd;
|
||||
fds.events = POLLOUT;
|
||||
fds.revents = 0;
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ lws_send_pipe_choked(struct lws *wsi)
|
|||
if (wsi->trunc_len)
|
||||
return 1;
|
||||
|
||||
fds.fd = wsi->sock;
|
||||
fds.fd = wsi->desc.sockfd;
|
||||
fds.events = POLLOUT;
|
||||
fds.revents = 0;
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ wsi_from_fd(const struct lws_context *context, lws_sockfd_type fd)
|
|||
int n = 0;
|
||||
|
||||
for (n = 0; n < context->fd_hashtable[h].length; n++)
|
||||
if (context->fd_hashtable[h].wsi[n]->sock == fd)
|
||||
if (context->fd_hashtable[h].wsi[n]->desc.sockfd == fd)
|
||||
return context->fd_hashtable[h].wsi[n];
|
||||
|
||||
return NULL;
|
||||
|
@ -59,7 +59,7 @@ wsi_from_fd(const struct lws_context *context, lws_sockfd_type fd)
|
|||
int
|
||||
insert_wsi(struct lws_context *context, struct lws *wsi)
|
||||
{
|
||||
int h = LWS_FD_HASH(wsi->sock);
|
||||
int h = LWS_FD_HASH(wsi->desc.sockfd);
|
||||
|
||||
if (context->fd_hashtable[h].length == (getdtablesize() - 1)) {
|
||||
lwsl_err("hash table overflow\n");
|
||||
|
@ -78,7 +78,7 @@ delete_from_fd(struct lws_context *context, lws_sockfd_type fd)
|
|||
int n = 0;
|
||||
|
||||
for (n = 0; n < context->fd_hashtable[h].length; n++)
|
||||
if (context->fd_hashtable[h].wsi[n]->sock == fd) {
|
||||
if (context->fd_hashtable[h].wsi[n]->desc.sockfd == fd) {
|
||||
while (n < context->fd_hashtable[h].length) {
|
||||
context->fd_hashtable[h].wsi[n] =
|
||||
context->fd_hashtable[h].wsi[n + 1];
|
||||
|
@ -417,7 +417,7 @@ lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi)
|
|||
|
||||
pt->fds[pt->fds_count++].revents = 0;
|
||||
pt->events[pt->fds_count] = pt->events[0];
|
||||
WSAEventSelect(wsi->sock, pt->events[0],
|
||||
WSAEventSelect(wsi->desc.sockfd, pt->events[0],
|
||||
LWS_POLLIN | LWS_POLLHUP | FD_CONNECT);
|
||||
}
|
||||
|
||||
|
@ -441,7 +441,7 @@ lws_plat_check_connection_error(struct lws *wsi)
|
|||
int optVal;
|
||||
int optLen = sizeof(int);
|
||||
|
||||
if (getsockopt(wsi->sock, SOL_SOCKET, SO_ERROR,
|
||||
if (getsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_ERROR,
|
||||
(char*)&optVal, &optLen) != SOCKET_ERROR && optVal &&
|
||||
optVal != LWS_EALREADY && optVal != LWS_EINPROGRESS &&
|
||||
optVal != LWS_EWOULDBLOCK && optVal != WSAEINVAL) {
|
||||
|
@ -465,7 +465,7 @@ lws_plat_change_pollfd(struct lws_context *context,
|
|||
if ((pfd->events & LWS_POLLOUT))
|
||||
networkevents |= LWS_POLLOUT;
|
||||
|
||||
if (WSAEventSelect(wsi->sock,
|
||||
if (WSAEventSelect(wsi->desc.sockfd,
|
||||
pt->events[0],
|
||||
networkevents) != SOCKET_ERROR)
|
||||
return 0;
|
||||
|
|
|
@ -133,7 +133,7 @@ int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len)
|
|||
goto handle_truncated_send;
|
||||
}
|
||||
|
||||
if (!lws_socket_is_valid(wsi->sock))
|
||||
if (!lws_socket_is_valid(wsi->desc.sockfd))
|
||||
lwsl_warn("** error invalid sock but expected to send\n");
|
||||
|
||||
/* limit sending */
|
||||
|
@ -744,7 +744,7 @@ lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len)
|
|||
{
|
||||
int n;
|
||||
|
||||
n = recv(wsi->sock, (char *)buf, len, 0);
|
||||
n = recv(wsi->desc.sockfd, (char *)buf, len, 0);
|
||||
if (n >= 0) {
|
||||
if (wsi->vhost)
|
||||
wsi->vhost->conn_stats.rx += n;
|
||||
|
@ -767,7 +767,7 @@ lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len)
|
|||
int n = 0;
|
||||
|
||||
#if LWS_POSIX
|
||||
n = send(wsi->sock, (char *)buf, len, MSG_NOSIGNAL);
|
||||
n = send(wsi->desc.sockfd, (char *)buf, len, MSG_NOSIGNAL);
|
||||
// lwsl_info("%s: sent len %d result %d", __func__, len, n);
|
||||
if (n >= 0)
|
||||
return n;
|
||||
|
@ -789,7 +789,7 @@ lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len)
|
|||
// !!!
|
||||
#endif
|
||||
|
||||
lwsl_debug("ERROR writing len %d to skt fd %d err %d / errno %d\n", len, wsi->sock, n, LWS_ERRNO);
|
||||
lwsl_debug("ERROR writing len %d to skt fd %d err %d / errno %d\n", len, wsi->desc.sockfd, n, LWS_ERRNO);
|
||||
return LWS_SSL_CAPABLE_ERROR;
|
||||
}
|
||||
#endif
|
||||
|
|
28
lib/pollfd.c
28
lib/pollfd.c
|
@ -39,7 +39,7 @@ _lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa)
|
|||
wsi->position_in_fds_table < pt->fds_count);
|
||||
|
||||
pfd = &pt->fds[wsi->position_in_fds_table];
|
||||
pa->fd = wsi->sock;
|
||||
pa->fd = wsi->desc.sockfd;
|
||||
pa->prev_events = pfd->events;
|
||||
pa->events = pfd->events = (pfd->events & ~_and) | _or;
|
||||
|
||||
|
@ -132,13 +132,13 @@ lws_accept_modulation(struct lws_context_per_thread *pt, int allow)
|
|||
int
|
||||
insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi)
|
||||
{
|
||||
struct lws_pollargs pa = { wsi->sock, LWS_POLLIN, 0 };
|
||||
struct lws_pollargs pa = { wsi->desc.sockfd, LWS_POLLIN, 0 };
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
int ret = 0;
|
||||
|
||||
|
||||
lwsl_debug("%s: %p: tsi=%d, sock=%d, pos-in-fds=%d\n",
|
||||
__func__, wsi, wsi->tsi, wsi->sock, pt->fds_count);
|
||||
__func__, wsi, wsi->tsi, wsi->desc.sockfd, pt->fds_count);
|
||||
|
||||
if ((unsigned int)pt->fds_count >= context->fd_limit_per_thread) {
|
||||
lwsl_err("Too many fds (%d vs %d)\n", context->max_fds,
|
||||
|
@ -147,16 +147,16 @@ insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi)
|
|||
}
|
||||
|
||||
#if !defined(_WIN32) && !defined(LWS_WITH_ESP8266)
|
||||
if (wsi->sock >= context->max_fds) {
|
||||
if (wsi->desc.sockfd >= context->max_fds) {
|
||||
lwsl_err("Socket fd %d is too high (%d)\n",
|
||||
wsi->sock, context->max_fds);
|
||||
wsi->desc.sockfd, context->max_fds);
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(wsi);
|
||||
assert(wsi->vhost);
|
||||
assert(lws_socket_is_valid(wsi->sock));
|
||||
assert(lws_socket_is_valid(wsi->desc.sockfd));
|
||||
|
||||
if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL,
|
||||
wsi->user_space, (void *) &pa, 1))
|
||||
|
@ -172,7 +172,7 @@ insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi)
|
|||
|
||||
// lwsl_notice("%s: %p: setting posinfds %d\n", __func__, wsi, wsi->position_in_fds_table);
|
||||
|
||||
pt->fds[wsi->position_in_fds_table].fd = wsi->sock;
|
||||
pt->fds[wsi->position_in_fds_table].fd = wsi->desc.sockfd;
|
||||
#if LWS_POSIX
|
||||
pt->fds[wsi->position_in_fds_table].events = LWS_POLLIN;
|
||||
#else
|
||||
|
@ -204,7 +204,7 @@ int
|
|||
remove_wsi_socket_from_fds(struct lws *wsi)
|
||||
{
|
||||
struct lws_context *context = wsi->context;
|
||||
struct lws_pollargs pa = { wsi->sock, 0, 0 };
|
||||
struct lws_pollargs pa = { wsi->desc.sockfd, 0, 0 };
|
||||
#if !defined(LWS_WITH_ESP8266)
|
||||
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
||||
struct lws *end_wsi;
|
||||
|
@ -213,8 +213,8 @@ remove_wsi_socket_from_fds(struct lws *wsi)
|
|||
int m, ret = 0;
|
||||
|
||||
#if !defined(_WIN32) && !defined(LWS_WITH_ESP8266)
|
||||
if (wsi->sock > context->max_fds) {
|
||||
lwsl_err("fd %d too high (%d)\n", wsi->sock, context->max_fds);
|
||||
if (wsi->desc.sockfd > context->max_fds) {
|
||||
lwsl_err("fd %d too high (%d)\n", wsi->desc.sockfd, context->max_fds);
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
@ -259,7 +259,7 @@ remove_wsi_socket_from_fds(struct lws *wsi)
|
|||
lws_pt_lock(pt);
|
||||
|
||||
lwsl_debug("%s: wsi=%p, sock=%d, fds pos=%d, end guy pos=%d, endfd=%d\n",
|
||||
__func__, wsi, wsi->sock, wsi->position_in_fds_table,
|
||||
__func__, wsi, wsi->desc.sockfd, wsi->position_in_fds_table,
|
||||
pt->fds_count, pt->fds[pt->fds_count].fd);
|
||||
|
||||
/* have the last guy take up the now vacant slot */
|
||||
|
@ -278,12 +278,12 @@ remove_wsi_socket_from_fds(struct lws *wsi)
|
|||
end_wsi->position_in_fds_table = m;
|
||||
|
||||
/* deletion guy's lws_lookup entry needs nuking */
|
||||
delete_from_fd(context, wsi->sock);
|
||||
delete_from_fd(context, wsi->desc.sockfd);
|
||||
/* removed wsi has no position any more */
|
||||
wsi->position_in_fds_table = -1;
|
||||
|
||||
/* remove also from external POLL support via protocol 0 */
|
||||
if (lws_socket_is_valid(wsi->sock))
|
||||
if (lws_socket_is_valid(wsi->desc.sockfd))
|
||||
if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_DEL_POLL_FD,
|
||||
wsi->user_space, (void *) &pa, 0))
|
||||
ret = -1;
|
||||
|
@ -396,7 +396,7 @@ network_sock:
|
|||
return 1;
|
||||
|
||||
if (wsi->position_in_fds_table < 0) {
|
||||
lwsl_err("%s: failed to find socket %d\n", __func__, wsi->sock);
|
||||
lwsl_err("%s: failed to find socket %d\n", __func__, wsi->desc.sockfd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
|
@ -543,7 +543,8 @@ enum connection_mode {
|
|||
/* special internal types */
|
||||
LWSCM_SERVER_LISTENER,
|
||||
LWSCM_CGI, /* stdin, stdout, stderr for another cgi master wsi */
|
||||
LWSCM_RAW, /* raw */
|
||||
LWSCM_RAW, /* raw with bulk handling */
|
||||
LWSCM_RAW_FILEDESC, /* raw without bulk handling */
|
||||
|
||||
/* HTTP Client related */
|
||||
LWSCM_HTTP_CLIENT = LWSCM_FLAG_IMPLIES_CALLBACK_CLOSED_CLIENT_HTTP,
|
||||
|
@ -928,8 +929,7 @@ enum {
|
|||
|
||||
#if defined(LWS_USE_LIBEV)
|
||||
LWS_EXTERN void
|
||||
lws_libev_accept(struct lws *new_wsi, lws_sockfd_type accept_fd);
|
||||
LWS_EXTERN void
|
||||
lws_libev_accept(struct lws *new_wsi, lws_sock_file_fd_type desc);
|
||||
lws_libev_io(struct lws *wsi, int flags);
|
||||
LWS_EXTERN int
|
||||
lws_libev_init_fd_table(struct lws_context *context);
|
||||
|
@ -956,7 +956,7 @@ LWS_EXTERN void lws_feature_status_libev(struct lws_context_creation_info *info)
|
|||
|
||||
#if defined(LWS_USE_LIBUV)
|
||||
LWS_EXTERN void
|
||||
lws_libuv_accept(struct lws *new_wsi, lws_sockfd_type accept_fd);
|
||||
lws_libuv_accept(struct lws *new_wsi, lws_sock_file_fd_type desc);
|
||||
LWS_EXTERN void
|
||||
lws_libuv_io(struct lws *wsi, int flags);
|
||||
LWS_EXTERN int
|
||||
|
@ -1408,8 +1408,7 @@ struct lws {
|
|||
unsigned long action_start;
|
||||
unsigned long latency_start;
|
||||
#endif
|
||||
/* pointer / int */
|
||||
lws_sockfd_type sock;
|
||||
lws_sock_file_fd_type desc; /* .filefd / .sockfd */
|
||||
|
||||
/* ints */
|
||||
int position_in_fds_table;
|
||||
|
@ -1562,7 +1561,7 @@ LWS_EXTERN int
|
|||
delete_from_fd(struct lws_context *context, lws_sockfd_type fd);
|
||||
#else
|
||||
#define wsi_from_fd(A,B) A->lws_lookup[B]
|
||||
#define insert_wsi(A,B) assert(A->lws_lookup[B->sock] == 0); A->lws_lookup[B->sock]=B
|
||||
#define insert_wsi(A,B) assert(A->lws_lookup[B->desc.sockfd] == 0); A->lws_lookup[B->desc.sockfd]=B
|
||||
#define delete_from_fd(A,B) A->lws_lookup[B]=0
|
||||
#endif
|
||||
|
||||
|
|
125
lib/server.c
125
lib/server.c
|
@ -142,7 +142,7 @@ lws_context_init_server(struct lws_context_creation_info *info,
|
|||
goto bail;
|
||||
}
|
||||
wsi->context = vhost->context;
|
||||
wsi->sock = sockfd;
|
||||
wsi->desc.sockfd = sockfd;
|
||||
wsi->mode = LWSCM_SERVER_LISTENER;
|
||||
wsi->protocol = vhost->protocols;
|
||||
wsi->tsi = m;
|
||||
|
@ -161,7 +161,7 @@ lws_context_init_server(struct lws_context_creation_info *info,
|
|||
vhost->lserv_wsi = wsi;
|
||||
|
||||
#if LWS_POSIX
|
||||
n = listen(wsi->sock, LWS_SOMAXCONN);
|
||||
n = listen(wsi->desc.sockfd, LWS_SOMAXCONN);
|
||||
if (n < 0) {
|
||||
lwsl_err("listen failed with error %d\n", LWS_ERRNO);
|
||||
vhost->lserv_wsi = NULL;
|
||||
|
@ -172,7 +172,7 @@ lws_context_init_server(struct lws_context_creation_info *info,
|
|||
} /* for each thread able to independently listen */
|
||||
#else
|
||||
#if defined(LWS_WITH_ESP8266)
|
||||
esp8266_tcp_stream_bind(wsi->sock, info->port, wsi);
|
||||
esp8266_tcp_stream_bind(wsi->desc.sockfd, info->port, wsi);
|
||||
#endif
|
||||
#endif
|
||||
if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) {
|
||||
|
@ -1502,7 +1502,7 @@ upgrade_ws:
|
|||
wsi->u.ws.rx_ubuf_alloc = n;
|
||||
lwsl_debug("Allocating RX buffer %d\n", n);
|
||||
#if LWS_POSIX && !defined(LWS_WITH_ESP32)
|
||||
if (setsockopt(wsi->sock, SOL_SOCKET, SO_SNDBUF,
|
||||
if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF,
|
||||
(const char *)&n, sizeof n)) {
|
||||
lwsl_warn("Failed to set SNDBUF to %d", n);
|
||||
return 1;
|
||||
|
@ -1611,7 +1611,7 @@ lws_create_new_server_wsi(struct lws_vhost *vhost)
|
|||
new_wsi->protocol = vhost->protocols;
|
||||
new_wsi->user_space = NULL;
|
||||
new_wsi->ietf_spec_revision = 0;
|
||||
new_wsi->sock = LWS_SOCK_INVALID;
|
||||
new_wsi->desc.sockfd = LWS_SOCK_INVALID;
|
||||
vhost->context->count_wsi_allocated++;
|
||||
|
||||
/*
|
||||
|
@ -1686,33 +1686,56 @@ lws_http_transaction_completed(struct lws *wsi)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* if not a socket, it's a raw, non-ssl file descriptor */
|
||||
|
||||
LWS_VISIBLE struct lws *
|
||||
lws_adopt_socket_vhost2(struct lws_vhost *vh, lws_sockfd_type accept_fd,
|
||||
int allow_ssl, int raw)
|
||||
lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type,
|
||||
lws_sock_file_fd_type fd, const char *vh_prot_name)
|
||||
{
|
||||
struct lws_context *context = vh->context;
|
||||
struct lws *new_wsi = lws_create_new_server_wsi(vh);
|
||||
int n;
|
||||
int n, ssl = 0;
|
||||
|
||||
if (!new_wsi) {
|
||||
compatible_close(accept_fd);
|
||||
if (type & LWS_ADOPT_SOCKET)
|
||||
compatible_close(fd.sockfd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lwsl_debug("%s: new wsi %p, sockfd %d, cb %p\n", __func__, new_wsi,
|
||||
accept_fd, context->vhost_list->protocols[0].callback);
|
||||
new_wsi->desc = fd;
|
||||
new_wsi->protocol = &context->vhost_list->
|
||||
protocols[vh->default_protocol_index];
|
||||
|
||||
new_wsi->sock = accept_fd;
|
||||
if (!(type & LWS_ADOPT_SOCKET)) {
|
||||
new_wsi->protocol = lws_vhost_name_to_protocol(new_wsi->vhost,
|
||||
vh_prot_name);
|
||||
if (!new_wsi->protocol) {
|
||||
lwsl_err("Protocol %s not enabled on vhost %s\n",
|
||||
vh_prot_name, new_wsi->vhost->name);
|
||||
lws_free(new_wsi);
|
||||
|
||||
/* the transport is accepted... give him time to negotiate */
|
||||
lws_set_timeout(new_wsi, PENDING_TIMEOUT_ESTABLISH_WITH_SERVER,
|
||||
context->timeout_secs);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (type & LWS_ADOPT_SOCKET) {
|
||||
|
||||
lwsl_debug("%s: new wsi %p, sockfd %d\n", __func__, new_wsi,
|
||||
(int)(size_t)fd.sockfd);
|
||||
|
||||
|
||||
/* the transport is accepted... give him time to negotiate */
|
||||
lws_set_timeout(new_wsi, PENDING_TIMEOUT_ESTABLISH_WITH_SERVER,
|
||||
context->timeout_secs);
|
||||
|
||||
#if LWS_POSIX == 0
|
||||
#if defined(LWS_WITH_ESP8266)
|
||||
esp8266_tcp_stream_accept(accept_fd, new_wsi);
|
||||
esp8266_tcp_stream_accept(accept_fd, new_wsi);
|
||||
#endif
|
||||
#endif
|
||||
} else //* file desc */
|
||||
lwsl_debug("%s: new wsi %p, filefd %d\n", __func__, new_wsi,
|
||||
(int)(size_t)fd.filefd);
|
||||
|
||||
/*
|
||||
* A new connection was accepted. Give the user a chance to
|
||||
* set properties of the newly created wsi. There's no protocol
|
||||
|
@ -1720,45 +1743,65 @@ lws_adopt_socket_vhost2(struct lws_vhost *vh, lws_sockfd_type accept_fd,
|
|||
* itself by default protocols[0]
|
||||
*/
|
||||
n = LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED;
|
||||
if (raw)
|
||||
n = LWS_CALLBACK_RAW_ADOPT;
|
||||
if ((context->vhost_list->protocols[vh->default_protocol_index].callback)(
|
||||
if (!(type & LWS_ADOPT_HTTP)) {
|
||||
if (!(type & LWS_ADOPT_SOCKET))
|
||||
n = LWS_CALLBACK_RAW_ADOPT_FILE;
|
||||
else
|
||||
n = LWS_CALLBACK_RAW_ADOPT;
|
||||
}
|
||||
if ((new_wsi->protocol->callback)(
|
||||
new_wsi, n, NULL, NULL, 0)) {
|
||||
/* force us off the timeout list by hand */
|
||||
lws_set_timeout(new_wsi, NO_PENDING_TIMEOUT, 0);
|
||||
compatible_close(new_wsi->sock);
|
||||
if (type & LWS_ADOPT_SOCKET) {
|
||||
/* force us off the timeout list by hand */
|
||||
lws_set_timeout(new_wsi, NO_PENDING_TIMEOUT, 0);
|
||||
compatible_close(new_wsi->desc.sockfd);
|
||||
}
|
||||
lws_free(new_wsi);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lws_libev_accept(new_wsi, new_wsi->sock);
|
||||
lws_libuv_accept(new_wsi, new_wsi->sock);
|
||||
if (!LWS_SSL_ENABLED(new_wsi->vhost) || !(type & LWS_ADOPT_ALLOW_SSL) ||
|
||||
!(type & LWS_ADOPT_SOCKET)) {
|
||||
/* non-SSL */
|
||||
if (!(type & LWS_ADOPT_HTTP)) {
|
||||
if (!(type & LWS_ADOPT_SOCKET))
|
||||
new_wsi->mode = LWSCM_RAW_FILEDESC;
|
||||
else
|
||||
new_wsi->mode = LWSCM_RAW;
|
||||
}
|
||||
} else {
|
||||
/* SSL */
|
||||
if (!(type & LWS_ADOPT_HTTP))
|
||||
new_wsi->mode = LWSCM_SSL_INIT_RAW;
|
||||
else
|
||||
new_wsi->mode = LWSCM_SSL_INIT;
|
||||
|
||||
if (!LWS_SSL_ENABLED(new_wsi->vhost) || allow_ssl == 0) {
|
||||
if (raw)
|
||||
new_wsi->mode = LWSCM_RAW;
|
||||
ssl = 1;
|
||||
}
|
||||
|
||||
lws_libev_accept(new_wsi, new_wsi->desc);
|
||||
lws_libuv_accept(new_wsi, new_wsi->desc);
|
||||
|
||||
if (!ssl) {
|
||||
if (insert_wsi_socket_into_fds(context, new_wsi)) {
|
||||
lwsl_err("%s: fail inserting socket\n", __func__);
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
if (raw)
|
||||
new_wsi->mode = LWSCM_SSL_INIT_RAW;
|
||||
else
|
||||
new_wsi->mode = LWSCM_SSL_INIT;
|
||||
if (lws_server_socket_service_ssl(new_wsi, accept_fd)) {
|
||||
} else
|
||||
if (lws_server_socket_service_ssl(new_wsi, fd.sockfd)) {
|
||||
lwsl_err("%s: fail ssl negotiation\n", __func__);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (!lws_header_table_attach(new_wsi, 0))
|
||||
lwsl_debug("Attached ah immediately\n");
|
||||
if (type & LWS_ADOPT_HTTP)
|
||||
if (!lws_header_table_attach(new_wsi, 0))
|
||||
lwsl_debug("Attached ah immediately\n");
|
||||
|
||||
return new_wsi;
|
||||
|
||||
fail:
|
||||
lws_close_free_wsi(new_wsi, LWS_CLOSE_STATUS_NOSTATUS);
|
||||
if (type & LWS_ADOPT_SOCKET)
|
||||
lws_close_free_wsi(new_wsi, LWS_CLOSE_STATUS_NOSTATUS);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
@ -1766,13 +1809,17 @@ fail:
|
|||
LWS_VISIBLE struct lws *
|
||||
lws_adopt_socket_vhost(struct lws_vhost *vh, lws_sockfd_type accept_fd)
|
||||
{
|
||||
return lws_adopt_socket_vhost2(vh, accept_fd, 1, 0);
|
||||
lws_sock_file_fd_type fd;
|
||||
|
||||
fd.sockfd = accept_fd;
|
||||
return lws_adopt_descriptor_vhost(vh, LWS_ADOPT_SOCKET |
|
||||
LWS_ADOPT_HTTP | LWS_ADOPT_ALLOW_SSL, fd, NULL);
|
||||
}
|
||||
|
||||
LWS_VISIBLE struct lws *
|
||||
lws_adopt_socket(struct lws_context *context, lws_sockfd_type accept_fd)
|
||||
{
|
||||
return lws_adopt_socket_vhost2(context->vhost_list, accept_fd, 1, 0);
|
||||
return lws_adopt_socket_vhost(context->vhost_list, accept_fd);
|
||||
}
|
||||
|
||||
/* Common read-buffer adoption for lws_adopt_*_readbuf */
|
||||
|
|
|
@ -30,6 +30,9 @@ lws_calllback_as_writeable(struct lws *wsi)
|
|||
case LWSCM_RAW:
|
||||
n = LWS_CALLBACK_RAW_WRITEABLE;
|
||||
break;
|
||||
case LWSCM_RAW_FILEDESC:
|
||||
n = LWS_CALLBACK_RAW_WRITEABLE_FILE;
|
||||
break;
|
||||
case LWSCM_WS_CLIENT:
|
||||
n = LWS_CALLBACK_CLIENT_WRITEABLE;
|
||||
break;
|
||||
|
@ -198,7 +201,7 @@ lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd)
|
|||
*/
|
||||
|
||||
ret = 1;
|
||||
if (wsi->mode == LWSCM_RAW)
|
||||
if (wsi->mode == LWSCM_RAW || wsi->mode == LWSCM_RAW_FILEDESC)
|
||||
ret = 0;
|
||||
while (ret == 1) {
|
||||
|
||||
|
@ -366,7 +369,7 @@ lws_service_timeout_check(struct lws *wsi, unsigned int sec)
|
|||
*/
|
||||
if ((time_t)sec > wsi->pending_timeout_limit) {
|
||||
//#if LWS_POSIX
|
||||
if (wsi->sock != LWS_SOCK_INVALID && wsi->position_in_fds_table >= 0)
|
||||
if (wsi->desc.sockfd != LWS_SOCK_INVALID && wsi->position_in_fds_table >= 0)
|
||||
n = pt->fds[wsi->position_in_fds_table].events;
|
||||
|
||||
/* no need to log normal idle keepalive timeout */
|
||||
|
@ -734,7 +737,7 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t
|
|||
while (wsi) {
|
||||
/* we have to take copies, because he may be deleted */
|
||||
wsi1 = wsi->timeout_list;
|
||||
tmp_fd = wsi->sock;
|
||||
tmp_fd = wsi->desc.sockfd;
|
||||
if (lws_service_timeout_check(wsi, (unsigned int)now)) {
|
||||
/* he did time out... */
|
||||
if (tmp_fd == our_fd)
|
||||
|
@ -863,17 +866,42 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t
|
|||
return 1;
|
||||
goto handled;
|
||||
|
||||
case LWSCM_RAW_FILEDESC:
|
||||
case LWSCM_RAW:
|
||||
if (pollfd->revents & LWS_POLLOUT) {
|
||||
n = lws_calllback_as_writeable(wsi);
|
||||
if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) {
|
||||
lwsl_info("failed at set pollfd\n");
|
||||
return 1;
|
||||
}
|
||||
if (n)
|
||||
goto close_and_handled;
|
||||
}
|
||||
n = LWS_CALLBACK_RAW_RX;
|
||||
if (wsi->mode == LWSCM_RAW_FILEDESC)
|
||||
n = LWS_CALLBACK_RAW_RX_FILE;
|
||||
|
||||
if (pollfd->revents & LWS_POLLIN) {
|
||||
if (user_callback_handle_rxflow(
|
||||
wsi->protocol->callback,
|
||||
wsi, n,
|
||||
wsi->user_space, NULL, 0)) {
|
||||
lwsl_debug("raw rx callback closed it\n");
|
||||
goto close_and_handled;
|
||||
}
|
||||
}
|
||||
n = 0;
|
||||
goto handled;
|
||||
|
||||
case LWSCM_WS_SERVING:
|
||||
case LWSCM_WS_CLIENT:
|
||||
case LWSCM_HTTP2_SERVING:
|
||||
case LWSCM_HTTP_CLIENT_ACCEPTED:
|
||||
case LWSCM_RAW:
|
||||
|
||||
/* 1: something requested a callback when it was OK to write */
|
||||
|
||||
if ((pollfd->revents & LWS_POLLOUT) &&
|
||||
((wsi->mode == LWSCM_RAW) ||
|
||||
(wsi->state == LWSS_ESTABLISHED ||
|
||||
((wsi->state == LWSS_ESTABLISHED ||
|
||||
wsi->state == LWSS_HTTP2_ESTABLISHED ||
|
||||
wsi->state == LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS ||
|
||||
wsi->state == LWSS_RETURNED_CLOSE_ALREADY ||
|
||||
|
|
|
@ -184,7 +184,7 @@ lws_ssl_client_bio_create(struct lws *wsi)
|
|||
#endif
|
||||
#endif /* USE_WOLFSSL */
|
||||
|
||||
wsi->client_bio = BIO_new_socket(wsi->sock, BIO_NOCLOSE);
|
||||
wsi->client_bio = BIO_new_socket(wsi->desc.sockfd, BIO_NOCLOSE);
|
||||
SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio);
|
||||
|
||||
#ifdef USE_WOLFSSL
|
||||
|
|
|
@ -470,7 +470,7 @@ lws_server_socket_service_ssl(struct lws *wsi, lws_sockfd_type accept_fd)
|
|||
|
||||
lws_latency_pre(context, wsi);
|
||||
|
||||
n = recv(wsi->sock, (char *)pt->serv_buf, context->pt_serv_buf_size,
|
||||
n = recv(wsi->desc.sockfd, (char *)pt->serv_buf, context->pt_serv_buf_size,
|
||||
MSG_PEEK);
|
||||
|
||||
/*
|
||||
|
@ -550,7 +550,7 @@ go_again:
|
|||
break;
|
||||
}
|
||||
|
||||
lwsl_err("SSL_accept failed socket %u: %s\n", wsi->sock,
|
||||
lwsl_err("SSL_accept failed socket %u: %s\n", wsi->desc.sockfd,
|
||||
lws_ssl_get_error_string(m, n, buf, sizeof(buf)));
|
||||
lws_ssl_elaborate_error();
|
||||
goto fail;
|
||||
|
|
227
plugins/protocol_lws_raw_test.c
Normal file
227
plugins/protocol_lws_raw_test.c
Normal file
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* ws protocol handler plugin for testing raw file and raw socket
|
||||
*
|
||||
* Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This file is made available under the Creative Commons CC0 1.0
|
||||
* Universal Public Domain Dedication.
|
||||
*
|
||||
* The person who associated a work with this deed has dedicated
|
||||
* the work to the public domain by waiving all of his or her rights
|
||||
* to the work worldwide under copyright law, including all related
|
||||
* and neighboring rights, to the extent allowed by law. You can copy,
|
||||
* modify, distribute and perform the work, even for commercial purposes,
|
||||
* all without asking permission.
|
||||
*
|
||||
* These test plugins are intended to be adapted for use in your code, which
|
||||
* may be proprietary. So unlike the library itself, they are licensed
|
||||
* Public Domain.
|
||||
*
|
||||
* Enable on a vhost like this
|
||||
*
|
||||
* "protocol-lws-raw-test": {
|
||||
* "status": "ok",
|
||||
* "fifo-path": "/tmp/lws-test-raw"
|
||||
* },
|
||||
*
|
||||
* Then you can feed it data through the FIFO like this
|
||||
*
|
||||
* $ sudo sh -c "echo hello > /tmp/lws-test-raw"
|
||||
*
|
||||
* This plugin simply prints the data. But it does it through the lws event loop /
|
||||
* service poll.
|
||||
*/
|
||||
|
||||
#if !defined (LWS_PLUGIN_STATIC)
|
||||
#define LWS_DLL
|
||||
#define LWS_INTERNAL
|
||||
#include "../lib/libwebsockets.h"
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
|
||||
struct per_vhost_data__raw_test {
|
||||
struct lws_context *context;
|
||||
struct lws_vhost *vhost;
|
||||
const struct lws_protocols *protocol;
|
||||
char fifo_path[100];
|
||||
int fifo;
|
||||
char zero_length_read;
|
||||
};
|
||||
|
||||
struct per_session_data__raw_test {
|
||||
int number;
|
||||
};
|
||||
|
||||
static int
|
||||
callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
|
||||
void *user, void *in, size_t len)
|
||||
{
|
||||
struct per_session_data__raw_test *pss =
|
||||
(struct per_session_data__raw_test *)user;
|
||||
struct per_vhost_data__raw_test *vhd =
|
||||
(struct per_vhost_data__raw_test *)
|
||||
lws_protocol_vh_priv_get(lws_get_vhost(wsi),
|
||||
lws_get_protocol(wsi));
|
||||
lws_sock_file_fd_type u;
|
||||
|
||||
(void)pss;
|
||||
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_PROTOCOL_INIT:
|
||||
vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
|
||||
lws_get_protocol(wsi),
|
||||
sizeof(struct per_vhost_data__raw_test));
|
||||
vhd->context = lws_get_context(wsi);
|
||||
vhd->protocol = lws_get_protocol(wsi);
|
||||
vhd->vhost = lws_get_vhost(wsi);
|
||||
{
|
||||
const struct lws_protocol_vhost_options *pvo =
|
||||
(const struct lws_protocol_vhost_options *)in;
|
||||
while (pvo) {
|
||||
if (!strcmp(pvo->name, "fifo-path"))
|
||||
strncpy(vhd->fifo_path, pvo->value, sizeof(vhd->fifo_path) - 1);
|
||||
pvo = pvo->next;
|
||||
}
|
||||
if (vhd->fifo_path[0] == '\0') {
|
||||
lwsl_err("Missing pvo \"fifo-path\"\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
unlink(vhd->fifo_path);
|
||||
if (mkfifo(vhd->fifo_path, 0666)) {
|
||||
lwsl_err("mkfifo failed\n");
|
||||
return 1;
|
||||
}
|
||||
vhd->fifo = open(vhd->fifo_path, O_NONBLOCK | O_RDONLY);
|
||||
if (vhd->fifo == -1) {
|
||||
lwsl_err("opening fifo failed\n");
|
||||
unlink(vhd->fifo_path);
|
||||
return 1;
|
||||
}
|
||||
lwsl_notice("FIFO %s created\n", vhd->fifo_path);
|
||||
u.filefd = vhd->fifo;
|
||||
if (!lws_adopt_descriptor_vhost(vhd->vhost, 0, u, "protocol-lws-raw-test")) {
|
||||
lwsl_err("Failed to adopt fifo descriptor\n");
|
||||
close(vhd->fifo);
|
||||
unlink(vhd->fifo_path);
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_PROTOCOL_DESTROY:
|
||||
if (!vhd)
|
||||
break;
|
||||
if (vhd->fifo >- 0) {
|
||||
close(vhd->fifo);
|
||||
unlink(vhd->fifo_path);
|
||||
}
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_RAW_ADOPT_FILE:
|
||||
lwsl_notice("LWS_CALLBACK_RAW_ADOPT_FILE\n");
|
||||
break;
|
||||
|
||||
|
||||
case LWS_CALLBACK_RAW_RX_FILE:
|
||||
lwsl_notice("LWS_CALLBACK_RAW_RX_FILE\n");
|
||||
{
|
||||
char buf[256];
|
||||
int n;
|
||||
|
||||
n = read(vhd->fifo, buf, sizeof(buf) - 1);
|
||||
if (n < 0) {
|
||||
lwsl_err("FIFO read failed\n");
|
||||
return 1;
|
||||
}
|
||||
/*
|
||||
* When nobody opened the other side of the FIFO, the FIFO fd acts well and
|
||||
* only signals POLLIN when somebody opened and wrote to it.
|
||||
*
|
||||
* But if the other side of the FIFO closed it, we will see an endless
|
||||
* POLLIN and 0 available to read.
|
||||
*
|
||||
* The only way to handle it is to reopen the FIFO our side and wait for a
|
||||
* new peer. This is a quirk of FIFOs not of LWS.
|
||||
*/
|
||||
if (n == 0) { /* peer closed - do reopen in close processing */
|
||||
vhd->zero_length_read = 1;
|
||||
return 1;
|
||||
}
|
||||
buf[n] = '\0';
|
||||
lwsl_info("read %d\n", n);
|
||||
puts(buf);
|
||||
}
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_RAW_CLOSE_FILE:
|
||||
lwsl_notice("LWS_CALLBACK_RAW_CLOSE_FILE\n");
|
||||
if (vhd->zero_length_read) {
|
||||
vhd->zero_length_read = 0;
|
||||
close(vhd->fifo);
|
||||
/* the wsi that adopted the fifo file is closing... reopen the fifo and readopt */
|
||||
vhd->fifo = open(vhd->fifo_path, O_NONBLOCK | O_RDONLY);
|
||||
if (vhd->fifo == -1) {
|
||||
lwsl_err("opening fifo failed\n");
|
||||
return 1;
|
||||
}
|
||||
lwsl_notice("FIFO %s reopened\n", vhd->fifo_path);
|
||||
u.filefd = vhd->fifo;
|
||||
if (!lws_adopt_descriptor_vhost(vhd->vhost, 0, u, "protocol-lws-raw-test")) {
|
||||
lwsl_err("Failed to adopt fifo descriptor\n");
|
||||
close(vhd->fifo);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_RAW_WRITEABLE_FILE:
|
||||
lwsl_notice("LWS_CALLBACK_RAW_WRITEABLE_FILE\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define LWS_PLUGIN_PROTOCOL_RAW_TEST \
|
||||
{ \
|
||||
"protocol-lws-raw-test", \
|
||||
callback_raw_test, \
|
||||
sizeof(struct per_session_data__raw_test), \
|
||||
1024, /* rx buf size must be >= permessage-deflate rx size */ \
|
||||
}
|
||||
|
||||
#if !defined (LWS_PLUGIN_STATIC)
|
||||
|
||||
static const struct lws_protocols protocols[] = {
|
||||
LWS_PLUGIN_PROTOCOL_RAW_TEST
|
||||
};
|
||||
|
||||
LWS_EXTERN LWS_VISIBLE int
|
||||
init_protocol_lws_raw_test(struct lws_context *context,
|
||||
struct lws_plugin_capability *c)
|
||||
{
|
||||
if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
|
||||
lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
|
||||
c->api_magic);
|
||||
return 1;
|
||||
}
|
||||
|
||||
c->protocols = protocols;
|
||||
c->count_protocols = ARRAY_SIZE(protocols);
|
||||
c->extensions = NULL;
|
||||
c->count_extensions = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_EXTERN LWS_VISIBLE int
|
||||
destroy_protocol_lws_raw_test(struct lws_context *context)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Add table
Reference in a new issue