AFUERA part2
This commit is contained in:
@@ -7,51 +7,9 @@
|
|||||||
#include "framebuffer.h"
|
#include "framebuffer.h"
|
||||||
#include "serial.h"
|
#include "serial.h"
|
||||||
#include "../kernel/system.h"
|
#include "../kernel/system.h"
|
||||||
#include "../kernel/kheap.h"
|
|
||||||
|
|
||||||
extern char* framebuffer;
|
extern char* framebuffer;
|
||||||
|
|
||||||
void psf_init()
|
|
||||||
{
|
|
||||||
uint16_t glyph = 0;
|
|
||||||
PSF_font *font = (PSF_font*)&FONT_START;
|
|
||||||
if (font->flags)
|
|
||||||
{
|
|
||||||
unicode = NULL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* s = (char*)((unsigned char*)&FONT_START + font->headersize + font->numglyph * font->bytesperglyph);
|
|
||||||
unicode = calloc(USHRT_MAX, 2);
|
|
||||||
|
|
||||||
while((uintptr_t)s>(uintptr_t)FONT_END){
|
|
||||||
uint16_t uc = (uint16_t)((unsigned char)s[0]);
|
|
||||||
if(uc == 0xFF) {
|
|
||||||
glyph++;
|
|
||||||
s++;
|
|
||||||
continue;
|
|
||||||
} else if(uc & 128) {
|
|
||||||
/* UTF-8 to unicode */
|
|
||||||
if((uc & 32) == 0 ) {
|
|
||||||
uc = ((s[0] & 0x1F)<<6)+(s[1] & 0x3F);
|
|
||||||
s++;
|
|
||||||
} else
|
|
||||||
if((uc & 16) == 0 ) {
|
|
||||||
uc = ((((s[0] & 0xF)<<6)+(s[1] & 0x3F))<<6)+(s[2] & 0x3F);
|
|
||||||
s+=2;
|
|
||||||
} else
|
|
||||||
if((uc & 8) == 0 ) {
|
|
||||||
uc = ((((((s[0] & 0x7)<<6)+(s[1] & 0x3F))<<6)+(s[2] & 0x3F))<<6)+(s[3] & 0x3F);
|
|
||||||
s+=3;
|
|
||||||
} else
|
|
||||||
uc = 0;
|
|
||||||
}
|
|
||||||
/* save translation */
|
|
||||||
unicode[uc] = glyph;
|
|
||||||
s++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void putpixel(uint32_t* fb, int pitch, int bpp, int x, int y, uint32_t color)
|
void putpixel(uint32_t* fb, int pitch, int bpp, int x, int y, uint32_t color)
|
||||||
{
|
{
|
||||||
uint32_t* pixel_addr = (uint32_t*)((uint8_t*)fb + y * pitch + x *(bpp / 8));
|
uint32_t* pixel_addr = (uint32_t*)((uint8_t*)fb + y * pitch + x *(bpp / 8));
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
hello, ramdisk world!
|
|
||||||
250
kernel/initrd.c
250
kernel/initrd.c
@@ -1,250 +0,0 @@
|
|||||||
// Initial TAR ramdisk kernel module
|
|
||||||
// Author: xamidev
|
|
||||||
// Licensed under the Unlicense. See the repo below.
|
|
||||||
// https://github.com/xamidev/blankos
|
|
||||||
|
|
||||||
#include "../libk/stdio.h"
|
|
||||||
#include <stdint.h>
|
|
||||||
#include "../libk/string.h"
|
|
||||||
#include "initrd.h"
|
|
||||||
#include "system.h"
|
|
||||||
#include "kheap.h"
|
|
||||||
|
|
||||||
static unsigned int octal_to_int(const char* str, size_t size)
|
|
||||||
{
|
|
||||||
unsigned int result = 0;
|
|
||||||
while (*str && size-- > 0)
|
|
||||||
{
|
|
||||||
result = (result << 3) | (*str - '0');
|
|
||||||
str++;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t tar_parse_size(const char* in)
|
|
||||||
{
|
|
||||||
uint32_t size = 0;
|
|
||||||
while (*in >= '0' && *in <= '7')
|
|
||||||
{
|
|
||||||
size = (size*8) + (*in - '0');
|
|
||||||
in++;
|
|
||||||
}
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t tar_get_size(tar_header_t* header)
|
|
||||||
{
|
|
||||||
uint32_t size = 0;
|
|
||||||
char* size_str = header->size;
|
|
||||||
|
|
||||||
for (int i=0; i<11 && size_str[i] != '\0'; i++)
|
|
||||||
{
|
|
||||||
size = size*8 + (size_str[i]-'0');
|
|
||||||
}
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void tar_find_file(uint8_t *tar_start, const char* filename)
|
|
||||||
{
|
|
||||||
uint8_t *ptr = tar_start;
|
|
||||||
|
|
||||||
while (1)
|
|
||||||
{
|
|
||||||
tar_header_t *header = (tar_header_t*)ptr;
|
|
||||||
|
|
||||||
if (header->filename[0] == '\0')
|
|
||||||
{
|
|
||||||
puts("[tar] EOF\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int filesize = octal_to_int(header->size, 11);
|
|
||||||
|
|
||||||
if (strcmp(header->filename, filename) == 0)
|
|
||||||
{
|
|
||||||
printf("[tar] found file '%s', size=%u bytes\n", header->filename, filesize);
|
|
||||||
|
|
||||||
uint8_t *file_data = ptr+TAR_BLOCK_SIZE;
|
|
||||||
printf("[tar] content of '%s':\n", filename);
|
|
||||||
for (unsigned int i=0; i<filesize; i++)
|
|
||||||
{
|
|
||||||
putc(file_data[i]);
|
|
||||||
}
|
|
||||||
puts("\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ptr += TAR_BLOCK_SIZE + ((filesize + TAR_BLOCK_SIZE-1) / TAR_BLOCK_SIZE) * TAR_BLOCK_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("[tar] file '%s' not found\n", filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ls_initrd(uint8_t* initrd, int verbose)
|
|
||||||
{
|
|
||||||
tar_header_t *header = (tar_header_t*)initrd;
|
|
||||||
|
|
||||||
if (verbose)
|
|
||||||
{
|
|
||||||
puts("Size Type Filename\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
while (header->filename[0] != '\0')
|
|
||||||
{
|
|
||||||
if (!verbose)
|
|
||||||
{
|
|
||||||
if (header->typeflag == '5')
|
|
||||||
{
|
|
||||||
colorprintf(cyan, black, "%s\n", header->filename);
|
|
||||||
} else {
|
|
||||||
printf("%s\n", header->filename);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (header->typeflag == '5')
|
|
||||||
{
|
|
||||||
printf("%7d\t%c\t", (int)header->size, header->typeflag);
|
|
||||||
colorprintf(cyan, black, " %s\n", header->filename);
|
|
||||||
} else {
|
|
||||||
|
|
||||||
printf("%7d\t%c\t %s\n", (int)header->size, header->typeflag, header->filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t size = tar_parse_size(header->size);
|
|
||||||
uint32_t next_file_offset = ((size+TAR_BLOCK_SIZE-1)/TAR_BLOCK_SIZE)*TAR_BLOCK_SIZE;
|
|
||||||
header = (tar_header_t*)((uint8_t*)header + next_file_offset + TAR_BLOCK_SIZE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void cat_initrd(uint8_t* initrd, const char* filename)
|
|
||||||
{
|
|
||||||
tar_header_t *header = (tar_header_t*)initrd;
|
|
||||||
|
|
||||||
while (header->filename[0] != '\0')
|
|
||||||
{
|
|
||||||
if (strcmp(header->filename, filename) == 0)
|
|
||||||
{
|
|
||||||
uint32_t size = tar_parse_size(header->size);
|
|
||||||
uint8_t* file_content = (uint8_t*)header + 512;
|
|
||||||
|
|
||||||
for (uint32_t i=0; i<size; i++) putc(file_content[i]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t next_file_offset = ((tar_parse_size(header->size)+TAR_BLOCK_SIZE-1)/TAR_BLOCK_SIZE)*TAR_BLOCK_SIZE;
|
|
||||||
header = (tar_header_t*)((uint8_t*)header + next_file_offset + TAR_BLOCK_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("File '%s' not found\n", filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
int tar_file_to_buffer(uint8_t* initrd, const char* filename, char* buffer)
|
|
||||||
{
|
|
||||||
uint8_t* current_block = initrd;
|
|
||||||
|
|
||||||
while (1)
|
|
||||||
{
|
|
||||||
if (current_block[0] == '\0')
|
|
||||||
{
|
|
||||||
//puts("[tar] EOF\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* file_name = (const char*)current_block;
|
|
||||||
uint32_t file_size = tar_parse_size((const char*)(current_block+124));
|
|
||||||
|
|
||||||
if (strcmp(file_name, filename) == 0)
|
|
||||||
{
|
|
||||||
uint8_t* file_data = current_block + TAR_BLOCK_SIZE;
|
|
||||||
if (sizeof(buffer) >= sizeof(file_data))
|
|
||||||
{
|
|
||||||
memcpy(buffer, file_data, file_size);
|
|
||||||
buffer[file_size] = '\0';
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
printf("Invalid destination buffer size %d bytes < %d bytes\n", sizeof(buffer), sizeof(file_data));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t total_size = ((file_size + TAR_BLOCK_SIZE - 1) / TAR_BLOCK_SIZE) * TAR_BLOCK_SIZE;
|
|
||||||
current_block += TAR_BLOCK_SIZE + total_size;
|
|
||||||
}
|
|
||||||
printf("[tar] file '%s' not found\n", filename);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t tar_get_file_size(uint8_t* initrd, const char* filename)
|
|
||||||
{
|
|
||||||
uint8_t* current_block = initrd;
|
|
||||||
|
|
||||||
while (1)
|
|
||||||
{
|
|
||||||
if (current_block[0] == '\0')
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* file_name = (const char*)current_block;
|
|
||||||
uint32_t file_size = tar_parse_size((const char*)(current_block+124));
|
|
||||||
|
|
||||||
if (strcmp(file_name, filename) == 0)
|
|
||||||
{
|
|
||||||
return file_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t total_size = ((file_size + TAR_BLOCK_SIZE - 1) / TAR_BLOCK_SIZE) * TAR_BLOCK_SIZE;
|
|
||||||
current_block += TAR_BLOCK_SIZE + total_size;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
tar_header_t* tar_find(uint8_t* initrd, const char* filename)
|
|
||||||
{
|
|
||||||
tar_header_t* header = (tar_header_t*)initrd;
|
|
||||||
while (header->filename[0] != '\0')
|
|
||||||
{
|
|
||||||
if (strcmp(header->filename, filename) == 0)
|
|
||||||
{
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t file_size = tar_get_size(header);
|
|
||||||
uint32_t file_blocks = (file_size + 511)/512;
|
|
||||||
header = (tar_header_t*) ((uintptr_t)header+(file_blocks+1)*512);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* tar_get_file_content(tar_header_t* header)
|
|
||||||
{
|
|
||||||
return (void*) ((uintptr_t)header+512);
|
|
||||||
}
|
|
||||||
|
|
||||||
void* load_file_from_initrd(uint8_t* initrd, const char* filename)
|
|
||||||
{
|
|
||||||
tar_header_t* file = tar_find(initrd, filename);
|
|
||||||
if (file == NULL)
|
|
||||||
{
|
|
||||||
printf("'%s' not found\n", filename);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t file_size = tar_get_size(file);
|
|
||||||
|
|
||||||
void* file_data = malloc(file_size);
|
|
||||||
if (file_data == NULL)
|
|
||||||
{
|
|
||||||
printf("Malloc error for file '%s'\n", filename);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* file_content = tar_get_file_content(file);
|
|
||||||
memcpy(file_data, file_content, file_size);
|
|
||||||
|
|
||||||
printf("[initrd] Loaded '%s' at 0x%x, size=%u\n", filename, (unsigned int)file_data, file_size);
|
|
||||||
|
|
||||||
return file_data;
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// Initial TAR ramdisk kernel module header
|
|
||||||
// Author: xamidev
|
|
||||||
// Licensed under the Unlicense. See the repo below.
|
|
||||||
// https://github.com/xamidev/blankos
|
|
||||||
|
|
||||||
#ifndef INITRD_H
|
|
||||||
#define INITRD_H
|
|
||||||
|
|
||||||
#define TAR_BLOCK_SIZE 512
|
|
||||||
|
|
||||||
#include "system.h"
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
char filename[100];
|
|
||||||
char mode[8];
|
|
||||||
char owner[8];
|
|
||||||
char group[8];
|
|
||||||
char size[12];
|
|
||||||
char mtime[12];
|
|
||||||
char checksum[8];
|
|
||||||
char typeflag;
|
|
||||||
char linkname[100];
|
|
||||||
char magic[6];
|
|
||||||
char version[2];
|
|
||||||
char uname[32];
|
|
||||||
char gname[32];
|
|
||||||
char devmajor[8];
|
|
||||||
char devminor[8];
|
|
||||||
char prefix[155];
|
|
||||||
} tar_header_t;
|
|
||||||
|
|
||||||
void tar_find_file(uint8_t *tar_start, const char* filename);
|
|
||||||
void ls_initrd(uint8_t* initrd, int verbose);
|
|
||||||
void cat_initrd(uint8_t* initrd, const char* filename);
|
|
||||||
int tar_file_to_buffer(uint8_t* initrd, const char* filename, char* buffer);
|
|
||||||
uint32_t tar_get_file_size(uint8_t* initrd, const char* filename);
|
|
||||||
void* load_file_from_initrd(uint8_t* initrd, const char* filename);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
// Kernel heap management
|
|
||||||
// Author: xamidev
|
|
||||||
// Licensed under the Unlicense. See the repo below.
|
|
||||||
// https://github.com/xamidev/blankos
|
|
||||||
|
|
||||||
#include "kheap.h"
|
|
||||||
#include <stdint.h>
|
|
||||||
#include "system.h"
|
|
||||||
#include "../libk/stdio.h"
|
|
||||||
|
|
||||||
// Free list allocator
|
|
||||||
|
|
||||||
static uint8_t heap[HEAP_SIZE];
|
|
||||||
static block_t* free_list = NULL;
|
|
||||||
|
|
||||||
void init_alloc()
|
|
||||||
{
|
|
||||||
free_list = (block_t*)heap;
|
|
||||||
free_list->size = HEAP_SIZE-sizeof(block_t);
|
|
||||||
free_list->next = NULL;
|
|
||||||
printf("[kernel] initialized heap and allocator, start=0x%x\n", heap);
|
|
||||||
}
|
|
||||||
|
|
||||||
void* malloc(size_t size)
|
|
||||||
{
|
|
||||||
block_t* prev = NULL;
|
|
||||||
block_t* curr = free_list;
|
|
||||||
|
|
||||||
while (curr != NULL)
|
|
||||||
{
|
|
||||||
if (curr->size >= size)
|
|
||||||
{
|
|
||||||
if (curr->size > (size_t)(size + sizeof(block_t)))
|
|
||||||
{
|
|
||||||
block_t* new_block = (block_t*)((uint8_t*)curr + sizeof(block_t) + size);
|
|
||||||
new_block->size = curr->size - size - sizeof(block_t);
|
|
||||||
new_block->next = curr->next;
|
|
||||||
curr->size = size;
|
|
||||||
curr->next = new_block;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prev == NULL)
|
|
||||||
{
|
|
||||||
free_list = curr->next;
|
|
||||||
} else {
|
|
||||||
prev->next = curr->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (void*)((uint8_t*)curr + sizeof(block_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
prev = curr;
|
|
||||||
curr = curr->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* calloc(size_t num, size_t size)
|
|
||||||
{
|
|
||||||
size_t total_size = num*size;
|
|
||||||
void* ptr = malloc(total_size);
|
|
||||||
|
|
||||||
if (ptr != NULL)
|
|
||||||
{
|
|
||||||
uint8_t* byte_ptr = (uint8_t*)ptr;
|
|
||||||
for (size_t i=0; i<total_size; i++)
|
|
||||||
{
|
|
||||||
byte_ptr[i] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void free(void* ptr)
|
|
||||||
{
|
|
||||||
if (ptr == NULL) return;
|
|
||||||
|
|
||||||
block_t* block_to_free = (block_t*)((uint8_t*)ptr - sizeof(block_t));
|
|
||||||
block_to_free->next = free_list;
|
|
||||||
free_list = block_to_free;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// Kernel heap management header
|
|
||||||
// Author: xamidev
|
|
||||||
// Licensed under the Unlicense. See the repo below.
|
|
||||||
// https://github.com/xamidev/blankos
|
|
||||||
|
|
||||||
#ifndef KHEAP_H
|
|
||||||
#define KHEAP_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include "system.h"
|
|
||||||
|
|
||||||
typedef struct block
|
|
||||||
{
|
|
||||||
size_t size;
|
|
||||||
struct block* next;
|
|
||||||
} block_t;
|
|
||||||
|
|
||||||
#define HEAP_SIZE 1024*1024 // 1MB malloc-able
|
|
||||||
|
|
||||||
void init_alloc();
|
|
||||||
void* malloc(size_t size);
|
|
||||||
void free(void* ptr);
|
|
||||||
void* calloc(size_t num, size_t size);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -12,8 +12,6 @@
|
|||||||
#include "../drivers/framebuffer.h"
|
#include "../drivers/framebuffer.h"
|
||||||
#include "kmain.h"
|
#include "kmain.h"
|
||||||
#include "multiboot2.h"
|
#include "multiboot2.h"
|
||||||
#include "kheap.h"
|
|
||||||
#include "initrd.h"
|
|
||||||
|
|
||||||
void kmain(multiboot2_info *mb_info)
|
void kmain(multiboot2_info *mb_info)
|
||||||
{
|
{
|
||||||
@@ -60,7 +58,6 @@ void kmain(multiboot2_info *mb_info)
|
|||||||
serial_printf(3, "Framebuffer Pitch: %u", fb_info->framebuffer_pitch);
|
serial_printf(3, "Framebuffer Pitch: %u", fb_info->framebuffer_pitch);
|
||||||
serial_printf(3, "Framebuffer BPP: %u", fb_info->framebuffer_bpp);
|
serial_printf(3, "Framebuffer BPP: %u", fb_info->framebuffer_bpp);
|
||||||
}
|
}
|
||||||
psf_init();
|
|
||||||
printf("[kernel] multiboot2 info at 0x%x, size=%u\n", mb_info, mb_info->total_size);
|
printf("[kernel] multiboot2 info at 0x%x, size=%u\n", mb_info, mb_info->total_size);
|
||||||
printf("[kernel] framebuffer discovered at 0x%x\n", (unsigned int)fb_info->framebuffer_addr);
|
printf("[kernel] framebuffer discovered at 0x%x\n", (unsigned int)fb_info->framebuffer_addr);
|
||||||
printf("[kernel] fb0: width=%u, height=%u, pitch=%u, bpp=%u\n", fb_info->framebuffer_width, fb_info->framebuffer_height, fb_info->framebuffer_pitch, fb_info->framebuffer_bpp);
|
printf("[kernel] fb0: width=%u, height=%u, pitch=%u, bpp=%u\n", fb_info->framebuffer_width, fb_info->framebuffer_height, fb_info->framebuffer_pitch, fb_info->framebuffer_bpp);
|
||||||
@@ -109,8 +106,6 @@ void kmain(multiboot2_info *mb_info)
|
|||||||
irq_install();
|
irq_install();
|
||||||
__asm__ __volatile__("sti");
|
__asm__ __volatile__("sti");
|
||||||
|
|
||||||
init_alloc();
|
|
||||||
|
|
||||||
timer_install();
|
timer_install();
|
||||||
printf("Nothing to do, halting...");
|
printf("Nothing to do, halting...");
|
||||||
asm("hlt");
|
asm("hlt");
|
||||||
|
|||||||
7
makefile
7
makefile
@@ -49,16 +49,11 @@ toolchain:
|
|||||||
wget $(TOOLCHAIN_SRC)
|
wget $(TOOLCHAIN_SRC)
|
||||||
tar xf $(TOOLCHAIN_FILE)
|
tar xf $(TOOLCHAIN_FILE)
|
||||||
|
|
||||||
iso: kernel.elf initrd
|
iso: kernel.elf
|
||||||
cp kernel.elf iso/boot/kernel.elf
|
cp kernel.elf iso/boot/kernel.elf
|
||||||
cp grub.cfg iso/boot/grub/grub.cfg
|
cp grub.cfg iso/boot/grub/grub.cfg
|
||||||
grub-mkrescue iso -o blankos.iso
|
grub-mkrescue iso -o blankos.iso
|
||||||
|
|
||||||
initrd:
|
|
||||||
mkdir -p iso/boot/grub
|
|
||||||
tar -cf $(OBJ_DIR)/initrd.tar -C initrd .
|
|
||||||
cp $(OBJ_DIR)/initrd.tar iso/boot
|
|
||||||
|
|
||||||
run: iso
|
run: iso
|
||||||
qemu-system-i386 -drive file=blankos.iso,format=raw
|
qemu-system-i386 -drive file=blankos.iso,format=raw
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user