v2api: rename "File2" to just "File"
Rename the symbols and the files.
This commit is contained in:
parent
94e8fc12ea
commit
ee5ab1cc29
@ -25,8 +25,8 @@ import (
|
|||||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// File2 implements the go-fuse v2 API (github.com/hanwen/go-fuse/v2/fs)
|
// File implements the go-fuse v2 API (github.com/hanwen/go-fuse/v2/fs)
|
||||||
type File2 struct {
|
type File struct {
|
||||||
fd *os.File
|
fd *os.File
|
||||||
// Has Release() already been called on this file? This also means that the
|
// Has Release() already been called on this file? This also means that the
|
||||||
// wlock entry has been freed, so let's not crash trying to access it.
|
// wlock entry has been freed, so let's not crash trying to access it.
|
||||||
@ -54,11 +54,11 @@ type File2 struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewFile returns a new go-fuse File instance.
|
// NewFile returns a new go-fuse File instance.
|
||||||
func NewFile2(fd *os.File, rn *RootNode, st *syscall.Stat_t) *File2 {
|
func NewFile(fd *os.File, rn *RootNode, st *syscall.Stat_t) *File {
|
||||||
qi := inomap.QInoFromStat(st)
|
qi := inomap.QInoFromStat(st)
|
||||||
e := openfiletable.Register(qi)
|
e := openfiletable.Register(qi)
|
||||||
|
|
||||||
return &File2{
|
return &File{
|
||||||
fd: fd,
|
fd: fd,
|
||||||
contentEnc: rn.contentEnc,
|
contentEnc: rn.contentEnc,
|
||||||
qIno: qi,
|
qIno: qi,
|
||||||
@ -68,13 +68,13 @@ func NewFile2(fd *os.File, rn *RootNode, st *syscall.Stat_t) *File2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// intFd - return the backing file descriptor as an integer.
|
// intFd - return the backing file descriptor as an integer.
|
||||||
func (f *File2) intFd() int {
|
func (f *File) intFd() int {
|
||||||
return int(f.fd.Fd())
|
return int(f.fd.Fd())
|
||||||
}
|
}
|
||||||
|
|
||||||
// readFileID loads the file header from disk and extracts the file ID.
|
// readFileID loads the file header from disk and extracts the file ID.
|
||||||
// Returns io.EOF if the file is empty.
|
// Returns io.EOF if the file is empty.
|
||||||
func (f *File2) readFileID() ([]byte, error) {
|
func (f *File) readFileID() ([]byte, error) {
|
||||||
// We read +1 byte to determine if the file has actual content
|
// We read +1 byte to determine if the file has actual content
|
||||||
// and not only the header. A header-only file will be considered empty.
|
// and not only the header. A header-only file will be considered empty.
|
||||||
// This makes File ID poisoning more difficult.
|
// This makes File ID poisoning more difficult.
|
||||||
@ -100,7 +100,7 @@ func (f *File2) readFileID() ([]byte, error) {
|
|||||||
// createHeader creates a new random header and writes it to disk.
|
// createHeader creates a new random header and writes it to disk.
|
||||||
// Returns the new file ID.
|
// Returns the new file ID.
|
||||||
// The caller must hold fileIDLock.Lock().
|
// The caller must hold fileIDLock.Lock().
|
||||||
func (f *File2) createHeader() (fileID []byte, err error) {
|
func (f *File) createHeader() (fileID []byte, err error) {
|
||||||
h := contentenc.RandomHeader()
|
h := contentenc.RandomHeader()
|
||||||
buf := h.Pack()
|
buf := h.Pack()
|
||||||
// Prevent partially written (=corrupt) header by preallocating the space beforehand
|
// Prevent partially written (=corrupt) header by preallocating the space beforehand
|
||||||
@ -130,7 +130,7 @@ func (f *File2) createHeader() (fileID []byte, err error) {
|
|||||||
//
|
//
|
||||||
// Called by Read() for normal reading,
|
// Called by Read() for normal reading,
|
||||||
// by Write() and Truncate() via doWrite() for Read-Modify-Write.
|
// by Write() and Truncate() via doWrite() for Read-Modify-Write.
|
||||||
func (f *File2) doRead(dst []byte, off uint64, length uint64) ([]byte, syscall.Errno) {
|
func (f *File) doRead(dst []byte, off uint64, length uint64) ([]byte, syscall.Errno) {
|
||||||
// Get the file ID, either from the open file table, or from disk.
|
// Get the file ID, either from the open file table, or from disk.
|
||||||
var fileID []byte
|
var fileID []byte
|
||||||
f.fileTableEntry.IDLock.Lock()
|
f.fileTableEntry.IDLock.Lock()
|
||||||
@ -221,7 +221,7 @@ func (f *File2) doRead(dst []byte, off uint64, length uint64) ([]byte, syscall.E
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read - FUSE call
|
// Read - FUSE call
|
||||||
func (f *File2) Read(ctx context.Context, buf []byte, off int64) (resultData fuse.ReadResult, errno syscall.Errno) {
|
func (f *File) Read(ctx context.Context, buf []byte, off int64) (resultData fuse.ReadResult, errno syscall.Errno) {
|
||||||
if len(buf) > fuse.MAX_KERNEL_WRITE {
|
if len(buf) > fuse.MAX_KERNEL_WRITE {
|
||||||
// This would crash us due to our fixed-size buffer pool
|
// This would crash us due to our fixed-size buffer pool
|
||||||
tlog.Warn.Printf("Read: rejecting oversized request with EMSGSIZE, len=%d", len(buf))
|
tlog.Warn.Printf("Read: rejecting oversized request with EMSGSIZE, len=%d", len(buf))
|
||||||
@ -257,7 +257,7 @@ func (f *File2) Read(ctx context.Context, buf []byte, off int64) (resultData fus
|
|||||||
// and by Truncate() to rewrite the last file block.
|
// and by Truncate() to rewrite the last file block.
|
||||||
//
|
//
|
||||||
// Empty writes do nothing and are allowed.
|
// Empty writes do nothing and are allowed.
|
||||||
func (f *File2) doWrite(data []byte, off int64) (uint32, syscall.Errno) {
|
func (f *File) doWrite(data []byte, off int64) (uint32, syscall.Errno) {
|
||||||
fileWasEmpty := false
|
fileWasEmpty := false
|
||||||
// Get the file ID, create a new one if it does not exist yet.
|
// Get the file ID, create a new one if it does not exist yet.
|
||||||
var fileID []byte
|
var fileID []byte
|
||||||
@ -342,7 +342,7 @@ func (f *File2) doWrite(data []byte, off int64) (uint32, syscall.Errno) {
|
|||||||
// This is an optimisation for streaming writes on NFS where a
|
// This is an optimisation for streaming writes on NFS where a
|
||||||
// Stat() call is very expensive.
|
// Stat() call is very expensive.
|
||||||
// The caller must "wlock.lock(f.devIno.ino)" otherwise this check would be racy.
|
// The caller must "wlock.lock(f.devIno.ino)" otherwise this check would be racy.
|
||||||
func (f *File2) isConsecutiveWrite(off int64) bool {
|
func (f *File) isConsecutiveWrite(off int64) bool {
|
||||||
opCount := openfiletable.WriteOpCount()
|
opCount := openfiletable.WriteOpCount()
|
||||||
return opCount == f.lastOpCount+1 && off == f.lastWrittenOffset+1
|
return opCount == f.lastOpCount+1 && off == f.lastWrittenOffset+1
|
||||||
}
|
}
|
||||||
@ -350,7 +350,7 @@ func (f *File2) isConsecutiveWrite(off int64) bool {
|
|||||||
// Write - FUSE call
|
// Write - FUSE call
|
||||||
//
|
//
|
||||||
// If the write creates a hole, pads the file to the next block boundary.
|
// If the write creates a hole, pads the file to the next block boundary.
|
||||||
func (f *File2) Write(ctx context.Context, data []byte, off int64) (uint32, syscall.Errno) {
|
func (f *File) Write(ctx context.Context, data []byte, off int64) (uint32, syscall.Errno) {
|
||||||
if len(data) > fuse.MAX_KERNEL_WRITE {
|
if len(data) > fuse.MAX_KERNEL_WRITE {
|
||||||
// This would crash us due to our fixed-size buffer pool
|
// This would crash us due to our fixed-size buffer pool
|
||||||
tlog.Warn.Printf("Write: rejecting oversized request with EMSGSIZE, len=%d", len(data))
|
tlog.Warn.Printf("Write: rejecting oversized request with EMSGSIZE, len=%d", len(data))
|
||||||
@ -384,7 +384,7 @@ func (f *File2) Write(ctx context.Context, data []byte, off int64) (uint32, sysc
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Release - FUSE call, close file
|
// Release - FUSE call, close file
|
||||||
func (f *File2) Release(ctx context.Context) syscall.Errno {
|
func (f *File) Release(ctx context.Context) syscall.Errno {
|
||||||
f.fdLock.Lock()
|
f.fdLock.Lock()
|
||||||
if f.released {
|
if f.released {
|
||||||
log.Panicf("ino%d fh%d: double release", f.qIno.Ino, f.intFd())
|
log.Panicf("ino%d fh%d: double release", f.qIno.Ino, f.intFd())
|
||||||
@ -397,7 +397,7 @@ func (f *File2) Release(ctx context.Context) syscall.Errno {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Flush - FUSE call
|
// Flush - FUSE call
|
||||||
func (f *File2) Flush(ctx context.Context) syscall.Errno {
|
func (f *File) Flush(ctx context.Context) syscall.Errno {
|
||||||
f.fdLock.RLock()
|
f.fdLock.RLock()
|
||||||
defer f.fdLock.RUnlock()
|
defer f.fdLock.RUnlock()
|
||||||
|
|
||||||
@ -414,7 +414,7 @@ func (f *File2) Flush(ctx context.Context) syscall.Errno {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fsync FUSE call
|
// Fsync FUSE call
|
||||||
func (f *File2) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) {
|
func (f *File) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) {
|
||||||
f.fdLock.RLock()
|
f.fdLock.RLock()
|
||||||
defer f.fdLock.RUnlock()
|
defer f.fdLock.RUnlock()
|
||||||
|
|
||||||
@ -422,7 +422,7 @@ func (f *File2) Fsync(ctx context.Context, flags uint32) (errno syscall.Errno) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Getattr FUSE call (like stat)
|
// Getattr FUSE call (like stat)
|
||||||
func (f *File2) Getattr(ctx context.Context, a *fuse.AttrOut) syscall.Errno {
|
func (f *File) Getattr(ctx context.Context, a *fuse.AttrOut) syscall.Errno {
|
||||||
f.fdLock.RLock()
|
f.fdLock.RLock()
|
||||||
defer f.fdLock.RUnlock()
|
defer f.fdLock.RUnlock()
|
||||||
|
|
@ -1,23 +0,0 @@
|
|||||||
package fusefrontend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/hanwen/go-fuse/v2/fs"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check that we have implemented the fs.File* interfaces
|
|
||||||
var _ = (fs.FileGetattrer)((*File2)(nil))
|
|
||||||
var _ = (fs.FileSetattrer)((*File2)(nil))
|
|
||||||
var _ = (fs.FileReleaser)((*File2)(nil))
|
|
||||||
var _ = (fs.FileReader)((*File2)(nil))
|
|
||||||
var _ = (fs.FileWriter)((*File2)(nil))
|
|
||||||
var _ = (fs.FileFsyncer)((*File2)(nil))
|
|
||||||
var _ = (fs.FileFlusher)((*File2)(nil))
|
|
||||||
var _ = (fs.FileAllocater)((*File2)(nil))
|
|
||||||
var _ = (fs.FileLseeker)((*File2)(nil))
|
|
||||||
|
|
||||||
/* TODO
|
|
||||||
var _ = (fs.FileHandle)((*File2)(nil))
|
|
||||||
var _ = (fs.FileGetlker)((*File2)(nil))
|
|
||||||
var _ = (fs.FileSetlker)((*File2)(nil))
|
|
||||||
var _ = (fs.FileSetlkwer)((*File2)(nil))
|
|
||||||
*/
|
|
@ -37,7 +37,7 @@ var allocateWarnOnce sync.Once
|
|||||||
// complicated and hard to get right.
|
// complicated and hard to get right.
|
||||||
//
|
//
|
||||||
// Other modes (hole punching, zeroing) are not supported.
|
// Other modes (hole punching, zeroing) are not supported.
|
||||||
func (f *File2) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno {
|
func (f *File) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno {
|
||||||
if mode != FALLOC_DEFAULT && mode != FALLOC_FL_KEEP_SIZE {
|
if mode != FALLOC_DEFAULT && mode != FALLOC_FL_KEEP_SIZE {
|
||||||
f := func() {
|
f := func() {
|
||||||
tlog.Info.Printf("fallocate: only mode 0 (default) and 1 (keep size) are supported")
|
tlog.Info.Printf("fallocate: only mode 0 (default) and 1 (keep size) are supported")
|
||||||
@ -93,7 +93,7 @@ func (f *File2) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32
|
|||||||
}
|
}
|
||||||
|
|
||||||
// truncate - called from Setattr.
|
// truncate - called from Setattr.
|
||||||
func (f *File2) truncate(newSize uint64) (errno syscall.Errno) {
|
func (f *File) truncate(newSize uint64) (errno syscall.Errno) {
|
||||||
var err error
|
var err error
|
||||||
// Common case first: Truncate to zero
|
// Common case first: Truncate to zero
|
||||||
if newSize == 0 {
|
if newSize == 0 {
|
||||||
@ -154,7 +154,7 @@ func (f *File2) truncate(newSize uint64) (errno syscall.Errno) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// statPlainSize stats the file and returns the plaintext size
|
// statPlainSize stats the file and returns the plaintext size
|
||||||
func (f *File2) statPlainSize() (uint64, error) {
|
func (f *File) statPlainSize() (uint64, error) {
|
||||||
fi, err := f.fd.Stat()
|
fi, err := f.fd.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Warn.Printf("ino%d fh%d: statPlainSize: %v", f.qIno.Ino, f.intFd(), err)
|
tlog.Warn.Printf("ino%d fh%d: statPlainSize: %v", f.qIno.Ino, f.intFd(), err)
|
||||||
@ -168,7 +168,7 @@ func (f *File2) statPlainSize() (uint64, error) {
|
|||||||
// truncateGrowFile extends a file using seeking or ftruncate performing RMW on
|
// truncateGrowFile extends a file using seeking or ftruncate performing RMW on
|
||||||
// the first and last block as necessary. New blocks in the middle become
|
// the first and last block as necessary. New blocks in the middle become
|
||||||
// file holes unless they have been fallocate()'d beforehand.
|
// file holes unless they have been fallocate()'d beforehand.
|
||||||
func (f *File2) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) syscall.Errno {
|
func (f *File) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) syscall.Errno {
|
||||||
if newPlainSz <= oldPlainSz {
|
if newPlainSz <= oldPlainSz {
|
||||||
log.Panicf("BUG: newSize=%d <= oldSize=%d", newPlainSz, oldPlainSz)
|
log.Panicf("BUG: newSize=%d <= oldSize=%d", newPlainSz, oldPlainSz)
|
||||||
}
|
}
|
23
internal/fusefrontend/file_api_check.go
Normal file
23
internal/fusefrontend/file_api_check.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package fusefrontend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hanwen/go-fuse/v2/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check that we have implemented the fs.File* interfaces
|
||||||
|
var _ = (fs.FileGetattrer)((*File)(nil))
|
||||||
|
var _ = (fs.FileSetattrer)((*File)(nil))
|
||||||
|
var _ = (fs.FileReleaser)((*File)(nil))
|
||||||
|
var _ = (fs.FileReader)((*File)(nil))
|
||||||
|
var _ = (fs.FileWriter)((*File)(nil))
|
||||||
|
var _ = (fs.FileFsyncer)((*File)(nil))
|
||||||
|
var _ = (fs.FileFlusher)((*File)(nil))
|
||||||
|
var _ = (fs.FileAllocater)((*File)(nil))
|
||||||
|
var _ = (fs.FileLseeker)((*File)(nil))
|
||||||
|
|
||||||
|
/* TODO
|
||||||
|
var _ = (fs.FileHandle)((*File)(nil))
|
||||||
|
var _ = (fs.FileGetlker)((*File)(nil))
|
||||||
|
var _ = (fs.FileSetlker)((*File)(nil))
|
||||||
|
var _ = (fs.FileSetlkwer)((*File)(nil))
|
||||||
|
*/
|
@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
// Will a write to plaintext offset "targetOff" create a file hole in the
|
// Will a write to plaintext offset "targetOff" create a file hole in the
|
||||||
// ciphertext? If yes, zero-pad the last ciphertext block.
|
// ciphertext? If yes, zero-pad the last ciphertext block.
|
||||||
func (f *File2) writePadHole(targetOff int64) syscall.Errno {
|
func (f *File) writePadHole(targetOff int64) syscall.Errno {
|
||||||
// Get the current file size.
|
// Get the current file size.
|
||||||
fi, err := f.fd.Stat()
|
fi, err := f.fd.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -43,7 +43,7 @@ func (f *File2) writePadHole(targetOff int64) syscall.Errno {
|
|||||||
|
|
||||||
// Zero-pad the file of size plainSize to the next block boundary. This is a no-op
|
// Zero-pad the file of size plainSize to the next block boundary. This is a no-op
|
||||||
// if the file is already block-aligned.
|
// if the file is already block-aligned.
|
||||||
func (f *File2) zeroPad(plainSize uint64) syscall.Errno {
|
func (f *File) zeroPad(plainSize uint64) syscall.Errno {
|
||||||
lastBlockLen := plainSize % f.contentEnc.PlainBS()
|
lastBlockLen := plainSize % f.contentEnc.PlainBS()
|
||||||
if lastBlockLen == 0 {
|
if lastBlockLen == 0 {
|
||||||
// Already block-aligned
|
// Already block-aligned
|
||||||
@ -57,7 +57,7 @@ func (f *File2) zeroPad(plainSize uint64) syscall.Errno {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lseek - FUSE call.
|
// Lseek - FUSE call.
|
||||||
func (f *File2) Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno) {
|
func (f *File) Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno) {
|
||||||
cipherOff := f.rootNode.contentEnc.PlainSizeToCipherSize(off)
|
cipherOff := f.rootNode.contentEnc.PlainSizeToCipherSize(off)
|
||||||
newCipherOff, err := syscall.Seek(f.intFd(), int64(cipherOff), int(whence))
|
newCipherOff, err := syscall.Seek(f.intFd(), int64(cipherOff), int(whence))
|
||||||
if err != nil {
|
if err != nil {
|
@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (f *File2) Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) (errno syscall.Errno) {
|
func (f *File) Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) (errno syscall.Errno) {
|
||||||
errno = f.setAttr(ctx, in)
|
errno = f.setAttr(ctx, in)
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
return errno
|
return errno
|
||||||
@ -19,7 +19,7 @@ func (f *File2) Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrO
|
|||||||
return f.Getattr(ctx, out)
|
return f.Getattr(ctx, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File2) setAttr(ctx context.Context, in *fuse.SetAttrIn) (errno syscall.Errno) {
|
func (f *File) setAttr(ctx context.Context, in *fuse.SetAttrIn) (errno syscall.Errno) {
|
||||||
f.fdLock.RLock()
|
f.fdLock.RLock()
|
||||||
defer f.fdLock.RUnlock()
|
defer f.fdLock.RUnlock()
|
||||||
if f.released {
|
if f.released {
|
@ -133,7 +133,7 @@ func (n *Node) Create(ctx context.Context, name string, flags uint32, mode uint3
|
|||||||
ch := n.newChild(ctx, &st, out)
|
ch := n.newChild(ctx, &st, out)
|
||||||
|
|
||||||
f := os.NewFile(uintptr(fd), cName)
|
f := os.NewFile(uintptr(fd), cName)
|
||||||
return ch, NewFile2(f, rn, &st), 0, 0
|
return ch, NewFile(f, rn, &st), 0, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlink - FUSE call. Delete a file.
|
// Unlink - FUSE call. Delete a file.
|
||||||
@ -216,7 +216,7 @@ func (n *Node) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFl
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
f := os.NewFile(uintptr(fd), cName)
|
f := os.NewFile(uintptr(fd), cName)
|
||||||
fh = NewFile2(f, rn, &st)
|
fh = NewFile(f, rn, &st)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,7 +224,7 @@ func (n *Node) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFl
|
|||||||
func (n *Node) Setattr(ctx context.Context, f fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) (errno syscall.Errno) {
|
func (n *Node) Setattr(ctx context.Context, f fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) (errno syscall.Errno) {
|
||||||
// Use the fd if the kernel gave us one
|
// Use the fd if the kernel gave us one
|
||||||
if f != nil {
|
if f != nil {
|
||||||
f2 := f.(*File2)
|
f2 := f.(*File)
|
||||||
return f2.Setattr(ctx, in, out)
|
return f2.Setattr(ctx, in, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,7 +286,7 @@ func (n *Node) Setattr(ctx context.Context, f fs.FileHandle, in *fuse.SetAttrIn,
|
|||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
return errno
|
return errno
|
||||||
}
|
}
|
||||||
f2 := f.(*File2)
|
f2 := f.(*File)
|
||||||
defer f2.Release(ctx)
|
defer f2.Release(ctx)
|
||||||
errno = syscall.Errno(f2.truncate(sz))
|
errno = syscall.Errno(f2.truncate(sz))
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
|
Loading…
Reference in New Issue
Block a user