NBD patches for 2024-04-25
- Avoid calling poll() within coroutine -----BEGIN PGP SIGNATURE----- iQEzBAABCAAdFiEEccLMIrHEYCkn0vOqp6FrSiUnQ2oFAmYqzkMACgkQp6FrSiUn Q2ol3wf9HbwiYkyHhqybb4ykEs75N8B2JPbOj6gYRSBn7rz90k1vElDCM2yQhlDN Ltuh8lTOaJb+Z4n2dKIF2m5hL2GTm/xtErIIpP7o6A+11mHW9ag/VLaAMdWJxmUr WEUIH6mVtuRcxTTCp01l/JAYpUxOoQs1fyQljONH5kg1MAZpTTD61/cuhrXlvPLU cVlrLfob90oYhydCq5o6ucW3GhaEYkaZzHIWFy7LphFySebMmnbnPhYf/JD6RZPL s5K7njMK1DOyguCLlOzSuRM4gIbYunnr0Ofr/orTlAUZvbhRGKUlH0RTMWVMzgek xArnEZYlsqF2wIvrz0GwMDL7BMmG7A== =vXJj -----END PGP SIGNATURE----- Merge tag 'pull-nbd-2024-04-25' of https://repo.or.cz/qemu/ericb into staging NBD patches for 2024-04-25 - Avoid calling poll() within coroutine # -----BEGIN PGP SIGNATURE----- # # iQEzBAABCAAdFiEEccLMIrHEYCkn0vOqp6FrSiUnQ2oFAmYqzkMACgkQp6FrSiUn # Q2ol3wf9HbwiYkyHhqybb4ykEs75N8B2JPbOj6gYRSBn7rz90k1vElDCM2yQhlDN # Ltuh8lTOaJb+Z4n2dKIF2m5hL2GTm/xtErIIpP7o6A+11mHW9ag/VLaAMdWJxmUr # WEUIH6mVtuRcxTTCp01l/JAYpUxOoQs1fyQljONH5kg1MAZpTTD61/cuhrXlvPLU # cVlrLfob90oYhydCq5o6ucW3GhaEYkaZzHIWFy7LphFySebMmnbnPhYf/JD6RZPL # s5K7njMK1DOyguCLlOzSuRM4gIbYunnr0Ofr/orTlAUZvbhRGKUlH0RTMWVMzgek # xArnEZYlsqF2wIvrz0GwMDL7BMmG7A== # =vXJj # -----END PGP SIGNATURE----- # gpg: Signature made Thu 25 Apr 2024 02:42:27 PM PDT # gpg: using RSA key 71C2CC22B1C4602927D2F3AAA7A16B4A2527436A # gpg: Good signature from "Eric Blake <eblake@redhat.com>" [full] # gpg: aka "Eric Blake (Free Software Programmer) <ebb9@byu.net>" [full] # gpg: aka "[jpeg image of size 6874]" [full] * tag 'pull-nbd-2024-04-25' of https://repo.or.cz/qemu/ericb: nbd/server: Mark negotiation functions as coroutine_fn nbd/server: do not poll within a coroutine context Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
commit
77bcaf5f22
28
nbd/client.c
28
nbd/client.c
@ -596,13 +596,31 @@ static int nbd_request_simple_option(QIOChannel *ioc, int opt, bool strict,
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Callback to learn when QIO TLS upgrade is complete */
|
||||
struct NBDTLSClientHandshakeData {
|
||||
bool complete;
|
||||
Error *error;
|
||||
GMainLoop *loop;
|
||||
};
|
||||
|
||||
static void nbd_client_tls_handshake(QIOTask *task, void *opaque)
|
||||
{
|
||||
struct NBDTLSClientHandshakeData *data = opaque;
|
||||
|
||||
qio_task_propagate_error(task, &data->error);
|
||||
data->complete = true;
|
||||
if (data->loop) {
|
||||
g_main_loop_quit(data->loop);
|
||||
}
|
||||
}
|
||||
|
||||
static QIOChannel *nbd_receive_starttls(QIOChannel *ioc,
|
||||
QCryptoTLSCreds *tlscreds,
|
||||
const char *hostname, Error **errp)
|
||||
{
|
||||
int ret;
|
||||
QIOChannelTLS *tioc;
|
||||
struct NBDTLSHandshakeData data = { 0 };
|
||||
struct NBDTLSClientHandshakeData data = { 0 };
|
||||
|
||||
ret = nbd_request_simple_option(ioc, NBD_OPT_STARTTLS, true, errp);
|
||||
if (ret <= 0) {
|
||||
@ -619,18 +637,20 @@ static QIOChannel *nbd_receive_starttls(QIOChannel *ioc,
|
||||
return NULL;
|
||||
}
|
||||
qio_channel_set_name(QIO_CHANNEL(tioc), "nbd-client-tls");
|
||||
data.loop = g_main_loop_new(g_main_context_default(), FALSE);
|
||||
trace_nbd_receive_starttls_tls_handshake();
|
||||
qio_channel_tls_handshake(tioc,
|
||||
nbd_tls_handshake,
|
||||
nbd_client_tls_handshake,
|
||||
&data,
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
if (!data.complete) {
|
||||
data.loop = g_main_loop_new(g_main_context_default(), FALSE);
|
||||
g_main_loop_run(data.loop);
|
||||
assert(data.complete);
|
||||
g_main_loop_unref(data.loop);
|
||||
}
|
||||
g_main_loop_unref(data.loop);
|
||||
|
||||
if (data.error) {
|
||||
error_propagate(errp, data.error);
|
||||
object_unref(OBJECT(tioc));
|
||||
|
11
nbd/common.c
11
nbd/common.c
@ -47,17 +47,6 @@ int nbd_drop(QIOChannel *ioc, size_t size, Error **errp)
|
||||
}
|
||||
|
||||
|
||||
void nbd_tls_handshake(QIOTask *task,
|
||||
void *opaque)
|
||||
{
|
||||
struct NBDTLSHandshakeData *data = opaque;
|
||||
|
||||
qio_task_propagate_error(task, &data->error);
|
||||
data->complete = true;
|
||||
g_main_loop_quit(data->loop);
|
||||
}
|
||||
|
||||
|
||||
const char *nbd_opt_lookup(uint32_t opt)
|
||||
{
|
||||
switch (opt) {
|
||||
|
@ -72,16 +72,6 @@ static inline int nbd_write(QIOChannel *ioc, const void *buffer, size_t size,
|
||||
return qio_channel_write_all(ioc, buffer, size, errp) < 0 ? -EIO : 0;
|
||||
}
|
||||
|
||||
struct NBDTLSHandshakeData {
|
||||
GMainLoop *loop;
|
||||
bool complete;
|
||||
Error *error;
|
||||
};
|
||||
|
||||
|
||||
void nbd_tls_handshake(QIOTask *task,
|
||||
void *opaque);
|
||||
|
||||
int nbd_drop(QIOChannel *ioc, size_t size, Error **errp);
|
||||
|
||||
#endif
|
||||
|
128
nbd/server.c
128
nbd/server.c
@ -195,8 +195,9 @@ static inline void set_be_option_rep(NBDOptionReply *rep, uint32_t option,
|
||||
|
||||
/* Send a reply header, including length, but no payload.
|
||||
* Return -errno on error, 0 on success. */
|
||||
static int nbd_negotiate_send_rep_len(NBDClient *client, uint32_t type,
|
||||
uint32_t len, Error **errp)
|
||||
static coroutine_fn int
|
||||
nbd_negotiate_send_rep_len(NBDClient *client, uint32_t type,
|
||||
uint32_t len, Error **errp)
|
||||
{
|
||||
NBDOptionReply rep;
|
||||
|
||||
@ -211,15 +212,15 @@ static int nbd_negotiate_send_rep_len(NBDClient *client, uint32_t type,
|
||||
|
||||
/* Send a reply header with default 0 length.
|
||||
* Return -errno on error, 0 on success. */
|
||||
static int nbd_negotiate_send_rep(NBDClient *client, uint32_t type,
|
||||
Error **errp)
|
||||
static coroutine_fn int
|
||||
nbd_negotiate_send_rep(NBDClient *client, uint32_t type, Error **errp)
|
||||
{
|
||||
return nbd_negotiate_send_rep_len(client, type, 0, errp);
|
||||
}
|
||||
|
||||
/* Send an error reply.
|
||||
* Return -errno on error, 0 on success. */
|
||||
static int G_GNUC_PRINTF(4, 0)
|
||||
static coroutine_fn int G_GNUC_PRINTF(4, 0)
|
||||
nbd_negotiate_send_rep_verr(NBDClient *client, uint32_t type,
|
||||
Error **errp, const char *fmt, va_list va)
|
||||
{
|
||||
@ -259,7 +260,7 @@ nbd_sanitize_name(const char *name)
|
||||
|
||||
/* Send an error reply.
|
||||
* Return -errno on error, 0 on success. */
|
||||
static int G_GNUC_PRINTF(4, 5)
|
||||
static coroutine_fn int G_GNUC_PRINTF(4, 5)
|
||||
nbd_negotiate_send_rep_err(NBDClient *client, uint32_t type,
|
||||
Error **errp, const char *fmt, ...)
|
||||
{
|
||||
@ -275,7 +276,7 @@ nbd_negotiate_send_rep_err(NBDClient *client, uint32_t type,
|
||||
/* Drop remainder of the current option, and send a reply with the
|
||||
* given error type and message. Return -errno on read or write
|
||||
* failure; or 0 if connection is still live. */
|
||||
static int G_GNUC_PRINTF(4, 0)
|
||||
static coroutine_fn int G_GNUC_PRINTF(4, 0)
|
||||
nbd_opt_vdrop(NBDClient *client, uint32_t type, Error **errp,
|
||||
const char *fmt, va_list va)
|
||||
{
|
||||
@ -288,7 +289,7 @@ nbd_opt_vdrop(NBDClient *client, uint32_t type, Error **errp,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int G_GNUC_PRINTF(4, 5)
|
||||
static coroutine_fn int G_GNUC_PRINTF(4, 5)
|
||||
nbd_opt_drop(NBDClient *client, uint32_t type, Error **errp,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
@ -302,7 +303,7 @@ nbd_opt_drop(NBDClient *client, uint32_t type, Error **errp,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int G_GNUC_PRINTF(3, 4)
|
||||
static coroutine_fn int G_GNUC_PRINTF(3, 4)
|
||||
nbd_opt_invalid(NBDClient *client, Error **errp, const char *fmt, ...)
|
||||
{
|
||||
int ret;
|
||||
@ -319,8 +320,9 @@ nbd_opt_invalid(NBDClient *client, Error **errp, const char *fmt, ...)
|
||||
* If @check_nul, require that no NUL bytes appear in buffer.
|
||||
* Return -errno on I/O error, 0 if option was completely handled by
|
||||
* sending a reply about inconsistent lengths, or 1 on success. */
|
||||
static int nbd_opt_read(NBDClient *client, void *buffer, size_t size,
|
||||
bool check_nul, Error **errp)
|
||||
static coroutine_fn int
|
||||
nbd_opt_read(NBDClient *client, void *buffer, size_t size,
|
||||
bool check_nul, Error **errp)
|
||||
{
|
||||
if (size > client->optlen) {
|
||||
return nbd_opt_invalid(client, errp,
|
||||
@ -343,7 +345,8 @@ static int nbd_opt_read(NBDClient *client, void *buffer, size_t size,
|
||||
/* Drop size bytes from the unparsed payload of the current option.
|
||||
* Return -errno on I/O error, 0 if option was completely handled by
|
||||
* sending a reply about inconsistent lengths, or 1 on success. */
|
||||
static int nbd_opt_skip(NBDClient *client, size_t size, Error **errp)
|
||||
static coroutine_fn int
|
||||
nbd_opt_skip(NBDClient *client, size_t size, Error **errp)
|
||||
{
|
||||
if (size > client->optlen) {
|
||||
return nbd_opt_invalid(client, errp,
|
||||
@ -366,8 +369,9 @@ static int nbd_opt_skip(NBDClient *client, size_t size, Error **errp)
|
||||
* Return -errno on I/O error, 0 if option was completely handled by
|
||||
* sending a reply about inconsistent lengths, or 1 on success.
|
||||
*/
|
||||
static int nbd_opt_read_name(NBDClient *client, char **name, uint32_t *length,
|
||||
Error **errp)
|
||||
static coroutine_fn int
|
||||
nbd_opt_read_name(NBDClient *client, char **name, uint32_t *length,
|
||||
Error **errp)
|
||||
{
|
||||
int ret;
|
||||
uint32_t len;
|
||||
@ -402,8 +406,8 @@ static int nbd_opt_read_name(NBDClient *client, char **name, uint32_t *length,
|
||||
|
||||
/* Send a single NBD_REP_SERVER reply to NBD_OPT_LIST, including payload.
|
||||
* Return -errno on error, 0 on success. */
|
||||
static int nbd_negotiate_send_rep_list(NBDClient *client, NBDExport *exp,
|
||||
Error **errp)
|
||||
static coroutine_fn int
|
||||
nbd_negotiate_send_rep_list(NBDClient *client, NBDExport *exp, Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
size_t name_len, desc_len;
|
||||
@ -444,7 +448,8 @@ static int nbd_negotiate_send_rep_list(NBDClient *client, NBDExport *exp,
|
||||
|
||||
/* Process the NBD_OPT_LIST command, with a potential series of replies.
|
||||
* Return -errno on error, 0 on success. */
|
||||
static int nbd_negotiate_handle_list(NBDClient *client, Error **errp)
|
||||
static coroutine_fn int
|
||||
nbd_negotiate_handle_list(NBDClient *client, Error **errp)
|
||||
{
|
||||
NBDExport *exp;
|
||||
assert(client->opt == NBD_OPT_LIST);
|
||||
@ -459,7 +464,8 @@ static int nbd_negotiate_handle_list(NBDClient *client, Error **errp)
|
||||
return nbd_negotiate_send_rep(client, NBD_REP_ACK, errp);
|
||||
}
|
||||
|
||||
static void nbd_check_meta_export(NBDClient *client, NBDExport *exp)
|
||||
static coroutine_fn void
|
||||
nbd_check_meta_export(NBDClient *client, NBDExport *exp)
|
||||
{
|
||||
if (exp != client->contexts.exp) {
|
||||
client->contexts.count = 0;
|
||||
@ -468,8 +474,9 @@ static void nbd_check_meta_export(NBDClient *client, NBDExport *exp)
|
||||
|
||||
/* Send a reply to NBD_OPT_EXPORT_NAME.
|
||||
* Return -errno on error, 0 on success. */
|
||||
static int nbd_negotiate_handle_export_name(NBDClient *client, bool no_zeroes,
|
||||
Error **errp)
|
||||
static coroutine_fn int
|
||||
nbd_negotiate_handle_export_name(NBDClient *client, bool no_zeroes,
|
||||
Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
g_autofree char *name = NULL;
|
||||
@ -536,9 +543,9 @@ static int nbd_negotiate_handle_export_name(NBDClient *client, bool no_zeroes,
|
||||
/* Send a single NBD_REP_INFO, with a buffer @buf of @length bytes.
|
||||
* The buffer does NOT include the info type prefix.
|
||||
* Return -errno on error, 0 if ready to send more. */
|
||||
static int nbd_negotiate_send_info(NBDClient *client,
|
||||
uint16_t info, uint32_t length, void *buf,
|
||||
Error **errp)
|
||||
static coroutine_fn int
|
||||
nbd_negotiate_send_info(NBDClient *client, uint16_t info, uint32_t length,
|
||||
void *buf, Error **errp)
|
||||
{
|
||||
int rc;
|
||||
|
||||
@ -565,7 +572,8 @@ static int nbd_negotiate_send_info(NBDClient *client,
|
||||
* -errno transmission error occurred or @fatal was requested, errp is set
|
||||
* 0 error message successfully sent to client, errp is not set
|
||||
*/
|
||||
static int nbd_reject_length(NBDClient *client, bool fatal, Error **errp)
|
||||
static coroutine_fn int
|
||||
nbd_reject_length(NBDClient *client, bool fatal, Error **errp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -583,7 +591,8 @@ static int nbd_reject_length(NBDClient *client, bool fatal, Error **errp)
|
||||
/* Handle NBD_OPT_INFO and NBD_OPT_GO.
|
||||
* Return -errno on error, 0 if ready for next option, and 1 to move
|
||||
* into transmission phase. */
|
||||
static int nbd_negotiate_handle_info(NBDClient *client, Error **errp)
|
||||
static coroutine_fn int
|
||||
nbd_negotiate_handle_info(NBDClient *client, Error **errp)
|
||||
{
|
||||
int rc;
|
||||
g_autofree char *name = NULL;
|
||||
@ -748,15 +757,33 @@ static int nbd_negotiate_handle_info(NBDClient *client, Error **errp)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Callback to learn when QIO TLS upgrade is complete */
|
||||
struct NBDTLSServerHandshakeData {
|
||||
bool complete;
|
||||
Error *error;
|
||||
Coroutine *co;
|
||||
};
|
||||
|
||||
static void
|
||||
nbd_server_tls_handshake(QIOTask *task, void *opaque)
|
||||
{
|
||||
struct NBDTLSServerHandshakeData *data = opaque;
|
||||
|
||||
qio_task_propagate_error(task, &data->error);
|
||||
data->complete = true;
|
||||
if (!qemu_coroutine_entered(data->co)) {
|
||||
aio_co_wake(data->co);
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle NBD_OPT_STARTTLS. Return NULL to drop connection, or else the
|
||||
* new channel for all further (now-encrypted) communication. */
|
||||
static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client,
|
||||
Error **errp)
|
||||
static coroutine_fn QIOChannel *
|
||||
nbd_negotiate_handle_starttls(NBDClient *client, Error **errp)
|
||||
{
|
||||
QIOChannel *ioc;
|
||||
QIOChannelTLS *tioc;
|
||||
struct NBDTLSHandshakeData data = { 0 };
|
||||
struct NBDTLSServerHandshakeData data = { 0 };
|
||||
|
||||
assert(client->opt == NBD_OPT_STARTTLS);
|
||||
|
||||
@ -777,17 +804,18 @@ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client,
|
||||
|
||||
qio_channel_set_name(QIO_CHANNEL(tioc), "nbd-server-tls");
|
||||
trace_nbd_negotiate_handle_starttls_handshake();
|
||||
data.loop = g_main_loop_new(g_main_context_default(), FALSE);
|
||||
data.co = qemu_coroutine_self();
|
||||
qio_channel_tls_handshake(tioc,
|
||||
nbd_tls_handshake,
|
||||
nbd_server_tls_handshake,
|
||||
&data,
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
if (!data.complete) {
|
||||
g_main_loop_run(data.loop);
|
||||
qemu_coroutine_yield();
|
||||
assert(data.complete);
|
||||
}
|
||||
g_main_loop_unref(data.loop);
|
||||
|
||||
if (data.error) {
|
||||
object_unref(OBJECT(tioc));
|
||||
error_propagate(errp, data.error);
|
||||
@ -803,10 +831,9 @@ static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client,
|
||||
*
|
||||
* For NBD_OPT_LIST_META_CONTEXT @context_id is ignored, 0 is used instead.
|
||||
*/
|
||||
static int nbd_negotiate_send_meta_context(NBDClient *client,
|
||||
const char *context,
|
||||
uint32_t context_id,
|
||||
Error **errp)
|
||||
static coroutine_fn int
|
||||
nbd_negotiate_send_meta_context(NBDClient *client, const char *context,
|
||||
uint32_t context_id, Error **errp)
|
||||
{
|
||||
NBDOptionReplyMetaContext opt;
|
||||
struct iovec iov[] = {
|
||||
@ -831,8 +858,9 @@ static int nbd_negotiate_send_meta_context(NBDClient *client,
|
||||
* Return true if @query matches @pattern, or if @query is empty when
|
||||
* the @client is performing _LIST_.
|
||||
*/
|
||||
static bool nbd_meta_empty_or_pattern(NBDClient *client, const char *pattern,
|
||||
const char *query)
|
||||
static coroutine_fn bool
|
||||
nbd_meta_empty_or_pattern(NBDClient *client, const char *pattern,
|
||||
const char *query)
|
||||
{
|
||||
if (!*query) {
|
||||
trace_nbd_negotiate_meta_query_parse("empty");
|
||||
@ -849,7 +877,8 @@ static bool nbd_meta_empty_or_pattern(NBDClient *client, const char *pattern,
|
||||
/*
|
||||
* Return true and adjust @str in place if it begins with @prefix.
|
||||
*/
|
||||
static bool nbd_strshift(const char **str, const char *prefix)
|
||||
static coroutine_fn bool
|
||||
nbd_strshift(const char **str, const char *prefix)
|
||||
{
|
||||
size_t len = strlen(prefix);
|
||||
|
||||
@ -865,8 +894,9 @@ static bool nbd_strshift(const char **str, const char *prefix)
|
||||
* Handle queries to 'base' namespace. For now, only the base:allocation
|
||||
* context is available. Return true if @query has been handled.
|
||||
*/
|
||||
static bool nbd_meta_base_query(NBDClient *client, NBDMetaContexts *meta,
|
||||
const char *query)
|
||||
static coroutine_fn bool
|
||||
nbd_meta_base_query(NBDClient *client, NBDMetaContexts *meta,
|
||||
const char *query)
|
||||
{
|
||||
if (!nbd_strshift(&query, "base:")) {
|
||||
return false;
|
||||
@ -885,8 +915,9 @@ static bool nbd_meta_base_query(NBDClient *client, NBDMetaContexts *meta,
|
||||
* and qemu:allocation-depth contexts are available. Return true if @query
|
||||
* has been handled.
|
||||
*/
|
||||
static bool nbd_meta_qemu_query(NBDClient *client, NBDMetaContexts *meta,
|
||||
const char *query)
|
||||
static coroutine_fn bool
|
||||
nbd_meta_qemu_query(NBDClient *client, NBDMetaContexts *meta,
|
||||
const char *query)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
@ -950,8 +981,9 @@ static bool nbd_meta_qemu_query(NBDClient *client, NBDMetaContexts *meta,
|
||||
*
|
||||
* Return -errno on I/O error, 0 if option was completely handled by
|
||||
* sending a reply about inconsistent lengths, or 1 on success. */
|
||||
static int nbd_negotiate_meta_query(NBDClient *client,
|
||||
NBDMetaContexts *meta, Error **errp)
|
||||
static coroutine_fn int
|
||||
nbd_negotiate_meta_query(NBDClient *client,
|
||||
NBDMetaContexts *meta, Error **errp)
|
||||
{
|
||||
int ret;
|
||||
g_autofree char *query = NULL;
|
||||
@ -990,7 +1022,8 @@ static int nbd_negotiate_meta_query(NBDClient *client,
|
||||
* Handle NBD_OPT_LIST_META_CONTEXT and NBD_OPT_SET_META_CONTEXT
|
||||
*
|
||||
* Return -errno on I/O error, or 0 if option was completely handled. */
|
||||
static int nbd_negotiate_meta_queries(NBDClient *client, Error **errp)
|
||||
static coroutine_fn int
|
||||
nbd_negotiate_meta_queries(NBDClient *client, Error **errp)
|
||||
{
|
||||
int ret;
|
||||
g_autofree char *export_name = NULL;
|
||||
@ -1118,7 +1151,8 @@ static int nbd_negotiate_meta_queries(NBDClient *client, Error **errp)
|
||||
* 1 if client sent NBD_OPT_ABORT, i.e. on valid disconnect,
|
||||
* errp is not set
|
||||
*/
|
||||
static int nbd_negotiate_options(NBDClient *client, Error **errp)
|
||||
static coroutine_fn int
|
||||
nbd_negotiate_options(NBDClient *client, Error **errp)
|
||||
{
|
||||
uint32_t flags;
|
||||
bool fixedNewstyle = false;
|
||||
|
Loading…
x
Reference in New Issue
Block a user