mirror of
https://github.com/ksherlock/profuse.git
synced 2024-06-08 04:29:30 +00:00
git-svn-id: https://profuse.googlecode.com/svn/branches/v2@115 aa027e90-d47c-11dd-86d7-074df07e0730
This commit is contained in:
parent
1ba3d83ec7
commit
ee7736d7b8
|
@ -9,6 +9,7 @@
|
|||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace ProFUSE;
|
||||
using namespace LittleEndian;
|
||||
|
@ -89,6 +90,10 @@ DavexDiskImage *DavexDiskImage::Open(MappedFile *file)
|
|||
}
|
||||
|
||||
DavexDiskImage *DavexDiskImage::Create(const char *name, size_t blocks)
|
||||
{
|
||||
return Create(name, blocks, "Untitled");
|
||||
}
|
||||
DavexDiskImage *DavexDiskImage::Create(const char *name, size_t blocks, const char *vname)
|
||||
{
|
||||
#undef __METHOD__
|
||||
#define __METHOD__ "DavexDiskImage::Create"
|
||||
|
@ -122,7 +127,10 @@ DavexDiskImage *DavexDiskImage::Create(const char *name, size_t blocks)
|
|||
header.push32le(0);
|
||||
|
||||
// volume Name
|
||||
header.pushBytes("\x08Untitled", 9);
|
||||
if (!vname || !*vname) vname = "Untitled";
|
||||
unsigned l = std::strlen(vname);
|
||||
header.push8(std::min(l, 15u));
|
||||
header.pushBytes(vname, std::min(l, 15u));
|
||||
|
||||
// name + reserved.
|
||||
header.resize(64);
|
||||
|
|
|
@ -14,6 +14,7 @@ public:
|
|||
virtual ~DavexDiskImage();
|
||||
|
||||
static DavexDiskImage *Create(const char *name, size_t blocks);
|
||||
static DavexDiskImage *Create(const char *name, size_t blocks, const char *vname);
|
||||
static DavexDiskImage *Open(MappedFile *);
|
||||
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include "Endian.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace ProFUSE;
|
||||
using namespace BigEndian;
|
||||
|
@ -85,6 +87,11 @@ static uint8_t FormatByte(size_t blocks)
|
|||
}
|
||||
}
|
||||
DiskCopy42Image *DiskCopy42Image::Create(const char *name, size_t blocks)
|
||||
{
|
||||
return Create(name, blocks, "Untitled");
|
||||
}
|
||||
|
||||
DiskCopy42Image *DiskCopy42Image::Create(const char *name, size_t blocks, const char *vname)
|
||||
{
|
||||
MappedFile *file = new MappedFile(name, blocks * 512 + 84);
|
||||
file->setOffset(84);
|
||||
|
@ -93,7 +100,12 @@ DiskCopy42Image *DiskCopy42Image::Create(const char *name, size_t blocks)
|
|||
Buffer header(84);
|
||||
|
||||
// name -- 64byte pstring.
|
||||
header.pushBytes("\x08Untitled", 9);
|
||||
|
||||
if (vname == NULL) vname = "Untitled";
|
||||
unsigned l = std::strlen(vname);
|
||||
header.push8(std::min(l, 63u));
|
||||
header.pushBytes(vname, std::min(l, 63u));
|
||||
|
||||
header.resize(64);
|
||||
|
||||
// data size -- number of bytes
|
||||
|
@ -139,11 +151,14 @@ DiskCopy42Image *DiskCopy42Image::Create(const char *name, size_t blocks)
|
|||
|
||||
void DiskCopy42Image::Validate(MappedFile *file)
|
||||
{
|
||||
#undef __METHOD__
|
||||
#define __METHOD__ "DiskCopy42Image::Validate"
|
||||
|
||||
size_t bytes = 0;
|
||||
size_t size = file->fileSize();
|
||||
const void *data = file->fileData();
|
||||
bool ok = false;
|
||||
uint32_t checksum;
|
||||
uint32_t checksum = 0;
|
||||
|
||||
do {
|
||||
if (size < 84) break;
|
||||
|
@ -167,6 +182,8 @@ void DiskCopy42Image::Validate(MappedFile *file)
|
|||
ok = true;
|
||||
} while (false);
|
||||
|
||||
if (!ok)
|
||||
throw Exception(__METHOD__ ": Invalid file format.");
|
||||
|
||||
uint32_t cs = Checksum(64 + (uint8_t *)data, bytes);
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ public:
|
|||
virtual ~DiskCopy42Image();
|
||||
|
||||
static DiskCopy42Image *Create(const char *name, size_t blocks);
|
||||
static DiskCopy42Image *Create(const char *name, size_t blocks, const char *vname);
|
||||
|
||||
static DiskCopy42Image *Open(MappedFile *);
|
||||
|
||||
static uint32_t Checksum(void *data, size_t size);
|
||||
|
|
64
Endian.h
64
Endian.h
|
@ -8,68 +8,68 @@
|
|||
namespace LittleEndian {
|
||||
|
||||
|
||||
uint8_t Read8(const void *vp)
|
||||
inline uint8_t Read8(const void *vp)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)vp;
|
||||
return (p[0]);
|
||||
}
|
||||
|
||||
uint16_t Read16(const void *vp)
|
||||
inline uint16_t Read16(const void *vp)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)vp;
|
||||
return p[0] | (p[1] << 8);
|
||||
}
|
||||
|
||||
uint32_t Read24(const void *vp)
|
||||
inline uint32_t Read24(const void *vp)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)vp;
|
||||
return (p[0]) | (p[1] << 8) | (p[2] << 16);
|
||||
}
|
||||
|
||||
|
||||
uint32_t Read32(const void *vp)
|
||||
inline uint32_t Read32(const void *vp)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)vp;
|
||||
return (p[0]) | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
|
||||
}
|
||||
|
||||
|
||||
uint8_t Read8(const void *vp, unsigned offset)
|
||||
inline uint8_t Read8(const void *vp, unsigned offset)
|
||||
{
|
||||
return Read8(offset + (const uint8_t *)vp);
|
||||
}
|
||||
|
||||
uint16_t Read16(const void *vp, unsigned offset)
|
||||
inline uint16_t Read16(const void *vp, unsigned offset)
|
||||
{
|
||||
return Read16(offset + (const uint8_t *)vp);
|
||||
}
|
||||
|
||||
uint32_t Read24(const void *vp, unsigned offset)
|
||||
inline uint32_t Read24(const void *vp, unsigned offset)
|
||||
{
|
||||
return Read24(offset + (const uint8_t *)vp);
|
||||
}
|
||||
|
||||
uint32_t Read32(const void *vp, unsigned offset)
|
||||
inline uint32_t Read32(const void *vp, unsigned offset)
|
||||
{
|
||||
return Read32(offset + (const uint8_t *)vp);
|
||||
}
|
||||
|
||||
|
||||
// write
|
||||
void Write8(void *vp, uint8_t x)
|
||||
inline void Write8(void *vp, uint8_t x)
|
||||
{
|
||||
uint8_t *p = (uint8_t *)vp;
|
||||
p[0] = x;
|
||||
}
|
||||
|
||||
void Write16(void *vp, uint16_t x)
|
||||
inline void Write16(void *vp, uint16_t x)
|
||||
{
|
||||
uint8_t *p = (uint8_t *)vp;
|
||||
p[0] = (x) & 0xff;
|
||||
p[1] = (x >> 8) & 0xff;
|
||||
}
|
||||
|
||||
void Write24(void *vp, uint32_t x)
|
||||
inline void Write24(void *vp, uint32_t x)
|
||||
{
|
||||
uint8_t *p = (uint8_t *)vp;
|
||||
p[0] = (x) & 0xff;
|
||||
|
@ -77,7 +77,7 @@ namespace LittleEndian {
|
|||
p[2] = (x >> 16) & 0xff;
|
||||
}
|
||||
|
||||
void Write32(void *vp, uint32_t x)
|
||||
inline void Write32(void *vp, uint32_t x)
|
||||
{
|
||||
uint8_t *p = (uint8_t *)vp;
|
||||
p[0] = (x) & 0xff;
|
||||
|
@ -86,22 +86,22 @@ namespace LittleEndian {
|
|||
p[3] = (x >> 24) & 0xff;
|
||||
}
|
||||
|
||||
void Write8(void *vp, unsigned offset, uint8_t x)
|
||||
inline void Write8(void *vp, unsigned offset, uint8_t x)
|
||||
{
|
||||
Write8(offset + (uint8_t *)vp, x);
|
||||
}
|
||||
|
||||
void Write16(void *vp, unsigned offset, uint16_t x)
|
||||
inline void Write16(void *vp, unsigned offset, uint16_t x)
|
||||
{
|
||||
Write16(offset + (uint8_t *)vp, x);
|
||||
}
|
||||
|
||||
void Write24(void *vp, unsigned offset, uint32_t x)
|
||||
inline void Write24(void *vp, unsigned offset, uint32_t x)
|
||||
{
|
||||
Write24(offset + (uint8_t *)vp, x);
|
||||
}
|
||||
|
||||
void Write32(void *vp, unsigned offset, uint32_t x)
|
||||
inline void Write32(void *vp, unsigned offset, uint32_t x)
|
||||
{
|
||||
Write32(offset + (uint8_t *)vp, x);
|
||||
}
|
||||
|
@ -112,48 +112,48 @@ namespace LittleEndian {
|
|||
namespace BigEndian {
|
||||
|
||||
|
||||
uint8_t Read8(const void *vp)
|
||||
inline uint8_t Read8(const void *vp)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)vp;
|
||||
return p[0];
|
||||
}
|
||||
|
||||
uint16_t Read16(const void *vp)
|
||||
inline uint16_t Read16(const void *vp)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)vp;
|
||||
return (p[0] << 8) | (p[1]);
|
||||
}
|
||||
|
||||
uint32_t Read24(const void *vp)
|
||||
inline uint32_t Read24(const void *vp)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)vp;
|
||||
return (p[0] << 16) | (p[1] << 8) | (p[2]);
|
||||
}
|
||||
|
||||
|
||||
uint32_t Read32(const void *vp)
|
||||
inline uint32_t Read32(const void *vp)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)vp;
|
||||
return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | (p[3]);
|
||||
}
|
||||
|
||||
|
||||
uint8_t Read8(const void *vp, unsigned offset)
|
||||
inline uint8_t Read8(const void *vp, unsigned offset)
|
||||
{
|
||||
return Read8(offset + (const uint8_t *)vp);
|
||||
}
|
||||
|
||||
uint16_t Read16(const void *vp, unsigned offset)
|
||||
inline uint16_t Read16(const void *vp, unsigned offset)
|
||||
{
|
||||
return Read16(offset + (const uint8_t *)vp);
|
||||
}
|
||||
|
||||
uint32_t Read24(const void *vp, unsigned offset)
|
||||
inline uint32_t Read24(const void *vp, unsigned offset)
|
||||
{
|
||||
return Read24(offset + (const uint8_t *)vp);
|
||||
}
|
||||
|
||||
uint32_t Read32(const void *vp, unsigned offset)
|
||||
inline uint32_t Read32(const void *vp, unsigned offset)
|
||||
{
|
||||
return Read32(offset + (const uint8_t *)vp);
|
||||
}
|
||||
|
@ -161,20 +161,20 @@ namespace BigEndian {
|
|||
|
||||
|
||||
// write
|
||||
void Write8(void *vp, uint8_t x)
|
||||
inline void Write8(void *vp, uint8_t x)
|
||||
{
|
||||
uint8_t *p = (uint8_t *)vp;
|
||||
p[0] = x;
|
||||
}
|
||||
|
||||
void Write16(void *vp, uint16_t x)
|
||||
inline void Write16(void *vp, uint16_t x)
|
||||
{
|
||||
uint8_t *p = (uint8_t *)vp;
|
||||
p[0] = (x >> 8) & 0xff;
|
||||
p[1] = (x) & 0xff;
|
||||
}
|
||||
|
||||
void Write24(void *vp, uint32_t x)
|
||||
inline void Write24(void *vp, uint32_t x)
|
||||
{
|
||||
uint8_t *p = (uint8_t *)vp;
|
||||
p[0] = (x >> 16) & 0xff;
|
||||
|
@ -182,7 +182,7 @@ namespace BigEndian {
|
|||
p[2] = (x) & 0xff;
|
||||
}
|
||||
|
||||
void Write32(void *vp, uint32_t x)
|
||||
inline void Write32(void *vp, uint32_t x)
|
||||
{
|
||||
uint8_t *p = (uint8_t *)vp;
|
||||
p[0] = (x >> 24) & 0xff;
|
||||
|
@ -191,22 +191,22 @@ namespace BigEndian {
|
|||
p[3] = (x) & 0xff;
|
||||
}
|
||||
|
||||
void Write8(void *vp, unsigned offset, uint8_t x)
|
||||
inline void Write8(void *vp, unsigned offset, uint8_t x)
|
||||
{
|
||||
Write8(offset + (uint8_t *)vp, x);
|
||||
}
|
||||
|
||||
void Write16(void *vp, unsigned offset, uint16_t x)
|
||||
inline void Write16(void *vp, unsigned offset, uint16_t x)
|
||||
{
|
||||
Write16(offset + (uint8_t *)vp, x);
|
||||
}
|
||||
|
||||
void Write24(void *vp, unsigned offset, uint32_t x)
|
||||
inline void Write24(void *vp, unsigned offset, uint32_t x)
|
||||
{
|
||||
Write24(offset + (uint8_t *)vp, x);
|
||||
}
|
||||
|
||||
void Write32(void *vp, unsigned offset, uint32_t x)
|
||||
inline void Write32(void *vp, unsigned offset, uint32_t x)
|
||||
{
|
||||
Write32(offset + (uint8_t *)vp, x);
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ UniversalDiskImage *UniversalDiskImage::Open(MappedFile *file)
|
|||
void UniversalDiskImage::Validate(MappedFile *file)
|
||||
{
|
||||
#undef __METHOD__
|
||||
#define __METHOD__ "DavexDiskImage::Validate"
|
||||
#define __METHOD__ "UniversalDiskImage::Validate"
|
||||
|
||||
const void *data = file->fileData();
|
||||
size_t size = file->fileSize();
|
||||
|
|
288
newfs_prodos.cpp
Normal file
288
newfs_prodos.cpp
Normal file
|
@ -0,0 +1,288 @@
|
|||
#include "BlockDevice.h"
|
||||
#include "UniversalDiskImage.h"
|
||||
#include "DiskCopy42Image.h"
|
||||
#include "DavexDiskImage.h"
|
||||
#include "RawDevice.h"
|
||||
#include "Exception.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#define NEWFS_VERSION "0.1"
|
||||
|
||||
using namespace ProFUSE;
|
||||
|
||||
void usage()
|
||||
{
|
||||
std::printf("newfs_prodos %s\n", NEWFS_VERSION);
|
||||
std::printf("\n");
|
||||
|
||||
std::printf("newfs_prodos [-v volume_name] [-s size] [-f format] file\n");
|
||||
std::printf("\n");
|
||||
std::printf(" -v volume_name specify the volume name.\n"
|
||||
" Default is Untitled.\n"
|
||||
" -s size specify size in blocks.\n"
|
||||
" Default is 1600 blocks (800K)\n"
|
||||
" -f format specify the disk image format. Valid values are:\n"
|
||||
" 2mg Universal Disk Image (default)\n"
|
||||
" dc42 DiskCopy 4.2 Image\n"
|
||||
" po ProDOS Order Disk Image\n"
|
||||
" do DOS Order Disk Image\n"
|
||||
" davex Davex Disk Image\n"
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* \d+ by block
|
||||
* \d+[Kk] by kilobyte
|
||||
* \d+[Mm] by megabyte
|
||||
*/
|
||||
unsigned parseBlocks(const char *cp)
|
||||
{
|
||||
unsigned long blocks = 0;
|
||||
char *mod;
|
||||
|
||||
errno = 0;
|
||||
blocks = std::strtoul(cp, &mod, 10);
|
||||
if (errno) return -1;
|
||||
|
||||
if (mod == cp) return -1;
|
||||
if (blocks > 0xffff) return -1;
|
||||
|
||||
if (mod)
|
||||
{
|
||||
switch(*mod)
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case 'm': // 1m = 1024*1024b = 2048 blocks
|
||||
case 'M':
|
||||
blocks *= 2048;
|
||||
break;
|
||||
|
||||
case 'k': // 1k = 1024b = 2 blocks
|
||||
case 'K':
|
||||
blocks *= 2;
|
||||
break;
|
||||
}
|
||||
if (blocks > 0xffff) return -1;
|
||||
}
|
||||
|
||||
|
||||
return (unsigned)blocks;
|
||||
}
|
||||
|
||||
unsigned parseFormat(const char *type, unsigned defv = 0)
|
||||
{
|
||||
if (type == 0 || *type == 0) return defv;
|
||||
|
||||
if (::strcasecmp(type, "2mg") == 0)
|
||||
return '2IMG';
|
||||
if (::strcasecmp(type, "2img") == 0)
|
||||
return '2IMG';
|
||||
if (::strcasecmp(type, "dc42") == 0)
|
||||
return 'DC42';
|
||||
if (::strcasecmp(type, "po") == 0)
|
||||
return 'PO__';
|
||||
if (::strcasecmp(type, "do") == 0)
|
||||
return 'DO__';
|
||||
if (::strcasecmp(type, "davex") == 0)
|
||||
return 'DVX_';
|
||||
|
||||
return defv;
|
||||
}
|
||||
|
||||
// return the filename extension, NULL if none.
|
||||
const char *extname(const char *src)
|
||||
{
|
||||
if (!src) return NULL;
|
||||
unsigned l = std::strlen(src);
|
||||
|
||||
for (unsigned i = 0; i < l; ++i)
|
||||
{
|
||||
char c = src[l - 1 - i];
|
||||
if (c == '/') return NULL;
|
||||
if (c == '.') return src + l - i;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// return the basename, without an extension.
|
||||
std::string filename(const std::string& src)
|
||||
{
|
||||
unsigned start;
|
||||
unsigned end;
|
||||
|
||||
|
||||
if (src.empty()) return std::string("");
|
||||
|
||||
start = end = 0;
|
||||
|
||||
for(unsigned i = 0, l = src.length(); i < l; ++i)
|
||||
{
|
||||
char c = src[i];
|
||||
if (c == '/') start = end = i + 1;
|
||||
if (c == '.') end = i;
|
||||
}
|
||||
|
||||
if (start == src.length()) return std::string("");
|
||||
|
||||
if (start == end) return src.substr(start);
|
||||
return src.substr(start, end - start);
|
||||
}
|
||||
|
||||
|
||||
bool ValidName(const char *cp)
|
||||
{
|
||||
|
||||
unsigned i = 0;
|
||||
if (!cp || !*cp) return false;
|
||||
|
||||
if (!isalpha(*cp)) return false;
|
||||
|
||||
for (i = 1; i <17; ++i)
|
||||
{
|
||||
unsigned char c = cp[i];
|
||||
if (c == 0) break;
|
||||
|
||||
if (c == '.') continue;
|
||||
if (isalnum(c)) continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return i < 16;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
unsigned blocks = 1600;
|
||||
std::string volumeName;
|
||||
std::string fileName;
|
||||
int format = 0;
|
||||
const char *fname;
|
||||
int c;
|
||||
|
||||
// ctype uses ascii only.
|
||||
::setlocale(LC_ALL, "C");
|
||||
|
||||
while ( (c = ::getopt(argc, argv, "hf:s:v:")) != -1)
|
||||
{
|
||||
switch(c)
|
||||
{
|
||||
case '?':
|
||||
case 'h':
|
||||
usage();
|
||||
return 0;
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
volumeName = optarg;
|
||||
// make sure it's legal.
|
||||
if (!ValidName(optarg))
|
||||
{
|
||||
std::fprintf(stderr, "Error: `%s' is not a valid ProDOS volume name.\n", optarg);
|
||||
return 0x40;
|
||||
}
|
||||
break;
|
||||
|
||||
case 's':
|
||||
blocks = parseBlocks(optarg);
|
||||
if (blocks > 0xffff)
|
||||
{
|
||||
std::fprintf(stderr, "Error: `%s' is not a valid disk size.\n", optarg);
|
||||
return 0x5a;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
{
|
||||
format = parseFormat(optarg);
|
||||
if (format == 0)
|
||||
{
|
||||
std::fprintf(stderr, "Error: `%s' is not a supposed disk image format.\n", optarg);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (argc != 1)
|
||||
{
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
||||
fname = argv[0];
|
||||
fileName = argv[0];
|
||||
|
||||
// generate a filename.
|
||||
if (volumeName.empty())
|
||||
{
|
||||
volumeName = filename(fileName);
|
||||
if (volumeName.empty() || !ValidName(volumeName.c_str()))
|
||||
volumeName = "Untitled";
|
||||
}
|
||||
|
||||
if (format == 0) format = parseFormat(extname(fname));
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
std::auto_ptr<BlockDevice> device;
|
||||
//auto_ptr<VolumeDirectory> volume;
|
||||
|
||||
// todo -- check if path matches /dev/xxx; if so, use RawDevice.
|
||||
// todo -- check if file exists at path?
|
||||
|
||||
switch(format)
|
||||
{
|
||||
case 'DC42':
|
||||
// todo -- pass in volume name
|
||||
device.reset(DiskCopy42Image::Create(fname, blocks, volumeName.c_str()));
|
||||
break;
|
||||
|
||||
case 'PO__':
|
||||
device.reset(ProDOSOrderDiskImage::Create(fname, blocks));
|
||||
break;
|
||||
|
||||
case 'DO__':
|
||||
device.reset(DOSOrderDiskImage::Create(fname, blocks));
|
||||
break;
|
||||
|
||||
case 'DVX_':
|
||||
device.reset(DavexDiskImage::Create(fname, blocks, volumeName.c_str()));
|
||||
break;
|
||||
|
||||
case '2IMG':
|
||||
default:
|
||||
device.reset(UniversalDiskImage::Create(fname, blocks));
|
||||
}
|
||||
|
||||
// VolumeDirectory assumes ownership of device,
|
||||
// but doesn't release it on exception.
|
||||
//volume.reset(new VolumeDirectory(name, device));
|
||||
device.release();
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
std::fprintf(stderr, "Error: %s\n", e.what());
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user