[haiku-commits] BRANCH pdziepak-github.nfs4 - src/add-ons/kernel/file_systems/nfs4

  • From: pdziepak-github.nfs4 <community@xxxxxxxxxxxx>
  • To: haiku-commits@xxxxxxxxxxxxx
  • Date: Fri, 20 Jul 2012 03:49:12 +0200 (CEST)

added 1 changeset to branch 'refs/remotes/pdziepak-github/nfs4'
old head: 7b6f80fee2eee9724b8146cc480c0ffe87b25c32
new head: 09dbdd3644142673a8df63ae77ff8ccd4524c835

----------------------------------------------------------------------------

09dbdd3: nfs4: Add full directory cache implementation

                                    [ Pawel Dziepak <pdziepak@xxxxxxxxxxx> ]

----------------------------------------------------------------------------

Commit:      09dbdd3644142673a8df63ae77ff8ccd4524c835

Author:      Pawel Dziepak <pdziepak@xxxxxxxxxxx>
Date:        Fri Jul 20 01:41:21 2012 UTC

----------------------------------------------------------------------------

9 files changed, 305 insertions(+), 67 deletions(-)
.../kernel/file_systems/nfs4/CacheRevalidator.cpp  |    3 +-
src/add-ons/kernel/file_systems/nfs4/Cookie.cpp    |    7 +
src/add-ons/kernel/file_systems/nfs4/Cookie.h      |    7 +-
.../kernel/file_systems/nfs4/DirectoryCache.cpp    |   74 +++++-
.../kernel/file_systems/nfs4/DirectoryCache.h      |   30 ++-
src/add-ons/kernel/file_systems/nfs4/Inode.h       |    7 +-
src/add-ons/kernel/file_systems/nfs4/InodeDir.cpp  |  210 ++++++++++++----
.../kernel/file_systems/nfs4/ReplyInterpreter.cpp  |   25 +-
src/add-ons/kernel/file_systems/nfs4/XDR.h         |    9 +

----------------------------------------------------------------------------

diff --git a/src/add-ons/kernel/file_systems/nfs4/CacheRevalidator.cpp 
b/src/add-ons/kernel/file_systems/nfs4/CacheRevalidator.cpp
index 6b13f04..6c5e05d 100644
--- a/src/add-ons/kernel/file_systems/nfs4/CacheRevalidator.cpp
+++ b/src/add-ons/kernel/file_systems/nfs4/CacheRevalidator.cpp
@@ -85,7 +85,8 @@ CacheRevalidator::_DirectoryCacheRevalidator()
                        continue;
                }
 
-               current->Lock();
+               if (current->Lock() != B_OK)
+                       continue;
 
                if (current->ExpireTime() > system_time()) {
                        current->Unlock();
diff --git a/src/add-ons/kernel/file_systems/nfs4/Cookie.cpp 
b/src/add-ons/kernel/file_systems/nfs4/Cookie.cpp
index 0102d68..016d107 100644
--- a/src/add-ons/kernel/file_systems/nfs4/Cookie.cpp
+++ b/src/add-ons/kernel/file_systems/nfs4/Cookie.cpp
@@ -233,3 +233,10 @@ OpenFileCookie::_ReleaseLockOwner(LockOwner* owner)
        return reply.ReleaseLockOwner();
 }
 
+
+OpenDirCookie::~OpenDirCookie()
+{
+       if (fSnapshot != NULL)
+               fSnapshot->ReleaseReference();
+}
+
diff --git a/src/add-ons/kernel/file_systems/nfs4/Cookie.h 
b/src/add-ons/kernel/file_systems/nfs4/Cookie.h
index 364dc17c..38a77d3 100644
--- a/src/add-ons/kernel/file_systems/nfs4/Cookie.h
+++ b/src/add-ons/kernel/file_systems/nfs4/Cookie.h
@@ -106,8 +106,11 @@ private:
 };
 
 struct OpenDirCookie : public Cookie {
-                       uint64                  fCookie;
-                       uint64                  fCookieVerf;
+                       DirectoryCacheSnapshot*         fSnapshot;
+                       NameCacheEntry*                         fCurrent;
+                       bool                                            fEOF;
+
+                                                                               
~OpenDirCookie();
 };
 
 
