mirror of
				https://github.com/FeralInteractive/gamemode.git
				synced 2025-11-04 07:54:18 +01:00 
			
		
		
		
	Added gamemode-cpu.c
Added gamemode-cpu.c which contains the functions for cpu core parking and pinning
This commit is contained in:
		
				
					committed by
					
						
						afayaz-feral
					
				
			
			
				
	
			
			
			
						parent
						
							2dbd565340
						
					
				
				
					commit
					495a659895
				
			
							
								
								
									
										409
									
								
								daemon/gamemode-cpu.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										409
									
								
								daemon/gamemode-cpu.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,409 @@
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2017-2019, Feral Interactive
 | 
			
		||||
All rights reserved.
 | 
			
		||||
 | 
			
		||||
Redistribution and use in source and binary forms, with or without
 | 
			
		||||
modification, are permitted provided that the following conditions are met:
 | 
			
		||||
 | 
			
		||||
 * Redistributions of source code must retain the above copyright notice,
 | 
			
		||||
   this list of conditions and the following disclaimer.
 | 
			
		||||
 * Redistributions in binary form must reproduce the above copyright
 | 
			
		||||
   notice, this list of conditions and the following disclaimer in the
 | 
			
		||||
   documentation and/or other materials provided with the distribution.
 | 
			
		||||
 * Neither the name of Feral Interactive nor the names of its contributors
 | 
			
		||||
   may be used to endorse or promote products derived from this software
 | 
			
		||||
   without specific prior written permission.
 | 
			
		||||
 | 
			
		||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 | 
			
		||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | 
			
		||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | 
			
		||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 | 
			
		||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 | 
			
		||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 | 
			
		||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 | 
			
		||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 | 
			
		||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 | 
			
		||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 | 
			
		||||
POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#define _GNU_SOURCE
 | 
			
		||||
 | 
			
		||||
#include <sched.h>
 | 
			
		||||
#include <linux/limits.h>
 | 
			
		||||
 | 
			
		||||
#include "common-external.h"
 | 
			
		||||
#include "common-cpu.h"
 | 
			
		||||
#include "common-helpers.h"
 | 
			
		||||
#include "common-logging.h"
 | 
			
		||||
 | 
			
		||||
#include "gamemode.h"
 | 
			
		||||
#include "gamemode-config.h"
 | 
			
		||||
 | 
			
		||||
#include "build-config.h"
 | 
			
		||||
 | 
			
		||||
