root/src/build/libroot/fs.cpp
/*
 * Copyright 2005-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#include <BeOSBuildCompatibility.h>

#include "fs_impl.h"

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <utime.h>
#include <sys/stat.h>
#include <sys/time.h>

#include <map>
#include <string>

#include <fs_attr.h>
#include <NodeMonitor.h>        // for B_STAT_* flags
#include <syscalls.h>

#include "fs_descriptors.h"
#include "NodeRef.h"
#include "remapped_functions.h"

#if defined(HAIKU_HOST_PLATFORM_FREEBSD)
#       include "fs_freebsd.h"
#endif


using namespace std;
using namespace BPrivate;


#if defined(HAIKU_HOST_PLATFORM_FREEBSD)
#       define haiku_host_platform_read         haiku_freebsd_read
#       define haiku_host_platform_write        haiku_freebsd_write
#       define haiku_host_platform_readv        haiku_freebsd_readv
#       define haiku_host_platform_writev       haiku_freebsd_writev
#       define HAIKU_HOST_STAT_ATIM(x)          ((x).st_atimespec)
#       define HAIKU_HOST_STAT_MTIM(x)          ((x).st_mtimespec)
#elif defined(HAIKU_HOST_PLATFORM_DARWIN)
#       define haiku_host_platform_read         read
#       define haiku_host_platform_write        write
#       define haiku_host_platform_readv        readv
#       define haiku_host_platform_writev       writev
#       define HAIKU_HOST_STAT_ATIM(x)          ((x).st_atimespec)
#       define HAIKU_HOST_STAT_MTIM(x)          ((x).st_mtimespec)
#else
#       define haiku_host_platform_read         read
#       define haiku_host_platform_write        write
#       define haiku_host_platform_readv        readv
#       define haiku_host_platform_writev       writev
#       define HAIKU_HOST_STAT_ATIM(x)          ((x).st_atim)
#       define HAIKU_HOST_STAT_MTIM(x)          ((x).st_mtim)
#endif

#define RETURN_AND_SET_ERRNO(err)                       \
        do {                                                                    \
                __typeof(err) __result = (err);         \
                if (__result < 0) {                                     \
                        errno = __result;                               \
                        return -1;                                              \
                }                                                                       \
                return __result;                                        \
        } while (0)


#if defined(_HAIKU_BUILD_NO_FUTIMENS) || defined(_HAIKU_BUILD_NO_FUTIMENS)

template<typename File>
static int
utimes_helper(File& file, const struct timespec times[2])
{
        if (times == NULL)
                return file.SetTimes(NULL);

        timeval timeBuffer[2];
        timeBuffer[0].tv_sec = times[0].tv_sec;
        timeBuffer[0].tv_usec = times[0].tv_nsec / 1000;
        timeBuffer[1].tv_sec = times[1].tv_sec;
        timeBuffer[1].tv_usec = times[1].tv_nsec / 1000;

        if (times[0].tv_nsec == UTIME_OMIT || times[1].tv_nsec == UTIME_OMIT) {
                struct stat st;
                if (file.GetStat(st) != 0)
                        return -1;

                if (times[0].tv_nsec == UTIME_OMIT && times[1].tv_nsec == UTIME_OMIT)
                        return 0;

                if (times[0].tv_nsec == UTIME_OMIT) {
                        timeBuffer[0].tv_sec = st.st_atimespec.tv_sec;
                        timeBuffer[0].tv_usec = st.st_atimespec.tv_nsec / 1000;
                }

                if (times[1].tv_nsec == UTIME_OMIT) {
                        timeBuffer[1].tv_sec = st.st_mtimespec.tv_sec;
                        timeBuffer[1].tv_usec = st.st_mtimespec.tv_nsec / 1000;
                }
        }

        if (times[0].tv_nsec == UTIME_NOW || times[1].tv_nsec == UTIME_NOW) {
                timeval now;
                gettimeofday(&now, NULL);

                if (times[0].tv_nsec == UTIME_NOW)
                        timeBuffer[0] = now;

                if (times[1].tv_nsec == UTIME_NOW)
                        timeBuffer[1] = now;
        }

        return file.SetTimes(timeBuffer);
}

#endif  // _HAIKU_BUILD_NO_FUTIMENS || _HAIKU_BUILD_NO_FUTIMENS


#ifdef _HAIKU_BUILD_NO_FUTIMENS

struct FDFile {
        FDFile(int fd)
                :
                fFD(fd)
        {
        }

        int GetStat(struct stat& _st)
        {
                return fstat(fFD, &_st);
        }

        int SetTimes(const timeval times[2])
        {
                return futimes(fFD, times);
        }

private:
        int fFD;
};


int
futimens(int fd, const struct timespec times[2])
{
        FDFile file(fd);
        return utimes_helper(file, times);
}

#endif  // _HAIKU_BUILD_NO_FUTIMENS

#ifdef _HAIKU_BUILD_NO_UTIMENSAT

struct FDPathFile {
        FDPathFile(int fd, const char* path, int flag)
                :
                fFD(fd),
                fPath(path),
                fFlag(flag)
        {
        }

        int GetStat(struct stat& _st)
        {
                return fstatat(fFD, fPath, &_st, fFlag);
        }

        int SetTimes(const timeval times[2])
        {
                // TODO: fFlag (AT_SYMLINK_NOFOLLOW) is not supported here!
                return futimesat(fFD, fPath, times);
        }

private:
        int                     fFD;
        const char*     fPath;
        int                     fFlag;
};


int
utimensat(int fd, const char* path, const struct timespec times[2], int flag)
{
        FDPathFile file(fd, path, flag);
        return utimes_helper(file, times);
}

#endif  // _HAIKU_BUILD_NO_UTIMENSAT


static status_t get_path(dev_t device, ino_t node, const char *name,
        string &path);


// find_dir_entry
static status_t
find_dir_entry(DIR *dir, const char *path, NodeRef ref, string &name,
        bool skipDot)
{
        // find the entry
        bool found = false;
        while (dirent *entry = readdir(dir)) {
                if ((strcmp(entry->d_name, ".") == 0 && skipDot)
                        || strcmp(entry->d_name, "..") == 0) {
                        // skip "." and ".."
                } else /*if (entry->d_ino == ref.node)*/ {
                                // Note: Linux doesn't seem to translate dirent::d_ino of
                                // mount points. Thus we always have to lstat().
                        // We also need to compare the device, which is generally not
                        // included in the dirent structure. Hence we lstat().
                        string entryPath(path);
                        entryPath += '/';
                        entryPath += entry->d_name;
                        struct stat st;
                        if (lstat(entryPath.c_str(), &st) == 0) {
                                if (NodeRef(st) == ref) {
                                        name = entry->d_name;
                                        found = true;
                                        break;
                                }
                        }
                }
        }

        if (!found)
                return B_ENTRY_NOT_FOUND;

        return B_OK;
}