diff --git a/src/add-ons/kernel/file_systems/nfs4/DirectoryCache.cpp 
b/src/add-ons/kernel/file_systems/nfs4/DirectoryCache.cpp
index 73dd3b5..a3065aa 100644
--- a/src/add-ons/kernel/file_systems/nfs4/DirectoryCache.cpp
+++ b/src/add-ons/kernel/file_systems/nfs4/DirectoryCache.cpp
@@ -14,8 +14,41 @@
 #include "Inode.h"
 
 
+
+NameCacheEntry::NameCacheEntry(const char* name, ino_t node)
+       :
+       fNode(node),
+       fName(strdup(name))
+{
+}
+
+
+NameCacheEntry::~NameCacheEntry()
+{
+       free(const_cast<char*>(fName));
+}
+
+
+DirectoryCacheSnapshot::DirectoryCacheSnapshot()
+{
+       mutex_init(&fLock, NULL);
+}
+
+
+DirectoryCacheSnapshot::~DirectoryCacheSnapshot()
+{
+       while (!fEntries.IsEmpty()) {
+               NameCacheEntry* current = fEntries.RemoveHead();
+               delete current;
+       }
+
+       mutex_destroy(&fLock);
+}
+
+
 DirectoryCache::DirectoryCache(Inode* inode)
        :
+       fDirectoryCache(NULL),
        fInode(inode),
        fTrashed(true)
 {
@@ -46,27 +79,26 @@ DirectoryCache::Trash()
                NameCacheEntry* current = fNameCache.RemoveHead();
                entry_cache_remove(fInode->GetFileSystem()->DevId(), 
fInode->ID(),
                        current->fName);
-               free(const_cast<char*>(current->fName));
                delete current;
        }
 
+       SetSnapshot(NULL);
+
        fTrashed = true;
 }
 
