mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-15 10:17:20 +00:00
symlink support in devfs.
it only barely works so don't get too carried away.. I noticed that teh symlink is length 0.. I guess I'll fix that tomorrow.. it also sometimes panics with "cleaned vnode isn't" but it's not more broken than it was before.. I really want to go over it with someone who understands the lifecycle of a vnode better than I do.. terry? kirk? david? john?
This commit is contained in:
parent
d026a5d864
commit
ed9a71b7fc
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=17567
@ -2,7 +2,7 @@
|
|||||||
/*
|
/*
|
||||||
* Written by Julian Elischer (julian@DIALix.oz.au)
|
* Written by Julian Elischer (julian@DIALix.oz.au)
|
||||||
*
|
*
|
||||||
* $Header: /home/ncvs/src/sys/miscfs/devfs/devfs_tree.c,v 1.26 1996/07/24 21:22:36 phk Exp $
|
* $Header: /home/ncvs/src/sys/miscfs/devfs/devfs_tree.c,v 1.27 1996/07/30 18:00:32 bde Exp $
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "param.h"
|
#include "param.h"
|
||||||
@ -157,25 +157,21 @@ dev_finddir(char *orig_path, dn_p dirnode, int create, dn_p *dn_pp)
|
|||||||
* find the next segment of the name *
|
* find the next segment of the name *
|
||||||
\***************************************/
|
\***************************************/
|
||||||
cp = name = path;
|
cp = name = path;
|
||||||
while((*cp != '/') && (*cp != 0))
|
while((*cp != '/') && (*cp != 0)) {
|
||||||
{
|
|
||||||
cp++;
|
cp++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/***********************************************\
|
/***********************************************\
|
||||||
* Check to see if it's the last component *
|
* Check to see if it's the last component *
|
||||||
\***********************************************/
|
\***********************************************/
|
||||||
if(*cp)
|
if(*cp) {
|
||||||
{
|
|
||||||
path = cp + 1; /* path refers to the rest */
|
path = cp + 1; /* path refers to the rest */
|
||||||
*cp = 0; /* name is now a separate string */
|
*cp = 0; /* name is now a separate string */
|
||||||
if(!(*path))
|
if(!(*path))
|
||||||
{
|
{
|
||||||
path = (char *)0; /* was trailing slash */
|
path = (char *)0; /* was trailing slash */
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
path = (char *)0; /* no more to do */
|
path = (char *)0; /* no more to do */
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,13 +179,10 @@ dev_finddir(char *orig_path, dn_p dirnode, int create, dn_p *dn_pp)
|
|||||||
* Start scanning along the linked list *
|
* Start scanning along the linked list *
|
||||||
\***************************************/
|
\***************************************/
|
||||||
devnmp = dev_findname(dirnode,name);
|
devnmp = dev_findname(dirnode,name);
|
||||||
if(devnmp)
|
if(devnmp) { /* check it's a directory */
|
||||||
{ /* check it's a directory */
|
|
||||||
dnp = devnmp->dnp;
|
dnp = devnmp->dnp;
|
||||||
if(dnp->type != DEV_DIR) return ENOTDIR;
|
if(dnp->type != DEV_DIR) return ENOTDIR;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
/***************************************\
|
/***************************************\
|
||||||
* The required element does not exist *
|
* The required element does not exist *
|
||||||
* So we will add it if asked to. *
|
* So we will add it if asked to. *
|
||||||
@ -203,12 +196,9 @@ dev_finddir(char *orig_path, dn_p dirnode, int create, dn_p *dn_pp)
|
|||||||
}
|
}
|
||||||
dnp = devnmp->dnp;
|
dnp = devnmp->dnp;
|
||||||
}
|
}
|
||||||
if(path) /* decide whether to recurse more or return */
|
if(path) { /* decide whether to recurse more or return */
|
||||||
{
|
|
||||||
return (dev_finddir(path,dnp,create,dn_pp));
|
return (dev_finddir(path,dnp,create,dn_pp));
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
*dn_pp = dnp;
|
*dn_pp = dnp;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -246,8 +236,7 @@ dev_add_name(char *name, dn_p dirnode, devnm_p back, dn_p dnp,
|
|||||||
* Allocate and fill out a new directory entry
|
* Allocate and fill out a new directory entry
|
||||||
*/
|
*/
|
||||||
if(!(devnmp = (devnm_p)malloc(sizeof(devnm_t),
|
if(!(devnmp = (devnm_p)malloc(sizeof(devnm_t),
|
||||||
M_DEVFSNAME, M_NOWAIT)))
|
M_DEVFSNAME, M_NOWAIT))) {
|
||||||
{
|
|
||||||
return ENOMEM;
|
return ENOMEM;
|
||||||
}
|
}
|
||||||
bzero(devnmp,sizeof(devnm_t));
|
bzero(devnmp,sizeof(devnm_t));
|
||||||
@ -365,6 +354,10 @@ dev_add_node(int entrytype, union typeinfo *by, dn_p proto, dn_p *dn_pp)
|
|||||||
{
|
{
|
||||||
return ENOMEM;
|
return ENOMEM;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
* If we have a proto, that means that we are duplicating some
|
||||||
|
* other device, which can only happen if we are not at the back plane
|
||||||
|
*/
|
||||||
if(proto) {
|
if(proto) {
|
||||||
/* XXX should check that we are NOT copying a device node */
|
/* XXX should check that we are NOT copying a device node */
|
||||||
bcopy(proto, dnp, sizeof(devnode_t));
|
bcopy(proto, dnp, sizeof(devnode_t));
|
||||||
@ -374,6 +367,9 @@ dev_add_node(int entrytype, union typeinfo *by, dn_p proto, dn_p *dn_pp)
|
|||||||
dnp->vn = NULL;
|
dnp->vn = NULL;
|
||||||
dnp->len = 0;
|
dnp->len = 0;
|
||||||
} else {
|
} else {
|
||||||
|
/*
|
||||||
|
* We have no prototype, so start off with a clean slate
|
||||||
|
*/
|
||||||
bzero(dnp,sizeof(devnode_t));
|
bzero(dnp,sizeof(devnode_t));
|
||||||
dnp->type = entrytype;
|
dnp->type = entrytype;
|
||||||
TIMEVAL_TO_TIMESPEC(&time,&(dnp->ctime))
|
TIMEVAL_TO_TIMESPEC(&time,&(dnp->ctime))
|
||||||
@ -403,6 +399,26 @@ dev_add_node(int entrytype, union typeinfo *by, dn_p proto, dn_p *dn_pp)
|
|||||||
dnp->ops = &devfs_vnodeop_p;
|
dnp->ops = &devfs_vnodeop_p;
|
||||||
dnp->mode |= 0555; /* default perms */
|
dnp->mode |= 0555; /* default perms */
|
||||||
break;
|
break;
|
||||||
|
case DEV_SLNK:
|
||||||
|
/*
|
||||||
|
* As it's a symlink allocate and store the link info
|
||||||
|
* Symlinks should only ever be created by the user,
|
||||||
|
* so they are not on the back plane and should not be
|
||||||
|
* propogated forward.. a bit like directories in that way..
|
||||||
|
* A symlink only exists on one plane and has it's own
|
||||||
|
* node.. therefore we might be on any random plane.
|
||||||
|
*/
|
||||||
|
dnp->by.Slnk.name = malloc(by->Slnk.namelen+1,
|
||||||
|
M_DEVFSNODE, M_NOWAIT);
|
||||||
|
if (!dnp->by.Slnk.name) {
|
||||||
|
free(dnp,M_DEVFSNODE);
|
||||||
|
return ENOMEM;
|
||||||
|
}
|
||||||
|
strncpy(dnp->by.Slnk.name,by->Slnk.name,by->Slnk.namelen);
|
||||||
|
dnp->by.Slnk.namelen = by->Slnk.namelen;
|
||||||
|
dnp->ops = &devfs_vnodeop_p;
|
||||||
|
dnp->mode |= 0555; /* default perms */
|
||||||
|
break;
|
||||||
/*******************************************************\
|
/*******************************************************\
|
||||||
* The rest of these can't happen except in the back plane*
|
* The rest of these can't happen except in the back plane*
|
||||||
\*******************************************************/
|
\*******************************************************/
|
||||||
@ -461,6 +477,9 @@ devfs_dn_free(dn_p dnp)
|
|||||||
if(--dnp->links <= 0 ) /* can be -1 for initial free, on error */
|
if(--dnp->links <= 0 ) /* can be -1 for initial free, on error */
|
||||||
{
|
{
|
||||||
/*probably need to do other cleanups XXX */
|
/*probably need to do other cleanups XXX */
|
||||||
|
if(dnp->type == DEV_SLNK) {
|
||||||
|
free(dnp->by.Slnk.name,M_DEVFSNODE);
|
||||||
|
}
|
||||||
devfs_dropvnode(dnp);
|
devfs_dropvnode(dnp);
|
||||||
free (dnp, M_DEVFSNODE);
|
free (dnp, M_DEVFSNODE);
|
||||||
}
|
}
|
||||||
@ -500,7 +519,8 @@ devfs_add_fronts(devnm_p parent,devnm_p child)
|
|||||||
for (falias = parent->next_front; falias; falias = falias->next_front)
|
for (falias = parent->next_front; falias; falias = falias->next_front)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* If a Dir (XXX symlink too one day)
|
* If a Dir (XXX symlink too one day?)
|
||||||
|
* (...Nope, symlinks don't propogate..)
|
||||||
* Make the node, using the original as a prototype)
|
* Make the node, using the original as a prototype)
|
||||||
*/
|
*/
|
||||||
if(type == DEV_DIR) {
|
if(type == DEV_DIR) {
|
||||||
@ -601,6 +621,7 @@ devfs_free_plane(struct devfsmount *devfs_mp_p)
|
|||||||
* Create and link in a new front element.. *
|
* Create and link in a new front element.. *
|
||||||
* Parent can be 0 for a root node *
|
* Parent can be 0 for a root node *
|
||||||
* Not presently usable to make a symlink XXX *
|
* Not presently usable to make a symlink XXX *
|
||||||
|
* (Ok, symlinks don't propogate)
|
||||||
* recursively will create subnodes corresponding to equivalent *
|
* recursively will create subnodes corresponding to equivalent *
|
||||||
* child nodes in the base level *
|
* child nodes in the base level *
|
||||||
\***************************************************************/
|
\***************************************************************/
|
||||||
@ -703,7 +724,7 @@ dev_free_name(devnm_p devnmp)
|
|||||||
*/
|
*/
|
||||||
if(parent) /* if not fs root */
|
if(parent) /* if not fs root */
|
||||||
{
|
{
|
||||||
if( *devnmp->prevp = devnmp->next)/* yes, assign */
|
if( (*devnmp->prevp = devnmp->next) )/* yes, assign */
|
||||||
{
|
{
|
||||||
devnmp->next->prevp = devnmp->prevp;
|
devnmp->next->prevp = devnmp->prevp;
|
||||||
}
|
}
|
||||||
@ -721,9 +742,9 @@ dev_free_name(devnm_p devnmp)
|
|||||||
* from that..
|
* from that..
|
||||||
* Remember that we may not HAVE a backing node.
|
* Remember that we may not HAVE a backing node.
|
||||||
*/
|
*/
|
||||||
if (back = devnmp->as.front.realthing) /* yes an assign */
|
if ( (back = devnmp->as.front.realthing) ) /* yes an assign */
|
||||||
{
|
{
|
||||||
if( *devnmp->prev_frontp = devnmp->next_front)/* yes, assign */
|
if((*devnmp->prev_frontp = devnmp->next_front))/* yes, assign */
|
||||||
{
|
{
|
||||||
devnmp->next_front->prev_frontp = devnmp->prev_frontp;
|
devnmp->next_front->prev_frontp = devnmp->prev_frontp;
|
||||||
}
|
}
|
||||||
@ -773,9 +794,12 @@ DBPRINT((" vntodn "));
|
|||||||
printf("No references! ");
|
printf("No references! ");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if((vn_p->v_type == VBAD) || (vn_p->v_type == VNON))
|
switch(vn_p->v_type) {
|
||||||
{
|
case VBAD:
|
||||||
printf("bad-type2 ");
|
printf("bad-type2 (VBAD)");
|
||||||
|
return(EINVAL);
|
||||||
|
case VNON:
|
||||||
|
printf("bad-type2 (VNON)");
|
||||||
return(EINVAL);
|
return(EINVAL);
|
||||||
}
|
}
|
||||||
*dn_pp = (dn_p)vn_p->v_data;
|
*dn_pp = (dn_p)vn_p->v_data;
|
||||||
@ -888,6 +912,7 @@ DBPRINT(("(New vnode)"));
|
|||||||
switch(dnp->type)
|
switch(dnp->type)
|
||||||
{
|
{
|
||||||
case DEV_SLNK:
|
case DEV_SLNK:
|
||||||
|
vn_p->v_type = VLNK;
|
||||||
break;
|
break;
|
||||||
case DEV_DIR:
|
case DEV_DIR:
|
||||||
if(dnp->by.Dir.parent == dnp)
|
if(dnp->by.Dir.parent == dnp)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Written by Julian Elischer (julian@DIALix.oz.au)
|
* Written by Julian Elischer (julian@DIALix.oz.au)
|
||||||
*
|
*
|
||||||
* $Header: /home/ncvs/src/sys/miscfs/devfs/devfs_vnops.c,v 1.20 1996/04/07 01:15:02 joerg Exp $
|
* $Header: /home/ncvs/src/sys/miscfs/devfs/devfs_vnops.c,v 1.21 1996/06/12 05:08:34 gpalmer Exp $
|
||||||
*
|
*
|
||||||
* symlinks can wait 'til later.
|
* symlinks can wait 'til later.
|
||||||
*/
|
*/
|
||||||
@ -102,7 +102,7 @@ DBPRINT(("lookup\n"));
|
|||||||
/*
|
/*
|
||||||
* Check accessiblity of directory.
|
* Check accessiblity of directory.
|
||||||
*/
|
*/
|
||||||
if (dir_node->type != DEV_DIR)
|
if (dir_node->type != DEV_DIR) /* XXX or symlink? */
|
||||||
{
|
{
|
||||||
return (ENOTDIR);
|
return (ENOTDIR);
|
||||||
}
|
}
|
||||||
@ -272,7 +272,7 @@ DBPRINT(("MKACCESS "));
|
|||||||
if ((dir_node->mode & ISVTX) &&
|
if ((dir_node->mode & ISVTX) &&
|
||||||
cnp->cn_cred->cr_uid != 0 &&
|
cnp->cn_cred->cr_uid != 0 &&
|
||||||
cnp->cn_cred->cr_uid != dir_node->uid &&
|
cnp->cn_cred->cr_uid != dir_node->uid &&
|
||||||
new_node->uid != cnp->cn_cred->cr_uid) {
|
cnp->cn_cred->cr_uid != new_node->uid) {
|
||||||
VOP_UNLOCK((*result_vnode));
|
VOP_UNLOCK((*result_vnode));
|
||||||
return (EPERM);
|
return (EPERM);
|
||||||
}
|
}
|
||||||
@ -974,8 +974,7 @@ DBPRINT(("link\n"));
|
|||||||
/*
|
/*
|
||||||
* Check we are doing legal things WRT the new flags
|
* Check we are doing legal things WRT the new flags
|
||||||
*/
|
*/
|
||||||
if ((fp->flags & (IMMUTABLE | APPEND))
|
if (fp->flags & (IMMUTABLE | APPEND)) {
|
||||||
|| (tdp->flags & APPEND) /*XXX eh?*/ ) {
|
|
||||||
error = EPERM;
|
error = EPERM;
|
||||||
goto abortit;
|
goto abortit;
|
||||||
}
|
}
|
||||||
@ -1101,7 +1100,6 @@ devfs_rename(struct vop_rename_args *ap)
|
|||||||
*/
|
*/
|
||||||
if ((tp && (tp->flags & (IMMUTABLE | APPEND)))
|
if ((tp && (tp->flags & (IMMUTABLE | APPEND)))
|
||||||
|| (fp->flags & (IMMUTABLE | APPEND))
|
|| (fp->flags & (IMMUTABLE | APPEND))
|
||||||
|| (tdp->flags & APPEND) /*XXX eh?*/
|
|
||||||
|| (fdp->flags & APPEND)) {
|
|| (fdp->flags & APPEND)) {
|
||||||
error = EPERM;
|
error = EPERM;
|
||||||
goto abortit;
|
goto abortit;
|
||||||
@ -1264,6 +1262,7 @@ devfs_rmdir(struct vop_rmdir_args *ap)
|
|||||||
DBPRINT(("rmdir\n"));
|
DBPRINT(("rmdir\n"));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static int
|
static int
|
||||||
devfs_symlink(struct vop_symlink_args *ap)
|
devfs_symlink(struct vop_symlink_args *ap)
|
||||||
@ -1275,10 +1274,27 @@ devfs_symlink(struct vop_symlink_args *ap)
|
|||||||
char *a_target;
|
char *a_target;
|
||||||
} */
|
} */
|
||||||
{
|
{
|
||||||
return EINVAL;
|
int err;
|
||||||
|
dn_p dnp;
|
||||||
|
union typeinfo by;
|
||||||
|
devnm_p nm_p;
|
||||||
|
struct vnode *vp;
|
||||||
|
|
||||||
DBPRINT(("symlink\n"));
|
DBPRINT(("symlink\n"));
|
||||||
|
if(err = devfs_vntodn(ap->a_dvp,&dnp)) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
by.Slnk.name = ap->a_target;
|
||||||
|
by.Slnk.namelen = strlen(ap->a_target);
|
||||||
|
dev_add_entry( ap->a_cnp->cn_nameptr, dnp, DEV_SLNK, &by, &nm_p);
|
||||||
|
if(err = devfs_dntovn(nm_p->dnp,&vp) ) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
*ap->a_vpp = vp;
|
||||||
|
VOP_SETATTR(vp, ap->a_vap, ap->a_cnp->cn_cred, ap->a_cnp->cn_proc);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Vnode op for readdir
|
* Vnode op for readdir
|
||||||
@ -1389,7 +1405,6 @@ DBPRINT(("readdir\n"));
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
*/
|
*/
|
||||||
#ifdef notyet
|
|
||||||
static int
|
static int
|
||||||
devfs_readlink(struct vop_readlink_args *ap)
|
devfs_readlink(struct vop_readlink_args *ap)
|
||||||
/*struct vop_readlink_args {
|
/*struct vop_readlink_args {
|
||||||
@ -1398,10 +1413,26 @@ devfs_readlink(struct vop_readlink_args *ap)
|
|||||||
struct ucred *a_cred;
|
struct ucred *a_cred;
|
||||||
} */
|
} */
|
||||||
{
|
{
|
||||||
|
struct vnode *vp = ap->a_vp;
|
||||||
|
struct uio *uio = ap->a_uio;
|
||||||
|
dn_p lnk_node;
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
|
||||||
DBPRINT(("readlink\n"));
|
DBPRINT(("readlink\n"));
|
||||||
return 0;
|
/* set up refs to dir */
|
||||||
|
if (error = devfs_vntodn(vp,&lnk_node))
|
||||||
|
return error;
|
||||||
|
if(lnk_node->type != DEV_SLNK)
|
||||||
|
return(EINVAL);
|
||||||
|
if (error = VOP_ACCESS(vp, VREAD, ap->a_cred, NULL)) { /* XXX */
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
error = uiomove(lnk_node->by.Slnk.name, lnk_node->by.Slnk.namelen, uio);
|
||||||
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef notyet
|
||||||
static int
|
static int
|
||||||
devfs_abortop(struct vop_abortop_args *ap)
|
devfs_abortop(struct vop_abortop_args *ap)
|
||||||
/*struct vop_abortop_args {
|
/*struct vop_abortop_args {
|
||||||
@ -1638,9 +1669,11 @@ devfs_dropvnode(dn_p dnp)
|
|||||||
#define devfs_seek ((int (*) __P((struct vop_seek_args *)))nullop)
|
#define devfs_seek ((int (*) __P((struct vop_seek_args *)))nullop)
|
||||||
#define devfs_mkdir ((int (*) __P((struct vop_mkdir_args *)))devfs_enotsupp)
|
#define devfs_mkdir ((int (*) __P((struct vop_mkdir_args *)))devfs_enotsupp)
|
||||||
#define devfs_rmdir ((int (*) __P((struct vop_rmdir_args *)))devfs_enotsupp)
|
#define devfs_rmdir ((int (*) __P((struct vop_rmdir_args *)))devfs_enotsupp)
|
||||||
|
/*
|
||||||
#define devfs_symlink ((int (*) __P((struct vop_symlink_args *)))devfs_enotsupp)
|
#define devfs_symlink ((int (*) __P((struct vop_symlink_args *)))devfs_enotsupp)
|
||||||
#define devfs_readlink \
|
#define devfs_readlink \
|
||||||
((int (*) __P((struct vop_readlink_args *)))devfs_enotsupp)
|
((int (*) __P((struct vop_readlink_args *)))devfs_enotsupp)
|
||||||
|
*/
|
||||||
#define devfs_abortop ((int (*) __P((struct vop_abortop_args *)))nullop)
|
#define devfs_abortop ((int (*) __P((struct vop_abortop_args *)))nullop)
|
||||||
#define devfs_lock ((int (*) __P((struct vop_lock_args *)))nullop)
|
#define devfs_lock ((int (*) __P((struct vop_lock_args *)))nullop)
|
||||||
#define devfs_unlock ((int (*) __P((struct vop_unlock_args *)))nullop)
|
#define devfs_unlock ((int (*) __P((struct vop_unlock_args *)))nullop)
|
||||||
@ -1775,7 +1808,7 @@ static struct vnodeopv_entry_desc dev_spec_vnodeop_entries[] = {
|
|||||||
{ &vop_rename_desc, (vop_t *)spec_rename }, /* rename */
|
{ &vop_rename_desc, (vop_t *)spec_rename }, /* rename */
|
||||||
{ &vop_mkdir_desc, (vop_t *)spec_mkdir }, /* mkdir */
|
{ &vop_mkdir_desc, (vop_t *)spec_mkdir }, /* mkdir */
|
||||||
{ &vop_rmdir_desc, (vop_t *)spec_rmdir }, /* rmdir */
|
{ &vop_rmdir_desc, (vop_t *)spec_rmdir }, /* rmdir */
|
||||||
{ &vop_symlink_desc, (vop_t *)spec_symlink }, /* symlink */
|
{ &vop_symlink_desc, (vop_t *)devfs_symlink }, /* symlink */
|
||||||
{ &vop_readdir_desc, (vop_t *)spec_readdir }, /* readdir */
|
{ &vop_readdir_desc, (vop_t *)spec_readdir }, /* readdir */
|
||||||
{ &vop_readlink_desc, (vop_t *)spec_readlink }, /* readlink */
|
{ &vop_readlink_desc, (vop_t *)spec_readlink }, /* readlink */
|
||||||
{ &vop_abortop_desc, (vop_t *)spec_abortop }, /* abortop */
|
{ &vop_abortop_desc, (vop_t *)spec_abortop }, /* abortop */
|
||||||
|
Loading…
Reference in New Issue
Block a user