mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-12-03 04:12:10 +08:00
Add new "alias" command.
* NEWS: Mention new command. * command.h (valid_user_defined_cmd_name_p): Declare. * defs.h (make_cleanup_dyn_string_delete): Declare. * utils.c: #include "dyn-string.h". (do_dyn_string_delete, make_cleanup_dyn_string_delete): New functions. * cli/cli-cmds.c: #include "dyn-string.h". (argv_to_dyn_string, valid_command_p, alias_command): New functions. (init_cli_cmds): Add new command. * cli/cli-decode.c (valid_user_defined_cmd_name_p): New function. doc/ * gdb.texinfo (Extending GDB): Document alias command. testsuite/ * gdb.base/alias.exp: Add tests for alias command.
This commit is contained in:
parent
509f0fd941
commit
5a56e9c5e9
@ -1,3 +1,16 @@
|
||||
2011-10-09 Doug Evans <dje@google.com>
|
||||
|
||||
Add new "alias" command.
|
||||
* NEWS: Mention new command.
|
||||
* command.h (valid_user_defined_cmd_name_p): Declare.
|
||||
* defs.h (make_cleanup_dyn_string_delete): Declare.
|
||||
* utils.c: #include "dyn-string.h".
|
||||
(do_dyn_string_delete, make_cleanup_dyn_string_delete): New functions.
|
||||
* cli/cli-cmds.c: #include "dyn-string.h".
|
||||
(argv_to_dyn_string, valid_command_p, alias_command): New functions.
|
||||
(init_cli_cmds): Add new command.
|
||||
* cli/cli-decode.c (valid_user_defined_cmd_name_p): New function.
|
||||
|
||||
2011-10-09 Jan Kratochvil <jan.kratochvil@redhat.com>
|
||||
|
||||
Fix compatibility with older GCCs.
|
||||
|
3
gdb/NEWS
3
gdb/NEWS
@ -81,7 +81,8 @@
|
||||
the first connection is made. The listening port used by GDBserver will
|
||||
become available after that.
|
||||
|
||||
* New commands "info macros", and "info definitions" have been added.
|
||||
* New commands "info macros", "info definitions",
|
||||
and "alias" have been added.
|
||||
|
||||
* New function parameters suffix @entry specifies value of function parameter
|
||||
at the time the function got called. Entry values are available only since
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "defs.h"
|
||||
#include "exceptions.h"
|
||||
#include "arch-utils.h"
|
||||
#include "dyn-string.h"
|
||||
#include "readline/readline.h"
|
||||
#include "readline/tilde.h"
|
||||
#include "completer.h"
|
||||
@ -1272,6 +1273,180 @@ apropos_command (char *searchstr, int from_tty)
|
||||
error (_("Error in regular expression: %s"), err);
|
||||
}
|
||||
}
|
||||
|
||||
/* Subroutine of alias_command to simplify it.
|
||||
Return the first N elements of ARGV flattened back to a string
|
||||
with a space separating each element.
|
||||
ARGV may not be NULL.
|
||||
This does not take care of quoting elements in case they contain spaces
|
||||
on purpose. */
|
||||
|
||||
static dyn_string_t
|
||||
argv_to_dyn_string (char **argv, int n)
|
||||
{
|
||||
int i;
|
||||
dyn_string_t result = dyn_string_new (10);
|
||||
|
||||
gdb_assert (argv != NULL);
|
||||
gdb_assert (n >= 0 && n <= countargv (argv));
|
||||
|
||||
for (i = 0; i < n; ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
dyn_string_append_char (result, ' ');
|
||||
dyn_string_append_cstr (result, argv[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Subroutine of alias_command to simplify it.
|
||||
Return TRUE if COMMAND exists, unambiguously. Otherwise FALSE. */
|
||||
|
||||
static int
|
||||
valid_command_p (char *command)
|
||||
{
|
||||
struct cmd_list_element *c;
|
||||
|
||||
c = lookup_cmd_1 (& command, cmdlist, NULL, 1);
|
||||
|
||||
if (c == NULL || c == (struct cmd_list_element *) -1)
|
||||
return FALSE;
|
||||
|
||||
/* This is the slightly tricky part.
|
||||
lookup_cmd_1 will return a pointer to the last part of COMMAND
|
||||
to match, leaving COMMAND pointing at the remainder. */
|
||||
while (*command == ' ' || *command == '\t')
|
||||
++command;
|
||||
return *command == '\0';
|
||||
}
|
||||
|
||||
/* Make an alias of an existing command. */
|
||||
|
||||
static void
|
||||
alias_command (char *args, int from_tty)
|
||||
{
|
||||
int i, alias_argc, command_argc;
|
||||
int abbrev_flag = 0;
|
||||
char *args2, *equals, *alias, *command;
|
||||
char **alias_argv, **command_argv;
|
||||
dyn_string_t alias_dyn_string, command_dyn_string;
|
||||
struct cmd_list_element *c;
|
||||
static const char usage[] = N_("Usage: alias [-a] [--] ALIAS = COMMAND");
|
||||
|
||||
if (args == NULL || strchr (args, '=') == NULL)
|
||||
error (_(usage));
|
||||
|
||||
args2 = xstrdup (args);
|
||||
make_cleanup (xfree, args2);
|
||||
equals = strchr (args2, '=');
|
||||
*equals = '\0';
|
||||
alias_argv = gdb_buildargv (args2);
|
||||
make_cleanup_freeargv (alias_argv);
|
||||
command_argv = gdb_buildargv (equals + 1);
|
||||
make_cleanup_freeargv (command_argv);
|
||||
|
||||
for (i = 0; alias_argv[i] != NULL; )
|
||||
{
|
||||
if (strcmp (alias_argv[i], "-a") == 0)
|
||||
{
|
||||
++alias_argv;
|
||||
abbrev_flag = 1;
|
||||
}
|
||||
else if (strcmp (alias_argv[i], "--") == 0)
|
||||
{
|
||||
++alias_argv;
|
||||
break;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (alias_argv[0] == NULL || command_argv[0] == NULL
|
||||
|| *alias_argv[0] == '\0' || *command_argv[0] == '\0')
|
||||
error (_(usage));
|
||||
|
||||
for (i = 0; alias_argv[i] != NULL; ++i)
|
||||
{
|
||||
if (! valid_user_defined_cmd_name_p (alias_argv[i]))
|
||||
{
|
||||
if (i == 0)
|
||||
error (_("Invalid command name: %s"), alias_argv[i]);
|
||||
else
|
||||
error (_("Invalid command element name: %s"), alias_argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
alias_argc = countargv (alias_argv);
|
||||
command_argc = countargv (command_argv);
|
||||
|
||||
/* COMMAND must exist.
|
||||
Reconstruct the command to remove any extraneous spaces,
|
||||
for better error messages. */
|
||||
command_dyn_string = argv_to_dyn_string (command_argv, command_argc);
|
||||
make_cleanup_dyn_string_delete (command_dyn_string);
|
||||
command = dyn_string_buf (command_dyn_string);
|
||||
if (! valid_command_p (command))
|
||||
error (_("Invalid command to alias to: %s"), command);
|
||||
|
||||
/* ALIAS must not exist. */
|
||||
alias_dyn_string = argv_to_dyn_string (alias_argv, alias_argc);
|
||||
make_cleanup_dyn_string_delete (alias_dyn_string);
|
||||
alias = dyn_string_buf (alias_dyn_string);
|
||||
if (valid_command_p (alias))
|
||||
error (_("Alias already exists: %s"), alias);
|
||||
|
||||
/* If ALIAS is one word, it is an alias for the entire COMMAND.
|
||||
Example: alias spe = set print elements
|
||||
|
||||
Otherwise ALIAS and COMMAND must have the same number of words,
|
||||
and every word except the last must match; and the last word of
|
||||
ALIAS is made an alias of the last word of COMMAND.
|
||||
Example: alias set print elms = set pr elem
|
||||
Note that unambiguous abbreviations are allowed. */
|
||||
|
||||
if (alias_argc == 1)
|
||||
{
|
||||
/* add_cmd requires *we* allocate space for name, hence the xstrdup. */
|
||||
add_com_alias (xstrdup (alias_argv[0]), command, class_alias,
|
||||
abbrev_flag);
|
||||
}
|
||||
else
|
||||
{
|
||||
int i;
|
||||
dyn_string_t alias_prefix_dyn_string, command_prefix_dyn_string;
|
||||
char *alias_prefix, *command_prefix;
|
||||
struct cmd_list_element *c_alias, *c_command;
|
||||
|
||||
if (alias_argc != command_argc)
|
||||
error (_("Mismatched command length between ALIAS and COMMAND."));
|
||||
|
||||
/* Create copies of ALIAS and COMMAND without the last word,
|
||||
and use that to verify the leading elements match. */
|
||||
alias_prefix_dyn_string =
|
||||
argv_to_dyn_string (alias_argv, alias_argc - 1);
|
||||
make_cleanup_dyn_string_delete (alias_prefix_dyn_string);
|
||||
command_prefix_dyn_string =
|
||||
argv_to_dyn_string (alias_argv, command_argc - 1);
|
||||
make_cleanup_dyn_string_delete (command_prefix_dyn_string);
|
||||
alias_prefix = dyn_string_buf (alias_prefix_dyn_string);
|
||||
command_prefix = dyn_string_buf (command_prefix_dyn_string);
|
||||
|
||||
c_command = lookup_cmd_1 (& command_prefix, cmdlist, NULL, 1);
|
||||
/* We've already tried to look up COMMAND. */
|
||||
gdb_assert (c_command != NULL
|
||||
&& c_command != (struct cmd_list_element *) -1);
|
||||
gdb_assert (c_command->prefixlist != NULL);
|
||||
c_alias = lookup_cmd_1 (& alias_prefix, cmdlist, NULL, 1);
|
||||
if (c_alias != c_command)
|
||||
error (_("ALIAS and COMMAND prefixes do not match."));
|
||||
|
||||
/* add_cmd requires *we* allocate space for name, hence the xstrdup. */
|
||||
add_alias_cmd (xstrdup (alias_argv[alias_argc - 1]),
|
||||
command_argv[command_argc - 1],
|
||||
class_alias, abbrev_flag, c_command->prefixlist);
|
||||
}
|
||||
}
|
||||
|
||||
/* Print a list of files and line numbers which a user may choose from
|
||||
in order to list a function which was specified ambiguously (as
|
||||
@ -1674,4 +1849,18 @@ When 'on', each command is displayed as it is executed."),
|
||||
NULL,
|
||||
NULL,
|
||||
&setlist, &showlist);
|
||||
|
||||
c = add_com ("alias", class_support, alias_command, _("\
|
||||
Define a new command that is an alias of an existing command.\n\
|
||||
Usage: alias [-a] [--] ALIAS = COMMAND\n\
|
||||
ALIAS is the name of the alias command to create.\n\
|
||||
COMMAND is the command being aliased to.\n\
|
||||
If \"-a\" is specified, the command is an abbreviation,\n\
|
||||
and will not appear in help command list output.\n\
|
||||
\n\
|
||||
Examples:\n\
|
||||
Make \"spe\" an alias of \"set print elements\":\n\
|
||||
alias spe = set print elements\n\
|
||||
Make \"elms\" an alias of \"elements\" in the \"set print\" command:\n\
|
||||
alias -a set print elms = set print elements"));
|
||||
}
|
||||
|
@ -126,7 +126,6 @@ set_cmd_completer (struct cmd_list_element *cmd,
|
||||
cmd->completer = completer; /* Ok. */
|
||||
}
|
||||
|
||||
|
||||
/* Add element named NAME.
|
||||
Space for NAME and DOC must be allocated by the caller.
|
||||
CLASS is the top level category into which commands are broken down
|
||||
@ -1138,6 +1137,34 @@ find_command_name_length (const char *text)
|
||||
return p - text;
|
||||
}
|
||||
|
||||
/* Return TRUE if NAME is a valid user-defined command name.
|
||||
This is a stricter subset of all gdb commands,
|
||||
see find_command_name_length. */
|
||||
|
||||
int
|
||||
valid_user_defined_cmd_name_p (const char *name)
|
||||
{
|
||||
const char *p;
|
||||
|
||||
if (*name == '\0')
|
||||
return FALSE;
|
||||
|
||||
/* Alas "42" is a legitimate user-defined command.
|
||||
In the interests of not breaking anything we preserve that. */
|
||||
|
||||
for (p = name; *p != '\0'; ++p)
|
||||
{
|
||||
if (isalnum (*p)
|
||||
|| *p == '-'
|
||||
|| *p == '_')
|
||||
; /* Ok. */
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* This routine takes a line of TEXT and a CLIST in which to start the
|
||||
lookup. When it returns it will have incremented the text pointer past
|
||||
the section of text it matched, set *RESULT_LIST to point to the list in
|
||||
|
@ -106,6 +106,8 @@ struct cmd_list_element;
|
||||
|
||||
/* Forward-declarations of the entry-points of cli/cli-decode.c. */
|
||||
|
||||
extern int valid_user_defined_cmd_name_p (const char *name);
|
||||
|
||||
extern struct cmd_list_element *add_cmd (char *, enum command_class,
|
||||
void (*fun) (char *, int), char *,
|
||||
struct cmd_list_element **);
|
||||
|
@ -340,6 +340,9 @@ extern struct cleanup *make_cleanup_dtor (make_cleanup_ftype *, void *,
|
||||
|
||||
extern struct cleanup *make_cleanup_freeargv (char **);
|
||||
|
||||
struct dyn_string;
|
||||
extern struct cleanup *make_cleanup_dyn_string_delete (struct dyn_string *);
|
||||
|
||||
struct ui_file;
|
||||
extern struct cleanup *make_cleanup_ui_file_delete (struct ui_file *);
|
||||
|
||||
|
@ -1,3 +1,7 @@
|
||||
2011-10-09 Doug Evans <dje@google.com>
|
||||
|
||||
* gdb.texinfo (Extending GDB): Document alias command.
|
||||
|
||||
2011-10-09 Jan Kratochvil <jan.kratochvil@redhat.com>
|
||||
|
||||
Support @entry in input expressions.
|
||||
|
@ -20609,11 +20609,12 @@ Displays whether the debugger is operating in interactive mode or not.
|
||||
@chapter Extending @value{GDBN}
|
||||
@cindex extending GDB
|
||||
|
||||
@value{GDBN} provides two mechanisms for extension. The first is based
|
||||
on composition of @value{GDBN} commands, and the second is based on the
|
||||
Python scripting language.
|
||||
@value{GDBN} provides three mechanisms for extension. The first is based
|
||||
on composition of @value{GDBN} commands, the second is based on the
|
||||
Python scripting language, and the third is for defining new aliases of
|
||||
existing commands.
|
||||
|
||||
To facilitate the use of these extensions, @value{GDBN} is capable
|
||||
To facilitate the use of the first two extensions, @value{GDBN} is capable
|
||||
of evaluating the contents of a file. When doing so, @value{GDBN}
|
||||
can recognize which scripting language is being used by looking at
|
||||
the filename extension. Files with an unrecognized filename extension
|
||||
@ -20648,6 +20649,7 @@ Display the current value of the @code{script-extension} option.
|
||||
@menu
|
||||
* Sequences:: Canned Sequences of Commands
|
||||
* Python:: Scripting @value{GDBN} using Python
|
||||
* Aliases:: Creating new spellings of existing commands
|
||||
@end menu
|
||||
|
||||
@node Sequences
|
||||
@ -24386,6 +24388,95 @@ substitute_prompt (``frame: \f,
|
||||
@end smallexample
|
||||
@end table
|
||||
|
||||
@node Aliases
|
||||
@section Creating new spellings of existing commands
|
||||
@cindex aliases for commands
|
||||
|
||||
It is often useful to define alternate spellings of existing commands.
|
||||
For example, if a new @value{GDBN} command defined in Python has
|
||||
a long name to type, it is handy to have an abbreviated version of it
|
||||
that involves less typing.
|
||||
|
||||
@value{GDBN} itself uses aliases. For example @samp{s} is an alias
|
||||
of the @samp{step} command even though it is otherwise an ambiguous
|
||||
abbreviation of other commands like @samp{set} and @samp{show}.
|
||||
|
||||
Aliases are also used to provide shortened or more common versions
|
||||
of multi-word commands. For example, @value{GDBN} provides the
|
||||
@samp{tty} alias of the @samp{set inferior-tty} command.
|
||||
|
||||
You can define a new alias with the @samp{alias} command.
|
||||
|
||||
@table @code
|
||||
|
||||
@kindex alias
|
||||
@item alias [-a] [--] @var{ALIAS} = @var{COMMAND}
|
||||
|
||||
@end table
|
||||
|
||||
@var{ALIAS} specifies the name of the new alias.
|
||||
Each word of @var{ALIAS} must consist of letters, numbers, dashes and
|
||||
underscores.
|
||||
|
||||
@var{COMMAND} specifies the name of an existing command
|
||||
that is being aliased.
|
||||
|
||||
The @samp{-a} option specifies that the new alias is an abbreviation
|
||||
of the command. Abbreviations are not shown in command
|
||||
lists displayed by the @samp{help} command.
|
||||
|
||||
The @samp{--} option specifies the end of options,
|
||||
and is useful when @var{ALIAS} begins with a dash.
|
||||
|
||||
Here is a simple example showing how to make an abbreviation
|
||||
of a command so that there is less to type.
|
||||
Suppose you were tired of typing @samp{disas}, the current
|
||||
shortest unambiguous abbreviation of the @samp{disassemble} command
|
||||
and you wanted an even shorter version named @samp{di}.
|
||||
The following will accomplish this.
|
||||
|
||||
@smallexample
|
||||
(gdb) alias -a di = disas
|
||||
@end smallexample
|
||||
|
||||
Note that aliases are different from user-defined commands.
|
||||
With a user-defined command, you also need to write documentation
|
||||
for it with the @samp{document} command.
|
||||
An alias automatically picks up the documentation of the existing command.
|
||||
|
||||
Here is an example where we make @samp{elms} an abbreviation of
|
||||
@samp{elements} in the @samp{set print elements} command.
|
||||
This is to show that you can make an abbreviation of any part
|
||||
of a command.
|
||||
|
||||
@smallexample
|
||||
(gdb) alias -a set print elms = set print elements
|
||||
(gdb) alias -a show print elms = show print elements
|
||||
(gdb) set p elms 20
|
||||
(gdb) show p elms
|
||||
Limit on string chars or array elements to print is 200.
|
||||
@end smallexample
|
||||
|
||||
Note that if you are defining an alias of a @samp{set} command,
|
||||
and you want to have an alias for the corresponding @samp{show}
|
||||
command, then you need to define the latter separately.
|
||||
|
||||
Unambiguously abbreviated commands are allowed in @var{COMMAND} and
|
||||
@var{ALIAS}, just as they are normally.
|
||||
|
||||
@smallexample
|
||||
(gdb) alias -a set pr elms = set p ele
|
||||
@end smallexample
|
||||
|
||||
Finally, here is an example showing the creation of a one word
|
||||
alias for a more complex command.
|
||||
This creates alias @samp{spe} of the command @samp{set print elements}.
|
||||
|
||||
@smallexample
|
||||
(gdb) alias spe = set print elements
|
||||
(gdb) spe 20
|
||||
@end smallexample
|
||||
|
||||
@node Interpreters
|
||||
@chapter Command Interpreters
|
||||
@cindex command interpreters
|
||||
|
@ -1,3 +1,7 @@
|
||||
2011-10-09 Doug Evans <dje@google.com>
|
||||
|
||||
* gdb.base/alias.exp: Add tests for alias command.
|
||||
|
||||
2011-10-09 Jan Kratochvil <jan.kratochvil@redhat.com>
|
||||
|
||||
* gdb.arch/amd64-entry-value.s: New file.
|
||||
|
68
gdb/testsuite/gdb.base/alias.exp
Normal file
68
gdb/testsuite/gdb.base/alias.exp
Normal file
@ -0,0 +1,68 @@
|
||||
# Test the alias command.
|
||||
# Copyright 2011 Free Software Foundation, Inc.
|
||||
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
gdb_exit
|
||||
gdb_start
|
||||
gdb_reinitialize_dir $srcdir/$subdir
|
||||
|
||||
# Helper to test the -a option to alias.
|
||||
# Aliases that are abbreviations of commands (e.g. r -> run)
|
||||
# do not appear in help output.
|
||||
|
||||
proc test_abbrev_not_present { alias_name } {
|
||||
global gdb_prompt
|
||||
set alias_present 0
|
||||
set test_name "abbrev $alias_name not present in help command list"
|
||||
gdb_test_multiple "help aliases" $test_name {
|
||||
-re "\[\r\n\]$alias_name \[^\r\n\]*" {
|
||||
set alias_present 1
|
||||
exp_continue
|
||||
}
|
||||
-re ".*$gdb_prompt $" {
|
||||
if { !$alias_present } then {
|
||||
pass $test_name
|
||||
} else {
|
||||
fail $test_name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc test_abbrev_alias { name gdb_command test_value } {
|
||||
gdb_test_no_output $gdb_command
|
||||
gdb_test_no_output "$name print elements $test_value"
|
||||
gdb_test "show print elements" "Limit .* is $test_value\[.\]" "verify $name"
|
||||
test_abbrev_not_present $name
|
||||
}
|
||||
|
||||
test_abbrev_alias set2 "alias -a set2=set" 42
|
||||
test_abbrev_alias set3 "alias -a set3= set" 43
|
||||
test_abbrev_alias set4 "alias -a set4 =set" 44
|
||||
test_abbrev_alias set5 "alias -a set5 = set" 45
|
||||
test_abbrev_alias set6 "alias -a -- set6 = set" 46
|
||||
test_abbrev_alias -a "alias -a -- -a = set" 47
|
||||
|
||||
gdb_test "alias set2=set" "already exists: set2"
|
||||
gdb_test "alias foo=bar" "Invalid command to alias to: bar"
|
||||
|
||||
gdb_test_no_output "alias spe = set p elem"
|
||||
gdb_test_no_output "spe 50"
|
||||
gdb_test "show print elements" "Limit .* is 50\[.\]" "verify spe"
|
||||
|
||||
gdb_test_no_output "alias set pr elms = set p elem"
|
||||
gdb_test_no_output "set pr elms 51"
|
||||
gdb_test "show print elements" "Limit .* is 51\[.\]" "verify set pr elms"
|
||||
gdb_test "help set print" "set print elms .*"
|
13
gdb/utils.c
13
gdb/utils.c
@ -20,6 +20,7 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include "defs.h"
|
||||
#include "dyn-string.h"
|
||||
#include "gdb_assert.h"
|
||||
#include <ctype.h>
|
||||
#include "gdb_string.h"
|
||||
@ -237,6 +238,18 @@ make_cleanup_freeargv (char **arg)
|
||||
return make_my_cleanup (&cleanup_chain, do_freeargv, arg);
|
||||
}
|
||||
|
||||
static void
|
||||
do_dyn_string_delete (void *arg)
|
||||
{
|
||||
dyn_string_delete ((dyn_string_t) arg);
|
||||
}
|
||||
|
||||
struct cleanup *
|
||||
make_cleanup_dyn_string_delete (dyn_string_t arg)
|
||||
{
|
||||
return make_my_cleanup (&cleanup_chain, do_dyn_string_delete, arg);
|
||||
}
|
||||
|
||||
static void
|
||||
do_bfd_close_cleanup (void *arg)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user