uv: upgrade to 4a88b3b
This commit is contained in:
parent
37bdd36d70
commit
3ea2a618ad
2
deps/uv/src/unix/dl.c
vendored
2
deps/uv/src/unix/dl.c
vendored
@ -34,7 +34,7 @@ int uv_dlopen(const char* filename, uv_lib_t* lib) {
|
|||||||
dlerror(); /* Reset error status. */
|
dlerror(); /* Reset error status. */
|
||||||
lib->errmsg = NULL;
|
lib->errmsg = NULL;
|
||||||
lib->handle = dlopen(filename, RTLD_LAZY);
|
lib->handle = dlopen(filename, RTLD_LAZY);
|
||||||
return uv__dlerror(lib);
|
return lib->handle ? 0 : uv__dlerror(lib);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
2
deps/uv/src/win/error.c
vendored
2
deps/uv/src/win/error.c
vendored
@ -83,6 +83,7 @@ uv_err_code uv_translate_sys_error(int sys_errno) {
|
|||||||
case ERROR_SIGNAL_REFUSED: return UV_EIO;
|
case ERROR_SIGNAL_REFUSED: return UV_EIO;
|
||||||
case ERROR_FILE_NOT_FOUND: return UV_ENOENT;
|
case ERROR_FILE_NOT_FOUND: return UV_ENOENT;
|
||||||
case ERROR_INVALID_NAME: return UV_ENOENT;
|
case ERROR_INVALID_NAME: return UV_ENOENT;
|
||||||
|
case ERROR_INVALID_REPARSE_DATA: return UV_ENOENT;
|
||||||
case ERROR_MOD_NOT_FOUND: return UV_ENOENT;
|
case ERROR_MOD_NOT_FOUND: return UV_ENOENT;
|
||||||
case ERROR_PATH_NOT_FOUND: return UV_ENOENT;
|
case ERROR_PATH_NOT_FOUND: return UV_ENOENT;
|
||||||
case ERROR_ACCESS_DENIED: return UV_EPERM;
|
case ERROR_ACCESS_DENIED: return UV_EPERM;
|
||||||
@ -111,6 +112,7 @@ uv_err_code uv_translate_sys_error(int sys_errno) {
|
|||||||
case ERROR_OPERATION_ABORTED: return UV_EINTR;
|
case ERROR_OPERATION_ABORTED: return UV_EINTR;
|
||||||
case WSAEINTR: return UV_EINTR;
|
case WSAEINTR: return UV_EINTR;
|
||||||
case ERROR_INVALID_DATA: return UV_EINVAL;
|
case ERROR_INVALID_DATA: return UV_EINVAL;
|
||||||
|
case ERROR_SYMLINK_NOT_SUPPORTED: return UV_EINVAL;
|
||||||
case WSAEINVAL: return UV_EINVAL;
|
case WSAEINVAL: return UV_EINVAL;
|
||||||
case ERROR_CANT_RESOLVE_FILENAME: return UV_ELOOP;
|
case ERROR_CANT_RESOLVE_FILENAME: return UV_ELOOP;
|
||||||
case ERROR_TOO_MANY_OPEN_FILES: return UV_EMFILE;
|
case ERROR_TOO_MANY_OPEN_FILES: return UV_EMFILE;
|
||||||
|
312
deps/uv/src/win/fs.c
vendored
312
deps/uv/src/win/fs.c
vendored
@ -161,47 +161,147 @@ static int is_path_dir(const wchar_t* path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int get_reparse_point(HANDLE handle, int* target_length) {
|
INLINE static int fs__readlink_handle(HANDLE handle, char** target_ptr,
|
||||||
void* buffer = NULL;
|
int64_t* target_len_ptr) {
|
||||||
REPARSE_DATA_BUFFER* reparse_data;
|
char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
||||||
DWORD bytes_returned;
|
REPARSE_DATA_BUFFER* reparse_data = (REPARSE_DATA_BUFFER*) buffer;
|
||||||
int rv = 0;
|
WCHAR *w_target;
|
||||||
|
DWORD w_target_len;
|
||||||
buffer = malloc(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
|
char* target;
|
||||||
if (!buffer) {
|
int target_len;
|
||||||
uv_fatal_error(ERROR_OUTOFMEMORY, "malloc");
|
DWORD bytes;
|
||||||
}
|
|
||||||
|
|
||||||
if (!DeviceIoControl(handle,
|
if (!DeviceIoControl(handle,
|
||||||
FSCTL_GET_REPARSE_POINT,
|
FSCTL_GET_REPARSE_POINT,
|
||||||
NULL,
|
NULL,
|
||||||
0,
|
0,
|
||||||
buffer,
|
buffer,
|
||||||
MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
|
sizeof buffer,
|
||||||
&bytes_returned,
|
&bytes,
|
||||||
NULL)) {
|
NULL)) {
|
||||||
free(buffer);
|
return -1;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reparse_data = (REPARSE_DATA_BUFFER*)buffer;
|
|
||||||
|
|
||||||
if (reparse_data->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
|
if (reparse_data->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
|
||||||
rv = 1;
|
/* Real symlink */
|
||||||
if (target_length) {
|
w_target = reparse_data->SymbolicLinkReparseBuffer.PathBuffer +
|
||||||
*target_length = reparse_data->SymbolicLinkReparseBuffer.SubstituteNameLength /
|
(reparse_data->SymbolicLinkReparseBuffer.SubstituteNameOffset /
|
||||||
sizeof(wchar_t);
|
sizeof(WCHAR));
|
||||||
|
w_target_len =
|
||||||
|
reparse_data->SymbolicLinkReparseBuffer.SubstituteNameLength /
|
||||||
|
sizeof(WCHAR);
|
||||||
|
|
||||||
|
/* Real symlinks can contain pretty much everything, but the only thing */
|
||||||
|
/* we really care about is undoing the implicit conversion to an NT */
|
||||||
|
/* namespaced path that CreateSymbolicLink will perform on absolute */
|
||||||
|
/* paths. If the path is win32-namespaced then the user must have */
|
||||||
|
/* explicitly made it so, and we better just return the unmodified */
|
||||||
|
/* reparse data. */
|
||||||
|
if (w_target_len >= 4 &&
|
||||||
|
w_target[0] == L'\\' &&
|
||||||
|
w_target[1] == L'?' &&
|
||||||
|
w_target[2] == L'?' &&
|
||||||
|
w_target[3] == L'\\') {
|
||||||
|
/* Starts with \??\ */
|
||||||
|
if (w_target_len >= 6 &&
|
||||||
|
((w_target[4] >= L'A' && w_target[4] <= L'Z') ||
|
||||||
|
(w_target[4] >= L'a' && w_target[4] <= L'z')) &&
|
||||||
|
w_target[5] == L':' &&
|
||||||
|
(w_target_len == 6 || w_target[6] == L'\\')) {
|
||||||
|
/* \??\«drive»:\ */
|
||||||
|
w_target += 4;
|
||||||
|
w_target_len -= 4;
|
||||||
|
|
||||||
|
} else if (w_target_len >= 8 &&
|
||||||
|
(w_target[4] == L'U' || w_target[4] == L'u') &&
|
||||||
|
(w_target[5] == L'N' || w_target[5] == L'n') &&
|
||||||
|
(w_target[6] == L'C' || w_target[6] == L'c') &&
|
||||||
|
w_target[7] == L'\\') {
|
||||||
|
/* \??\UNC\«server»\«share»\ - make sure the final path looks like */
|
||||||
|
/* \\«server»\«share»\ */
|
||||||
|
w_target += 6;
|
||||||
|
w_target[0] = L'\\';
|
||||||
|
w_target_len -= 6;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (reparse_data->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
|
} else if (reparse_data->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
|
||||||
rv = 1;
|
/* Junction. */
|
||||||
if (target_length) {
|
w_target = reparse_data->MountPointReparseBuffer.PathBuffer +
|
||||||
*target_length = reparse_data->MountPointReparseBuffer.SubstituteNameLength /
|
(reparse_data->MountPointReparseBuffer.SubstituteNameOffset /
|
||||||
|
sizeof(WCHAR));
|
||||||
|
w_target_len = reparse_data->MountPointReparseBuffer.SubstituteNameLength /
|
||||||
sizeof(wchar_t);
|
sizeof(wchar_t);
|
||||||
|
|
||||||
|
/* Only treat junctions that look like \??\«drive»:\ as symlink. */
|
||||||
|
/* Junctions can also be used as mount points, like \??\Volume{«guid»}, */
|
||||||
|
/* but that's confusing for programs since they wouldn't be able to */
|
||||||
|
/* actually understand such a path when returned by uv_readlink(). */
|
||||||
|
/* UNC paths are never valid for junctions so we don't care about them. */
|
||||||
|
if (!(w_target_len >= 6 &&
|
||||||
|
w_target[0] == L'\\' &&
|
||||||
|
w_target[1] == L'?' &&
|
||||||
|
w_target[2] == L'?' &&
|
||||||
|
w_target[3] == L'\\' &&
|
||||||
|
((w_target[4] >= L'A' && w_target[4] <= L'Z') ||
|
||||||
|
(w_target[4] >= L'a' && w_target[4] <= L'z')) &&
|
||||||
|
w_target[5] == L':' &&
|
||||||
|
(w_target_len == 6 || w_target[6] == L'\\'))) {
|
||||||
|
SetLastError(ERROR_SYMLINK_NOT_SUPPORTED);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Remove leading \??\ */
|
||||||
|
w_target += 4;
|
||||||
|
w_target_len -= 4;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/* Reparse tag does not indicate a symlink. */
|
||||||
|
SetLastError(ERROR_SYMLINK_NOT_SUPPORTED);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(buffer);
|
/* Compute the length of the target. */
|
||||||
return rv;
|
target_len = WideCharToMultiByte(CP_UTF8,
|
||||||
|
0,
|
||||||
|
w_target,
|
||||||
|
w_target_len,
|
||||||
|
NULL,
|
||||||
|
0,
|
||||||
|
NULL,
|
||||||
|
NULL);
|
||||||
|
if (target_len == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If requested, allocate memory and convert to UTF8. */
|
||||||
|
if (target_ptr != NULL) {
|
||||||
|
int r;
|
||||||
|
target = (char*) malloc(target_len + 1);
|
||||||
|
if (target == NULL) {
|
||||||
|
SetLastError(ERROR_OUTOFMEMORY);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = WideCharToMultiByte(CP_UTF8,
|
||||||
|
0,
|
||||||
|
w_target,
|
||||||
|
w_target_len,
|
||||||
|
target,
|
||||||
|
target_len,
|
||||||
|
NULL,
|
||||||
|
NULL);
|
||||||
|
assert(r == target_len);
|
||||||
|
target[target_len] = '\0';
|
||||||
|
|
||||||
|
*target_ptr = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target_len_ptr != NULL) {
|
||||||
|
*target_len_ptr = target_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -455,11 +555,12 @@ void fs__unlink(uv_fs_t* req, const wchar_t* path) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_dir_symlink = info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY &&
|
is_dir_symlink = (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
|
||||||
get_reparse_point(handle, NULL);
|
(info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT);
|
||||||
|
|
||||||
CloseHandle(handle);
|
CloseHandle(handle);
|
||||||
|
|
||||||
|
/* Todo: very inefficient; fix this. */
|
||||||
if (is_dir_symlink) {
|
if (is_dir_symlink) {
|
||||||
fs__rmdir(req, path);
|
fs__rmdir(req, path);
|
||||||
} else {
|
} else {
|
||||||
@ -583,7 +684,6 @@ void fs__readdir(uv_fs_t* req, const wchar_t* path, int flags) {
|
|||||||
|
|
||||||
|
|
||||||
INLINE static int fs__stat_handle(HANDLE handle, uv_statbuf_t* statbuf) {
|
INLINE static int fs__stat_handle(HANDLE handle, uv_statbuf_t* statbuf) {
|
||||||
int target_length;
|
|
||||||
BY_HANDLE_FILE_INFORMATION info;
|
BY_HANDLE_FILE_INFORMATION info;
|
||||||
|
|
||||||
if (!GetFileInformationByHandle(handle, &info)) {
|
if (!GetFileInformationByHandle(handle, &info)) {
|
||||||
@ -600,28 +700,25 @@ INLINE static int fs__stat_handle(HANDLE handle, uv_statbuf_t* statbuf) {
|
|||||||
|
|
||||||
statbuf->st_mode = 0;
|
statbuf->st_mode = 0;
|
||||||
|
|
||||||
if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT &&
|
if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
|
||||||
get_reparse_point(handle, &target_length)) {
|
if (fs__readlink_handle(handle, NULL, &statbuf->st_size) != 0) {
|
||||||
statbuf->st_mode = S_IFLNK;
|
return -1;
|
||||||
/* Adjust for long path */
|
}
|
||||||
statbuf->st_size = target_length - JUNCTION_PREFIX_LEN;
|
statbuf->st_mode |= S_IFLNK;
|
||||||
|
} else if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||||
|
statbuf->st_mode |= _S_IFDIR;
|
||||||
|
statbuf->st_size = 0;
|
||||||
} else {
|
} else {
|
||||||
if (info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
|
statbuf->st_mode |= _S_IFREG;
|
||||||
statbuf->st_mode |= (_S_IREAD + (_S_IREAD >> 3) + (_S_IREAD >> 6));
|
|
||||||
} else {
|
|
||||||
statbuf->st_mode |= ((_S_IREAD|_S_IWRITE) + ((_S_IREAD|_S_IWRITE) >> 3) +
|
|
||||||
((_S_IREAD|_S_IWRITE) >> 6));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
||||||
statbuf->st_mode |= _S_IFDIR;
|
|
||||||
} else {
|
|
||||||
statbuf->st_mode |= _S_IFREG;
|
|
||||||
}
|
|
||||||
|
|
||||||
statbuf->st_size = ((int64_t) info.nFileSizeHigh << 32) +
|
statbuf->st_size = ((int64_t) info.nFileSizeHigh << 32) +
|
||||||
(int64_t) info.nFileSizeLow;
|
(int64_t) info.nFileSizeLow;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
|
||||||
|
statbuf->st_mode |= (_S_IREAD + (_S_IREAD >> 3) + (_S_IREAD >> 6));
|
||||||
|
} else {
|
||||||
|
statbuf->st_mode |= ((_S_IREAD|_S_IWRITE) + ((_S_IREAD|_S_IWRITE) >> 3) +
|
||||||
|
((_S_IREAD|_S_IWRITE) >> 6));
|
||||||
}
|
}
|
||||||
|
|
||||||
statbuf->st_mtime = FILETIME_TO_TIME_T(info.ftLastWriteTime);
|
statbuf->st_mtime = FILETIME_TO_TIME_T(info.ftLastWriteTime);
|
||||||
@ -659,7 +756,16 @@ INLINE static void fs__stat(uv_fs_t* req, const wchar_t* path, int do_lstat) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fs__stat_handle(handle, &req->stat) != 0) {
|
if (fs__stat_handle(handle, &req->stat) != 0) {
|
||||||
SET_REQ_WIN32_ERROR(req, GetLastError());
|
DWORD error = GetLastError();
|
||||||
|
if (do_lstat && error == ERROR_SYMLINK_NOT_SUPPORTED) {
|
||||||
|
/* We opened a reparse point but it was not a symlink. Try again. */
|
||||||
|
fs__stat(req, path, 0);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/* Stat failed. */
|
||||||
|
SET_REQ_WIN32_ERROR(req, GetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
CloseHandle(handle);
|
CloseHandle(handle);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1099,117 +1205,31 @@ void fs__symlink(uv_fs_t* req, const wchar_t* path, const wchar_t* new_path,
|
|||||||
|
|
||||||
|
|
||||||
void fs__readlink(uv_fs_t* req, const wchar_t* path) {
|
void fs__readlink(uv_fs_t* req, const wchar_t* path) {
|
||||||
int result = -1;
|
HANDLE handle;
|
||||||
BOOL rv;
|
|
||||||
HANDLE symlink;
|
|
||||||
void* buffer = NULL;
|
|
||||||
DWORD bytes_returned;
|
|
||||||
REPARSE_DATA_BUFFER* reparse_data;
|
|
||||||
int utf8size;
|
|
||||||
wchar_t* substitute_name;
|
|
||||||
int substitute_name_length;
|
|
||||||
|
|
||||||
symlink = CreateFileW(path,
|
handle = CreateFileW(path,
|
||||||
0,
|
|
||||||
0,
|
|
||||||
NULL,
|
|
||||||
OPEN_EXISTING,
|
|
||||||
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
|
|
||||||
NULL);
|
|
||||||
|
|
||||||
if (INVALID_HANDLE_VALUE == symlink) {
|
|
||||||
result = -1;
|
|
||||||
SET_REQ_WIN32_ERROR(req, GetLastError());
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer = malloc(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
|
|
||||||
if (!buffer) {
|
|
||||||
uv_fatal_error(ERROR_OUTOFMEMORY, "malloc");
|
|
||||||
}
|
|
||||||
|
|
||||||
rv = DeviceIoControl(symlink,
|
|
||||||
FSCTL_GET_REPARSE_POINT,
|
|
||||||
NULL,
|
|
||||||
0,
|
0,
|
||||||
buffer,
|
0,
|
||||||
MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
|
NULL,
|
||||||
&bytes_returned,
|
OPEN_EXISTING,
|
||||||
|
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
if (!rv) {
|
if (handle == INVALID_HANDLE_VALUE) {
|
||||||
result = -1;
|
|
||||||
SET_REQ_WIN32_ERROR(req, GetLastError());
|
SET_REQ_WIN32_ERROR(req, GetLastError());
|
||||||
goto done;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
reparse_data = (REPARSE_DATA_BUFFER*)buffer;
|
if (fs__readlink_handle(handle, (char**) &req->ptr, NULL) != 0) {
|
||||||
if (reparse_data->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
|
|
||||||
substitute_name = reparse_data->SymbolicLinkReparseBuffer.PathBuffer +
|
|
||||||
(reparse_data->SymbolicLinkReparseBuffer.SubstituteNameOffset /
|
|
||||||
sizeof(wchar_t));
|
|
||||||
substitute_name_length =
|
|
||||||
reparse_data->SymbolicLinkReparseBuffer.SubstituteNameLength /
|
|
||||||
sizeof(wchar_t);
|
|
||||||
} else if (reparse_data->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
|
|
||||||
substitute_name = reparse_data->MountPointReparseBuffer.PathBuffer +
|
|
||||||
(reparse_data->MountPointReparseBuffer.SubstituteNameOffset /
|
|
||||||
sizeof(wchar_t));
|
|
||||||
substitute_name_length =
|
|
||||||
reparse_data->MountPointReparseBuffer.SubstituteNameLength /
|
|
||||||
sizeof(wchar_t);
|
|
||||||
} else {
|
|
||||||
result = -1;
|
|
||||||
/* something is seriously wrong */
|
|
||||||
SET_REQ_WIN32_ERROR(req, GetLastError());
|
SET_REQ_WIN32_ERROR(req, GetLastError());
|
||||||
goto done;
|
CloseHandle(handle);
|
||||||
}
|
return;
|
||||||
|
|
||||||
/* Strip off the leading \??\ from the substitute name buffer.*/
|
|
||||||
if (wcsncmp(substitute_name, JUNCTION_PREFIX, JUNCTION_PREFIX_LEN) == 0) {
|
|
||||||
substitute_name += JUNCTION_PREFIX_LEN;
|
|
||||||
substitute_name_length -= JUNCTION_PREFIX_LEN;
|
|
||||||
}
|
|
||||||
|
|
||||||
utf8size = uv_utf16_to_utf8(substitute_name,
|
|
||||||
substitute_name_length,
|
|
||||||
NULL,
|
|
||||||
0);
|
|
||||||
if (!utf8size) {
|
|
||||||
result = -1;
|
|
||||||
SET_REQ_WIN32_ERROR(req, GetLastError());
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
|
|
||||||
req->ptr = malloc(utf8size + 1);
|
|
||||||
if (!req->ptr) {
|
|
||||||
uv_fatal_error(ERROR_OUTOFMEMORY, "malloc");
|
|
||||||
}
|
|
||||||
|
|
||||||
utf8size = uv_utf16_to_utf8(substitute_name,
|
|
||||||
substitute_name_length,
|
|
||||||
(char*)req->ptr,
|
|
||||||
utf8size);
|
|
||||||
if (!utf8size) {
|
|
||||||
result = -1;
|
|
||||||
SET_REQ_WIN32_ERROR(req, GetLastError());
|
|
||||||
goto done;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req->flags |= UV_FS_FREE_PTR;
|
req->flags |= UV_FS_FREE_PTR;
|
||||||
((char*)req->ptr)[utf8size] = '\0';
|
SET_REQ_RESULT(req, 0);
|
||||||
result = 0;
|
|
||||||
|
|
||||||
done:
|
CloseHandle(handle);
|
||||||
if (buffer) {
|
|
||||||
free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (symlink != INVALID_HANDLE_VALUE) {
|
|
||||||
CloseHandle(symlink);
|
|
||||||
}
|
|
||||||
|
|
||||||
SET_REQ_RESULT(req, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user