mirror of
https://git.proxmox.com/git/libgit2
synced 2025-08-05 12:52:58 +00:00
Add proper threading support to libgit2
We now depend on libpthread on all Unix platforms (should be installed by default) and use a simple wrapper for Windows threads under Win32. Signed-off-by: Vicent Marti <tanoku@gmail.com>
This commit is contained in:
parent
b5abb881a6
commit
bbcc7ffc69
10
README.md
10
README.md
@ -34,15 +34,17 @@ libgit2 is already very usable.
|
||||
Building libgit2 - External dependencies
|
||||
========================================
|
||||
|
||||
libgit2 builds cleanly on most platforms without any external dependencies. However, the following libraries
|
||||
may be used on some platforms.
|
||||
libgit2 builds cleanly on most platforms without any external dependencies.
|
||||
Under Unix-like systems, like Linux, *BSD and Mac OS X, libgit2 expects `pthreads` to be available;
|
||||
they should be installed by default on all systems. Under Windows, libgit2 uses the native Windows API
|
||||
for threading.
|
||||
|
||||
Additionally, he following libraries may be used as replacement for built-in functionality:
|
||||
|
||||
* LibSSL **(optional)** <http://www.openssl.org/>
|
||||
|
||||
libgit2 can be built using the SHA1 implementation of LibSSL-Crypto, instead of the built-in custom implementations. Performance wise, they are quite similar.
|
||||
|
||||
* pthreads-w32 **(required for MinGW)** <http://sourceware.org/pthreads-win32/>
|
||||
|
||||
Building libgit2 - Using waf
|
||||
======================
|
||||
|
||||
|
@ -32,7 +32,6 @@
|
||||
*/
|
||||
|
||||
#define GIT_HAS_TLS 1
|
||||
#define GIT_HAS_PTHREAD 1
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
# undef GIT_TLS
|
||||
@ -47,7 +46,6 @@
|
||||
#elif defined(__INTEL_COMPILER)
|
||||
# if defined(_WIN32) || defined(_WIN32_CE)
|
||||
# define GIT_TLS __declspec(thread)
|
||||
# undef GIT_HAS_PTHREAD
|
||||
# else
|
||||
# define GIT_TLS __thread
|
||||
# endif
|
||||
@ -56,11 +54,9 @@
|
||||
defined(_WIN32_CE) || \
|
||||
defined(__BORLANDC__)
|
||||
# define GIT_TLS __declspec(thread)
|
||||
# undef GIT_HAS_PTHREAD
|
||||
|
||||
#else
|
||||
# undef GIT_HAS_TLS
|
||||
# undef GIT_HAS_PTHREAD
|
||||
# define GIT_TLS /* nothing: tls vars are thread-global */
|
||||
#endif
|
||||
|
||||
@ -71,10 +67,6 @@
|
||||
# define GIT_TLS
|
||||
#endif
|
||||
|
||||
#ifdef GIT_HAS_PTHREAD
|
||||
# define GIT_THREADS 1
|
||||
#else
|
||||
# undef GIT_THREADS
|
||||
#endif
|
||||
#define GIT_THREADS 1
|
||||
|
||||
#endif /* INCLUDE_git_thread_utils_h__ */
|
||||
|
@ -11,9 +11,6 @@
|
||||
#include "git2/thread-utils.h"
|
||||
#include "cc-compat.h"
|
||||
|
||||
#ifdef GIT_HAS_PTHREAD
|
||||
# include <pthread.h>
|
||||
#endif
|
||||
#ifdef GIT_HAVE_INTTYPES_H
|
||||
# include <inttypes.h>
|
||||
#endif
|
||||
@ -34,6 +31,7 @@
|
||||
# include <windows.h>
|
||||
# include "msvc-compat.h"
|
||||
# include "mingw-compat.h"
|
||||
# include "win32/pthread.h"
|
||||
|
||||
# define snprintf _snprintf
|
||||
|
||||
@ -43,6 +41,7 @@ typedef SSIZE_T ssize_t;
|
||||
|
||||
# include <unistd.h>
|
||||
# include <arpa/inet.h>
|
||||
# include <pthread.h>
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -1,108 +1,27 @@
|
||||
#ifndef INCLUDE_thread_utils_h__
|
||||
#define INCLUDE_thread_utils_h__
|
||||
|
||||
#if defined(GIT_HAS_PTHREAD)
|
||||
typedef pthread_t git_thread;
|
||||
# define git_thread_create(thread, attr, start_routine, arg) pthread_create(thread, attr, start_routine, arg)
|
||||
# define git_thread_kill(thread) pthread_cancel(thread)
|
||||
# define git_thread_exit(status) pthread_exit(status)
|
||||
# define git_thread_join(id, status) pthread_join(id, status)
|
||||
|
||||
/* Pthreads Mutex */
|
||||
typedef pthread_mutex_t git_lck;
|
||||
# define GITLCK_INIT PTHREAD_MUTEX_INITIALIZER
|
||||
# define gitlck_init(a) pthread_mutex_init(a, NULL)
|
||||
# define gitlck_lock(a) pthread_mutex_lock(a)
|
||||
# define gitlck_unlock(a) pthread_mutex_unlock(a)
|
||||
# define gitlck_free(a) pthread_mutex_destroy(a)
|
||||
#define git_thread pthread_t
|
||||
#define git_thread_create(thread, attr, start_routine, arg) pthread_create(thread, attr, start_routine, arg)
|
||||
#define git_thread_kill(thread) pthread_cancel(thread)
|
||||
#define git_thread_exit(status) pthread_exit(status)
|
||||
#define git_thread_join(id, status) pthread_join(id, status)
|
||||
|
||||
/* Pthreads condition vars */
|
||||
typedef pthread_cond_t git_cnd;
|
||||
# define GITCND_INIT PTHREAD_COND_INITIALIZER
|
||||
# define gitcnd_init(c, a) pthread_cond_init(c, a)
|
||||
# define gitcnd_free(c) pthread_cond_destroy(c)
|
||||
# define gitcnd_wait(c, l) pthread_cond_wait(c, l)
|
||||
# define gitcnd_signal(c) pthread_cond_signal(c)
|
||||
# define gitcnd_broadcast(c) pthread_cond_broadcast(c)
|
||||
/* Pthreads Mutex */
|
||||
#define git_mutex pthread_mutex_t
|
||||
#define git_mutex_init(a) pthread_mutex_init(a, NULL)
|
||||
#define git_mutex_lock(a) pthread_mutex_lock(a)
|
||||
#define git_mutex_unlock(a) pthread_mutex_unlock(a)
|
||||
#define git_mutex_free(a) pthread_mutex_destroy(a)
|
||||
|
||||
# if defined(GIT_HAS_ASM_ATOMIC)
|
||||
# include <asm/atomic.h>
|
||||
typedef atomic_t git_refcnt;
|
||||
# define gitrc_init(a, v) atomic_set(a, v)
|
||||
# define gitrc_inc(a) atomic_inc_return(a)
|
||||
# define gitrc_dec(a) atomic_dec_and_test(a)
|
||||
# define gitrc_free(a) (void)0
|
||||
# elif defined(GIT_WIN32)
|
||||
typedef long git_refcnt;
|
||||
# define gitrc_init(a, v) (*a = v)
|
||||
# define gitrc_inc(a) (InterlockedIncrement(a))
|
||||
# define gitrc_dec(a) (!InterlockedDecrement(a))
|
||||
# define gitrc_free(a) (void)0
|
||||
# else
|
||||
typedef struct { git_lck lock; int counter; } git_refcnt;
|
||||
|
||||
/** Initialize to 0. No memory barrier is issued. */
|
||||
GIT_INLINE(void) gitrc_init(git_refcnt *p, int value)
|
||||
{
|
||||
gitlck_init(&p->lock);
|
||||
p->counter = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment.
|
||||
*
|
||||
* Atomically increments @p by 1. A memory barrier is also
|
||||
* issued before and after the operation.
|
||||
*
|
||||
* @param p pointer of type git_refcnt
|
||||
*/
|
||||
GIT_INLINE(void) gitrc_inc(git_refcnt *p)
|
||||
{
|
||||
gitlck_lock(&p->lock);
|
||||
p->counter++;
|
||||
gitlck_unlock(&p->lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement and test.
|
||||
*
|
||||
* Atomically decrements @p by 1 and returns true if the
|
||||
* result is 0, or false for all other cases. A memory
|
||||
* barrier is also issued before and after the operation.
|
||||
*
|
||||
* @param p pointer of type git_refcnt
|
||||
*/
|
||||
GIT_INLINE(int) gitrc_dec(git_refcnt *p)
|
||||
{
|
||||
int c;
|
||||
gitlck_lock(&p->lock);
|
||||
c = --p->counter;
|
||||
gitlck_unlock(&p->lock);
|
||||
return !c;
|
||||
}
|
||||
|
||||
/** Free any resources associated with the counter. */
|
||||
# define gitrc_free(p) gitlck_free(&(p)->lock)
|
||||
# endif
|
||||
|
||||
#elif defined(GIT_THREADS)
|
||||
# error GIT_THREADS but no git_lck implementation
|
||||
|
||||
#else
|
||||
/* no threads support */
|
||||
typedef struct { int dummy; } git_lck;
|
||||
# define GIT_MUTEX_INIT {}
|
||||
# define gitlck_init(a) (void)0
|
||||
# define gitlck_lock(a) (void)0
|
||||
# define gitlck_unlock(a) (void)0
|
||||
# define gitlck_free(a) (void)0
|
||||
|
||||
typedef struct { int counter; } git_refcnt;
|
||||
# define gitrc_init(a,v) ((a)->counter = v)
|
||||
# define gitrc_inc(a) ((a)->counter++)
|
||||
# define gitrc_dec(a) (--(a)->counter == 0)
|
||||
# define gitrc_free(a) (void)0
|
||||
#endif
|
||||
/* Pthreads condition vars */
|
||||
#define git_cond pthread_cond_t
|
||||
#define git_cond_init(c, a) pthread_cond_init(c, a)
|
||||
#define git_cond_free(c) pthread_cond_destroy(c)
|
||||
#define git_cond_wait(c, l) pthread_cond_wait(c, l)
|
||||
#define git_cond_signal(c) pthread_cond_signal(c)
|
||||
#define git_cond_broadcast(c) pthread_cond_broadcast(c)
|
||||
|
||||
extern int git_online_cpus(void);
|
||||
|
||||
|
120
src/win32/pthread.c
Normal file
120
src/win32/pthread.c
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License, version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* In addition to the permissions in the GNU General Public License,
|
||||
* the authors give you unlimited permission to link the compiled
|
||||
* version of this file into combinations with other programs,
|
||||
* and to distribute those combinations without any restriction
|
||||
* coming from the use of this file. (The General Public License
|
||||
* restrictions do apply in other respects; for example, they cover
|
||||
* modification of the file, and distribution when not linked into
|
||||
* a combined executable.)
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* Original code by Ramiro Polla (Public Domain)
|
||||
*/
|
||||
|
||||
#include "pthread.h"
|
||||
|
||||
int pthread_create(pthread_t *GIT_RESTRICT thread,
|
||||
const pthread_attr_t *GIT_RESTRICT GIT_UNUSED(attr),
|
||||
void *(*start_routine)(void*), void *GIT_RESTRICT arg)
|
||||
{
|
||||
GIT_UNUSED_ARG(attr);
|
||||
*thread = (pthread_t) CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)start_routine, arg, 0, NULL);
|
||||
return *thread ? GIT_SUCCESS : GIT_EOSERR;
|
||||
}
|
||||
|
||||
int pthread_cond_signal(pthread_cond_t *cond)
|
||||
{
|
||||
WakeConditionVariable(cond);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_cond_wait(pthread_cond_t *GIT_RESTRICT cond,
|
||||
pthread_mutex_t *GIT_RESTRICT mutex)
|
||||
{
|
||||
int ret;
|
||||
ret = SleepConditionVariableCS(cond, mutex, INFINITE);
|
||||
return -(!ret);
|
||||
}
|
||||
|
||||
int pthread_mutex_lock(pthread_mutex_t *mutex)
|
||||
{
|
||||
EnterCriticalSection(mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_mutex_unlock(pthread_mutex_t *mutex)
|
||||
{
|
||||
LeaveCriticalSection(mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_join(pthread_t thread, void **value_ptr)
|
||||
{
|
||||
int ret;
|
||||
ret = WaitForSingleObject(thread, INFINITE);
|
||||
if (ret && value_ptr)
|
||||
GetExitCodeThread(thread, (void*) value_ptr);
|
||||
return -(!!ret);
|
||||
}
|
||||
|
||||
int pthread_cond_broadcast(pthread_cond_t *cond)
|
||||
{
|
||||
WakeAllConditionVariable(cond);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_mutex_destroy(pthread_mutex_t *mutex)
|
||||
{
|
||||
int ret;
|
||||
ret = CloseHandle(mutex);
|
||||
return -(!ret);
|
||||
}
|
||||
|
||||
int pthread_cond_destroy(pthread_cond_t *GIT_UNUSED(cond))
|
||||
{
|
||||
GIT_UNUSED_ARG(cond);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_cond_init(pthread_cond_t *GIT_RESTRICT cond,
|
||||
const pthread_condattr_t *GIT_RESTRICT GIT_UNUSED(condattr))
|
||||
{
|
||||
GIT_UNUSED_ARG(condattr);
|
||||
InitializeConditionVariable(cond);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_mutex_init(pthread_mutex_t *GIT_RESTRICT mutex,
|
||||
const pthread_mutexattr_t *GIT_RESTRICT GIT_UNUSED(mutexattr))
|
||||
{
|
||||
GIT_UNUSED_ARG(mutexattr);
|
||||
InitializeCriticalSection(mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_num_processors_np(void)
|
||||
{
|
||||
DWORD_PTR p, s;
|
||||
int n = 0;
|
||||
|
||||
if (GetProcessAffinityMask(GetCurrentProcess(), &p, &s))
|
||||
for (; p; p >>= 1)
|
||||
n += p&1;
|
||||
|
||||
return n ? n : 1;
|
||||
}
|
||||
|
67
src/win32/pthread.h
Normal file
67
src/win32/pthread.h
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License, version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* In addition to the permissions in the GNU General Public License,
|
||||
* the authors give you unlimited permission to link the compiled
|
||||
* version of this file into combinations with other programs,
|
||||
* and to distribute those combinations without any restriction
|
||||
* coming from the use of this file. (The General Public License
|
||||
* restrictions do apply in other respects; for example, they cover
|
||||
* modification of the file, and distribution when not linked into
|
||||
* a combined executable.)
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* Original code by Ramiro Polla (Public Domain)
|
||||
*/
|
||||
|
||||
#ifndef GIT_PTHREAD_H
|
||||
#define GIT_PTHREAD_H
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
#if defined (_MSC_VER)
|
||||
# define GIT_RESTRICT __restrict
|
||||
#else
|
||||
# define GIT_RESTRICT __restrict__
|
||||
#endif
|
||||
|
||||
typedef int pthread_mutexattr_t;
|
||||
typedef int pthread_condattr_t;
|
||||
typedef int pthread_attr_t;
|
||||
typedef CRITICAL_SECTION pthread_mutex_t;
|
||||
typedef CONDITION_VARIABLE pthread_cond_t;
|
||||
typedef HANDLE pthread_t;
|
||||
|
||||
#define PTHREAD_MUTEX_INITIALIZER {(void*)-1};
|
||||
|
||||
int pthread_create(pthread_t *GIT_RESTRICT,
|
||||
const pthread_attr_t *GIT_RESTRICT,
|
||||
void *(*start_routine)(void*), void *__restrict);
|
||||
|
||||
int pthread_join(pthread_t, void **);
|
||||
|
||||
int pthread_mutex_init(pthread_mutex_t *GIT_RESTRICT, const pthread_mutexattr_t *GIT_RESTRICT);
|
||||
int pthread_mutex_destroy(pthread_mutex_t *);
|
||||
int pthread_mutex_lock(pthread_mutex_t *);
|
||||
int pthread_mutex_unlock(pthread_mutex_t *);
|
||||
|
||||
int pthread_cond_init(pthread_cond_t *GIT_RESTRICT, const pthread_condattr_t *GIT_RESTRICT);
|
||||
int pthread_cond_destroy(pthread_cond_t *);
|
||||
int pthread_cond_broadcast(pthread_cond_t *);
|
||||
int pthread_cond_signal(pthread_cond_t *);
|
||||
int pthread_cond_wait(pthread_cond_t *GIT_RESTRICT, pthread_mutex_t *GIT_RESTRICT);
|
||||
|
||||
int pthread_num_processors_np(void);
|
||||
|
||||
#endif
|
@ -27,17 +27,6 @@
|
||||
#include "vector.h"
|
||||
#include "fileops.h"
|
||||
|
||||
BEGIN_TEST(refcnt0, "increment refcount twice, decrement twice")
|
||||
git_refcnt p;
|
||||
|
||||
gitrc_init(&p, 0);
|
||||
gitrc_inc(&p);
|
||||
gitrc_inc(&p);
|
||||
must_be_true(!gitrc_dec(&p));
|
||||
must_be_true(gitrc_dec(&p));
|
||||
gitrc_free(&p);
|
||||
END_TEST
|
||||
|
||||
BEGIN_TEST(string0, "compare prefixes")
|
||||
must_be_true(git__prefixcmp("", "") == 0);
|
||||
must_be_true(git__prefixcmp("a", "") == 0);
|
||||
@ -626,8 +615,6 @@ END_TEST
|
||||
|
||||
|
||||
BEGIN_SUITE(core)
|
||||
ADD_TEST(refcnt0);
|
||||
|
||||
ADD_TEST(string0);
|
||||
ADD_TEST(string1);
|
||||
|
||||
|
44
wscript
44
wscript
@ -5,10 +5,12 @@ from waflib.Build import BuildContext, CleanContext, \
|
||||
# Unix flags
|
||||
CFLAGS_UNIX = ["-O2", "-Wall", "-Wextra"]
|
||||
CFLAGS_UNIX_DBG = ['-g']
|
||||
CFLAGS_UNIX_PED = ['-pedantic', '-Werror']
|
||||
|
||||
# Windows MSVC flags
|
||||
CFLAGS_WIN32_COMMON = ['/TC', '/W4', '/WX', '/nologo', '/Zi']
|
||||
CFLAGS_WIN32_COMMON = ['/TC', '/W4', '/nologo', '/Zi']
|
||||
CFLAGS_WIN32_RELEASE = ['/O2', '/MD']
|
||||
CFLAGS_WIN32_PED = ['/Wx']
|
||||
|
||||
# Note: /RTC* cannot be used with optimization on.
|
||||
CFLAGS_WIN32_DBG = ['/Od', '/RTC1', '/RTCc', '/DEBUG', '/MDd']
|
||||
@ -31,9 +33,10 @@ PPC optimized version (ppc) or the SHA1 functions from OpenSSL (openssl)")
|
||||
help='Select target architecture (ia64, x64, x86, x86_amd64, x86_ia64)')
|
||||
opt.add_option('--without-sqlite', action='store_false', default=True,
|
||||
dest='use_sqlite', help='Disable sqlite support')
|
||||
opt.add_option('--strict', action='store_true', default=False,
|
||||
help='Max warning level; treat warnings as errors')
|
||||
|
||||
def configure(conf):
|
||||
|
||||
# load the MSVC configuration flags
|
||||
if conf.options.msvc:
|
||||
conf.env['MSVC_VERSIONS'] = ['msvc ' + conf.options.msvc]
|
||||
@ -43,25 +46,38 @@ def configure(conf):
|
||||
# default configuration for C programs
|
||||
conf.load('compiler_c')
|
||||
|
||||
dbg = conf.options.debug
|
||||
debug = conf.options.debug
|
||||
pedantic = conf.options.strict
|
||||
|
||||
conf.env.CFLAGS = CFLAGS_UNIX + (CFLAGS_UNIX_DBG if dbg else [])
|
||||
if conf.env.CC_NAME == 'msvc':
|
||||
conf.env.CFLAGS = CFLAGS_WIN32_COMMON
|
||||
conf.env.LINKFLAGS = CFLAGS_WIN32_L
|
||||
if debug:
|
||||
conf.env.CFLAGS += CFLAGS_WIN32_DEBUG
|
||||
conf.env.LINKFLAGS += CFLAGS_WIN32_L_DBG
|
||||
else:
|
||||
conf.env.CFLAGS += CFLAGS_WIN32_RELEASE
|
||||
|
||||
if pedantic:
|
||||
conf.ENV.CFLAGS += CFLAGS_WIN32_PED
|
||||
|
||||
elif conf.env.CC_NAME == 'gcc':
|
||||
conf.env.CFLAGS = CFLAGS_UNIX
|
||||
if debug:
|
||||
conf.env.CFLAGS += CFLAGS_UNIX_DBG
|
||||
if pedantic:
|
||||
conf.env.CFLAGS += CFLAGS_UNIX_PED
|
||||
|
||||
|
||||
# Win32 Platform: MinGW and MSVC
|
||||
if conf.env.DEST_OS == 'win32':
|
||||
conf.env.PLATFORM = 'win32'
|
||||
conf.env.DEFINES += ['WIN32', '_DEBUG', '_LIB']
|
||||
|
||||
if conf.env.CC_NAME == 'msvc':
|
||||
conf.env.CFLAGS = CFLAGS_WIN32_COMMON + \
|
||||
(CFLAGS_WIN32_DBG if dbg else CFLAGS_WIN32_RELEASE)
|
||||
conf.env.LINKFLAGS += CFLAGS_WIN32_L + \
|
||||
(CFLAGS_WIN32_L_DBG if dbg else [])
|
||||
conf.env.DEFINES += ['WIN32', '_DEBUG', '_LIB']
|
||||
|
||||
elif conf.env.CC_NAME == 'gcc':
|
||||
conf.check_cc(lib='pthread', uselib_store='pthread')
|
||||
|
||||
# Unix-like platforms: Linux, Darwin, Cygwin, *BSD, etc
|
||||
else:
|
||||
conf.env.PLATFORM = 'unix'
|
||||
conf.check_cc(lib='pthread', uselib_store='pthread')
|
||||
|
||||
# Do not build ZLib with GZIP support
|
||||
conf.env.DEFINES += ['NO_GZIP']
|
||||
|
Loading…
Reference in New Issue
Block a user