mirror of
https://github.com/thepigeongenerator/cpusetcores.git
synced 2025-12-18 06:25:46 +01:00
Compare commits
14 Commits
2bd68392cb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 05cb47ca3d | |||
| 0cf95e888f | |||
| 47b3f041bf | |||
| ed1794ffbf | |||
| 6ce633b06b | |||
| dbcf29dc82 | |||
| 53472e6fc6 | |||
| d44235ce0f | |||
| 96ec279d9a | |||
| 4200d859aa | |||
| 82a33791de | |||
| bdccc05440 | |||
| 2731f908b3 | |||
| 782e78760b |
@@ -5,8 +5,8 @@ trim_trailing_whitespace = true
|
||||
|
||||
[*.{c,h}]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
indent_size = tab
|
||||
|
||||
[{makefile,Makefile}]
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
indent_size = tab
|
||||
|
||||
10
README.md
Normal file
10
README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# cpusetcores
|
||||
This is a utility application for managing CPU cores / threads on Linux machines.
|
||||
It allows you to enable/disable threads (via reading/writing to `/sys/devices/system/cpu/cpuX/online`).
|
||||
|
||||
The plan is to eventually through deamons or some kernel magic to manage the amount of cores automatically, in hopes of saving power.
|
||||
How well this'll work and how much power is gained from it is still researched and the project is not actively being worked on.
|
||||
So for now it just serves as something faster than a shell script.
|
||||
|
||||
I assume the kernel likely already has things for this, or it is being worked on.
|
||||
I just haven't looked into it that much yet, since I haven't really started the project yet.
|
||||
80
makefile
80
makefile
@@ -1,72 +1,32 @@
|
||||
# project name = the workspace directory name
|
||||
NAME := $(shell basename $(PWD))
|
||||
SHELL = bash -e -o pipefail -O globstar
|
||||
|
||||
# compiler settings
|
||||
CC := clang
|
||||
STD := c17
|
||||
LANG = c
|
||||
CFLAGS := -target x86_64-pc-linux-gnu -Wall -Wextra -Wpedantic -Wno-pointer-arith
|
||||
LDFLAGS := -lc
|
||||
NAME = cpusetcores
|
||||
VERSION = 0.0.2
|
||||
DEBUG ?= 0
|
||||
CC ?= cc
|
||||
|
||||
ifeq ($(DEBUG),1)
|
||||
CFLAGS += -DDEBUG -fsanitize=address,undefined -g -Og
|
||||
PROF := dbg
|
||||
else
|
||||
REL_FLAGS += -O2
|
||||
PROF := rel
|
||||
endif
|
||||
CFLAGS = -c -std=gnu99 -Wall -Wextra -Wpedantic -MMD -MP
|
||||
LFLAGS = -flto
|
||||
|
||||
# dirs
|
||||
DIR_BIN := bin
|
||||
DIR_OBJ := obj
|
||||
DIR_BUILD := $(DIR_BIN)/$(PROF)
|
||||
DIR := $(DIR_BIN)/$(PROF) $(DIR_OBJ)/$(PROF)
|
||||
SRC := $(wildcard src/*.c) $(wildcard src/**/*.c)
|
||||
OBJ := $(SRC:%.c=obj/%.o)
|
||||
BIN := bin/$(NAME)
|
||||
|
||||
# source files
|
||||
SRC := $(wildcard src/*.c) $(wildcard src/**/*.c) $(wildcard src/**/**/*.c) $(wildcard src/**/**/**/*.c) $(wildcard src/**/**/**/**/*.c)
|
||||
.PHONY:
|
||||
compile: $(BIN)
|
||||
|
||||
# output locations
|
||||
OBJ := $(patsubst src/%,$(DIR_OBJ)/$(PROF)/%,$(SRC:.c=.o))
|
||||
DEP := $(OBJ:.o=.d)
|
||||
TARGET := $(DIR_BUILD)/$(NAME)
|
||||
$(BIN): $(OBJ)
|
||||
$(info [CC/LD] $@)
|
||||
@mkdir -p $(@D)
|
||||
@$(CC) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
define wr_colour
|
||||
@echo -e "\033[$(2)m$(1)\033[0m"
|
||||
endef
|
||||
obj/%.o: %.c
|
||||
$(info [CC] $@)
|
||||
@mkdir -p $(@D)
|
||||
@$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
compile: compile_commands.json $(DIR) $(TARGET)
|
||||
clean:
|
||||
rm -rf $(DIR_BIN) $(DIR_OBJ) compile_commands.json
|
||||
|
||||
# create the binary (linking step)
|
||||
$(TARGET): $(OBJ)
|
||||
@$(call wr_colour,"CC: '$(CC)'",94)
|
||||
@$(call wr_colour,"CFLAGS: '$(CFLAGS)'",94)
|
||||
@$(call wr_colour,"LDFLAGS: '$(LDFLAGS)'",94)
|
||||
@$(call wr_colour,"linking to: '$@'",92)
|
||||
@$(CC) -o $(TARGET) $^ $(CFLAGS) $(LDFLAGS)
|
||||
@$(call wr_colour,"current profile: '$(PROF)'",93)
|
||||
|
||||
# create .o and .d files
|
||||
$(DIR_OBJ)/$(PROF)/%.o: src/%.c
|
||||
@$(call wr_colour,"compiling $(notdir $@) from $(notdir $<)",92)
|
||||
@mkdir -p $(dir $@)
|
||||
@$(CC) -o $@ -MD -MP -c $< $(CFLAGS) -std=$(STD) -x $(LANG) -Wno-unused-command-line-argument
|
||||
|
||||
# create directories
|
||||
$(DIR):
|
||||
mkdir -p $@
|
||||
|
||||
# update compile commands if the makefile has been updated (for linting)
|
||||
ifeq ($(DEBUG),1)
|
||||
compile_commands.json: makefile
|
||||
$(MAKE) clean
|
||||
@touch compile_commands.json
|
||||
bear -- make compile
|
||||
else
|
||||
compile_commands.json:
|
||||
endif
|
||||
rm -rv obj/ bin/
|
||||
|
||||
# include the dependencies
|
||||
-include $(DEP)
|
||||
|
||||
59
src/atrb.h
Normal file
59
src/atrb.h
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2025 Quinn
|
||||
// Licensed under the MIT Licence. See LICENSE for details
|
||||
#pragma once
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#if __has_attribute(__pure__)
|
||||
#define PURE __attribute__((__pure__))
|
||||
#else
|
||||
#define PURE
|
||||
#endif
|
||||
|
||||
#if __has_attribute(__const__)
|
||||
#define CONST __attribute__((__const__))
|
||||
#else
|
||||
#define CONST
|
||||
#endif
|
||||
|
||||
#if __has_attribute(__noreturn__)
|
||||
#define NORET __attribute__((__noreturn__))
|
||||
#else
|
||||
#define NORET
|
||||
#endif
|
||||
|
||||
#if __has_attribute(__malloc__)
|
||||
#define MALLOC __attribute__((__malloc__))
|
||||
#else
|
||||
#define MALLOC
|
||||
#endif
|
||||
|
||||
#if __has_attribute(__used__)
|
||||
#define USED __attribute__((__used__))
|
||||
#else
|
||||
#define USED
|
||||
#endif
|
||||
|
||||
#if __has_attribute(__unused__)
|
||||
#define UNUSED __attribute__((__unused__))
|
||||
#else
|
||||
#define UNUSED
|
||||
#endif
|
||||
|
||||
#if __has_attribute(__deprecated__)
|
||||
#define DEPRECATED __attribute__((__deprecated__))
|
||||
#else
|
||||
#define DEPRECATED
|
||||
#endif
|
||||
|
||||
#if __has_attribute(__format__)
|
||||
#define FORMAT(...) __attribute__((format(__VA_ARGS__)))
|
||||
#else
|
||||
#define FORMAT(...)
|
||||
#endif
|
||||
|
||||
#if __has_attribute(__nonnull__)
|
||||
#define NONNULL(...) __attribute__((nonnull(__VA_ARGS__)))
|
||||
#else
|
||||
#define NONNULL(...)
|
||||
#endif
|
||||
#endif
|
||||
39
src/cpu.c
39
src/cpu.c
@@ -1,39 +1,44 @@
|
||||
#include "cpu.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
char const* const cpu_path = "/sys/devices/system/cpu/cpu%i/online";
|
||||
const char cpu_path[] = "/sys/devices/system/cpu/cpu%i/online";
|
||||
|
||||
bool getcpu(uint32_t id) {
|
||||
// get the file path
|
||||
char path[64]; // contains the file path (max length is 64 due to the path and a bunch of extra wiggle room)
|
||||
snprintf(path, 64, cpu_path, id); // writes the path using the id
|
||||
int cpu_getenabled(uint id) {
|
||||
const size_t pathlen = sizeof(cpu_path) + (uint)(log10(-1u) + 1);
|
||||
char path[pathlen];
|
||||
snprintf(path, pathlen, cpu_path, id);
|
||||
|
||||
// if the file doesn't exist; return true
|
||||
if (access(path, R_OK) != 0)
|
||||
return true;
|
||||
return 1;
|
||||
|
||||
// read a character from the file, store in state
|
||||
char state = '\0';
|
||||
FILE* f = fopen(path, "r");
|
||||
FILE *f = fopen(path, "r");
|
||||
assert(f);
|
||||
fread(&state, 1, 1, f);
|
||||
fclose(f);
|
||||
|
||||
// return whether state is truthy
|
||||
return !!(state - 0x30);
|
||||
return state != '0';
|
||||
}
|
||||
|
||||
void setcpu(uint32_t id, bool state) {
|
||||
char path[64];
|
||||
snprintf(path, 64, cpu_path, id);
|
||||
int cpu_setenabled(uint id, int state) {
|
||||
const size_t pathlen = sizeof(cpu_path) + (uint)(log10(-1u) + 1);
|
||||
char path[pathlen];
|
||||
snprintf(path, pathlen, cpu_path, id);
|
||||
|
||||
char s = 0x30 + (char)state; // convert the state to a character
|
||||
char s = 0x30 + (state & 1);
|
||||
|
||||
// write the state to the file (creates file if it doesn't exist)
|
||||
FILE* f = fopen(path, "w");
|
||||
if (access(path, W_OK) != 0)
|
||||
return 1;
|
||||
|
||||
FILE *f = fopen(path, "w");
|
||||
assert(f);
|
||||
fwrite(&s, 1, 1, f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
12
src/cpu.h
12
src/cpu.h
@@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "util/intdef.h"
|
||||
|
||||
bool getcpu(uint32_t); // gets the state of core (id)
|
||||
void setcpu(uint32_t, bool); // sets the state of core (id)
|
||||
/* gets the current state of a CPU thread,
|
||||
* returns a boolean value (`1` for enabled, `0` for disabled) */
|
||||
int cpu_getenabled(uint id);
|
||||
|
||||
/* sets the current state of a CPU thread.
|
||||
* returns `0` upon success, `1` upon failure */
|
||||
int cpu_setenabled(uint id, int state);
|
||||
|
||||
18
src/error.c
18
src/error.c
@@ -1,18 +0,0 @@
|
||||
#include "error.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdnoreturn.h>
|
||||
|
||||
noreturn void fatal(char const* fmt, ...) {
|
||||
char buf[128];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buf, 128, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
fprintf(stderr, "\033[91mE: %s\033[0m\n", buf);
|
||||
exit(1);
|
||||
}
|
||||
24
src/error.h
24
src/error.h
@@ -1,7 +1,21 @@
|
||||
// Copyright (c) 2025 Quinn
|
||||
// Licensed under the MIT Licence. See LICENSE for details
|
||||
#pragma once
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdnoreturn.h>
|
||||
|
||||
// prints an error message and aborts execution
|
||||
noreturn void fatal(char const*, ...);
|
||||
#if __INCLUDE_LEVEL__ > 0
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "util/macro.h"
|
||||
#endif
|
||||
|
||||
#define debug(s, ...) printf("\033[95m" __FILE__ ":" MACRO_STR2(__LINE__) ": [DBG]: " s "\033[0m\n", ##__VA_ARGS__)
|
||||
#define info(s, ...) printf(__FILE__ ":" MACRO_STR2(__LINE__) ": [INF]: " s "\n", ##__VA_ARGS__)
|
||||
#define warn(s, ...) fprintf(stderr, "\033[93m" __FILE__ ":" MACRO_STR2(__LINE__) ": [WAR]: " s "\033[0m\n", ##__VA_ARGS__)
|
||||
#define error(s, ...) fprintf(stderr, "\033[91m" __FILE__ ":" MACRO_STR2(__LINE__) ": [ERR]: " s "\033[0m\n", ##__VA_ARGS__)
|
||||
|
||||
#define fatal(s, ...) \
|
||||
do { \
|
||||
fprintf(stderr, "\033[101m" __FILE__ ":" MACRO_STR2(__LINE__) ": [FAT]: " s "\033[0m\n", ##__VA_ARGS__); \
|
||||
exit(EXIT_FAILURE); \
|
||||
} while (0)
|
||||
|
||||
36
src/main.c
36
src/main.c
@@ -1,3 +1,4 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/sysinfo.h>
|
||||
@@ -7,28 +8,25 @@
|
||||
#include "error.h"
|
||||
#include "opts.h"
|
||||
|
||||
static inline bool cpu_setter(uint32_t id, bool nstate, uint8_t opts) {
|
||||
bool cstate = !nstate;
|
||||
if (!(opts & OPT_SET_ALL))
|
||||
cstate = getcpu(id);
|
||||
|
||||
if (cstate != nstate) {
|
||||
setcpu(id, nstate);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
/* sets CPU `id` to `nstate`, if it hasn't been set yet.
|
||||
* If `opt` contains `OPT_SET_ALL`, this check is ignored.
|
||||
* Returns `0` upon success, `1` upon failure. */
|
||||
static int setcpu(uint id, int nstate, u8 opts) {
|
||||
bool nsetall = !(opts & OPT_SET_ALL); // if SET_ALL has not been set
|
||||
int cstate = nsetall && cpu_getenabled(id); // get state if nsetall=1
|
||||
return (nsetall && cstate == nstate) || cpu_setenabled(id, nstate); // set if cstate matches nstate, unless nsetall=1
|
||||
}
|
||||
|
||||
static inline void print_cpu_count(int32_t mcpus) {
|
||||
static inline void print_cpu_count(uint mcpus) {
|
||||
printf("%i/%i cpus enabled.\n", get_nprocs(), mcpus); // get the number of available processors
|
||||
}
|
||||
|
||||
int32_t main(int32_t argc, char** argv) {
|
||||
int main(int argc, char **argv) {
|
||||
if (geteuid() != 0) fatal("must be executed as the root user!");
|
||||
|
||||
int32_t ncpus; // the number of CPUs to activate
|
||||
uint8_t opts = getoptions(argc, argv, &ncpus); // the options to use
|
||||
int32_t mcpus = get_nprocs_conf(); // the max number of CPUs that are available
|
||||
int mcpus, ncpus; // the number of CPUs to activate
|
||||
u8 opts = getoptions(argc, argv, &ncpus); // the options to use
|
||||
mcpus = get_nprocs_conf(); // the max number of CPUs that are available
|
||||
|
||||
if (opts & OPT_LIST_CORES && ncpus < 0) {
|
||||
print_cpu_count(mcpus);
|
||||
@@ -42,18 +40,18 @@ int32_t main(int32_t argc, char** argv) {
|
||||
if (ncpus > mcpus) fatal("%i exeeds the maximum numbers of cpus available (%i)", ncpus, mcpus);
|
||||
if (ncpus < 1) fatal("may not keep less than 1 cpu enabled, requested to enable %i", ncpus);
|
||||
|
||||
char const* const cpu_set_log = "set cpu %i to %hi\n";
|
||||
for (int32_t id = 1; id < mcpus; id++) { // start at CPU 1, as CPU 0 is not writeable
|
||||
const char *const cpu_set_log = "set cpu %i to %hi\n";
|
||||
for (int id = 1; id < mcpus; id++) { // start at CPU 1, as CPU 0 is not writeable
|
||||
// whilst the id is less then the amount of cpus to enable
|
||||
if (id < ncpus) {
|
||||
// enable the cpu & print if it was actually set (if -v was given)
|
||||
if (cpu_setter(id, true, opts) && (opts & OPT_VERBOSE))
|
||||
if (!setcpu(id, true, opts) && (opts & OPT_VERBOSE))
|
||||
printf(cpu_set_log, id, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// disable the cpu & print if it was actually set (if -v was given)
|
||||
if (cpu_setter(id, false, opts) && (opts & OPT_VERBOSE)) {
|
||||
if (!setcpu(id, false, opts) && (opts & OPT_VERBOSE)) {
|
||||
printf(cpu_set_log, id, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
54
src/opts.c
54
src/opts.c
@@ -1,45 +1,49 @@
|
||||
#include "opts.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "error.h"
|
||||
#include "util/intdef.h"
|
||||
|
||||
uint8_t getoptions(int32_t argc, char* const* argv, int32_t* ncpus) {
|
||||
u8 getoptions(int argc, char *const *argv, int *ncpus) {
|
||||
*ncpus = -1;
|
||||
uint8_t opts = 0;
|
||||
char opt;
|
||||
|
||||
char const* const msg = "-%c has already been set";
|
||||
while ((opt = getopt(argc, argv, "lavi")) != -1) {
|
||||
while ((opt = getopt(argc, argv, "lavih")) != -1) {
|
||||
switch (opt) {
|
||||
case 'l':
|
||||
if (opts & OPT_LIST_CORES) fatal(msg, 'l');
|
||||
opts |= OPT_LIST_CORES;
|
||||
break;
|
||||
case 'a':
|
||||
if (opts & OPT_SET_ALL) fatal(msg, 'a');
|
||||
opts |= OPT_SET_ALL;
|
||||
break;
|
||||
case 'v':
|
||||
if (opts & OPT_VERBOSE) fatal(msg, 'v');
|
||||
opts |= OPT_VERBOSE | OPT_LIST_CORES;
|
||||
break;
|
||||
case 'i':
|
||||
if (opts & OPT_INVERT) fatal(msg, 'i');
|
||||
opts |= OPT_INVERT;
|
||||
break;
|
||||
case 'l': opts |= OPT_LIST_CORES; break;
|
||||
case 'a': opts |= OPT_SET_ALL; break;
|
||||
case 'v': opts |= OPT_VERBOSE | OPT_LIST_CORES; break;
|
||||
case 'i': opts |= OPT_INVERT; break;
|
||||
case '?':
|
||||
fatal("usage: %s [number] [-l] [-a] [-v] [-i]", argv[0]);
|
||||
printf("specify -h for help\n");
|
||||
exit(EXIT_FAILURE);
|
||||
case 'h':
|
||||
printf(
|
||||
"%s: a tool for turning on/off CPU threads\n"
|
||||
"USAGE: %s [integer] [-l] [-a] [-v] [-i] [-h]\n"
|
||||
"ARGUMENTS:\n"
|
||||
"\t-l : List cores after executing the command. May be specified without integer.\n"
|
||||
"\t-a : Writes to all cores, regardless of their state.\n"
|
||||
"\t-v : Print the cores that are written. (implies -l)\n"
|
||||
"\t-i : Instead of the given number.\n"
|
||||
"\t-h : Shows this dialogue.\n",
|
||||
argv[0], argv[0]);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind < argc) {
|
||||
char* num = argv[optind];
|
||||
*ncpus = atoi(num);
|
||||
} else if (opts & OPT_LIST_CORES) {
|
||||
*ncpus = -1;
|
||||
} else
|
||||
char *end;
|
||||
char *num = argv[optind];
|
||||
*ncpus = strtol(num, &end, 0);
|
||||
if (errno || end == num || *end != '\0')
|
||||
fatal("failed to parse numeric value from string '%s'", num);
|
||||
} else if (!(opts & OPT_LIST_CORES))
|
||||
fatal("must execute with either a number or [-l]");
|
||||
|
||||
return opts;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#include "util/intdef.h"
|
||||
|
||||
enum opt {
|
||||
OPT_LIST_CORES = 1, // option that lists the total amount of cores that are set
|
||||
OPT_SET_ALL = 2, // option to set all cores, regardless of their assumed state
|
||||
@@ -8,4 +10,7 @@ enum opt {
|
||||
OPT_INVERT = 8, // 'num' now represents the amount of cores to disable
|
||||
};
|
||||
|
||||
uint8_t getoptions(int32_t, char* const*, int32_t*);
|
||||
/* acquires the options given by the user through arguments.
|
||||
* returns the bitmap of these options.
|
||||
* `ncpus` is set to `-1`, or the amount of CPUs that the user requested to enable.*/
|
||||
u8 getoptions(int argc, char *const *argv, int *ncpus);
|
||||
|
||||
20
src/util/intdef.h
Normal file
20
src/util/intdef.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2025 Quinn
|
||||
// Licensed under the MIT Licence. See LICENSE for details
|
||||
#pragma once
|
||||
|
||||
/* variable-width integer types */
|
||||
typedef unsigned int uint; // ≥16 bit unsigned integer
|
||||
typedef unsigned long ulong; // ≥32 bit unsigned integer
|
||||
typedef signed long long llong; // ≥64 bit signed integer
|
||||
typedef unsigned long long ullong; // ≥64 bit unsigned integer
|
||||
|
||||
/* fixed-width integer types */
|
||||
#include <stdint.h>
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
typedef int64_t i64;
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
9
src/util/macro.h
Normal file
9
src/util/macro.h
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) 2025 Quinn
|
||||
// Licensed under the MIT Licence. See LICENSE for details
|
||||
#pragma once
|
||||
|
||||
#define WIDTHOF(t) (sizeof(t) * 8) // gets the bit width of a type
|
||||
#define MACRO_CAT(x, y) x##y // concatenate two macro variables together
|
||||
#define MACRO_CAT2(x, y) MACRO_CAT(x, y) // concatenate two macro variables together
|
||||
#define MACRO_STR(v) #v // for converting macro variable into a string
|
||||
#define MACRO_STR2(v) MACRO_STR(v) // for a recursive string generation
|
||||
Reference in New Issue
Block a user