mirror of
https://github.com/fpagliughi/sockpp.git
synced 2026-01-12 00:04:45 +08:00
More consistent use of error_code. socket::last_error() now returns a std::error_code, while socket::last_errno() returns platform-specific error.
This commit is contained in:
@@ -70,7 +70,7 @@ int main(int argc, char* argv[])
|
||||
cout << "Created a connection from " << conn.address() << endl;
|
||||
|
||||
// Set a timeout for the responses
|
||||
if (!conn.read_timeout(seconds(5))) {
|
||||
if (!conn.read_timeout(5s)) {
|
||||
cerr << "Error setting timeout on TCP stream: "
|
||||
<< conn.last_error_str() << endl;
|
||||
}
|
||||
|
||||
@@ -55,9 +55,9 @@ int main(int argc, char* argv[])
|
||||
sockpp::initialize();
|
||||
|
||||
// Implicitly creates an inet_address from {host,port}
|
||||
// and then tries the connection.
|
||||
// and then tries the connection using a timeout of 5sec.
|
||||
|
||||
sockpp::tcp_connector conn({host, port}, seconds{5});
|
||||
sockpp::tcp_connector conn({host, port}, 5s);
|
||||
if (!conn) {
|
||||
cerr << "Error connecting to server at "
|
||||
<< sockpp::inet_address(host, port)
|
||||
@@ -68,7 +68,7 @@ int main(int argc, char* argv[])
|
||||
cout << "Created a connection from " << conn.address() << endl;
|
||||
|
||||
// Set a timeout for the responses
|
||||
if (!conn.read_timeout(seconds(5))) {
|
||||
if (!conn.read_timeout(5s)) {
|
||||
cerr << "Error setting timeout on TCP stream: "
|
||||
<< conn.last_error_str() << endl;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ int main(int argc, char* argv[])
|
||||
string s, sret;
|
||||
while (getline(cin, s) && !s.empty()) {
|
||||
if (conn.write(s) != (int) s.length()) {
|
||||
if (conn.last_error() == EPIPE) {
|
||||
if (conn.last_error() == errc::broken_pipe) {
|
||||
cerr << "It appears that the socket was closed." << endl;
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -80,7 +80,7 @@ class result {
|
||||
* This should be called after a failed system call to get the cause of
|
||||
* the error.
|
||||
*/
|
||||
static int get_last_error() {
|
||||
static int get_last_errno() {
|
||||
#if defined(_WIN32)
|
||||
return ::WSAGetLastError();
|
||||
#else
|
||||
@@ -89,6 +89,16 @@ class result {
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the last error from an operation.
|
||||
* This should be called after a failed system call to get the cause of
|
||||
* the error.
|
||||
*/
|
||||
static std::error_code get_last_error() {
|
||||
int ec = get_last_errno();
|
||||
return error_code{ ec, std::system_category() };
|
||||
}
|
||||
|
||||
friend class socket;
|
||||
|
||||
public:
|
||||
@@ -148,6 +158,16 @@ public:
|
||||
static result from_error(errc err) {
|
||||
return result(err);
|
||||
}
|
||||
/**
|
||||
* Creates an unsuccessful result from an platform-specific integer
|
||||
* error code and an optional category.
|
||||
* @param ec The platform-specific error code.
|
||||
* @param ecat The error category.
|
||||
* @return The result of an unsuccessful operation.
|
||||
*/
|
||||
static result from_last_error() {
|
||||
return from_error(get_last_errno());
|
||||
}
|
||||
/**
|
||||
* Determines if the result represents a failed operation.
|
||||
*
|
||||
|
||||
@@ -206,15 +206,6 @@ protected:
|
||||
close(release());
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* OS-specific means to retrieve the last error from an operation.
|
||||
* This should be called after a failed system call to set the
|
||||
* lastErr_ member variable. Normally this would be called from
|
||||
* @ref check_ret.
|
||||
*/
|
||||
static int get_last_error() {
|
||||
return ioresult::get_last_error();
|
||||
}
|
||||
/**
|
||||
* Cache the last system error code into this object.
|
||||
* This should be called after a failed system call to store the error
|
||||
@@ -222,7 +213,7 @@ protected:
|
||||
* @return The error value set.
|
||||
*/
|
||||
int set_last_error() const {
|
||||
return lastErr_ = get_last_error();
|
||||
return lastErr_ = ioresult::get_last_errno();
|
||||
}
|
||||
/**
|
||||
* Checks the value and if less than zero, sets last error.
|
||||
@@ -232,7 +223,7 @@ protected:
|
||||
*/
|
||||
template <typename T>
|
||||
T check_ret(T ret) const {
|
||||
lastErr_ = (ret < 0) ? get_last_error() : 0;
|
||||
lastErr_ = (ret < 0) ? ioresult::get_last_errno() : 0;
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
@@ -244,7 +235,7 @@ protected:
|
||||
*/
|
||||
template <typename T>
|
||||
bool check_ret_bool(T ret) const {
|
||||
lastErr_ = (ret < 0) ? get_last_error() : 0;
|
||||
lastErr_ = (ret < 0) ? ioresult::get_last_errno() : 0;
|
||||
return ret >= 0;
|
||||
}
|
||||
/**
|
||||
@@ -271,7 +262,7 @@ protected:
|
||||
* @return Returns the value sent to it, `ret`.
|
||||
*/
|
||||
socket_t check_socket(socket_t ret) const {
|
||||
lastErr_ = (ret == INVALID_SOCKET) ? get_last_error() : 0;
|
||||
lastErr_ = (ret == INVALID_SOCKET) ? ioresult::get_last_errno() : 0;
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
@@ -283,8 +274,8 @@ protected:
|
||||
* @return @em true if the value is a valid socket (not INVALID_SOCKET)
|
||||
* or @em false is is an error (INVALID_SOCKET)
|
||||
*/
|
||||
bool check_socket_bool(socket_t ret) const{
|
||||
lastErr_ = (ret == INVALID_SOCKET) ? get_last_error() : 0;
|
||||
bool check_socket_bool(socket_t ret) const {
|
||||
lastErr_ = (ret == INVALID_SOCKET) ? ioresult::get_last_errno() : 0;
|
||||
return ret != INVALID_SOCKET;
|
||||
}
|
||||
/**
|
||||
@@ -579,7 +570,17 @@ public:
|
||||
* This is typically the code from the underlying OS operation.
|
||||
* @return The code for the last errror.
|
||||
*/
|
||||
int last_error() const { return lastErr_; }
|
||||
std::error_code last_error() const {
|
||||
return std::error_code{ lastErr_, std::system_category() };
|
||||
}
|
||||
/**
|
||||
* Gets the platform-specific errror from the last failed operation.
|
||||
* This is integer the code from the OS:
|
||||
* @li On *nix systems, this is the `errno` for the current thread.
|
||||
* @li On Windows, this is the value returned by `WSAGetLastError()`.
|
||||
* @return The platform-specific for the last errror.
|
||||
*/
|
||||
int last_errno() const { return lastErr_; }
|
||||
/**
|
||||
* Gets a string describing the last errror.
|
||||
* This is typically the returned message from the system strerror().
|
||||
|
||||
@@ -71,8 +71,8 @@ protected:
|
||||
* Creates a streaming socket.
|
||||
* @return An OS handle to a stream socket.
|
||||
*/
|
||||
static socket_t create_handle(int domain) {
|
||||
return (socket_t) ::socket(domain, COMM_TYPE, 0);
|
||||
static socket_t create_handle(int domain, int protocol=0) {
|
||||
return (socket_t) ::socket(domain, COMM_TYPE, protocol);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -47,7 +47,7 @@ acceptor acceptor::create(int domain)
|
||||
{
|
||||
acceptor acc(create_handle(domain));
|
||||
if (!acc)
|
||||
acc.clear(get_last_error());
|
||||
acc.set_last_error();
|
||||
return acc;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,17 +36,7 @@
|
||||
|
||||
#include "sockpp/connector.h"
|
||||
#include <cerrno>
|
||||
#if defined(_WIN32)
|
||||
// Winsock calls return non-POSIX error codes
|
||||
#undef EINPROGRESS
|
||||
#define EINPROGRESS WSAEINPROGRESS
|
||||
|
||||
#undef ETIMEDOUT
|
||||
#define ETIMEDOUT WSAETIMEDOUT
|
||||
|
||||
#undef EWOULDBLOCK
|
||||
#define EWOULDBLOCK WSAEWOULDBLOCK
|
||||
#else
|
||||
#if !defined(_WIN32)
|
||||
#include <sys/poll.h>
|
||||
#if defined(__APPLE__)
|
||||
#include <net/if.h>
|
||||
@@ -87,7 +77,7 @@ bool connector::connect(const sock_address& addr)
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool connector::connect(const sock_address& addr, std::chrono::microseconds timeout)
|
||||
bool connector::connect(const sock_address& addr, microseconds timeout)
|
||||
{
|
||||
if (timeout.count() <= 0)
|
||||
return connect(addr);
|
||||
@@ -105,10 +95,9 @@ bool connector::connect(const sock_address& addr, std::chrono::microseconds time
|
||||
if (!non_blocking)
|
||||
set_non_blocking(true);
|
||||
|
||||
// TODO: Reimplement with poll() for systems with lots of sockets.
|
||||
|
||||
if (!check_ret_bool(::connect(handle(), addr.sockaddr_ptr(), addr.size()))) {
|
||||
if (last_error() == EINPROGRESS || last_error() == EWOULDBLOCK) {
|
||||
auto err = last_error();
|
||||
if (err == errc::operation_in_progress || err == errc::operation_would_block) {
|
||||
// TODO: Windows has a WSAPoll() function we can use.
|
||||
#if defined(_WIN32)
|
||||
// Non-blocking connect -- call `select` to wait until the timeout:
|
||||
@@ -138,7 +127,7 @@ bool connector::connect(const sock_address& addr, std::chrono::microseconds time
|
||||
}
|
||||
}
|
||||
|
||||
if (last_error() != 0) {
|
||||
if (last_error()) {
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ socket socket::create(int domain, int type, int protocol /*=0*/)
|
||||
{
|
||||
socket sock(::socket(domain, type, protocol));
|
||||
if (!sock)
|
||||
sock.clear(get_last_error());
|
||||
sock.set_last_error();
|
||||
return sock;
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ socket socket::clone() const
|
||||
int socket::get_flags() const
|
||||
{
|
||||
int flags = ::fcntl(handle_, F_GETFL, 0);
|
||||
lastErr_ = (flags == -1) ? get_last_error() : 0;
|
||||
lastErr_ = (flags == -1) ? ioresult::get_last_errno() : 0;
|
||||
return flags;
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ std::tuple<socket, socket> socket::pair(int domain, int type, int protocol /*=0*
|
||||
sock1.reset(sv[1]);
|
||||
}
|
||||
else {
|
||||
int err = get_last_error();
|
||||
int err = ioresult::get_last_errno();
|
||||
sock0.clear(err);
|
||||
sock1.clear(err);
|
||||
}
|
||||
|
||||
@@ -49,9 +49,9 @@ namespace sockpp {
|
||||
|
||||
stream_socket stream_socket::create(int domain, int protocol /*=0*/)
|
||||
{
|
||||
stream_socket sock(::socket(domain, COMM_TYPE, protocol));
|
||||
stream_socket sock(create_handle(domain, protocol));
|
||||
if (!sock)
|
||||
sock.clear(get_last_error());
|
||||
sock.set_last_error();
|
||||
return sock;
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ ssize_t stream_socket::read_n(void *buf, size_t n)
|
||||
uint8_t *b = reinterpret_cast<uint8_t*>(buf);
|
||||
|
||||
while (nr < n) {
|
||||
if ((nx = read(b+nr, n-nr)) < 0 && last_error() == EINTR)
|
||||
if ((nx = read(b + nr, n - nr)) < 0 && last_error() == errc::interrupted)
|
||||
continue;
|
||||
|
||||
if (nx <= 0)
|
||||
@@ -201,7 +201,7 @@ ssize_t stream_socket::write_n(const void *buf, size_t n)
|
||||
const uint8_t *b = reinterpret_cast<const uint8_t*>(buf);
|
||||
|
||||
while (nw < n) {
|
||||
if ((nx = write(b+nw, n-nw)) < 0 && last_error() == EINTR)
|
||||
if ((nx = write(b + nw, n - nw)) < 0 && last_error() == errc::interrupted)
|
||||
continue;
|
||||
|
||||
if (nx <= 0)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// --------------------------------------------------------------------------
|
||||
// This file is part of the "sockpp" C++ socket library.
|
||||
//
|
||||
// Copyright (c) 2019 Frank Pagliughi
|
||||
// Copyright (c) 2019-2023 Frank Pagliughi
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
@@ -65,7 +65,7 @@ TEST_CASE("acceptor handle constructor", "[acceptor]") {
|
||||
REQUIRE(!sock);
|
||||
REQUIRE(!sock.is_open());
|
||||
// TODO: Should this set an error?
|
||||
REQUIRE(sock.last_error() == 0);
|
||||
REQUIRE(!sock.last_error());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ TEST_CASE("acceptor address constructor", "[acceptor]") {
|
||||
acceptor sock(ADDR);
|
||||
REQUIRE(sock);
|
||||
REQUIRE(sock.is_open());
|
||||
REQUIRE(sock.last_error() == 0);
|
||||
REQUIRE(!sock.last_error());
|
||||
REQUIRE(sock.address() == ADDR);
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ TEST_CASE("acceptor address constructor", "[acceptor]") {
|
||||
#if defined(_WIN32)
|
||||
REQUIRE(sock.last_error() == WSAEINVAL);
|
||||
#else
|
||||
REQUIRE(sock.last_error() == EAFNOSUPPORT);
|
||||
REQUIRE(sock.last_error() == errc::address_family_not_supported);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ TEST_CASE("acceptor create", "[acceptor]") {
|
||||
|
||||
REQUIRE(sock);
|
||||
REQUIRE(sock.is_open());
|
||||
REQUIRE(sock.last_error() == 0);
|
||||
REQUIRE(!sock.last_error());
|
||||
|
||||
// Windows returns unknown family for unbound socket
|
||||
// Windows returns a different error code than *nix
|
||||
@@ -123,7 +123,7 @@ TEST_CASE("acceptor create", "[acceptor]") {
|
||||
#if defined(_WIN32)
|
||||
REQUIRE(sock.last_error() == WSAEINVAL);
|
||||
#else
|
||||
REQUIRE(sock.last_error() == EAFNOSUPPORT);
|
||||
REQUIRE(sock.last_error() == errc::address_family_not_supported);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ TEST_CASE("connector unspecified address", "[connector]") {
|
||||
#if defined(_WIN32)
|
||||
REQUIRE(conn.last_error() == WSAENOTSOCK);
|
||||
#else
|
||||
REQUIRE(conn.last_error() == EAFNOSUPPORT);
|
||||
REQUIRE(conn.last_error() == errc::address_family_not_supported);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// --------------------------------------------------------------------------
|
||||
// This file is part of the "sockpp" C++ socket library.
|
||||
//
|
||||
// Copyright (c) 2019 Frank Pagliughi
|
||||
// Copyright (c) 2019-2023 Frank Pagliughi
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
@@ -65,7 +65,7 @@ TEST_CASE("datagram_socket handle constructor", "[datagram_socket]") {
|
||||
REQUIRE(!sock);
|
||||
REQUIRE(!sock.is_open());
|
||||
// TODO: Should this set an error?
|
||||
REQUIRE(sock.last_error() == 0);
|
||||
REQUIRE(!sock.last_error());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ TEST_CASE("datagram_socket address constructor", "[datagram_socket]") {
|
||||
datagram_socket sock(ADDR);
|
||||
REQUIRE(sock);
|
||||
REQUIRE(sock.is_open());
|
||||
REQUIRE(sock.last_error() == 0);
|
||||
REQUIRE(!sock.last_error());
|
||||
REQUIRE(sock.address() == ADDR);
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ TEST_CASE("datagram_socket address constructor", "[datagram_socket]") {
|
||||
#if defined(_WIN32)
|
||||
REQUIRE(sock.last_error() == WSAEINVAL);
|
||||
#else
|
||||
REQUIRE(sock.last_error() == EAFNOSUPPORT);
|
||||
REQUIRE(sock.last_error() == errc::address_family_not_supported);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// --------------------------------------------------------------------------
|
||||
// This file is part of the "sockpp" C++ socket library.
|
||||
//
|
||||
// Copyright (c) 2019 Frank Pagliughi
|
||||
// Copyright (c) 2019-2023 Frank Pagliughi
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
@@ -87,7 +87,7 @@ TEST_CASE("socket constructors", "[socket]") {
|
||||
REQUIRE(!sock);
|
||||
REQUIRE(!sock.is_open());
|
||||
REQUIRE(sock.handle() == INVALID_SOCKET);
|
||||
REQUIRE(sock.last_error() == 0);
|
||||
REQUIRE(!sock.last_error());
|
||||
}
|
||||
|
||||
SECTION("handle constructor") {
|
||||
@@ -97,7 +97,7 @@ TEST_CASE("socket constructors", "[socket]") {
|
||||
REQUIRE(sock);
|
||||
REQUIRE(sock.is_open());
|
||||
REQUIRE(sock.handle() == HANDLE);
|
||||
REQUIRE(sock.last_error() == 0);
|
||||
REQUIRE(!sock.last_error());
|
||||
}
|
||||
|
||||
SECTION("move constructor") {
|
||||
@@ -109,7 +109,7 @@ TEST_CASE("socket constructors", "[socket]") {
|
||||
// Make sure the new socket got the handle
|
||||
REQUIRE(sock);
|
||||
REQUIRE(sock.handle() == HANDLE);
|
||||
REQUIRE(sock.last_error() == 0);
|
||||
REQUIRE(!sock.last_error());
|
||||
|
||||
// Make sure the handle was moved out of the org_sock
|
||||
REQUIRE(!org_sock);
|
||||
@@ -131,19 +131,19 @@ TEST_CASE("socket errors", "[socket]") {
|
||||
REQUIRE(!ok);
|
||||
REQUIRE(!sock);
|
||||
|
||||
int err = sock.last_error();
|
||||
REQUIRE(err != 0);
|
||||
auto err = sock.last_error();
|
||||
REQUIRE(err);
|
||||
|
||||
// last_error() is sticky, unlike `errno`
|
||||
REQUIRE(sock.last_error() == err);
|
||||
|
||||
// We can clear the error
|
||||
sock.clear();
|
||||
REQUIRE(sock.last_error() == 0);
|
||||
REQUIRE(!sock.last_error());
|
||||
|
||||
// Test arbitrary clear value
|
||||
sock.clear(42);
|
||||
REQUIRE(sock.last_error() == 42);
|
||||
REQUIRE(sock.last_error().value() == 42);
|
||||
REQUIRE(!sock);
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ TEST_CASE("failed socket pair", "[socket]") {
|
||||
REQUIRE(!sock1);
|
||||
REQUIRE(!sock2);
|
||||
|
||||
REQUIRE(sock1.last_error() != 0);
|
||||
REQUIRE(sock1.last_error());
|
||||
REQUIRE(sock1.last_error() == sock2.last_error());
|
||||
}
|
||||
|
||||
@@ -311,7 +311,7 @@ TEST_CASE("thread-safe last error", "[socket]") {
|
||||
|
||||
std::thread thr([&] {
|
||||
// Test #1
|
||||
REQUIRE(sock.last_error() == 0);
|
||||
REQUIRE(!sock.last_error());
|
||||
{
|
||||
// Wait for Test #2
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
@@ -321,7 +321,7 @@ TEST_CASE("thread-safe last error", "[socket]") {
|
||||
}
|
||||
|
||||
// Test #3
|
||||
REQUIRE(sock.last_error() == 0);
|
||||
REQUIRE(!sock.last_error());
|
||||
});
|
||||
|
||||
{
|
||||
@@ -337,7 +337,7 @@ TEST_CASE("thread-safe last error", "[socket]") {
|
||||
bool ok = sock.get_option(SOL_SOCKET, SO_REUSEADDR, &reuse, &len);
|
||||
|
||||
REQUIRE(!ok);
|
||||
REQUIRE(sock.last_error() != 0);
|
||||
REQUIRE(sock.last_error());
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// --------------------------------------------------------------------------
|
||||
// This file is part of the "sockpp" C++ socket library.
|
||||
//
|
||||
// Copyright (c) 2019 Frank Pagliughi
|
||||
// Copyright (c) 2019-2023 Frank Pagliughi
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
@@ -65,7 +65,7 @@ TEST_CASE("stream_socket handle constructor", "[stream_socket]") {
|
||||
REQUIRE(!sock);
|
||||
REQUIRE(!sock.is_open());
|
||||
// TODO: Should this set an error?
|
||||
REQUIRE(sock.last_error() == 0);
|
||||
REQUIRE(!sock.last_error());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ TEST_CASE("stream_socket address constructor", "[stream_socket]") {
|
||||
stream_socket sock(ADDR);
|
||||
REQUIRE(sock);
|
||||
REQUIRE(sock.is_open());
|
||||
REQUIRE(sock.last_error() == 0);
|
||||
REQUIRE(!sock.last_error());
|
||||
REQUIRE(sock.address() == ADDR);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ TEST_CASE("stream_socket address constructor", "[stream_socket]") {
|
||||
stream_socket sock(ADDR);
|
||||
REQUIRE(!sock);
|
||||
REQUIRE(!sock.is_open());
|
||||
REQUIRE(sock.last_error() == EAFNOSUPPORT);
|
||||
REQUIRE(sock.last_error() == errc::address_family_not_supported);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -72,7 +72,7 @@ TEST_CASE("tcp_socket handle constructor", "[tcp_socket]") {
|
||||
REQUIRE(!sock);
|
||||
REQUIRE(!sock.is_open());
|
||||
// TODO: Should this set an error?
|
||||
REQUIRE(sock.last_error() == 0);
|
||||
REQUIRE(!sock.last_error());
|
||||
|
||||
//REQUIRE(sock.family() == AF_INET);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user