// find_dir_entry
static status_t
find_dir_entry(const char *path, NodeRef ref, string &name, bool skipDot)
{
        // open dir
        DIR *dir = opendir(path);
        if (!dir)
                return errno;

        status_t error = find_dir_entry(dir, path, ref, name, skipDot);

        // close dir
        closedir(dir);

        return error;
}


// normalize_dir_path: Make path absolute and remove redundant entries.
static status_t
normalize_dir_path(const char *path, string &normalizedPath)
{
        const size_t pathLen = strlen(path);

        // Add CWD to relative paths.
        if (pathLen == 0 || path[0] != '/') {
                char pwd[PATH_MAX];
                if (getcwd(pwd, sizeof(pwd)) == NULL)
                        return B_ERROR;

                normalizedPath = pwd;
        }

        const char *end = &path[pathLen];
        const char *next;
        for (const char *ptr = path; ptr < end; ptr = next + 1) {
                next = (char *)memchr(ptr, '/', end - ptr);
                if (next == NULL)
                        next = end;

                size_t len = next - ptr;
                if (len == 2 && ptr[0] == '.' && ptr[1] == '.') {
                        string::size_type pos = normalizedPath.rfind('/');
                        if (pos != string::npos)
                                normalizedPath.resize(pos);
                        continue;
                } else if (len == 0 || (len == 1 && ptr[0] == '.')) {
                        continue;
                }

                if (normalizedPath.length() != 1)
                        normalizedPath += '/';

                normalizedPath.append(ptr, len);
        }

        if (normalizedPath.length() == 0)
                normalizedPath += '/';

        return B_OK;
}


// normalize_entry_path
static status_t
normalize_entry_path(const char *path, string &normalizedPath)
{
        const char *dirPath = NULL;
        const char *leafName = NULL;

        string dirPathString;
        if (const char *lastSlash = strrchr(path, '/')) {
                // found a slash: decompose into dir path and leaf name
                leafName = lastSlash + 1;
                if (leafName[0] == '\0') {
                        // slash is at the end: the whole path is a dir name
                        leafName = NULL;
                } else {
                        dirPathString = string(path, leafName - path);
                        dirPath = dirPathString.c_str();
                }
        } else {
                // path contains no slash, so it is a path relative to the current dir
                dirPath = ".";
                leafName = path;
        }

        // catch special case: no leaf, or leaf is a directory
        if (!leafName || strcmp(leafName, ".") == 0 || strcmp(leafName, "..") == 0)
                return normalize_dir_path(path, normalizedPath);

        // normalize the dir path
        status_t error = normalize_dir_path(dirPath, normalizedPath);
        if (error != B_OK)
                return error;

        // append the leaf name
        if (normalizedPath.length() > 1) // don't append "/", if parent is root
                normalizedPath += '/';
        normalizedPath += leafName;

        return B_OK;
}


// #pragma mark -

typedef map<NodeRef, string> DirPathMap;
static DirPathMap sDirPathMap;

// get_path
static status_t
get_path(const NodeRef *ref, const char *name, string &path)
{
        if (!ref && !name)
                return B_BAD_VALUE;

        // no ref or absolute path
        if (!ref || (name && name[0] == '/')) {
                path = name;
                return B_OK;
        }

        // get the dir path
        if (ref) {
                DirPathMap::iterator it = sDirPathMap.find(*ref);
                if (it == sDirPathMap.end())
                        return B_ENTRY_NOT_FOUND;

                path = it->second;

                // stat the path to check, if it is still valid
                struct stat st;
                if (stat(path.c_str(), &st) < 0) {
                        sDirPathMap.erase(it);
                        return errno;
                }

                // compare the NodeRef
                if (NodeRef(st) != *ref) {
                        sDirPathMap.erase(it);
                        return B_ENTRY_NOT_FOUND;
                }

                // still a directory?
                if (!S_ISDIR(st.st_mode)) {
                        sDirPathMap.erase(it);
                        return B_NOT_A_DIRECTORY;
                }
        }

        // if there's a name, append it
        if (name) {
                path += '/';
                path += name;
        }

        return B_OK;
}