static int read_small_file (char *path, char **buf, size_t *buflen)
 | 
			
		||||
{
 | 
			
		||||
	FILE *f = fopen(path, "r");
 | 
			
		||||
 | 
			
		||||
	if (!f) {
 | 
			
		||||
		LOG_ERROR("Couldn't open file at %s (%s), will not apply cpu core parking!\n", path, strerror(errno));
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ssize_t nread = getline(buf, buflen, f);
 | 
			
		||||
 | 
			
		||||
	if (nread == -1) {
 | 
			
		||||
		LOG_ERROR("Couldn't read file at %s (%s), will not apply cpu core parking!\n", path, strerror(errno));
 | 
			
		||||
		fclose(f);
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fclose (f);
 | 
			
		||||
 | 
			
		||||
	while (nread > 0 && ((*buf)[nread - 1] == '\n' || (*buf)[nread - 1] == '\r'))
 | 
			
		||||
		nread--;
 | 
			
		||||
 | 
			
		||||
	(*buf)[nread] = '\0';
 | 
			
		||||
 | 
			
		||||
	return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int walk_sysfs (char *cpulist, char **buf, size_t *buflen, GameModeCPUInfo *info)
 | 
			
		||||
{
 | 
			
		||||
	char path[PATH_MAX];
 | 
			
		||||
	unsigned long long max_cache = 0;
 | 
			
		||||
	long from, to;
 | 
			
		||||
 | 
			
		||||
	char *list = cpulist;
 | 
			
		||||
	while ((list = parse_cpulist(list, &from, &to))) {
 | 
			
		||||
		for (long cpu = from; cpu < to + 1; cpu++) {
 | 
			
		||||
			int ret = snprintf(path, PATH_MAX, "/sys/devices/system/cpu/cpu%ld/cache/index3/size", cpu);
 | 
			
		||||
			if (ret < 0 || ret >= PATH_MAX) {
 | 
			
		||||
				LOG_ERROR("snprintf failed, will not apply cpu core parking!\n");
 | 
			
		||||
				return 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (!read_small_file(path, buf, buflen))
 | 
			
		||||
				return 0;
 | 
			
		||||
 | 
			
		||||
			char *endp;
 | 
			
		||||
			unsigned long long cache_size = strtoull (*buf, &endp, 10);
 | 
			
		||||
 | 
			
		||||
			if (*endp == 'K') {
 | 
			
		||||
				cache_size *= 1024;
 | 
			
		||||
			} else if (*endp == 'M') {
 | 
			
		||||
				cache_size *= 1024 * 1024;
 | 
			
		||||
			} else if (*endp == 'G') {
 | 
			
		||||
				cache_size *= 1024 * 1024 * 1024;
 | 
			
		||||
			} else if (*endp != '\0') {
 | 
			
		||||
				LOG_ERROR("cpu L3 cache size (%s) is silly, will not apply cpu core parking!\n", *buf);
 | 
			
		||||
				return 0;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (cache_size > max_cache) {
 | 
			
		||||
				max_cache = cache_size;
 | 
			
		||||
				CPU_ZERO_S(CPU_ALLOC_SIZE(info->num_cpu), info->to_keep);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (cache_size == max_cache)
 | 
			
		||||
				CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep);
 | 
			
		||||
 | 
			
		||||
			CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int walk_string (char *cpulist, char *config_cpulist, GameModeCPUInfo *info)
 | 
			
		||||
{
 | 
			
		||||
	long from, to;
 | 
			
		||||
 | 
			
		||||
	char *list = cpulist;
 | 
			
		||||
	while ((list = parse_cpulist(list, &from, &to))) {
 | 
			
		||||
		for (long cpu = from; cpu < to + 1; cpu++) {
 | 
			
		||||
			CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online);
 | 
			
		||||
 | 
			
		||||
			if (info->park_or_pin == 0)
 | 
			
		||||
				CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	list = config_cpulist;
 | 
			
		||||
	while ((list = parse_cpulist(list, &from, &to))) {
 | 
			
		||||
		for (long cpu = from; cpu < to + 1; cpu++) {
 | 
			
		||||
			if (CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online)) {
 | 
			
		||||
				if (info->park_or_pin == 0)
 | 
			
		||||
					CPU_CLR_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep);
 | 
			
		||||
				else
 | 
			
		||||
					CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Attempts to identify the current in use CPU information
 | 
			
		||||
 */
 | 
			
		||||
int game_mode_initialise_cpu(GameModeConfig *config, GameModeCPUInfo **info)
 | 
			
		||||
{
 | 
			
		||||
	/* Verify input, this is programmer error */
 | 
			
		||||
	if (!info || *info)
 | 
			
		||||
		FATAL_ERROR("Invalid GameModeCPUInfo passed to %s", __func__);
 | 
			
		||||
 | 
			
		||||
	/* Early out if we have this feature turned off */
 | 
			
		||||
	char park_cores[CONFIG_VALUE_MAX];
 | 
			
		||||
	char pin_cores[CONFIG_VALUE_MAX];
 | 
			
		||||
	config_get_cpu_park_cores(config, park_cores);
 | 
			
		||||
	config_get_cpu_pin_cores(config, pin_cores);
 | 
			
		||||
 | 
			
		||||
	int park_or_pin = -1;
 | 
			
		||||
 | 
			
		||||
	if (pin_cores[0] != '\0') {
 | 
			
		||||
		if (strcasecmp (pin_cores, "no") == 0 || strcasecmp (pin_cores, "false") == 0 || strcmp (pin_cores, "0") == 0) {
 | 
			
		||||
			park_or_pin = -2;
 | 
			
		||||
		} else if (strcasecmp (pin_cores, "yes") == 0 || strcasecmp (pin_cores, "true") == 0 || strcmp (pin_cores, "1") == 0) {
 | 
			
		||||
			pin_cores[0] = '\0';
 | 
			
		||||
			park_or_pin = 1;
 | 
			
		||||
		} else {
 | 
			
		||||
			park_or_pin = 1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (park_or_pin < 1 && park_cores[0] != '\0') {
 | 
			
		||||
		if (strcasecmp (park_cores, "no") == 0 || strcasecmp (park_cores, "false") == 0 || strcmp (park_cores, "0") == 0) {
 | 
			
		||||
			if (park_or_pin == -2)
 | 
			
		||||
				return 0;
 | 
			
		||||
 | 
			
		||||
			park_or_pin = -1;
 | 
			
		||||
		} else if (strcasecmp (park_cores, "yes") == 0 || strcasecmp (park_cores, "true") == 0 || strcmp (park_cores, "1") == 0) {
 | 
			
		||||
			park_cores[0] = '\0';
 | 
			
		||||
			park_or_pin = 0;
 | 
			
		||||
		} else {
 | 
			
		||||
			park_or_pin = 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* always default to pin */
 | 
			
		||||
	if (park_or_pin < 0)
 | 
			
		||||
		park_or_pin = 1;
 | 
			
		||||
 | 
			
		||||
	char *buf = NULL, *buf2 = NULL;
 | 
			
		||||
	size_t buflen = 0, buf2len = 0;
 | 
			
		||||
 | 
			
		||||
	/* first we find which cores are online, this also helps us to determine the max
 | 
			
		||||
	 * cpu core number that we need to allocate the cpulist later */
 | 
			
		||||
	if (!read_small_file("/sys/devices/system/cpu/online", &buf, &buflen))
 | 
			
		||||
		goto error_exit;
 | 
			
		||||
 | 
			
		||||
	long from, to, max = 0;
 | 
			
		||||
	char *s = buf;
 | 
			
		||||
	while ((s = parse_cpulist(s, &from, &to))) {
 | 
			
		||||
		if (to > max)
 | 
			
		||||
			max = to;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* either parsing failed or we have only a single core, in either case
 | 
			
		||||
	 * we cannot optimize anyway */
 | 
			
		||||
	if (max == 0)
 | 
			
		||||
		goto early_exit;
 | 
			
		||||
 | 
			
		||||
	GameModeCPUInfo *new_info = malloc(sizeof(GameModeCPUInfo));
 | 
			
		||||
	memset(new_info, 0, sizeof(GameModeCPUInfo));
 | 
			
		||||
 | 
			
		||||
	new_info->num_cpu = (size_t)(max + 1);
 | 
			
		||||
	new_info->park_or_pin = park_or_pin;
 | 
			
		||||
	new_info->online = CPU_ALLOC(new_info->num_cpu);
 | 
			
		||||
	new_info->to_keep = CPU_ALLOC(new_info->num_cpu);
 | 
			
		||||
 | 
			
		||||
	CPU_ZERO_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->online);
 | 
			
		||||
	CPU_ZERO_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->to_keep);
 | 
			
		||||
 | 
			
		||||
	if (park_or_pin == 0 && park_cores[0] != '\0') {
 | 
			
		||||
		if (!walk_string (buf, park_cores, new_info))
 | 
			
		||||
			goto error_exit;
 | 
			
		||||
	} else if (park_or_pin == 1 && pin_cores[0] != '\0') {
 | 
			
		||||
		if (!walk_string (buf, pin_cores, new_info))
 | 
			
		||||
			goto error_exit;
 | 
			
		||||
	} else if (!walk_sysfs (buf, &buf2, &buf2len, new_info)) {
 | 
			
		||||
		goto error_exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (park_or_pin == 0 && CPU_EQUAL_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->online, new_info->to_keep)) {
 | 
			
		||||
		game_mode_free_cpu(&new_info);
 | 
			
		||||
		LOG_MSG("cpu L3 cache is uniform, will not apply cpu core parking!\n");
 | 
			
		||||
		goto error_exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (CPU_COUNT_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->to_keep) == 0) {
 | 
			
		||||
		game_mode_free_cpu(&new_info);
 | 
			
		||||
		LOG_MSG("logic or config wanted to park/unpin every single cpu core, will not apply cpu core parking/pinning!\n");
 | 
			
		||||
		goto error_exit;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Give back the new cpu info */
 | 
			
		||||
	*info = new_info;
 | 
			
		||||
 | 
			
		||||
early_exit:
 | 
			
		||||
	free (buf);
 | 
			
		||||
	free (buf2);
 | 
			
		||||
	return 0;
 | 
			
		||||
 | 
			
		||||
error_exit:
 | 
			
		||||
	free (buf);
 | 
			
		||||
	free (buf2);
 | 
			
		||||
	return -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int log_state (char *cpulist, int *pos, const long first, const long last)
 | 
			
		||||
{
 | 
			
		||||
	int ret;
 | 
			
		||||
	if (*pos != 0) {
 | 
			
		||||
		ret = snprintf(cpulist+*pos, ARG_MAX - (size_t)*pos, ",");
 | 
			
		||||
 | 
			
		||||
		if (ret < 0 || (size_t)ret >= (ARG_MAX - (size_t)*pos)) {
 | 
			
		||||
			LOG_ERROR("snprintf failed, will not apply cpu core parking!\n");
 | 
			
		||||
			return 0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		*pos += ret;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (first == last)
 | 
			
		||||
		ret = snprintf(cpulist+*pos, ARG_MAX - (size_t)*pos, "%ld", first);
 | 
			
		||||
	else
 | 
			
		||||
		ret = snprintf(cpulist+*pos, ARG_MAX - (size_t)*pos, "%ld-%ld", first,last);
 | 
			
		||||
 | 
			
		||||
	if (ret < 0 || (size_t)ret >= (ARG_MAX - (size_t)*pos)) {
 | 
			
		||||
		LOG_ERROR("snprintf failed, will not apply cpu core parking!\n");
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	*pos += ret;
 | 
			
		||||
	return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Park the unwanted cpu cores when gamemode is active
 | 
			
		||||
 */
 | 
			
		||||
int game_mode_park_cpu(const GameModeCPUInfo *info)
 | 
			
		||||
{
 | 
			
		||||
	if (!info || info->park_or_pin == 1)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	long first = -1, last = -1;
 | 
			
		||||
 | 
			
		||||
	char cpulist[ARG_MAX];
 | 
			
		||||
	int pos = 0;
 | 
			
		||||
 | 
			
		||||
	for (long cpu = 0; cpu < (long)(info->num_cpu); cpu++) {
 | 
			
		||||
		if (CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online) && !CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep)) {
 | 
			
		||||
			if (first == -1) {
 | 
			
		||||
				first = cpu;
 | 
			
		||||
				last = cpu;
 | 
			
		||||
			} else if (last + 1 == cpu) {
 | 
			
		||||
				last = cpu;
 | 
			
		||||
			} else {
 | 
			
		||||
				if (!log_state (cpulist, &pos, first, last))
 | 
			
		||||
					return 0;
 | 
			
		||||
 | 
			
		||||
				first = cpu;
 | 
			
		||||
				last = cpu;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (first != -1)
 | 
			
		||||
		log_state (cpulist, &pos, first, last);
 | 
			
		||||
 | 
			
		||||
	const char *const exec_args[] = {
 | 
			
		||||
		"pkexec", LIBEXECDIR "/cpucorectl", "offline", cpulist, NULL,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	LOG_MSG("Requesting parking of cores %s\n", cpulist);
 | 
			
		||||
	int ret = run_external_process(exec_args, NULL, -1);
 | 
			
		||||
	if (ret != 0) {
 | 
			
		||||
		LOG_ERROR("Failed to park cpu cores\n");
 | 
			
		||||
		return ret;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Restore the parked cpu cores when gamemode is disabled
 | 
			
		||||
 */
 | 
			
		||||
int game_mode_unpark_cpu(const GameModeCPUInfo *info)
 | 
			
		||||
{
 | 
			
		||||
	if (!info || info->park_or_pin == 1)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	long first = -1, last = -1;
 | 
			
		||||
 | 
			
		||||
	char cpulist[ARG_MAX];
 | 
			
		||||
	int pos = 0;
 | 
			
		||||
 | 
			
		||||
	for (long cpu = 0; cpu < (long)(info->num_cpu); cpu++) {
 | 
			
		||||
		if (CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online) && !CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep)) {
 | 
			
		||||
			if (first == -1) {
 | 
			
		||||
				first = cpu;
 | 
			
		||||
				last = cpu;
 | 
			
		||||
			} else if (last + 1 == cpu) {
 | 
			
		||||
				last = cpu;
 | 
			
		||||
			} else {
 | 
			
		||||
				if (!log_state (cpulist, &pos, first, last))
 | 
			
		||||
					return 0;
 | 
			
		||||
 | 
			
		||||
				first = cpu;
 | 
			
		||||
				last = cpu;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (first != -1)
 | 
			
		||||
		log_state (cpulist, &pos, first, last);
 | 
			
		||||
 | 
			
		||||
	const char *const exec_args[] = {
 | 
			
		||||
		"pkexec", LIBEXECDIR "/cpucorectl", "online", cpulist, NULL,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	LOG_MSG("Requesting unparking of cores %s\n", cpulist);
 | 
			
		||||
	int ret = run_external_process(exec_args, NULL, -1);
 | 
			
		||||
	if (ret != 0) {
 | 
			
		||||
		LOG_ERROR("Failed to unpark cpu cores\n");
 | 
			
		||||
		return ret;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void game_mode_apply_core_pinning(const GameModeCPUInfo *info, const pid_t client)
 | 
			
		||||
{
 | 
			
		||||
	if (!info || info->park_or_pin == 0)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	if (sched_setaffinity(client, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep) != 0)
 | 
			
		||||
		LOG_ERROR("Failed to pin process: %s\n", strerror(errno));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Simply used to free the CPU info object */
 | 
			
		||||
void game_mode_free_cpu(GameModeCPUInfo **info)
 | 
			
		||||
{
 | 
			
		||||
	if (!(*info)) {
 | 
			
		||||
		CPU_FREE((*info)->online);
 | 
			
		||||
		(*info)->online = NULL;
 | 
			
		||||
 | 
			
		||||
		CPU_FREE((*info)->to_keep);
 | 
			
		||||
		(*info)->to_keep = NULL;
 | 
			
		||||
 | 
			
		||||
		free(*info);
 | 
			
		||||
		*info = NULL;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user