root/src/add-ons/kernel/file_systems/layers/write_overlay/write_overlay.cpp
/*
 * Copyright 2009-2016, Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Michael Lotz <mmlr@mlotz.ch>
 */

#include <new>
#include <stdlib.h>
#include <string.h>

#include <dirent.h>

#include <util/kernel_cpp.h>
#include <util/AutoLock.h>

#include <fs_cache.h>
#include <fs_info.h>
#include <fs_interface.h>
#include <io_requests.h>

#include <debug.h>
#include <KernelExport.h>
#include <NodeMonitor.h>

#include "IORequest.h"


//#define TRACE_OVERLAY
#ifdef TRACE_OVERLAY
#define TRACE(x...)                     dprintf("write_overlay: " x)
#define TRACE_VOLUME(x...)      dprintf("write_overlay: " x)
#define TRACE_ALWAYS(x...)      dprintf("write_overlay: " x)
#else
#define TRACE(x...)                     /* nothing */
#define TRACE_VOLUME(x...)      /* nothing */
#define TRACE_ALWAYS(x...)      dprintf("write_overlay: " x)
#endif


namespace write_overlay {

status_t publish_overlay_vnode(fs_volume *volume, ino_t inodeNumber,
        void *privateNode, int type);

class OverlayInode;

struct open_cookie {
        OverlayInode *  node;
        int                             open_mode;
        void *                  super_cookie;
};


struct open_dir_cookie {
        uint32                  index;
};


struct overlay_dirent {
        ino_t                   inode_number;
        char *                  name;
        OverlayInode *  node; // only for attributes

        void                    remove_and_dispose(fs_volume *volume, ino_t directoryInode)
                                        {
                                                notify_entry_removed(volume->id, directoryInode,
                                                        name, inode_number);
                                                remove_vnode(volume, inode_number);
                                                free(name);
                                                free(this);
                                        }

        void                    dispose_attribute(fs_volume *volume, ino_t fileInode)
                                        {
                                                notify_attribute_changed(volume->id, -1, fileInode,
                                                        name, B_ATTR_REMOVED);
                                                free(name);
                                                free(this);
                                        }
};


struct write_buffer {
        write_buffer *  next;
        off_t                   position;
        size_t                  length;
        uint8                   buffer[1];
};


class OverlayVolume {
public:
                                                        OverlayVolume(fs_volume *volume);
                                                        ~OverlayVolume();

                fs_volume *                     Volume() { return fVolume; }
                fs_volume *                     SuperVolume() { return fVolume->super_volume; }

                ino_t                           BuildInodeNumber() { return fCurrentInodeNumber++; }

private:
                fs_volume *                     fVolume;
                ino_t                           fCurrentInodeNumber;
};


class OverlayInode {
public:
                                                        OverlayInode(OverlayVolume *volume,
                                                                fs_vnode *superVnode, ino_t inodeNumber,
                                                                OverlayInode *parentDir = NULL,
                                                                const char *name = NULL, mode_t mode = 0,
                                                                bool attribute = false,
                                                                type_code attributeType = 0);
                                                        ~OverlayInode();

                status_t                        InitCheck();

                bool                            Lock() { return recursive_lock_lock(&fLock) == B_OK; }
                void                            Unlock() { recursive_lock_unlock(&fLock); }

                bool                            IsVirtual() { return fIsVirtual; }
                bool                            IsModified() { return fIsModified; }
                bool                            IsDataModified() { return fIsDataModified; }
                bool                            IsAttribute() { return fIsAttribute; }

                fs_volume *                     Volume() { return fVolume->Volume(); }
                fs_volume *                     SuperVolume() { return fVolume->SuperVolume(); }

                void                            SetSuperVnode(fs_vnode *superVnode);
                fs_vnode *                      SuperVnode() { return &fSuperVnode; }

                void                            SetInodeNumber(ino_t inodeNumber);
                ino_t                           InodeNumber() { return fInodeNumber; }

                void                            SetModified();
                void                            SetDataModified();
                void                            CreateCache();

                void                            SetParentDir(OverlayInode *parentDir);
                OverlayInode *          ParentDir() { return fParentDir; }

                bool                            IsNonEmptyDirectory();

                status_t                        Lookup(const char *name, ino_t *inodeNumber);
                status_t                        LookupAttribute(const char *name,
                                                                OverlayInode **node);

                void                            SetName(const char *name);
                status_t                        GetName(char *buffer, size_t bufferSize);

                status_t                        ReadStat(struct stat *stat);
                status_t                        WriteStat(const struct stat *stat, uint32 statMask);

                status_t                        Create(const char *name, int openMode, int perms,
                                                                void **cookie, ino_t *newInodeNumber,
                                                                bool attribute = false,
                                                                type_code attributeType = 0);
                status_t                        Open(int openMode, void **cookie);
                status_t                        Close(void *cookie);
                status_t                        FreeCookie(void *cookie);
                status_t                        Read(void *cookie, off_t position, void *buffer,
                                                                size_t *length, bool readPages,
                                                                IORequest *ioRequest);
                status_t                        Write(void *cookie, off_t position,
                                                                const void *buffer, size_t length,
                                                                IORequest *request);

                status_t                        SynchronousIO(void *cookie, IORequest *request);

                status_t                        SetFlags(void *cookie, int flags);

                status_t                        CreateDir(const char *name, int perms);
                status_t                        RemoveDir(const char *name);
                status_t                        OpenDir(void **cookie, bool attribute = false);
                status_t                        CloseDir(void *cookie);
                status_t                        FreeDirCookie(void *cookie);
                status_t                        ReadDir(void *cookie, struct dirent *buffer,
                                                                size_t bufferSize, uint32 *num,
                                                                bool attribute = false);
                status_t                        RewindDir(void *cookie);

                status_t                        CreateSymlink(const char *name, const char *path,
                                                                int mode);
                status_t                        ReadSymlink(char *buffer, size_t *bufferSize);

                status_t                        AddEntry(overlay_dirent *entry,
                                                                bool attribute = false);
                status_t                        RemoveEntry(const char *name,
                                                                overlay_dirent **entry, bool attribute = false);

private:
                void                            _TrimBuffers();

                status_t                        _PopulateStat();
                status_t                        _PopulateDirents();
                status_t                        _PopulateAttributeDirents();
                status_t                        _CreateCommon(const char *name, int type, int perms,
                                                                ino_t *newInodeNumber, OverlayInode **node,
                                                                bool attribute, type_code attributeType, bool exclusive = false);

                recursive_lock          fLock;
                OverlayVolume *         fVolume;
                OverlayInode *          fParentDir;
                const char *            fName;
                fs_vnode                        fSuperVnode;
                ino_t                           fInodeNumber;
                write_buffer *          fWriteBuffers;
                off_t                           fOriginalNodeLength;
                overlay_dirent **       fDirents;
                uint32                          fDirentCount;
                overlay_dirent **       fAttributeDirents;
                uint32                          fAttributeDirentCount;
                struct stat                     fStat;
                bool                            fHasStat;
                bool                            fHasDirents;
                bool                            fHasAttributeDirents;
                bool                            fIsVirtual;
                bool                            fIsAttribute;
                bool                            fIsModified;
                bool                            fIsDataModified;
                void *                          fFileCache;
};


//      #pragma mark OverlayVolume


OverlayVolume::OverlayVolume(fs_volume *volume)
        :       fVolume(volume),
                fCurrentInodeNumber((ino_t)1 << 60)
{
}


OverlayVolume::~OverlayVolume()
{
}


//      #pragma mark OverlayInode


OverlayInode::OverlayInode(OverlayVolume *volume, fs_vnode *superVnode,
        ino_t inodeNumber, OverlayInode *parentDir, const char *name, mode_t mode,
        bool attribute, type_code attributeType)
        :       fVolume(volume),
                fParentDir(parentDir),
                fName(name),
                fInodeNumber(inodeNumber),
                fWriteBuffers(NULL),
                fOriginalNodeLength(-1),
                fDirents(NULL),
                fDirentCount(0),
                fAttributeDirents(NULL),
                fAttributeDirentCount(0),
                fHasStat(false),
                fHasDirents(false),
                fHasAttributeDirents(false),
                fIsVirtual(superVnode == NULL),
                fIsAttribute(attribute),
                fIsModified(false),
                fIsDataModified(false),
                fFileCache(NULL)
{
        TRACE("inode created %" B_PRIdINO "\n", fInodeNumber);

        recursive_lock_init(&fLock, "write overlay inode lock");
        if (superVnode != NULL)
                fSuperVnode = *superVnode;
        else {
                fStat.st_dev = SuperVolume()->id;
                fStat.st_ino = fInodeNumber;
                fStat.st_mode = mode;
                fStat.st_nlink = 1;
                fStat.st_uid = 0;
                fStat.st_gid = 0;
                fStat.st_size = 0;
                fStat.st_rdev = 0;
                fStat.st_blksize = 1024;
                fStat.st_atime = fStat.st_mtime = fStat.st_ctime = fStat.st_crtime
                        = time(NULL);
                fStat.st_type = attributeType;
                fHasStat = true;
        }
}


OverlayInode::~OverlayInode()
{
        TRACE("inode destroyed %" B_PRIdINO "\n", fInodeNumber);
        if (fFileCache != NULL)
                file_cache_delete(fFileCache);

        write_buffer *element = fWriteBuffers;
        while (element) {
                write_buffer *next = element->next;
                free(element);
                element = next;
        }

        for (uint32 i = 0; i < fDirentCount; i++) {
                free(fDirents[i]->name);
                free(fDirents[i]);
        }
        free(fDirents);

        for (uint32 i = 0; i < fAttributeDirentCount; i++) {
                free(fAttributeDirents[i]->name);
                free(fAttributeDirents[i]);
        }
        free(fAttributeDirents);

        recursive_lock_destroy(&fLock);
}


status_t
OverlayInode::InitCheck()
{
        return B_OK;
}


void
OverlayInode::SetSuperVnode(fs_vnode *superVnode)
{
        RecursiveLocker locker(fLock);
        fSuperVnode = *superVnode;
}


void
OverlayInode::SetInodeNumber(ino_t inodeNumber)
{
        RecursiveLocker locker(fLock);
        fInodeNumber = inodeNumber;
}


void
OverlayInode::SetModified()
{
        if (fIsAttribute) {
                fIsModified = true;
                return;
        }

        // we must ensure that a modified node never get's put, as we cannot get it
        // from the underlying filesystem, so we get an additional reference here
        // and deliberately leak it
        // TODO: what about non-force unmounting then?
        void *unused = NULL;
        get_vnode(Volume(), fInodeNumber, &unused);
        fIsModified = true;
}


void
OverlayInode::SetDataModified()
{
        fIsDataModified = true;
        if (!fIsModified)
                SetModified();
}


void
OverlayInode::CreateCache()
{
        if (!S_ISDIR(fStat.st_mode) && !S_ISLNK(fStat.st_mode)) {
                fFileCache = file_cache_create(fStat.st_dev, fStat.st_ino, 0);
                if (fFileCache != NULL)
                        file_cache_disable(fFileCache);
        }
}


void
OverlayInode::SetParentDir(OverlayInode *parentDir)
{
        RecursiveLocker locker(fLock);
        fParentDir = parentDir;
        if (fHasDirents && fDirentCount >= 2)
                fDirents[1]->inode_number = parentDir->InodeNumber();
}


bool
OverlayInode::IsNonEmptyDirectory()
{
        RecursiveLocker locker(fLock);
        if (!fHasStat)
                _PopulateStat();

        if (!S_ISDIR(fStat.st_mode))
                return false;

        if (!fHasDirents)
                _PopulateDirents();

        return fDirentCount > 2; // accounting for "." and ".." entries
}


status_t
OverlayInode::Lookup(const char *name, ino_t *inodeNumber)
{
        RecursiveLocker locker(fLock);
        if (!fHasDirents)
                _PopulateDirents();

        for (uint32 i = 0; i < fDirentCount; i++) {
                if (strcmp(fDirents[i]->name, name) == 0) {
                        *inodeNumber = fDirents[i]->inode_number;
                        locker.Unlock();

                        OverlayInode *node = NULL;
                        status_t result = get_vnode(Volume(), *inodeNumber,
                                (void **)&node);
                        if (result == B_OK && node != NULL && i >= 2)
                                node->SetParentDir(this);
                        return result;
                }
        }

        return B_ENTRY_NOT_FOUND;
}


status_t
OverlayInode::LookupAttribute(const char *name, OverlayInode **node)
{
        RecursiveLocker locker(fLock);
        if (!fHasAttributeDirents)
                _PopulateAttributeDirents();

        for (uint32 i = 0; i < fAttributeDirentCount; i++) {
                overlay_dirent *dirent = fAttributeDirents[i];
                if (strcmp(dirent->name, name) == 0) {
                        if (dirent->node == NULL) {
                                OverlayInode *newNode = new(std::nothrow) OverlayInode(fVolume,
                                        SuperVnode(), fInodeNumber, NULL, dirent->name, 0, true, 0);
                                if (newNode == NULL)
                                        return B_NO_MEMORY;

                                status_t result = newNode->InitCheck();
                                if (result != B_OK) {
                                        delete newNode;
                                        return result;
                                }

                                dirent->node = newNode;
                        }

                        *node = dirent->node;
                        return B_OK;
                }
        }

        return B_ENTRY_NOT_FOUND;
}


void
OverlayInode::SetName(const char *name)
{
        RecursiveLocker locker(fLock);
        fName = name;
        if (!fIsModified)
                SetModified();
}


status_t
OverlayInode::GetName(char *buffer, size_t bufferSize)
{
        RecursiveLocker locker(fLock);
        if (fName != NULL) {
                strlcpy(buffer, fName, bufferSize);
                return B_OK;
        }

        if (fIsVirtual || fIsAttribute)
                return B_UNSUPPORTED;

        if (fSuperVnode.ops->get_vnode_name == NULL)
                return B_UNSUPPORTED;

        return fSuperVnode.ops->get_vnode_name(SuperVolume(), &fSuperVnode, buffer,
                bufferSize);
}


status_t
OverlayInode::ReadStat(struct stat *stat)
{
        RecursiveLocker locker(fLock);
        if (!fHasStat)
                _PopulateStat();

        memcpy(stat, &fStat, sizeof(struct stat));
        stat->st_blocks = (stat->st_size + DEV_BSIZE - 1) / DEV_BSIZE;
        return B_OK;
}


status_t
OverlayInode::WriteStat(const struct stat *stat, uint32 statMask)
{
        if (fIsAttribute)
                return B_UNSUPPORTED;

        RecursiveLocker locker(fLock);
        if (!fHasStat)
                _PopulateStat();

        if (statMask & B_STAT_SIZE) {
                if (fStat.st_size != stat->st_size) {
                        fStat.st_size = stat->st_size;
                        if (!fIsDataModified)
                                SetDataModified();
                        _TrimBuffers();
                }
        }

        if (statMask & B_STAT_MODE)
                fStat.st_mode = (fStat.st_mode & ~S_IUMSK) | (stat->st_mode & S_IUMSK);
        if (statMask & B_STAT_UID)
                fStat.st_uid = stat->st_uid;
        if (statMask & B_STAT_GID)
                fStat.st_gid = stat->st_gid;

        if (statMask & B_STAT_MODIFICATION_TIME)
                fStat.st_mtime = stat->st_mtime;
        if (statMask & B_STAT_CREATION_TIME)
                fStat.st_crtime = stat->st_crtime;

        if ((statMask & (B_STAT_MODE | B_STAT_UID | B_STAT_GID)) != 0
                && (statMask & B_STAT_MODIFICATION_TIME) == 0) {
                fStat.st_mtime = time(NULL);
                statMask |= B_STAT_MODIFICATION_TIME;
        }

        if (!fIsModified)
                SetModified();

        notify_stat_changed(SuperVolume()->id, -1, fInodeNumber, statMask);
        return B_OK;
}


status_t
OverlayInode::Create(const char *name, int openMode, int perms, void **cookie,
        ino_t *newInodeNumber, bool attribute, type_code attributeType)
{
        OverlayInode *newNode = NULL;
        status_t result = _CreateCommon(name, attribute ? S_ATTR : S_IFREG, perms,
                newInodeNumber, &newNode, attribute, attributeType, (openMode & O_EXCL) != 0);
        if (result != B_OK)
                return result;

        return newNode->Open(openMode, cookie);
}


status_t
OverlayInode::Open(int openMode, void **_cookie)
{
        RecursiveLocker locker(fLock);
        if (!fHasStat)
                _PopulateStat();

        open_cookie *cookie = (open_cookie *)malloc(sizeof(open_cookie));
        if (cookie == NULL)
                return B_NO_MEMORY;

        cookie->open_mode = openMode;
        cookie->node = this;
        *_cookie = cookie;

        if (fIsVirtual) {
                if (openMode & O_TRUNC) {
                        fStat.st_size = 0;
                        _TrimBuffers();
                }

                return B_OK;
        }

        if ((fIsAttribute && fSuperVnode.ops->open_attr == NULL)
                || (!fIsAttribute && fSuperVnode.ops->open == NULL))
                return B_UNSUPPORTED;

        if (openMode & O_TRUNC) {
                if (fStat.st_size != 0) {
                        fStat.st_size = 0;
                        _TrimBuffers();
                        if (!fIsDataModified)
                                SetDataModified();
                }
        }

        openMode &= ~(O_RDWR | O_WRONLY | O_TRUNC | O_CREAT);
        status_t result;
        if (fIsAttribute) {
                result = fSuperVnode.ops->open_attr(SuperVolume(), &fSuperVnode,
                        fName, openMode, &cookie->super_cookie);
        } else {
                result = fSuperVnode.ops->open(SuperVolume(), &fSuperVnode,
                        openMode, &cookie->super_cookie);
        }

        if (result != B_OK) {
                free(cookie);
                return result;
        }

        if (fOriginalNodeLength < 0) {
                struct stat stat;
                if (fIsAttribute) {
                        result = fSuperVnode.ops->read_attr_stat(SuperVolume(),
                                &fSuperVnode, cookie->super_cookie, &stat);
                } else {
                        result = fSuperVnode.ops->read_stat(SuperVolume(),
                                &fSuperVnode, &stat);
                }

                if (result != B_OK)
                        return result;

                fOriginalNodeLength = stat.st_size;
        }

        return B_OK;
}


status_t
OverlayInode::Close(void *_cookie)
{
        if (fIsVirtual)
                return B_OK;

        open_cookie *cookie = (open_cookie *)_cookie;
        if (fIsAttribute) {
                return fSuperVnode.ops->close_attr(SuperVolume(), &fSuperVnode,
                        cookie->super_cookie);
        }

        return fSuperVnode.ops->close(SuperVolume(), &fSuperVnode,
                cookie->super_cookie);
}


status_t
OverlayInode::FreeCookie(void *_cookie)
{
        status_t result = B_OK;
        open_cookie *cookie = (open_cookie *)_cookie;
        if (!fIsVirtual) {
                if (fIsAttribute) {
                        result = fSuperVnode.ops->free_attr_cookie(SuperVolume(),
                                &fSuperVnode, cookie->super_cookie);
                } else {
                        result = fSuperVnode.ops->free_cookie(SuperVolume(),
                                &fSuperVnode, cookie->super_cookie);
                }
        }

        free(cookie);
        return result;
}


status_t
OverlayInode::Read(void *_cookie, off_t position, void *buffer, size_t *length,
        bool readPages, IORequest *ioRequest)
{
        RecursiveLocker locker(fLock);
        if (position >= fStat.st_size) {
                *length = 0;
                return B_OK;
        }

        uint8 *pointer = (uint8 *)buffer;
        write_buffer *element = fWriteBuffers;
        size_t bytesLeft = (size_t)MIN(fStat.st_size - position, (off_t)*length);
        *length = bytesLeft;

        void *superCookie = _cookie;
        if (!fIsVirtual && !readPages && _cookie != NULL)
                superCookie = ((open_cookie *)_cookie)->super_cookie;

        while (bytesLeft > 0) {
                size_t gapSize = bytesLeft;
                if (element != NULL) {
                        gapSize = (size_t)MIN((off_t)bytesLeft, element->position > position ?
                                element->position - position : 0);
                }

                if (gapSize > 0 && !fIsVirtual && position < fOriginalNodeLength) {
                        // there's a part missing between the read position and our
                        // next position, fill the gap with original file content
                        size_t readLength = (size_t)MIN(fOriginalNodeLength - position,
                                (off_t)gapSize);
                        status_t result = B_ERROR;
                        if (readPages) {
                                iovec vector;
                                vector.iov_base = pointer;
                                vector.iov_len = readLength;

                                result = fSuperVnode.ops->read_pages(SuperVolume(),
                                        &fSuperVnode, superCookie, position, &vector, 1,
                                        &readLength);
                        } else if (ioRequest != NULL) {
                                IORequest *subRequest;
                                result = ioRequest->CreateSubRequest(position, position,
                                        readLength, subRequest);
                                if (result != B_OK)
                                        return result;

                                bool wereSuppressed = ioRequest->SuppressChildNotifications();
                                ioRequest->SetSuppressChildNotifications(true);
                                result = fSuperVnode.ops->io(SuperVolume(), &fSuperVnode,
                                        superCookie, subRequest);
                                if (result != B_OK)
                                        return result;

                                result = subRequest->Wait(0, 0);
                                readLength = subRequest->TransferredBytes();
                                ioRequest->SetSuppressChildNotifications(wereSuppressed);
                        } else if (fIsAttribute) {
                                result = fSuperVnode.ops->read_attr(SuperVolume(), &fSuperVnode,
                                        superCookie, position, pointer, &readLength);
                        } else {
                                result = fSuperVnode.ops->read(SuperVolume(), &fSuperVnode,
                                        superCookie, position, pointer, &readLength);
                        }

                        if (result != B_OK)
                                return result;

                        pointer += readLength;
                        position += readLength;
                        bytesLeft -= readLength;
                        gapSize -= readLength;
                }

                if (gapSize > 0) {
                        // there's a gap before our next position which we cannot
                        // fill with original file content, zero it out
                        if (ioRequest != NULL)
                                ;// TODO: handle this case
                        else
                                user_memset(pointer, 0, gapSize);

                        bytesLeft -= gapSize;
                        position += gapSize;
                        pointer += gapSize;
                }

                // we've reached the end
                if (bytesLeft == 0 || element == NULL)
                        break;

                off_t elementEnd = element->position + element->length;
                if (elementEnd > position) {
                        size_t copyLength = (size_t)MIN(elementEnd - position,
                                (off_t)bytesLeft);

                        const void *source = element->buffer + (position
                                - element->position);
                        if (ioRequest != NULL) {
                                ioRequest->CopyData(source, ioRequest->Offset()
                                        + ((addr_t)pointer - (addr_t)buffer), copyLength);
                        } else if (user_memcpy(pointer, source, copyLength) < B_OK)
                                return B_BAD_ADDRESS;

                        bytesLeft -= copyLength;
                        position += copyLength;
                        pointer += copyLength;
                }

                element = element->next;
        }

        return B_OK;
}


status_t
OverlayInode::Write(void *_cookie, off_t position, const void *buffer,
        size_t length, IORequest *ioRequest)
{
        RecursiveLocker locker(fLock);
        if (_cookie != NULL) {
                open_cookie *cookie = (open_cookie *)_cookie;
                if (cookie->open_mode & O_APPEND)
                        position = fStat.st_size;
        }

        if (!fIsDataModified)
                SetDataModified();

        // find insertion point
        write_buffer **link = &fWriteBuffers;
        write_buffer *other = fWriteBuffers;
        write_buffer *swallow = NULL;
        off_t newPosition = position;
        size_t newLength = length;
        uint32 swallowCount = 0;

        while (other) {
                off_t newEnd = newPosition + newLength;
                off_t otherEnd = other->position + other->length;
                if (otherEnd < newPosition) {
                        // other is completely before us
                        link = &other->next;
                        other = other->next;
                        continue;
                }

                if (other->position > newEnd) {
                        // other is completely past us
                        break;
                }

                swallowCount++;
                if (swallow == NULL)
                        swallow = other;

                if (other->position <= newPosition) {
                        if (swallowCount == 1 && otherEnd >= newEnd) {
                                // other chunk completely covers us, just copy
                                void *target = other->buffer + (newPosition - other->position);
                                if (ioRequest != NULL)
                                        ioRequest->CopyData(ioRequest->Offset(), target, length);
                                else if (user_memcpy(target, buffer, length) < B_OK)
                                        return B_BAD_ADDRESS;

                                fStat.st_mtime = time(NULL);
                                if (fIsAttribute) {
                                        notify_attribute_changed(SuperVolume()->id, -1,
                                                fInodeNumber, fName, B_ATTR_CHANGED);
                                } else {
                                        notify_stat_changed(SuperVolume()->id, -1, fInodeNumber,
                                                B_STAT_MODIFICATION_TIME);
                                }
                                return B_OK;
                        }

                        newLength += newPosition - other->position;
                        newPosition = other->position;
                }

                if (otherEnd > newEnd)
                        newLength += otherEnd - newEnd;

                other = other->next;
        }

        write_buffer *element = (write_buffer *)malloc(sizeof(write_buffer) - 1
                + newLength);
        if (element == NULL)
                return B_NO_MEMORY;

        element->next = *link;
        element->position = newPosition;
        element->length = newLength;
        *link = element;

        bool sizeChanged = false;
        off_t newEnd = newPosition + newLength;
        if (newEnd > fStat.st_size) {
                fStat.st_size = newEnd;
                sizeChanged = true;

                if (fFileCache)
                        file_cache_set_size(fFileCache, newEnd);
        }

        // populate the buffer with the existing chunks
        if (swallowCount > 0) {
                while (swallowCount-- > 0) {
                        memcpy(element->buffer + (swallow->position - newPosition),
                                swallow->buffer, swallow->length);

                        element->next = swallow->next;
                        free(swallow);
                        swallow = element->next;
                }
        }

        void *target = element->buffer + (position - newPosition);
        if (ioRequest != NULL)
                ioRequest->CopyData(0, target, length);
        else if (user_memcpy(target, buffer, length) < B_OK)
                return B_BAD_ADDRESS;

        fStat.st_mtime = time(NULL);

        if (fIsAttribute) {
                notify_attribute_changed(SuperVolume()->id, -1, fInodeNumber, fName,
                        B_ATTR_CHANGED);
        } else {
                notify_stat_changed(SuperVolume()->id, -1, fInodeNumber,
                        B_STAT_MODIFICATION_TIME | (sizeChanged ? B_STAT_SIZE : 0));
        }

        return B_OK;
}


status_t
OverlayInode::SynchronousIO(void *cookie, IORequest *request)
{
        status_t result;
        size_t length = request->Length();
        if (request->IsWrite())
                result = Write(cookie, request->Offset(), NULL, length, request);
        else
                result = Read(cookie, request->Offset(), NULL, &length, false, request);

        if (result == B_OK)
                request->SetTransferredBytes(false, length);

        request->SetStatusAndNotify(result);
        return result;
}


status_t
OverlayInode::SetFlags(void *_cookie, int flags)
{
        // we can only handle O_APPEND, O_NONBLOCK is ignored.
        open_cookie *cookie = (open_cookie *)_cookie;
        cookie->open_mode = (cookie->open_mode & ~O_APPEND) | (flags & ~O_APPEND);
        return B_OK;
}


status_t
OverlayInode::CreateDir(const char *name, int perms)
{
        return _CreateCommon(name, S_IFDIR, perms, NULL, NULL, false, 0);
}


status_t
OverlayInode::RemoveDir(const char *name)
{
        return RemoveEntry(name, NULL);
}


status_t
OverlayInode::OpenDir(void **cookie, bool attribute)
{
        RecursiveLocker locker(fLock);
        if (!attribute) {
                if (!fHasStat)
                        _PopulateStat();

                if (!S_ISDIR(fStat.st_mode))
                        return B_NOT_A_DIRECTORY;
        }

        if (!attribute && !fHasDirents)
                _PopulateDirents();
        else if (attribute && !fHasAttributeDirents)
                _PopulateAttributeDirents();

        open_dir_cookie *dirCookie = (open_dir_cookie *)malloc(
                sizeof(open_dir_cookie));
        if (dirCookie == NULL)
                return B_NO_MEMORY;

        dirCookie->index = 0;
        *cookie = dirCookie;
        return B_OK;
}


status_t
OverlayInode::CloseDir(void *cookie)
{
        return B_OK;
}


status_t
OverlayInode::FreeDirCookie(void *cookie)
{
        free(cookie);
        return B_OK;
}


status_t
OverlayInode::ReadDir(void *cookie, struct dirent *buffer, size_t bufferSize,
        uint32 *num, bool attribute)
{
        RecursiveLocker locker(fLock);
        uint32 direntCount = attribute ? fAttributeDirentCount : fDirentCount;
        overlay_dirent **dirents = attribute ? fAttributeDirents : fDirents;

        open_dir_cookie *dirCookie = (open_dir_cookie *)cookie;
        if (dirCookie->index >= direntCount) {
                *num = 0;
                return B_OK;
        }

        overlay_dirent *dirent = dirents[dirCookie->index++];
        size_t nameLength = MIN(strlen(dirent->name),
                bufferSize - offsetof(struct dirent, d_name)) + 1;

        buffer->d_dev = SuperVolume()->id;
        buffer->d_pdev = 0;
        buffer->d_ino = dirent->inode_number;
        buffer->d_pino = 0;
        buffer->d_reclen = offsetof(struct dirent, d_name) + nameLength;
        strlcpy(buffer->d_name, dirent->name, nameLength);

        *num = 1;
        return B_OK;
}


status_t
OverlayInode::RewindDir(void *cookie)
{
        open_dir_cookie *dirCookie = (open_dir_cookie *)cookie;
        dirCookie->index = 0;
        return B_OK;
}


status_t
OverlayInode::CreateSymlink(const char *name, const char *path, int mode)
{
        OverlayInode *newNode = NULL;
        // TODO: find out why mode is ignored
        status_t result = _CreateCommon(name, S_IFLNK, 0777, NULL, &newNode,
                false, 0);
        if (result != B_OK)
                return result;

        return newNode->Write(NULL, 0, path, strlen(path), NULL);
}


status_t
OverlayInode::ReadSymlink(char *buffer, size_t *bufferSize)
{
        if (fIsVirtual) {
                if (!S_ISLNK(fStat.st_mode))
                        return B_BAD_VALUE;

                status_t result = Read(NULL, 0, buffer, bufferSize, false, NULL);
                *bufferSize = fStat.st_size;
                return result;
        }

        if (fSuperVnode.ops->read_symlink == NULL)
                return B_UNSUPPORTED;

        return fSuperVnode.ops->read_symlink(SuperVolume(), &fSuperVnode, buffer,
                bufferSize);
}


status_t
OverlayInode::AddEntry(overlay_dirent *entry, bool attribute)
{
        RecursiveLocker locker(fLock);
        if (!attribute && !fHasDirents)
                _PopulateDirents();
        else if (attribute && !fHasAttributeDirents)
                _PopulateAttributeDirents();

        status_t result = RemoveEntry(entry->name, NULL, attribute);
        if (result != B_OK && result != B_ENTRY_NOT_FOUND)
                return B_FILE_EXISTS;

        overlay_dirent **newDirents = (overlay_dirent **)realloc(
                attribute ? fAttributeDirents : fDirents,
                sizeof(overlay_dirent *)
                        * ((attribute ? fAttributeDirentCount : fDirentCount) + 1));
        if (newDirents == NULL)
                return B_NO_MEMORY;

        if (attribute) {
                fAttributeDirents = newDirents;
                fAttributeDirents[fAttributeDirentCount++] = entry;
        } else {
                fDirents = newDirents;
                fDirents[fDirentCount++] = entry;
        }

        if (!fIsModified)
                SetModified();

        return B_OK;
}


status_t
OverlayInode::RemoveEntry(const char *name, overlay_dirent **_entry,
        bool attribute)
{
        RecursiveLocker locker(fLock);
        if (!attribute && !fHasDirents)
                _PopulateDirents();
        else if (attribute && !fHasAttributeDirents)
                _PopulateAttributeDirents();

        uint32 direntCount = attribute ? fAttributeDirentCount : fDirentCount;
        overlay_dirent **dirents = attribute ? fAttributeDirents : fDirents;
        for (uint32 i = 0; i < direntCount; i++) {
                overlay_dirent *entry = dirents[i];
                if (strcmp(entry->name, name) == 0) {
                        if (_entry == NULL && !attribute) {
                                // check for non-empty directories when trying
                                // to dispose the entry
                                OverlayInode *node = NULL;
                                status_t result = get_vnode(Volume(), entry->inode_number,
                                        (void **)&node);
                                if (result != B_OK)
                                        return result;

                                if (node->IsNonEmptyDirectory())
                                        result = B_DIRECTORY_NOT_EMPTY;

                                put_vnode(Volume(), entry->inode_number);
                                if (result != B_OK)
                                        return result;
                        }

                        for (uint32 j = i + 1; j < direntCount; j++)
                                dirents[j - 1] = dirents[j];

                        if (attribute)
                                fAttributeDirentCount--;
                        else
                                fDirentCount--;

                        if (_entry != NULL)
                                *_entry = entry;
                        else if (attribute)
                                entry->dispose_attribute(Volume(), fInodeNumber);
                        else
                                entry->remove_and_dispose(Volume(), fInodeNumber);

                        if (!fIsModified)
                                SetModified();

                        return B_OK;
                }
        }

        return B_ENTRY_NOT_FOUND;
}


void
OverlayInode::_TrimBuffers()
{
        // the file size has been changed and we want to trim
        // off everything that goes beyond the new size
        write_buffer **link = &fWriteBuffers;
        write_buffer *buffer = fWriteBuffers;

        while (buffer != NULL) {
                off_t bufferEnd = buffer->position + buffer->length;
                if (bufferEnd > fStat.st_size)
                        break;

                link = &buffer->next;
                buffer = buffer->next;
        }

        if (buffer == NULL) {
                // didn't find anything crossing or past the end
                return;
        }

        if (buffer->position < fStat.st_size) {
                // got a crossing buffer to resize
                size_t newLength = fStat.st_size - buffer->position;
                write_buffer *newBuffer = (write_buffer *)realloc(buffer,
                        sizeof(write_buffer) - 1 + newLength);

                if (newBuffer != NULL) {
                        buffer = newBuffer;
                        *link = newBuffer;
                } else {
                        // we don't really care if it worked, if it didn't we simply
                        // keep the old buffer and reset it's size
                }

                buffer->length = newLength;
                link = &buffer->next;
                buffer = buffer->next;
        }

        // everything else we can throw away
        *link = NULL;
        while (buffer != NULL) {
                write_buffer *next = buffer->next;
                free(buffer);
                buffer = next;
        }
}


status_t
OverlayInode::_PopulateStat()
{
        if (fHasStat)
                return B_OK;

        fHasStat = true;
        if (fIsAttribute) {
                if (fName == NULL || fSuperVnode.ops->open_attr == NULL
                        || fSuperVnode.ops->read_attr_stat == NULL)
                        return B_UNSUPPORTED;

                void *cookie = NULL;
                status_t result = fSuperVnode.ops->open_attr(SuperVolume(),
                        &fSuperVnode, fName, O_RDONLY, &cookie);
                if (result != B_OK)
                        return result;

                result = fSuperVnode.ops->read_attr_stat(SuperVolume(), &fSuperVnode,
                        cookie, &fStat);

                if (fSuperVnode.ops->close_attr != NULL)
                        fSuperVnode.ops->close_attr(SuperVolume(), &fSuperVnode, cookie);

                if (fSuperVnode.ops->free_attr_cookie != NULL) {
                        fSuperVnode.ops->free_attr_cookie(SuperVolume(), &fSuperVnode,
                                cookie);
                }

                return B_OK;
        }

        if (fSuperVnode.ops->read_stat == NULL)
                return B_UNSUPPORTED;

        return fSuperVnode.ops->read_stat(SuperVolume(), &fSuperVnode, &fStat);
}


status_t
OverlayInode::_PopulateDirents()
{
        if (fHasDirents)
                return B_OK;

        fDirents = (overlay_dirent **)malloc(sizeof(overlay_dirent *) * 2);
        if (fDirents == NULL)
                return B_NO_MEMORY;

        const char *names[] = { ".", ".." };
        ino_t inodes[] = { fInodeNumber,
                fParentDir != NULL ? fParentDir->InodeNumber() : 0 };
        for (uint32 i = 0; i < 2; i++) {
                fDirents[i] = (overlay_dirent *)malloc(sizeof(overlay_dirent));
                if (fDirents[i] == NULL)
                        return B_NO_MEMORY;

                fDirents[i]->inode_number = inodes[i];
                fDirents[i]->name = strdup(names[i]);
                if (fDirents[i]->name == NULL) {
                        free(fDirents[i]);
                        return B_NO_MEMORY;
                }

                fDirentCount++;
        }

        fHasDirents = true;
        if (fIsVirtual || fSuperVnode.ops->open_dir == NULL
                || fSuperVnode.ops->read_dir == NULL)
                return B_OK;

        // we don't really care about errors from here on
        void *superCookie = NULL;
        status_t result = fSuperVnode.ops->open_dir(SuperVolume(),
                &fSuperVnode, &superCookie);
        if (result != B_OK)
                return B_OK;

        size_t bufferSize = offsetof(struct dirent, d_name) + B_FILE_NAME_LENGTH;
        struct dirent *buffer = (struct dirent *)malloc(bufferSize);
        if (buffer == NULL)
                goto close_dir;

        while (true) {
                uint32 num = 1;
                result = fSuperVnode.ops->read_dir(SuperVolume(),
                        &fSuperVnode, superCookie, buffer, bufferSize, &num);
                if (result != B_OK || num == 0)
                        break;

                overlay_dirent **newDirents = (overlay_dirent **)realloc(fDirents,
                        sizeof(overlay_dirent *) * (fDirentCount + num));
                if (newDirents == NULL) {
                        TRACE_ALWAYS("failed to allocate storage for dirents\n");
                        break;
                }

                fDirents = newDirents;
                struct dirent *dirent = buffer;
                for (uint32 i = 0; i < num; i++) {
                        if (strcmp(dirent->d_name, ".") != 0
                                        && strcmp(dirent->d_name, "..") != 0) {
                                overlay_dirent *entry = (overlay_dirent *)malloc(
                                        sizeof(overlay_dirent));
                                if (entry == NULL) {
                                        TRACE_ALWAYS("failed to allocate storage for dirent\n");
                                        break;
                                }

                                entry->inode_number = dirent->d_ino;
                                entry->name = strdup(dirent->d_name);
                                if (entry->name == NULL) {
                                        TRACE_ALWAYS("failed to duplicate dirent entry name\n");
                                        free(entry);
                                        break;
                                }

                                fDirents[fDirentCount++] = entry;
                        }

                        dirent = (struct dirent *)((uint8 *)dirent + dirent->d_reclen);
                }
        }

        free(buffer);

close_dir:
        if (fSuperVnode.ops->close_dir != NULL)
                fSuperVnode.ops->close_dir(SuperVolume(), &fSuperVnode, superCookie);

        if (fSuperVnode.ops->free_dir_cookie != NULL) {
                fSuperVnode.ops->free_dir_cookie(SuperVolume(), &fSuperVnode,
                        superCookie);
        }

        return B_OK;
}


status_t
OverlayInode::_PopulateAttributeDirents()
{
        if (fHasAttributeDirents)
                return B_OK;

        fHasAttributeDirents = true;
        if (fIsVirtual || fSuperVnode.ops->open_attr_dir == NULL
                || fSuperVnode.ops->read_attr_dir == NULL)
                return B_OK;

        // we don't really care about errors from here on
        void *superCookie = NULL;
        status_t result = fSuperVnode.ops->open_attr_dir(SuperVolume(),
                &fSuperVnode, &superCookie);
        if (result != B_OK)
                return B_OK;

        size_t bufferSize = offsetof(struct dirent, d_name) + B_FILE_NAME_LENGTH;
        struct dirent *buffer = (struct dirent *)malloc(bufferSize);
        if (buffer == NULL)
                goto close_attr_dir;

        while (true) {
                uint32 num = 1;
                result = fSuperVnode.ops->read_attr_dir(SuperVolume(),
                        &fSuperVnode, superCookie, buffer, bufferSize, &num);
                if (result != B_OK || num == 0)
                        break;

                overlay_dirent **newDirents = (overlay_dirent **)realloc(
                        fAttributeDirents, sizeof(overlay_dirent *)
                                * (fAttributeDirentCount + num));
                if (newDirents == NULL) {
                        TRACE_ALWAYS("failed to allocate storage for attribute dirents\n");
                        break;
                }

                fAttributeDirents = newDirents;
                struct dirent *dirent = buffer;
                for (uint32 i = 0; i < num; i++) {
                        overlay_dirent *entry = (overlay_dirent *)malloc(
                                sizeof(overlay_dirent));
                        if (entry == NULL) {
                                TRACE_ALWAYS("failed to allocate storage for attr dirent\n");
                                break;
                        }

                        entry->node = NULL;
                        entry->inode_number = fInodeNumber;
                        entry->name = strdup(dirent->d_name);
                        if (entry->name == NULL) {
                                TRACE_ALWAYS("failed to duplicate dirent entry name\n");
                                free(entry);
                                break;
                        }

                        fAttributeDirents[fAttributeDirentCount++] = entry;
                        dirent = (struct dirent *)((uint8 *)dirent + dirent->d_reclen);
                }
        }

        free(buffer);

close_attr_dir:
        if (fSuperVnode.ops->close_attr_dir != NULL) {
                fSuperVnode.ops->close_attr_dir(SuperVolume(), &fSuperVnode,
                        superCookie);
        }

        if (fSuperVnode.ops->free_attr_dir_cookie != NULL) {
                fSuperVnode.ops->free_attr_dir_cookie(SuperVolume(), &fSuperVnode,
                        superCookie);
        }

        return B_OK;
}


status_t
OverlayInode::_CreateCommon(const char *name, int type, int perms,
        ino_t *newInodeNumber, OverlayInode **_node, bool attribute,
        type_code attributeType, bool exclusive)
{
        RecursiveLocker locker(fLock);
        status_t result;

        if (!fHasStat)
                _PopulateStat();

        if (!attribute && !S_ISDIR(fStat.st_mode))
                return B_NOT_A_DIRECTORY;

        locker.Unlock();

        if (!attribute && (type == S_IFDIR || type == S_IFLNK || exclusive)) {
                ino_t lookupInodeNumber;
                result = Lookup(name, &lookupInodeNumber);

                if (result != B_ENTRY_NOT_FOUND) {
                        put_vnode(Volume(), lookupInodeNumber);
                        return B_FILE_EXISTS;
                }
        }

        overlay_dirent *entry = (overlay_dirent *)malloc(sizeof(overlay_dirent));
        if (entry == NULL)
                return B_NO_MEMORY;

        entry->node = NULL;
        entry->name = strdup(name);
        if (entry->name == NULL) {
                free(entry);
                return B_NO_MEMORY;
        }

        if (attribute)
                entry->inode_number = fInodeNumber;
        else
                entry->inode_number = fVolume->BuildInodeNumber();

        OverlayInode *node = new(std::nothrow) OverlayInode(fVolume, NULL,
                entry->inode_number, this, entry->name, (perms & S_IUMSK) | type
                        | (attribute ? S_ATTR : 0), attribute, attributeType);
        if (node == NULL) {
                free(entry->name);
                free(entry);
                return B_NO_MEMORY;
        }

        result = AddEntry(entry, attribute);
        if (result != B_OK) {
                free(entry->name);
                free(entry);
                delete node;
                return result;
        }

        if (!attribute) {
                result = publish_overlay_vnode(fVolume->Volume(), entry->inode_number,
                        node, type);
                if (result != B_OK) {
                        RemoveEntry(entry->name, NULL);
                        delete node;
                        return result;
                }
        } else
                entry->node = node;

        node->Lock();
        node->SetDataModified();
        if (!attribute)
                node->CreateCache();
        node->Unlock();

        if (newInodeNumber != NULL)
                *newInodeNumber = entry->inode_number;
        if (_node != NULL)
                *_node = node;

        if (attribute) {
                notify_attribute_changed(SuperVolume()->id, -1, fInodeNumber,
                        entry->name, B_ATTR_CREATED);
        } else {
                notify_entry_created(SuperVolume()->id, fInodeNumber, entry->name,
                        entry->inode_number);
        }

        return B_OK;
}


//      #pragma mark - vnode ops


#define OVERLAY_CALL(op, params...) \
        TRACE("relaying op: " #op "\n"); \
        OverlayInode *node = (OverlayInode *)vnode->private_node; \
        if (node->IsVirtual()) \
                return B_UNSUPPORTED; \
        fs_vnode *superVnode = node->SuperVnode(); \
        if (superVnode->ops->op != NULL) \
                return superVnode->ops->op(volume->super_volume, superVnode, params); \
        return B_UNSUPPORTED;


static status_t
overlay_put_vnode(fs_volume *volume, fs_vnode *vnode, bool reenter)
{
        TRACE("put_vnode\n");
        OverlayInode *node = (OverlayInode *)vnode->private_node;
        if (node->IsVirtual() || node->IsModified()) {
                panic("loosing virtual/modified node\n");
                delete node;
                return B_OK;
        }

        status_t result = B_OK;
        fs_vnode *superVnode = node->SuperVnode();
        if (superVnode->ops->put_vnode != NULL) {
                result = superVnode->ops->put_vnode(volume->super_volume, superVnode,
                        reenter);
        }

        delete node;
        return result;
}


static status_t
overlay_remove_vnode(fs_volume *volume, fs_vnode *vnode, bool reenter)
{
        TRACE("remove_vnode\n");
        OverlayInode *node = (OverlayInode *)vnode->private_node;
        if (node->IsVirtual()) {
                delete node;
                return B_OK;
        }

        status_t result = B_OK;
        fs_vnode *superVnode = node->SuperVnode();
        if (superVnode->ops->put_vnode != NULL) {
                result = superVnode->ops->put_vnode(volume->super_volume, superVnode,
                        reenter);
        }

        delete node;
        return result;
}


static status_t
overlay_get_super_vnode(fs_volume *volume, fs_vnode *vnode,
        fs_volume *superVolume, fs_vnode *_superVnode)
{
        if (volume == superVolume) {
                *_superVnode = *vnode;
                return B_OK;
        }

        OverlayInode *node = (OverlayInode *)vnode->private_node;
        if (node->IsVirtual()) {
                *_superVnode = *vnode;
                return B_OK;
        }

        fs_vnode *superVnode = node->SuperVnode();
        if (superVnode->ops->get_super_vnode != NULL) {
                return superVnode->ops->get_super_vnode(volume->super_volume,
                        superVnode, superVolume, _superVnode);
        }

        *_superVnode = *superVnode;
        return B_OK;
}


static status_t
overlay_lookup(fs_volume *volume, fs_vnode *vnode, const char *name, ino_t *id)
{
        TRACE("lookup: \"%s\"\n", name);
        return ((OverlayInode *)vnode->private_node)->Lookup(name, id);
}


static status_t
overlay_get_vnode_name(fs_volume *volume, fs_vnode *vnode, char *buffer,
        size_t bufferSize)
{
        return ((OverlayInode *)vnode->private_node)->GetName(buffer, bufferSize);
}


static bool
overlay_can_page(fs_volume *volume, fs_vnode *vnode, void *cookie)
{
        TRACE("relaying op: can_page\n");
        OverlayInode *node = (OverlayInode *)vnode->private_node;
        if (node->IsVirtual())
                return false;

        fs_vnode *superVnode = node->SuperVnode();
        if (superVnode->ops->can_page != NULL) {
                return superVnode->ops->can_page(volume->super_volume, superVnode,
                        cookie);
        }

        return false;
}


static status_t
overlay_read_pages(fs_volume *volume, fs_vnode *vnode, void *cookie, off_t pos,
        const iovec *vecs, size_t count, size_t *numBytes)
{
        OverlayInode *node = (OverlayInode *)vnode->private_node;
        size_t bytesLeft = *numBytes;

        for (size_t i = 0; i < count; i++) {
                size_t transferBytes = MIN(vecs[i].iov_len, bytesLeft);
                status_t result = node->Read(cookie, pos, vecs[i].iov_base,
                        &transferBytes, true, NULL);
                if (result != B_OK) {
                        *numBytes -= bytesLeft;
                        return result;
                }

                bytesLeft -= transferBytes;
                if (bytesLeft == 0)
                        return B_OK;

                if (transferBytes < vecs[i].iov_len) {
                        *numBytes -= bytesLeft;
                        return B_OK;
                }

                pos += transferBytes;
        }

        *numBytes = 0;
        return B_OK;
}


static status_t
overlay_write_pages(fs_volume *volume, fs_vnode *vnode, void *cookie, off_t pos,
        const iovec *vecs, size_t count, size_t *numBytes)
{
        OverlayInode *node = (OverlayInode *)vnode->private_node;
        size_t bytesLeft = *numBytes;

        for (size_t i = 0; i < count; i++) {
                size_t transferBytes = MIN(vecs[i].iov_len, bytesLeft);
                status_t result = node->Write(cookie, pos, vecs[i].iov_base,
                        transferBytes, NULL);
                if (result != B_OK) {
                        *numBytes -= bytesLeft;
                        return result;
                }

                bytesLeft -= transferBytes;
                if (bytesLeft == 0)
                        return B_OK;

                if (transferBytes < vecs[i].iov_len) {
                        *numBytes -= bytesLeft;
                        return B_OK;
                }

                pos += transferBytes;
        }

        *numBytes = 0;
        return B_OK;
}


static status_t
overlay_io(fs_volume *volume, fs_vnode *vnode, void *cookie,
        io_request *request)
{
        OverlayInode *node = (OverlayInode *)vnode->private_node;
        if (io_request_is_write(request) || node->IsModified())
                return node->SynchronousIO(cookie, (IORequest *)request);

        TRACE("relaying op: io\n");
        fs_vnode *superVnode = node->SuperVnode();
        if (superVnode->ops->io != NULL) {
                return superVnode->ops->io(volume->super_volume, superVnode,
                        cookie != NULL ? ((open_cookie *)cookie)->super_cookie : NULL,
                        request);
        }

        return B_UNSUPPORTED;
}


static status_t
overlay_cancel_io(fs_volume *volume, fs_vnode *vnode, void *cookie,
        io_request *request)
{
        OVERLAY_CALL(cancel_io, cookie, request)
}


static status_t
overlay_get_file_map(fs_volume *volume, fs_vnode *vnode, off_t offset,
        size_t size, struct file_io_vec *vecs, size_t *count)
{
        OVERLAY_CALL(get_file_map, offset, size, vecs, count)
}


static status_t
overlay_ioctl(fs_volume *volume, fs_vnode *vnode, void *cookie, uint32 op,
        void *buffer, size_t length)
{
        OVERLAY_CALL(ioctl, cookie, op, buffer, length)
}


static status_t
overlay_set_flags(fs_volume *volume, fs_vnode *vnode, void *cookie,
        int flags)
{
        return ((OverlayInode *)vnode->private_node)->SetFlags(cookie, flags);
}


static status_t
overlay_select(fs_volume *volume, fs_vnode *vnode, void *cookie, uint8 event,
        selectsync *sync)
{
        OVERLAY_CALL(select, cookie, event, sync)
}


static status_t
overlay_deselect(fs_volume *volume, fs_vnode *vnode, void *cookie, uint8 event,
        selectsync *sync)
{
        OVERLAY_CALL(deselect, cookie, event, sync)
}


static status_t
overlay_fsync(fs_volume *volume, fs_vnode *vnode, bool dataOnly)
{
        return B_OK;
}


static status_t
overlay_read_symlink(fs_volume *volume, fs_vnode *vnode, char *buffer,
        size_t *bufferSize)
{
        TRACE("read_symlink\n");
        return ((OverlayInode *)vnode->private_node)->ReadSymlink(buffer,
                bufferSize);
}


static status_t
overlay_create_symlink(fs_volume *volume, fs_vnode *vnode, const char *name,
        const char *path, int mode)
{
        TRACE("create_symlink: \"%s\" -> \"%s\"\n", name, path);
        return ((OverlayInode *)vnode->private_node)->CreateSymlink(name, path,
                mode);
}


static status_t
overlay_link(fs_volume *volume, fs_vnode *vnode, const char *name,
        fs_vnode *target)
{
        return B_UNSUPPORTED;
}


static status_t
overlay_unlink(fs_volume *volume, fs_vnode *vnode, const char *name)
{
        TRACE("unlink: \"%s\"\n", name);
        return ((OverlayInode *)vnode->private_node)->RemoveEntry(name, NULL);
}


static status_t
overlay_rename(fs_volume *volume, fs_vnode *vnode,
        const char *fromName, fs_vnode *toVnode, const char *toName)
{
        TRACE("rename: \"%s\" -> \"%s\"\n", fromName, toName);
        OverlayInode *fromNode = (OverlayInode *)vnode->private_node;
        OverlayInode *toNode = (OverlayInode *)toVnode->private_node;
        overlay_dirent *entry = NULL;

        status_t result = fromNode->RemoveEntry(fromName, &entry);
        if (result != B_OK)
                return result;

        char *oldName = entry->name;
        entry->name = strdup(toName);
        if (entry->name == NULL) {
                entry->name = oldName;
                if (fromNode->AddEntry(entry) != B_OK)
                        entry->remove_and_dispose(volume, fromNode->InodeNumber());

                return B_NO_MEMORY;
        }

        result = toNode->AddEntry(entry);
        if (result != B_OK) {
                free(entry->name);
                entry->name = oldName;
                if (fromNode->AddEntry(entry) != B_OK)
                        entry->remove_and_dispose(volume, fromNode->InodeNumber());

                return result;
        }

        OverlayInode *node = NULL;
        result = get_vnode(volume, entry->inode_number, (void **)&node);
        if (result == B_OK && node != NULL) {
                node->SetName(entry->name);
                node->SetParentDir(toNode);
                put_vnode(volume, entry->inode_number);
        }

        free(oldName);
        notify_entry_moved(volume->id, fromNode->InodeNumber(), fromName,
                toNode->InodeNumber(), toName, entry->inode_number);
        return B_OK;
}


static status_t
overlay_access(fs_volume *volume, fs_vnode *vnode, int mode)
{
        // TODO: implement
        return B_OK;
}


static status_t
overlay_read_stat(fs_volume *volume, fs_vnode *vnode, struct stat *stat)
{
        TRACE("read_stat\n");
        return ((OverlayInode *)vnode->private_node)->ReadStat(stat);
}


static status_t
overlay_write_stat(fs_volume *volume, fs_vnode *vnode, const struct stat *stat,
        uint32 statMask)
{
        TRACE("write_stat\n");
        return ((OverlayInode *)vnode->private_node)->WriteStat(stat, statMask);
}


static status_t
overlay_create(fs_volume *volume, fs_vnode *vnode, const char *name,
        int openMode, int perms, void **cookie, ino_t *newVnodeID)
{
        TRACE("create: \"%s\"\n", name);
        return ((OverlayInode *)vnode->private_node)->Create(name, openMode,
                perms, cookie, newVnodeID);
}


static status_t
overlay_open(fs_volume *volume, fs_vnode *vnode, int openMode, void **cookie)
{
        TRACE("open\n");
        return ((OverlayInode *)vnode->private_node)->Open(openMode, cookie);
}


static status_t
overlay_close(fs_volume *volume, fs_vnode *vnode, void *cookie)
{
        TRACE("close\n");
        return ((OverlayInode *)vnode->private_node)->Close(cookie);
}


static status_t
overlay_free_cookie(fs_volume *volume, fs_vnode *vnode, void *cookie)
{
        TRACE("free_cookie\n");
        return ((OverlayInode *)vnode->private_node)->FreeCookie(cookie);
}


static status_t
overlay_read(fs_volume *volume, fs_vnode *vnode, void *cookie, off_t pos,
        void *buffer, size_t *length)
{
        TRACE("read\n");
        return ((OverlayInode *)vnode->private_node)->Read(cookie, pos, buffer,
                length, false, NULL);
}


static status_t
overlay_write(fs_volume *volume, fs_vnode *vnode, void *cookie, off_t pos,
        const void *buffer, size_t *length)
{
        TRACE("write\n");
        return ((OverlayInode *)vnode->private_node)->Write(cookie, pos, buffer,
                *length, NULL);
}


static status_t
overlay_create_dir(fs_volume *volume, fs_vnode *vnode, const char *name,
        int perms)
{
        TRACE("create_dir: \"%s\"\n", name);
        return ((OverlayInode *)vnode->private_node)->CreateDir(name, perms);
}


static status_t
overlay_remove_dir(fs_volume *volume, fs_vnode *vnode, const char *name)
{
        TRACE("remove_dir: \"%s\"\n", name);
        return ((OverlayInode *)vnode->private_node)->RemoveDir(name);
}


static status_t
overlay_open_dir(fs_volume *volume, fs_vnode *vnode, void **cookie)
{
        TRACE("open_dir\n");
        return ((OverlayInode *)vnode->private_node)->OpenDir(cookie);
}


static status_t
overlay_close_dir(fs_volume *volume, fs_vnode *vnode, void *cookie)
{
        TRACE("close_dir\n");
        return ((OverlayInode *)vnode->private_node)->CloseDir(cookie);
}


static status_t
overlay_free_dir_cookie(fs_volume *volume, fs_vnode *vnode, void *cookie)
{
        TRACE("free_dir_cookie\n");
        return ((OverlayInode *)vnode->private_node)->FreeDirCookie(cookie);
}


static status_t
overlay_read_dir(fs_volume *volume, fs_vnode *vnode, void *cookie,
        struct dirent *buffer, size_t bufferSize, uint32 *num)
{
        TRACE("read_dir\n");
        return ((OverlayInode *)vnode->private_node)->ReadDir(cookie, buffer,
                bufferSize, num);
}


static status_t
overlay_rewind_dir(fs_volume *volume, fs_vnode *vnode, void *cookie)
{
        TRACE("rewind_dir\n");
        return ((OverlayInode *)vnode->private_node)->RewindDir(cookie);
}


static status_t
overlay_open_attr_dir(fs_volume *volume, fs_vnode *vnode, void **cookie)
{
        TRACE("open_attr_dir\n");
        return ((OverlayInode *)vnode->private_node)->OpenDir(cookie, true);
}


static status_t
overlay_close_attr_dir(fs_volume *volume, fs_vnode *vnode, void *cookie)
{
        TRACE("close_attr_dir\n");
        return ((OverlayInode *)vnode->private_node)->CloseDir(cookie);
}


static status_t
overlay_free_attr_dir_cookie(fs_volume *volume, fs_vnode *vnode, void *cookie)
{
        TRACE("free_attr_dir_cookie\n");
        return ((OverlayInode *)vnode->private_node)->FreeDirCookie(cookie);
}


static status_t
overlay_read_attr_dir(fs_volume *volume, fs_vnode *vnode, void *cookie,
        struct dirent *buffer, size_t bufferSize, uint32 *num)
{
        TRACE("read_attr_dir\n");
        return ((OverlayInode *)vnode->private_node)->ReadDir(cookie, buffer,
                bufferSize, num, true);
}


static status_t
overlay_rewind_attr_dir(fs_volume *volume, fs_vnode *vnode, void *cookie)
{
        TRACE("rewind_attr_dir\n");
        return ((OverlayInode *)vnode->private_node)->RewindDir(cookie);
}


static status_t
overlay_create_attr(fs_volume *volume, fs_vnode *vnode, const char *name,
        uint32 type, int openMode, void **cookie)
{
        TRACE("create_attr\n");
        return ((OverlayInode *)vnode->private_node)->Create(name, openMode, 0,
                cookie, NULL, true, type);
}


static status_t
overlay_open_attr(fs_volume *volume, fs_vnode *vnode, const char *name,
        int openMode, void **cookie)
{
        TRACE("open_attr\n");
        OverlayInode *node = NULL;
        OverlayInode *parentNode = (OverlayInode *)vnode->private_node;
        status_t result = parentNode->LookupAttribute(name, &node);
        if (result != B_OK)
                return result;
        if (node == NULL)
                return B_ERROR;

        return node->Open(openMode, cookie);
}


static status_t
overlay_close_attr(fs_volume *volume, fs_vnode *vnode, void *_cookie)
{
        TRACE("close_attr\n");
        open_cookie *cookie = (open_cookie *)_cookie;
        return cookie->node->Close(cookie);
}


static status_t
overlay_free_attr_cookie(fs_volume *volume, fs_vnode *vnode, void *_cookie)
{
        TRACE("free_attr_cookie\n");
        open_cookie *cookie = (open_cookie *)_cookie;
        return cookie->node->FreeCookie(cookie);
}


static status_t
overlay_read_attr(fs_volume *volume, fs_vnode *vnode, void *_cookie, off_t pos,
        void *buffer, size_t *length)
{
        TRACE("read_attr\n");
        open_cookie *cookie = (open_cookie *)_cookie;
        return cookie->node->Read(cookie, pos, buffer, length, false, NULL);
}


static status_t
overlay_write_attr(fs_volume *volume, fs_vnode *vnode, void *_cookie, off_t pos,
        const void *buffer, size_t *length)
{
        TRACE("write_attr\n");
        open_cookie *cookie = (open_cookie *)_cookie;
        return cookie->node->Write(cookie, pos, buffer, *length, NULL);
}


static status_t
overlay_read_attr_stat(fs_volume *volume, fs_vnode *vnode, void *_cookie,
        struct stat *stat)
{
        TRACE("read_attr_stat\n");
        open_cookie *cookie = (open_cookie *)_cookie;
        return cookie->node->ReadStat(stat);
}


static status_t
overlay_write_attr_stat(fs_volume *volume, fs_vnode *vnode, void *_cookie,
        const struct stat *stat, int statMask)
{
        TRACE("write_attr_stat\n");
        open_cookie *cookie = (open_cookie *)_cookie;
        return cookie->node->WriteStat(stat, statMask);
}


static status_t
overlay_rename_attr(fs_volume *volume, fs_vnode *vnode,
        const char *fromName, fs_vnode *toVnode, const char *toName)
{
        TRACE("rename attr: \"%s\" -> \"%s\"\n", fromName, toName);
        OverlayInode *fromNode = (OverlayInode *)vnode->private_node;
        OverlayInode *toNode = (OverlayInode *)toVnode->private_node;
        overlay_dirent *entry = NULL;

        status_t result = fromNode->RemoveEntry(fromName, &entry, true);
        if (result != B_OK)
                return result;

        char *oldName = entry->name;
        entry->name = strdup(toName);
        if (entry->name == NULL) {
                entry->name = oldName;
                if (fromNode->AddEntry(entry, true) != B_OK)
                        entry->dispose_attribute(volume, fromNode->InodeNumber());

                return B_NO_MEMORY;
        }

        result = toNode->AddEntry(entry, true);
        if (result != B_OK) {
                free(entry->name);
                entry->name = oldName;
                if (fromNode->AddEntry(entry, true) != B_OK)
                        entry->dispose_attribute(volume, fromNode->InodeNumber());

                return result;
        }

        OverlayInode *node = entry->node;
        if (node == NULL)
                return B_ERROR;

        node->SetName(entry->name);
        node->SetSuperVnode(toNode->SuperVnode());
        node->SetInodeNumber(toNode->InodeNumber());

        notify_attribute_changed(volume->id, -1, fromNode->InodeNumber(), fromName,
                B_ATTR_REMOVED);
        notify_attribute_changed(volume->id, -1, toNode->InodeNumber(), toName,
                B_ATTR_CREATED);

        free(oldName);
        return B_OK;
}


static status_t
overlay_remove_attr(fs_volume *volume, fs_vnode *vnode, const char *name)
{
        TRACE("remove_attr\n");
        OverlayInode *node = (OverlayInode *)vnode->private_node;
        status_t result = node->RemoveEntry(name, NULL, true);
        if (result != B_OK)
                return result;

        notify_attribute_changed(volume->id, -1, node->InodeNumber(), name,
                B_ATTR_REMOVED);
        return result;
}


static status_t
overlay_create_special_node(fs_volume *volume, fs_vnode *vnode,
        const char *name, fs_vnode *subVnode, mode_t mode, uint32 flags,
        fs_vnode *_superVnode, ino_t *nodeID)
{
        OVERLAY_CALL(create_special_node, name, subVnode, mode, flags, _superVnode, nodeID)
}


static fs_vnode_ops sOverlayVnodeOps = {
        &overlay_lookup,
        &overlay_get_vnode_name,

        &overlay_put_vnode,
        &overlay_remove_vnode,

        &overlay_can_page,
        &overlay_read_pages,
        &overlay_write_pages,

        &overlay_io,
        &overlay_cancel_io,

        &overlay_get_file_map,

        /* common */
        &overlay_ioctl,
        &overlay_set_flags,
        &overlay_select,
        &overlay_deselect,
        &overlay_fsync,

        &overlay_read_symlink,
        &overlay_create_symlink,
        &overlay_link,
        &overlay_unlink,
        &overlay_rename,

        &overlay_access,
        &overlay_read_stat,
        &overlay_write_stat,
        NULL,   // fs_preallocate

        /* file */
        &overlay_create,
        &overlay_open,
        &overlay_close,
        &overlay_free_cookie,
        &overlay_read,
        &overlay_write,

        /* directory */
        &overlay_create_dir,
        &overlay_remove_dir,
        &overlay_open_dir,
        &overlay_close_dir,
        &overlay_free_dir_cookie,
        &overlay_read_dir,
        &overlay_rewind_dir,

        /* attribute directory operations */
        &overlay_open_attr_dir,
        &overlay_close_attr_dir,
        &overlay_free_attr_dir_cookie,
        &overlay_read_attr_dir,
        &overlay_rewind_attr_dir,

        /* attribute operations */
        &overlay_create_attr,
        &overlay_open_attr,
        &overlay_close_attr,
        &overlay_free_attr_cookie,
        &overlay_read_attr,
        &overlay_write_attr,

        &overlay_read_attr_stat,
        &overlay_write_attr_stat,
        &overlay_rename_attr,
        &overlay_remove_attr,

        /* support for node and FS layers */
        &overlay_create_special_node,
        &overlay_get_super_vnode
};


//      #pragma mark - volume ops


#define OVERLAY_VOLUME_CALL(op, params...) \
        TRACE_VOLUME("relaying volume op: " #op "\n"); \
        if (volume->super_volume->ops->op != NULL) \
                return volume->super_volume->ops->op(volume->super_volume, params);


static status_t
overlay_unmount(fs_volume *volume)
{
        TRACE_VOLUME("relaying volume op: unmount\n");
        if (volume->super_volume != NULL
                && volume->super_volume->ops != NULL
                && volume->super_volume->ops->unmount != NULL)
                volume->super_volume->ops->unmount(volume->super_volume);

        delete (OverlayVolume *)volume->private_volume;
        return B_OK;
}


static status_t
overlay_read_fs_info(fs_volume *volume, struct fs_info *info)
{
        TRACE_VOLUME("relaying volume op: read_fs_info\n");
        status_t result = B_UNSUPPORTED;
        if (volume->super_volume->ops->read_fs_info != NULL) {
                result = volume->super_volume->ops->read_fs_info(volume->super_volume,
                        info);
                if (result != B_OK)
                        return result;

                info->flags &= ~B_FS_IS_READONLY;

                // TODO: maybe calculate based on available ram
                off_t available = 1024 * 1024 * 100 / info->block_size;
                info->total_blocks += available;
                info->free_blocks += available;
                return B_OK;
        }

        return B_UNSUPPORTED;
}


static status_t
overlay_write_fs_info(fs_volume *volume, const struct fs_info *info,
        uint32 mask)
{
        OVERLAY_VOLUME_CALL(write_fs_info, info, mask)
        return B_UNSUPPORTED;
}


static status_t
overlay_sync(fs_volume *volume)
{
        TRACE_VOLUME("relaying volume op: sync\n");
        if (volume->super_volume->ops->sync != NULL)
                return volume->super_volume->ops->sync(volume->super_volume);
        return B_UNSUPPORTED;
}


static status_t
overlay_get_vnode(fs_volume *volume, ino_t id, fs_vnode *vnode, int *_type,
        uint32 *_flags, bool reenter)
{
        TRACE_VOLUME("relaying volume op: get_vnode\n");
        if (volume->super_volume->ops->get_vnode != NULL) {
                status_t status = volume->super_volume->ops->get_vnode(
                        volume->super_volume, id, vnode, _type, _flags, reenter);
                if (status != B_OK)
                        return status;

                OverlayInode *node = new(std::nothrow) OverlayInode(
                        (OverlayVolume *)volume->private_volume, vnode, id);
                if (node == NULL) {
                        vnode->ops->put_vnode(volume->super_volume, vnode, reenter);
                        return B_NO_MEMORY;
                }

                status = node->InitCheck();
                if (status != B_OK) {
                        vnode->ops->put_vnode(volume->super_volume, vnode, reenter);
                        delete node;
                        return status;
                }

                vnode->private_node = node;
                vnode->ops = &sOverlayVnodeOps;
                return B_OK;
        }

        return B_UNSUPPORTED;
}


static status_t
overlay_open_index_dir(fs_volume *volume, void **cookie)
{
        OVERLAY_VOLUME_CALL(open_index_dir, cookie)
        return B_UNSUPPORTED;
}


static status_t
overlay_close_index_dir(fs_volume *volume, void *cookie)
{
        OVERLAY_VOLUME_CALL(close_index_dir, cookie)
        return B_UNSUPPORTED;
}


static status_t
overlay_free_index_dir_cookie(fs_volume *volume, void *cookie)
{
        OVERLAY_VOLUME_CALL(free_index_dir_cookie, cookie)
        return B_UNSUPPORTED;
}


static status_t
overlay_read_index_dir(fs_volume *volume, void *cookie, struct dirent *buffer,
        size_t bufferSize, uint32 *_num)
{
        OVERLAY_VOLUME_CALL(read_index_dir, cookie, buffer, bufferSize, _num)
        return B_UNSUPPORTED;
}


static status_t
overlay_rewind_index_dir(fs_volume *volume, void *cookie)
{
        OVERLAY_VOLUME_CALL(rewind_index_dir, cookie)
        return B_UNSUPPORTED;
}


static status_t
overlay_create_index(fs_volume *volume, const char *name, uint32 type,
        uint32 flags)
{
        OVERLAY_VOLUME_CALL(create_index, name, type, flags)
        return B_UNSUPPORTED;
}


static status_t
overlay_remove_index(fs_volume *volume, const char *name)
{
        OVERLAY_VOLUME_CALL(remove_index, name)
        return B_UNSUPPORTED;
}


static status_t
overlay_read_index_stat(fs_volume *volume, const char *name, struct stat *stat)
{
        OVERLAY_VOLUME_CALL(read_index_stat, name, stat)
        return B_UNSUPPORTED;
}


static status_t
overlay_open_query(fs_volume *volume, const char *query, uint32 flags,
        port_id port, uint32 token, void **_cookie)
{
        OVERLAY_VOLUME_CALL(open_query, query, flags, port, token, _cookie)
        return B_UNSUPPORTED;
}


static status_t
overlay_close_query(fs_volume *volume, void *cookie)
{
        OVERLAY_VOLUME_CALL(close_query, cookie)
        return B_UNSUPPORTED;
}


static status_t
overlay_free_query_cookie(fs_volume *volume, void *cookie)
{
        OVERLAY_VOLUME_CALL(free_query_cookie, cookie)
        return B_UNSUPPORTED;
}


static status_t
overlay_read_query(fs_volume *volume, void *cookie, struct dirent *buffer,
        size_t bufferSize, uint32 *_num)
{
        OVERLAY_VOLUME_CALL(read_query, cookie, buffer, bufferSize, _num)
        return B_UNSUPPORTED;
}


static status_t
overlay_rewind_query(fs_volume *volume, void *cookie)
{
        OVERLAY_VOLUME_CALL(rewind_query, cookie)
        return B_UNSUPPORTED;
}


static status_t
overlay_all_layers_mounted(fs_volume *volume)
{
        return B_OK;
}


static status_t
overlay_create_sub_vnode(fs_volume *volume, ino_t id, fs_vnode *vnode)
{
        OverlayInode *node = new(std::nothrow) OverlayInode(
                (OverlayVolume *)volume->private_volume, vnode, id);
        if (node == NULL)
                return B_NO_MEMORY;

        status_t status = node->InitCheck();
        if (status != B_OK) {
                delete node;
                return status;
        }

        vnode->private_node = node;
        vnode->ops = &sOverlayVnodeOps;
        return B_OK;
}


static status_t
overlay_delete_sub_vnode(fs_volume *volume, fs_vnode *vnode)
{
        delete (OverlayInode *)vnode->private_node;
        return B_OK;
}


static fs_volume_ops sOverlayVolumeOps = {
        &overlay_unmount,

        &overlay_read_fs_info,
        &overlay_write_fs_info,
        &overlay_sync,

        &overlay_get_vnode,
        &overlay_open_index_dir,
        &overlay_close_index_dir,
        &overlay_free_index_dir_cookie,
        &overlay_read_index_dir,
        &overlay_rewind_index_dir,

        &overlay_create_index,
        &overlay_remove_index,
        &overlay_read_index_stat,

        &overlay_open_query,
        &overlay_close_query,
        &overlay_free_query_cookie,
        &overlay_read_query,
        &overlay_rewind_query,

        &overlay_all_layers_mounted,
        &overlay_create_sub_vnode,
        &overlay_delete_sub_vnode
};


//      #pragma mark - filesystem module


static status_t
overlay_mount(fs_volume *volume, const char *device, uint32 flags,
        const char *args, ino_t *rootID)
{
        if (volume->super_volume == NULL)
                return B_UNSUPPORTED;

        TRACE_VOLUME("mounting write overlay\n");
        volume->private_volume = new(std::nothrow) OverlayVolume(volume);
        if (volume->private_volume == NULL)
                return B_NO_MEMORY;

        volume->ops = &sOverlayVolumeOps;
        return B_OK;
}


static status_t
overlay_std_ops(int32 op, ...)
{
        switch (op) {
                case B_MODULE_INIT:
                case B_MODULE_UNINIT:
                        return B_OK;
                default:
                        return B_ERROR;
        }
}


static file_system_module_info sOverlayFileSystem = {
        {
                "file_systems/write_overlay" B_CURRENT_FS_API_VERSION,
                0,
                overlay_std_ops,
        },

        "write_overlay",                                // short_name
        "Write Overlay File System",    // pretty_name
        0,                                                              // DDM flags

        // scanning
        NULL, // identify_partition
        NULL, // scan_partition
        NULL, // free_identify_partition_cookie
        NULL, // free_partition_content_cookie

        // general operations
        &overlay_mount,

        // capability querying
        NULL, // get_supported_operations

        NULL, // validate_resize
        NULL, // validate_move
        NULL, // validate_set_content_name
        NULL, // validate_set_content_parameters
        NULL, // validate_initialize

        // shadow partition modification
        NULL, // shadow_changed

        // writing
        NULL, // defragment
        NULL, // repair
        NULL, // resize
        NULL, // move
        NULL, // set_content_name
        NULL, // set_content_parameters
        NULL // initialize
};


status_t
publish_overlay_vnode(fs_volume *volume, ino_t inodeNumber, void *privateNode,
        int type)
{
        return publish_vnode(volume, inodeNumber, privateNode, &sOverlayVnodeOps,
                type, 0);
}

}       // namespace write_overlay

using namespace write_overlay;

module_info *modules[] = {
        (module_info *)&sOverlayFileSystem,
        NULL,
};