// get_path
status_t
BPrivate::get_path(int fd, const char *name, string &path)
{
        // get the node ref for the fd, if any, and the path part is not absolute
        if (fd >= 0 && !(name && name[0] == '/')) {
                // get descriptor
                Descriptor *descriptor = get_descriptor(fd);
                if (!descriptor)
                        return B_FILE_ERROR;

                // Handle symlink descriptors here explicitly, so this function can be
                // used more flexibly.
                if (SymlinkDescriptor* symlinkDescriptor
                                = dynamic_cast<SymlinkDescriptor*>(descriptor)) {
                        path = symlinkDescriptor->path;
                        if (name == NULL)
                                return B_OK;
                        path += '/';
                        path += name;
                        return B_OK;
                }

                // get node ref for the descriptor
                NodeRef ref;
                status_t error = descriptor->GetNodeRef(ref);
                if (error != B_OK)
                        return error;

                return ::get_path(&ref, name, path);

        } else  // no descriptor or absolute path
                return ::get_path((NodeRef*)NULL, name, path);
}

// get_path
static status_t
get_path(dev_t device, ino_t directory, const char *name, string &path)
{
        NodeRef ref;
        ref.device = device;
        ref.node = directory;

        return get_path(&ref, name, path);
}

// add_dir_path
static void
add_dir_path(const char *path, const NodeRef &ref)
{
        // add the normalized path
        string normalizedPath;
        if (normalize_dir_path(path, normalizedPath) == B_OK)
                sDirPathMap[ref] = normalizedPath;
}


// #pragma mark -

// _kern_entry_ref_to_path
status_t
_kern_entry_ref_to_path(dev_t device, ino_t node, const char *leaf,
        char *userPath, size_t pathLength)
{
        // get the path
        string path;
        status_t error = get_path(device, node, leaf, path);
        if (error != B_OK)
                return error;

        // copy it back to the user buffer
        if (strlcpy(userPath, path.c_str(), pathLength) >= pathLength)
                return B_BUFFER_OVERFLOW;

        return B_OK;
}


// #pragma mark -

// _kern_create_dir
status_t
_kern_create_dir(int fd, const char *path, int perms)
{
        // get a usable path
        string realPath;
        status_t error = get_path(fd, path, realPath);
        if (error != B_OK)
                return error;

        // mkdir
        if (mkdir(realPath.c_str(), perms) < 0)
                return errno;

        return B_OK;
}

// _kern_create_dir_entry_ref
status_t
_kern_create_dir_entry_ref(dev_t device, ino_t node, const char *name,
        int perms)
{
        // get a usable path
        string realPath;
        status_t error = get_path(device, node, name, realPath);
        if (error != B_OK)
                return error;

        // mkdir
        if (mkdir(realPath.c_str(), perms) < 0)
                return errno;

        return B_OK;
}

// open_dir
static int
open_dir(const char *path)
{
        // open the dir
        DIR *dir = opendir(path);
        if (!dir)
                return errno;

        // stat the entry
        struct stat st;
        if (stat(path, &st) < 0) {
                closedir(dir);
                return errno;
        }

        if (!S_ISDIR(st.st_mode)) {
                closedir(dir);
                return B_NOT_A_DIRECTORY;
        }

        // cache dir path
        NodeRef ref(st);
        add_dir_path(path, ref);

        // create descriptor
        DirectoryDescriptor *descriptor = new DirectoryDescriptor(dir, ref);
        return add_descriptor(descriptor);
}

// _kern_open_dir
int
_kern_open_dir(int fd, const char *path)
{
        // get a usable path
        string realPath;
        status_t error = get_path(fd, path, realPath);
        if (error != B_OK)
                return error;

        return open_dir(realPath.c_str());
}

// _kern_open_dir_entry_ref
int
_kern_open_dir_entry_ref(dev_t device, ino_t node, const char *name)
{
        // get a usable path
        string realPath;
        status_t error = get_path(device, node, name, realPath);
        if (error != B_OK)
                return error;

        return open_dir(realPath.c_str());
}

// _kern_open_parent_dir
int
_kern_open_parent_dir(int fd, char *name, size_t nameLength)
{
        // get a usable path
        string realPath;
        status_t error = get_path(fd, NULL, realPath);
        if (error != B_OK)
                return error;

        // stat the entry
        struct stat st;
        if (stat(realPath.c_str(), &st) < 0)
                return errno;

        if (!S_ISDIR(st.st_mode))
                return B_NOT_A_DIRECTORY;

        // get the entry name
        realPath += "/..";
        string entryName;
        error = find_dir_entry(realPath.c_str(), NodeRef(st),
                entryName, false);
        if (error != B_OK)
                return error;

        if (strlcpy(name, entryName.c_str(), nameLength) >= nameLength)
                return B_BUFFER_OVERFLOW;

        // open the parent directory

        return open_dir(realPath.c_str());
}

// _kern_read_dir
ssize_t
_kern_read_dir(int fd, struct dirent *buffer, size_t bufferSize,
        uint32 maxCount)
{
        if (maxCount <= 0)
                return B_BAD_VALUE;

        // get the descriptor
        DirectoryDescriptor *descriptor
                = dynamic_cast<DirectoryDescriptor*>(get_descriptor(fd));
        if (!descriptor)
                return B_FILE_ERROR;

        // get the next entry
        dirent *entry;
        errno = 0;
        if (dynamic_cast<AttrDirDescriptor*>(descriptor))
                entry = fs_read_attr_dir(descriptor->dir);
        else
                entry = readdir(descriptor->dir);
        if (!entry)
                return errno;

        // copy the entry
        int entryLen = &entry->d_name[strlen(entry->d_name) + 1] - (char*)entry;
        if (entryLen > (int)bufferSize)
                return B_BUFFER_OVERFLOW;

        memcpy(buffer, entry, entryLen);

        return 1;
}

// _kern_rewind_dir
status_t
_kern_rewind_dir(int fd)
{
        // get the descriptor
        DirectoryDescriptor *descriptor
                = dynamic_cast<DirectoryDescriptor*>(get_descriptor(fd));
        if (!descriptor)
                return B_FILE_ERROR;

        // rewind
        if (dynamic_cast<AttrDirDescriptor*>(descriptor))
                fs_rewind_attr_dir(descriptor->dir);
        else
                rewinddir(descriptor->dir);

        return B_OK;
}


// #pragma mark -

// open_file
static int
open_file(const char *path, int openMode, int perms)
{
        // stat the node
        bool exists = true;
        struct stat st;
        if (lstat(path, &st) < 0) {
                exists = false;
                if (!(openMode & O_CREAT))
                        return errno;
        }

        Descriptor *descriptor;
        if (exists && S_ISLNK(st.st_mode) && (openMode & O_NOTRAVERSE) != 0) {
                // a symlink not to be followed: create a special descriptor
                // normalize path first
                string normalizedPath;
                status_t error = normalize_entry_path(path, normalizedPath);
                if (error != B_OK)
                        return error;

                descriptor = new SymlinkDescriptor(normalizedPath.c_str());
        } else {
                // open the file
                openMode &= ~O_NOTRAVERSE;
                int newFD = open(path, openMode, perms);
                if (newFD < 0)
                        return errno;

                descriptor = new FileDescriptor(newFD);
        }

        // cache path, if this is a directory
        if (exists && S_ISDIR(st.st_mode))
                add_dir_path(path, NodeRef(st));

        return add_descriptor(descriptor);
}

// _kern_open
int
_kern_open(int fd, const char *path, int openMode, int perms)
{
        // get a usable path
        string realPath;
        status_t error = get_path(fd, path, realPath);
        if (error != B_OK)
                return error;

        return open_file(realPath.c_str(), openMode, perms);
}

// _kern_open_entry_ref
int
_kern_open_entry_ref(dev_t device, ino_t node, const char *name,
        int openMode, int perms)
{
        // get a usable path
        string realPath;
        status_t error = get_path(device, node, name, realPath);
        if (error != B_OK)
                return error;

        return open_file(realPath.c_str(), openMode, perms);
}

// _kern_seek
off_t
_kern_seek(int fd, off_t pos, int seekType)
{
        // get the descriptor
        FileDescriptor *descriptor
                = dynamic_cast<FileDescriptor*>(get_descriptor(fd));
        if (!descriptor)
                return B_FILE_ERROR;

        // seek
        off_t result = lseek(descriptor->fd, pos, seekType);
        if (result < 0)
                return errno;

        return result;
}

// _kern_read
ssize_t
_kern_read(int fd, off_t pos, void *buffer, size_t bufferSize)
{
        // get the descriptor
        FileDescriptor *descriptor
                = dynamic_cast<FileDescriptor*>(get_descriptor(fd));
        if (!descriptor)
                return B_FILE_ERROR;

        // seek
        if (pos != -1) {
                off_t result = lseek(descriptor->fd, pos, SEEK_SET);
                if (result < 0)
                        return errno;
        }

        // read
        ssize_t bytesRead = haiku_host_platform_read(descriptor->fd, buffer,
                bufferSize);
        if (bytesRead < 0)
                return errno;

        return bytesRead;
}

// _kern_write
ssize_t
_kern_write(int fd, off_t pos, const void *buffer, size_t bufferSize)
{
        // get the descriptor
        FileDescriptor *descriptor
                = dynamic_cast<FileDescriptor*>(get_descriptor(fd));
        if (!descriptor)
                return B_FILE_ERROR;

        // seek
        if (pos != -1) {
                off_t result = lseek(descriptor->fd, pos, SEEK_SET);
                if (result < 0)
                        return errno;
        }

        // read
        ssize_t bytesWritten = haiku_host_platform_write(descriptor->fd, buffer,
                bufferSize);
        if (bytesWritten < 0)
                return errno;

        return bytesWritten;
}

// _kern_close
status_t
_kern_close(int fd)
{
        return delete_descriptor(fd);
}

// _kern_dup
int
_kern_dup(int fd)
{
        // get the descriptor
        Descriptor *descriptor = get_descriptor(fd);
        if (!descriptor)
                return B_FILE_ERROR;

        // clone it
        Descriptor *clone = NULL;
        status_t error = descriptor->Dup(clone);
        if (error != B_OK)
                return error;
        if (clone == NULL)
                debugger("Dup() succeeded but descriptor is NULL");

        return add_descriptor(clone);
}

// _kern_fsync
status_t
_kern_fsync(int fd, bool dataOnly)
{
        // get the descriptor
        FileDescriptor *descriptor
                = dynamic_cast<FileDescriptor*>(get_descriptor(fd));
        if (!descriptor)
                return B_FILE_ERROR;

        // sync
        if (fsync(descriptor->fd) < 0)
                return errno;

        return B_OK;
}

// _kern_read_stat
status_t
_kern_read_stat(int fd, const char *path, bool traverseLink,
        struct stat *st, size_t statSize)
{
        if (path) {
                // get a usable path
                string realPath;
                status_t error = get_path(fd, path, realPath);
                if (error != B_OK)
                        return error;

                // stat
                int result;
                if (traverseLink)
                        result = stat(realPath.c_str(), st);
                else
                        result = lstat(realPath.c_str(), st);

                if (result < 0)
                        return errno;
        } else {
                Descriptor *descriptor = get_descriptor(fd);
                if (!descriptor)
                        return B_FILE_ERROR;

                return descriptor->GetStat(traverseLink, st);
        }

        return B_OK;
}

// _kern_write_stat
status_t
_kern_write_stat(int fd, const char *path, bool traverseLink,
        const struct stat *st, size_t statSize, int statMask)
{
        // get a usable path
        int realFD = -1;
        string realPath;
        status_t error;
        bool isSymlink = false;
        if (path) {
                error = get_path(fd, path, realPath);
                if (error != B_OK)
                        return error;

                // stat it to see, if it is a symlink
                struct stat tmpStat;
                if (lstat(realPath.c_str(), &tmpStat) < 0)
                        return errno;

                isSymlink = S_ISLNK(tmpStat.st_mode);
        } else {
                Descriptor *descriptor = get_descriptor(fd);
                if (!descriptor)
                        return B_FILE_ERROR;

                if (FileDescriptor *fileFD
                                = dynamic_cast<FileDescriptor*>(descriptor)) {
                        realFD = fileFD->fd;
                } else if (dynamic_cast<DirectoryDescriptor*>(descriptor)) {
                        error = get_path(fd, NULL, realPath);
                        if (error != B_OK)
                                return error;
                } else if (SymlinkDescriptor *linkFD
                                = dynamic_cast<SymlinkDescriptor*>(descriptor)) {
                        realPath = linkFD->path;
                        isSymlink = true;
                } else
                        return B_FILE_ERROR;
        }

        // We're screwed, if the node to manipulate is a symlink. All the
        // available functions traverse symlinks.
        if (isSymlink && !traverseLink)
                return B_ERROR;

        if (realFD >= 0) {
                if (statMask & B_STAT_MODE) {
                        if (fchmod(realFD, st->st_mode) < 0)
                                return errno;
                }

                if (statMask & B_STAT_UID) {
                        if (fchown(realFD, st->st_uid, (gid_t)-1) < 0)
                                return errno;
                }

                if (statMask & B_STAT_GID) {
                        if (fchown(realFD, (uid_t)-1, st->st_gid) < 0)
                                return errno;
                }

                if (statMask & B_STAT_SIZE) {
                        if (ftruncate(realFD, st->st_size) < 0)
                                return errno;
                }

                if (statMask & (B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME)) {
                        // Grab the previous mod and access times so we only overwrite
                        // the specified time and not both
                        struct stat oldStat;
                        if (~statMask & (B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME)) {
                                if (fstat(realFD, &oldStat) < 0)
                                        return errno;
                        }

                        struct timespec times[2];
                        times[0] = (statMask & B_STAT_ACCESS_TIME)
                                ? HAIKU_HOST_STAT_ATIM(*st) : HAIKU_HOST_STAT_ATIM(oldStat);
                        times[1] = (statMask & B_STAT_MODIFICATION_TIME)
                                ? HAIKU_HOST_STAT_MTIM(*st) : HAIKU_HOST_STAT_MTIM(oldStat);
                        if (futimens(realFD, times) < 0)
                                return errno;
                }

                // not supported
                if (statMask & (B_STAT_CREATION_TIME | B_STAT_CHANGE_TIME))
                        return B_ERROR;

                return 0;
        } else {
                if (statMask & B_STAT_MODE) {
                        if (chmod(realPath.c_str(), st->st_mode) < 0)
                                return errno;
                }

                if (statMask & B_STAT_UID) {
                        if (chown(realPath.c_str(), st->st_uid, (gid_t)-1) < 0)
                                return errno;
                }

                if (statMask & B_STAT_GID) {
                        if (chown(realPath.c_str(), (uid_t)-1, st->st_gid) < 0)
                                return errno;
                }

                if (statMask & B_STAT_SIZE) {
                        if (truncate(realPath.c_str(), st->st_size) < 0)
                                return errno;
                }

                if (statMask & (B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME)) {
                        // Grab the previous mod and access times so we only overwrite
                        // the specified time and not both
                        struct stat oldStat;
                        if (~statMask & (B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME)) {
                                if (stat(realPath.c_str(), &oldStat) < 0)
                                        return errno;
                        }

                        utimbuf buffer;
                        buffer.actime = (statMask & B_STAT_ACCESS_TIME) ? st->st_atime : oldStat.st_atime;
                        buffer.modtime = (statMask & B_STAT_MODIFICATION_TIME) ? st->st_mtime : oldStat.st_mtime;
                        if (utime(realPath.c_str(), &buffer) < 0)
                                return errno;
                }

                // not supported
                if (statMask & (B_STAT_CREATION_TIME | B_STAT_CHANGE_TIME))
                        return B_ERROR;
        }

        return B_OK;
}


// #pragma mark -

// _kern_create_symlink
status_t
_kern_create_symlink(int fd, const char *path, const char *toPath, int mode)
{
        // Note: path must not be NULL, so this will always work.
        // get a usable path
        string realPath;
        status_t error = get_path(fd, path, realPath);
        if (error != B_OK)
                return error;

        // symlink
        if (symlink(toPath, realPath.c_str()) < 0)
                return errno;

        return B_OK;
}

// _kern_read_link
status_t
_kern_read_link(int fd, const char *path, char *buffer, size_t *_bufferSize)
{
        // get the path
        string realPath;
        status_t error = get_path(fd, path, realPath);
        if (error != B_OK)
                return error;

        ssize_t bytesRead = readlink(realPath.c_str(), buffer, *_bufferSize);
        if (bytesRead < 0)
                return errno;

        // On Haiku _kern_read_link will return the length of the link, *not*
        // the number of bytes written to the buffer parameter. To emulate that
        // here, we use readlink() to read the links contents, and then if it is
        // possible that the link contents didn't fit in buffer, we'll fall back
        // to lstat() to get the full length of the link.
        if (static_cast<size_t>(bytesRead) < *_bufferSize) {
                buffer[bytesRead] = '\0';
                *_bufferSize = bytesRead;
        } else {
                // The number of bytes copied by readlink() tells us that the entire
                // link might not have fit into buffer. Fall back to getting the full
                // length of the link using lstat.
                struct stat linkStat;
                if (lstat(realPath.c_str(), &linkStat) != 0)
                        return errno;

                *_bufferSize = linkStat.st_size;
        }

        return B_OK;
}

// _kern_unlink
status_t
_kern_unlink(int fd, const char *path)
{
        // get a usable path
        string realPath;
        status_t error = get_path(fd, path, realPath);
        if (error != B_OK)
                return error;

        // unlink
        if (unlink(realPath.c_str()) < 0)
                return errno;

        return B_OK;
}

// _kern_rename
status_t
_kern_rename(int oldDir, const char *oldPath, int newDir, const char *newPath)
{
        // get usable paths
        string realOldPath;
        status_t error = get_path(oldDir, oldPath, realOldPath);
        if (error != B_OK)
                return error;

        string realNewPath;
        error = get_path(newDir, newPath, realNewPath);
        if (error != B_OK)
                return error;

        // rename
        if (rename(realOldPath.c_str(), realNewPath.c_str()) < 0)
                return errno;

        return B_OK;
}


// #pragma mark -

// _kern_lock_node
status_t
_kern_lock_node(int fd)
{
        return B_ERROR;
}

// _kern_unlock_node
status_t
_kern_unlock_node(int fd)
{
        return B_ERROR;
}


// #pragma mark -


#if !defined(HAIKU_HOST_PLATFORM_HAIKU)
ssize_t
read_pos(int fd, off_t pos, void *buffer, size_t bufferSize)
{
        // seek
        off_t result = lseek(fd, pos, SEEK_SET);
        if (result < 0)
                return errno;

        // read
        ssize_t bytesRead = haiku_host_platform_read(fd, buffer, bufferSize);
        if (bytesRead < 0) {
                errno = bytesRead;
                return -1;
        }

        return bytesRead;
}

ssize_t
write_pos(int fd, off_t pos, const void *buffer, size_t bufferSize)
{
        // If this is an attribute descriptor, let it do the job.
        AttributeDescriptor* descriptor
                = dynamic_cast<AttributeDescriptor*>(get_descriptor(fd));
        if (descriptor != NULL) {
                status_t error = descriptor->Write(pos, buffer, bufferSize);
                if (error != B_OK) {
                        errno = error;
                        return -1;
                }

                return bufferSize;
        }

        // seek
        off_t result = lseek(fd, pos, SEEK_SET);
        if (result < 0)
                return errno;

        // write
        ssize_t bytesWritten = haiku_host_platform_write(fd, buffer, bufferSize);
        if (bytesWritten < 0) {
                errno = bytesWritten;
                return -1;
        }

        return bytesWritten;
}
#endif


ssize_t
_kern_readv(int fd, off_t pos, const struct iovec *vec, size_t count)
{
        // seek
        off_t result = lseek(fd, pos, SEEK_SET);
        if (result < 0)
                return errno;

        // read
        return haiku_host_platform_readv(fd, vec, count);
}


ssize_t
_kern_writev(int fd, off_t pos, const struct iovec *vec, size_t count)
{
        // seek
        off_t result = lseek(fd, pos, SEEK_SET);
        if (result < 0)
                return errno;

        // write
        return haiku_host_platform_writev(fd, vec, count);
}


// #pragma mark -


int
_haiku_build_fchmod(int fd, mode_t mode)
{
        return _haiku_build_fchmodat(fd, NULL, mode, AT_SYMLINK_NOFOLLOW);
}


int
_haiku_build_fchmodat(int fd, const char* path, mode_t mode, int flag)
{
        if (fd >= 0 && fd != AT_FDCWD && get_descriptor(fd) == NULL)
                return fchmodat(fd, path, mode, flag);

        struct stat st;
        st.st_mode = mode;

        RETURN_AND_SET_ERRNO(_kern_write_stat(fd, path,
                (flag & AT_SYMLINK_NOFOLLOW) == 0, &st, sizeof(st), B_STAT_MODE));
}


int
_haiku_build_fstat(int fd, struct stat* st)
{
        return _haiku_build_fstatat(fd, NULL, st, AT_SYMLINK_NOFOLLOW);
}


int
_haiku_build_fstatat(int fd, const char* path, struct stat* st, int flag)
{
        if (fd >= 0 && fd != AT_FDCWD && get_descriptor(fd) == NULL)
                return fstatat(fd, path, st, flag);

        RETURN_AND_SET_ERRNO(_kern_read_stat(fd, path,
                (flag & AT_SYMLINK_NOFOLLOW) == 0, st, sizeof(*st)));
}


int
_haiku_build_mkdirat(int fd, const char* path, mode_t mode)
{
        if (fd >= 0 && fd != AT_FDCWD && get_descriptor(fd) == NULL)
                return mkdirat(fd, path, mode);

        RETURN_AND_SET_ERRNO(_kern_create_dir(fd, path, mode));
}


int
_haiku_build_mkfifoat(int fd, const char* path, mode_t mode)
{
        return mkfifoat(fd, path, mode);

        // TODO: Handle non-system FDs.
}


int
_haiku_build_utimensat(int fd, const char* path, const struct timespec times[2],
        int flag)
{
        if (fd >= 0 && fd != AT_FDCWD && get_descriptor(fd) == NULL)
                return utimensat(fd, path, times, flag);

        struct stat stat;
        status_t status;
        uint32 mask = 0;

        // Init the stat time fields to the current time, if at least one time is
        // supposed to be set to it.
        if (times == NULL || times[0].tv_nsec == UTIME_NOW
                || times[1].tv_nsec == UTIME_NOW) {
                timeval now;
                gettimeofday(&now, NULL);
                HAIKU_HOST_STAT_ATIM(stat).tv_sec
                        = HAIKU_HOST_STAT_MTIM(stat).tv_sec = now.tv_sec;
                HAIKU_HOST_STAT_ATIM(stat).tv_nsec
                        = HAIKU_HOST_STAT_MTIM(stat).tv_nsec = now.tv_usec * 1000;
        }

        if (times != NULL) {
                // access time
                if (times[0].tv_nsec != UTIME_OMIT) {
                        mask |= B_STAT_ACCESS_TIME;

                        if (times[0].tv_nsec != UTIME_NOW) {
                                if (times[0].tv_nsec < 0 || times[0].tv_nsec > 999999999)
                                        RETURN_AND_SET_ERRNO(EINVAL);
                        }

                        HAIKU_HOST_STAT_ATIM(stat) = times[0];
                }

                // modified time
                if (times[1].tv_nsec != UTIME_OMIT) {
                        mask |= B_STAT_MODIFICATION_TIME;

                        if (times[1].tv_nsec != UTIME_NOW) {
                                if (times[1].tv_nsec < 0 || times[1].tv_nsec > 999999999)
                                        RETURN_AND_SET_ERRNO(EINVAL);
                        }

                        HAIKU_HOST_STAT_MTIM(stat) = times[1];
                }
        } else
                mask |= B_STAT_ACCESS_TIME | B_STAT_MODIFICATION_TIME;

        // set the times -- as per spec we even need to do this, if both have
        // UTIME_OMIT set
        status = _kern_write_stat(fd, path, (flag & AT_SYMLINK_NOFOLLOW) == 0,
                &stat, sizeof(struct stat), mask);

        RETURN_AND_SET_ERRNO(status);
}


int
_haiku_build_futimens(int fd, const struct timespec times[2])
{
        return _haiku_build_utimensat(fd, NULL, times, AT_SYMLINK_NOFOLLOW);
}


