mirror of
https://github.com/genodelabs/genode.git
synced 2025-03-23 04:25:21 +00:00
parent
bfddf08f75
commit
a59f73f7d3
@ -372,9 +372,7 @@ extern "C" int execve(char const *filename,
|
||||
|
||||
for (unsigned i = 0; i < MAX_INTERPRETER_NESTING_LEVELS; i++) {
|
||||
|
||||
try {
|
||||
Libc::resolve_symlinks(path.string(), resolved_path); }
|
||||
catch (Libc::Symlink_resolve_error) {
|
||||
if (Libc::resolve_symlinks(path.string(), resolved_path).failed()) {
|
||||
warning("execve: executable binary '", filename, "' does not exist");
|
||||
return Libc::Errno(ENOENT);
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ extern "C" {
|
||||
#include <internal/plugin_registry.h>
|
||||
#include <internal/plugin.h>
|
||||
#include <internal/file.h>
|
||||
#include <internal/file_operations.h>
|
||||
#include <internal/mem_alloc.h>
|
||||
#include <internal/mmap_registry.h>
|
||||
#include <internal/errno.h>
|
||||
@ -104,7 +105,7 @@ typedef Token<Vfs::Scanner_policy_path_element> Path_element_token;
|
||||
/**
|
||||
* Resolve symbolic links in a given absolute path
|
||||
*/
|
||||
void Libc::resolve_symlinks(char const *path, Absolute_path &resolved_path)
|
||||
Symlink_resolve_result Libc::resolve_symlinks(char const *path, Absolute_path &resolved_path)
|
||||
{
|
||||
char path_element[PATH_MAX];
|
||||
char symlink_target[PATH_MAX];
|
||||
@ -118,7 +119,7 @@ void Libc::resolve_symlinks(char const *path, Absolute_path &resolved_path)
|
||||
do {
|
||||
if (follow_count++ == FOLLOW_LIMIT) {
|
||||
errno = ELOOP;
|
||||
throw Symlink_resolve_error();
|
||||
return Symlink_resolve_error();
|
||||
}
|
||||
|
||||
current_iteration_working_path = next_iteration_working_path;
|
||||
@ -140,7 +141,7 @@ void Libc::resolve_symlinks(char const *path, Absolute_path &resolved_path)
|
||||
next_iteration_working_path.append_element(path_element);
|
||||
} catch (Path_base::Path_too_long) {
|
||||
errno = ENAMETOOLONG;
|
||||
throw Symlink_resolve_error();
|
||||
return Symlink_resolve_error();
|
||||
}
|
||||
|
||||
/*
|
||||
@ -152,14 +153,14 @@ void Libc::resolve_symlinks(char const *path, Absolute_path &resolved_path)
|
||||
int res;
|
||||
FNAME_FUNC_WRAPPER_GENERIC(res = , stat, next_iteration_working_path.base(), &stat_buf);
|
||||
if (res == -1) {
|
||||
throw Symlink_resolve_error();
|
||||
return Symlink_resolve_error();
|
||||
}
|
||||
if (S_ISLNK(stat_buf.st_mode)) {
|
||||
FNAME_FUNC_WRAPPER_GENERIC(res = , readlink,
|
||||
next_iteration_working_path.base(),
|
||||
symlink_target, sizeof(symlink_target) - 1);
|
||||
if (res < 1)
|
||||
throw Symlink_resolve_error();
|
||||
return Symlink_resolve_error();
|
||||
|
||||
/* zero terminate target */
|
||||
symlink_target[res] = 0;
|
||||
@ -174,7 +175,7 @@ void Libc::resolve_symlinks(char const *path, Absolute_path &resolved_path)
|
||||
next_iteration_working_path.append_element(symlink_target);
|
||||
} catch (Path_base::Path_too_long) {
|
||||
errno = ENAMETOOLONG;
|
||||
throw Symlink_resolve_error();
|
||||
return Symlink_resolve_error();
|
||||
}
|
||||
}
|
||||
symlink_resolved_in_this_iteration = true;
|
||||
@ -188,10 +189,12 @@ void Libc::resolve_symlinks(char const *path, Absolute_path &resolved_path)
|
||||
|
||||
resolved_path = next_iteration_working_path;
|
||||
resolved_path.remove_trailing('/');
|
||||
|
||||
return Symlinks_resolved_ok();
|
||||
}
|
||||
|
||||
|
||||
static void resolve_symlinks_except_last_element(char const *path, Absolute_path &resolved_path)
|
||||
static Symlink_resolve_result resolve_symlinks_except_last_element(char const *path, Absolute_path &resolved_path)
|
||||
{
|
||||
Absolute_path absolute_path_without_last_element(path, cwd().base());
|
||||
absolute_path_without_last_element.strip_last_element();
|
||||
@ -205,8 +208,10 @@ static void resolve_symlinks_except_last_element(char const *path, Absolute_path
|
||||
resolved_path.append_element(absolute_path_last_element.base());
|
||||
} catch (Path_base::Path_too_long) {
|
||||
errno = ENAMETOOLONG;
|
||||
throw Symlink_resolve_error();
|
||||
return Symlink_resolve_error();
|
||||
}
|
||||
|
||||
return Symlinks_resolved_ok();
|
||||
}
|
||||
|
||||
|
||||
@ -222,14 +227,14 @@ extern "C" int access(const char *path, int amode)
|
||||
if (path[0] == '\0')
|
||||
return Errno(ENOENT);
|
||||
|
||||
try {
|
||||
Absolute_path resolved_path;
|
||||
resolve_symlinks(path, resolved_path);
|
||||
FNAME_FUNC_WRAPPER(access, resolved_path.base(), amode);
|
||||
} catch (Symlink_resolve_error) {
|
||||
Absolute_path resolved_path;
|
||||
|
||||
if (resolve_symlinks(path, resolved_path).failed()) {
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
FNAME_FUNC_WRAPPER(access, resolved_path.base(), amode);
|
||||
}
|
||||
|
||||
|
||||
@ -408,14 +413,13 @@ extern "C" int lstat(const char *path, struct stat *buf)
|
||||
if (path[0] == '\0')
|
||||
return Errno(ENOENT);
|
||||
|
||||
try {
|
||||
Absolute_path resolved_path;
|
||||
resolve_symlinks_except_last_element(path, resolved_path);
|
||||
resolved_path.remove_trailing('/');
|
||||
FNAME_FUNC_WRAPPER(stat, resolved_path.base(), buf);
|
||||
} catch (Symlink_resolve_error) {
|
||||
Absolute_path resolved_path;
|
||||
|
||||
if (resolve_symlinks_except_last_element(path, resolved_path).failed())
|
||||
return -1;
|
||||
}
|
||||
|
||||
resolved_path.remove_trailing('/');
|
||||
FNAME_FUNC_WRAPPER(stat, resolved_path.base(), buf);
|
||||
}
|
||||
|
||||
|
||||
@ -427,14 +431,13 @@ extern "C" int mkdir(const char *path, mode_t mode)
|
||||
if (path[0] == '\0')
|
||||
return Errno(ENOENT);
|
||||
|
||||
try {
|
||||
Absolute_path resolved_path;
|
||||
resolve_symlinks_except_last_element(path, resolved_path);
|
||||
resolved_path.remove_trailing('/');
|
||||
FNAME_FUNC_WRAPPER(mkdir, resolved_path.base(), mode);
|
||||
} catch(Symlink_resolve_error) {
|
||||
Absolute_path resolved_path;
|
||||
|
||||
if (resolve_symlinks_except_last_element(path, resolved_path).failed())
|
||||
return -1;
|
||||
}
|
||||
|
||||
resolved_path.remove_trailing('/');
|
||||
FNAME_FUNC_WRAPPER(mkdir, resolved_path.base(), mode);
|
||||
}
|
||||
|
||||
|
||||
@ -550,17 +553,12 @@ __SYS_(int, open, (const char *pathname, int flags, ...),
|
||||
Plugin *plugin;
|
||||
File_descriptor *new_fdo;
|
||||
|
||||
try {
|
||||
resolve_symlinks_except_last_element(pathname, resolved_path);
|
||||
} catch (Symlink_resolve_error) {
|
||||
if (resolve_symlinks_except_last_element(pathname, resolved_path).failed())
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(flags & O_NOFOLLOW)) {
|
||||
/* resolve last element */
|
||||
try {
|
||||
resolve_symlinks(resolved_path.base(), resolved_path);
|
||||
} catch (Symlink_resolve_error) {
|
||||
if (resolve_symlinks(resolved_path.base(), resolved_path).failed()) {
|
||||
if (errno == ENOENT) {
|
||||
if (!(flags & O_CREAT))
|
||||
return -1;
|
||||
@ -671,13 +669,12 @@ extern "C" ssize_t readlink(const char *path, char *buf, ::size_t bufsiz)
|
||||
if (path[0] == '\0')
|
||||
return Errno(ENOENT);
|
||||
|
||||
try {
|
||||
Absolute_path resolved_path;
|
||||
resolve_symlinks_except_last_element(path, resolved_path);
|
||||
FNAME_FUNC_WRAPPER(readlink, resolved_path.base(), buf, bufsiz);
|
||||
} catch(Symlink_resolve_error) {
|
||||
Absolute_path resolved_path;
|
||||
|
||||
if (resolve_symlinks_except_last_element(path, resolved_path).failed())
|
||||
return -1;
|
||||
}
|
||||
|
||||
FNAME_FUNC_WRAPPER(readlink, resolved_path.base(), buf, bufsiz);
|
||||
}
|
||||
|
||||
|
||||
@ -689,18 +686,18 @@ extern "C" int rename(const char *oldpath, const char *newpath)
|
||||
if ((oldpath[0] == '\0') || (newpath[0] == '\0'))
|
||||
return Errno(ENOENT);
|
||||
|
||||
try {
|
||||
Absolute_path resolved_oldpath, resolved_newpath;
|
||||
resolve_symlinks_except_last_element(oldpath, resolved_oldpath);
|
||||
resolve_symlinks_except_last_element(newpath, resolved_newpath);
|
||||
Absolute_path resolved_oldpath, resolved_newpath;
|
||||
|
||||
resolved_oldpath.remove_trailing('/');
|
||||
resolved_newpath.remove_trailing('/');
|
||||
|
||||
FNAME_FUNC_WRAPPER(rename, resolved_oldpath.base(), resolved_newpath.base());
|
||||
} catch(Symlink_resolve_error) {
|
||||
if (resolve_symlinks_except_last_element(oldpath, resolved_oldpath).failed())
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (resolve_symlinks_except_last_element(newpath, resolved_newpath).failed())
|
||||
return -1;
|
||||
|
||||
resolved_oldpath.remove_trailing('/');
|
||||
resolved_newpath.remove_trailing('/');
|
||||
|
||||
FNAME_FUNC_WRAPPER(rename, resolved_oldpath.base(), resolved_newpath.base());
|
||||
}
|
||||
|
||||
|
||||
@ -712,25 +709,24 @@ extern "C" int rmdir(const char *path)
|
||||
if (path[0] == '\0')
|
||||
return Errno(ENOENT);
|
||||
|
||||
try {
|
||||
Absolute_path resolved_path;
|
||||
resolve_symlinks_except_last_element(path, resolved_path);
|
||||
resolved_path.remove_trailing('/');
|
||||
Absolute_path resolved_path;
|
||||
|
||||
struct stat stat_buf { };
|
||||
if (resolve_symlinks_except_last_element(path, resolved_path).failed())
|
||||
return -1;
|
||||
|
||||
if (stat(resolved_path.base(), &stat_buf) == -1)
|
||||
return -1;
|
||||
resolved_path.remove_trailing('/');
|
||||
|
||||
if (!S_ISDIR(stat_buf.st_mode)) {
|
||||
errno = ENOTDIR;
|
||||
return -1;
|
||||
}
|
||||
struct stat stat_buf { };
|
||||
|
||||
FNAME_FUNC_WRAPPER(rmdir, resolved_path.base());
|
||||
} catch(Symlink_resolve_error) {
|
||||
if (stat(resolved_path.base(), &stat_buf) == -1)
|
||||
return -1;
|
||||
|
||||
if (!S_ISDIR(stat_buf.st_mode)) {
|
||||
errno = ENOTDIR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
FNAME_FUNC_WRAPPER(rmdir, resolved_path.base());
|
||||
}
|
||||
|
||||
|
||||
@ -742,14 +738,13 @@ extern "C" int stat(const char *path, struct stat *buf)
|
||||
if (path[0] == '\0')
|
||||
return Errno(ENOENT);
|
||||
|
||||
try {
|
||||
Absolute_path resolved_path;
|
||||
resolve_symlinks(path, resolved_path);
|
||||
resolved_path.remove_trailing('/');
|
||||
FNAME_FUNC_WRAPPER(stat, resolved_path.base(), buf);
|
||||
} catch(Symlink_resolve_error) {
|
||||
Absolute_path resolved_path;
|
||||
|
||||
if (resolve_symlinks(path, resolved_path).failed())
|
||||
return -1;
|
||||
}
|
||||
|
||||
resolved_path.remove_trailing('/');
|
||||
FNAME_FUNC_WRAPPER(stat, resolved_path.base(), buf);
|
||||
}
|
||||
|
||||
|
||||
@ -761,13 +756,12 @@ extern "C" int symlink(const char *oldpath, const char *newpath)
|
||||
if ((oldpath[0] == '\0') || (newpath[0] == '\0'))
|
||||
return Errno(ENOENT);
|
||||
|
||||
try {
|
||||
Absolute_path resolved_path;
|
||||
resolve_symlinks_except_last_element(newpath, resolved_path);
|
||||
FNAME_FUNC_WRAPPER(symlink, oldpath, resolved_path.base());
|
||||
} catch(Symlink_resolve_error) {
|
||||
Absolute_path resolved_path;
|
||||
|
||||
if (resolve_symlinks_except_last_element(newpath, resolved_path).failed())
|
||||
return -1;
|
||||
}
|
||||
|
||||
FNAME_FUNC_WRAPPER(symlink, oldpath, resolved_path.base());
|
||||
}
|
||||
|
||||
|
||||
@ -779,13 +773,12 @@ extern "C" int unlink(const char *path)
|
||||
if (path[0] == '\0')
|
||||
return Errno(ENOENT);
|
||||
|
||||
try {
|
||||
Absolute_path resolved_path;
|
||||
resolve_symlinks_except_last_element(path, resolved_path);
|
||||
FNAME_FUNC_WRAPPER(unlink, resolved_path.base());
|
||||
} catch(Symlink_resolve_error) {
|
||||
Absolute_path resolved_path;
|
||||
|
||||
if (resolve_symlinks_except_last_element(path, resolved_path).failed())
|
||||
return -1;
|
||||
}
|
||||
|
||||
FNAME_FUNC_WRAPPER(unlink, resolved_path.base());
|
||||
}
|
||||
|
||||
|
||||
|
@ -28,7 +28,12 @@ namespace Libc {
|
||||
|
||||
typedef Genode::Path<PATH_MAX> Absolute_path;
|
||||
|
||||
void resolve_symlinks(char const *path, Absolute_path &resolved_path);
|
||||
struct Symlinks_resolved_ok { };
|
||||
struct Symlink_resolve_error { };
|
||||
using Symlink_resolve_result = Attempt<Symlinks_resolved_ok, Symlink_resolve_error>;
|
||||
|
||||
Symlink_resolve_result resolve_symlinks(char const *path,
|
||||
Absolute_path &resolved_path);
|
||||
}
|
||||
|
||||
#endif /* _LIBC__INTERNAL__FILE_OPERATIONS_H_ */
|
||||
|
@ -35,10 +35,6 @@ namespace Libc {
|
||||
|
||||
typedef Genode::Path<PATH_MAX> Absolute_path;
|
||||
|
||||
class Symlink_resolve_error : Genode::Exception { };
|
||||
|
||||
void resolve_symlinks(char const *path, Absolute_path &resolved_path);
|
||||
|
||||
class Plugin : public List<Plugin>::Element
|
||||
{
|
||||
protected:
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
/* libc-internal includes */
|
||||
#include <internal/kernel.h>
|
||||
#include <internal/file_operations.h>
|
||||
|
||||
Libc::Kernel * Libc::Kernel::_kernel_ptr;
|
||||
|
||||
@ -84,7 +85,8 @@ void Libc::Kernel::_init_file_descriptors()
|
||||
|
||||
} diag_guard { *this };
|
||||
|
||||
auto resolve_symlinks = [&] (Absolute_path next_iteration_working_path, Absolute_path &resolved_path)
|
||||
auto resolve_symlinks = [&] (Absolute_path next_iteration_working_path,
|
||||
Absolute_path &resolved_path) -> Symlink_resolve_result
|
||||
{
|
||||
char path_element[PATH_MAX];
|
||||
char symlink_target[PATH_MAX];
|
||||
@ -97,7 +99,7 @@ void Libc::Kernel::_init_file_descriptors()
|
||||
do {
|
||||
if (follow_count++ == FOLLOW_LIMIT) {
|
||||
errno = ELOOP;
|
||||
throw Symlink_resolve_error();
|
||||
return Symlink_resolve_error();
|
||||
}
|
||||
|
||||
current_iteration_working_path = next_iteration_working_path;
|
||||
@ -119,7 +121,7 @@ void Libc::Kernel::_init_file_descriptors()
|
||||
next_iteration_working_path.append_element(path_element);
|
||||
} catch (Path_base::Path_too_long) {
|
||||
errno = ENAMETOOLONG;
|
||||
throw Symlink_resolve_error();
|
||||
return Symlink_resolve_error();
|
||||
}
|
||||
|
||||
/*
|
||||
@ -130,13 +132,13 @@ void Libc::Kernel::_init_file_descriptors()
|
||||
struct stat stat_buf;
|
||||
int res = _vfs.stat_from_kernel(next_iteration_working_path.base(), &stat_buf);
|
||||
if (res == -1) {
|
||||
throw Symlink_resolve_error();
|
||||
return Symlink_resolve_error();
|
||||
}
|
||||
if (S_ISLNK(stat_buf.st_mode)) {
|
||||
res = readlink(next_iteration_working_path.base(),
|
||||
symlink_target, sizeof(symlink_target) - 1);
|
||||
if (res < 1)
|
||||
throw Symlink_resolve_error();
|
||||
return Symlink_resolve_error();
|
||||
|
||||
/* zero terminate target */
|
||||
symlink_target[res] = 0;
|
||||
@ -151,7 +153,7 @@ void Libc::Kernel::_init_file_descriptors()
|
||||
next_iteration_working_path.append_element(symlink_target);
|
||||
} catch (Path_base::Path_too_long) {
|
||||
errno = ENAMETOOLONG;
|
||||
throw Symlink_resolve_error();
|
||||
return Symlink_resolve_error();
|
||||
}
|
||||
}
|
||||
symlink_resolved_in_this_iteration = true;
|
||||
@ -165,21 +167,28 @@ void Libc::Kernel::_init_file_descriptors()
|
||||
|
||||
resolved_path = next_iteration_working_path;
|
||||
resolved_path.remove_trailing('/');
|
||||
|
||||
return Symlinks_resolved_ok();
|
||||
};
|
||||
|
||||
typedef String<Vfs::MAX_PATH_LEN> Path;
|
||||
|
||||
auto resolve_absolute_path = [&] (Path const &path)
|
||||
struct Absolute_path_resolved_ok { };
|
||||
struct Absolute_path_resolve_error { };
|
||||
using Absolute_path_resolve_result = Attempt<Absolute_path_resolved_ok,
|
||||
Absolute_path_resolve_error>;
|
||||
|
||||
auto resolve_absolute_path = [&] (Path const &path, Absolute_path &abs_path) -> Absolute_path_resolve_result
|
||||
{
|
||||
Absolute_path abs_path { };
|
||||
Absolute_path abs_dir(path.string(), _cwd.base()); abs_dir.strip_last_element();
|
||||
Absolute_path dir_entry(path.string(), _cwd.base()); dir_entry.keep_only_last_element();
|
||||
|
||||
try {
|
||||
resolve_symlinks(abs_dir, abs_path);
|
||||
if (resolve_symlinks(abs_dir, abs_path).failed())
|
||||
return Absolute_path_resolve_error();
|
||||
abs_path.append_element(dir_entry.string());
|
||||
return abs_path;
|
||||
} catch (Path_base::Path_too_long) { return Absolute_path(); }
|
||||
return Absolute_path_resolved_ok();
|
||||
} catch (Path_base::Path_too_long) { return Absolute_path_resolve_error(); }
|
||||
};
|
||||
|
||||
auto init_fd = [&] (Xml_node const &node, char const *attr,
|
||||
@ -189,55 +198,55 @@ void Libc::Kernel::_init_file_descriptors()
|
||||
return;
|
||||
|
||||
Path const attr_value { node.attribute_value(attr, Path()) };
|
||||
try {
|
||||
Absolute_path const path { resolve_absolute_path(attr_value) };
|
||||
|
||||
struct stat out_stat { };
|
||||
if (_vfs.stat_from_kernel(path.string(), &out_stat) != 0) {
|
||||
warning("failed to call 'stat' on ", path);
|
||||
diag_guard.show = true;
|
||||
return;
|
||||
}
|
||||
Absolute_path path { };
|
||||
|
||||
File_descriptor *fd =
|
||||
_vfs.open_from_kernel(path.string(), flags, libc_fd);
|
||||
|
||||
if (!fd)
|
||||
return;
|
||||
|
||||
if (fd->libc_fd != libc_fd) {
|
||||
error("could not allocate fd ",libc_fd," for ",path,", "
|
||||
"got fd ",fd->libc_fd);
|
||||
_vfs.close_from_kernel(fd);
|
||||
diag_guard.show = true;
|
||||
return;
|
||||
}
|
||||
|
||||
fd->cloexec = node.attribute_value("cloexec", false);
|
||||
|
||||
/*
|
||||
* We need to manually register the path. Normally this is done
|
||||
* by '_open'. But we call the local 'open' function directly
|
||||
* because we want to explicitly specify the libc fd ID.
|
||||
*/
|
||||
if (fd->fd_path)
|
||||
warning("may leak former FD path memory");
|
||||
|
||||
{
|
||||
char *dst = (char *)_heap.alloc(path.max_len());
|
||||
copy_cstring(dst, path.string(), path.max_len());
|
||||
fd->fd_path = dst;
|
||||
}
|
||||
|
||||
::off_t const seek = node.attribute_value("seek", 0ULL);
|
||||
if (seek)
|
||||
_vfs.lseek_from_kernel(fd, seek);
|
||||
|
||||
} catch (Symlink_resolve_error) {
|
||||
warning("failed to resolve path for ", attr_value);
|
||||
if (resolve_absolute_path(attr_value, path).failed()) {
|
||||
warning("failed to resolve path for ", path);
|
||||
diag_guard.show = true;
|
||||
return;
|
||||
}
|
||||
|
||||
struct stat out_stat { };
|
||||
if (_vfs.stat_from_kernel(path.string(), &out_stat) != 0) {
|
||||
warning("failed to call 'stat' on ", path);
|
||||
diag_guard.show = true;
|
||||
return;
|
||||
}
|
||||
|
||||
File_descriptor *fd =
|
||||
_vfs.open_from_kernel(path.string(), flags, libc_fd);
|
||||
|
||||
if (!fd)
|
||||
return;
|
||||
|
||||
if (fd->libc_fd != libc_fd) {
|
||||
error("could not allocate fd ",libc_fd," for ",path,", "
|
||||
"got fd ",fd->libc_fd);
|
||||
_vfs.close_from_kernel(fd);
|
||||
diag_guard.show = true;
|
||||
return;
|
||||
}
|
||||
|
||||
fd->cloexec = node.attribute_value("cloexec", false);
|
||||
|
||||
/*
|
||||
* We need to manually register the path. Normally this is done
|
||||
* by '_open'. But we call the local 'open' function directly
|
||||
* because we want to explicitly specify the libc fd ID.
|
||||
*/
|
||||
if (fd->fd_path)
|
||||
warning("may leak former FD path memory");
|
||||
|
||||
{
|
||||
char *dst = (char *)_heap.alloc(path.max_len());
|
||||
copy_cstring(dst, path.string(), path.max_len());
|
||||
fd->fd_path = dst;
|
||||
}
|
||||
|
||||
::off_t const seek = node.attribute_value("seek", 0ULL);
|
||||
if (seek)
|
||||
_vfs.lseek_from_kernel(fd, seek);
|
||||
};
|
||||
|
||||
if (_vfs.root_dir_has_dirents()) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user