mirror of
https://github.com/curl/curl.git
synced 2026-07-29 04:23:08 +03:00
move easy_pollset declarations and code to select.[ch]
This commit is contained in:
parent
317096dd97
commit
1a20a4a534
11 changed files with 224 additions and 217 deletions
|
|
@ -63,6 +63,7 @@
|
|||
#include "url.h"
|
||||
#include "multiif.h"
|
||||
#include "curl_threads.h"
|
||||
#include "select.h"
|
||||
#include "strdup.h"
|
||||
|
||||
#ifdef USE_ARES
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
#include "http_proxy.h"
|
||||
#include "multiif.h"
|
||||
#include "sendf.h"
|
||||
#include "select.h"
|
||||
#include "cf-h2-proxy.h"
|
||||
|
||||
/* The last 3 #include files should be in this order */
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include "cf-haproxy.h"
|
||||
#include "curl_trc.h"
|
||||
#include "multiif.h"
|
||||
#include "select.h"
|
||||
|
||||
/* The last 3 #include files should be in this order */
|
||||
#include "curl_printf.h"
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#include "multiif.h"
|
||||
#include "cf-https-connect.h"
|
||||
#include "http2.h"
|
||||
#include "select.h"
|
||||
#include "vquic/vquic.h"
|
||||
|
||||
/* The last 3 #include files should be in this order */
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
#include "curl_trc.h"
|
||||
#include "multiif.h"
|
||||
#include "progress.h"
|
||||
#include "select.h"
|
||||
#include "vquic/vquic.h" /* for quic cfilters */
|
||||
|
||||
/* The last 3 #include files should be in this order */
|
||||
|
|
|
|||
139
lib/cfilters.c
139
lib/cfilters.c
|
|
@ -1099,142 +1099,3 @@ CURLcode Curl_conn_send(struct Curl_easy *data, int sockindex,
|
|||
*pnwritten = 0;
|
||||
return CURLE_FAILED_INIT;
|
||||
}
|
||||
|
||||
void Curl_pollset_reset(struct Curl_easy *data,
|
||||
struct easy_pollset *ps)
|
||||
{
|
||||
size_t i;
|
||||
(void)data;
|
||||
memset(ps, 0, sizeof(*ps));
|
||||
for(i = 0; i < MAX_SOCKSPEREASYHANDLE; i++)
|
||||
ps->sockets[i] = CURL_SOCKET_BAD;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void Curl_pollset_change(struct Curl_easy *data,
|
||||
struct easy_pollset *ps, curl_socket_t sock,
|
||||
int add_flags, int remove_flags)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
(void)data;
|
||||
DEBUGASSERT(VALID_SOCK(sock));
|
||||
if(!VALID_SOCK(sock))
|
||||
return;
|
||||
|
||||
DEBUGASSERT(add_flags <= (CURL_POLL_IN|CURL_POLL_OUT));
|
||||
DEBUGASSERT(remove_flags <= (CURL_POLL_IN|CURL_POLL_OUT));
|
||||
DEBUGASSERT((add_flags&remove_flags) == 0); /* no overlap */
|
||||
for(i = 0; i < ps->num; ++i) {
|
||||
if(ps->sockets[i] == sock) {
|
||||
ps->actions[i] &= (unsigned char)(~remove_flags);
|
||||
ps->actions[i] |= (unsigned char)add_flags;
|
||||
/* all gone? remove socket */
|
||||
if(!ps->actions[i]) {
|
||||
if((i + 1) < ps->num) {
|
||||
memmove(&ps->sockets[i], &ps->sockets[i + 1],
|
||||
(ps->num - (i + 1)) * sizeof(ps->sockets[0]));
|
||||
memmove(&ps->actions[i], &ps->actions[i + 1],
|
||||
(ps->num - (i + 1)) * sizeof(ps->actions[0]));
|
||||
}
|
||||
--ps->num;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* not present */
|
||||
if(add_flags) {
|
||||
/* Having more SOCKETS per easy handle than what is defined
|
||||
* is a programming error. This indicates that we need
|
||||
* to raise this limit, making easy_pollset larger.
|
||||
* Since we use this in tight loops, we do not want to make
|
||||
* the pollset dynamic unnecessarily.
|
||||
* The current maximum in practise is HTTP/3 eyeballing where
|
||||
* we have up to 4 sockets involved in connection setup.
|
||||
*/
|
||||
DEBUGASSERT(i < MAX_SOCKSPEREASYHANDLE);
|
||||
if(i < MAX_SOCKSPEREASYHANDLE) {
|
||||
ps->sockets[i] = sock;
|
||||
ps->actions[i] = (unsigned char)add_flags;
|
||||
ps->num = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Curl_pollset_set(struct Curl_easy *data,
|
||||
struct easy_pollset *ps, curl_socket_t sock,
|
||||
bool do_in, bool do_out)
|
||||
{
|
||||
Curl_pollset_change(data, ps, sock,
|
||||
(do_in ? CURL_POLL_IN : 0)|
|
||||
(do_out ? CURL_POLL_OUT : 0),
|
||||
(!do_in ? CURL_POLL_IN : 0)|
|
||||
(!do_out ? CURL_POLL_OUT : 0));
|
||||
}
|
||||
|
||||
static void ps_add(struct Curl_easy *data, struct easy_pollset *ps,
|
||||
unsigned int bitmap, curl_socket_t *socks)
|
||||
{
|
||||
if(bitmap) {
|
||||
unsigned int i;
|
||||
for(i = 0; i < MAX_SOCKSPEREASYHANDLE; ++i) {
|
||||
if(!(bitmap & GETSOCK_MASK_RW(i)) || !VALID_SOCK((socks[i]))) {
|
||||
break;
|
||||
}
|
||||
if(bitmap & GETSOCK_READSOCK(i)) {
|
||||
if(bitmap & GETSOCK_WRITESOCK(i))
|
||||
Curl_pollset_add_inout(data, ps, socks[i]);
|
||||
else
|
||||
/* is READ, since we checked MASK_RW above */
|
||||
Curl_pollset_add_in(data, ps, socks[i]);
|
||||
}
|
||||
else
|
||||
Curl_pollset_add_out(data, ps, socks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Curl_pollset_add_socks(struct Curl_easy *data,
|
||||
struct easy_pollset *ps,
|
||||
unsigned int (*socks_cb)(struct Curl_easy *data,
|
||||
curl_socket_t *socks))
|
||||
{
|
||||
curl_socket_t socks[MAX_SOCKSPEREASYHANDLE];
|
||||
unsigned int bitmap;
|
||||
|
||||
bitmap = socks_cb(data, socks);
|
||||
ps_add(data, ps, bitmap, socks);
|
||||
}
|
||||
|
||||
void Curl_pollset_check(struct Curl_easy *data,
|
||||
struct easy_pollset *ps, curl_socket_t sock,
|
||||
bool *pwant_read, bool *pwant_write)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
(void)data;
|
||||
DEBUGASSERT(VALID_SOCK(sock));
|
||||
for(i = 0; i < ps->num; ++i) {
|
||||
if(ps->sockets[i] == sock) {
|
||||
*pwant_read = !!(ps->actions[i] & CURL_POLL_IN);
|
||||
*pwant_write = !!(ps->actions[i] & CURL_POLL_OUT);
|
||||
return;
|
||||
}
|
||||
}
|
||||
*pwant_read = *pwant_write = FALSE;
|
||||
}
|
||||
|
||||
bool Curl_pollset_want_read(struct Curl_easy *data,
|
||||
struct easy_pollset *ps,
|
||||
curl_socket_t sock)
|
||||
{
|
||||
unsigned int i;
|
||||
(void)data;
|
||||
for(i = 0; i < ps->num; ++i) {
|
||||
if((ps->sockets[i] == sock) && (ps->actions[i] & CURL_POLL_IN))
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -632,56 +632,6 @@ CURLcode Curl_conn_send(struct Curl_easy *data, int sockindex,
|
|||
size_t *pnwritten);
|
||||
|
||||
|
||||
void Curl_pollset_reset(struct Curl_easy *data,
|
||||
struct easy_pollset *ps);
|
||||
|
||||
/* Change the poll flags (CURL_POLL_IN/CURL_POLL_OUT) to the poll set for
|
||||
* socket `sock`. If the socket is not already part of the poll set, it
|
||||
* will be added.
|
||||
* If the socket is present and all poll flags are cleared, it will be removed.
|
||||
*/
|
||||
void Curl_pollset_change(struct Curl_easy *data,
|
||||
struct easy_pollset *ps, curl_socket_t sock,
|
||||
int add_flags, int remove_flags);
|
||||
|
||||
void Curl_pollset_set(struct Curl_easy *data,
|
||||
struct easy_pollset *ps, curl_socket_t sock,
|
||||
bool do_in, bool do_out);
|
||||
|
||||
#define Curl_pollset_add_in(data, ps, sock) \
|
||||
Curl_pollset_change((data), (ps), (sock), CURL_POLL_IN, 0)
|
||||
#define Curl_pollset_add_out(data, ps, sock) \
|
||||
Curl_pollset_change((data), (ps), (sock), CURL_POLL_OUT, 0)
|
||||
#define Curl_pollset_add_inout(data, ps, sock) \
|
||||
Curl_pollset_change((data), (ps), (sock), \
|
||||
CURL_POLL_IN|CURL_POLL_OUT, 0)
|
||||
#define Curl_pollset_set_in_only(data, ps, sock) \
|
||||
Curl_pollset_change((data), (ps), (sock), \
|
||||
CURL_POLL_IN, CURL_POLL_OUT)
|
||||
#define Curl_pollset_set_out_only(data, ps, sock) \
|
||||
Curl_pollset_change((data), (ps), (sock), \
|
||||
CURL_POLL_OUT, CURL_POLL_IN)
|
||||
|
||||
void Curl_pollset_add_socks(struct Curl_easy *data,
|
||||
struct easy_pollset *ps,
|
||||
unsigned int (*socks_cb)(struct Curl_easy *data,
|
||||
curl_socket_t *socks));
|
||||
|
||||
/**
|
||||
* Check if the pollset, as is, wants to read and/or write regarding
|
||||
* the given socket.
|
||||
*/
|
||||
void Curl_pollset_check(struct Curl_easy *data,
|
||||
struct easy_pollset *ps, curl_socket_t sock,
|
||||
bool *pwant_read, bool *pwant_write);
|
||||
|
||||
/**
|
||||
* Return TRUE if the pollset contains socket with CURL_POLL_IN.
|
||||
*/
|
||||
bool Curl_pollset_want_read(struct Curl_easy *data,
|
||||
struct easy_pollset *ps,
|
||||
curl_socket_t sock);
|
||||
|
||||
/**
|
||||
* Types and macros used to keep the current easy handle in filter calls,
|
||||
* allowing for nested invocations. See #10336.
|
||||
|
|
|
|||
139
lib/select.c
139
lib/select.c
|
|
@ -492,3 +492,142 @@ unsigned int Curl_waitfds_add_ps(struct Curl_waitfds *cwfds,
|
|||
}
|
||||
return need;
|
||||
}
|
||||
|
||||
void Curl_pollset_reset(struct Curl_easy *data,
|
||||
struct easy_pollset *ps)
|
||||
{
|
||||
size_t i;
|
||||
(void)data;
|
||||
memset(ps, 0, sizeof(*ps));
|
||||
for(i = 0; i < MAX_SOCKSPEREASYHANDLE; i++)
|
||||
ps->sockets[i] = CURL_SOCKET_BAD;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void Curl_pollset_change(struct Curl_easy *data,
|
||||
struct easy_pollset *ps, curl_socket_t sock,
|
||||
int add_flags, int remove_flags)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
(void)data;
|
||||
DEBUGASSERT(VALID_SOCK(sock));
|
||||
if(!VALID_SOCK(sock))
|
||||
return;
|
||||
|
||||
DEBUGASSERT(add_flags <= (CURL_POLL_IN|CURL_POLL_OUT));
|
||||
DEBUGASSERT(remove_flags <= (CURL_POLL_IN|CURL_POLL_OUT));
|
||||
DEBUGASSERT((add_flags&remove_flags) == 0); /* no overlap */
|
||||
for(i = 0; i < ps->num; ++i) {
|
||||
if(ps->sockets[i] == sock) {
|
||||
ps->actions[i] &= (unsigned char)(~remove_flags);
|
||||
ps->actions[i] |= (unsigned char)add_flags;
|
||||
/* all gone? remove socket */
|
||||
if(!ps->actions[i]) {
|
||||
if((i + 1) < ps->num) {
|
||||
memmove(&ps->sockets[i], &ps->sockets[i + 1],
|
||||
(ps->num - (i + 1)) * sizeof(ps->sockets[0]));
|
||||
memmove(&ps->actions[i], &ps->actions[i + 1],
|
||||
(ps->num - (i + 1)) * sizeof(ps->actions[0]));
|
||||
}
|
||||
--ps->num;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* not present */
|
||||
if(add_flags) {
|
||||
/* Having more SOCKETS per easy handle than what is defined
|
||||
* is a programming error. This indicates that we need
|
||||
* to raise this limit, making easy_pollset larger.
|
||||
* Since we use this in tight loops, we do not want to make
|
||||
* the pollset dynamic unnecessarily.
|
||||
* The current maximum in practise is HTTP/3 eyeballing where
|
||||
* we have up to 4 sockets involved in connection setup.
|
||||
*/
|
||||
DEBUGASSERT(i < MAX_SOCKSPEREASYHANDLE);
|
||||
if(i < MAX_SOCKSPEREASYHANDLE) {
|
||||
ps->sockets[i] = sock;
|
||||
ps->actions[i] = (unsigned char)add_flags;
|
||||
ps->num = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Curl_pollset_set(struct Curl_easy *data,
|
||||
struct easy_pollset *ps, curl_socket_t sock,
|
||||
bool do_in, bool do_out)
|
||||
{
|
||||
Curl_pollset_change(data, ps, sock,
|
||||
(do_in ? CURL_POLL_IN : 0)|
|
||||
(do_out ? CURL_POLL_OUT : 0),
|
||||
(!do_in ? CURL_POLL_IN : 0)|
|
||||
(!do_out ? CURL_POLL_OUT : 0));
|
||||
}
|
||||
|
||||
static void ps_add(struct Curl_easy *data, struct easy_pollset *ps,
|
||||
unsigned int bitmap, curl_socket_t *socks)
|
||||
{
|
||||
if(bitmap) {
|
||||
unsigned int i;
|
||||
for(i = 0; i < MAX_SOCKSPEREASYHANDLE; ++i) {
|
||||
if(!(bitmap & GETSOCK_MASK_RW(i)) || !VALID_SOCK((socks[i]))) {
|
||||
break;
|
||||
}
|
||||
if(bitmap & GETSOCK_READSOCK(i)) {
|
||||
if(bitmap & GETSOCK_WRITESOCK(i))
|
||||
Curl_pollset_add_inout(data, ps, socks[i]);
|
||||
else
|
||||
/* is READ, since we checked MASK_RW above */
|
||||
Curl_pollset_add_in(data, ps, socks[i]);
|
||||
}
|
||||
else
|
||||
Curl_pollset_add_out(data, ps, socks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Curl_pollset_add_socks(struct Curl_easy *data,
|
||||
struct easy_pollset *ps,
|
||||
unsigned int (*socks_cb)(struct Curl_easy *data,
|
||||
curl_socket_t *socks))
|
||||
{
|
||||
curl_socket_t socks[MAX_SOCKSPEREASYHANDLE];
|
||||
unsigned int bitmap;
|
||||
|
||||
bitmap = socks_cb(data, socks);
|
||||
ps_add(data, ps, bitmap, socks);
|
||||
}
|
||||
|
||||
void Curl_pollset_check(struct Curl_easy *data,
|
||||
struct easy_pollset *ps, curl_socket_t sock,
|
||||
bool *pwant_read, bool *pwant_write)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
(void)data;
|
||||
DEBUGASSERT(VALID_SOCK(sock));
|
||||
for(i = 0; i < ps->num; ++i) {
|
||||
if(ps->sockets[i] == sock) {
|
||||
*pwant_read = !!(ps->actions[i] & CURL_POLL_IN);
|
||||
*pwant_write = !!(ps->actions[i] & CURL_POLL_OUT);
|
||||
return;
|
||||
}
|
||||
}
|
||||
*pwant_read = *pwant_write = FALSE;
|
||||
}
|
||||
|
||||
bool Curl_pollset_want_read(struct Curl_easy *data,
|
||||
struct easy_pollset *ps,
|
||||
curl_socket_t sock)
|
||||
{
|
||||
unsigned int i;
|
||||
(void)data;
|
||||
for(i = 0; i < ps->num; ++i) {
|
||||
if((ps->sockets[i] == sock) && (ps->actions[i] & CURL_POLL_IN))
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
|
|
|||
79
lib/select.h
79
lib/select.h
|
|
@ -110,6 +110,85 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms);
|
|||
} while(0)
|
||||
#endif
|
||||
|
||||
|
||||
/* we support N sockets per easy handle. Set the corresponding bit to what
|
||||
action we should wait for */
|
||||
#define MAX_SOCKSPEREASYHANDLE 16
|
||||
|
||||
/* the write bits start at bit 16 for the *getsock() bitmap */
|
||||
#define GETSOCK_WRITEBITSTART 16
|
||||
|
||||
#define GETSOCK_BLANK 0U /* no bits set */
|
||||
|
||||
/* set the bit for the given sock number to make the bitmap for writable */
|
||||
#define GETSOCK_WRITESOCK(x) (1U << (GETSOCK_WRITEBITSTART + (x)))
|
||||
|
||||
/* set the bit for the given sock number to make the bitmap for readable */
|
||||
#define GETSOCK_READSOCK(x) (1U << (x))
|
||||
|
||||
/* mask for checking if read and/or write is set for index x */
|
||||
#define GETSOCK_MASK_RW(x) (GETSOCK_READSOCK(x)|GETSOCK_WRITESOCK(x))
|
||||
|
||||
|
||||
/* Polling requested by an easy handle.
|
||||
* `action` is CURL_POLL_IN, CURL_POLL_OUT or CURL_POLL_INOUT.
|
||||
*/
|
||||
struct easy_pollset {
|
||||
curl_socket_t sockets[MAX_SOCKSPEREASYHANDLE];
|
||||
unsigned int num;
|
||||
unsigned char actions[MAX_SOCKSPEREASYHANDLE];
|
||||
};
|
||||
|
||||
void Curl_pollset_reset(struct Curl_easy *data,
|
||||
struct easy_pollset *ps);
|
||||
|
||||
/* Change the poll flags (CURL_POLL_IN/CURL_POLL_OUT) to the poll set for
|
||||
* socket `sock`. If the socket is not already part of the poll set, it
|
||||
* will be added.
|
||||
* If the socket is present and all poll flags are cleared, it will be removed.
|
||||
*/
|
||||
void Curl_pollset_change(struct Curl_easy *data,
|
||||
struct easy_pollset *ps, curl_socket_t sock,
|
||||
int add_flags, int remove_flags);
|
||||
|
||||
void Curl_pollset_set(struct Curl_easy *data,
|
||||
struct easy_pollset *ps, curl_socket_t sock,
|
||||
bool do_in, bool do_out);
|
||||
|
||||
#define Curl_pollset_add_in(data, ps, sock) \
|
||||
Curl_pollset_change((data), (ps), (sock), CURL_POLL_IN, 0)
|
||||
#define Curl_pollset_add_out(data, ps, sock) \
|
||||
Curl_pollset_change((data), (ps), (sock), CURL_POLL_OUT, 0)
|
||||
#define Curl_pollset_add_inout(data, ps, sock) \
|
||||
Curl_pollset_change((data), (ps), (sock), \
|
||||
CURL_POLL_IN|CURL_POLL_OUT, 0)
|
||||
#define Curl_pollset_set_in_only(data, ps, sock) \
|
||||
Curl_pollset_change((data), (ps), (sock), \
|
||||
CURL_POLL_IN, CURL_POLL_OUT)
|
||||
#define Curl_pollset_set_out_only(data, ps, sock) \
|
||||
Curl_pollset_change((data), (ps), (sock), \
|
||||
CURL_POLL_OUT, CURL_POLL_IN)
|
||||
|
||||
void Curl_pollset_add_socks(struct Curl_easy *data,
|
||||
struct easy_pollset *ps,
|
||||
unsigned int (*socks_cb)(struct Curl_easy *data,
|
||||
curl_socket_t *socks));
|
||||
|
||||
/**
|
||||
* Check if the pollset, as is, wants to read and/or write regarding
|
||||
* the given socket.
|
||||
*/
|
||||
void Curl_pollset_check(struct Curl_easy *data,
|
||||
struct easy_pollset *ps, curl_socket_t sock,
|
||||
bool *pwant_read, bool *pwant_write);
|
||||
|
||||
/**
|
||||
* Return TRUE if the pollset contains socket with CURL_POLL_IN.
|
||||
*/
|
||||
bool Curl_pollset_want_read(struct Curl_easy *data,
|
||||
struct easy_pollset *ps,
|
||||
curl_socket_t sock);
|
||||
|
||||
struct curl_pollfds {
|
||||
struct pollfd *pfds;
|
||||
unsigned int n;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
#include "connect.h"
|
||||
#include "progress.h"
|
||||
#include "transfer.h"
|
||||
#include "select.h"
|
||||
#include "vtls/vtls.h"
|
||||
#include "curl_ntlm_core.h"
|
||||
#include "escape.h"
|
||||
|
|
|
|||
|
|
@ -471,34 +471,6 @@ struct hostname {
|
|||
#define FIRSTSOCKET 0
|
||||
#define SECONDARYSOCKET 1
|
||||
|
||||
/* we support N sockets per easy handle. Set the corresponding bit to what
|
||||
action we should wait for */
|
||||
#define MAX_SOCKSPEREASYHANDLE 16
|
||||
|
||||
/* the write bits start at bit 16 for the *getsock() bitmap */
|
||||
#define GETSOCK_WRITEBITSTART 16
|
||||
|
||||
#define GETSOCK_BLANK 0U /* no bits set */
|
||||
|
||||
/* set the bit for the given sock number to make the bitmap for writable */
|
||||
#define GETSOCK_WRITESOCK(x) (1U << (GETSOCK_WRITEBITSTART + (x)))
|
||||
|
||||
/* set the bit for the given sock number to make the bitmap for readable */
|
||||
#define GETSOCK_READSOCK(x) (1U << (x))
|
||||
|
||||
/* mask for checking if read and/or write is set for index x */
|
||||
#define GETSOCK_MASK_RW(x) (GETSOCK_READSOCK(x)|GETSOCK_WRITESOCK(x))
|
||||
|
||||
|
||||
/* Polling requested by an easy handle.
|
||||
* `action` is CURL_POLL_IN, CURL_POLL_OUT or CURL_POLL_INOUT.
|
||||
*/
|
||||
struct easy_pollset {
|
||||
curl_socket_t sockets[MAX_SOCKSPEREASYHANDLE];
|
||||
unsigned int num;
|
||||
unsigned char actions[MAX_SOCKSPEREASYHANDLE];
|
||||
};
|
||||
|
||||
/*
|
||||
* Specific protocol handler.
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue