nfs4: Move low level NFS4 code to Inode's base class
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -9,17 +9,10 @@
|
||||
#define INODE_H
|
||||
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <SupportDefs.h>
|
||||
|
||||
#include "Cookie.h"
|
||||
#include "FileSystem.h"
|
||||
#include "NFS4Defs.h"
|
||||
#include "ReplyInterpreter.h"
|
||||
#include "NFS4Inode.h"
|
||||
|
||||
|
||||
class Inode {
|
||||
class Inode : public NFS4Inode {
|
||||
public:
|
||||
static status_t CreateInode(FileSystem* fs, const FileInfo& fi,
|
||||
Inode** inode);
|
||||
@@ -36,22 +29,23 @@ public:
|
||||
inline OpenFileCookie* WriteCookie();
|
||||
inline void SetWriteCookie(OpenFileCookie* cookie);
|
||||
|
||||
status_t GetChangeInfo(uint64* change);
|
||||
status_t LookUp(const char* name, ino_t* id);
|
||||
|
||||
status_t Access(int mode);
|
||||
|
||||
status_t Commit();
|
||||
|
||||
status_t LookUp(const char* name, ino_t* id);
|
||||
status_t CreateObject(const char* name, const char* path,
|
||||
int mode, FileType type);
|
||||
|
||||
status_t CreateLink(const char* name, const char* path,
|
||||
int mode);
|
||||
status_t ReadLink(void* buffer, size_t* length);
|
||||
|
||||
status_t Link(Inode* dir, const char* name);
|
||||
status_t Remove(const char* name, FileType type);
|
||||
static status_t Rename(Inode* from, Inode* to,
|
||||
const char* fromName, const char* toName);
|
||||
|
||||
status_t Access(int mode);
|
||||
status_t Stat(struct stat* st);
|
||||
status_t WriteStat(const struct stat* st, uint32 mask);
|
||||
|
||||
@@ -77,46 +71,32 @@ public:
|
||||
const struct flock* lock);
|
||||
status_t ReleaseAllLocks(OpenFileCookie* cookie);
|
||||
|
||||
|
||||
protected:
|
||||
Inode();
|
||||
|
||||
bool _HandleErrors(uint32 nfs4Error,
|
||||
RPC::Server* serv,
|
||||
OpenFileCookie* cookie = NULL);
|
||||
|
||||
status_t _ConfirmOpen(const FileHandle& fh,
|
||||
OpenFileCookie* cookie);
|
||||
|
||||
status_t _ReadDirOnce(DirEntry** dirents, uint32* count,
|
||||
OpenDirCookie* cookie, bool* eof,
|
||||
uint64* change, uint64* dirCookie,
|
||||
uint64* dirCookieVerf);
|
||||
status_t _FillDirEntry(struct dirent* de, ino_t id,
|
||||
const char* name, uint32 pos, uint32 size);
|
||||
status_t _ReadDirUp(struct dirent* de, uint32 pos,
|
||||
status_t ReadDirUp(struct dirent* de, uint32 pos,
|
||||
uint32 size);
|
||||
status_t _GetDirSnapshot(DirectoryCacheSnapshot**
|
||||
status_t FillDirEntry(struct dirent* de, ino_t id,
|
||||
const char* name, uint32 pos, uint32 size);
|
||||
status_t GetDirSnapshot(DirectoryCacheSnapshot**
|
||||
_snapshot, OpenDirCookie* cookie,
|
||||
uint64* _change);
|
||||
|
||||
status_t _ChildAdded(const char* name, uint64 fileID,
|
||||
status_t ChildAdded(const char* name, uint64 fileID,
|
||||
const FileHandle& fileHandle);
|
||||
|
||||
static inline status_t _CheckLockType(short ltype, uint32 mode);
|
||||
status_t UpdateAttrCache(bool force = false);
|
||||
|
||||
static inline ino_t _FileIdToInoT(uint64 fileid);
|
||||
static inline status_t CheckLockType(short ltype, uint32 mode);
|
||||
|
||||
static inline ino_t FileIdToInoT(uint64 fileid);
|
||||
|
||||
uint32 fType;
|
||||
|
||||
struct stat fAttrCache;
|
||||
mutex fAttrCacheLock;
|
||||
time_t fAttrCacheExpire;
|
||||
static const time_t kAttrCacheExpirationTime = 60;
|
||||
status_t _UpdateAttrCache(bool force = false);
|
||||
|
||||
uint32 fType;
|
||||
|
||||
FileInfo fInfo;
|
||||
FileSystem* fFileSystem;
|
||||
|
||||
DirectoryCache* fCache;
|
||||
|
||||
@@ -130,7 +110,7 @@ protected:
|
||||
|
||||
|
||||
inline ino_t
|
||||
Inode::_FileIdToInoT(uint64 fileid)
|
||||
Inode::FileIdToInoT(uint64 fileid)
|
||||
{
|
||||
if (sizeof(ino_t) >= sizeof(uint64))
|
||||
return fileid;
|
||||
@@ -143,7 +123,7 @@ Inode::_FileIdToInoT(uint64 fileid)
|
||||
inline ino_t
|
||||
Inode::ID() const
|
||||
{
|
||||
return _FileIdToInoT(fInfo.fFileId);
|
||||
return FileIdToInoT(fInfo.fFileId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -20,95 +20,7 @@
|
||||
status_t
|
||||
Inode::CreateDir(const char* name, int mode)
|
||||
{
|
||||
bool badOwner = false;
|
||||
|
||||
do {
|
||||
RPC::Server* serv = fFileSystem->Server();
|
||||
Request request(serv);
|
||||
RequestBuilder& req = request.Builder();
|
||||
|
||||
req.PutFH(fInfo.fHandle);
|
||||
|
||||
uint32 i = 0;
|
||||
AttrValue cattr[3];
|
||||
cattr[i].fAttribute = FATTR4_MODE;
|
||||
cattr[i].fFreePointer = false;
|
||||
cattr[i].fData.fValue32 = mode;
|
||||
i++;
|
||||
|
||||
if (!badOwner && fFileSystem->IsAttrSupported(FATTR4_OWNER)) {
|
||||
cattr[i].fAttribute = FATTR4_OWNER;
|
||||
cattr[i].fFreePointer = true;
|
||||
cattr[i].fData.fPointer = gIdMapper->GetOwner(getuid());
|
||||
i++;
|
||||
}
|
||||
|
||||
if (!badOwner && fFileSystem->IsAttrSupported(FATTR4_OWNER_GROUP)) {
|
||||
cattr[i].fAttribute = FATTR4_OWNER_GROUP;
|
||||
cattr[i].fFreePointer = true;
|
||||
cattr[i].fData.fPointer = gIdMapper->GetOwnerGroup(getgid());
|
||||
i++;
|
||||
}
|
||||
|
||||
req.Create(NF4DIR, name, cattr, i);
|
||||
|
||||
req.GetFH();
|
||||
Attribute attr[] = { FATTR4_FILEID };
|
||||
req.GetAttr(attr, sizeof(attr) / sizeof(Attribute));
|
||||
|
||||
status_t result = request.Send();
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
ReplyInterpreter& reply = request.Reply();
|
||||
|
||||
if (reply.NFS4Error() == NFS4ERR_BADOWNER) {
|
||||
badOwner = true;
|
||||
continue;
|
||||
}
|
||||
if (_HandleErrors(reply.NFS4Error(), serv))
|
||||
continue;
|
||||
|
||||
reply.PutFH();
|
||||
|
||||
uint64 before, after;
|
||||
bool atomic;
|
||||
result = reply.Create(&before, &after, atomic);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
FileHandle handle;
|
||||
result = reply.GetFH(&handle);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
AttrValue* values;
|
||||
uint32 count;
|
||||
result = reply.GetAttr(&values, &count);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
uint32 fileID;
|
||||
if (count == 0)
|
||||
fileID = fFileSystem->AllocFileId();
|
||||
else
|
||||
fileID = values[0].fData.fValue64;
|
||||
|
||||
result = _ChildAdded(name, fileID, handle);
|
||||
if (result != B_OK)
|
||||
return B_OK;
|
||||
|
||||
if (fCache->Lock() == B_OK) {
|
||||
if (atomic && fCache->ChangeInfo() == before) {
|
||||
fCache->AddEntry(name, fileID, true);
|
||||
fCache->SetChangeInfo(after);
|
||||
} else if (fCache->ChangeInfo() != before)
|
||||
fCache->Trash();
|
||||
fCache->Unlock();
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
} while (true);
|
||||
return CreateObject(name, NULL, mode, NF4DIR);
|
||||
}
|
||||
|
||||
|
||||
@@ -118,116 +30,22 @@ Inode::OpenDir(OpenDirCookie* cookie)
|
||||
if (fType != NF4DIR)
|
||||
return B_NOT_A_DIRECTORY;
|
||||
|
||||
do {
|
||||
RPC::Server* serv = fFileSystem->Server();
|
||||
Request request(serv);
|
||||
RequestBuilder& req = request.Builder();
|
||||
status_t result = Access(R_OK);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
req.PutFH(fInfo.fHandle);
|
||||
req.Access();
|
||||
cookie->fFileSystem = fFileSystem;
|
||||
cookie->fSpecial = 0;
|
||||
cookie->fSnapshot = NULL;
|
||||
cookie->fCurrent = NULL;
|
||||
cookie->fEOF = false;
|
||||
|
||||
status_t result = request.Send();
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
ReplyInterpreter& reply = request.Reply();
|
||||
|
||||
if (_HandleErrors(reply.NFS4Error(), serv))
|
||||
continue;
|
||||
|
||||
reply.PutFH();
|
||||
|
||||
uint32 allowed;
|
||||
result = reply.Access(NULL, &allowed);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
if (allowed & ACCESS4_READ != ACCESS4_READ)
|
||||
return B_PERMISSION_DENIED;
|
||||
|
||||
cookie->fFileSystem = fFileSystem;
|
||||
cookie->fSpecial = 0;
|
||||
cookie->fSnapshot = NULL;
|
||||
cookie->fCurrent = NULL;
|
||||
cookie->fEOF = false;
|
||||
|
||||
fFileSystem->Root()->MakeInfoInvalid();
|
||||
|
||||
return B_OK;
|
||||
} while (true);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
Inode::_ReadDirOnce(DirEntry** dirents, uint32* count, OpenDirCookie* cookie,
|
||||
bool* eof, uint64* change, uint64* dirCookie, uint64* dirCookieVerf)
|
||||
{
|
||||
do {
|
||||
RPC::Server* serv = fFileSystem->Server();
|
||||
Request request(serv);
|
||||
RequestBuilder& req = request.Builder();
|
||||
|
||||
req.PutFH(fInfo.fHandle);
|
||||
|
||||
Attribute dirAttr[] = { FATTR4_CHANGE };
|
||||
if (*change == 0)
|
||||
req.GetAttr(dirAttr, sizeof(dirAttr) / sizeof(Attribute));
|
||||
|
||||
Attribute attr[] = { FATTR4_FSID, FATTR4_FILEID };
|
||||
req.ReadDir(*count, *dirCookie, *dirCookieVerf, attr,
|
||||
sizeof(attr) / sizeof(Attribute));
|
||||
|
||||
req.GetAttr(dirAttr, sizeof(dirAttr) / sizeof(Attribute));
|
||||
|
||||
status_t result = request.Send(cookie);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
ReplyInterpreter& reply = request.Reply();
|
||||
|
||||
if (_HandleErrors(reply.NFS4Error(), serv))
|
||||
continue;
|
||||
|
||||
reply.PutFH();
|
||||
|
||||
AttrValue* before = NULL;
|
||||
uint32 attrCount;
|
||||
if (*change == 0) {
|
||||
result = reply.GetAttr(&before, &attrCount);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
result = reply.ReadDir(dirCookie, dirCookieVerf, dirents,
|
||||
count, eof);
|
||||
if (result != B_OK) {
|
||||
delete[] before;
|
||||
return result;
|
||||
}
|
||||
|
||||
AttrValue* after;
|
||||
result = reply.GetAttr(&after, &attrCount);
|
||||
if (result != B_OK) {
|
||||
delete[] before;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (*change == 0 && before[0].fData.fValue64 == after[0].fData.fValue64
|
||||
|| *change == after[0].fData.fValue64)
|
||||
*change = after[0].fData.fValue64;
|
||||
else
|
||||
return B_ERROR;
|
||||
|
||||
delete[] before;
|
||||
delete[] after;
|
||||
|
||||
return B_OK;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
Inode::_FillDirEntry(struct dirent* de, ino_t id, const char* name, uint32 pos,
|
||||
Inode::FillDirEntry(struct dirent* de, ino_t id, const char* name, uint32 pos,
|
||||
uint32 size)
|
||||
{
|
||||
uint32 nameSize = strlen(name);
|
||||
@@ -249,7 +67,7 @@ Inode::_FillDirEntry(struct dirent* de, ino_t id, const char* name, uint32 pos,
|
||||
|
||||
|
||||
status_t
|
||||
Inode::_ReadDirUp(struct dirent* de, uint32 pos, uint32 size)
|
||||
Inode::ReadDirUp(struct dirent* de, uint32 pos, uint32 size)
|
||||
{
|
||||
do {
|
||||
RPC::Server* serv = fFileSystem->Server();
|
||||
@@ -271,7 +89,7 @@ Inode::_ReadDirUp(struct dirent* de, uint32 pos, uint32 size)
|
||||
|
||||
ReplyInterpreter& reply = request.Reply();
|
||||
|
||||
if (_HandleErrors(reply.NFS4Error(), serv))
|
||||
if (HandleErrors(reply.NFS4Error(), serv))
|
||||
continue;
|
||||
|
||||
reply.PutFH();
|
||||
@@ -295,13 +113,13 @@ Inode::_ReadDirUp(struct dirent* de, uint32 pos, uint32 size)
|
||||
} else
|
||||
fileId = fFileSystem->AllocFileId();
|
||||
|
||||
return _FillDirEntry(de, _FileIdToInoT(fileId), "..", pos, size);
|
||||
return FillDirEntry(de, FileIdToInoT(fileId), "..", pos, size);
|
||||
} while (true);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
Inode::_GetDirSnapshot(DirectoryCacheSnapshot** _snapshot,
|
||||
Inode::GetDirSnapshot(DirectoryCacheSnapshot** _snapshot,
|
||||
OpenDirCookie* cookie, uint64* _change)
|
||||
{
|
||||
DirectoryCacheSnapshot* snapshot = new DirectoryCacheSnapshot;
|
||||
@@ -317,7 +135,7 @@ Inode::_GetDirSnapshot(DirectoryCacheSnapshot** _snapshot,
|
||||
uint32 count;
|
||||
DirEntry* dirents;
|
||||
|
||||
status_t result = _ReadDirOnce(&dirents, &count, cookie, &eof, &change,
|
||||
status_t result = ReadDirOnce(&dirents, &count, cookie, &eof, &change,
|
||||
&dirCookie, &dirCookieVerf);
|
||||
if (result != B_OK) {
|
||||
delete snapshot;
|
||||
@@ -335,9 +153,9 @@ Inode::_GetDirSnapshot(DirectoryCacheSnapshot** _snapshot,
|
||||
|
||||
ino_t id;
|
||||
if (dirents[i].fAttrCount == 2)
|
||||
id = _FileIdToInoT(dirents[i].fAttrs[1].fData.fValue64);
|
||||
id = FileIdToInoT(dirents[i].fAttrs[1].fData.fValue64);
|
||||
else
|
||||
id = _FileIdToInoT(fFileSystem->AllocFileId());
|
||||
id = FileIdToInoT(fFileSystem->AllocFileId());
|
||||
|
||||
NameCacheEntry* entry = new NameCacheEntry(dirents[i].fName, id);
|
||||
if (entry == NULL || entry->fName == NULL) {
|
||||
@@ -381,7 +199,7 @@ Inode::ReadDir(void* _buffer, uint32 size, uint32* _count,
|
||||
cookie->fSnapshot = fCache->GetSnapshot();
|
||||
if (cookie->fSnapshot == NULL) {
|
||||
uint64 change;
|
||||
result = _GetDirSnapshot(&cookie->fSnapshot, cookie, &change);
|
||||
result = GetDirSnapshot(&cookie->fSnapshot, cookie, &change);
|
||||
if (result != B_OK) {
|
||||
fCache->Unlock();
|
||||
fFileSystem->Revalidator().Unlock();
|
||||
@@ -405,7 +223,7 @@ Inode::ReadDir(void* _buffer, uint32 size, uint32* _count,
|
||||
struct dirent* de = reinterpret_cast<dirent*>(buffer + pos);
|
||||
|
||||
status_t result;
|
||||
result = _FillDirEntry(de, fInfo.fFileId, ".", pos, size);
|
||||
result = FillDirEntry(de, fInfo.fFileId, ".", pos, size);
|
||||
|
||||
if (result == B_BUFFER_OVERFLOW)
|
||||
overflow = true;
|
||||
@@ -422,9 +240,11 @@ Inode::ReadDir(void* _buffer, uint32 size, uint32* _count,
|
||||
|
||||
status_t result;
|
||||
if (strcmp(fInfo.fName, "/"))
|
||||
result = _ReadDirUp(de, pos, size);
|
||||
else
|
||||
result = _FillDirEntry(de, _FileIdToInoT(fInfo.fFileId), "..", pos, size);
|
||||
result = ReadDirUp(de, pos, size);
|
||||
else {
|
||||
result = FillDirEntry(de, FileIdToInoT(fInfo.fFileId), "..", pos,
|
||||
size);
|
||||
}
|
||||
|
||||
if (result == B_BUFFER_OVERFLOW)
|
||||
overflow = true;
|
||||
@@ -452,7 +272,7 @@ Inode::ReadDir(void* _buffer, uint32 size, uint32* _count,
|
||||
break;
|
||||
}
|
||||
|
||||
if (_FillDirEntry(de, cookie->fCurrent->fNode, cookie->fCurrent->fName,
|
||||
if (FillDirEntry(de, cookie->fCurrent->fNode, cookie->fCurrent->fName,
|
||||
pos, size) == B_BUFFER_OVERFLOW) {
|
||||
overflow = true;
|
||||
break;
|
||||
|
||||
@@ -19,182 +19,55 @@
|
||||
#include "RootInode.h"
|
||||
|
||||
|
||||
status_t
|
||||
Inode::_ConfirmOpen(const FileHandle& fh, OpenFileCookie* cookie)
|
||||
{
|
||||
do {
|
||||
RPC::Server* serv = fFileSystem->Server();
|
||||
Request request(serv);
|
||||
|
||||
RequestBuilder& req = request.Builder();
|
||||
|
||||
req.PutFH(fh);
|
||||
req.OpenConfirm(cookie->fSequence++, cookie->fStateId,
|
||||
cookie->fStateSeq);
|
||||
|
||||
status_t result = request.Send();
|
||||
if (result != B_OK) {
|
||||
fFileSystem->RemoveOpenFile(cookie);
|
||||
return result;
|
||||
}
|
||||
|
||||
ReplyInterpreter& reply = request.Reply();
|
||||
|
||||
if (_HandleErrors(reply.NFS4Error(), serv))
|
||||
continue;
|
||||
|
||||
reply.PutFH();
|
||||
result = reply.OpenConfirm(&cookie->fStateSeq);
|
||||
if (result != B_OK) {
|
||||
fFileSystem->RemoveOpenFile(cookie);
|
||||
return result;
|
||||
}
|
||||
|
||||
break;
|
||||
} while (true);
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
Inode::Create(const char* name, int mode, int perms, OpenFileCookie* cookie,
|
||||
ino_t* id)
|
||||
{
|
||||
bool confirm;
|
||||
status_t result;
|
||||
|
||||
cookie->fMode = mode;
|
||||
cookie->fSequence = 0;
|
||||
cookie->fLocks = NULL;
|
||||
|
||||
bool badOwner = false;
|
||||
FileHandle fh;
|
||||
do {
|
||||
cookie->fClientId = fFileSystem->NFSServer()->ClientId();
|
||||
uint64 fileID;
|
||||
FileHandle handle;
|
||||
ChangeInfo changeInfo;
|
||||
status_t result = CreateFile(name, mode, perms, cookie, &changeInfo,
|
||||
&fileID, &handle);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
RPC::Server* serv = fFileSystem->Server();
|
||||
Request request(serv);
|
||||
RequestBuilder& req = request.Builder();
|
||||
*id = FileIdToInoT(fileID);
|
||||
|
||||
cookie->fOwnerId = atomic_add64(&cookie->fLastOwnerId, 1);
|
||||
FileInfo fi;
|
||||
fi.fFileId = fileID;
|
||||
fi.fHandle = handle;
|
||||
fi.fParent = fInfo.fHandle;
|
||||
fi.fName = strdup(name);
|
||||
|
||||
req.PutFH(fInfo.fHandle);
|
||||
char* path = reinterpret_cast<char*>(malloc(strlen(name) + 2 +
|
||||
strlen(fInfo.fPath)));
|
||||
strcpy(path, fInfo.fPath);
|
||||
strcat(path, "/");
|
||||
strcat(path, name);
|
||||
fi.fPath = path;
|
||||
|
||||
AttrValue cattr[4];
|
||||
uint32 i = 0;
|
||||
if ((mode & O_TRUNC) == O_TRUNC) {
|
||||
cattr[i].fAttribute = FATTR4_SIZE;
|
||||
cattr[i].fFreePointer = false;
|
||||
cattr[i].fData.fValue64 = 0;
|
||||
i++;
|
||||
}
|
||||
cattr[i].fAttribute = FATTR4_MODE;
|
||||
cattr[i].fFreePointer = false;
|
||||
cattr[i].fData.fValue32 = perms;
|
||||
i++;
|
||||
fFileSystem->InoIdMap()->AddEntry(fi, *id);
|
||||
|
||||
if (!badOwner && fFileSystem->IsAttrSupported(FATTR4_OWNER)) {
|
||||
cattr[i].fAttribute = FATTR4_OWNER;
|
||||
cattr[i].fFreePointer = true;
|
||||
cattr[i].fData.fPointer = gIdMapper->GetOwner(getuid());
|
||||
i++;
|
||||
}
|
||||
if (fCache->Lock() == B_OK) {
|
||||
if (changeInfo.fAtomic
|
||||
&& fCache->ChangeInfo() == changeInfo.fBefore) {
|
||||
fCache->AddEntry(name, fileID, true);
|
||||
fCache->SetChangeInfo(changeInfo.fAfter);
|
||||
} else if (fCache->ChangeInfo() != changeInfo.fBefore)
|
||||
fCache->Trash();
|
||||
fCache->Unlock();
|
||||
}
|
||||
|
||||
if (!badOwner && fFileSystem->IsAttrSupported(FATTR4_OWNER_GROUP)) {
|
||||
cattr[i].fAttribute = FATTR4_OWNER_GROUP;
|
||||
cattr[i].fFreePointer = true;
|
||||
cattr[i].fData.fPointer = gIdMapper->GetOwnerGroup(getgid());
|
||||
i++;
|
||||
}
|
||||
|
||||
req.Open(CLAIM_NULL, cookie->fSequence++, sModeToAccess(mode),
|
||||
cookie->fClientId, OPEN4_CREATE, cookie->fOwnerId, name, cattr,
|
||||
i, (mode & O_EXCL) == O_EXCL);
|
||||
|
||||
req.GetFH();
|
||||
|
||||
if (fFileSystem->IsAttrSupported(FATTR4_FILEID)) {
|
||||
Attribute attr[] = { FATTR4_FILEID };
|
||||
req.GetAttr(attr, sizeof(attr) / sizeof(Attribute));
|
||||
}
|
||||
|
||||
result = request.Send();
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
ReplyInterpreter& reply = request.Reply();
|
||||
|
||||
if (reply.NFS4Error() == NFS4ERR_BADOWNER) {
|
||||
badOwner = true;
|
||||
continue;
|
||||
}
|
||||
if (_HandleErrors(reply.NFS4Error(), serv))
|
||||
continue;
|
||||
|
||||
reply.PutFH();
|
||||
|
||||
bool atomic;
|
||||
uint64 before, after;
|
||||
result = reply.Open(cookie->fStateId, &cookie->fStateSeq, &confirm,
|
||||
&before, &after, &atomic);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
reply.GetFH(&fh);
|
||||
|
||||
uint64 fileId;
|
||||
if (fFileSystem->IsAttrSupported(FATTR4_FILEID)) {
|
||||
AttrValue* values;
|
||||
uint32 count;
|
||||
result = reply.GetAttr(&values, &count);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
fileId = values[0].fData.fValue64;
|
||||
|
||||
delete[] values;
|
||||
} else
|
||||
fileId = fFileSystem->AllocFileId();
|
||||
|
||||
*id = _FileIdToInoT(fileId);
|
||||
|
||||
FileInfo fi;
|
||||
fi.fFileId = fileId;
|
||||
fi.fHandle = fh;
|
||||
fi.fParent = fInfo.fHandle;
|
||||
fi.fName = strdup(name);
|
||||
|
||||
char* path = reinterpret_cast<char*>(malloc(strlen(name) + 2 +
|
||||
strlen(fInfo.fPath)));
|
||||
strcpy(path, fInfo.fPath);
|
||||
strcat(path, "/");
|
||||
strcat(path, name);
|
||||
fi.fPath = path;
|
||||
|
||||
fFileSystem->InoIdMap()->AddEntry(fi, *id);
|
||||
|
||||
if (fCache->Lock() == B_OK) {
|
||||
if (atomic && fCache->ChangeInfo() == before) {
|
||||
fCache->AddEntry(name, fileId, true);
|
||||
fCache->SetChangeInfo(after);
|
||||
} else if (fCache->ChangeInfo() != before)
|
||||
fCache->Trash();
|
||||
fCache->Unlock();
|
||||
}
|
||||
|
||||
cookie->fFileSystem = fFileSystem;
|
||||
cookie->fInfo = fi;
|
||||
|
||||
break;
|
||||
} while (true);
|
||||
cookie->fFileSystem = fFileSystem;
|
||||
cookie->fInfo = fi;
|
||||
|
||||
fFileSystem->AddOpenFile(cookie);
|
||||
fFileSystem->Root()->MakeInfoInvalid();
|
||||
|
||||
if (confirm)
|
||||
return _ConfirmOpen(fh, cookie);
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
@@ -202,95 +75,18 @@ Inode::Create(const char* name, int mode, int perms, OpenFileCookie* cookie,
|
||||
status_t
|
||||
Inode::Open(int mode, OpenFileCookie* cookie)
|
||||
{
|
||||
bool confirm;
|
||||
status_t result;
|
||||
|
||||
cookie->fFileSystem = fFileSystem;
|
||||
cookie->fInfo = fInfo;
|
||||
cookie->fMode = mode;
|
||||
cookie->fSequence = 0;
|
||||
cookie->fLocks = NULL;
|
||||
|
||||
do {
|
||||
cookie->fClientId = fFileSystem->NFSServer()->ClientId();
|
||||
|
||||
RPC::Server* serv = fFileSystem->Server();
|
||||
Request request(serv);
|
||||
RequestBuilder& req = request.Builder();
|
||||
|
||||
cookie->fOwnerId = atomic_add64(&cookie->fLastOwnerId, 1);
|
||||
|
||||
// Since we are opening the file using a pair (parentFH, name) we
|
||||
// need to check for race conditions.
|
||||
if (fFileSystem->IsAttrSupported(FATTR4_FILEID)) {
|
||||
req.PutFH(fInfo.fParent);
|
||||
req.LookUp(fInfo.fName);
|
||||
AttrValue attr;
|
||||
attr.fAttribute = FATTR4_FILEID;
|
||||
attr.fFreePointer = false;
|
||||
attr.fData.fValue64 = fInfo.fFileId;
|
||||
req.Verify(&attr, 1);
|
||||
} else if (fFileSystem->ExpireType() == FH4_PERSISTENT) {
|
||||
req.PutFH(fInfo.fParent);
|
||||
req.LookUp(fInfo.fName);
|
||||
AttrValue attr;
|
||||
attr.fAttribute = FATTR4_FILEHANDLE;
|
||||
attr.fFreePointer = true;
|
||||
attr.fData.fPointer = malloc(sizeof(fInfo.fHandle));
|
||||
memcpy(attr.fData.fPointer, &fInfo.fHandle, sizeof(fInfo.fHandle));
|
||||
req.Verify(&attr, 1);
|
||||
}
|
||||
|
||||
req.PutFH(fInfo.fParent);
|
||||
if ((mode & O_TRUNC) == O_TRUNC) {
|
||||
AttrValue attr;
|
||||
attr.fAttribute = FATTR4_SIZE;
|
||||
attr.fFreePointer = false;
|
||||
attr.fData.fValue64 = 0;
|
||||
req.Open(CLAIM_NULL, cookie->fSequence++, sModeToAccess(mode),
|
||||
cookie->fClientId, OPEN4_CREATE, cookie->fOwnerId, fInfo.fName,
|
||||
&attr, 1, false);
|
||||
} else
|
||||
req.Open(CLAIM_NULL, cookie->fSequence++, sModeToAccess(mode),
|
||||
cookie->fClientId, OPEN4_NOCREATE, cookie->fOwnerId, fInfo.fName);
|
||||
|
||||
result = request.Send();
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
ReplyInterpreter& reply = request.Reply();
|
||||
|
||||
if (_HandleErrors(reply.NFS4Error(), serv))
|
||||
continue;
|
||||
|
||||
// Verify if the file we want to open is the file this Inode
|
||||
// represents.
|
||||
if (fFileSystem->IsAttrSupported(FATTR4_FILEID) ||
|
||||
fFileSystem->ExpireType() == FH4_PERSISTENT) {
|
||||
reply.PutFH();
|
||||
result = reply.LookUp();
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
result = reply.Verify();
|
||||
if (result != B_OK && reply.NFS4Error() == NFS4ERR_NOT_SAME)
|
||||
return B_ENTRY_NOT_FOUND;
|
||||
else if (result != B_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
reply.PutFH();
|
||||
result = reply.Open(cookie->fStateId, &cookie->fStateSeq, &confirm);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
break;
|
||||
} while (true);
|
||||
status_t result = OpenFile(cookie, mode);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
fFileSystem->AddOpenFile(cookie);
|
||||
|
||||
if (confirm)
|
||||
return _ConfirmOpen(fInfo.fHandle, cookie);
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
@@ -299,34 +95,7 @@ status_t
|
||||
Inode::Close(OpenFileCookie* cookie)
|
||||
{
|
||||
fFileSystem->RemoveOpenFile(cookie);
|
||||
|
||||
do {
|
||||
RPC::Server* serv = fFileSystem->Server();
|
||||
Request request(serv);
|
||||
RequestBuilder& req = request.Builder();
|
||||
|
||||
req.PutFH(fInfo.fHandle);
|
||||
req.Close(cookie->fSequence++, cookie->fStateId,
|
||||
cookie->fStateSeq);
|
||||
|
||||
status_t result = request.Send();
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
ReplyInterpreter& reply = request.Reply();
|
||||
|
||||
if (_HandleErrors(reply.NFS4Error(), serv, cookie))
|
||||
continue;
|
||||
|
||||
reply.PutFH();
|
||||
result = reply.Close();
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
break;
|
||||
} while (true);
|
||||
|
||||
return B_OK;
|
||||
return CloseFile(cookie);
|
||||
}
|
||||
|
||||
|
||||
@@ -336,37 +105,20 @@ Inode::Read(OpenFileCookie* cookie, off_t pos, void* buffer, size_t* _length,
|
||||
{
|
||||
*eof = false;
|
||||
uint32 size = 0;
|
||||
uint32 len = 0;
|
||||
|
||||
status_t result;
|
||||
while (size < *_length && !*eof) {
|
||||
do {
|
||||
RPC::Server* serv = fFileSystem->Server();
|
||||
Request request(serv);
|
||||
RequestBuilder& req = request.Builder();
|
||||
|
||||
req.PutFH(fInfo.fHandle);
|
||||
req.Read(cookie->fStateId, cookie->fStateSeq, pos + size,
|
||||
*_length - size);
|
||||
|
||||
status_t result = request.Send(cookie);
|
||||
if (result != B_OK)
|
||||
uint32 len = *_length - size;
|
||||
result = ReadFile(cookie, pos + size, &len,
|
||||
reinterpret_cast<char*>(buffer) + size, eof);
|
||||
if (result != B_OK) {
|
||||
if (size == 0)
|
||||
return result;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
ReplyInterpreter& reply = request.Reply();
|
||||
|
||||
if (_HandleErrors(reply.NFS4Error(), serv, cookie))
|
||||
continue;
|
||||
|
||||
reply.PutFH();
|
||||
result = reply.Read(reinterpret_cast<char*>(buffer) + size, &len,
|
||||
eof);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
size += len;
|
||||
|
||||
break;
|
||||
} while (true);
|
||||
size += len;
|
||||
}
|
||||
|
||||
*_length = size;
|
||||
@@ -380,74 +132,21 @@ Inode::Write(OpenFileCookie* cookie, off_t pos, const void* _buffer,
|
||||
size_t *_length)
|
||||
{
|
||||
uint32 size = 0;
|
||||
uint32 len = 0;
|
||||
uint64 fileSize;
|
||||
const char* buffer = reinterpret_cast<const char*>(_buffer);
|
||||
|
||||
fWriteDirty = true;
|
||||
|
||||
while (size < *_length) {
|
||||
do {
|
||||
RPC::Server* serv = fFileSystem->Server();
|
||||
Request request(serv);
|
||||
RequestBuilder& req = request.Builder();
|
||||
|
||||
if (size == 0 && (cookie->fMode & O_APPEND) == O_APPEND) {
|
||||
struct stat st;
|
||||
status_t result = Stat(&st);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
fileSize = st.st_size;
|
||||
pos = fileSize;
|
||||
}
|
||||
|
||||
req.PutFH(fInfo.fHandle);
|
||||
if ((cookie->fMode & O_APPEND) == O_APPEND) {
|
||||
AttrValue attr;
|
||||
attr.fAttribute = FATTR4_SIZE;
|
||||
attr.fFreePointer = false;
|
||||
attr.fData.fValue64 = fileSize + size;
|
||||
req.Verify(&attr, 1);
|
||||
}
|
||||
req.Write(cookie->fStateId, cookie->fStateSeq, buffer + size,
|
||||
pos + size, *_length - size);
|
||||
|
||||
status_t result = request.Send(cookie);
|
||||
if (result != B_OK)
|
||||
uint32 len = *_length - size;
|
||||
status_t result = WriteFile(cookie, pos + size, &len, buffer + size);
|
||||
if (result != B_OK) {
|
||||
if (size == 0)
|
||||
return result;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
ReplyInterpreter& reply = request.Reply();
|
||||
|
||||
// append: race condition
|
||||
if (reply.NFS4Error() == NFS4ERR_NOT_SAME) {
|
||||
if (size == 0)
|
||||
continue;
|
||||
else {
|
||||
*_length = size;
|
||||
return B_OK;
|
||||
}
|
||||
}
|
||||
|
||||
if (_HandleErrors(reply.NFS4Error(), serv, cookie))
|
||||
continue;
|
||||
|
||||
reply.PutFH();
|
||||
|
||||
if ((cookie->fMode & O_APPEND) == O_APPEND) {
|
||||
result = reply.Verify();
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
result = reply.Write(&len);
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
size += len;
|
||||
|
||||
break;
|
||||
} while (true);
|
||||
size += len;
|
||||
}
|
||||
|
||||
*_length = size;
|
||||
@@ -464,26 +163,10 @@ Inode::Commit()
|
||||
{
|
||||
if (!fWriteDirty)
|
||||
return B_OK;
|
||||
|
||||
do {
|
||||
RPC::Server* serv = fFileSystem->Server();
|
||||
Request request(serv);
|
||||
RequestBuilder& req = request.Builder();
|
||||
|
||||
req.PutFH(fInfo.fHandle);
|
||||
req.Commit(0, 0);
|
||||
|
||||
status_t result = request.Send();
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
|
||||
ReplyInterpreter& reply = request.Reply();
|
||||
|
||||
if (_HandleErrors(reply.NFS4Error(), serv))
|
||||
continue;
|
||||
|
||||
reply.PutFH();
|
||||
return reply.Commit();
|
||||
} while (true);
|
||||
status_t result = CommitWrites();
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
fWriteDirty = false;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ KernelAddon nfs4 :
|
||||
InodeDir.cpp
|
||||
InodeRegular.cpp
|
||||
kernel_interface.cpp
|
||||
NFS4Inode.cpp
|
||||
NFS4Object.cpp
|
||||
NFS4Server.cpp
|
||||
ReplyInterpreter.cpp
|
||||
Request.cpp
|
||||
|
||||
@@ -207,6 +207,12 @@ enum OpenFlags {
|
||||
OPEN4_RESULT_LOCKTYPE_POSIX = 4
|
||||
};
|
||||
|
||||
struct ChangeInfo {
|
||||
bool fAtomic;
|
||||
uint64 fBefore;
|
||||
uint64 fAfter;
|
||||
};
|
||||
|
||||
enum WriteStable {
|
||||
UNSTABLE4 = 0,
|
||||
DATA_SYNC4 = 1,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2012 Haiku, Inc. All rights reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*
|
||||
* Authors:
|
||||
* Paweł Dziepak, pdziepak@quarnos.org
|
||||
*/
|
||||
#ifndef NFS4INODE_H
|
||||
#define NFS4INODE_H
|
||||
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <SupportDefs.h>
|
||||
|
||||
#include "NFS4Object.h"
|
||||
#include "ReplyInterpreter.h"
|
||||
|
||||
|
||||
class NFS4Inode : protected NFS4Object {
|
||||
public:
|
||||
status_t GetChangeInfo(uint64* change);
|
||||
status_t ReadLink(void* buffer, size_t* length);
|
||||
|
||||
protected:
|
||||
status_t Access(uint32* allowed);
|
||||
|
||||
status_t CommitWrites();
|
||||
|
||||
status_t LookUp(const char* name, uint64* change, uint64* fileID,
|
||||
FileHandle* handle);
|
||||
|
||||
status_t Link(Inode* dir, const char* name,
|
||||
ChangeInfo* changeInfo);
|
||||
|
||||
static status_t Rename(Inode* from, Inode* to, const char* fromName,
|
||||
const char* toName, ChangeInfo* fromChange,
|
||||
ChangeInfo* toChange, uint64* fileID);
|
||||
|
||||
status_t GetStat(AttrValue** values, uint32* count);
|
||||
status_t WriteStat(OpenFileCookie* cookie, AttrValue* attrs,
|
||||
uint32 attrCount);
|
||||
|
||||
status_t CreateFile(const char* name, int mode, int perms,
|
||||
OpenFileCookie* cookie, ChangeInfo* changeInfo,
|
||||
uint64* fileID, FileHandle* handle);
|
||||
status_t OpenFile(OpenFileCookie* cookie, int mode);
|
||||
status_t CloseFile(OpenFileCookie* cookie);
|
||||
|
||||
status_t ReadFile(OpenFileCookie* cookie, uint64 position,
|
||||
uint32* length, void* buffer, bool* eof);
|
||||
status_t WriteFile(OpenFileCookie* cookie, uint64 position,
|
||||
uint32* length, const void* buffer);
|
||||
|
||||
status_t CreateObject(const char* name, const char* path,
|
||||
int mode, FileType type, ChangeInfo* changeInfo,
|
||||
uint64* fileID, FileHandle* handle);
|
||||
status_t RemoveObject(const char* name, FileType type,
|
||||
ChangeInfo* changeInfo);
|
||||
|
||||
status_t ReadDirOnce(DirEntry** dirents, uint32* count,
|
||||
OpenDirCookie* cookie, bool* eof, uint64* change,
|
||||
uint64* dirCookie, uint64* dirCookieVerf);
|
||||
|
||||
status_t TestLock(OpenFileCookie* cookie, LockType* type,
|
||||
uint64* position, uint64* length, bool& conflict);
|
||||
status_t AcquireLock(OpenFileCookie* cookie, LockInfo* lockInfo,
|
||||
bool wait);
|
||||
status_t ReleaseLock(OpenFileCookie* cookie, LockInfo* lockInfo);
|
||||
|
||||
status_t ConfirmOpen(const FileHandle& fileHandle,
|
||||
OpenFileCookie* cookie);
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // NFS4INODE_H
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2012 Haiku, Inc. All rights reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*
|
||||
* Authors:
|
||||
* Paweł Dziepak, pdziepak@quarnos.org
|
||||
*/
|
||||
|
||||
|
||||
#include "NFS4Object.h"
|
||||
|
||||
|
||||
bool
|
||||
NFS4Object::HandleErrors(uint32 nfs4Error, RPC::Server* serv,
|
||||
OpenFileCookie* cookie)
|
||||
{
|
||||
uint32 leaseTime;
|
||||
|
||||
switch (nfs4Error) {
|
||||
case NFS4_OK:
|
||||
return false;
|
||||
|
||||
// server needs more time, we need to wait
|
||||
case NFS4ERR_LOCKED:
|
||||
case NFS4ERR_DELAY:
|
||||
if (cookie == NULL) {
|
||||
snooze_etc(sSecToBigTime(5), B_SYSTEM_TIMEBASE,
|
||||
B_RELATIVE_TIMEOUT);
|
||||
return true;
|
||||
} else if ((cookie->fMode & O_NONBLOCK) == 0) {
|
||||
status_t result = acquire_sem_etc(cookie->fSnoozeCancel, 1,
|
||||
B_RELATIVE_TIMEOUT, sSecToBigTime(5));
|
||||
if (result != B_TIMED_OUT) {
|
||||
release_sem(cookie->fSnoozeCancel);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
// server is in grace period, we need to wait
|
||||
case NFS4ERR_GRACE:
|
||||
leaseTime = fFileSystem->NFSServer()->LeaseTime();
|
||||
if (cookie == NULL) {
|
||||
snooze_etc(sSecToBigTime(leaseTime) / 3, B_SYSTEM_TIMEBASE,
|
||||
B_RELATIVE_TIMEOUT);
|
||||
return true;
|
||||
} else if ((cookie->fMode & O_NONBLOCK) == 0) {
|
||||
status_t result = acquire_sem_etc(cookie->fSnoozeCancel, 1,
|
||||
B_RELATIVE_TIMEOUT, sSecToBigTime(leaseTime) / 3);
|
||||
if (result != B_TIMED_OUT) {
|
||||
release_sem(cookie->fSnoozeCancel);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
// server has rebooted, reclaim share and try again
|
||||
case NFS4ERR_STALE_CLIENTID:
|
||||
case NFS4ERR_STALE_STATEID:
|
||||
fFileSystem->NFSServer()->ServerRebooted(cookie->fClientId);
|
||||
return true;
|
||||
|
||||
// FileHandle has expired
|
||||
case NFS4ERR_FHEXPIRED:
|
||||
if (fInfo.UpdateFileHandles(fFileSystem) == B_OK)
|
||||
return true;
|
||||
return false;
|
||||
|
||||
// filesystem has been moved
|
||||
case NFS4ERR_LEASE_MOVED:
|
||||
case NFS4ERR_MOVED:
|
||||
fFileSystem->Migrate(serv);
|
||||
return true;
|
||||
|
||||
// lease has expired
|
||||
case NFS4ERR_EXPIRED:
|
||||
if (cookie != NULL) {
|
||||
fFileSystem->NFSServer()->ClientId(cookie->fClientId, true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2012 Haiku, Inc. All rights reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*
|
||||
* Authors:
|
||||
* Paweł Dziepak, pdziepak@quarnos.org
|
||||
*/
|
||||
#ifndef NFS4OBJECT_H
|
||||
#define NFS4OBJECT_H
|
||||
|
||||
|
||||
#include "Cookie.h"
|
||||
#include "FileSystem.h"
|
||||
#include "NFS4Defs.h"
|
||||
|
||||
|
||||
class NFS4Object {
|
||||
public:
|
||||
bool HandleErrors(uint32 nfs4Error, RPC::Server* serv,
|
||||
OpenFileCookie* cookie = NULL);
|
||||
|
||||
protected:
|
||||
FileInfo fInfo;
|
||||
FileSystem* fFileSystem;
|
||||
};
|
||||
|
||||
|
||||
#endif // NFS4OBJECT_H
|
||||
|
||||
@@ -70,7 +70,7 @@ RootInode::_UpdateInfo(bool force)
|
||||
|
||||
ReplyInterpreter& reply = request.Reply();
|
||||
|
||||
if (_HandleErrors(reply.NFS4Error(), serv))
|
||||
if (HandleErrors(reply.NFS4Error(), serv))
|
||||
continue;
|
||||
|
||||
reply.PutFH();
|
||||
@@ -152,7 +152,7 @@ RootInode::ProbeMigration()
|
||||
if (reply.NFS4Error() == NFS4ERR_MOVED)
|
||||
return true;
|
||||
|
||||
if (_HandleErrors(reply.NFS4Error(), serv))
|
||||
if (HandleErrors(reply.NFS4Error(), serv))
|
||||
continue;
|
||||
|
||||
return false;
|
||||
@@ -179,7 +179,7 @@ RootInode::GetLocations(AttrValue** attrv)
|
||||
|
||||
ReplyInterpreter& reply = request.Reply();
|
||||
|
||||
if (_HandleErrors(reply.NFS4Error(), serv))
|
||||
if (HandleErrors(reply.NFS4Error(), serv))
|
||||
continue;
|
||||
|
||||
reply.PutFH();
|
||||
|
||||
Reference in New Issue
Block a user