root/src/add-ons/kernel/file_systems/bfs/Attribute.cpp
/*
 * Copyright 2004-2020, Axel Dörfler, axeld@pinc-software.de.
 * This file may be used under the terms of the MIT License.
 */


//!     Connection between pure inode and kernel_interface attributes.


#include "Attribute.h"


// TODO: clean this up, find a better separation between Inode and this class
// TODO: even after Create(), the attribute cannot be stat() for until the
// first write


extern void fill_stat_buffer(Inode* inode, struct stat& stat);


Attribute::Attribute(Inode* inode)
        :
        fNodeGetter(inode->GetVolume()),
        fInode(inode),
        fSmall(NULL),
        fAttribute(NULL),
        fName(NULL)
{
}


Attribute::Attribute(Inode* inode, attr_cookie* cookie)
        :
        fNodeGetter(inode->GetVolume()),
        fInode(inode),
        fSmall(NULL),
        fAttribute(NULL),
        fName(NULL)
{
        Get(cookie->name);
}


Attribute::~Attribute()
{
        Put();
}


status_t
Attribute::InitCheck()
{
        return (fSmall != NULL || fAttribute != NULL) ? B_OK : B_NO_INIT;
}


status_t
Attribute::CheckAccess(const char* name, int openMode)
{
        // Opening the name attribute using this function is not allowed,
        // also using the reserved indices name, last_modified, and size
        // shouldn't be allowed.
        // TODO: we might think about allowing to update those values, but
        //      really change their corresponding values in the bfs_inode structure
        if (name[0] == FILE_NAME_NAME && name[1] == '\0'
// TODO: reenable this check -- some WonderBrush locale files used them
/*              || !strcmp(name, "name")
                || !strcmp(name, "last_modified")
                || !strcmp(name, "size")*/)
                RETURN_ERROR(B_NOT_ALLOWED);

        return fInode->CheckPermissions(open_mode_to_access(openMode));
}


status_t
Attribute::Get(const char* name)
{
        Put();

        fName = name;

        // try to find it in the small data region
        if (recursive_lock_lock(&fInode->SmallDataLock()) == B_OK) {
                status_t status = fNodeGetter.SetTo(fInode);
                if (status != B_OK)
                        return status;

                fSmall = fInode->FindSmallData(fNodeGetter.Node(), (const char*)name);
                if (fSmall != NULL)
                        return B_OK;

                recursive_lock_unlock(&fInode->SmallDataLock());
                fNodeGetter.Unset();
        }

        // then, search in the attribute directory
        return fInode->GetAttribute(name, &fAttribute);
}


void
Attribute::Put()
{
        if (fSmall != NULL) {
                recursive_lock_unlock(&fInode->SmallDataLock());
                fNodeGetter.Unset();
                fSmall = NULL;
        }

        if (fAttribute != NULL) {
                fInode->ReleaseAttribute(fAttribute);
                fAttribute = NULL;
        }
}


status_t
Attribute::Create(const char* name, type_code type, int openMode,
        attr_cookie** _cookie)
{
        status_t status = CheckAccess(name, openMode);
        if (status != B_OK)
                return status;

        bool exists = Get(name) == B_OK;
        if (exists && (openMode & O_EXCL) != 0)
                return B_FILE_EXISTS;

        attr_cookie* cookie = new(std::nothrow) attr_cookie;
        if (cookie == NULL)
                RETURN_ERROR(B_NO_MEMORY);

        fName = name;

        // initialize the cookie
        strlcpy(cookie->name, fName, B_ATTR_NAME_LENGTH);
        cookie->type = type;
        cookie->open_mode = openMode;
        cookie->create = true;

        if (exists && (openMode & O_TRUNC) != 0)
                _Truncate();

        *_cookie = cookie;
        return B_OK;
}


status_t
Attribute::Open(const char* name, int openMode, attr_cookie** _cookie)
{
        status_t status = CheckAccess(name, openMode);
        if (status < B_OK)
                return status;

        status = Get(name);
        if (status < B_OK)
                return status;

        attr_cookie* cookie = new(std::nothrow) attr_cookie;
        if (cookie == NULL)
                RETURN_ERROR(B_NO_MEMORY);

        // initialize the cookie
        strlcpy(cookie->name, fName, B_ATTR_NAME_LENGTH);
        cookie->open_mode = openMode;
        cookie->create = false;

        // Should we truncate the attribute?
        if ((openMode & O_TRUNC) != 0)
                _Truncate();

        *_cookie = cookie;
        return B_OK;
}


status_t
Attribute::Stat(struct stat& stat)
{
        if (fSmall == NULL && fAttribute == NULL)
                return B_NO_INIT;

        if (fSmall != NULL) {
                fill_stat_buffer(fInode, stat);

                // overwrite some data to suit our needs
                stat.st_type = fSmall->Type();
                stat.st_size = fSmall->DataSize();
                stat.st_mtim = stat.st_ctim;
                        // attribute changes cause status_change updates
        }

        if (fAttribute != NULL)
                fill_stat_buffer(fAttribute, stat);

        return B_OK;
}


status_t
Attribute::Read(attr_cookie* cookie, off_t pos, uint8* buffer, size_t* _length)
{
        if (fSmall == NULL && fAttribute == NULL)
                return B_NO_INIT;

        // TODO: move small_data logic from Inode::ReadAttribute() over to here!
        return fInode->ReadAttribute(cookie->name, 0, pos, buffer, _length);
}


status_t
Attribute::Write(Transaction& transaction, attr_cookie* cookie, off_t pos,
        const uint8* buffer, size_t* _length, bool* _created)
{
        if (!cookie->create && fSmall == NULL && fAttribute == NULL)
                return B_NO_INIT;

        return fInode->WriteAttribute(transaction, cookie->name, cookie->type,
                pos, buffer, _length, _created);
}


status_t
Attribute::_Truncate()
{
        if (fSmall != NULL) {
                // TODO: as long as Inode::_AddSmallData() works like it does,
                // we've got nothing to do here
                return B_OK;
        }

        if (fAttribute != NULL) {
                Transaction transaction(fAttribute->GetVolume(),
                        fAttribute->BlockNumber());
                fAttribute->WriteLockInTransaction(transaction);

                status_t status = fAttribute->SetFileSize(transaction, 0);
                if (status >= B_OK)
                        status = fAttribute->WriteBack(transaction);

                if (status < B_OK)
                        return status;

                transaction.Done();
        }

        return B_OK;
}