mirror of
https://github.com/systemd/systemd.git
synced 2025-08-29 01:49:55 +03:00
Merge pull request #28761 from esposem/cmdline_manager
bootctl: handle UKI cmdline addons
This commit is contained in:
@ -92,6 +92,7 @@ static int status_entries(
|
||||
|
||||
r = show_boot_entry(
|
||||
boot_config_default_entry(config),
|
||||
&config->global_addons,
|
||||
/* show_as_default= */ false,
|
||||
/* show_as_selected= */ false,
|
||||
/* show_discovered= */ false);
|
||||
|
@ -43,6 +43,15 @@ static const char* const boot_entry_type_json_table[_BOOT_ENTRY_TYPE_MAX] = {
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type_json, BootEntryType);
|
||||
|
||||
BootEntryAddon* boot_entry_addon_free(BootEntryAddon *addon) {
|
||||
if (!addon)
|
||||
return NULL;
|
||||
|
||||
free(addon->location);
|
||||
free(addon->cmdline);
|
||||
return mfree(addon);
|
||||
}
|
||||
|
||||
static void boot_entry_free(BootEntry *entry) {
|
||||
assert(entry);
|
||||
|
||||
@ -57,6 +66,7 @@ static void boot_entry_free(BootEntry *entry) {
|
||||
free(entry->machine_id);
|
||||
free(entry->architecture);
|
||||
strv_free(entry->options);
|
||||
free(entry->local_addons.items);
|
||||
free(entry->kernel);
|
||||
free(entry->efi);
|
||||
strv_free(entry->initrd);
|
||||
@ -435,6 +445,7 @@ void boot_config_free(BootConfig *config) {
|
||||
for (size_t i = 0; i < config->n_entries; i++)
|
||||
boot_entry_free(config->entries + i);
|
||||
free(config->entries);
|
||||
free(config->global_addons.items);
|
||||
|
||||
set_free(config->inodes_seen);
|
||||
}
|
||||
@ -753,13 +764,12 @@ static int boot_entry_load_unified(
|
||||
static int find_sections(
|
||||
int fd,
|
||||
const char *path,
|
||||
char **ret_osrelease,
|
||||
char **ret_cmdline) {
|
||||
IMAGE_SECTION_HEADER **ret_sections,
|
||||
PeHeader **ret_pe_header) {
|
||||
|
||||
_cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
|
||||
_cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
|
||||
_cleanup_free_ char *osrel = NULL, *cmdline = NULL;
|
||||
_cleanup_free_ PeHeader *pe_header = NULL;
|
||||
IMAGE_SECTION_HEADER *sections;
|
||||
PeHeader *pe_header;
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
@ -773,25 +783,217 @@ static int find_sections(
|
||||
if (r < 0)
|
||||
return log_warning_errno(r, "Failed to parse PE sections of '%s': %m", path);
|
||||
|
||||
if (!pe_is_uki(pe_header, sections))
|
||||
return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Parsed PE file '%s' is not a UKI.", path);
|
||||
if (ret_pe_header)
|
||||
*ret_pe_header = TAKE_PTR(pe_header);
|
||||
if (ret_sections)
|
||||
*ret_sections = TAKE_PTR(sections);
|
||||
|
||||
r = pe_read_section_data(fd, pe_header, sections, ".osrel", PE_SECTION_SIZE_MAX, (void**) &osrel, NULL);
|
||||
if (r < 0)
|
||||
return log_warning_errno(r, "Failed to read .osrel section of '%s': %m", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int find_cmdline_section(
|
||||
int fd,
|
||||
const char *path,
|
||||
IMAGE_SECTION_HEADER *sections,
|
||||
PeHeader *pe_header,
|
||||
char **ret_cmdline) {
|
||||
|
||||
int r;
|
||||
char *cmdline = NULL;
|
||||
|
||||
if (!ret_cmdline)
|
||||
return 0;
|
||||
|
||||
r = pe_read_section_data(fd, pe_header, sections, ".cmdline", PE_SECTION_SIZE_MAX, (void**) &cmdline, NULL);
|
||||
if (r < 0 && r != -ENXIO) /* cmdline is optional */
|
||||
if (r == -ENXIO) /* cmdline is optional */
|
||||
*ret_cmdline = NULL;
|
||||
else if (r < 0)
|
||||
return log_warning_errno(r, "Failed to read .cmdline section of '%s': %m", path);
|
||||
|
||||
if (ret_osrelease)
|
||||
*ret_osrelease = TAKE_PTR(osrel);
|
||||
if (ret_cmdline)
|
||||
else if (ret_cmdline)
|
||||
*ret_cmdline = TAKE_PTR(cmdline);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int find_osrel_section(
|
||||
int fd,
|
||||
const char *path,
|
||||
IMAGE_SECTION_HEADER *sections,
|
||||
PeHeader *pe_header,
|
||||
char **ret_osrelease) {
|
||||
|
||||
int r;
|
||||
|
||||
if (!ret_osrelease)
|
||||
return 0;
|
||||
|
||||
r = pe_read_section_data(fd, pe_header, sections, ".osrel", PE_SECTION_SIZE_MAX, (void**) ret_osrelease, NULL);
|
||||
if (r < 0)
|
||||
return log_warning_errno(r, "Failed to read .osrel section of '%s': %m", path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int find_uki_sections(
|
||||
int fd,
|
||||
const char *path,
|
||||
char **ret_osrelease,
|
||||
char **ret_cmdline) {
|
||||
|
||||
_cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
|
||||
_cleanup_free_ PeHeader *pe_header = NULL;
|
||||
int r;
|
||||
|
||||
r = find_sections(fd, path, §ions, &pe_header);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (!pe_is_uki(pe_header, sections))
|
||||
return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Parsed PE file '%s' is not a UKI.", path);
|
||||
|
||||
r = find_osrel_section(fd, path, sections, pe_header, ret_osrelease);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = find_cmdline_section(fd, path, sections, pe_header, ret_cmdline);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int find_addon_sections(
|
||||
int fd,
|
||||
const char *path,
|
||||
char **ret_cmdline) {
|
||||
|
||||
_cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
|
||||
_cleanup_free_ PeHeader *pe_header = NULL;
|
||||
int r;
|
||||
|
||||
r = find_sections(fd, path, §ions, &pe_header);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return find_cmdline_section(fd, path, sections, pe_header, ret_cmdline);
|
||||
}
|
||||
|
||||
static int insert_boot_entry_addon(
|
||||
BootEntryAddons *addons,
|
||||
char *location,
|
||||
char *cmdline) {
|
||||
|
||||
if (!GREEDY_REALLOC(addons->items, addons->count + 1))
|
||||
return log_oom();
|
||||
|
||||
addons->items[addons->count] = (BootEntryAddon) {
|
||||
.location = location,
|
||||
.cmdline = cmdline,
|
||||
};
|
||||
addons->count++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int boot_entries_find_unified_addons(
|
||||
BootConfig *config,
|
||||
int d_fd,
|
||||
const char *addon_dir,
|
||||
const char *root,
|
||||
BootEntryAddons *addons) {
|
||||
|
||||
_cleanup_closedir_ DIR *d = NULL;
|
||||
_cleanup_free_ char *full = NULL;
|
||||
int r;
|
||||
|
||||
assert(addons);
|
||||
assert(config);
|
||||
|
||||
r = chase_and_opendirat(d_fd, addon_dir, CHASE_AT_RESOLVE_IN_ROOT, &full, &d);
|
||||
if (r == -ENOENT)
|
||||
return 0;
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to open '%s/%s': %m", root, addon_dir);
|
||||
|
||||
*addons = (BootEntryAddons) {};
|
||||
|
||||
FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) {
|
||||
_cleanup_free_ char *j = NULL, *cmdline = NULL, *location = NULL;
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
|
||||
if (!dirent_is_file(de))
|
||||
continue;
|
||||
|
||||
if (!endswith_no_case(de->d_name, ".addon.efi"))
|
||||
continue;
|
||||
|
||||
fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY);
|
||||
if (fd < 0) {
|
||||
log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
r = config_check_inode_relevant_and_unseen(config, fd, de->d_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0) /* inode already seen or otherwise not relevant */
|
||||
continue;
|
||||
|
||||
j = path_join(full, de->d_name);
|
||||
if (!j)
|
||||
return log_oom();
|
||||
|
||||
if (find_addon_sections(fd, j, &cmdline) < 0)
|
||||
continue;
|
||||
|
||||
location = strdup(j);
|
||||
if (!location)
|
||||
return log_oom();
|
||||
|
||||
r = insert_boot_entry_addon(addons, TAKE_PTR(location), TAKE_PTR(cmdline));
|
||||
if (r < 0) {
|
||||
free(addons);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int boot_entries_find_unified_global_addons(
|
||||
BootConfig *config,
|
||||
const char *root,
|
||||
const char *d_name) {
|
||||
|
||||
int r;
|
||||
_cleanup_closedir_ DIR *d = NULL;
|
||||
|
||||
r = chase_and_opendir(root, NULL, CHASE_PROHIBIT_SYMLINKS, NULL, &d);
|
||||
if (r == -ENOENT)
|
||||
return 0;
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to open '%s/%s': %m", root, d_name);
|
||||
|
||||
return boot_entries_find_unified_addons(config, dirfd(d), d_name, root, &config->global_addons);
|
||||
}
|
||||
|
||||
static int boot_entries_find_unified_local_addons(
|
||||
BootConfig *config,
|
||||
int d_fd,
|
||||
const char *d_name,
|
||||
const char *root,
|
||||
BootEntry *ret) {
|
||||
|
||||
_cleanup_free_ char *addon_dir = NULL;
|
||||
|
||||
assert(ret);
|
||||
|
||||
addon_dir = strjoin(d_name, ".extra.d");
|
||||
if (!addon_dir)
|
||||
return log_oom();
|
||||
|
||||
return boot_entries_find_unified_addons(config, d_fd, addon_dir, root, &ret->local_addons);
|
||||
}
|
||||
|
||||
static int boot_entries_find_unified(
|
||||
BootConfig *config,
|
||||
const char *root,
|
||||
@ -839,13 +1041,18 @@ static int boot_entries_find_unified(
|
||||
if (!j)
|
||||
return log_oom();
|
||||
|
||||
if (find_sections(fd, j, &osrelease, &cmdline) < 0)
|
||||
if (find_uki_sections(fd, j, &osrelease, &cmdline) < 0)
|
||||
continue;
|
||||
|
||||
r = boot_entry_load_unified(root, j, osrelease, cmdline, config->entries + config->n_entries);
|
||||
if (r < 0)
|
||||
continue;
|
||||
|
||||
/* look for .efi.extra.d */
|
||||
r = boot_entries_find_unified_local_addons(config, dirfd(d), de->d_name, full, config->entries + config->n_entries);
|
||||
if (r < 0)
|
||||
continue;
|
||||
|
||||
config->n_entries++;
|
||||
}
|
||||
|
||||
@ -1062,6 +1269,7 @@ int boot_config_load(
|
||||
int r;
|
||||
|
||||
assert(config);
|
||||
config->global_addons = (BootEntryAddons) {};
|
||||
|
||||
if (esp_path) {
|
||||
r = boot_loader_read_conf_path(config, esp_path, "/loader/loader.conf");
|
||||
@ -1075,6 +1283,10 @@ int boot_config_load(
|
||||
r = boot_entries_find_unified(config, esp_path, "/EFI/Linux/");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = boot_entries_find_unified_global_addons(config, esp_path, "/loader/addons/");
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (xbootldr_path) {
|
||||
@ -1238,13 +1450,130 @@ static void boot_entry_file_list(
|
||||
*ret_status = status;
|
||||
}
|
||||
|
||||
static void print_addon(
|
||||
BootEntryAddon *addon,
|
||||
const char *addon_str) {
|
||||
|
||||
printf(" %s: %s\n", addon_str, addon->location);
|
||||
printf(" cmdline: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), addon->cmdline);
|
||||
}
|
||||
|
||||
static int print_cmdline(
|
||||
const BootEntry *e,
|
||||
const BootEntryAddons *global_arr) {
|
||||
|
||||
_cleanup_free_ char *final_cmdline = NULL;
|
||||
|
||||
assert(e);
|
||||
|
||||
if (!strv_isempty(e->options)) {
|
||||
_cleanup_free_ char *t = NULL, *t2 = NULL;
|
||||
_cleanup_strv_free_ char **ts = NULL;
|
||||
|
||||
t = strv_join(e->options, " ");
|
||||
if (!t)
|
||||
return log_oom();
|
||||
|
||||
ts = strv_split_newlines(t);
|
||||
if (!ts)
|
||||
return log_oom();
|
||||
|
||||
t2 = strv_join(ts, "\n ");
|
||||
if (!t2)
|
||||
return log_oom();
|
||||
|
||||
printf(" ukiCmdline: %s\n", t2);
|
||||
final_cmdline = TAKE_PTR(t2);
|
||||
}
|
||||
|
||||
FOREACH_ARRAY(addon, global_arr->items, global_arr->count) {
|
||||
print_addon(addon, "globalAddon");
|
||||
if (!strextend(&final_cmdline, " ", addon->cmdline))
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.count) {
|
||||
/* Add space at the beginning of addon_str to align it correctly */
|
||||
print_addon(addon, " localAddon");
|
||||
if (!strextend(&final_cmdline, " ", addon->cmdline))
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
if (final_cmdline)
|
||||
printf(" finalCmdline: %s\n", final_cmdline);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int json_addon(
|
||||
BootEntryAddon *addon,
|
||||
const char *addon_str,
|
||||
JsonVariant **array) {
|
||||
|
||||
int r;
|
||||
|
||||
r = json_variant_append_arrayb(array,
|
||||
JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR(addon_str, JSON_BUILD_STRING(addon->location)),
|
||||
JSON_BUILD_PAIR("cmdline", JSON_BUILD_STRING(addon->cmdline))));
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int json_cmdline(
|
||||
const BootEntry *e,
|
||||
const BootEntryAddons *global_arr,
|
||||
JsonVariant **v) {
|
||||
|
||||
_cleanup_free_ char *final_cmdline = NULL, *def_cmdline = NULL;
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *addons_array = NULL;
|
||||
int r;
|
||||
|
||||
assert(e);
|
||||
|
||||
if (!strv_isempty(e->options)) {
|
||||
def_cmdline = strv_join(e->options, " ");
|
||||
if (!def_cmdline)
|
||||
return log_oom();
|
||||
final_cmdline = TAKE_PTR(def_cmdline);
|
||||
}
|
||||
|
||||
FOREACH_ARRAY(addon, global_arr->items, global_arr->count) {
|
||||
r = json_addon(addon, "globalAddon", &addons_array);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!strextend(&final_cmdline, " ", addon->cmdline))
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.count) {
|
||||
r = json_addon(addon, "localAddon", &addons_array);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!strextend(&final_cmdline, " ", addon->cmdline))
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
r = json_variant_merge_objectb(
|
||||
v, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR_CONDITION(def_cmdline, "ukiCmdline", JSON_BUILD_STRING(def_cmdline)),
|
||||
JSON_BUILD_PAIR("addons", JSON_BUILD_VARIANT(addons_array)),
|
||||
JSON_BUILD_PAIR_CONDITION(final_cmdline, "finalCmdline", JSON_BUILD_STRING(final_cmdline))));
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int show_boot_entry(
|
||||
const BootEntry *e,
|
||||
const BootEntryAddons *global_addons,
|
||||
bool show_as_default,
|
||||
bool show_as_selected,
|
||||
bool show_reported) {
|
||||
|
||||
int status = 0;
|
||||
int status = 0, r = 0;
|
||||
|
||||
/* Returns 0 on success, negative on processing error, and positive if something is wrong with the
|
||||
boot entry itself. */
|
||||
@ -1323,24 +1652,9 @@ int show_boot_entry(
|
||||
*s,
|
||||
&status);
|
||||
|
||||
if (!strv_isempty(e->options)) {
|
||||
_cleanup_free_ char *t = NULL, *t2 = NULL;
|
||||
_cleanup_strv_free_ char **ts = NULL;
|
||||
|
||||
t = strv_join(e->options, " ");
|
||||
if (!t)
|
||||
return log_oom();
|
||||
|
||||
ts = strv_split_newlines(t);
|
||||
if (!ts)
|
||||
return log_oom();
|
||||
|
||||
t2 = strv_join(ts, "\n ");
|
||||
if (!t2)
|
||||
return log_oom();
|
||||
|
||||
printf(" options: %s\n", t2);
|
||||
}
|
||||
r = print_cmdline(e, global_addons);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (e->device_tree)
|
||||
boot_entry_file_list("devicetree", e->root, e->device_tree, &status);
|
||||
@ -1363,16 +1677,9 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
|
||||
|
||||
for (size_t i = 0; i < config->n_entries; i++) {
|
||||
_cleanup_free_ char *opts = NULL;
|
||||
const BootEntry *e = config->entries + i;
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
|
||||
if (!strv_isempty(e->options)) {
|
||||
opts = strv_join(e->options, " ");
|
||||
if (!opts)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
r = json_variant_merge_objectb(
|
||||
&v, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR("type", JSON_BUILD_STRING(boot_entry_type_json_to_string(e->type))),
|
||||
@ -1385,7 +1692,6 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) {
|
||||
JSON_BUILD_PAIR_CONDITION(e->version, "version", JSON_BUILD_STRING(e->version)),
|
||||
JSON_BUILD_PAIR_CONDITION(e->machine_id, "machineId", JSON_BUILD_STRING(e->machine_id)),
|
||||
JSON_BUILD_PAIR_CONDITION(e->architecture, "architecture", JSON_BUILD_STRING(e->architecture)),
|
||||
JSON_BUILD_PAIR_CONDITION(opts, "options", JSON_BUILD_STRING(opts)),
|
||||
JSON_BUILD_PAIR_CONDITION(e->kernel, "linux", JSON_BUILD_STRING(e->kernel)),
|
||||
JSON_BUILD_PAIR_CONDITION(e->efi, "efi", JSON_BUILD_STRING(e->efi)),
|
||||
JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", JSON_BUILD_STRV(e->initrd)),
|
||||
@ -1394,6 +1700,10 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) {
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
r = json_cmdline(e, &config->global_addons, &v);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
/* Sanitizers (only memory sanitizer?) do not like function call with too many
|
||||
* arguments and trigger false positive warnings. Let's not add too many json objects
|
||||
* at once. */
|
||||
@ -1419,6 +1729,7 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) {
|
||||
for (size_t n = 0; n < config->n_entries; n++) {
|
||||
r = show_boot_entry(
|
||||
config->entries + n,
|
||||
&config->global_addons,
|
||||
/* show_as_default= */ n == (size_t) config->default_entry,
|
||||
/* show_as_selected= */ n == (size_t) config->selected_entry,
|
||||
/* show_discovered= */ true);
|
||||
|
@ -20,6 +20,18 @@ typedef enum BootEntryType {
|
||||
_BOOT_ENTRY_TYPE_INVALID = -EINVAL,
|
||||
} BootEntryType;
|
||||
|
||||
typedef struct BootEntryAddon {
|
||||
char *location;
|
||||
char *cmdline;
|
||||
} BootEntryAddon;
|
||||
|
||||
typedef struct BootEntryAddons {
|
||||
BootEntryAddon *items;
|
||||
size_t count;
|
||||
} BootEntryAddons;
|
||||
|
||||
BootEntryAddon* boot_entry_addon_free(BootEntryAddon *t);
|
||||
|
||||
typedef struct BootEntry {
|
||||
BootEntryType type;
|
||||
bool reported_by_loader;
|
||||
@ -34,6 +46,7 @@ typedef struct BootEntry {
|
||||
char *machine_id;
|
||||
char *architecture;
|
||||
char **options;
|
||||
BootEntryAddons local_addons;
|
||||
char *kernel; /* linux is #defined to 1, yikes! */
|
||||
char *efi;
|
||||
char **initrd;
|
||||
@ -66,6 +79,8 @@ typedef struct BootConfig {
|
||||
BootEntry *entries;
|
||||
size_t n_entries;
|
||||
|
||||
BootEntryAddons global_addons;
|
||||
|
||||
ssize_t default_entry;
|
||||
ssize_t selected_entry;
|
||||
|
||||
@ -119,6 +134,7 @@ static inline const char* boot_entry_title(const BootEntry *entry) {
|
||||
|
||||
int show_boot_entry(
|
||||
const BootEntry *e,
|
||||
const BootEntryAddons *global_addons,
|
||||
bool show_as_default,
|
||||
bool show_as_selected,
|
||||
bool show_reported);
|
||||
|
Reference in New Issue
Block a user