* Copyright 2004-2008, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2008-2017, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*/
#include <fs/KPath.h>
#include <stdlib.h>
#include <string.h>
#include <team.h>
#include <vfs.h>
#include <slab/Slab.h>
#define TRACE(x) ;
#ifdef _KERNEL_MODE
extern object_cache* sPathNameCache;
#endif
KPath::KPath(size_t bufferSize)
:
fBuffer(NULL),
fBufferSize(0),
fPathLength(0),
fLocked(false),
fLazy(false),
fFailed(false),
fIsNull(false)
{
SetTo(NULL, DEFAULT, bufferSize);
}
KPath::KPath(const char* path, int32 flags, size_t bufferSize)
:
fBuffer(NULL),
fBufferSize(0),
fPathLength(0),
fLocked(false),
fLazy(false),
fFailed(false),
fIsNull(false)
{
SetTo(path, flags, bufferSize);
}
KPath::KPath(const KPath& other)
:
fBuffer(NULL),
fBufferSize(0),
fPathLength(0),
fLocked(false),
fLazy(false),
fFailed(false),
fIsNull(false)
{
*this = other;
}
KPath::~KPath()
{
_FreeBuffer();
}
status_t
KPath::SetTo(const char* path, int32 flags, size_t bufferSize)
{
if (bufferSize == 0)
bufferSize = B_PATH_NAME_LENGTH;
if (fBuffer != NULL && fBufferSize != bufferSize) {
_FreeBuffer();
fBufferSize = 0;
}
fPathLength = 0;
fLocked = false;
fBufferSize = bufferSize;
fLazy = (flags & LAZY_ALLOC) != 0;
fIsNull = path == NULL;
if (path != NULL || !fLazy) {
status_t status = _AllocateBuffer();
if (status != B_OK)
return status;
}
return SetPath(path, flags);
}
void
KPath::Adopt(KPath& other)
{
_FreeBuffer();
fBuffer = other.fBuffer;
fBufferSize = other.fBufferSize;
fPathLength = other.fPathLength;
fLazy = other.fLazy;
fFailed = other.fFailed;
fIsNull = other.fIsNull;
other.fBuffer = NULL;
if (!other.fLazy)
other.fBufferSize = 0;
other.fPathLength = 0;
other.fFailed = false;
other.fIsNull = other.fLazy;
}
status_t
KPath::InitCheck() const
{
if (fBuffer != NULL || (fLazy && !fFailed && fBufferSize != 0))
return B_OK;
return fFailed ? B_NO_MEMORY : B_NO_INIT;
}
\param flags Understands the following two options:
- \c NORMALIZE
- \c TRAVERSE_LEAF_LINK
*/
status_t
KPath::SetPath(const char* path, int32 flags)
{
if (path == NULL && fLazy && fBuffer == NULL) {
fIsNull = true;
return B_OK;
}
if (fBuffer == NULL) {
if (fLazy) {
status_t status = _AllocateBuffer();
if (status != B_OK)
return B_NO_MEMORY;
} else
return B_NO_INIT;
}
fIsNull = false;
if (path != NULL) {
if ((flags & NORMALIZE) != 0) {
status_t status = _Normalize(path,
(flags & TRAVERSE_LEAF_LINK) != 0);
if (status != B_OK)
return status;
} else {
size_t length = strlen(path);
if (length >= fBufferSize)
return B_BUFFER_OVERFLOW;
memcpy(fBuffer, path, length + 1);
fPathLength = length;
_ChopTrailingSlashes();
}
} else {
fBuffer[0] = '\0';
fPathLength = 0;
if (fLazy)
fIsNull = true;
}
return B_OK;
}
const char*
KPath::Path() const
{
return fIsNull ? NULL : fBuffer;
}
\param force In lazy mode, this will allocate a buffer when set.
Otherwise, \c NULL will be returned if set to NULL.
*/
char*
KPath::LockBuffer(bool force)
{
if (fBuffer == NULL && fLazy) {
if (fIsNull && !force)
return NULL;
_AllocateBuffer();
}
if (fBuffer == NULL || fLocked)
return NULL;
fLocked = true;
fIsNull = false;
return fBuffer;
}
void
KPath::UnlockBuffer()
{
if (!fLocked) {
#ifdef _KERNEL_MODE
panic("KPath::UnlockBuffer(): Buffer not locked!");
#endif
return;
}
fLocked = false;
if (fBuffer == NULL)
return;
fPathLength = strnlen(fBuffer, fBufferSize);
if (fPathLength == fBufferSize) {
TRACE(("KPath::UnlockBuffer(): WARNING: Unterminated buffer!\n"));
fPathLength--;
fBuffer[fPathLength] = '\0';
}
_ChopTrailingSlashes();
}
char*
KPath::DetachBuffer()
{
char* buffer = fBuffer;
#ifdef _KERNEL_MODE
if (fBufferSize == B_PATH_NAME_LENGTH) {
buffer = (char*)malloc(fBufferSize);
memcpy(buffer, fBuffer, fBufferSize);
_FreeBuffer();
}
#endif
if (fBuffer != NULL) {
fBuffer = NULL;
fPathLength = 0;
fLocked = false;
}
return buffer;
}
const char*
KPath::Leaf() const
{
if (fBuffer == NULL)
return NULL;
for (int32 i = fPathLength - 1; i >= 0; i--) {
if (fBuffer[i] == '/')
return fBuffer + i + 1;
}
return fBuffer;
}
status_t
KPath::ReplaceLeaf(const char* newLeaf)
{
const char* leaf = Leaf();
if (leaf == NULL)
return B_NO_INIT;
int32 leafIndex = leaf - fBuffer;
if (leafIndex != 0 || fBuffer[leafIndex - 1]) {
fBuffer[leafIndex] = '\0';
fPathLength = leafIndex;
_ChopTrailingSlashes();
}
if (newLeaf != NULL)
return Append(newLeaf);
return B_OK;
}
bool
KPath::RemoveLeaf()
{
const char* leaf = Leaf();
if (leaf == NULL || leaf == fBuffer || leaf[0] == '\0')
return false;
int32 leafIndex = leaf - fBuffer;
fBuffer[leafIndex] = '\0';
fPathLength = leafIndex;
_ChopTrailingSlashes();
return true;
}
status_t
KPath::Append(const char* component, bool isComponent)
{
if (fBuffer == NULL)
return B_NO_INIT;
if (component == NULL)
return B_BAD_VALUE;
if (fPathLength == 0)
return SetPath(component);
size_t componentLength = strlen(component);
if (componentLength < 1)
return B_OK;
bool insertSlash = isComponent && fBuffer[fPathLength - 1] != '/'
&& component[0] != '/';
size_t resultPathLength = fPathLength + componentLength
+ (insertSlash ? 1 : 0);
if (resultPathLength >= fBufferSize)
return B_BUFFER_OVERFLOW;
if (insertSlash)
fBuffer[fPathLength++] = '/';
memcpy(fBuffer + fPathLength, component, componentLength + 1);
fPathLength = resultPathLength;
return B_OK;
}
status_t
KPath::Normalize(bool traverseLeafLink)
{
if (fBuffer == NULL)
return B_NO_INIT;
if (fPathLength == 0)
return B_BAD_VALUE;
return _Normalize(fBuffer, traverseLeafLink);
}
KPath&
KPath::operator=(const KPath& other)
{
if (other.fBuffer == fBuffer)
return *this;
SetTo(other.fBuffer, fLazy ? KPath::LAZY_ALLOC : KPath::DEFAULT,
other.fBufferSize);
return *this;
}
KPath&
KPath::operator=(const char* path)
{
SetPath(path);
return *this;
}
bool
KPath::operator==(const KPath& other) const
{
if (fBuffer == NULL)
return !other.fBuffer;
return other.fBuffer != NULL
&& fPathLength == other.fPathLength
&& strcmp(fBuffer, other.fBuffer) == 0;
}
bool
KPath::operator==(const char* path) const
{
if (fBuffer == NULL)
return path == NULL;
return path != NULL && strcmp(fBuffer, path) == 0;
}
bool
KPath::operator!=(const KPath& other) const
{
return !(*this == other);
}
bool
KPath::operator!=(const char* path) const
{
return !(*this == path);
}
status_t
KPath::_AllocateBuffer()
{
if (fBuffer == NULL && fBufferSize != 0) {
#ifdef _KERNEL_MODE
if (fBufferSize == B_PATH_NAME_LENGTH)
fBuffer = (char*)object_cache_alloc(sPathNameCache, 0);
else
#endif
fBuffer = (char*)malloc(fBufferSize);
}
if (fBuffer == NULL) {
fFailed = true;
return B_NO_MEMORY;
}
fBuffer[0] = '\0';
fFailed = false;
return B_OK;
}
void
KPath::_FreeBuffer()
{
#ifdef _KERNEL_MODE
if (fBufferSize == B_PATH_NAME_LENGTH)
object_cache_free(sPathNameCache, fBuffer, 0);
else
#endif
free(fBuffer);
fBuffer = NULL;
}
status_t
KPath::_Normalize(const char* path, bool traverseLeafLink)
{
status_t error = vfs_normalize_path(path, fBuffer, fBufferSize,
traverseLeafLink,
team_get_kernel_team_id() == team_get_current_team_id());
if (error != B_OK) {
fBuffer[0] = '\0';
fPathLength = 0;
return error;
}
fPathLength = strlen(fBuffer);
return B_OK;
}
void
KPath::_ChopTrailingSlashes()
{
if (fBuffer != NULL) {
while (fPathLength > 1 && fBuffer[fPathLength - 1] == '/')
fBuffer[--fPathLength] = '\0';
}
}