-
+// TODO: separate AddEntry() for Name and Directory Cache are needed
 status_t
 DirectoryCache::AddEntry(const char* name, ino_t node)
 {
-       NameCacheEntry* entry = new(std::nothrow) NameCacheEntry;
+       NameCacheEntry* entry = new(std::nothrow) NameCacheEntry(name, node);
        if (entry == NULL)
                return B_NO_MEMORY;
-
-       entry->fName = strdup(name);
        if (entry->fName == NULL) {
                delete entry;
                return B_NO_MEMORY;
        }
-       entry->fNode = node;
+
        fNameCache.Add(entry);
 
        return entry_cache_add(fInode->GetFileSystem()->DevId(), fInode->ID(), 
name,
@@ -82,10 +114,8 @@ DirectoryCache::RemoveEntry(const char* name)
        NameCacheEntry* current = iterator.Next();
        while (current != NULL) {
                if (strcmp(current->fName, name) == 0) {
-                       free(const_cast<char*>(current->fName));
-                       delete current;
-
                        fNameCache.Remove(previous, current);
+                       delete current;
                        break;
                }
 
@@ -93,10 +123,36 @@ DirectoryCache::RemoveEntry(const char* name)
                current = iterator.Next();
        }
 
+       if (fDirectoryCache != NULL) {
+               MutexLocker _(fDirectoryCache->fLock);
+               iterator = fDirectoryCache->fEntries.GetIterator();
+               previous = NULL;
+               current = iterator.Next();
+               while (current != NULL) {
+                       if (strcmp(current->fName, name) == 0) {
+                               fDirectoryCache->fEntries.Remove(previous, 
current);
+                               delete current;
+                               break;
+                       }
+
+                       previous = current;
+                       current = iterator.Next();
+               }
+       }
+
        entry_cache_remove(fInode->GetFileSystem()->DevId(), fInode->ID(), 
name);
 }
 
 
+void
+DirectoryCache::SetSnapshot(DirectoryCacheSnapshot* snapshot)
+{
+       if (fDirectoryCache != NULL)
+               fDirectoryCache->ReleaseReference();
+       fDirectoryCache = snapshot;
+}
+
+
 status_t
 DirectoryCache::Revalidate()
 {
diff --git a/src/add-ons/kernel/file_systems/nfs4/DirectoryCache.h 
b/src/add-ons/kernel/file_systems/nfs4/DirectoryCache.h
index f5e949f..0e9b07b 100644
--- a/src/add-ons/kernel/file_systems/nfs4/DirectoryCache.h
+++ b/src/add-ons/kernel/file_systems/nfs4/DirectoryCache.h
@@ -12,6 +12,7 @@
 #include <lock.h>
 #include <SupportDefs.h>
 #include <util/DoublyLinkedList.h>
+#include <util/KernelReferenceable.h>
 #include <util/SinglyLinkedList.h>
 
 
@@ -21,8 +22,18 @@ struct NameCacheEntry :
        public SinglyLinkedListLinkImpl<NameCacheEntry> {
                        ino_t                   fNode;
                        const char*             fName;
+
+                                                       NameCacheEntry(const 
char* name, ino_t node);
+                                                       ~NameCacheEntry();
 };
 
+struct DirectoryCacheSnapshot : public KernelReferenceable {
+       SinglyLinkedList<NameCacheEntry>        fEntries;
+       mutex                                                           fLock;
+
+                                                                               
DirectoryCacheSnapshot();
+                                                                               
~DirectoryCacheSnapshot();
+};
 
 class DirectoryCache : public DoublyLinkedListLinkImpl<DirectoryCache> {
 public:
@@ -38,6 +49,9 @@ public:
                        status_t                AddEntry(const char* name, 
ino_t node);
                        void                    RemoveEntry(const char* name);
 
+                       void                    
SetSnapshot(DirectoryCacheSnapshot* snapshot);
+       inline  DirectoryCacheSnapshot* GetSnapshot();
+
        inline  SinglyLinkedList<NameCacheEntry>&       EntriesList();
 
                        status_t                Revalidate();
@@ -53,6 +67,9 @@ public:
 private:
                        SinglyLinkedList<NameCacheEntry>        fNameCache;
 
+                       DirectoryCacheSnapshot* fDirectoryCache;
+                       //mutex                 fDirectoryCacheLock;
+
                        Inode*                  fInode;
 
                        bool                    fTrashed;
@@ -82,7 +99,14 @@ DirectoryCache::Unlock()
 }
 
 
-inline SinglyLinkedList<NameCacheEntry>&
+inline DirectoryCacheSnapshot*
+DirectoryCache::GetSnapshot()
+{
+       return fDirectoryCache;
+}
+
+
+inline SinglyLinkedList<NameCacheEntry>&
 DirectoryCache::EntriesList()
 {
        return fNameCache;
@@ -94,8 +118,10 @@ DirectoryCache::ValidateChangeInfo(uint64 change)
 {
        if (fTrashed || change != fChange) {
                Trash();
-               change = fChange;
+               fChange = change;
                fExpireTime = system_time() + kExpirationTime;
+               fTrashed = false;
+
                return B_ERROR;
        }
 
diff --git a/src/add-ons/kernel/file_systems/nfs4/Inode.h 
b/src/add-ons/kernel/file_systems/nfs4/Inode.h
index 76f589d..feb9964 100644
--- a/src/add-ons/kernel/file_systems/nfs4/Inode.h
+++ b/src/add-ons/kernel/file_systems/nfs4/Inode.h
@@ -81,11 +81,16 @@ protected:
                                                                        
OpenFileCookie* cookie);
 
                                        status_t        _ReadDirOnce(DirEntry** 
dirents, uint32* count,
-                                                                       
OpenDirCookie* cookie, bool* eof);
+                                                                       
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,
                                                                        uint32 
size);
+                                       status_t        
_GetDirSnapshot(DirectoryCacheSnapshot**
+                                                                       
_snapshot, OpenDirCookie* cookie,
+                                                                       uint64* 
_change);
 
        static inline   status_t        _CheckLockType(short ltype, uint32 
mode);
 
diff --git a/src/add-ons/kernel/file_systems/nfs4/InodeDir.cpp 
b/src/add-ons/kernel/file_systems/nfs4/InodeDir.cpp
index 4671f97..05af489 100644
--- a/src/add-ons/kernel/file_systems/nfs4/InodeDir.cpp
+++ b/src/add-ons/kernel/file_systems/nfs4/InodeDir.cpp
@@ -122,8 +122,9 @@ Inode::OpenDir(OpenDirCookie* cookie)
                        return B_PERMISSION_DENIED;
 
                cookie->fFileSystem = fFileSystem;
-               cookie->fCookie = 0;
-               cookie->fCookieVerf = 2;
+               cookie->fSnapshot = NULL;
+               cookie->fCurrent = NULL;
+               cookie->fEOF = false;
 
                fFileSystem->Root()->MakeInfoInvalid();
 
@@ -134,7 +135,7 @@ Inode::OpenDir(OpenDirCookie* cookie)
 
 status_t
 Inode::_ReadDirOnce(DirEntry** dirents, uint32* count, OpenDirCookie* cookie,
-       bool* eof)
+       bool* eof, uint64* change, uint64* dirCookie, uint64* dirCookieVerf)
 {
        do {
                RPC::Server* serv = fFileSystem->Server();
@@ -143,10 +144,16 @@ Inode::_ReadDirOnce(DirEntry** dirents, uint32* count, 
OpenDirCookie* cookie,
 
                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, cookie->fCookie, cookie->fCookieVerf, attr,
+               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;
@@ -157,8 +164,39 @@ Inode::_ReadDirOnce(DirEntry** dirents, uint32* count, 
OpenDirCookie* cookie,
                        continue;
 
                reply.PutFH();
-               return reply.ReadDir(&cookie->fCookie, &cookie->fCookieVerf, 
dirents,
+
+               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);
 }
 
@@ -236,23 +274,67 @@ Inode::_ReadDirUp(struct dirent* de, uint32 pos, uint32 
size)
        } while (true);
 }
 
-// TODO: Currently inode numbers returned by ReadDir are virtually random.
-// Apparently Haiku does not use that information (contrary to inode number
-// returned by LookUp) so fixing it can wait until directory caches are
-// implemented.
-// When directories are cached client should store inode numbers it assigned
-// to directroy entries and use them consequently.
+
 status_t
-Inode::ReadDir(void* _buffer, uint32 size, uint32* _count,
-       OpenDirCookie* cookie)
+Inode::_GetDirSnapshot(DirectoryCacheSnapshot** _snapshot,
+       OpenDirCookie* cookie, uint64* _change)
 {
-       uint32 count = 0;
-       uint32 pos = 0;
-       uint32 this_count;
+       DirectoryCacheSnapshot* snapshot = new DirectoryCacheSnapshot;
+       if (snapshot == NULL)
+               return B_NO_MEMORY;
+
+       uint64 change = 0;
+       uint64 dirCookie = 0;
+       uint64 dirCookieVerf = 0;
        bool eof = false;
 
-       char* buffer = reinterpret_cast<char*>(_buffer);
+       while (!eof) {
+               uint32 count;
+               DirEntry* dirents;
+
+               status_t result = _ReadDirOnce(&dirents, &count, cookie, &eof, 
&change,
+                       &dirCookie, &dirCookieVerf);
+               if (result != B_OK) {
+                       delete snapshot;
+                       return result;
+               }
+
+               uint32 i;
+               for (i = 0; i < count; i++) {
+
+                       // FATTR4_FSID is mandatory
+                       void* data = dirents[i].fAttrs[0].fData.fPointer;
+                       FileSystemId* fsid = 
reinterpret_cast<FileSystemId*>(data);
+                       if (*fsid != fFileSystem->FsId())
+                               continue;
 
+                       ino_t id;
+                       if (dirents[i].fAttrCount == 2)
+                               id = 
_FileIdToInoT(dirents[i].fAttrs[1].fData.fValue64);
+                       else
+                               id = _FileIdToInoT(fFileSystem->AllocFileId());
+       
+                       NameCacheEntry* entry = new 
NameCacheEntry(dirents[i].fName, id);
+                       if (entry == NULL || entry->fName == NULL) {
+                               if (entry->fName == NULL)
+                                       delete entry;
+                               delete snapshot;
+                               delete[] dirents;
+                               return B_NO_MEMORY;
+                       }
+                       snapshot->fEntries.Add(entry);
+               }
+
+               delete[] dirents;
+       }
+
+       *_snapshot = snapshot;
+       *_change = change;
+
+       return B_OK;
+}
+
+/*
        if (cookie->fCookie == 0 && cookie->fCookieVerf == 2 && count < 
*_count) {
                struct dirent* de = reinterpret_cast<dirent*>(buffer + pos);
 
@@ -275,51 +357,79 @@ Inode::ReadDir(void* _buffer, uint32 size, uint32* _count,
                count++;
                cookie->fCookieVerf--;
        }
+*/
 
-       bool overflow = false;
-       while (count < *_count && !eof) {
-               this_count = *_count - count;
-               DirEntry* dirents;
+status_t
+Inode::ReadDir(void* _buffer, uint32 size, uint32* _count,
+       OpenDirCookie* cookie)
+{
+       if (cookie->fEOF) {
+               *_count = 0;
+               return B_OK;
+       }
 
-               status_t result = _ReadDirOnce(&dirents, &this_count, cookie, 
&eof);
-               if (result != B_OK)
-                       return result;
+       status_t result;
+       if (cookie->fSnapshot == NULL) {
+               fFileSystem->Revalidator().Lock();
+               if (fCache->Lock() != B_OK) {
+                       fCache->ResetAndLock();
+               } else {
+                       fFileSystem->Revalidator().RemoveDirectory(fCache);
+               }
 
-               uint32 i, entries = 0;
-               for (i = 0; i < min_c(this_count, *_count - count); i++) {
-                       struct dirent* de = reinterpret_cast<dirent*>(buffer + 
pos);
+               cookie->fSnapshot = fCache->GetSnapshot();
+               if (cookie->fSnapshot == NULL) {
+                       uint64 change;
+                       result = _GetDirSnapshot(&cookie->fSnapshot, cookie, 
&change);
+                       if (result != B_OK) {
+                               fCache->Unlock();
+                               fFileSystem->Revalidator().Unlock();
+                               return result;
+                       }
+                       fCache->ValidateChangeInfo(change);
+                       fCache->SetSnapshot(cookie->fSnapshot);
+               }
+               cookie->fSnapshot->AcquireReference();
+               fFileSystem->Revalidator().AddDirectory(fCache);
+               fCache->Unlock();
+               fFileSystem->Revalidator().Unlock();
+       }
 
-                       // FATTR4_FSID is mandatory
-                       void* data = dirents[i].fAttrs[0].fData.fPointer;
-                       FileSystemId* fsid = 
reinterpret_cast<FileSystemId*>(data);
-                       if (*fsid != fFileSystem->FsId())
-                               continue;
+       char* buffer = reinterpret_cast<char*>(_buffer);
+       uint32 pos = 0;
 
-                       ino_t id;
-                       if (dirents[i].fAttrCount == 2)
-                               id = 
_FileIdToInoT(dirents[i].fAttrs[1].fData.fValue64);
-                       else
-                               id = _FileIdToInoT(fFileSystem->AllocFileId());
+       MutexLocker _(cookie->fSnapshot->fLock);
+       uint32 i;
+       bool overflow = false;
+       for (i = 0; i < *_count; i++) {
+               struct dirent* de = reinterpret_cast<dirent*>(buffer + pos);
 
-                       const char* name = dirents[i].fName;
-                       if (_FillDirEntry(de, id, name, pos, size) == 
B_BUFFER_OVERFLOW) {
-                               eof = true;
-                               overflow = true;
-                               break;
-                       }
+               if (cookie->fCurrent == NULL)
+                       cookie->fCurrent = cookie->fSnapshot->fEntries.Head();
+               else {
+                       cookie->fCurrent
+                               = 
cookie->fSnapshot->fEntries.GetNext(cookie->fCurrent);
+               }
 
-                       pos += de->d_reclen;
-                       entries++;
+               if (cookie->fCurrent == NULL) {
+                       cookie->fEOF = true;
+                       break;
                }
-               delete[] dirents;
-               count += entries;
+
+               if (_FillDirEntry(de, cookie->fCurrent->fNode, 
cookie->fCurrent->fName,
+                       pos, size) == B_BUFFER_OVERFLOW) {
+                       overflow = true;
+                       break;
+               }
+
+               pos += de->d_reclen;
        }
 
-       if (count == 0 && overflow)
+       if (i == 0 && overflow)
                return B_BUFFER_OVERFLOW;
 
-       *_count = count;
-       
+       *_count = i;
+
        return B_OK;
 }
 
diff --git a/src/add-ons/kernel/file_systems/nfs4/ReplyInterpreter.cpp 
b/src/add-ons/kernel/file_systems/nfs4/ReplyInterpreter.cpp
index eedc5af..c80b5da 100644
--- a/src/add-ons/kernel/file_systems/nfs4/ReplyInterpreter.cpp
+++ b/src/add-ons/kernel/file_systems/nfs4/ReplyInterpreter.cpp
@@ -326,12 +326,33 @@ ReplyInterpreter::ReadDir(uint64* cookie, uint64* 
cookieVerf,
 
        bool isNext;
        uint32 count = 0;
-       DirEntry* entries = new(std::nothrow) DirEntry[*_count];
+
+       // TODO: using  list instead of array would make this much more elegant
+       // and efficient
+       XDR::Stream::Position dataStart = fReply->Stream().Current();
+       isNext = fReply->Stream().GetBoolean();
+       while (isNext) {
+               fReply->Stream().GetUHyper();
+
+               free(fReply->Stream().GetString());
+               AttrValue* values;
+               uint32 attrCount;
+               _DecodeAttrs(fReply->Stream(), &values, &attrCount);
+               delete[] values;
+
+               count++;
+
+               isNext = fReply->Stream().GetBoolean();
+       }
+
+       DirEntry* entries = new(std::nothrow) DirEntry[count];
        if (entries == NULL)
                return B_NO_MEMORY;
 
+       count = 0;
+       fReply->Stream().SetPosition(dataStart);
        isNext = fReply->Stream().GetBoolean();
-       while (isNext && count < *_count) {
+       while (isNext) {
                *cookie = fReply->Stream().GetUHyper();
 
                entries[count].fName = fReply->Stream().GetString();
diff --git a/src/add-ons/kernel/file_systems/nfs4/XDR.h 
b/src/add-ons/kernel/file_systems/nfs4/XDR.h
index f52ae0f..78cfbc6 100644
--- a/src/add-ons/kernel/file_systems/nfs4/XDR.h
+++ b/src/add-ons/kernel/file_systems/nfs4/XDR.h
@@ -39,6 +39,8 @@ public:
                                                        ReadStream(void* 
buffer, uint32 size);
        virtual                                 ~ReadStream();
 
+       inline  void                    SetPosition(Position position);
+
        inline  int                             Size() const;
 
                        int32                   GetInt();
@@ -108,6 +110,13 @@ Stream::Current() const
 }
 
 
+inline void
+ReadStream::SetPosition(Position position)
+{
+       fPosition = position;
+}
+
+
 inline int
 ReadStream::Size() const
 {


Other related posts: