⛏️ index : haiku.git

/*
 * 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)
		| (openMode & O_TRUNC ? W_OK : 0));
}


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;
}