int
_haiku_build_faccessat(int fd, const char* path, int accessMode, int flag)
{
        if (fd >= 0 && fd != AT_FDCWD && get_descriptor(fd) == NULL)
                return faccessat(fd, path, accessMode, flag);

        // stat the file
        struct stat st;
        status_t error = _kern_read_stat(fd, path, false, &st, sizeof(st));
        if (error != B_OK)
                RETURN_AND_SET_ERRNO(error);

        // get the current user
        uid_t uid = (flag & AT_EACCESS) != 0 ? geteuid() : getuid();

        int fileMode = 0;

        if (uid == 0) {
                // user is root
                // root has always read/write permission, but at least one of the
                // X bits must be set for execute permission
                fileMode = R_OK | W_OK;
                if ((st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0)
                        fileMode |= X_OK;
        } else if (st.st_uid == uid) {
                // user is node owner
                if ((st.st_mode & S_IRUSR) != 0)
                        fileMode |= R_OK;
                if ((st.st_mode & S_IWUSR) != 0)
                        fileMode |= W_OK;
                if ((st.st_mode & S_IXUSR) != 0)
                        fileMode |= X_OK;
        } else if (st.st_gid == ((flag & AT_EACCESS) != 0 ? getegid() : getgid())) {
                // user is in owning group
                if ((st.st_mode & S_IRGRP) != 0)
                        fileMode |= R_OK;
                if ((st.st_mode & S_IWGRP) != 0)
                        fileMode |= W_OK;
                if ((st.st_mode & S_IXGRP) != 0)
                        fileMode |= X_OK;
        } else {
                // user is one of the others
                if ((st.st_mode & S_IROTH) != 0)
                        fileMode |= R_OK;
                if ((st.st_mode & S_IWOTH) != 0)
                        fileMode |= W_OK;
                if ((st.st_mode & S_IXOTH) != 0)
                        fileMode |= X_OK;
        }

        if ((accessMode & ~fileMode) != 0)
                RETURN_AND_SET_ERRNO(EACCES);

        return 0;
}


int
_haiku_build_fchdir(int fd)
{
        if (is_unknown_or_system_descriptor(fd))
                return fchdir(fd);

        RETURN_AND_SET_ERRNO(B_FILE_ERROR);
}


int
_haiku_build_close(int fd)
{
        if (get_descriptor(fd) == NULL)
                return close(fd);

        RETURN_AND_SET_ERRNO(_kern_close(fd));
}


int
_haiku_build_dup(int fd)
{
        if (get_descriptor(fd) == NULL)
                return close(fd);

        RETURN_AND_SET_ERRNO(_kern_dup(fd));
}


int
_haiku_build_dup2(int fd1, int fd2)
{
        if (is_unknown_or_system_descriptor(fd1))
                return dup2(fd1, fd2);

        // TODO: Handle non-system FDs.
        RETURN_AND_SET_ERRNO(B_NOT_SUPPORTED);
}


int
_haiku_build_linkat(int toFD, const char* toPath, int pathFD, const char* path,
        int flag)
{
        return linkat(toFD, toPath, pathFD, path, flag);

        // TODO: Handle non-system FDs.
}


int
_haiku_build_unlinkat(int fd, const char* path, int flag)
{
        if (fd >= 0 && fd != AT_FDCWD && get_descriptor(fd) == NULL)
                return unlinkat(fd, path, flag);

        RETURN_AND_SET_ERRNO(_kern_unlink(fd, path));
}


ssize_t
_haiku_build_readlinkat(int fd, const char* path, char* buffer,
        size_t bufferSize)
{
        if (fd >= 0 && fd != AT_FDCWD && get_descriptor(fd) == NULL)
                return readlinkat(fd, path, buffer, bufferSize);

        status_t error = _kern_read_link(fd, path, buffer, &bufferSize);
        if (error != B_OK)
                RETURN_AND_SET_ERRNO(error);

        return bufferSize;
}


int
_haiku_build_symlinkat(const char* toPath, int fd, const char* symlinkPath)
{
        if (fd >= 0 && fd != AT_FDCWD && get_descriptor(fd) == NULL)
                return symlinkat(toPath, fd, symlinkPath);

        RETURN_AND_SET_ERRNO(_kern_create_symlink(fd, symlinkPath, toPath,
                S_IRWXU | S_IRWXG | S_IRWXO));
}


int
_haiku_build_ftruncate(int fd, off_t newSize)
{
        if (fd >= 0 && fd != AT_FDCWD && get_descriptor(fd) == NULL)
                return ftruncate(fd, newSize);

        struct stat st;
        st.st_size = newSize;

        RETURN_AND_SET_ERRNO(_kern_write_stat(fd, NULL, false, &st, sizeof(st),
                B_STAT_SIZE));
}


int
_haiku_build_fchown(int fd, uid_t owner, gid_t group)
{
        return _haiku_build_fchownat(fd, NULL, owner, group, AT_SYMLINK_NOFOLLOW);
}


int
_haiku_build_fchownat(int fd, const char* path, uid_t owner, gid_t group,
        int flag)
{
        if (fd >= 0 && fd != AT_FDCWD && get_descriptor(fd) == NULL)
                return fchownat(fd, path, owner, group, flag);

        struct stat st;
        st.st_uid = owner;
        st.st_gid = group;

        RETURN_AND_SET_ERRNO(_kern_write_stat(fd, path,
                (flag & AT_SYMLINK_NOFOLLOW) == 0, &st, sizeof(st),
                B_STAT_UID | B_STAT_GID));
}


int
_haiku_build_mknodat(int fd, const char* name, mode_t mode, dev_t dev)
{
        return mknodat(fd, name, mode, dev);

        // TODO: Handle non-system FDs.
}


int
_haiku_build_creat(const char* path, mode_t mode)
{
        return _haiku_build_open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
}


int
_haiku_build_open(const char* path, int openMode, ...)
{
        mode_t permissions = 0;
        if ((openMode & O_CREAT) != 0) {
                va_list args;
                va_start(args, openMode);
                permissions = va_arg(args, int);
                va_end(args);
        }
        return _haiku_build_openat(AT_FDCWD, path, openMode, permissions);
}


int
_haiku_build_openat(int fd, const char* path, int openMode, ...)
{
        mode_t permissions = 0;
        if ((openMode & O_CREAT) != 0) {
                va_list args;
                va_start(args, openMode);
                permissions = va_arg(args, int);
                va_end(args);
        }

        // adapt the permissions as required by POSIX
        mode_t mask = umask(0);
        umask(mask);
        permissions &= ~mask;

        RETURN_AND_SET_ERRNO(_kern_open(fd, path, openMode, permissions));
}


int
_haiku_build_fcntl(int fd, int op, ...)
{
        va_list args;
        va_start(args, op);
        int argument = va_arg(args, int);
        va_end(args);

        if (is_unknown_or_system_descriptor(fd))
                return fcntl(fd, op, argument);

        RETURN_AND_SET_ERRNO(B_NOT_SUPPORTED);
}


int
_haiku_build_renameat(int fromFD, const char* from, int toFD, const char* to)
{
        if ((fromFD >= 0 && fromFD != AT_FDCWD && get_descriptor(fromFD) == NULL)
                || (toFD >= 0 && toFD != AT_FDCWD && get_descriptor(toFD) == NULL)) {
                return renameat(fromFD, from, toFD, to);
        }

        RETURN_AND_SET_ERRNO(_kern_rename(fromFD, from, toFD, to));
}